Asana のあるプロジェクトから未完了タスクの一覧を出す

Python で Asana から未完了タスクの一覧を出す方法を紹介します。

ここでは、 Python で、スプレッドシートに貼り付けるためのデータを取得します。

## 作りたい表の形

Asanaのタスクから、次のようなスプレドシートの表を作ることにします。

| Section | Task | Status |
|:--|:--|:--|
| セクション1 | タスク1 | ステータス1 |
| セクション1 | タスク2 | ステータス1 |
| セクション1 | タスク3 | ステータス2 |
| セクション1 | タスク4 | ステータス2 |
| セクション2 | タスク5 | ステータス1 |
| セクション2 | タスク6 | ステータス1 |
| セクション2 | タスク7 | ステータス2 |
| セクション2 | タスク8 | ステータス2 |
| セクション3 | タスク9 | ステータス2 |
| セクション3 | タスク10 | ステータス3 |


## 環境

* Python 3.9.0
* pyperclip
* asana

## コード

一連のコードです。

```python
PERSONAL_ACCESS_TOKEN='XXXXXXXX'
PROJECT_ID='XXXXXXXX'

import asana

client = asana.Client.access_token(PERSONAL_ACCESS_TOKEN)

def find_sections(project_id):
    result = client.sections.get_sections_for_project(project_id, {}, opt_pretty=True)
    return result

def find_tasks(project_id): 
    result = client.tasks.get_tasks(
        {
            'completed_since': 'now',
            'opt_fields' : [
                'this.name', 'this.due_on',
                'this.custom_fields',
                'this.assignee',
                'this.assignee.name',
                'this.memberships.section',
                'this.memberships.section.name'
            ],
            'project': project_id
        },
        opt_pretty=False
    )
    return result

section_ids = [section['gid'] for section in find_sections(PROJECT_ID)]

text = 'Section\tTask\tStatus\n'
# i = 1
for task in find_tasks(PROJECT_ID):
    sections = list(
        filter(
            lambda x: x['section']['gid'] in section_ids,
            filter(lambda x: 'section' in x, task['memberships'])
        )
    )
    section = sections[0]['section'] if len(sections) > 0 else None
    
    statuses = list(filter(lambda x: x['gid'] == '1234567890123456', task['custom_fields']))
    status = statuses[0]['enum_value'] if len(statuses) > 0 else None
    
    # if i == 1:
    #    print(task['custom_fields'])
    #    i += 1

    text += f'{section["name"] if section is not None else None}\t' + \
        f'{task["name"]}\t' + \
        f'{status["name"] if status is not None else None}'
    text += "\n"

import pyperclip
pyperclip.copy(text)
```

### 説明

まずアクセストークンとプロジェクトIDを格納します。

```python
PERSONAL_ACCESS_TOKEN='XXXXXXXX'
PROJECT_ID='XXXXXXXX'
```

Asana のクライアントを作ります。

```python
import asana

client = asana.Client.access_token(PERSONAL_ACCESS_TOKEN)
```

クライアントを利用して、あるプロジェクト内のセクションの一覧と、あるプロジェクト内のタスクの一覧を取得する関数を作ります。
タスクの一覧を取得するときに、セクション名も取れるのですが、そのときにタスクが複数のセクションに所属していると、それらがすべて取得できてしまいます。
そういった場合に、表示するべきセクションを絞り込むために、あるプロジェクトないのセクションを一度取得しています。

`find_tasks` では、 完了日の条件のみを指定しています。
タスクの基本情報と一緒に取得したいデータを、 `opt_fields` に設定しています。 どんなフィールドが使えるのかは、 `get_task` の レスポンスの構造を見るとわかります。

```python
def find_sections(project_id):
    result = client.sections.get_sections_for_project(project_id, {}, opt_pretty=True)
    return result

def find_tasks(project_id): 
    result = client.tasks.get_tasks(
        {
            'completed_since': 'now',
            'opt_fields' : [
                'this.name', 'this.due_on',
                'this.custom_fields',
                'this.assignee',
                'this.assignee.name',
                'this.memberships.section',
                'this.memberships.section.name'
            ],
            'project': project_id
        },
        opt_pretty=False
    )
    return result
```

セクションをすべて取得して、そのIDをリストにしておきます。
その後、プロジェクト内の未完了タスクをすべて取得して、表示する項目を整理して、 `text` にタブ区切りの文字列を生成します。

```python
section_ids = [section['gid'] for section in find_sections(PROJECT_ID)]

text = 'Section\tTask\tStatus\n'
# i = 1
for task in find_tasks(PROJECT_ID):
    sections = list(
        filter(
            lambda x: x['section']['gid'] in section_ids,
            filter(lambda x: 'section' in x, task['memberships'])
        )
    )
    section = sections[0]['section'] if len(sections) > 0 else None
    
    statuses = list(filter(lambda x: x['gid'] == '1234567890123456', task['custom_fields']))
    status = statuses[0]['enum_value'] if len(statuses) > 0 else None
    
    # if i == 1:
    #     print(task['custom_fields'])
    #     i += 1

    text += f'{section["name"] if section is not None else None}\t' + \
        f'{task["name"]}\t' + \
        f'{status["name"] if status is not None else None}'
    text += "\n"
```

ここで `status` は、カスタマイズとして追加されたフィールドです。
カスタマイズされたフィールドは、 `task` の `custom_fields` に含まれて返ってきます。
そこで、表示したいカスタムフィールドの `gid` で値をフィルタ・表示しています。

コメントアウトされている箇所をコメントインすると、 カスタムフィールドの一覧が表示されるので、`gid` を調べることができるようになります。

最後に、組み立てた文字列をクリップボードにコピーします。

```python
import pyperclip
pyperclip.copy(text)
```

コピーした文字列を Excel などに貼り付けると、 表として表示されます。