プロジェクト

全般

プロフィール

バグ #227

未完了

[Task4] Redmine統合機能実装 - ニュース⇄チケット連携

Redmine Admin さんが4日前に追加.

ステータス:
新規
優先度:
通常
担当者:
-
開始日:
2025-06-04
期日:
進捗率:

0%

予定工数:

説明

Task 4: Redmine統合機能実装

概要

ニュース機能とRedmineの統合機能を実装し、ニュースからチケット作成・関連チケット表示機能を提供する

作業ディレクトリ

/home/ito/task2-service/

実装ファイル構成

├── api/services/
│   └── redmineService.js        # Redmine API統合
├── ui/src/components/redmine/
│   ├── RedmineIntegration.jsx   # Redmine統合UI
│   ├── TicketCreator.jsx        # チケット作成
│   ├── RelatedTickets.jsx       # 関連チケット表示
│   └── index.js
├── ui/src/hooks/
│   └── useRedmine.js            # Redmine統合フック
└── ui/src/services/
    └── redmineAPI.js            # Redmine APIクライアント

実装仕様

1. Redmine API統合サービス

// api/services/redmineService.js
class RedmineService {
  constructor() {
    this.baseURL = 'https://call2arm.com';
    this.apiKey = 'feb66d81a5f4ff9c585ce30fce2ac06e0554aec6';
  }

  // ニュースからチケット作成
  async createTicketFromNews(newsItem) {
    const ticketData = {
      issue: {
        project_id: 1,
        subject: `ニュース対応: ${newsItem.title}`,
        description: this.formatNewsDescription(newsItem),
        tracker_id: 1, // 機能
        priority_id: 2, // 通常
        custom_fields: [
          { id: 1, value: newsItem.url }, // ニュースURL
          { id: 2, value: newsItem.source } // ニュースソース
        ]
      }
    };
    return this.createIssue(ticketData);
  }

  // 関連チケット検索
  async searchRelatedTickets(newsTitle) {
    const keywords = this.extractKeywords(newsTitle);
    return this.searchIssues({
      subject: keywords.join(' OR '),
      limit: 10
    });
  }
}

2. React統合コンポーネント

// ui/src/components/redmine/RedmineIntegration.jsx
const RedmineIntegration = ({ news }) => {
  const { createTicket, relatedTickets, loading } = useRedmine();

  const handleCreateTicket = async () => {
    try {
      const ticket = await createTicket(news);
      toast.success(`チケット #${ticket.id} を作成しました`);
    } catch (error) {
      toast.error('チケット作成に失敗しました');
    }
  };

  return (
    <div className="line-card p-4 mt-4">
      <h3 className="line-heading text-lg mb-3">Redmine連携</h3>
      
      {/* チケット作成ボタン */}
      <button 
        onClick={handleCreateTicket}
        className="line-btn-primary mb-4"
        disabled={loading}
      >
        {loading ? 'チケット作成中...' : 'チケットを作成'}
      </button>

      {/* 関連チケット表示 */}
      <RelatedTickets tickets={relatedTickets} />
    </div>
  );
};

3. API統合エンドポイント

// api/routes/news.js に追加
// ニュースからチケット作成
router.post('/:id/redmine', async (req, res) => {
  try {
    const news = await getNewsById(req.params.id);
    const ticket = await redmineService.createTicketFromNews(news);
    
    res.json({
      success: true,
      data: {
        ticket: ticket.issue,
        news: news
      }
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: error.message
    });
  }
});

// 関連チケット取得
router.get('/:id/redmine/related', async (req, res) => {
  try {
    const news = await getNewsById(req.params.id);
    const tickets = await redmineService.searchRelatedTickets(news.title);
    
    res.json({
      success: true,
      data: tickets
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: error.message
    });
  }
});

4. カスタムフック実装

// ui/src/hooks/useRedmine.js
import { useState, useCallback } from 'react';
import { redmineAPI } from '../services/redmineAPI';

export const useRedmine = () => {
  const [loading, setLoading] = useState(false);
  const [relatedTickets, setRelatedTickets] = useState([]);

  const createTicket = useCallback(async (newsItem) => {
    setLoading(true);
    try {
      const response = await redmineAPI.createTicketFromNews(newsItem);
      return response.data.ticket;
    } finally {
      setLoading(false);
    }
  }, []);

  const searchRelatedTickets = useCallback(async (newsId) => {
    const response = await redmineAPI.getRelatedTickets(newsId);
    setRelatedTickets(response.data);
  }, []);

  return {
    loading,
    relatedTickets,
    createTicket,
    searchRelatedTickets
  };
};

成果物

  • RedmineService クラス実装
  • RedmineIntegration コンポーネント
  • TicketCreator コンポーネント
  • RelatedTickets コンポーネント
  • useRedmine カスタムフック
  • API エンドポイント統合
  • エラーハンドリング実装

機能要件

チケット作成機能

  • ニュース情報をRedmineチケットとして作成
  • カスタムフィールド活用 (URL、ソース等)
  • プロジェクト・トラッカー・優先度設定
  • 成功/失敗フィードバック

関連チケット表示

  • ニュースタイトルベースのキーワード検索
  • 関連度順ソート
  • チケット詳細プレビュー
  • 直接Redmineリンク

UI統合

  • ニュースカード内統合表示
  • モーダル・サイドパネル対応
  • ローディング状態表示
  • エラーメッセージ表示

テスト項目

# チケット作成API
curl -X POST http://localhost:3002/api/news/123/redmine

# 関連チケット取得
curl http://localhost:3002/api/news/123/redmine/related

# UI統合テスト
# - チケット作成ボタン動作
# - 関連チケット表示
# - エラーハンドリング

セキュリティ要件

  • API Key適切な管理
  • CORS設定確認
  • 入力値サニタイズ
  • 認証・認可確認

完了条件

  • Redmine API統合完了
  • チケット作成機能動作確認
  • 関連チケット検索動作確認
  • UI統合完了
  • エラーハンドリング確認
  • セキュリティ要件確認

参照: 親チケット #223, 関連 #224, #225
実装目標: 2日
優先度: 中

表示するデータがありません

他の形式にエクスポート: Atom PDF