【JIRA API+Python】課題作成や課題検索をコードでやる

# 【JIRA API+Python】課題作成や課題検索をコードでやる

## 動機

- JIRAって便利だけど手作業での課題作成が面倒
- APIを使えば自動で行けそう
- 備忘録としてまとめる

## 事前準備

1. APIトークンを取得

    - 取得方法([Atlassian アカウントの API トークンを管理する](https://support.atlassian.com/ja/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/)より引用)

    > 1. <https://id.atlassian.com/manage-profile/security/api-tokens> にログインします。
    > 1. [API トークンを作成] を選択します。
    > 1. API トークンには、その機能を説明する名前を付けてください。
    > 1. API トークンの有効期限を選択します。
        1. トークンの有効期限は 1 〜 365 日です。
    > 1. [作成] を選択します。
    > 1. [クリップボードにコピー] を選択してから、スクリプトにトークンを貼り付けるか、安全な場所に保存します。

2. (オプション)環境変数を設定

    ```sh
    export JIRA_USER_NAME=<your-email-address>
    export JIRA_API_TOKEN=<your-api-token>
    export JIRA_BASE_URL=<https://your-domain.attlasian.net/>
    ```

## JIRA APIの認証方法(Python)

1. ユーザIDとAPIトークンから作った文字列をbase64でエンコード

    - base64へのエンコードは[[Qiita]Python3でBase64エンコードした際の型の扱い](https://qiita.com/cactanaka/items/fffed80dbfdddb4a4fea)を参考にさせていただきました

        ```python
        import os
        import base64
        user_name = os.getenv('JIRA_USER_NAME')
        api_token = os.getenv('JIRA_API_TOKEN')
        auth_info = user_name + ':' + api_token
        encoded_auth_info = base64.b64encode(auth_info.encode()).decode('ascii')
        ```

2. エンコードした認証情報をヘッダーに入れる

    - ヘッダー

        ```python
        headers = {
           'Authorization': 'Basic {}'.format(encoded_auth_info),
           'Content-Type': 'application/json', 
        }
        ```

## 課題新規作成

```python
import requests
jira_base_url = os.getenv('JIRA_BASE_URL')
jira_api_url = jira_url + 'rest/api/3/'
request_url = jira_api_url + 'issue' # 課題の追加の場合

item_data = {
    'fields': {
        'assignee': { # 担当者
            'id': <担当者のアカウントID>
        },
        'issuetype': { # 課題タイプ(タスク、エピック、サブタスク等)
            'id': <課題タイプに応じたID>
        },
        'project': { # プロジェクト
            'key': <プロジェクトのキー>
        },
        'parent': { # 親課題
            'key': <親となる課題のキー>
        },
        'summary': <課題のタイトル>
    }
}

response = requests.post(
    request_url,
    headers=headers,
    json=item_data,
)

status_code = response.status_code

if status_code == 201: # ステータスコードが201のとき作成成功
    result = response.json()
    print('作成された課題のキー: {}'.format(result['key']))
```

- 注意点
  - プロジェクトや課題にはIDがあるが、文字列扱い
  - 課題タイプ(タスク、エピック、サブタスク)に振られているIDは、プロジェクトごとに(多分)異なる
    - 調べる方法は後述

## 他にも色々できます

### 課題のタイプを指定して検索する
  - 使用例：課題タイプが「エピック」の課題のみ調べる

    ```python
    import os
    import requests
    jira_base_url = os.getenv('JIRA_BASE_URL')
    jira_api_url = jira_url + 'rest/api/3/'
    request_url = jira_api_url + 'search'
    item_data = {
        'fields': [ # fieldsで指定すると、記載した情報のみ返す(何も指定しないと全部返ってくる)
            'summary',
            'issuetype',
            'assignee',
        ],
        'fieldsByKeys': False,
        'jql': 'issuetype = {} AND project = {}'.format(<課題タイプに付与されたID>, <プロジェクトのIDorキー>),
        'maxResults': 10, # 何件まで結果を出すか(デフォルトは50)
        'startAt': 0,
    }
    response = requests.post(
        request_url,
        headers=headers,
        json=item_data,
    )

    status_code = response.status_code

    if status_code == 200: # ステータスコードが200のとき検索成功
        result = response.json()
        result_issues = result['issues'] # 検索結果の課題リスト
        for result_issue in result_issues:
            print('課題のキー: {}'.format(result_issue['key']))
            print('課題のタイトル: {}'.format(result_issue['fields']['summary']))
    ```

### プロジェクトごとの課題タイプに付与されているIDを調べる
  - 使用例：「エピック」や「タスク」のIDを調べる

    ```python
    import os
    import requests
    jira_base_url = os.getenv('JIRA_BASE_URL')
    jira_api_url = jira_url + 'rest/api/3/'
    request_url = jira_api_url + 'issuetype/project'
    params = {
        'projectId': <your-project-id>
    }
    response = requests.get(
        request_url,
        headers=headers,
        params=params,
    )

    status_code = response.status_code

    if status_code == 200:
        result = response.json()
        for res in result:
            print('課題タイプの名前: {}'.format(res['name']))
            print('課題タイプのID: {}'.format(res['id']))
    ```