nulab Backlog で管理しているプロジェクトのデータをバックアップした方法について紹介します。
nulab の Backlog は、 シンプルで使いやすいところはあるのですが、 プロジェクトのサイズが大きい場合に、 課題の階層が2階層しか作れず、 ほかのプロジェクト管理ツールに移したいときがあったりします。 そんなときに、 Backlog のデータのインポートまではしないにしても、過去の記述がわからなくなった場合のために、プロジェクト全体をコピーしておけると安心です。 ただし、課題を検索して保存するだけでは、添付ファイルが保存されません。 そこで、 Python を使って添付ファイルもまとめてバックアップします。
Environment
- Python
Code
まず、プロジェクトの情報、API Key を backlog_credential.py に保存します。
|
1 2 3 |
api_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' project_key = 'PROJECT' url_base = 'https://xxxxxxxxx.backlog.com' |
そして次のプログラムを実行します。 これにより、課題、コメント、添付ファイルがJSONとして保存されます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
from backlog_credential import * from pathlib import Path import requests import time import os SLEEP_TIME = 1 DEBUG = False def build_get_url(path, params = {}): url = url_base + path + '?apiKey=' + api_key for key in params: url += '&' + key + '=' + str(params[key]) return url def get_all_projects(): url = build_get_url('/api/v2/projects') response = requests.get(url) response.raise_for_status() return response.json() def get_project_id(project_key): for project in get_all_projects(): if project['projectKey'] == project_key: return project['id'] raise Exception('Project not found') def get_issue_list(project_id, offset = 0): params = { 'projectId[]': project_id, 'offset': offset, 'count': 100, 'sort': 'created', } url = build_get_url('/api/v2/issues', params) result = requests.get(url) # raise exception if not success result.raise_for_status() time.sleep(SLEEP_TIME) if DEBUG: print("headers:", result.headers) return result.json() def get_issue(issue_id): url = build_get_url('/api/v2/issues/' + str(issue_id)) result = requests.get(url) time.sleep(SLEEP_TIME) return result.json() def get_all_issues(project_id, offset=0): offset = 0 issues = [] while True: result = get_issue_list(project_id, offset) issues += result if len(result) < 100: break offset += 100 return issues def get_issue_comments(issue_key): params = { 'count': 100, 'order': 'asc', } url = build_get_url( '/api/v2/issues/' + issue_key + '/comments', params) result = requests.get(url) # raise exception if not success result.raise_for_status() return result.json() def get_attached_file_list(issue_key): url = build_get_url( '/api/v2/issues/' + issue_key + '/attachments') result = requests.get(url) time.sleep(SLEEP_TIME) return result.json() def get_and_save_attached_file(issue_key, attachment_id, filepath): url = build_get_url( '/api/v2/issues/' + issue_key + '/attachments/' + str(attachment_id)) result = requests.get(url) time.sleep(SLEEP_TIME) with open(filepath, 'wb') as f: f.write(result.content) return result # create issue directory, and save the content as content.json def backup_issue(issue, backup_dir): issue_id = issue['issueKey'] issue_path = os.path.join(backup_dir, 'issues', str(issue_id)) if not os.path.exists(issue_path): os.makedirs(issue_path) with open(os.path.join(issue_path, 'content.json'), 'w') as f: f.write(str(issue)) # get all comments and save it as comments.json comments = get_issue_comments(issue_id) with open(os.path.join(issue_path, 'comments.json'), 'w') as f: f.write(str(comments)) # get attached file list and save it as attached_files.json attached_files = get_attached_file_list(issue_id) with open(os.path.join(issue_path, 'attached_files.json'), 'w') as f: f.write(str(attached_files)) # download attached files for file in attached_files: get_and_save_attached_file( issue_id, file['id'], os.path.join(issue_path, file['name'])) def backup_project(project_key, backup_dir='backup'): backup_dir = Path(backup_dir) if not backup_dir.exists(): backup_dir.mkdir() project_id = get_project_id(project_key) issues = get_all_issues(project_id) for issue in issues: backup_issue(issue, backup_dir) if __name__ == '__main__': backup_project(project_key) |
2024年頃に作ったもので、当時の nulab の API を前提としています。
関数 backup_project は、 2番目の引数にバックアップ先のディレクトリを渡すことができます。


