Если вы когда-нибудь проводили полдня, вручную экспортируя данные из Jira, копируя их в электронную таблицу, и задавались вопросом, почему не стали барменом, эта статья для вас. Сегодня мы углубимся в создание уровня автоматизации управления проектами с помощью Jira API и Python, по сути, создав своего собственного цифрового помощника, который никогда не жалуется на повторяющиеся задачи.
Преимущество Jira REST API в том, что он открывает мир возможностей. Хотите автоматически синхронизировать задачи между проектами? Создавать массовые отчёты одним кликом мыши? Запускать рабочие процессы на основе внешних событий? С Python и Jira API вы не просто менеджер проектов — вы разработчик (крутой, а не тот, который «застрял в отладке в 2 часа ночи»).
Понимание архитектуры Jira: фундамент
Прежде чем начать писать код, который вызовет зависть у коллег, давайте разберёмся, с чем мы имеем дело. Jira работает на иерархической структуре, которая, если честно, у меня заняла больше времени на понимание, чем следовало бы.
Проекты служат вашими контейнерами верхнего уровня — думайте о них как о разных отделах или продуктовых линейках. Доски — это место, где происходит визуальное волшебство, отображающее ваши задачи в рабочем процессе, соответствующем процессу вашей команды. Задачи — это атомарные единицы: ошибки, функции, задачи, всё, что не даёт вам спать по ночам. Рабочие процессы определяют путь, который проходит каждая задача, а схемы контролируют правила, регулирующие разрешения, уведомления и настройки полей.
Вот визуальное представление того, как взаимодействуют эти компоненты:
Теперь, когда мы разобрались в структуре, давайте поговорим о реальной работе.
Настройка среды: погружение в работу
Вам понадобится несколько вещей, прежде чем мы сможем продолжить:
- Python 3.7+ установлен на вашем компьютере (в идеале не Python 2, который вышел из эксплуатации в 2020 году и, вероятно, не вернётся, чтобы преследовать нас);
- Экземпляр Jira (облачный или серверный) с правами администратора;
- Токен API из вашей учётной записи Atlassian;
- Библиотека
requestsдля HTTP-операций.
Давайте начнём с установки:
pip install requests
pip install jira
Библиотека requests даёт нам низкоуровневый контроль над HTTP, а библиотека jira предоставляет высокоуровневые абстракции. Мы будем использовать обе, потому что иногда вам нужна удобство, а иногда точность.
Аутентификация: ваш ключ к хранилищу сокровищ Jira
Прежде чем Jira посмотрит на наши запросы, нам нужно пройти аутентификацию. Есть несколько способов сделать это, но самый надёжный — аутентификация по токену API. Вот как:
Сначала сгенерируйте токен API из вашей учётной записи Atlassian:
- Перейдите в настройки учётной записи в Atlassian;
- Выберите «Безопасность» в левом меню;
- Нажмите «Создать и управлять своими токенами API»;
- Сгенерируйте новый токен и сохраните его в надёжном месте (например, в менеджере паролей, но не в комментариях к коду — я видел, как такое происходило).
Теперь давайте создадим наш первый помощник по аутентификации:
import requests
import json
from requests.auth import HTTPBasicAuth
class JiraConnection:
def __init__(self, domain, email, api_token):
self.domain = domain
self.email = email
self.api_token = api_token
self.base_url = f"https://{domain}.atlassian.net/rest/api/3"
self.headers = {
"Content-Type": "application/json",
"Accept": "application/json"
}
def make_request(self, method, endpoint, data=None, params=None):
url = f"{self.base_url}/{endpoint}"
auth = HTTPBasicAuth(self.email, self.api_token)
try:
if method == "GET":
response = requests.get(url, headers=self.headers, auth=auth, params=params)
elif method == "POST":
response = requests.post(url, headers=self.headers, auth=auth, json=data)
elif method == "PUT":
response = requests.put(url, headers=self.headers, auth=auth, json=data)
else:
raise ValueError(f"Неподдерживаемый метод: {method}")
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Ошибка запроса: {e}")
return None
Этот класс инкапсулирует логику аутентификации и предоставляет чистый интерфейс для выполнения аутентифицированных запросов. Это как иметь вышибалу, который знает вас и всегда пропускает в лучшие части клуба Jira.
Получение проектов и задач: операция чтения
Теперь мы можем на самом деле поговорить с Jira. Давайте начнём с получения всех проектов в вашем экземпляре:
jira_conn = JiraConnection("ваш-домен", "ваш[email protected]", "ваш-токен-api")
def get_all_projects():
response = jira_conn.make_request("GET", "project")
if response:
for project in response:
print(f"Проект: {project['name']} (Ключ: {project['key']})")
print(f" Тип: {project['projectTypeKey']}")
print(f" Руководитель: {project.get('lead', {}).get('displayName', 'Н/Д')}")
print()
get_all_projects()
Теперь давайте углубимся. Получение всех задач из конкретного проекта — вот где становится интересно:
def get_issues_by_project(project_key, max_results=50):
params = {
"jql": f"project = {project_key}",
"maxResults": max_results,
"expand": "changelog"
}
issues = []
start_at = 0
while True:
params["startAt"] = start_at
response = jira_conn.make_request("GET", "search", params=params)
if not response or "issues" not in response:
break
issues.extend(response["issues"])
if len(response["issues"]) < max_results:
break
start_at += max_results
return issues
def display_issues(issues):
for issue in issues:
print(f"[{issue['key']}] {issue['fields']['summary']}")
print(f" Статус: {issue['fields']['status']['name']}")
print(f" Исполнитель: {issue['fields']['assignee']['displayName'] if issue['fields']['assignee'] else 'Не назначен'}")
print(f" Приоритет: {issue['fields']['priority']['name']}")
print()
issues = get_issues_by_project("ПРОЕК")
display_issues(issues)
Логика пагинации здесь имеет решающее значение — Jira ограничивает ответы, чтобы предотвратить перегрузку API, поэтому нам нужно реализовать правильную пагинацию для получения всех задач.
Создание и изменение задач: операция записи
Чтение данных — это весело, но создание задач программно? Вот где начинается настоящее волшебство автоматизации.
def create_issue(project_key, issue_type, summary, description, priority="Средний", assignee=None):
data = {
"fields": {
"project": {"key": project_key},
"issuetype": {"name": issue_type},
"summary": summary,
"description": description,
"priority": {"name": priority}
}
}
if assignee:
data["fields"]["assignee"] = {"id": assignee}
response = jira_conn.make_request("POST", "issue", data=data)
if response:
print(f"Задача создана успешно: {response['key']}")
return response['key']
return None
new_issue = create_issue(
"ПРОЕК",
"Ошибка",
"Кнопка входа не реагирует на мобильных устройствах",
"Кнопка входа не реагирует на клики на мобильных устройствах",
"Высокий"
)
Давайте также реализуем пакетные операции, потому что иногда вам нужно создать несколько задач, не уронив пальцы:
def bulk_create_issues(project_key, issues_data):
created_keys = []
for issue_data in
