Если вы когда-нибудь проводили полдня, вручную экспортируя данные из Jira, копируя их в электронную таблицу, и задавались вопросом, почему не стали барменом, эта статья для вас. Сегодня мы углубимся в создание уровня автоматизации управления проектами с помощью Jira API и Python, по сути, создав своего собственного цифрового помощника, который никогда не жалуется на повторяющиеся задачи.

Преимущество Jira REST API в том, что он открывает мир возможностей. Хотите автоматически синхронизировать задачи между проектами? Создавать массовые отчёты одним кликом мыши? Запускать рабочие процессы на основе внешних событий? С Python и Jira API вы не просто менеджер проектов — вы разработчик (крутой, а не тот, который «застрял в отладке в 2 часа ночи»).

Понимание архитектуры Jira: фундамент

Прежде чем начать писать код, который вызовет зависть у коллег, давайте разберёмся, с чем мы имеем дело. Jira работает на иерархической структуре, которая, если честно, у меня заняла больше времени на понимание, чем следовало бы.

Проекты служат вашими контейнерами верхнего уровня — думайте о них как о разных отделах или продуктовых линейках. Доски — это место, где происходит визуальное волшебство, отображающее ваши задачи в рабочем процессе, соответствующем процессу вашей команды. Задачи — это атомарные единицы: ошибки, функции, задачи, всё, что не даёт вам спать по ночам. Рабочие процессы определяют путь, который проходит каждая задача, а схемы контролируют правила, регулирующие разрешения, уведомления и настройки полей.

Вот визуальное представление того, как взаимодействуют эти компоненты:

graph TD A["Jira Project"] --> B["Board"] A --> C["Issues"] C --> D["Workflow"] C --> E["Schemes"] B --> F["Sprint"] F --> G["Issue States"] D --> H["Status Transitions"] E --> I["Permissions"] E --> J["Notifications"]

Теперь, когда мы разобрались в структуре, давайте поговорим о реальной работе.

Настройка среды: погружение в работу

Вам понадобится несколько вещей, прежде чем мы сможем продолжить:

  • 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