0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS Lambdaを使ってBacklogに繰り返しタスク登録

Last updated at Posted at 2025-03-31

私のチームではそれぞれのメンバーが複数プロジェクトに関わっていますが、ヌーラボ社のBacklogにあらゆるタスクを登録して共有することで、タスクやり忘れを防止したり、稼働状況を見える化できるようにしています。
中には定期的に繰り返して行うタスクもありますが、Backlogには繰り返し登録機能がないのでいつも手動登録するのが面倒だなって思っていました。
※例) 定期セキュリティチェック、パッチ適用、交通費精算、契約更新、定期レポート作成など

少しでも効率化を図りたかったのでLambdaで自動化を試してみました。

大まかな概要と流れはこんな感じです。

  1. 定期的に発生する種類のタスクをエクセル管理表に書き出す
  2. エクセル管理表を S3 にアップロード
  3. 1日1回 Lambdaがエクセルを読み取り、Backlog API経由でその日のタスクを登録

エクセル管理表があるならわざわざ Backlog 登録する意味あるのか?と思われる方もいると思いますが、Backlogで管理していれば毎回の作業履歴も残るし、スポットタスクも含めてBacklogで一元管理することができるので意味はあると思ってます。

Lambdaサンプルコード(python)
import os
import json
import boto3
from botocore.exceptions import ClientError
import sys
import ast
import requests
import openpyxl
from io import BytesIO
from datetime import datetime, timedelta, date, timezone
from dateutil.relativedelta import relativedelta

REGION_NAME = "<リージョン名>"
BUCKET_NAME = '<バケット名>'
INPUT_PREFIX = 'input'
INPUT_EXCEL_NAME = 'Backlog_Issue_list.xlsx'
INPUT_OBJECT_KEY_NAME = f'{INPUT_PREFIX}/{INPUT_EXCEL_NAME}'
INPUT_EXCEL_SHEETNAME = 'LIST'

BACKLOG_PRJ_ID="<Backlog プロジェクト ID>"
# 種別=ルーチンタスク
BACKLOG_ISSUETYPE_ROUTINE="<ルーチン作業のBacklog種別 ID >"
# 種別=スポットタスク
BACKLOG_ISSUETYPE_TASK="< スポットタスクのBacklog 種別 ID >"
# シークレットネーム
SECRET_NAME = "Backlog"
# BacklogApiのシークレットキーネーム
SECRET_KEY_NAME = "BacklogApiKey"

s3_client = boto3.client('s3')

def lambda_handler(event, context):

    try:
        t_delta = timedelta(hours=9)
        JST = timezone(t_delta, 'JST')
        TODAY = datetime.now(JST).strftime("%Y-%m-%d")
        DayOfWeek = datetime.now(JST).strftime('%a')
        PREFIX_SUMMARY = datetime.now(JST).strftime("[%m-%d]-")
    except Exception as e:
        print(f"Error: {e}")


    # Excelデータ取得
    try:
        s3_resp = s3_client.get_object(Bucket=BUCKET_NAME, Key=INPUT_OBJECT_KEY_NAME)
        # エクセルに入力制限があると対応していないのでWARNINGが出るが無視する。
        openpyxl.reader.excel.warnings.simplefilter('ignore')
        wb = openpyxl.load_workbook(BytesIO(s3_resp['Body'].read()),data_only=True)
        main_ws = wb[INPUT_EXCEL_SHEETNAME]
    except Exception as e:
        # エラー
        print("ERROR エクセルファイルが読み込めません")
        sys.exit()
    else:
        # BacklogのAPIキーをシークレットマネージャから読み込む
        ApiKey = get_secret(SECRET_NAME, REGION_NAME)

        #エクセルファイルを読み込む
        row_num=0
        for row in main_ws.rows:
            
            row_num +=1
            row_dic = {}

            # 1 行目の処理(ヘッダーセルとして格納)
            if row[0].row == 1:
                print('1行目はヘッダ行')
                header_cells = row

            # 2行目以降の処理
            else:
                # セルの値を「key-value」で登録
                for k, v in zip(header_cells, row):
                    row_dic[k.value] = v.value

                print("%s 行目" % row[0].row )

                # interval, NumOfDays_UntilDeadlineが未入力の行はスキップする                
                if row_dic["interval"] == None: continue
                if row_dic["NumOfDays_UntilDeadline"] == None: continue

                # 期限日を計算する
                NumOfDays_UntilDeadline = int(row_dic['NumOfDays_UntilDeadline'])
                dueDate = date.today() + relativedelta(days=NumOfDays_UntilDeadline)

                projectId = BACKLOG_PRJ_ID
                summary = PREFIX_SUMMARY + row_dic['summary']
                priorityId = "2"
                assigneeId = row_dic['assigneeId']
                description = row_dic['description']

                #### 毎月のルーチンタスク ####
                if row_dic['interval'] == "MONTHLY":
                    issueTypeId = BACKLOG_ISSUETYPE_ROUTINE
                    addDate = date.today().replace(day=int(row_dic['addTiming']))
                    # 条件が一致する時だけタスク登録する。
                    if str(addDate) != str(TODAY):
                        continue
                    else:
                        print("MONTHLY: 日付条件一致するので追加 %s" % (addDate))
                        print("期限は %s" % (dueDate))
                        print("%s %s" % (issueTypeId, summary))

                        backlog_issue_add(ApiKey, BACKLOG_PRJ_ID, summary, issueTypeId, priorityId, assigneeId, description, dueDate )

                #### 毎週のルーチンタスク ####
                elif row_dic['interval'] == "WEEKLY":
                    issueTypeId = BACKLOG_ISSUETYPE_ROUTINE

                    # 条件が一致する時だけタスク登録する。
                    if str(row_dic['addTiming']) != str(DayOfWeek):
                        continue
                    else:
                        print("WEEKLY: 日付条件一致するので追加 %s" % (DayOfWeek))
                        print("期限は %s" % (dueDate))
                        print("%s %s" % (issueTypeId, summary))

                        backlog_issue_add(ApiKey, BACKLOG_PRJ_ID, summary, issueTypeId, priorityId, assigneeId, description, dueDate )

                #### スポットのタスク ####
                elif row_dic['interval'] == "SPOT":
                    issueTypeId = BACKLOG_ISSUETYPE_TASK
                    # 条件が一致する時だけタスク登録する。
                    if str(row_dic['addTiming']) != str(TODAY):
                        continue
                    else:
                        print("SPOT: 日付条件一致するので追加 %s" % (TODAY))
                        print("期限は %s" % (dueDate))
                        print("%s %s" % (issueTypeId, summary))

                        backlog_issue_add(ApiKey, BACKLOG_PRJ_ID, summary, issueTypeId, priorityId, assigneeId, description, dueDate )


def backlog_issue_add(ApiKey, projectId, summary, issueTypeId, priorityId, assigneeId, description,dueDate): 
        url = 'https://xxxx.backlog.com/api/v2/issues?apiKey=' + ApiKey
        data = {
                'projectId': projectId,
                'summary': summary,
                'issueTypeId': issueTypeId,
                'priorityId': priorityId,
                'assigneeId': assigneeId,
                'description': description,
                'dueDate': dueDate
            }   
        headers = {
                'Content-Type': 'application/x-www-form-urlencoded'
        }
        r = requests.post(url, data = data, headers = headers)
        print(r.status_code)
        print(r.json())


def get_secret(secret_name, region_name):

    # Create a Secrets Manager client
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=region_name
    )

    try:
        get_secret_value_response = client.get_secret_value(
            SecretId=SECRET_NAME
        )
    except ClientError as e:
        # For a list of exceptions thrown, see
        # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
        raise e

    secret = get_secret_value_response['SecretString']
    secret_list = ast.literal_eval(secret)

    return secret_list[SECRET_KEY_NAME]

※エクセルの読み書き用に openpyxl レイヤーを使ってます。
※Backlog API とREST通信するように requestレイヤーを使てってます。

1. 定期的に発生する種類のタスクをエクセル管理表に書き出す

定期的に発生するタイミングとして、毎月/毎週/都度(3か月に一回とか)などがあるかなと想定して、エクセル管理表のフォーマットをいったん以下のように考えてみました。これを「 Backlog_Issue_list.xlsx 」というファイル名で作成します。
一行目はヘッダ行で以下のような形式になります。
BacklogIssueListSample.png

A列: interval
Backlog登録するのが、毎月なのか、毎週なのか、都度なのかを入力する列です。

説明
MONTHLY 毎月決まった日付にタスクをBacklog登録する
WEEKLY 毎週決まった曜日にタスクをBacklog登録する
SPOT 指定した特定に日にタスクをBacklog登録する

B列: addTiming
タスクを登録するタイミング(日付や曜日)を入力する列です。interval の形式によって入力方法が異なります。

interva形式 入力する内容
MONTHLYの場合 毎月何日に登録したいのか日付を入力します
例)毎月1日であれば「1」、毎月15日であれば「15」
WEEKLY 毎週何曜日に登録したいのかアルファベット3文字で入力します。
例) 毎週月曜であれば「Mon」、毎週木曜であれば「Thu」
SPOT チケット登録したい特定の年月日を「YYYY-MM-DD」で入力します。
例)2025年3月31日であれば「2025-03-31」

C列,D列: assigneeName, assigneeId
Backlog上の担当者として割り当てる人の名前(assigneeName)をリスト形式で選択する列です。Backlog API で登録する時にはassigneeNameではなく assigneeIdが必要ですが、assigneeNameをリスト形式で選択すれば自動的にassigneeIdがD列に入るようにしてます。

E列: summary
Backlogに登録する件名を入力する列です。

F列: description
Backlogに登録するタスクの概要を入力する列です。

G列: NumOfDays_UntilDeadline
タスク登録した日から何日後を期限とするかを入力する列です。

2. エクセル管理表を S3 にアップロード

作成したエクセル管理表を Lambda で設定したパスに合わせてS3バケットにアップロードします。

3. 1日1回 Lambdaがエクセルを読み取り、Backlog API経由でその日のタスクを登録

Sampleソースコードで必要な箇所を修正してLambda関数を登録します。

LambdaにアタッチするIAMロールに必要なポリシーは以下です。

  • AWSLambdaVPCAccessExecutionRole
  • SecretsManagerReadWrite
  • AmazonS3ReadOnlyAccess
  • CloudWatchLogsFullAccess

Lambda関数の中でBacklogに登録ユーザのAPIキーを保存して読みだしています。適当に以下としてます。

  • シークレットの名前: Backlog
  • 暗号化キー: aws/secretsmanager
  • シークレットのキー: BacklogApiKey
  • シークレットの値: Backlogで発行したAPIキー

Lambdaが実行されると、エクセル管理表にその日登録するべきタスクがあれば下記のようにBacklog登録してくれます。
BacklogIssueSample.png

注意事項

以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?