見出し画像

ForgeアプリでAtlassianAPIを呼び出す


はじめに

作成したForgeアプリケーションでアトラシアンのAPIを呼び出してみましょう!


前提条件

以下の記事の作業が完了していることを前提として進めております。




API呼び出し準備

正常にForgeAppを作成できていた場合、以下のようにアトラシアンサイト上で表示されているはずです。

issue-panel-sampleはアプリ名です



コードの概要

あまり細かくお伝えせずに大まかにどのような流れでお伝えしていきますのでご容赦ください。

src/frontend/index.jsx

import React, { useEffect, useState } from 'react';
import ForgeReconciler, { Text } from '@forge/react';
import { invoke } from '@forge/bridge';

const App = () => {
  const [data, setData] = useState(null);
  useEffect(() => {
    invoke('getText', { example: 'my-invoke-variable' }).then(setData);
  }, []);
  return (
    <>
      <Text>Hello world!</Text>
      <Text>{data ? data : 'Loading...'}</Text>
    </>
  );
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);


useEffect

アプリが読み込まれた時に動作します。例えばJira上でアプリボタンを押下しアプリが読み込まれた時などです。このコードではgetTextと名前のついたバックエンドの関数を呼び出し、レスポンスをsetDataに代入するといった内容です


return

実際にユーザーに表示される画面です。

<Text>Hello world!</Text>

は見ての通り、テキストを表示する処理です。皆さんのアプリでもテキストが表示されているはずです。


<Text>{data ? data : 'Loading...'}</Text>

data に値がある場合 → data を表示する,data が null や undefined の場合 → 'Loading...' を表示するという処理です。
ちなみにデフォルトではdataの初期値はnullを指定しています。useEffectの処理でsrc/resolvers/index.jsを呼び出すとreturnがHello, world!となっているのでHello, world!という文字列がdataに代入されるといった仕組みです。

useState

  const [data, setData] = useState(null);

以下の記事で丁寧に説明されています。
理解がまだの方は是非ご一読ください。


src/resolvers/index.js

import Resolver from '@forge/resolver';

const resolver = new Resolver();

resolver.define('getText', (req) => {
  console.log(req);
  return 'Hello, world!';
});

export const handler = resolver.getDefinitions();


useEffectが動作した際にこのgetText内の処理が呼び出されます。
この処理では特に難しいことはしていないです。ログを出力した後、文字列のHello worldを返却するというだけです。
注目していただきたのは(req)の部分です。これはsrc/frontend/index.jsxから送られたデータも含まれています。


実際にレスポンスを見てみましょう。

次のコマンドを入力し実行(Enterキーを押す)

forge tunnel


アプリが表示されているJiraを再読み込みさせてください。
すると以下のレスポンスが返却されるはずです。
このデータはsrc/frontend/index.jsxから渡されたデータ、つまりsrc/resolvers/index.js内のconsole.log(req)の出力内容です。

{
  "payload": { "example": "my-invoke-variable" },
  "context": {
    "accountId": "********",
    "localId": "ari:cloud:ecosystem::extension/********/********/********/********",
    "cloudId": "********",
    "moduleKey": "********",
    "extension": {
      "issue": "[Object]",
      "project": "[Object]",
      "isNewToIssue": false,
      "type": "jira:issuePanel"
    },
    "accountType": "licensed",
    "installContext": "ari:cloud:jira::site/********",
    "license": undefined,
    "jobId": undefined
  }
}


このレスポンスでは基本的なJiraの情報が受け取れます。アプリがどの課題キーで読み込まれたのかや、どのプロジェクトで読み込まれたなど。



Tunnelを使用して課題キーのログを取得してみる

せっかくなので先ほどのContextデータから課題キーを取得してターミナルに出力させてみましょう。


まず、今回ログに出力したいのは課題キーです。課題キーはContext/extension/issueの配下に含まれています。従って以下のように変更してみましょう

console.log(req)

console.log(req.context.extension.issue)


すると以下のように出力が変わるはずです

{ key: '課題キー', id: '課題ID', type: '課題タイプ', typeId: '課題タイプID' }


課題キーだけ出力させたい場合は以下のように指定してください。

console.log(req.context.extension.issue.key)

アプリを再度読み込むと課題キーだけが表示されるはずです。


これでAPI呼び出しの準備が整いました!!!


Jira Cloud REST APIを呼び出す

これまでの過程で課題キーをログへ出力するところまでは上手くできました。
ここからは先ほど取得した課題キーを使用してJiraCloudAPIを実行してみようと思います。


Jira Cloud REST APIとは

Jira Cloud REST API は、Jira Cloud にアクセスし、データを取得・更新・削除できるAPIです。外部アプリやシステムと連携したりすることもできます。


Get Issueを実行してみる

今回は上記のリンクでもわかるように課題の情報を取得するAPIを実行してみようと思います。
課題の情報の取得といってもピンとこないかもしれませんが、例えば対象課題のカスタムフィールドの値などを取得する場合などにも使用が可能です。


まずはターミナルで以下のコマンドを実行して出力内容が確認できるようにしておきましょう。

forge tunnel


ForgeUiKitでAPIを呼び出す一般的な場所は、src/resolvers/index.js内です。
ドキュメントでは以下の処理の記載があります。

import api, { route } from "@forge/api";

const response = await api.asUser().requestJira(route`/rest/api/3/issue/{issueIdOrKey}`, {
  headers: {
    'Accept': 'application/json'
  }
});

console.log(`Response: ${response.status} ${response.statusText}`);
console.log(await response.json());


これでGetIssueが実行できるというわけですが、このコードをそのままsrc/resolvers/index.jsに記載しても動作しません。なぜなら{issueIdOrKey}には適切な課題キーを入力しないといけないからです。

皆さん思い出してください。
この前のセクションで私たちは課題キーをログに出力することができています。この課題キーを使用して APIを呼び出すことができれば何も怖いことはありません。


従って以下のようにコード(src/resolvers/index.js)を変更します。

import Resolver from '@forge/resolver';
import api, { route } from "@forge/api";


const resolver = new Resolver();

resolver.define('getText', async (req) => {
  const issueKey = req.context.extension.issue.key;
  const response = await api.asUser().requestJira(route`/rest/api/3/issue/${issueKey}`, {
    headers: {
      'Accept': 'application/json'
    }
  });
  
  console.log(`Response: ${response.status} ${response.statusText}`);
  console.log(await response.json());
  return 'Hello, world!';
});

export const handler = resolver.getDefinitions();


まず最初に確認が必要なのが、APIを実行するためにForgeApp自体に権限を付与する必要があるかどうかを確認する必要があります。


const issueKey = req.context.extension.issue.key;

これは issueKeyという箱に先ほど取得した課題キーの値を入れています。つまり、issueKeyという箱を呼び出せばいつでも課題キーが使用できるというわけです。

const response = await api.asUser().requestJira(route`/rest/api/3/issue/${issueKey}`, {
    headers: {
      'Accept': 'application/json'
    }
  });
  
  console.log(`Response: ${response.status} ${response.statusText}`);
  console.log(await response.json());

これは公式ドキュメントから引用してきたものに少し変更を加えています。{issueIdOrKey}→${issueKey}に変更
これで正しく課題キーを使用してリクエストができるというわけです。


実際に動かしてみようとするとターミナルに以下のエラーが出力されるはずです。

forge lint --fixコマンドを実行してねと言われています。


このエラーが出力される原因はForgeAppの権限不足です。
公式のドキュメントをもう一度見てみましょう

read:jira-workが必須と記載があります。この記載をApp側にしていないため、エラーが出力されます。


修正するために一度ターミナル上でControl + Cを実行してtunnelを停止させましょう。

停止させたらターミナル上で

forge lint --fix

を実行してください。

すると以下のような出力がされるはずです。

✔ Fixed 1 error and 0 warnings


何が修正されているのかを確認するためにymlファイルを確認してみましょう。
お気づきの方もいらっしゃるかと思いますが以下の項目が自動で追加されています。

permissions:
  scopes:
    - read:jira-work


追加されていることを確認したらターミナル上で以下を実行してください。

forge deploy


すると以下のようなメッセージが出力されるはずです。

We've detected new scopes or egress URLs in your app.
Run forge install --upgrade and restart your tunnel to put them into effect.


この指示通り、ターミナル上で以下を実行してください。

forge install --upgrade


そして再度ログをターミナル上で確認するためにTunnelを起動させたいので以下を実行してください。

forge tunnel


アプリを読み込むとたくさんのレスポンスがログ上に出力されるはずです。
私が最初このレスポンスを見た時は頭痛がしました。笑


以上でAPIの呼び出しが正常に完了です!!!
実装お疲れ様でした!


次回はこのレスポンスを使用してアプリのUIにレスポンス内容を反映させてみたいと思います。


お楽しみに!

いいなと思ったら応援しよう!