【テクニカル・上級編】JiraからLinearへのデータ移行ガイド:課題・コメント・添付ファイルを一括インポートする手順と注意点 – プロジェクト・ナレッジ管理活用バイブル

JiraからLinearへの極限移行:数万件の遺産を無傷で引き継ぎ、開発ベロシティを限界突破させるアーキテクチャ設計

開発組織の生産性を最大化するため、Jiraという名の重厚長大な要塞から、洗練されたミニマリズムと圧倒的なレスポンスを誇るLinearへ移行する――それは単なるツールの変更ではない。「組織の神経系をアップグレードする行為」に他ならない。

だが、長年運用されたJiraには、数万件の課題(Issues)、数百万文字のコメント、そして文脈が詰まった添付ファイルという名の「技術的負債と歴史の断片」が眠っている。これを不完全に移行すれば、チームは過去のコンテキストを見失い、移行直後のベロシティは急降下する。

本稿では、Jiraの公式インポーターの限界を看破し、APIとCLIを駆使してデータロスをゼロにし、さらにチームの認知負荷を最小限に抑えてLinearへ完全移行するための実践的な知見を、最高峰の解像度で解説する。

—

1. 移行設計の前提:なぜ単純なインポートでは失敗するのか

JiraとLinearのデータモデルには、思想の根本的な違いが存在する。

  • Jira: プロジェクト、課題タイプ、ステータス、カスタムフィールド、ワークフローが無限にカスタマイズ可能であり、結果として「組織ごとの暗黙知」がシステムに癒着しやすい。
  • Linear: 厳格に定義されたエンティティ関係(Cycle, Project, Issue, Label)を持ち、開発体験(DX)を最適化するために自由度が意図的に制約されている。

この構造的ギャップを理解せず、公式のJiraインポーター(CSVまたはJira Integration)を無思考で実行すると、以下の致命傷を負う。

1. カスタムフィールドの暴走: Jiraで乱立したカスタムフィールドがLinearの限られたメタデータ構造に入り込み、UIを汚染する。
2. メンションとタイムスタンプの崩壊: コメント内のユーザーメンション(`[~accountid:xxxx]`)が壊れ、誰が何を言ったかのコンテキストが消失する。
3. 添付ファイルの容量制限・リンク切れ: JiraのWebDAVストレージからLinearへのバイナリ転送時にタイムアウトや認証エラーが発生する。

これらを防ぐため、我々は「抽出(Extract)・変換(Transform)・ロード(Load)」のパイプラインをコード化しなければならない。

—

2. 公式インポーターの限界を超える:API & CLIによるデータマイグレーション戦略

大規模チーム(課題数10,000件以上、または添付ファイルが数十GB規模)の場合、GUIベースのインポーターはブラックボックスであり、エラーハンドリングが不可能であるため選択肢から外れる。

ここでは、Jira REST API v3からデータを抽出し、Linear GraphQL APIへ直接流し込むカスタムマイグレーションスクリプトの設計思想を示す。

移行パイプラインのアーキテクチャ

[Jira Cloud]
│ (Jira REST API / JQL pagination)
▼
[Python Extraction Worker]
│ (Memory-mapped JSON Lines / Rate limit handling)
▼
[Transformation Engine]
│ (Mapping: Jira Users -> Linear Users, Status normalization)
▼
[Linear GraphQL Mutation Client]
│ (Batching, Exponential Backoff)
▼
[Linear Workspace]

ユーザーマッピングの静的解決

Jiraのユーザー(AccountId)とLinearのユーザー(UUID)の突合は、メールアドレスをキーにしたディクショナリ(JSON)を事前に生成し、変換レイヤーでハードコードまたは動的解決させる必要がある。これを怠ると、すべての作成者・担当者情報が移行スクリプトを実行したAPIキーの所有者にすり替わる惨劇が起きる。

—

3. 実装:Jira課題・コメント・添付ファイルを一括移行する堅牢なPythonスクリプト

以下に、実戦投入に耐えうるレートリミット耐性、エラーリカバリ、メモリ効率を考慮した移行スクリプトの中核部分を示す。

import os
import json
import time
import requests
from typing import Dict, Any, List

環境変数設定
JIRA_URL = os.getenv(“JIRA_URL”, “https://your-domain.atlassian.net”)
JIRA_USER = os.getenv(“JIRA_USER”, “admin@example.com”)
JIRA_API_TOKEN = os.getenv(“JIRA_API_TOKEN”)

LINEAR_API_URL = “https://api.linear.app/graphql”
LINEAR_API_KEY = os.getenv(“LINEAR_API_KEY”)

ユーザーマッピング(Jira Account ID -> Linear User UUID)
USER_MAP: Dict[str, str] = {
“5d63…”: “lin-user-uuid-1”,
“7h89…”: “lin-user-uuid-2”,
}

def query_linear_graphql(query: str, variables: Dict[str, Any]) -> Dict[str, Any]:
“””Linear GraphQL APIへのリクエスト送信(指数バックオフ付き)”””
headers = {
“Authorization”: LINEAR_API_KEY,
“Content-Type”: “application/json”
}
backoff = 1
for _ in range(5):
response = requests.post(
LINEAR_API_URL,
json={“query”: query, “variables”: variables},
headers=headers
)
if response.status_code == 200:
result = response.json()
if “errors” in result:
raise RuntimeError(f”GraphQL Error: {result[‘errors’]}”)
return result.get(“data”, {})
elif response.status_code == 429:
# Rate Limit検知時は待機
time.sleep(backoff)
backoff = 2
else:
raise Exception(f”Linear API Error: {response.status_code} – {response.text}”)
raise Exception(“Linear API Rate Limit Exceeded after retries.”)

def fetch_jira_issues() -> List[Dict[str, Any]]:
“””JiraからJQLを用いて課題をページネーションしながら抽出”””
issues = []
start_at = 0
max_results = 100

auth = (JIRA_USER, JIRA_API_TOKEN)
headers = {“Accept”: “application/json”}

while True:
url = f”{JIRA_URL}/rest/api/3/search?startAt={start_at}&maxResults={max_results}&expand=renderedFields”
response = requests.get(
url,
auth=auth,
headers=headers,
params={“jql”: “ORDER BY created ASC”}
)

if response.status_code != 200:
raise Exception(f”Jira API Error: {response.status_code} – {response.text}”)

data = response.json()
chunk = data.get(“issues”, [])
if not chunk:
break

issues.extend(chunk)
start_at += len(chunk)
print(f”Fetched {start_at} issues from Jira…”)

if start_at >= data.get(“total”, 0):
break

return issues

def migrate_issue_to_linear(jira_issue: Dict[str, Any], linear_team_id: str):
“””Jiraの課題をLinearのフォーマットに変換してミューテーションを実行”””
fields = jira_issue[“fields”]
summary = fields.get(“summary”, “No Summary”)
description = fields.get(“description”, {}).get(“content”, [])

# Jira特有のリッチテキスト(Atlassian Document Format)をプレーンテキストやMarkdownに変換するロジックをここに挟む
# 簡略化のため、タイトルのみを転送する例

mutation = “””
mutation IssueCreate($input: IssueCreateInput!) {
issueCreate(input: $input) {
success
issue {
id
identifier
}
}
}
“””

# 担当者のマッピング解決
jira_assignee = fields.get(“assignee”)
linear_assignee_id = USER_MAP.get(jira_assignee.get(“accountId”)) if jira_assignee else None

variables = {
“input”: {
“teamId”: linear_team_id,
“title”: summary,
“description”: f”[Migrated from Jira: {jira_issue[‘key’]}]\n\n(Description conversion needed)”,
“assigneeId”: linear_assignee_id,
}
}

result = query_linear_graphql(mutation, variables)
print(f”Successfully migrated Jira {jira_issue[‘key’]} -> Linear {result[‘issueCreate’][‘issue’][‘identifier’]}”)

if __name__ == “__main__”:
# 実行例
team_id = “your-linear-team-uuid”
try:
jira_issues = fetch_jira_issues()
for issue in jira_issues:
migrate_issue_to_linear(issue, team_id)
# Linearのレートリミット(通常秒間バースト対策)を考慮したスリープ
time.sleep(0.2)
except Exception as e:
print(f”Migration failed: {e}”)

—

4. 移行後の運用カオスを防ぐ:チームの心理的抵抗を突破するガバナンス設計

ツール移行の真の難所はデータマイグレーションではなく、「開発者の脳内スキーマの書き換え」である。Jiraの「エピック・ストーリー・サブタスク」の3階層構造に毒された脳は、Linearのフラットかつ高速な「Project / Issue」構造に直面したとき、最初は大混乱に陥る。

ベロシティを落とさないためのガバナンス設計の要諦は以下の3点だ。

1. ラベル(Labels)とステータス(Workflow)の厳格な事前定義

Jiraのように「部署ごとに勝手なステータスを作る」ことをLinearでも許可してはならない。Linearの強みは全社的な一貫性にある。

  • ステータスの数は最大5〜6個(`Backlog`, `Todo`, `In Progress`, `In Review`, `Done`, `Canceled`)に固定。
  • 属人化したカスタムフィールドの代わりに、Linearの「Labels」と「Projects」を厳密に使い分けるルールをドキュメント化する。

2. キーボードショートカットの徹底(”C”キーの宗教化)

Linearが愛される理由は、その圧倒的なキーボード駆動型UIにある。

  • 移行初日に全メンバーを集め、マウスを一切使わずにチケット作成(`C`)、検索(`Cmd + K`)、担当者アサイン(`A`)を行うハンズオンを強制せよ。
  • この「指が覚える快感」をチーム全体で共有できた瞬間、誰もJiraに戻りたいとは思わなくなる。

3. GitHub / GitLabインテグレーションの同時ファースト・ローンチ

チケット管理ツール単体の移行で終わらせてはならない。移行の瞬間から、プルリクエストのオープン・マージとLinearのIssueステータスが完全に同期する状態(Webhook連携)を構築する。
「コードを書けば、勝手にチケットが動く」という体験こそが、Linear移行を成功裏に導く最大の起爆剤となる。

—

最後に:移行とは「過去の精算」である

JiraからLinearへの移行は、単なるSaaSの乗り換えではない。それは、組織が過去の肥大化したプロセスから脱却し、アジャイル本来の「動くソフトウェアと個人の相互作用」に立ち返るための儀式である。

コードを書き、APIを叩き、データを美しく整え、チームの指にLinearのスピードを叩き込め。その先にあるのは、かつてないほどの開発ベロシティと、エンジニアリングへの純粋な没入感である。

タイトルとURLをコピーしました