Skip to content

実績工数を他システムから定期的に反映する

概要

既に他のシステムで実績工数を入力している場合、TimeTracker NXとの二重入力は避けたいところです。そこで、Web APIを活用すれば他システムから実績工数データを自動的に取り込むことが可能です。

この処理を定期的に実行する設定にすれば、データ入力は常に一元化され、利用者の負担は増えません。

利用場面

  • 他システムで管理している実績工数をTimeTracker NXに反映する

使用するAPI

以下のAPIを使用します。

  • ユーザーの取得
  • プロジェクトの取得
  • ワークアイテムの取得
  • 実績工数の追加
    //======================================================================
    //  実績工数を反映する
    //======================================================================
    //======================================================================
    //  外部ファイル(ライブラリ、他ファイル)のインクルード
    //======================================================================

    const https = require('https'); // HTTPS通信を利用する場合に設定する
    const http = require('http'); // HTTP通信を利用する場合に設定する
    const axios = require('axios');
    const fs = require('fs');

    //======================================================================
    //  共通部の定義
    //======================================================================

    // APIを呼び出すURLを設定する
    const baseURLString = 'http://yourserver/TimeTrackerNX/api';

    // サーバースペックに合わせてウェイト時間を設定する
    // 1分間に1500件程度の処理に収まるようするため、
    // 目安として推奨スペックでは100ms程度とする 
    const requestWaitTime = 100;    //単位:ms

    //----------------------------------------------------------------------
    //  Web API間の処理待ち関数
    //----------------------------------------------------------------------
    async function wait(time){
        const d1 = new Date();
        while (true) {
            const d2 = new Date();
            if (d2 - d1 > time) {
                break;
            }
        }
    }

    //======================================================================
    //  メイン処理
    //======================================================================
    main();

    async function main(){

        // 引数チェック    
        if (process.argv.length < 3){
            return 'enter username and password'; 
        }

        const userName = process.argv[2];
        var pass;
        var authString; //ブロック内スコープから外に出すためvarで宣言(固定値)

        // 引数でパスワードが未入力の場合は空白とみなす
        if (process.argv.length > 3){
            pass = process.argv[3];
        } else {
            pass = '';        
        }

        // 認証情報を取得する
        var sendUrl = baseURLString + '/auth/token';

        try{
            const { data } = await axios.post(
                sendUrl,
                {
                    loginName : userName,
                    password : pass
                },
    /*
            // https通信時に使用する
            httpsAgent  : new https.Agent({
                rejectUnauthorized: false
                        })
    */    
            );
            authString = 'Bearer ' + data.token;

        } catch (err) {
            console.log(err);
        }

        // 以降のAPI通信で利用する送信ヘッダを作成する
        const request = axios.create({
            baseURL     : baseURLString,
            headers     :{
                            'Authorization'   : authString
                        },
    /*
            // https通信時に使用する
            httpsAgent  : new https.Agent({
                rejectUnauthorized: false
                        })
    */    
                    });

        // JSON形式のデータ(例:後述のchangeActualTime.json)を入力とする
        const filepath = './changeActualTime.json';

        var updateData = JSON.parse(fs.readFileSync(filepath, {encoding: "utf-8"}));

        // JSON形式の実績工数データから1つ分のデータを取り出す
        for (key in updateData){
            var sendUrl = '/system/users';
            var userId;

            // ユーザーを一意に特定できる情報(コード)をもとに反映対象ユーザーのIDを取得
            try {
                const { data } = await request.get(
                    sendUrl,
                    {
                        params: {
                            code                : updateData[key].code
                        }
                    }
                );
                userId = data.data[0].id;
            } catch (e) {
                console.log(e);
            }

            // 実績を反映
            // 事前に対象のプロジェクト・ワークアイテムにリソースを割り当てること
            var sendUrl = '/system/users/'+userId+'/timeEntries';

            // 実績の追加 (リクエストに1つ分の実績工数情報を設定して送信)
            try {
                const { data } = await request.post(
                    sendUrl,
                    {
                        workItemId  : updateData[key].workItemId,
                        startTime   : updateData[key].startTime,
                        finishTime  : updateData[key].finishTime
                    }
                );
            } catch (e) {
                console.log(e);
            }
            await wait(requestWaitTime);
        }
    }

入力情報

上記のサンプルスクリプトで使用する追加用の実績情報ファイルを以下のリンク先から参照できます。

private async void Execute_Click(object sender, EventArgs e)
    {
        //======================================================//
        //  必要な変数等の定義                                    //
        //======================================================//
        JavaScriptSerializer serializar = new JavaScriptSerializer();
        var httpClient = new HttpClient();

        string serverUrl = "http://WIN-Q1OR68LM6RG.mshome.net/TimeTrackerNX/";
        string loginname = "okamoto";
        string password = "";

        // トークンの取得
        var tokenRequest = new Dictionary<string, string>
        {
            {"loginName",loginname },
            {"password",password }
        };
        var jsonData = serializar.Serialize(tokenRequest);
        var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
        string requestUrl = serverUrl + "/api/auth/token";
        var response = await httpClient.PostAsync(requestUrl, content);
        string responseData = await response.Content.ReadAsStringAsync();
        Token token;

        try
        {
            token = serializar.Deserialize<Token>(responseData);
        }
        catch
        {
            MessageBox.Show("トークン取得に失敗しました。\n接続先のサーバーURL、ログイン名、パスワードを確認してください。");
            return;
        }
        // ヘッダ設定
        httpClient.DefaultRequestHeaders.Add("contentType", "application/json");
        httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + token.token);


        //==========================================//
        // CSVを入力にデータを取得                    //
        //==========================================//
        String[] actualTimeData = null;
        List<string> lists = new List<string>();

        StreamReader sr = new StreamReader(@"changeActualTime.csv", System.Text.Encoding.GetEncoding("shift_jis"));
        {
            //タイトル行の回避(1行目はデータにしない)
            string line = sr.ReadLine();

            while (!sr.EndOfStream)
            {
                line = sr.ReadLine();

                actualTimeData = line.Split(',');
                lists.AddRange(actualTimeData);
            }
        }


        //==========================================//
        // 実績工数データの追加                   //
        //==========================================//
        // 要素数 5 (user/code/workItemId/startTime/finishTime)
        const int ActualTimeDataMemberNum = 5;
        string userId = null;

        for (int i = 0; i < lists.Count; i = i + ActualTimeDataMemberNum)
        {

            //------------------------------------------//
            // 実績工数データの設定                   //
            //------------------------------------------//
            var ActualTimeData = new Dictionary<string, string>
            {
                {"user",lists[i]},
                {"code",lists[i+1]},
                {"workItemId",lists[i+2]},
                {"startTime",lists[i+3] },
                {"finishTime",lists[i+4]},
            };


            //------------------------------------------//
            // ユーザー情報の取得                    //
            //------------------------------------------//
            requestUrl = serverUrl + "/api/system/users?code=" + lists[i + 1];
            response = await httpClient.GetAsync(requestUrl);
            responseData = await response.Content.ReadAsStringAsync();


            var user = serializar.Deserialize<ResponseUsers>(responseData);
            userId = user.data[0].id;


            //------------------------------------------//
            // 実績工数の追加                      //
            //------------------------------------------//                
            requestUrl = serverUrl + "/api/system/users/" + userId + "/timeEntries";

            TimeEntries entriesTimeData = new TimeEntries();
            entriesTimeData.workItemId = lists[i + 2];
            entriesTimeData.startTime = DateTime.Parse(lists[i + 3]);
            entriesTimeData.finishTime = DateTime.Parse(lists[i + 4]);


            jsonData = serializar.Serialize(entriesTimeData);
            content = new StringContent(jsonData, Encoding.UTF8, "application/json");
            response = await httpClient.PostAsync(requestUrl, content);
            responseData = await response.Content.ReadAsStringAsync();


        }
    }
    // Tokenレスポンス用のクラス
public class Token
{
    public string token { get; set; }
}

public class ResponseUsers
{
    public string totalCount;
    public ResponseUser[] data;
}

public class ResponseUser
{
    public string isDeleted;
    public string createdAt;
    public string updatedAt;
    public string createdBy;
    public string updatedBy;
    public string id;

    public ResponseUser()
    {
        this.id = null;
    }
}


public class TimeEntries
{
    public string workItemId;
    public DateTime startTime;
    public DateTime finishTime;

    public TimeEntries()
    {
        this.workItemId = null;
        this.startTime = DateTime.MinValue;
        this.finishTime = DateTime.MaxValue;
    }

}

入力情報

上記のサンプルスクリプトで使用する追加用の実績情報ファイルを以下のリンク先から参照できます。