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形式なので、ツール側で対応していない場合がある。
今回は俯瞰する事が目的だったので、そこそこ時間かけずに実現できたかと思います。
他にも手法あると思いますので、色々試してみると面白いと思います。



