PythonでBacklogのプロジェクト横断ガントチャートを表示してみた。
2018.11.16
Backlogの複数プロジェクトをガントチャート表示してみました。
環境:Ubuntu16.04LTS + Python3 + python_gannt
はじめに
JIG-SAWではnulabのbacklog を使っています。
とても使いやすいツールで、中でもガントチャートの表示が見やすく重宝しております。
しかしながら、記事作成時点で複数プロジェクトを横断した表示ができません。
各自が様々なプロジェクトに参加しているので、チーム内の忙しさみたいなものが把握できず困っていました。
追加要件として、今回やりたい事は以下のような内容です。
・できるだけ簡単に公開したい。
・人手を介さず自動生成したい。
公式対応に期待しつつ、簡単に表示してみました。
結果
まずは結果です。
以下のようなガントチャートが画像で出力されました。
※表示の都合上 png ファイルですが、出力結果は svg ファイルとなります。
実装
環境
色々と検討しましたが Python のライブラリ発見したので作ってみる事にしました。
https://xael.org/pages/python-gantt-en.html
環境は Ubuntu16.04LTS + Python3 + Python-Gantt になります。
全体像
処理の全体的な流れから説明します。
1. backlog から表示対象の課題を取得する。
2. Python-Gantt の Task に課題を登録する。
3. Task をソートする。
4. Python-Gantt の Project に Task を登録する。
5. 画像を出力する。
# 1. backlog から表示対象の課題を取得する。 result = webAPICall(api, param) # 2. python-gantt の Task に課題を登録する。 task_list = create_task_list(result) # 3. Task をソートする。 task_list = task_sort(task_list) # 4. python-gantt の Project に Task を登録する。 project = create_project(task_list, filename) # 5. 画像を出力する。 create_svg(project, filename)
1. backlog から表示対象の課題を取得する。
backlog からの課題取得は backlog API を利用しています。
https://developer.nulab-inc.com/ja/docs/backlog/
条件を指定して、対象の課題を JSON 形式で取得します。
import requests import json # backlog APIキー apiKey = '************************' # backlog URL base_url = 'https://backlog.url.jp{}apiKey={}' # backlog API 選択 api = '/api/v2/issues?' # backlog API 検索条件 param = "&count={}".format(100) param = param + "&assigneeId[]={}".format(253156) param = param + "&assigneeId[]={}".format(84848) param = param + "&assigneeId[]={}".format(267795) param = param + "&assigneeId[]={}".format(107914) param = param + "&assigneeId[]={}".format(9597) param = param + "&assigneeId[]={}".format(254058) param = param + "&statusId[]={}".format(1) param = param + "&statusId[]={}".format(2) param = param + "&statusId[]={}".format(3) def webAPICall(api, param): # URLを組み立てる。 url = base_url.format(api, apiKey) + param # 読み込むオブジェクトの作成 readObj = requests.get(url) # webAPIからのJSONを取得 response = json.loads(readObj.text) # JSONを返す return response
2. python-gantt の Task に課題を登録する。
ここから Python-Gantt を使って、 backlog から取得した JSON を加工していきます。
とはいえ、 Python-Gantt のサンプルソースがそのまま使えます。
苦労したのは Python-Gantt 関係ない部分でした。
backlog の課題をガントチャートのどの日付に表示するか?という問題があったので、下記のようにしました。
・開始日と期限日が設定されていれば、その期間を表示する。
・開始日だけ設定されていれば、開始日の1日だけ表示する。
・期限日だけ設定されていれば、期限日の1日だけ表示する。
・開始日も期限日も設定されていなければ、表示しない。
from datetime import datetime as dt from datetime import timedelta import gantt # 出力ファイル名 filename = "all schedule" def create_task_list(tasks): # フォントを変更します。 gantt.define_font_attributes(fill='black', stroke='black', stroke_width=0, font_family="Verdana") task_list = [] # 課題の分だけ繰り返します。 for key in tasks: # 表示項目は キー + タイトル とします。 name = "{} {}".format(key["issueKey"], key["summary"].replace("\t", "")) time_format = '%Y-%m-%dT%H:%M:%SZ' if (key["startDate"] is None): if (key["dueDate"] is None): continue else: start = dt.strptime(key["dueDate"], time_format) duration = 1 else: start = dt.strptime(key["startDate"], time_format) if (key["dueDate"] is None): duration = 1 else: dueDate = dt.strptime(key["dueDate"], time_format) duration = (dueDate - start) // timedelta(days=1) percent_done = 0 # 今回は進捗率は使いません。 resources = [] resources.append(gantt.Resource(key["assignee"]["name"])) color = "#FF8080" t = gantt.Task(name=name, start=start, duration=duration, percent_done=percent_done, resources=resources, color=color) task_list.append(t) return task_list
3. Task をソートする。
ソートするには Task の開始日など必要になります。
これらの取得方法も Python-Gantt で用意されていました。
なお、私はドキュメント上から取得方法など見つけられませんでした。
公式サイトにてライブラリのソースも公開されております。
(少し長いですが)難しくありませんでしたので、困ったら読んでみる事をオススメします。
ソートアルゴリズムは簡単な選択ソートです。
def task_sort(task_list): if len(task_list) == 1: return task_list max = None max_num = 0 cnt = 0 for task in task_list: if (max is None): max = task continue cnt += 1 if (max.start_date() > task.start_date()): continue max = task max_num = cnt max = task_list.pop(max_num) res_list = task_sort(task_list) res_list.append(max) return res_list
4. python-gantt の Project に Task を登録する。
ほとんどサンプル通りです。
def create_project(task_list, filename): # Create a project p = gantt.Project(name=filename) for task in task_list: p.add_task(task) return p
5. 画像を出力する。
こちらもほとんどサンプル通りです。
一番右側の課題の キー + タイトル が見えるように表示範囲の終了側を少しだけ伸ばしました。
def create_svg(project, filename): gant_today = dt.now() gant_today = gant_today.replace(hour=0, minute=0, second=0, microsecond=0) gant_start = task_list[0].start_date() gant_end = task_list[-1].end_date() + timedelta(days=14) project.make_svg_for_tasks(filename=filename+'.svg', today=gant_today, start=gant_start, end=gant_end)
ソース(全体)
ソースを置いておきますので、よかったら動かしてみてください。
from datetime import datetime as dt from datetime import timedelta import gantt import requests import json # 出力ファイル名 filename = "all schedule" # backlog APIキー apiKey = '************************' # backlog URL base_url = 'https://backlog.url.jp{}apiKey={}' # backlog API 選択 api = '/api/v2/issues?' # backlog API 検索条件 param = "&count={}".format(100) param = param + "&assigneeId[]={}".format(253156) param = param + "&assigneeId[]={}".format(84848) param = param + "&assigneeId[]={}".format(267795) param = param + "&assigneeId[]={}".format(107914) param = param + "&assigneeId[]={}".format(9597) param = param + "&assigneeId[]={}".format(254058) param = param + "&statusId[]={}".format(1) param = param + "&statusId[]={}".format(2) param = param + "&statusId[]={}".format(3) def webAPICall(api, param): # URLを組み立てる。 url = base_url.format(api, apiKey) + param # 読み込むオブジェクトの作成 readObj = requests.get(url) # webAPIからのJSONを取得 response = json.loads(readObj.text) # JSONを返す return response def task_sort(task_list): if len(task_list) == 1: return task_list max = None max_num = 0 cnt = 0 for task in task_list: if (max is None): max = task continue cnt += 1 if (max.start_date() > task.start_date()): continue max = task max_num = cnt max = task_list.pop(max_num) res_list = task_sort(task_list) res_list.append(max) return res_list def create_project(task_list, filename): # Create a project p = gantt.Project(name=filename) for task in task_list: p.add_task(task) return p def create_svg(project, filename): gant_today = dt.now() gant_today = gant_today.replace(hour=0, minute=0, second=0, microsecond=0) gant_start = task_list[0].start_date() gant_end = task_list[-1].end_date() + timedelta(days=14) project.make_svg_for_tasks(filename=filename+'.svg', today=gant_today, start=gant_start, end=gant_end) def create_task_list(tasks): # フォントを変更します。 gantt.define_font_attributes(fill='black', stroke='black', stroke_width=0, font_family="Verdana") task_list = [] # 課題の分だけ繰り返します。 for key in tasks: # 表示項目は キー + タイトル とします。 name = "{} {}".format(key["issueKey"], key["summary"].replace("\t", "")) time_format = '%Y-%m-%dT%H:%M:%SZ' if (key["startDate"] is None): if (key["dueDate"] is None): continue else: start = dt.strptime(key["dueDate"], time_format) duration = 1 else: start = dt.strptime(key["startDate"], time_format) if (key["dueDate"] is None): duration = 1 else: dueDate = dt.strptime(key["dueDate"], time_format) duration = (dueDate - start) // timedelta(days=1) percent_done = 0 # 今回は進捗率は使いません。 resources = [] resources.append(gantt.Resource(key["assignee"]["name"])) color = "#FF8080" t = gantt.Task(name=name, start=start, duration=duration, percent_done=percent_done, resources=resources, color=color) task_list.append(t) return task_list # 1. backlog から表示対象の課題を取得する。 result = webAPICall(api, param) # 2. python-gantt の Task に課題を登録する。 task_list = create_task_list(result) # 3. Task をソートする。 task_list = task_sort(task_list) # 4. python-gantt の Project に Task を登録する。 project = create_project(task_list, filename) # 5. 画像を出力する。 create_svg(project, filename)
まとめ
Python-Gantt は日本語の情報が少なかったので記事にしようと思ったのですが、
Python初心者でも簡単に作る事ができました。
また、今回は画像での出力となりました。
画像ということで、こんなメリット/デメリットがありました。
■メリット
・公開する為にサーバーを準備する必要がない。
・既存のwiki等に張り付けできる。
■デメリット
・リンククリックで直接課題にアクセスできない。
・画像がSVG形式なので、ツール側で対応していない場合がある。
今回は俯瞰する事が目的だったので、そこそこ時間かけずに実現できたかと思います。
他にも手法あると思いますので、色々試してみると面白いと思います。