LoginSignup
1
2

More than 3 years have passed since last update.

Redmine チケット及び期限切れチケットをpython-redmineとRESTの両方で

Last updated at Posted at 2021-01-19

Redmineのチケット、期限切れチケットの一覧を
DataFrameで還元するコード。

テストコードは引き続き書き中。

dataframe→Mardownテーブル化→Chat投入の流れで使用します。

Markdownテーブル化、チャット投入ライブラリは
既に作ってあります。


#!/opt/anaconda3/bin/python3
# -*- coding: utf-8 -*-

"""Redmineチケット状態を管理する

  チケット情報を得て状況分析を行う 

  Todo:
     * まだRedmineとRocketChatのみ。他のOSSに対しても同様に作る

"""

################################################
# library
################################################

import datetime
import redminelib
import requests
import pandas as pd
import sys

from pprint import pprint
from redminelib import Redmine

# 個別ライブラリLoad

################################################
# 独自例外定義 
################################################
#class MaxRetryError(Exception):
#    pass
#

################################################
# 情報定義 
################################################
#
## Redmine Instance
#HOST = "http://xxxxxxxxxxxxxxxxxxxxxxxxxx:3100/"
#API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx47ac"
#
## 抽出対象
#PROJECT_ID = 'wbs-kaisui'
#PROJECT_NAME = '個別WBS(開発推進)'
#TRACKER_NAME ='やること'
#
## Dataframe columns定義
#COLUMNS = ['Project','Tracker','Sprint','親チケットID','チケットID','TicketTitle','担当', 'Status','開始日','期限']
#
## 探索条件
#QUERY = '開始日 <= @TODAY and 期限 < @TODAY'
#
## Sort条件
#SORT_KEY = ['担当','期限']
#
################################################
# RedmineTicketManager 
################################################

class RedmineTicketManager():
    """Redmineチケット管理Class

    Attributes:
        API_KEY (int)   : Redmineのadmin API key
        HOST (bool)     : Redmineのホスト 

    """

    def __init__(self, HOST, API_KEY):
        """Redmineインスタンス生成

        API_KEY,HOSTからRedmineインスタンスを返す

        Args:
            HOST (str):      Redmine Host 
            API_KEY (str):   adminに応じたAPI_KEY

        Returns:

        Raises:
            TypeError: 引数型の不備
            Exception: Redmineインスタンス生成不備

        Examples:
            >>> redmine = RedmineUserManager(HOST, API_KEY) 

        Note:
            Redmineのグループは事前に存在する必要があります。
            新しいグループが記載されたとしても自動でRedmine上で作成しません。
            __init__ではboolを返してはならないので留意

        """ 

        # 引数チェック 型    
        if not isinstance(HOST, str):
            print(f'引数:HOSTの型が正しくありません str <-> {type(HOST)}')
            raise TypeError

        if not isinstance(API_KEY, str):
            print(f'引数:API_KEYの型が正しくありません str <-> {type(API_KEY)}')
            raise TypeError

        #TODO 引数チェク追加

        # Redmineインスタンス生成(self.redmine)
        try:
            self.redmine = Redmine(HOST, key=API_KEY)
        except exception as e: 
            print(f'Redmine Instance生成に失敗しました')
            print(f'エラー詳細:{e}')
        else:
            if not isinstance(self.redmine, redminelib.Redmine):
                print(f'Redmine Instance生成に失敗しました')
                raise Exception

    ########################################################    
    # 期限切れチケット探索処理 
    ########################################################
    def createOverdueTicket(self, PROJECT_ID, TRACKER_NAME):
        '''期限切れチケットを探索する

        プロジェクト、トラッカーを指定して期限超過チケットを探索する。

        Args:
            PROJECT_ID : str RedmineプロジェクトID(プロジェクト名ではない)
            TRACKER_NAME: str Redmine検索対象とするトラッカー名

        Returns:
            プロジェクト、トラッカー上限にヒットするチケット全量: DataFrame
            プロジェクト、トラッカー条件にヒットし、かつ期限超過チケット: DataFrame

        Raises:
            TypeError: 引数型の不備
            Exception: チケット探索時の例外

        Examples:
            >>> RTM = RedmineTicketManager(HOST, API_KEY)
            >>> df, df_overdue = RTM.createOverdueTicket(PROJECT_ID, TRACKER_NAME)

        Note:

        '''
        # Dataframe columns定義
        COLUMNS = ['Project','Tracker','Sprint','親チケットID','チケットID','TicketTitle','担当', 'Status','開始日','期限']

        # 探索条件
        QUERY = '開始日 <= @TODAY and 期限 < @TODAY'

        # Sort条件
        SORT_KEY = ['担当','期限']

        # 今日日付生成
        TODAY = datetime.date.today()
        pprint(f'判定基準日: {TODAY}')
        INIT_DATE = datetime.date(1900, 1, 1)
        pprint(f'始まりの日: {INIT_DATE}')
        print('-'*80)

        print(f'Redmineチケットを探索します')

        try:
            # 管理者権限で全てのチケットを取得
            tickets=self.redmine.issue.filter(
                project_id=PROJECT_ID,
                tracker_name=TRACKER_NAME,
            )

        except Exception as e:
            print(f'チケット取得に失敗しました:{PROJECT_ID} {TRACKER_NAME}')
            print(f'エラー詳細:{e}')
            print()
        else:
            # チケット遅延判定処理、データ作成 

            # 仮の入れ物を用意
            _list = []

            # チケット成形処理
            for ticket in tickets:
                # ticketインスタンスから情報取得
                projectName = ticket.project.name
                id = ticket.id
                subject = ticket.subject
                authorName = ticket.author.name
                authorID = ticket.author.id
                status = ticket.status.name
                tracker = ticket.tracker.name
                fixed_version = ticket.fixed_version.name

                # 担当者が設定されていない場合は未設定と表示
                try:
                    assigned_to = ticket.assigned_to.name
                except:
                    assigned_to = "未設定"

                # 開始日が設定されていない場合は始まりの日を設定
                try:
                    startDate = ticket.start_date
                except:
                    startDate = INIT_DATE 

                # 期日が設定されていない場合は始まりの日を設定
                try:
                    dueDate = ticket.due_date
                except:
                    dueDate = INIT_DATE 

                # 親チケットが設定されていいない場合は0を設定
                try:
                    parent = ticket.parent
                except:
                    parent = 0 

                # 蓄積
                _list.append([projectName, tracker, fixed_version, parent, id, subject, assigned_to, status, startDate, dueDate])


            print(f'チケット抽出が完了しました')

            # DataFrame化
            df = pd.DataFrame(_list)
            df.columns = COLUMNS

            # 遅延チケット抽出
            # 開始日が今日以前、期限が本日超過しているものを対象とする
            df_overdue = df.query(QUERY).sort_values(SORT_KEY).reset_index(drop=True)

            print(f'期限超過Redmineチケットを抽出しました')
            print(f'期限超過チケット件数: {len(df_overdue)}')

            return df ,df_overdue


    ########################################################    
    # 期限切れチケット探索処理 by REST 
    ########################################################
    def createOverdueTicketByREST(self, RESTAPI, HEADERS, PROJECT_ID, TRACKER_ID):
        '''期限切れチケットを探索する By REST

        プロジェクト、トラッカーを指定して期限超過チケットを探索する。
        python_redmineを使用せず直接RESTを使って情報取得する。

        Args:
            PROJECT_ID : str RedmineプロジェクトID(プロジェクト名ではない)
            TRACKER_ID: str Redmine検索対象とするトラッカーID(トラッカー名ではない)

        Returns:
            プロジェクト、トラッカー上限にヒットするチケット全量: DataFrame
            プロジェクト、トラッカー条件にヒットし、かつ期限超過チケット: DataFrame

        Raises:
            TypeError: 引数型の不備
            Exception: チケット探索時の例外

        Examples:
            >>> RTM = RedmineTicketManagerByREST(HOST, API_KEY)
            >>> df, df_overdue = RTM.createOverdueTicketByREST(PROJECT_ID, TRACKER_ID)

        Note:
            NAMEなのかIDなのか、使い分けがめんどい感じがある。。。
        '''
        # request parameter生成
        #URL = f'http://192.168.10.104:3100' 
        #API = f'{URL}/projects/{PROJECT_ID}/issues.json?tracker_id={TRACKER_ID}'
        #HEADERS = { 'Content-Type': 'application/json', 'X-Redmine-API-Key': '864b3f0933e8084295d47380bf07a168ba2947ac'}

        # 今日日付生成
        TODAY = datetime.date.today()
        pprint(f'判定基準日: {TODAY}')

        # Dataframe columns定義
        COLUMNS = ['Project','Tracker','Sprint','親チケットID','チケットID','TicketTitle','担当', 'Status','開始日','期限']

        # 探索条件
        QUERY = '開始日 <= @TODAY and 期限 < @TODAY'

        # Sort条件
        SORT_KEY = ['担当','期限']

        # 探索処理実行
        ## 入れ物を用意
        _list = []

        # 探索実施
        try:
            response = requests.get(RESTAPI, 
                                    headers=HEADERS)
        except Exception as e:
            print(f'Redmineチケット探索に失敗しました') 
            print(f'{e}')
        else:
            # 結果加工
            for _ in response.json()['issues']:

                # 親と担当者はデータとして存在しないケースあり
                # try〜catchで判定する必要がある
                try:
                    parent = _['parent']['id']
                except:
                    parent = 0

                try:
                    assigned_to = _['assigned_to']['name']
                except:
                    assigned_to = "None"

                # 開始、期限は存在する。
                # 設定がない場合は Noneが入っている
                if _['start_date'] != None:
                    startDate = _['start_date']
                else:
                    startDate = '1900-01-01'

                if _['due_date'] != None:
                    dueDate = _['due_date']
                else:
                    dueDate = '1900-01-01'

                # データ蓄積    
                _list.append([_['project']['name'], 
                              _['tracker']['name'],
                              _['fixed_version']['name'],
                              parent,
                              _['id'],
                              _['description'],
                              assigned_to,
                              _['status']['name'],
                              startDate,
                              dueDate,
                             ])

        # DataFrameに変換して抽出処理実施    
        df = pd.DataFrame(_list)
        df.columns = COLUMNS
        df['開始日'] = pd.to_datetime(df['開始日'])
        df['期限'] = pd.to_datetime(df['期限']) 

        # 遅延チケット抽出
        # 開始日が今日以前、期限が本日超過しているものを対象とする
        df_overdue = df.query(QUERY).sort_values(SORT_KEY).reset_index(drop=True)

        print(f'期限超過Redmineチケットを抽出しました')
        print(f'期限超過チケット件数: {len(df_overdue)}')

        return df ,df_overdue

1
2
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
1
2