GoogleスプレッドシートからJIRAのタスク、サブタスクを作成する

# 作成理由
JIRAでタスク管理しているとサブタスクが増えていき、担当者を振ったりするのがめんどくさい…
そのためスプレッドシートから一括で作成できるようなツールを作成しました。

基本的にはコードをコピーしてGASに貼り付けて
添付画像のように表を作れば動くと思います。
### 作成したスプレッドシートの画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3889564/d320c3db-e868-d79a-9b83-39260e077658.png)


  
**今回はJIRAのAPI TOKEN払い出し方法やチームメンバーのID取得方法などは割愛します。**
なにかの記事を読めばわかると思います。

## 利用方法
| 項目名 | 利用方法 |必須|備考 |
|:---|:---| :-:|:---|
|JIRA課題名|作成したいタスク名|△　　|親タスク作成したい場合は必須 <br>※サブタスク作成時は不要|
|サブタスク名|作成したいサブタスク名|△　　|親タスクの下に書いていけば自動的に親タスクのサブタスクになる <br>サブタスク作成時のみ必須<br>※親タスク作成時は不要|
|説明|タスクの説明欄|||
|担当|タスクの担当者|◯|プルダウンから選択|
|期日|タスクの期日|◯|カレンダーから選択|
|親タスク番号|親タスクの番号を記述|△|作成されているタスクにサブタスクを作成したい時のみ必須|
|完|作成が成功したらチェックが入る||基本触らない|
|エラーメッセージ|なにかエラーがあったらエラー文が入る|||

スプレッドシートの画面をみてもわかるように
作成したい親タスクの下にサブタスクを書いていく。
サブタスクを作成しない場合は親タスクが連続しても問題ない。
サブタスクのみ作成したい場合はどのサブタスクにしたいのかを明記するため親タスク番号欄に親タスク番号を書く。
記述が終わったら「START」ボタンを押下する。

# 実際のコード
コメントは本来0ですが説明のために書いてます。

**所詮ツールなのでindexベタ書き多様してるのはお許しを**

``` javascript:main.gs
const createJiraTask = async () => {
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('タスク作成');
  const datas = getSpredsheetData(sheet).entries();
  let response = '';
  let mainTaskResponse = { key: '' };
  // data配列の中身 0:親タスク名 1:サブタスク名 2:説明 3:担当 4:期日 5:親タスク番号 6: 完了フラグ
  for (const [index, data] of datas) {
    // 完了じゃないかつ 担当が空じゃない場合
    if (!data[6] && data[3] != '') {
      try {
        // 親タスクを作成する場合
        if (data[0]) {
          // 作成した親タスクの情報をresponseに格納
          response = await postTask(
            createTaskRequest(data[0], data[2], data[3], conversionDate(data[4]))
          );
        // 親タスク作成済みのサブタスクを作成する場合
        } else if (data[5]) {
          // mainTaskResponse.key内の親タスク番号と表の親タスク番号が一致しないときは表からさいどしゅと
          if (mainTaskResponse.key != data[5]) {
            mainTaskResponse = await getTask(data[5]);
          }
          await postTask(
            createSubTaskRequest(data[1], data[2], data[3], conversionDate(data[4]), mainTaskResponse.key, mainTaskResponse.id)
          );
        // サブタスクを作成する場合
        } else if (data[1] && response) {
          await postTask(
            createSubTaskRequest(data[1], data[2], data[3], conversionDate(data[4]), response.key, response.id)
          );
        }
        checkComplete(index);
      } catch (e) {
        // エラー時はエラーメッセージを記述
        sheet.getRange(index + 4, 9).setValue(e);
      }
    }
  }
}

const getSpredsheetData = (sheet) => {
  const lastRow = sheet.getLastRow();
  // 「4」は行数「2」は列数「7」は取得したい列数
  // 今回はスプレッドシートの記述欄がB4から始まってるのでこのような記述になる
  return sheet.getRange(4,2,lastRow,7).getValues();
}

// JIRAのAPIが日付を「-」繋がりしか許容していないため変換
const conversionDate = (date) => {
  return Utilities.formatDate(date, Session.getScriptTimeZone(), "yyyy-MM-dd")
}

// 「完」チェックボックスにチェックを入れる
const checkComplete = (index) => {
  sheet.getRange(index + 4, 8).setValue(true);
}
```

```javascript:api.gs
const postTask = async (json) => {
  response = UrlFetchApp.fetch(
    `${JIRA_URL}/rest/api/2/issue` ,
    {
      method: 'post',
      payload: json,
      contentType: 'application/json',
      headers: { 'Authorization': `Basic ${Utilities.base64Encode(`${MAIL}:${TOKEN}`)}`},
      muteHttpExceptions:true
    }
  )
  const responseCode = response.getResponseCode();
  const responseBody = response.getContentText();
  result = JSON.parse(responseBody);
  if (responseCode == 201) {
    return result
  }
  throw Error(JSON.stringify(result));
}

const getTask = async (taskNumber) => {
  response = UrlFetchApp.fetch(
    `${JIRA_URL}/rest/api/3/issue/${taskNumber}`,
    {
      method: 'get',
      contentType: 'application/json',
      headers: { 'Authorization': `Basic ${Utilities.base64Encode(`${MAIL}:${TOKEN}`)}`},
      muteHttpExceptions:true
    }
  )
  const responseCode = response.getResponseCode();
  const responseBody = response.getContentText();
  result = JSON.parse(responseBody);
  if ([200, 201].includes(responseCode)) {
    return result
  }
  throw Error(JSON.stringify(result));
}

const createTaskRequest = (name, description, user, date) => {
  return JSON.stringify(
    {
      "fields": {
        "project": {
          "key": PROJECT_NAME
        },
        "summary": name,
        "description": description,
        "issuetype": {
          "name": "Task"
        },
        "assignee": {
          "id": USERS[user]
        },
        "duedate": date
      }
    }
  );
}

const createSubTaskRequest = (name, description, user, date, key, id) => {
  return JSON.stringify(
    {
      "fields": {
        "project": {
          "key": PROJECT_NAME
        },
        "summary": name,
        "description": description,
        "issuetype": {
          "id": "10043"
        },
        "assignee": {
          "id": USERS[user]
        },
        "parent": {
          "key": key
        },
        "duedate": date
      }
    }
  );
}


```

```javascript:const.gs
//SS関連
const SHEET_ID = 'XXXXXXXXXXXXXX';

/** JIRA関連 */
const JIRA_URL = 'https://XXXXXXXXXXXXX.atlassian.net';
const PROJECT_NAME = 'XXXX';
const TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const MAIL = 'XXXXXXXX@gmail.com'; // 自分の

// JIRAのユーザー名とID
const USERS = {
  'Aさん': 'XXXXXXXXXXXXX',
  'Bさん': 'XXXXXXXXXXXXX',
  'Cさん': 'XXXXXXXXXXXXX'
}

```

# 最後に
JIRAも結構カスタマイズができるので快適なJIRAライフを満喫してください！

需要がありそうならJIRA関連の投稿もしていきます。