Node.js で Jira の API から課題情報を取得する
モチベーション
- Jira の課題には StoryPoint を設定していたが、Jira の画面上だけでは必要な情報を必要な形で取ってこれなかった
- 今回のスプリントや前回のスプリントでどれだけの StoryPoint を消化できたかを知りたかった(これは Jira のデフォルトの機能にもあるが、色々と絞り込み条件をカスタマイズしたかった)
- プロジェクトの全体の StoryPoint を消化するためには、あと何スプリント必要なのかを知りたかった(プロジェクトの予測を数値にして報告するため)
- Jira の課題を Excel に出力するプラグインはあるが、データが複雑なので、 Excel で頑張るよりも、API で取得して TypeScript でいじった方が簡単そうだった
上記の理由から、Jira の API から課題を取得することにしました。
API トークンを作成
まずは API トークンを作成します。
JIRA 画面の右上プロフィールをクリック
↓
設定 をクリック
↓
アカウントの設定(アカウントの環境設定)をクリック
↓
左側の「セキュリティ」タブをクリック
↓
API トークンの作成と管理 をクリック
↓
API トークンを作成する をクリック
ここで API トークンを作成し、コピーしておきます。
jira-client でアクセス
Jira の API は jira-client を使って叩きます。
npm i dotenv
npm i jira-client
npm i -D @types/jira-client
.env
ファイルに以下を用意します。
JIRA_HOST="[会社のアカウント].atlassian.net"
MAIL_ADDRESS="ユーザー名に使っているメールアドレス"
JIRA_API_TOKEN="設定画面から取得したAPIトークン"
PROJECT_NAME="Jiraのプロジェクト名。 課題のJQLでproject = 'XXX'で指定されているやつ"
API から Jira の課題を取得するサンプルは以下のようになります。
import JiraApi, { JsonResponse } from "jira-client";
import dotenv from "dotenv";
dotenv.config();
const host = process.env.JIRA_HOST;
const mailAddress = process.env.MAIL_ADDRESS;
const apiToken = process.env.JIRA_API_TOKEN;
const projectName = process.env.PROJECT_NAME;
let jiraClient: JiraApi | undefined;
function getJiraClient() {
if (
host === undefined ||
mailAddress === undefined ||
apiToken === undefined
) {
throw new Error("環境変数が設定されていません。");
}
if (jiraClient === undefined) {
jiraClient = new JiraApi({
protocol: "https",
host,
username: mailAddress,
password: apiToken,
apiVersion: "2",
strictSSL: true,
});
} else {
console.log("jiraClient はキャッシュされています。");
}
return jiraClient;
}
export async function getAllIssues() {
const client = getJiraClient();
if (client === undefined) {
throw new Error("jiraClient が生成されていません。");
}
if (projectName === undefined) {
throw new Error("プロジェクト名が環境変数に設定されていません。");
}
const jql = `project = "${projectName}" ORDER BY created DESC`;
const jiraResponse: JsonResponse = await client.searchJira(jql, {
startAt: 0,
maxResults: 10,
});
const jira: Jira = {
startAt: jiraResponse.startAt,
maxResults: jiraResponse.maxResults,
total: jiraResponse.total,
issues: jiraResponse.issues,
};
console.log(jira.total);
}
以下の部分で maxResults
を設定しています。
最大が 100
です。
await client.searchJira(jql, {
startAt: 1,
maxResults: 10,
});
そのため、100 以上の課題をすべて取得する場合は、 (total / maxResults) + 1
回分 リクエストを投げる必要があります。
Jira のレスポンスのうち、必要なものの型をつけていく
Jira からのレスポンスは any 型で定義されています。
型定義は以下です。
interface JsonResponse {
[name: string]: any;
}
実際にレスポンスを見ても、以下のような不定形と思われるプロパティが多々あります。
customfield_10128: null,
customfield_10007: null,
customfield_10129: null,
これらのフィールド名は、「自分のプロジェクト」だけでなく、「社内の他のプロジェクトで設定したフィールド」も含まれています。
つまり、社内のプロジェクト間で Jira の使い方が統一されていない場合は、自分のプロジェクトにとっては不要な(他のプロジェクトで作成した)カスタムフィールドが大量に紛れ込んでくるということです。
自分のプロジェクトで使っていないフィールドの値は null
になっていました。
その他にも issues
の中に fields
があって、 その fields
が持っている parent
が issue
と同じ型になっていて使いづらかったり、色々とフィールドが入れ子になっていたりと、そのままだと非常に使いにくいように感じました。
そんなわけで、型を自分で定義して、レスポンスを型にはめていけば、API から取得したデータの利用が楽になります。
業務的なの情報が入らないようにコードを抜粋すると、こんな感じに型を作ります。
export type Jira = {
startAt: number
maxResults: number
total: number
issues: Issue[]
}
export type Issue = {
id: string
key: string
fields: Fields
}
export type Fields = {
parent: Parent
summary: string
status: Status
targetServices: TargetService[] // ここは会社特有のもの。fields.customfield_xxxx の値を入れる
sprints: Sprint[]
storyPoint: number // fields.customfield_yyyy の値を入れる
created: string
updated: string
description: string
statuscategorychangedate: string
}
上の型をつける作業は、プロパティ(フィールド)が不定であるがゆえに、レスポンスの値を見ながら各自が頑張って設定することになります。
頑張って型をつけるサンプルは以下のようなイメージです。
// サンプルです
function otharfunc() {
const jira: Jira = {
startAt: jiraResponse.startAt,
maxResults: jiraResponse.maxResults,
total: jiraResponse.total,
issues: jiraResponse.issues,
}
// 型をつける関数を呼んでる
const typedJira = getTypedJira(jira)
}
function getTypedJira(jira: any): Jira {
const typedIssues: Issue[] = jira.issues.map((issue: Issue) => {
const fields: any = issue.fields
const sprints = fields.customfield_xxxx.map((sprint: any) => {
return {
name: sprint.name,
state: sprint.state,
startDate: sprint.startDate,
endDate: sprint.endDate,
}
})
const typedFields: Fields = {
parent: {
summary: fields.parent.fields.summary,
},
summary: fields.summary,
status: fields.status,
targetServices: fields.customfield_xxxx,
sprints,
storyPoint: fields.customfield_yyy,
created: fields.created,
updated: fields.updated,
description: fields.description,
statuscategorychangedate: fields.statuscategorychangedate,
}
return {
id: issue.id,
key: issue.key,
fields: typedFields,
}
})
const typedJira: Jira = {
startAt: jira.startAt,
maxResults: jira.maxResults,
total: jira.total,
issues: typedIssues,
}
return typedJira
}
上の例で型をつけた typedJira
は元々の Jira のレスポンスが反映された形になっているので、ネストが多くて若干使いづらいです。
そのため、自分は typedJira
の issues[n].fields
から本当にほしい情報を取ってきて、別のオブジェクトに入れて使っています(文章だと伝わりにくいですね)
コード例を記事にしたいところですが、ここまでいくと仕事に寄りすぎてしまうので、泣く泣く割愛します。
各自いい感じに型をつけて Jira のレスポンスを使うのが良いと思います。
あとは個人的な工夫点として、型をつけた Jira のレスポンスはキャッシュしておく、JSON ファイルに日付をつけて保存しておくようにしました。
何度も Jira にリクエストを投げてもそんなに頻繁には結果は変わらないからです。
同じ日のレスポンスはキャッシュから、キャッシュがないときはローカルの JSON ファイルを読み込んで返すようにしました。
Author And Source
この問題について(Node.js で Jira の API から課題情報を取得する), 我々は、より多くの情報をここで見つけました https://zenn.dev/fjsh/articles/587fa445a649b9著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol