def process_comment_payload_from_gh(payload): # =================================================== ПОДГОТОВКА =================================================== def parse_payload(payload): payload_parsed = {} # словарь issue (название, описание, ссылка) # действие и его автор payload_parsed['action'] = payload['action'] payload_parsed['sender_id'] = payload['sender']['id'] # sender - тот, кто совершил действие payload_parsed['sender_login'] = payload['sender']['login'] # заполение полей issue payload_parsed['issue_title'] = payload['issue']['title'] payload_parsed['issue_body'] = payload['issue']['body'] payload_parsed['issue_author_id'] = payload['issue']['user']['id'] payload_parsed['issue_author_login'] = payload['issue']['user']['login'] # идентификаторы (для связи и логов) payload_parsed['issue_id'] = payload['issue']['id'] payload_parsed['repos_id'] = payload['repository']['id'] payload_parsed['issue_number'] = payload['issue']['number'] # ссылка на issue (для фразы бота и логов) payload_parsed['issue_url'] = payload['issue']['html_url'] # комментарий payload_parsed['comment_body'] = payload['comment']['body'] payload_parsed['comment_id'] = payload['comment']['id'] payload_parsed['comment_author_id'] = payload['comment']['user']['id'] payload_parsed['comment_author_login'] = payload['comment']['user']['login'] return payload_parsed try: issue = parse_payload(payload) except: error_text = 'ERROR: unknown payload type' WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'received webhook from GITHUB: issues comments' + '\n' + error_text) return HttpResponse(error_text, status=200) # авторизация в redmine по токену api_key_redmime = read_file('api_keys/api_key_redmime.txt') # загрузка ключа для redmine api api_key_redmime = api_key_redmime.replace('\n', '') # избавляемся от \n в конце строки # загрузка template из файла issue_redmine_template = read_file('data/issue_redmine_template.json') issue_redmine_template = Template(issue_redmine_template) # шаблон для каждого issue # заголовки авторизации и приложения, при отправке запросов на редмайн headers = {'X-Redmine-API-Key': api_key_redmime, 'Content-Type': 'application/json'} # ============================================ ВСПОМОГАТЕЛЬНЫЕ КОМАНДЫ ============================================= # issue_body # comment_body # comment_edit # comment_edit_else's # добавляем фразу бота, со ссылкой на аккаунт пользователя в гитхабе def add_bot_phrase(issue, to): # добавляем фразу бота к описанию issue if (to == 'issue_body'): author_url_gh = '"' + issue['issue_author_login'] + '":' + 'https://github.com/' + issue['issue_author_login'] issue_url_gh = '"issue":' + issue['issue_url'] issue_body = '>I am a bot, bleep-bloop.\n' +\ '>' + author_url_gh + ' Has opened the ' + issue_url_gh + ' in Github.\n' +\ issue['issue_body'] return issue_body # добавляем фразу бота к комментарию elif (to == 'comment_body'): # добавляем фразу бота author_url = '"' + issue['comment_author_login'] + '":' + 'https://github.com/' + issue['comment_author_login'] comment_url = '"comment":' + issue['issue_url'] + '#issuecomment-' + str(issue['comment_id']) comment_body = '>I am a bot, bleep-bloop.\n' +\ '>' + author_url + ' Has left a ' + comment_url + ' in Github.\n' +\ issue['comment_body'] return comment_body # добавляем фразу бота к комментарию (изменение своего комментария) elif (to == 'comment_edit'): # добавляем фразу бота author_url = '"' + issue['comment_author_login'] + '":' + 'https://github.com/' + issue['comment_author_login'] comment_url = '"comment":' + issue['issue_url'] + '#issuecomment-' + str(issue['comment_id']) comment_body = '>I am a bot, bleep-bloop.\n' +\ '>' + author_url + ' Has edited his ' + comment_url + ' in Github.\n' +\ issue['comment_body'] return comment_body # добавляем фразу бота к комментарию (изменение чужого комментария) elif (to == "comment_edit_else's"): # добавляем фразу бота sender_url = '"' + issue['sender_login'] + '":' + 'https://github.com/' + issue['sender_login'] author_url = '"' + issue['comment_author_login'] + '":' + 'https://github.com/' + issue['comment_author_login'] comment_url = '"comment":' + issue['issue_url'] + '#issuecomment-' + str(issue['comment_id']) comment_body = '>I am a bot, bleep-bloop.\n' +\ '>' + sender_url + ' Has edited ' + author_url + " 's " + comment_url + ' in Github.\n' +\ issue['comment_body'] return comment_body else: WRITE_LOG("\nERROR: process_comment_payload_from_gh.add_bot_phrase - unknown parameter 'to': " + to + '.' + "\nPlease, check the code on possible typos." + "\nAlternatively, add logic to process '" + to + "' action correctly.\n") return None # типичные ошибки на этапе проверки: не связаны проекты, задачи, комментарии, неизвестное действие и т.п. def PREPARATION_ERR(error_text): # добавляем, чтобы в начале получилось: 'PREPARATION ERROR' error_text = 'PREPARATION ' + error_text WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'received webhook from GITHUB: issues_comments | ' + 'action: ' + str(issue['action']) + '\n' + error_text) return HttpResponse(error_text, status=200) # логическая ошибка: неизвестное действие, неправильные label-ы в гитхабе и т.п. def LOGICAL_ERR(error_text): # добавляем, чтобы в начале получилось: 'LOGICAL ERROR' error_text = 'LOGICAL ' + error_text WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'received webhook from GITHUB: issues_comments | ' + 'action: ' + str(issue['action']) + '\n' + error_text) return HttpResponse(error_text, status=200) # ============================================= КОМАНДЫ ДЛЯ ЗАГРУЗКИ =============================================== def post_comment(linked_projects, issue): # ----------------------------------------------- ПОДГОТОВКА ---------------------------------------------- # дополнительная проверка, что проекты связаны if (linked_projects.count() == 0): error_text = "ERROR: process_comment_payload_from_gh.post_comment\n" +\ "comment " + str(issue['action']) +" in GITHUB, but the project is not linked to REDMINE" return PREPARATION_ERR(error_text) linked_projects = linked_projects[0] project_id_rm = linked_projects.project_id_rm linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id']) # дополнительная проверка, что issue связаны if (linked_issues.count() == 0): error_text = "ERROR: process_comment_payload_from_gh.post_comment\n" +\ "comment " + str(issue['action']) + " in GITHUB, but the issue is not linked to REDMINE" return PREPARATION_ERR(error_text) linked_issues = linked_issues[0] # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА ----------------------------------------- # проверяем, если автор issue - бот if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])): issue_body = del_bot_phrase(issue['issue_body']) else: issue_body = add_bot_phrase(issue, 'issue_body') comment_body = add_bot_phrase(issue, 'comment_body') # добавляем фразу бота # обработка спец. символов issue_title = align_special_symbols(issue['issue_title']) issue_body = align_special_symbols(issue_body) comment_body = align_special_symbols(comment_body) # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ---------------------------------------- issue_templated = issue_redmine_template.render( project_id=project_id_rm, issue_id=linked_issues.issue_id_rm, priority_id=linked_issues.priority_id_rm, subject=issue_title, description=issue_body, notes=comment_body) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') issue_url_rm = url_rm.replace('.json', '/' + str(linked_issues.issue_id_rm) + '.json') request_result = requests.put(issue_url_rm, data=issue_templated, headers=headers) # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ -------------------------------------------- # (делаем привязку комментариев после получения веб-хука от редмайна) # ДЕБАГГИНГ log_comment_gh(request_result, issue, linked_issues, linked_projects.project_id_rm) return request_result # изменение комментария (постим новый, пишем 'edited comment') def edit_comment(linked_projects, issue): # ----------------------------------------------- ПОДГОТОВКА ---------------------------------------------- # дополнительная проверка, что проекты связаны if (linked_projects.count() == 0): error_text = "ERROR: process_comment_payload_from_gh.edit_comment\n" +\ "comment " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE" return PREPARATION_ERR(error_text) linked_projects = linked_projects[0] project_id_rm = linked_projects.project_id_rm linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id']) # дополнительная проверка, что issue связаны if (linked_issues.count() == 0): error_text = "ERROR: process_comment_payload_from_gh.edit_comment\n" +\ "comment " + str(issue['action']) + " in GITHUB, but the issue is not linked to REDMINE" return PREPARATION_ERR(error_text) linked_issues = linked_issues[0] linked_comments = linked_issues.get_comment_by_id_gh(issue['comment_id']) # дополнительная проверка, что комментарии связаны if (linked_comments.count() == 0): error_text = "ERROR: process_comment_payload_from_gh.edit_comment\n" +\ "comment " + str(issue['action']) + " in GITHUB, but it's not linked to REDMINE" return PREPARATION_ERR(error_text) linked_comments = linked_comments[0] # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА ----------------------------------------- # проверяем, если автор issue - бот if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])): issue_body = del_bot_phrase(issue['issue_body']) else: issue_body = add_bot_phrase(issue, 'issue_body') # если изменил свой комментарий if (issue['sender_id'] == issue['comment_author_id']): comment_body = add_bot_phrase(issue, 'comment_edit') # добавляем фразу бота # если изменил не свой комментарий else: comment_body = add_bot_phrase(issue, "comment_edit_else's") # добавляем фразу бота # обработка спец. символов issue_title = align_special_symbols(issue['issue_title']) issue_body = align_special_symbols(issue_body) comment_body = align_special_symbols(comment_body) # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ---------------------------------------- issue_templated = issue_redmine_template.render( project_id=project_id_rm, issue_id=linked_issues.issue_id_rm, priority_id=linked_issues.priority_id_rm, subject=issue_title, description=issue_body, notes=comment_body) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') issue_url_rm = url_rm.replace('.json', '/' + str(linked_issues.issue_id_rm) + '.json') request_result = requests.put(issue_url_rm, data=issue_templated, headers=headers) # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ -------------------------------------------- # (делаем привязку комментариев после получения веб-хука от редмайна) # ДЕБАГГИНГ log_comment_gh(request_result, issue, linked_issues, linked_projects.project_id_rm) return request_result ''' # изменение комментария (не постим новый, а изменяем старый) # СТАРЫЙ КОД: перед использованием - привести в порядок (как edit_comment выше) def edit_comment(issue, linked_issues): # дополнительная проверка, что issue связаны # (на случай, если изменили не связанный issue) if (linked_issues.count() == 0): WRITE_LOG('\n' + '='*35 + ' ' + str(datetime.datetime.today()) + ' ' + '='*35 + '\n' + 'received webhook from GITHUB: issue_comments | ' + 'action: ' + str(issue['action']) + '\n' + "ERROR: posted comment in GITHUB, but the issue is not linked to REDMINE") return HttpResponse(status=200) linked_issues = linked_issues[0] # дополнительная проверка, что комментарии связаны linked_comments = linked_issues.get_comment_by_id_gh(issue['comment_id']) if (linked_comments.count() == 0): WRITE_LOG('\n' + '='*35 + ' ' + str(datetime.datetime.today()) + ' ' + '='*35 + '\n' + 'received webhook from GITHUB: issue_comments | ' + 'action: ' + str(issue['action']) + '\n' + "ERROR: edited comment in GITHUB, but it is not linked to REDMINE") return HttpResponse(status=200) linked_comments = linked_comments[0] # ------------------------------------------- ОБРАБАТЫВАЕМ ФРАЗУ БОТА ------------------------------------------ # проверяем, если автор issue - бот if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])): issue_body = del_bot_phrase(issue['issue_body']) else: issue_body = add_bot_phrase(issue) comment_body = bot_speech_comment(issue) # добавляем фразу бота # обработка спец. символов issue_title = align_special_symbols(issue['issue_title']) issue_body = align_special_symbols(issue_body) comment_body = align_special_symbols(comment_body) # ----------------------------------------- ЗАГРУЖАЕМ ДАННЫЕ В РЕДМАЙН ----------------------------------------- issue_templated = issue_redmine_template.render( project_id=project_id_rm, issue_id=linked_issues.issue_id_rm, priority_id=priority_ids_rm[0], subject=issue_title, description=issue_body, notes=comment_body) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') issue_url_rm = url_rm.replace('.json', '/' + str(linked_issues.issue_id_rm) + '.json') request_result = requests.put(issue_url_rm, data=issue_templated, headers=headers) # ------------------------------------------- ПРИВЯЗКА КОММЕНТАРИЕВ -------------------------------------------- # (делаем привязку после получения веб-хука от редмайна) # ДЕБАГГИНГ log_comment_gh(request_result, issue, linked_issues) return request_result''' # ========================================= ЗАГРУЗКА КОММЕНТАРИЯ В REDMINE ========================================= linked_projects = Linked_Projects.objects.get_project_by_id_gh(issue['repos_id']) if (issue['action'] == 'created'): if (chk_if_gh_user_is_our_bot(issue['sender_id'])): error_text = prevent_cyclic_comment_gh(issue) return HttpResponse(error_text, status=200) request_result = post_comment(linked_projects, issue) elif (issue['action'] == 'edited'): if (chk_if_gh_user_is_our_bot(issue['sender_id'])): error_text = prevent_cyclic_comment_gh(issue) return HttpResponse(error_text, status=200) request_result = edit_comment(linked_projects, issue) else: error_text = "ERROR: process_comment_payload_from_gh\n" + \ "WRONG ACTION" return LOGICAL_ERR(error_text) return align_request_result(request_result)
def query_data_gh_to_rm(linked_projects): # =================================================== ПОДГОТОВКА =================================================== # авторизация в redmine по токену api_key_redmime = read_file('api_keys/api_key_redmime.txt') # загрузка ключа для redmine api api_key_redmime = api_key_redmime.replace('\n', '') # избавляемся от \n в конце строки # загрузка template из файла issue_redmine_template = read_file('data/issue_redmine_template.json') issue_redmine_template = Template(issue_redmine_template) # шаблон для каждого issue в редмайне # заголовки авторизации и приложения, при отправке запросов на редмайн headers_rm = {'X-Redmine-API-Key': api_key_redmime, 'Content-Type': 'application/json'} # загрузка issue template из файла issue_github_template = read_file('data/issue_github_template.json') issue_github_template = Template(issue_github_template) # шаблон для каждого issue # авторизация в гитхабе по токену api_key_github = read_file('api_keys/api_key_github.txt') # загрузка ключа для github api api_key_github = api_key_github.replace('\n', '') # избавляемся от \n в конце строки # заголовки авторизации и приложения, при отправке запросов на гитхаб headers_gh = {'Authorization': 'token ' + api_key_github, 'Content-Type': 'application/json'} # ============================================ ВСПОМОГАТЕЛЬНЫЕ КОМАНДЫ ============================================= # добавляем пробелы, чтобы отделить от лога проектов и задач (как табуляция) # лог связи комментариев def log_link_comments_start(): if (not allow_log_project_linking): return 0 WRITE_LOG_GRN('\n ' + '=' * 33 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 33 + '\n' + " LINKING COMMENTS IN PROGRESS" + '\n') def log_link_comments_finish(): if (not allow_log_project_linking): return 0 WRITE_LOG_GRN(" LINKING COMMENTS FINISHED" + '\n' + ' ' + '=' * 33 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 33 + '\n') # лог связи задач def log_link_issues_start(): if (not allow_log_project_linking): return 0 WRITE_LOG_GRN('\n ' + '=' * 34 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 34 + '\n' + " LINKING ISSUES IN PROGRESS" + '\n') def log_link_issues_finish(): if (not allow_log_project_linking): return 0 WRITE_LOG_GRN(" LINKING ISSUES FINISHED" + '\n' + ' ' + '=' * 34 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 34 + '\n') def log_issue_gh(result, issue, linked_issues): if (not allow_log_project_linking): return 0 if (not detailed_log_project_linking): return 0 action_rm = 'POST' WRITE_LOG('\n ' + '-' * 34 + ' ' + str(datetime.datetime.today()) + ' ' + '-' * 34 + '\n' + ' linking issue from GITHUB to REDMINE' + '\n' + ' ' + action_rm + ' result in REDMINE: ' + str(result.status_code) + ' ' + str(result.reason) + '\n' + ' GITHUB | ---------------- issue ----------------' + '\n' + ' | author_login: '******'issue_author_login']) + '\n' + ' | issue_url: ' + issue['issue_url'] + '\n' + ' | issue_id: ' + str(issue['issue_id']) + '\n' + ' |\n' + ' REDMINE | ---------------- issue ----------------' + '\n' + ' | issue_id: ' + str(linked_issues.issue_id_rm)) def log_comment_gh(result, comment, linked_issues): if (not allow_log_project_linking): return 0 if (not detailed_log_project_linking): return 0 action_rm = 'EDIT' WRITE_LOG('\n ' + '-' * 33 + ' ' + str(datetime.datetime.today()) + ' ' + '-' * 33 + '\n' + ' linking comment from GITHUB to REDMINE' + '\n' + ' ' + action_rm + ' result in REDMINE: ' + str(result.status_code) + ' ' + str(result.reason) + '\n' + ' GITHUB | --------------- comment ---------------' + '\n' + ' | author_login: '******'comment_author_login']) + '\n' + ' | comment_id: ' + str(comment['comment_id']) + '\n' + ' |\n' + ' REDMINE | ---------------- issue ----------------' + '\n' + ' | issue_id: ' + str(linked_issues.issue_id_rm)) def log_issue_gh_already_linked(linked_issues, issue): if (not allow_log_project_linking): return 0 if (not detailed_log_project_linking): return 0 WRITE_LOG('\n ' + '-' * 34 + ' ' + str(datetime.datetime.today()) + ' ' + '-' * 34 + '\n' + ' linking issue from GITHUB to REDMINE' + '\n' + ' WARNING: issues are already linked!' + '\n' + ' GITHUB | ---------------- issue ----------------' + '\n' + ' | author_login: '******'issue_author_login']) + '\n' + ' | issue_url: ' + issue['issue_url'] + '\n' + ' | issue_id: ' + str(issue['issue_id']) + '\n' + ' |\n' + ' REDMINE | ---------------- issue ----------------' + '\n' + ' | issue_id: ' + str(linked_issues.issue_id_rm)) def log_comment_gh_already_linked(linked_comments): if (not allow_log_project_linking): return 0 if (not detailed_log_project_linking): return 0 WRITE_LOG('\n ' + '-' * 33 + ' ' + str(datetime.datetime.today()) + ' ' + '-' * 33 + '\n' + ' linking comment from GITHUB to REDMINE\n' + ' WARNING: comments are already linked\n' + ' GITHUB | --------------- comment ---------------' + '\n' + ' | comment_id: ' + str(linked_comments.comment_id_gh) + '\n' + ' |\n' + ' REDMINE | --------------- comment ---------------' + '\n' + ' | comment_id: ' + str(linked_comments.comment_id_rm) + '\n') #def log_correct_gh_labels(issue, tracker, linked_issues): # issue_body # comment_body # добавляем фразу бота, со ссылкой на аккаунт пользователя в гитхабе def add_bot_phrase(issue, to): # добавляем фразу бота к описанию issue if (to == 'issue_body'): # добавляем фразу бота author_url_gh = '"' + issue['issue_author_login'] + '":' + 'https://github.com/' + issue['issue_author_login'] issue_url_gh = '"issue":' + issue['issue_url'] issue_body = '>I am a bot, bleep-bloop.\n' +\ '>' + author_url_gh + ' Has opened the ' + issue_url_gh + ' in Github.\n' +\ issue['issue_body'] return issue_body # добавляем фразу бота к комментарию elif (to == 'comment_body'): # добавляем фразу бота author_url = '"' + issue['comment_author_login'] + '":' + 'https://github.com/' + issue['comment_author_login'] comment_url = '"comment":' + issue['issue_url'] + '#issuecomment-' + str(issue['comment_id']) comment_body = '>I am a bot, bleep-bloop.\n' +\ '>' + author_url + ' Has left a ' + comment_url + ' in Github.\n' +\ issue['comment_body'] return comment_body else: WRITE_LOG("\nERROR: 'process_payload_from_gh.add_bot_phrase'\n" + "unknown parameter 'to': " + to + '.\n' + "Please, check your code on possible typos.\n" + "Alternatively, add logic to process '" + to + "' parameter correctly.") return None # исправление label-ов в гитхабе def correct_gh_labels(issue, tracker, linked_issues): if (not allow_correct_github_labels): return 0 # добавление label-ов priority = match_priority_to_gh(linked_issues.priority_id_rm) status = match_status_to_gh(linked_issues.status_id_rm) # ---------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ---------------------------------------- issue_title = align_special_symbols(issue['issue_title']) issue_body = align_special_symbols(issue['issue_body']) issue_templated = issue_github_template.render( title=issue_title, body=issue_body, priority=priority, status=status, tracker=tracker) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') url_gh = make_gh_repos_url(linked_issues.repos_id_gh) # добавление issue_id к ссылке issue_url_gh = url_gh + '/' + str(linked_issues.issue_num_gh) request_result = requests.patch(issue_url_gh, data=issue_templated, headers=headers_gh) return request_result # парсинг комментария def parse_comment(issue, comment): comment_parsed = {} # словарь issue (название, описание, ссылка) # заполение полей issue comment_parsed['issue_title'] = issue['issue_title'] comment_parsed['issue_body'] = issue['issue_body'] comment_parsed['issue_author_id'] = issue['issue_author_id'] # автор issue comment_parsed['issue_author_login'] = issue['issue_author_login'] # идентификаторы (для связи и логов) comment_parsed['issue_id'] = issue['issue_id'] comment_parsed['repos_id'] = issue['repos_id'] comment_parsed['issue_number'] = issue['issue_number'] # ссылка на issue (для фразы бота и логов) comment_parsed['issue_url'] = issue['issue_url'] # комментарий comment_parsed['comment_body'] = comment['body'] comment_parsed['comment_id'] = comment['id'] comment_parsed['comment_author_id'] = comment['user']['id'] comment_parsed['comment_author_login'] = comment['user']['login'] return comment_parsed # парсинг задачи def parse_issue(issue): issue_parsed = {} # словарь issue (название, описание, ссылка) # заполение полей issue issue_parsed['issue_title'] = issue['title'] issue_parsed['issue_body'] = issue['body'] issue_parsed['issue_author_id'] = issue['user']['id'] issue_parsed['issue_author_login'] = issue['user']['login'] # идентификаторы (для связи и логов) issue_parsed['issue_id'] = issue['id'] issue_parsed['repos_id'] = linked_projects.repos_id_gh issue_parsed['issue_number'] = issue['number'] # ссылка на issue (для фразы бота и логов) issue_parsed['issue_url'] = issue['html_url'] issue_parsed['labels'] = issue['labels'] issue_parsed['action'] = 'opened' issue_parsed['state'] = issue['state'] return issue_parsed def prevent_cyclic_comment_gh(issue): error_text = ' The user, who left the comment: ' + issue['issue_author_login'] + ' | user id: ' + str(issue['issue_author_id']) + ' (our bot)\n' + \ ' Aborting action, in order to prevent cyclic: GH -> S -> RM -> S -> GH -> ...' if (allow_log_cyclic): WRITE_LOG(error_text) return error_text def chk_if_issues_are_linked(issue_id_gh): linked_issues = Linked_Issues.objects.get_issue_by_id_gh(issue_id_gh) if (len(linked_issues) == 0): return False else: return True def chk_if_comments_are_linked(comment_id_gh): linked_comments = Linked_Comments.objects.get_by_comment_id_gh(comment_id_gh) if (len(linked_comments) == 0): return False else: return True # ============================================= КОМАНДЫ ДЛЯ ЗАГРУЗКИ =============================================== def post_comment(linked_projects, linked_issues, comment): # ----------------------------------------------- ПОДГОТОВКА ---------------------------------------------- # проверяем, если автор комментария - наш бот if (chk_if_gh_user_is_our_bot(comment['comment_author_id'])): error_text = prevent_cyclic_comment_gh(comment) return HttpResponse(error_text, status=200) project_id_rm = linked_projects.project_id_rm # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА ----------------------------------------- # проверяем, если автор issue - наш бот if (chk_if_gh_user_is_our_bot(comment['issue_author_id'])): issue_body = del_bot_phrase(comment['issue_body']) else: issue_body = add_bot_phrase(comment, 'issue_body') comment_body = add_bot_phrase(comment, 'comment_body') # добавляем фразу бота # обработка спец. символов issue_title = align_special_symbols(comment['issue_title']) issue_body = align_special_symbols(issue_body) comment_body = align_special_symbols(comment_body) # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ---------------------------------------- issue_templated = issue_redmine_template.render( project_id=project_id_rm, issue_id=linked_issues.issue_id_rm, priority_id=linked_issues.priority_id_rm, subject=issue_title, description=issue_body, notes=comment_body) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') issue_url_rm = url_rm.replace('.json', '/' + str(linked_issues.issue_id_rm) + '.json') request_result = requests.put(issue_url_rm, data=issue_templated, headers=headers_rm) # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ -------------------------------------------- # (делаем привязку комментариев после получения веб-хука от редмайна) # ДЕБАГГИНГ log_comment_gh(request_result, comment, linked_issues) if (request_result.status_code == 200): request_result.status_code = 201 return request_result def post_issue(linked_projects, issue): # ----------------------------------------------- ПОДГОТОВКА ---------------------------------------------- project_id_rm = linked_projects.project_id_rm # настройка label-ов priority_id_rm = priority_ids_rm[0] tracker_id_rm = None for label in issue['labels']: tracker_rm = match_label_to_rm(label['name']) # если label известный if (tracker_rm != None): if (tracker_rm['type'] == 'Tracker'): if (tracker_id_rm == None): tracker_id_rm = tracker_rm['id_rm'] # если пользователь выбрал более одного трекера -> значение по умолчанию else: tracker_id_rm = tracker_ids_rm[0] # трекер по умолчанию # проверяем, был ли установлен трекер if (tracker_id_rm == None): tracker_id_rm = tracker_ids_rm[0] if (issue['state'] == 'closed'): status_id_rm = status_ids_rm[5] # статус "закрытый" is_opened = False else: status_id_rm = status_ids_rm[0] # статус по умолчанию is_opened = True # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА ----------------------------------------- #title = '[From Github] ' + issue['issue_title'] title = issue['issue_title'] issue_body = add_bot_phrase(issue, 'issue_body') # добавляем фразу бота # обработка спец. символов title = align_special_symbols(title) issue_body = align_special_symbols(issue_body) # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ---------------------------------------- issue_templated = issue_redmine_template.render( project_id=project_id_rm, tracker_id=tracker_id_rm, status_id=status_id_rm, priority_id=priority_id_rm, subject=title, description=issue_body) # кодировка по умолчанию (Latin-1) на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') request_result = requests.post(url_rm, data=issue_templated, headers=headers_rm) # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ -------------------------------------------- posted_issue = json.loads(request_result.text) # занесение в базу данных информацию о том, что данные issues связаны linked_issues = Linked_Issues.objects.create_linked_issues( posted_issue['issue']['id'], # id issue в редмайне issue['issue_id'], # id issue в гитхабе issue['repos_id'], # id репозитория в гитхабе issue['issue_number'], # номер issue в репозитории гитхаба tracker_id_rm, # id трекера в редмайне status_id_rm, # id статуса в редмайне priority_id_rm, # id приоритета в редмайне is_opened) # закрыт / открыт # добавляем linked_issues в linked_projects linked_projects.add_linked_issues(linked_issues) # корректируем label-ы в гитхабе tracker = match_tracker_to_gh(linked_issues.tracker_id_rm) correct_gh_labels(issue, tracker, linked_issues) # ДЕБАГГИНГ log_issue_gh(request_result, issue, linked_issues) result = {} result['request_result'] = request_result result['linked_issues'] = linked_issues return result # ====================================== ЗАГРУЗКА ДАННЫХ ИЗ GITHUB В REDMINE ======================================= # загрузка комментариев у задачи def link_comments_in_issue(linked_issues, issue): # ----------------------------------------------- ПОДГОТОВКА ---------------------------------------------- log_link_comments_start() repos_url_gh = linked_projects.url_gh[19:] # избавляемся от 'https://github.com/' # запрос кол-ва всех comment issue_comments_url = 'https://api.github.com/repos/' + repos_url_gh + '/issues/' + str(linked_issues.issue_num_gh) + '/comments' request_result = requests.get(issue_comments_url) # запрос всех комментариев requested_comments = json.loads(request_result.text) # ------------------------------ ЗАПРОС ВСЕХ КОММЕНТАРИЕВ С ЗАДАЧИ В ГИТХАБЕ ------------------------------ if (request_result.status_code != 200): error_text = ' ERROR WHILE LINKING COMMENTS: ' + str(request_result) + '\n ' + str(request_result.text) WRITE_LOG_ERR(error_text) return 0 # цикл перебора всех комментариев в задаче for comment in requested_comments: comment_parsed = parse_comment(issue, comment) if (chk_if_comments_are_linked(comment_parsed['comment_id'])): # комментарии уже связаны linked_comments = Linked_Comments.objects.get_by_comment_id_gh(comment_parsed['comment_id']) log_comment_gh_already_linked(linked_comments[0]) continue # отправляем комментарий в редмайн post_result = post_comment(linked_projects, linked_issues, comment_parsed) # пропускаем коментарии бота if (post_result.status_code == 200): continue # ошибка при отправке комментария в редмайн if (post_result.status_code != 201): error_text = ' ERROR WHILE LINKING COMMENTS: ' + str(request_result) + '\n ' + str( request_result.text) WRITE_LOG_ERR(error_text) return 0 log_link_comments_finish() # загрузка задач у проекта def link_issues_in_project(linked_projects): # ----------------------------------------------- ПОДГОТОВКА ---------------------------------------------- log_link_issues_start() repos_url_gh = linked_projects.url_gh[19:] # избавляемся от 'https://github.com/' # запрос кол-ва всех issue url_gh = 'https://api.github.com/search/issues?q=repo:' + repos_url_gh request_result = requests.get(url_gh) # для total_count WRITE_LOG(' ISSUES total_count QUERRY RESULT: ' + str(request_result)) num_issues = json.loads(request_result.text)['total_count'] WRITE_LOG(' total_count: ' + str(num_issues)) # ----------------------------------- ЗАПРОС ВСЕХ ЗАДАЧ С ПРОЕКТА В ГИТХАБЕ ------------------------------------ issues = [] WRITE_LOG(' QUERRYING ISSUES FROM GITHUB') #per_page = 6 # для тестов per_page = 100 # кол-во issue за страницу page = 1 # цикл перехода по всем страницам, по 100 issue за страницу while True: WRITE_LOG(' per_page: ' + str(per_page) + ' page: ' + str(page) + ' | status: ' + str(request_result.status_code) + ' ' + str(request_result.reason)) ''' https://api.github.com/search/issues?q=repo:AlexanderND/issues_linker_auto_labels_test/issues&per_page=5&page=1 ''' url = url_gh + '/issues&per_page=' + str(per_page) + '&page=' + str(page) request_result = requests.get(url) issues_on_page = json.loads(request_result.text)['items'] # цикл перебора всех задач на странице for issue in issues_on_page: issue_parsed = parse_issue(issue) # сохраняем данные issues.append(issue_parsed) # переход на след. страницу page += 1 # цикл с постусловием if (page >= 1 + num_issues / per_page): break # --------------------------------- ОТПРАВКА ЗАДАЧ В СВЯЗАННЫЙ ПРОЕКТ В РЕДМАЙНЕ ------------------------------- # отправляем задачи в редмайн в обратном порядке for issue in reversed(issues): if (chk_if_issues_are_linked(issue['issue_id'])): # задачи уже связаны linked_issues = Linked_Issues.objects.get_issue_by_id_gh(issue['issue_id']) log_issue_gh_already_linked(linked_issues[0], issue) link_comments_in_issue(linked_issues[0], issue) continue post_result = post_issue(linked_projects, issue) # если успешно создали новую задачу в редмайне, осуществляем привязку комментариев if (post_result['request_result'].status_code != 201): error_text = ' ERROR WHILE LINKING ISSUES: ' + str(request_result) + '\n ' + str( request_result.text) WRITE_LOG_ERR(error_text) return 0 else: link_comments_in_issue(post_result['linked_issues'], issue) log_link_issues_finish() link_issues_in_project(linked_projects)
def process_payload_from_gh(payload): # =================================================== ПОДГОТОВКА =================================================== def parse_payload(payload): payload_parsed = {} # словарь issue (название, описание, ссылка) # действие и его автор payload_parsed['action'] = payload['action'] payload_parsed['sender_id'] = payload['sender'][ 'id'] # sender - тот, кто совершил действие payload_parsed['sender_login'] = payload['sender']['login'] # заполение полей issue payload_parsed['issue_title'] = payload['issue']['title'] payload_parsed['issue_body'] = payload['issue']['body'] payload_parsed['issue_author_id'] = payload['issue']['user']['id'] payload_parsed['issue_author_login'] = payload['issue']['user'][ 'login'] # идентификаторы (для связи и логов) payload_parsed['issue_id'] = payload['issue']['id'] payload_parsed['repos_id'] = payload['repository']['id'] payload_parsed['issue_number'] = payload['issue']['number'] # ссылка на issue (для фразы бота и логов) payload_parsed['issue_url'] = payload['issue']['html_url'] payload_parsed['labels'] = payload['issue']['labels'] #if ((payload_parsed['action'] == 'labeled') | (payload_parsed['action'] == 'unlabeled')): # payload_parsed['label'] = payload['issue']['label'] return payload_parsed try: issue = parse_payload(payload) except: error_text = 'ERROR: unknown payload type' WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'received webhook from GITHUB: issues' + '\n' + error_text) return HttpResponse(error_text, status=200) # авторизация в redmine по токену api_key_redmime = read_file( 'api_keys/api_key_redmime.txt') # загрузка ключа для redmine api api_key_redmime = api_key_redmime.replace( '\n', '') # избавляемся от \n в конце строки # загрузка template из файла issue_redmine_template = read_file('data/issue_redmine_template.json') issue_redmine_template = Template( issue_redmine_template) # шаблон для каждого issue # заголовки авторизации и приложения, при отправке запросов на редмайн headers_rm = { 'X-Redmine-API-Key': api_key_redmime, 'Content-Type': 'application/json' } # авторизация в гитхабе по токену api_key_github = read_file( 'api_keys/api_key_github.txt') # загрузка ключа для github api api_key_github = api_key_github.replace( '\n', '') # избавляемся от \n в конце строки # загрузка issue template из файла issue_github_template = read_file('data/issue_github_template.json') issue_github_template = Template( issue_github_template) # шаблон для каждого issue # заголовки авторизации и приложения, при отправке запросов на гитхаб headers_gh = { 'Authorization': 'token ' + api_key_github, 'Content-Type': 'application/json' } # ============================================ ВСПОМОГАТЕЛЬНЫЕ КОМАНДЫ ============================================= # issue_body # comment_body_action # добавляем фразу бота, со ссылкой на аккаунт пользователя в гитхабе def add_bot_phrase(issue, to): # добавляем фразу бота к описанию issue if (to == 'issue_body'): # добавляем фразу бота author_url_gh = '"' + issue[ 'issue_author_login'] + '":' + 'https://github.com/' + issue[ 'issue_author_login'] issue_url_gh = '"issue":' + issue['issue_url'] issue_body = '>I am a bot, bleep-bloop.\n' +\ '>' + author_url_gh + ' Has opened the ' + issue_url_gh + ' in Github.\n' +\ issue['issue_body'] return issue_body # добавляем фразу бота (комментарием) к действию в гитхабе (закрыл, изменил и т.д.) elif (to == 'comment_body_action'): author_url = '"' + issue[ 'sender_login'] + '":' + 'https://github.com/' + issue[ 'sender_login'] issue_url = '"issue":' + issue['issue_url'] comment_body = 'I am a bot, bleep-bloop.\n' +\ author_url + ' Has ' + issue['action'] + ' the ' + issue_url + ' in Github.\n' return comment_body else: WRITE_LOG("\nERROR: 'process_payload_from_gh.add_bot_phrase'\n" + "unknown parameter 'to': " + to + '.\n' + "Please, check your code on possible typos.\n" + "Alternatively, add logic to process '" + to + "' parameter correctly.") return None # обновление linked_issues в базе данных сервера (tracker_id, status_id, priority_id) def update_linked_issues(linked_issues, tracker_id, status_id, priority_id, is_opened): linked_issues.tracker_id_rm = tracker_id linked_issues.status_id_rm = status_id linked_issues.priority_id_rm = priority_id linked_issues.is_opened = is_opened linked_issues.save() # TODO: отправлять комментарий бота, что нельзя установить неправильные label-ы + логи? # исправление label-ов в гитхабе def correct_gh_labels(issue, tracker, linked_issues): if (not allow_correct_github_labels): return 0 # добавление label-ов priority = match_priority_to_gh(linked_issues.priority_id_rm) status = match_status_to_gh(linked_issues.status_id_rm) # ---------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ---------------------------------------- issue_templated = issue_github_template.render( title=issue['issue_title'], body=issue['issue_body'], priority=priority, status=status, tracker=tracker) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') url_gh = make_gh_repos_url(linked_issues.repos_id_gh) # добавление issue_id к ссылке issue_url_gh = url_gh + '/' + str(linked_issues.issue_num_gh) request_result = requests.patch(issue_url_gh, data=issue_templated, headers=headers_gh) return request_result # TODO: отправлять комментарий бота, что нельзя открыть rejected issue + логи? # закрыть issue в гитхабе def close_gh_issue(linked_issues, url_gh): # ---------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ---------------------------------------- issue_templated = issue_github_template.render(state='closed') # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') # добавление issue_id к ссылке issue_url_gh = url_gh + '/' + str(linked_issues.issue_num_gh) request_result = requests.patch(issue_url_gh, data=issue_templated, headers=headers_gh) return request_result # типичные ошибки на этапе проверки: не связаны проекты, задачи, комментарии, неизвестное действие и т.п. def PREPARATION_ERR(error_text): # добавляем, чтобы в начале получилось: 'PREPARATION ERROR' error_text = 'PREPARATION ' + error_text WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'received webhook from GITHUB: issues | ' + 'action: ' + str(issue['action']) + '\n' + error_text) return HttpResponse(error_text, status=200) # логическая ошибка: неизвестное действие, неправильные label-ы в гитхабе и т.п. def LOGICAL_ERR(error_text): # добавляем, чтобы в начале получилось: 'LOGICAL ERROR' error_text = 'LOGICAL ' + error_text WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'received webhook from GITHUB: issues | ' + 'action: ' + str(issue['action']) + '\n' + error_text) return HttpResponse(error_text, status=200) # ============================================= КОМАНДЫ ДЛЯ ЗАГРУЗКИ =============================================== def post_issue(linked_projects, issue): # ----------------------------------------------- ПОДГОТОВКА ---------------------------------------------- # дополнительная проверка, что проекты связаны if (linked_projects.count() == 0): error_text = "ERROR: process_payload_from_gh.post_issue\n" +\ "issue " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE" return PREPARATION_ERR(error_text) linked_projects = linked_projects[0] project_id_rm = linked_projects.project_id_rm # настройка label-ов tracker_id_rm = None status_id_rm = status_ids_rm[0] priority_id_rm = priority_ids_rm[0] for label in issue['labels']: tracker_rm = match_label_to_rm(label['name']) # если label известный if (tracker_rm != None): if (tracker_rm['type'] == 'Tracker'): if (tracker_id_rm == None): tracker_id_rm = tracker_rm['id_rm'] # если пользователь выбрал более одного трекера -> значение по умолчанию else: tracker_id_rm = tracker_ids_rm[0] # проверяем, был ли установлен трекер if (tracker_id_rm == None): tracker_id_rm = tracker_ids_rm[0] # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА ----------------------------------------- #title = '[From Github] ' + issue['issue_title'] title = issue['issue_title'] issue_body = add_bot_phrase(issue, 'issue_body') # добавляем фразу бота # обработка спец. символов title = align_special_symbols(title) issue_body = align_special_symbols(issue_body) # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ---------------------------------------- issue_templated = issue_redmine_template.render( project_id=project_id_rm, tracker_id=tracker_id_rm, status_id=status_id_rm, priority_id=priority_id_rm, subject=title, description=issue_body) # кодировка по умолчанию (Latin-1) на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') request_result = requests.post(url_rm, data=issue_templated, headers=headers_rm) # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ -------------------------------------------- posted_issue = json.loads(request_result.text) # занесение в базу данных информацию о том, что данные issues связаны linked_issues = Linked_Issues.objects.create_linked_issues( posted_issue['issue']['id'], # id issue в редмайне issue['issue_id'], # id issue в гитхабе issue['repos_id'], # id репозитория в гитхабе issue['issue_number'], # номер issue в репозитории гитхаба tracker_id_rm, # id трекера в редмайне status_id_rm, # id статуса в редмайне priority_id_rm, # id приоритета в редмайне True) # открыт # добавляем linked_issues в linked_projects linked_projects.add_linked_issues(linked_issues) # корректируем label-ы в гитхабе tracker = match_tracker_to_gh(linked_issues.tracker_id_rm) correct_gh_labels(issue, tracker, linked_issues) # ДЕБАГГИНГ log_issue_gh(request_result, issue, linked_issues, linked_projects.project_id_rm) return request_result # is_opened - issue открыто / закрыто def edit_issue(linked_projects, issue, is_opened): # ----------------------------------------------- ПОДГОТОВКА ---------------------------------------------- # дополнительная проверка, что проекты связаны if (linked_projects.count() == 0): error_text = "ERROR: process_payload_from_gh.edit_issue\n" +\ "issue " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE" return PREPARATION_ERR(error_text) linked_projects = linked_projects[0] project_id_rm = linked_projects.project_id_rm repos_id_gh = linked_projects.repos_id_gh url_gh = make_gh_repos_url(repos_id_gh) linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id']) # дополнительная проверка, что issue связаны if (linked_issues.count() == 0): error_text = "ERROR: process_payload_from_gh.edit_issue\n" +\ "issue " + str(issue['action']) + " in GITHUB, but it's not linked to REDMINE" return PREPARATION_ERR(error_text) linked_issues = linked_issues[0] # проверка: что issue был отклонён (запрещаем открывать вновь) """ if (linked_issues.status_id_rm == status_ids_rm[4]): if (is_opened == True): return close_gh_issue(linked_issues, url_gh) """ # настройка label-ов tracker_id_rm = None for label in issue['labels']: tracker_rm = match_label_to_rm(label['name']) # если label известный if (tracker_rm != None): if (tracker_rm['type'] == 'Tracker'): if (tracker_id_rm == None): tracker_id_rm = tracker_rm['id_rm'] # если пользователь выбрал более одного трекера -> значение по умолчанию else: tracker_id_rm = tracker_ids_rm[0] # проверяем, был ли установлен трекер if (tracker_id_rm == None): tracker_id_rm = tracker_ids_rm[0] # корректируем label-ы в гитхабе tracker = match_tracker_to_gh(linked_issues.tracker_id_rm) correct_gh_labels(issue, tracker, linked_issues) # обновляем информацию в таблице update_linked_issues(linked_issues, tracker_id_rm, linked_issues.status_id_rm, linked_issues.priority_id_rm, is_opened) # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА ----------------------------------------- #title = '[From Github] ' + issue['issue_title'] title = issue['issue_title'] # проверяем, если автор issue - бот if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])): issue_body = del_bot_phrase( issue['issue_body']) # удаляем фразу бота else: issue_body = add_bot_phrase(issue, 'issue_body') # добавляем фразу бота comment_body = add_bot_phrase( issue, 'comment_body_action' ) # добавляем фразу бота в комментарий к действию # обработка спец. символов title = align_special_symbols(title) issue_body = align_special_symbols(issue_body) comment_body = align_special_symbols(comment_body) # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ---------------------------------------- if (is_opened): status_id = linked_issues.status_id_rm else: status_id = status_ids_rm[5] # closed issue_templated = issue_redmine_template.render( project_id=project_id_rm, issue_id=linked_issues.issue_id_rm, tracker_id=linked_issues.tracker_id_rm, status_id=status_id, priority_id=linked_issues.priority_id_rm, subject=title, description=issue_body, notes=comment_body) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') issue_url_rm = url_rm.replace( '.json', '/' + str(linked_issues.issue_id_rm) + '.json') request_result = requests.put(issue_url_rm, data=issue_templated, headers=headers_rm) # ДЕБАГГИНГ log_issue_gh(request_result, issue, linked_issues, linked_projects.project_id_rm) return request_result def delete_issue(linked_projects, issue): # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------- # дополнительная проверка, что проекты связаны if (linked_projects.count() == 0): error_text = "ERROR: process_payload_from_gh.delete_issue\n" +\ "issue " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE" return PREPARATION_ERR(error_text) linked_projects = linked_projects[0] linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id']) # дополнительная проверка, что issue связаны if (linked_issues.count() == 0): error_text = "ERROR: process_payload_from_gh.delete_issue\n" +\ "issue " + str(issue['action']) + " in GITHUB, but it's not linked to REDMINE" return PREPARATION_ERR(error_text) linked_issues = linked_issues[0] # -------------------------------------- УДАЛЕНИЕ ДАННЫХ В РЕДМАЙНЕ ---------------------------------------- issue_url_rm = url_rm.replace( '.json', '/' + str(linked_issues.issue_id_rm) + '.json') request_result = requests.delete(issue_url_rm, headers=headers_rm) # удаление linked_issues из базы данных linked_issues.delete() # ДЕБАГГИНГ log_issue_gh(request_result, issue, linked_issues, linked_projects.project_id_rm) return request_result def reject_issue(linked_projects, issue): # ----------------------------------------------- ПОДГОТОВКА ---------------------------------------------- # дополнительная проверка, что проекты связаны if (linked_projects.count() == 0): error_text = "ERROR: process_payload_from_gh.reject_issue\n" +\ "issue " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE" return PREPARATION_ERR(error_text) linked_projects = linked_projects[0] project_id_rm = linked_projects.project_id_rm repos_id_gh = linked_projects.repos_id_gh url_gh = make_gh_repos_url(repos_id_gh) linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id']) # дополнительная проверка, что issue связаны if (linked_issues.count() == 0): error_text = "ERROR: process_payload_from_gh.reject_issue\n" +\ "issue " + str(issue['action']) + " in GITHUB, but it's not linked to REDMINE" return PREPARATION_ERR(error_text) linked_issues = linked_issues[0] # обновляем информацию в таблице update_linked_issues( linked_issues, linked_issues.tracker_id_rm, status_ids_rm[4], # 4 - rejected linked_issues.priority_id_rm, False) # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА ----------------------------------------- #title = '[From Github] ' + issue['issue_title'] title = issue['issue_title'] # проверяем, если автор issue - бот if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])): issue_body = del_bot_phrase( issue['issue_body']) # удаляем фразу бота else: issue_body = add_bot_phrase(issue, 'issue_body') # добавляем фразу бота comment_body = add_bot_phrase( issue, 'comment_body_action' ) # добавляем фразу бота в комментарий к действию # обработка спец. символов title = align_special_symbols(title) issue_body = align_special_symbols(issue_body) comment_body = align_special_symbols(comment_body) # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ---------------------------------------- issue_templated = issue_redmine_template.render( project_id=project_id_rm, issue_id=linked_issues.issue_id_rm, tracker_id=linked_issues.tracker_id_rm, status_id=status_ids_rm[4], # 4 - rejected priority_id=linked_issues.priority_id_rm, subject=title, description=issue_body, notes=comment_body) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') issue_url_rm = url_rm.replace( '.json', '/' + str(linked_issues.issue_id_rm) + '.json') request_result = requests.put(issue_url_rm, data=issue_templated, headers=headers_rm) # удаление linked_issues из базы данных linked_issues.delete() # ДЕБАГГИНГ log_issue_gh(request_result, issue, linked_issues, linked_projects.project_id_rm) return request_result # TODO: бот не совсем корректно реагирует, если изменить трекер и что-либо ещё (частично исправил) # TODO: также, бот несколько раз упоминает действие в редмайне (labeled, unlabeld) (так как гитхаб отсылает все изменения столько раз, сколько label-ов было изменено...) def label_issue(linked_projects, issue): # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------- # дополнительная проверка, что проекты связаны if (linked_projects.count() == 0): error_text = "ERROR: process_payload_from_gh.label_issue\n" +\ "issue " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE" return PREPARATION_ERR(error_text) linked_projects = linked_projects[0] project_id_rm = linked_projects.project_id_rm linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id']) # дополнительная проверка, что issue связаны if (linked_issues.count() == 0): error_text = "ERROR: process_payload_from_gh.label_issue\n" +\ "issue " + str(issue['action']) + " in GITHUB, but it's not linked to REDMINE" return PREPARATION_ERR(error_text) linked_issues = linked_issues[0] priority_id_rm = None status_id_rm = None tracker_id_rm = None incorrect_labels = False labels = issue['labels'] for label in labels: label_gh = match_label_to_rm(label['name']) #если label известный if (label_gh != None): if (label_gh['type'] == 'Priority'): if (priority_id_rm == None): priority_id_rm = label_gh['id_rm'] if (priority_id_rm != linked_issues.priority_id_rm): incorrect_labels = True else: incorrect_labels = True elif (label_gh['type'] == 'Status'): if (status_id_rm == None): status_id_rm = label_gh['id_rm'] if (status_id_rm != linked_issues.status_id_rm): incorrect_labels = True else: incorrect_labels = True elif (label_gh['type'] == 'Tracker'): if (tracker_id_rm == None): tracker_id_rm = label_gh['id_rm'] # пользователь выбрал новый трекер, но не удалил старый -> выбираем новый else: if (tracker_id_rm == linked_issues.tracker_id_rm): tracker_id_rm = label_gh['id_rm'] # проверяем, был ли изменён трекер if (tracker_id_rm == None): tracker_id_rm = linked_issues.tracker_id_rm # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА ----------------------------------------- #title = '[From Github] ' + issue['issue_title'] title = issue['issue_title'] # проверяем, если автор issue - бот if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])): issue_body = del_bot_phrase( issue['issue_body']) # удаляем фразу бота else: issue_body = add_bot_phrase(issue, 'issue_body') # добавляем фразу бота comment_body = add_bot_phrase( issue, 'comment_body_action' ) # добавляем фразу бота в комментарий к действию # обработка спец. символов title = align_special_symbols(title) issue_body = align_special_symbols(issue_body) comment_body = align_special_symbols(comment_body) # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ---------------------------------------- # корректируем label-ы в гитхабе tracker = match_tracker_to_gh(tracker_id_rm) request_result = correct_gh_labels( issue, tracker, linked_issues) # корректируем label-ы в гитхабе if (request_result.status_code != 200): # сообщаем об ошибке error_text = "ERROR: process_payload_from_gh.label_issue\n" +\ "Encountered some error while trying to correct labels in Github" return LOGICAL_ERR(error_text) # TODO: похоже, он не успевает изменить linked_issues.tracker_id_rm: вебхуки приходят почти одновременно # проверяем, был ли изменён трекер и предотвращаем множественную отправку сообщений в гитхаб if ((tracker_id_rm != linked_issues.tracker_id_rm) & (issue['action'] == 'labeled')): # обновляем информацию в таблице update_linked_issues(linked_issues, tracker_id_rm, linked_issues.status_id_rm, linked_issues.priority_id_rm, True) issue_templated = issue_redmine_template.render( project_id=project_id_rm, issue_id=linked_issues.issue_id_rm, tracker_id=tracker_id_rm, status_id=linked_issues.status_id_rm, priority_id=linked_issues.priority_id_rm, subject=title, description=issue_body, notes=comment_body) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') issue_url_rm = url_rm.replace( '.json', '/' + str(linked_issues.issue_id_rm) + '.json') request_result = requests.put(issue_url_rm, data=issue_templated, headers=headers_rm) # проверяем, корректные ли label-ы if (incorrect_labels): # сообщаем об ошибке error_text = "ERROR: process_payload_from_gh.label_issue\n" +\ "incorrect labels in GITHUB" return LOGICAL_ERR(error_text) else: # ДЕБАГГИНГ log_issue_gh(request_result, issue, linked_issues, linked_projects.project_id_rm) return request_result # ============================================ ЗАГРУЗКА ISSUE В REDMINE ============================================ do_delete_issues = False # запрет удаления issues (вместо удаления ставим rejected) linked_projects = Linked_Projects.objects.get_project_by_id_gh( issue['repos_id']) if (issue['action'] == 'opened'): if (chk_if_gh_user_is_our_bot(issue['sender_id'])): error_text = prevent_cyclic_issue_gh(issue) return HttpResponse(error_text, status=200) request_result = post_issue(linked_projects, issue) elif (issue['action'] == 'edited'): if (chk_if_gh_user_is_our_bot(issue['sender_id'])): error_text = prevent_cyclic_issue_gh(issue) return HttpResponse(error_text, status=200) request_result = edit_issue(linked_projects, issue, True) elif (issue['action'] == 'closed'): if (chk_if_gh_user_is_our_bot(issue['sender_id'])): error_text = prevent_cyclic_issue_gh(issue) return HttpResponse(error_text, status=200) request_result = edit_issue(linked_projects, issue, False) elif (issue['action'] == 'reopened'): if (chk_if_gh_user_is_our_bot(issue['sender_id'])): error_text = prevent_cyclic_issue_gh(issue) return HttpResponse(error_text, status=200) request_result = edit_issue(linked_projects, issue, True) elif (issue['action'] == 'deleted'): if (chk_if_gh_user_is_our_bot(issue['sender_id'])): error_text = prevent_cyclic_issue_gh(issue) return HttpResponse(error_text, status=200) if (do_delete_issues): request_result = delete_issue(linked_projects, issue) else: request_result = reject_issue(linked_projects, issue) # Совершенно безразлично, 'labeled' или 'unlabeled' elif ((issue['action'] == 'labeled') | (issue['action'] == 'unlabeled')): if (chk_if_gh_user_is_our_bot(issue['sender_id'])): error_text = prevent_cyclic_issue_gh(issue) return HttpResponse(error_text, status=200) request_result = label_issue(linked_projects, issue) else: error_text = "ERROR: process_payload_from_gh\n" +\ "UNKNOWN ACTION" return LOGICAL_ERR(error_text) return align_request_result(request_result)
def relink_projects(): # =================================================== ПОДГОТОВКА =================================================== # авторизация в redmine по токену (локальный сервер) api_key_redmime = read_file( 'api_keys/api_key_redmime_local.txt') # загрузка ключа для redmine api #api_key_redmime = read_file('api_keys/api_key_redmime.txt') # загрузка ключа для redmine api api_key_redmime = api_key_redmime.replace( '\n', '') # избавляемся от \n в конце строки ''' # авторизация в redmine по токену (реальный сервер) api_key_redmime = read_file('api_keys/api_key_redmime_local.txt') # загрузка ключа для redmine api api_key_redmime = api_key_redmime.replace('\n', '') # избавляемся от \n в конце строки ''' # авторизация в гитхабе по токену api_key_github = read_file( 'api_keys/api_key_github.txt') # загрузка ключа для github api api_key_github = api_key_github.replace( '\n', '') # избавляемся от \n в конце строки linked_projects = Linked_Projects.objects.get_all() for linked_project in linked_projects: # ============================ РЕСИНХРОНИЗАЦИЯ ВСЕХ СВЯЗАННЫХ ПРОЕКТОВ (GH -> GM) ============================== repos_id_gh = linked_project.repos_id_gh url_gh = linked_project.url_gh project_id_rm = linked_project.project_id_rm url_rm = linked_project.url_rm def log_relink_projects_start(): if (not allow_log_project_linking): return 0 WRITE_LOG_GRN( '\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'RELINKING PROJECTS IN PROGRESS' + '\n' + 'GITHUB | ---------------------------------------' + '\n' + ' | repos_id: ' + str(repos_id_gh) + '\n' + ' | repos_url: ' + url_gh + '\n' + 'REDMINE | ---------------------------------------' + '\n' + ' | project_id: ' + str(project_id_rm) + '\n' + ' | project_url: ' + url_rm) def log_relink_projects_finish(): if (not allow_log_project_linking): return 0 WRITE_LOG_GRN( 'RELINKING PROJECTS FINISHED' + '\n' + 'GITHUB | ---------------------------------------' + '\n' + ' | repos_id: ' + str(repos_id_gh) + '\n' + ' | repos_url: ' + url_gh + '\n' + 'REDMINE | ---------------------------------------' + '\n' + ' | project_id: ' + str(project_id_rm) + '\n' + ' | project_url: ' + url_rm + '\n' + '\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n') log_relink_projects_start() # запрос issues и комментариев к ним из гитхаба и отправка в редмайн query_data_gh_to_rm(linked_project) log_relink_projects_finish() response_text = "re-linking projects successfully!" return HttpResponse(response_text, status=200) # <br> - новая строка в html
def link_projects(payload): # =================================================== ПОДГОТОВКА =================================================== # достаём id из payload-а #project_id_rm = payload['project_id_rm'] #repos_id_gh = payload['repos_id_gh'] # достаём ссылки из payload-а url_rm = payload['url_rm'] url_gh = payload['url_gh'] # авторизация в redmine по токену (локальный сервер) api_key_redmime = read_file( 'api_keys/api_key_redmime.txt') # загрузка ключа для redmine api api_key_redmime = api_key_redmime.replace( '\n', '') # избавляемся от \n в конце строки # авторизация в гитхабе по токену api_key_github = read_file( 'api_keys/api_key_github.txt') # загрузка ключа для github api api_key_github = api_key_github.replace( '\n', '') # избавляемся от \n в конце строки # заголовки авторизации и приложения, при отправке запросов на редмайн headers_rm = { 'X-Redmine-API-Key': api_key_redmime, 'Content-Type': 'application/json' } # заголовки авторизации и приложения, при отправке запросов на гитхаб headers_gh = { 'Authorization': 'token ' + api_key_github, 'Content-Type': 'application/json' } def log_linking_error(error_text, request_result): WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'ERROR: tried to LINK PROJECTS, but ' + error_text + '\n' + 'REDMINE | ---------------------------------------' + '\n' + ' | project_url: ' + url_rm + '\n' + 'GITHUB | ---------------------------------------' + '\n' + ' | repos_url: ' + url_gh + '\n' + 'REQUEST_RESULT | ---------------------------------------' + '\n' + ' | status_code: ' + str(request_result.status_code) + '\n' + ' | text: ' + request_result.text) def log_linking_error_url(error_text): WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'ERROR: tried to LINK PROJECTS, but ' + error_text + '\n' + 'REDMINE | ---------------------------------------' + '\n' + ' | project_url: ' + url_rm + '\n' + 'GITHUB | ---------------------------------------' + '\n' + ' | repos_url: ' + url_gh + '\n') def linking_error(error_text, request_result): log_linking_error(error_text, request_result) return HttpResponse(error_text.replace('\n', '<br>'), status=200) # <br> - новая строка в html def linking_error_url(error_text): log_linking_error_url(error_text) return HttpResponse(error_text.replace('\n', '<br>'), status=200) # <br> - новая строка в html # ========================================= ПОЛУЧЕНИЕ PROJECT_ID РЕДМАЙНА ========================================== # связь по ссылке if (url_rm != ''): # для удобства, выводим как json api_url_rm = url_rm + '.json' request_result = requests.get(api_url_rm, headers=headers_gh) if (request_result.status_code != 200): error_text = 'Something went wrong in REDMINE.\n' +\ 'Please, check if the URL is correct.\n' +\ 'Aborting action, in order to prevent a perpetual loop.\n' +\ 'If the URL are correct, then try again sometime later.' return linking_error(error_text, request_result) project_id_rm = json.loads(request_result.text)['project']['id'] # TODO: связь по id else: error_text = "You didn't input REDMINE's URL.\n" +\ 'The linking by id has not been implemented (yet).\n' +\ 'Please, input the URL.\n' +\ 'Aborting action, in order to prevent a perpetual loop.' return linking_error_url(error_text) # ========================================== ПОЛУЧЕНИЕ REPOS_ID ГИТХАБА ============================================ # связь по ссылке if (url_gh != ''): # удаляем 19 первых символов (https://github.com/) repos_url_gh = url_gh[19:] # AlexanderND/test api_url_gh = 'https://api.github.com/repos/' + repos_url_gh # https://api.github.com/repos/AlexanderND/test request_result = requests.get(api_url_gh, headers=headers_gh) if (request_result.status_code != 200): error_text = 'Something went wrong in GITHUB.\n' +\ 'Please, check if the URL is correct.\n' +\ 'Aborting action, in order to prevent a perpetual loop.\n' +\ 'If the URL are correct, then try again sometime later.' return linking_error(error_text, request_result) repos_id_gh = json.loads(request_result.text)['id'] # TODO: связь по id else: error_text = "You didn't input REDMINE's URL.\n" +\ 'The linking by id has not been implemented (yet).\n' +\ 'Please, input the URL.\n' +\ 'Aborting action, in order to prevent a perpetual loop.' return linking_error_url(error_text) # ============================================= СОХРАНЕНИЕ ID-ШНИКОВ =============================================== def log_link_projects_start(): if (not allow_log_project_linking): return 0 WRITE_LOG_GRN( '\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'LINKING PROJECTS IN PROGRESS' + '\n' + 'GITHUB | ---------------------------------------' + '\n' + ' | repos_id: ' + str(repos_id_gh) + '\n' + ' | repos_url: ' + url_gh + '\n' + 'REDMINE | ---------------------------------------' + '\n' + ' | project_id: ' + str(project_id_rm) + '\n' + ' | project_url: ' + url_rm) def log_link_projects_finish(): if (not allow_log_project_linking): return 0 WRITE_LOG_GRN( 'LINKING PROJECTS FINISHED' + '\n' + 'GITHUB | ---------------------------------------' + '\n' + ' | repos_id: ' + str(repos_id_gh) + '\n' + ' | repos_url: ' + url_gh + '\n' + 'REDMINE | ---------------------------------------' + '\n' + ' | project_id: ' + str(project_id_rm) + '\n' + ' | project_url: ' + url_rm + '\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n') # проверка, что проекты уже связаны def load_linked_projects(project_id_rm, repos_id_gh): linked_projects = Linked_Projects.objects.get_linked_projects( project_id_rm, repos_id_gh) if (len(linked_projects) < 1): linked_projects = Linked_Projects.objects.create_linked_projects( project_id_rm, repos_id_gh, url_rm, url_gh) return linked_projects else: return linked_projects[0] # будет только один связанный проект log_link_projects_start() # занесение в базу данных информацию о том, что данные проекты связаны linked_projects = load_linked_projects(project_id_rm, repos_id_gh) # ============================== СОЗДАНИЕ НЕОБХОДИМЫХ ДЛЯ РАБОТЫ LABEL-ОВ В ГИТХАБЕ ================================ # создание label-ов в гитхабе github_label_template = read_file('data/github_label_template.json') github_label_template = Template( github_label_template) # шаблон создания label-ов labels_url_gh = api_url_gh + '/labels' # загрузка label-ов, необходимых для связи с гитхабом github_labels = read_file('data/github_labels.json') github_labels = json.loads(github_labels) labels = github_labels['labels'] response_text = 'Projects posted successfully!\n' +\ "(or not, I actually don't know)\n" +\ "Labels:\n\n" # загрузка label-ов в гитхаб def log_label_post(label, post_result): if (not allow_log_project_linking): return 0 if (not detailed_log_project_linking): return 0 post_result_text = str(post_result.text) log_text = '\n' + '-' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '-' * 35 + '\n' +\ 'POSTing new label to GITHUB:' + '\n' +\ 'LABEL | ---------------------------------------' + '\n' +\ ' | name: ' + label['name'] + '\n' +\ ' | description: ' + label['description'] + '\n' +\ ' | color: ' + label['color'] + '\n' +\ ' | default: ' + label['default'] + '\n' +\ 'POST RESULT | ---------------------------------------' + '\n' +\ ' | status: ' + str(post_result) + '\n' +\ ' | text: ' + post_result_text + '\n' if (post_result.status_code == 201): WRITE_LOG(log_text) # скорее всего, label просто уже существует elif (post_result.status_code == 422): WRITE_LOG_WAR(log_text) else: WRITE_LOG_ERR(log_text) # TODO: исправить постинг label-ов (не приходит description) # TODO: исправить постинг label-ов (не приходит default) # постим label-ы в гитхаб for label in labels: # загружаем label-ы label_templated = github_label_template.render( name=label['name'], description=label['description'], color=label['color'], default=label['default']) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне label_templated = label_templated.encode('utf-8') request_result = requests.post(labels_url_gh, data=label_templated, headers=headers_gh) log_text = '' log_text += label['name'] + '\n' log_text += str(request_result) + '\n' log_text += str(request_result.text) + '\n' # ДЕБАГГИНГ log_label_post(label, request_result) response_text += log_text # =================================== ЗАГРУЗКА ВСЕХ ISSUE ИЗ ГИТХАБА В РЕДМАЙН ===================================== # запрос issues и комментариев к ним из гитхаба и отправка в редмайн query_data_gh_to_rm(linked_projects) log_link_projects_finish() # TODO: выдавать то же, что выдавалось бы без переопределения метода create return HttpResponse(response_text.replace('\n', '<br>'), status=200) # <br> - новая строка в html
def process_payload_from_rm(payload): # =================================================== ПОДГОТОВКА =================================================== def parse_payload(payload): payload = payload[ 'payload'] # достаём содержимое payload. payload payload. payload? payload! payload_parsed = {} # словарь issue (название, описание, ссылка) # автор issue payload_parsed['issue_author_id'] = payload['issue']['author']['id'] payload_parsed['issue_author_login'] = payload['issue']['author'][ 'login'] payload_parsed['issue_author_firstname'] = payload['issue']['author'][ 'firstname'] payload_parsed['issue_author_lastname'] = payload['issue']['author'][ 'lastname'] payload_parsed['action'] = payload['action'] # совершённое действие # при update возможна добавка комментария if (payload_parsed['action'] == 'updated'): # тело комментария payload_parsed['comment_body'] = payload['journal']['notes'] # id комментария (для связи и логов) payload_parsed['comment_id'] = payload['journal']['id'] # автор комментария payload_parsed['comment_author_id'] = payload['journal']['author'][ 'id'] payload_parsed['comment_author_login'] = payload['journal'][ 'author']['login'] payload_parsed['comment_author_firstname'] = payload['journal'][ 'author']['firstname'] payload_parsed['comment_author_lastname'] = payload['journal'][ 'author']['lastname'] # заполение полей issue payload_parsed['issue_title'] = payload['issue']['subject'] payload_parsed['issue_body'] = payload['issue']['description'] payload_parsed['tracker_id'] = payload['issue']['tracker']['id'] payload_parsed['status_id'] = payload['issue']['status']['id'] payload_parsed['priority_id'] = payload['issue']['priority']['id'] # идентификаторы (для связи и логов) payload_parsed['issue_id'] = payload['issue']['id'] payload_parsed['project_id'] = payload['issue']['project']['id'] # ссылка на issue (для фразы бота и логов) payload_parsed['issue_url'] = payload['url'] return payload_parsed try: issue = parse_payload(payload) except: error_text = 'ERROR: unknown payload type' WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'received webhook from REDMINE: issues' + '\n' + error_text) return HttpResponse(error_text, status=200) # авторизация в гитхабе по токену api_key_github = read_file( 'api_keys/api_key_github.txt') # загрузка ключа для github api api_key_github = api_key_github.replace( '\n', '') # избавляемся от \n в конце строки # загрузка issue template из файла issue_github_template = read_file('data/issue_github_template.json') issue_github_template = Template( issue_github_template) # шаблон для каждого issue # загрузка comment template из файла comment_github_template = read_file('data/comment_github_template.json') comment_github_template = Template( comment_github_template) # шаблон для каждого issue # заголовки авторизации и приложения, при отправке запросов на гитхаб headers = { 'Authorization': 'token ' + api_key_github, 'Content-Type': 'application/json' } # ============================================ ВСПОМОГАТЕЛЬНЫЕ КОМАНДЫ ============================================= # issue_body # comment_body # comment_body_action # issue_label # добавляем фразу бота def add_bot_phrase(issue, to): # добавляем фразу бота к описанию issue if (to == 'issue_body'): firstname = issue['issue_author_firstname'] lastname = issue['issue_author_lastname'] login = issue['issue_author_login'] # добавляем фразу бота issue_body = '>I am a bot, bleep-bloop.\n' +\ '>' + firstname + ' ' + lastname + ' (' + login + ') Has opened the issue in Redmine.\n\n' +\ issue['issue_body'] return issue_body # добавляем фразу бота к комментарию elif (to == 'comment_body'): firstname = issue['comment_author_firstname'] lastname = issue['comment_author_lastname'] login = issue['comment_author_login'] # добавляем фразу бота comment_body = '>I am a bot, bleep-bloop.\n' + \ '>' + firstname + ' ' + lastname + ' (' + login + ') Has commented / edited with comment the issue in Redmine.\n\n' +\ issue['comment_body'] return comment_body # добавляем фразу бота (комментарием) к действию в редмайне (закрыл, изменил и т.д.) elif (to == 'comment_body_action'): firstname = issue['comment_author_firstname'] lastname = issue['comment_author_lastname'] login = issue['comment_author_login'] # добавляем фразу бота comment_body = 'I am a bot, bleep-bloop.\n' +\ firstname + ' ' + lastname + ' (' + login + ') Has edited the issue in Redmine.\n\n' return comment_body else: WRITE_LOG( "\nERROR: process_payload_from_rm.add_bot_phrase - unknown parameter 'to': " + to + '.' + "\nPlease, check your code on possible typos." + "\nAlternatively, add logic to process '" + to + "' action correctly.\n") return None # обновление linked_issues в базе данных сервера (tracker_id, status_id, priority_id) def update_linked_issues(linked_issues, issue): linked_issues.tracker_id_rm = issue['tracker_id'] linked_issues.priority_id_rm = issue['priority_id'] # если rejected if (issue['status_id'] == status_ids_rm[4]): linked_issues.is_opened = False linked_issues.status_id_rm = issue['status_id'] # если closed elif (issue['status_id'] == status_ids_rm[5]): linked_issues.is_opened = False # иначе - открываем else: linked_issues.is_opened = True linked_issues.status_id_rm = issue['status_id'] linked_issues.save() # типичные ошибки на этапе проверки: не связаны проекты, задачи, комментарии, неизвестное действие и т.п. def PREPARATION_ERR(error_text): # добавляем, чтобы в начале получилось: 'PREPARATION ERROR' error_text = 'PREPARATION ' + error_text WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'received webhook from REDMINE: issues | ' + 'action: ' + str(issue['action']) + '\n' + error_text) return HttpResponse(error_text, status=200) # логическая ошибка: неизвестное действие, неправильные label-ы в гитхабе и т.п. def LOGICAL_ERR(error_text): # добавляем, чтобы в начале получилось: 'LOGICAL ERROR' error_text = 'LOGICAL ' + error_text WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'received webhook from REDMINE: issues | ' + 'action: ' + str(issue['action']) + '\n' + error_text) return HttpResponse(error_text, status=200) # ============================================= КОМАНДЫ ДЛЯ ЗАГРУЗКИ =============================================== def post_issue(linked_projects, issue): # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------- # дополнительная проверка, что проекты связаны if (linked_projects.count() == 0): error_text = "ERROR: process_payload_from_rm.post_issue\n" +\ "issue " + str(issue['action']) + " in REDMINE, but the project is not linked to GITHUB" return PREPARATION_ERR(error_text) linked_projects = linked_projects[0] repos_id_gh = linked_projects.repos_id_gh url_gh = make_gh_repos_url(repos_id_gh) # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА ----------------------------------------- #title = '[From Redmine] ' + issue['issue_title'] title = issue['issue_title'] issue_body = add_bot_phrase(issue, 'issue_body') # добавляем фразу бота # обработка спец. символов title = align_special_symbols(title) issue_body = align_special_symbols(issue_body) # --------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ---------------------------------------- # добавление label-ов tracker = match_tracker_to_gh(issue['tracker_id']) status = match_status_to_gh(issue['status_id']) priority = match_priority_to_gh(issue['priority_id']) issue_templated = issue_github_template.render(title=title, body=issue_body, priority=priority, status=status, tracker=tracker) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') request_result = requests.post(url_gh, data=issue_templated, headers=headers) # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ -------------------------------------------- posted_issue = json.loads(request_result.text) # занесение в базу данных информацию о том, что данные issues связаны linked_issues = Linked_Issues.objects.create_linked_issues( issue['issue_id'], # id issue в редмайне posted_issue['id'], # id issue в гитхабе repos_id_gh, # id репозитория в гитхабе posted_issue['number'], # номер issue в репозитории гитхаба issue['tracker_id'], # id трекера в редмайне issue['status_id'], # id статуса в редмайне issue['priority_id'], # id приоритета в редмайне True) # открыт # добавляем linked_issues в linked_projects linked_projects.add_linked_issues(linked_issues) # ДЕБАГГИНГ log_issue_post_rm(request_result, issue, linked_issues) return request_result # загрузка комментария. нет необходимости в подготовке, так как запускается из edit_issue # (redmine не различает оставление комментария и изменение issue) def post_comment(linked_issues, issue, url_gh): # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА ----------------------------------------- # нет комментария if (issue['comment_body'] == ''): comment_body = add_bot_phrase( issue, 'comment_body_action') # добавляем фразу бота else: comment_body = add_bot_phrase( issue, 'comment_body') # добавляем фразу бота # обработка спец. символов comment_body = align_special_symbols(comment_body) # --------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ---------------------------------------- comment_templated = comment_github_template.render(body=comment_body) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне comment_templated = comment_templated.encode('utf-8') # добавление issue_id к ссылке issue_comments_url_gh = url_gh + '/' + str( linked_issues.issue_num_gh) + '/comments' request_result = requests.post(issue_comments_url_gh, data=comment_templated, headers=headers) # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ -------------------------------------------- #занесение в базу данных информации о том, что комментарии связаны posted_comment = json.loads(request_result.text) linked_comments = linked_issues.add_comment(issue['comment_id'], posted_comment['id']) # ДЕБАГГИНГ log_comment_rm(request_result, issue, linked_issues, linked_comments) return request_result def edit_issue(linked_projects, issue): # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------- # дополнительная проверка, что проекты связаны if (linked_projects.count() == 0): error_text = "ERROR: process_payload_from_rm.edit_issue\n" +\ "issue " + str(issue['action']) + " in REDMINE, but the project is not linked to GITHUB" return PREPARATION_ERR(error_text) linked_projects = linked_projects[0] repos_id_gh = linked_projects.repos_id_gh url_gh = make_gh_repos_url(repos_id_gh) linked_issues = linked_projects.get_issue_by_id_rm(issue['issue_id']) # дополнительная проверка, что issue связаны if (linked_issues.count() == 0): error_text = "ERROR: process_payload_from_rm.edit_issue\n" +\ "issue " + str(issue['action']) + " in REDMINE, but it's not linked to GITHUB" return PREPARATION_ERR(error_text) linked_issues = linked_issues[0] # добавление label-ов state_gh = "opened" # открыть / закрыть issue tracker = match_tracker_to_gh(issue['tracker_id']) priority = match_priority_to_gh(issue['priority_id']) if (issue['status_id'] != linked_issues.status_id_rm): status = match_status_to_gh(issue['status_id']) if (status == 'Status: closed'): status = match_status_to_gh( linked_issues.status_id_rm ) # не меняем статус (нет label-а closed) state_gh = 'closed' elif (status == 'Status: rejected'): state_gh = 'closed' else: status = match_status_to_gh(linked_issues.status_id_rm) post_comment(linked_issues, issue, url_gh) # ОТПРАВЛЯЕМ КОММЕНТАРИЙ В ГИТХАБ # ----------------------------------------- ОБРАБОТКА ФРАЗЫ БОТА ------------------------------------------- #title = '[From Redmine (edited)] ' + issue['issue_title'] title = issue['issue_title'] # проверяем, если автор issue - бот if (chk_if_rm_user_is_our_bot(issue['issue_author_id'])): issue_body = del_bot_phrase( issue['issue_body']) # удаляем фразу бота else: issue_body = add_bot_phrase(issue, 'issue_body') # добавляем фразу бота # обработка спец. символов title = align_special_symbols(title) issue_body = align_special_symbols(issue_body) # ---------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ---------------------------------------- issue_templated = issue_github_template.render(title=title, body=issue_body, state=state_gh, priority=priority, status=status, tracker=tracker) # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне issue_templated = issue_templated.encode('utf-8') # добавление issue_id к ссылке issue_url_gh = url_gh + '/' + str(linked_issues.issue_num_gh) request_result = requests.patch(issue_url_gh, data=issue_templated, headers=headers) # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ -------------------------------------------- # обновляем информацию в таблице update_linked_issues(linked_issues, issue) # ДЕБАГГИНГ log_issue_edit_rm(request_result, issue, linked_issues) return request_result # привязка комментария на редмайне к гитхабу (да, это костыль) # пришлось привязать к id комментария в фразе бота на редмайне (редмайн не посылает внятный ответ на PUT запрос) def link_comment_to_github(linked_projects, issue): # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------- # дополнительная проверка, что проекты связаны if (linked_projects.count() == 0): error_text = "ERROR: process_payload_from_rm.link_comment_to_github\n" +\ "tried to link comment from REDMINE to GITHUB, but the project is not linked to GITHUB" return PREPARATION_ERR(error_text) linked_projects = linked_projects[0] linked_issues = linked_projects.get_issue_by_id_rm(issue['issue_id']) # дополнительная проверка, что issue связаны if (linked_issues.count() == 0): error_text = "ERROR: process_payload_from_rm.link_comment_to_github\n" +\ "tried to link comment from REDMINE to GITHUB, but the issue is not linked to GITHUB" return PREPARATION_ERR(error_text) linked_issues = linked_issues[0] # определяем действие (определяем, нужно ли привязывать комментарий к гитхабу - # комментарий от бота может оказаться сообщением о действии пользователя на гитхабе action = issue['comment_body'].split(' ')[6] if (action == 'left'): # достаём id комментария в гитхабе comment_id_gh_str = issue['comment_body'].split( '#issuecomment-')[1] comment_id_gh_str = comment_id_gh_str.split(' ')[0] comment_id_gh = int(comment_id_gh_str) # занесение в базу данных информацию о том, что комментарии связаны linked_comments = linked_issues.add_comment( issue['comment_id'], comment_id_gh) responce_text = log_link_comment_crutch(issue, linked_comments) return HttpResponse(responce_text, status=201) else: error_text = prevent_cyclic_comment_rm(issue) return HttpResponse(error_text, status=200) # ============================================ ЗАГРУЗКА ISSUE В GITHUB ============================================= linked_projects = Linked_Projects.objects.get_project_by_id_rm( issue['project_id']) if (issue['action'] == 'opened'): if (chk_if_rm_user_is_our_bot(issue['issue_author_id'])): error_text = prevent_cyclic_issue_rm(issue) return HttpResponse(error_text, status=200) if (allow_issues_post_rm_to_gh): request_result = post_issue(linked_projects, issue) else: error_text = "WARNING: process_payload_from_rm\n" + \ "PROHIBITED ACTION" WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' + 'received webhook from REDMINE: issues | ' + 'action: ' + str(issue['action']) + '\n' + error_text) return HttpResponse(error_text, status=200) elif (issue['action'] == 'updated'): if (chk_if_rm_user_is_our_bot(issue['comment_author_id'])): # попытка связать комментарий на редмайне с гитхабом return link_comment_to_github(linked_projects, issue) # изменение issue + добавление комментария request_result = edit_issue(linked_projects, issue) else: error_text = "ERROR: process_payload_from_rm\n" + \ "WRONG ACTION" return LOGICAL_ERR(error_text) return align_request_result(request_result)