Skip to content

Instantly share code, notes, and snippets.

@m-mizutani
Created September 15, 2025 09:30
Show Gist options
  • Select an option

  • Save m-mizutani/5a43f42d031ac8d4ac1d9e4bcbcdd2db to your computer and use it in GitHub Desktop.

Select an option

Save m-mizutani/5a43f42d031ac8d4ac1d9e4bcbcdd2db to your computer and use it in GitHub Desktop.
設計ファイル

インシデントステータス管理機能 設計書

アーキテクチャ概要

graph TB
    subgraph "Slack Interface"
        SC[Status Command]
        EM[Edit Modal]
        DM[Declare Modal]
    end
    
    subgraph "Web UI"
        IL[Incident List]
        ID[Incident Detail]
        SH[Status History]
    end
    
    subgraph "Controller Layer"
        SLC[Slack Controller]
        GQL[GraphQL Controller]
    end
    
    subgraph "UseCase Layer"
        IUC[Incident UseCase]
        SUC[Status UseCase]
    end
    
    subgraph "Domain Layer"
        IS[IncidentStatus]
        ISH[StatusHistory]
    end
    
    subgraph "Repository Layer"
        FR[Firestore Repository]
        MR[Memory Repository]
    end
    
    SC --> SLC
    EM --> SLC
    DM --> SLC
    IL --> GQL
    ID --> GQL
    SH --> GQL
    
    SLC --> IUC
    SLC --> SUC
    GQL --> IUC
    GQL --> SUC
    
    IUC --> IS
    SUC --> IS
    SUC --> ISH
    
    IS --> FR
    ISH --> FR
    IS --> MR
    ISH --> MR
Loading

データモデル設計

1. IncidentStatus (ステータス列挙型)

type IncidentStatus string

const (
    IncidentStatusTriage     IncidentStatus = "triage"
    IncidentStatusHandling   IncidentStatus = "handling"
    IncidentStatusMonitoring IncidentStatus = "monitoring"
    IncidentStatusClosed     IncidentStatus = "closed"
)

2. StatusHistory (ステータス履歴エンティティ)

type StatusHistoryID string // UUID v7 typed string
type IncidentID string      // typed string

type StatusHistory struct {
    ID           StatusHistoryID   // 履歴ID (UUID v7)
    IncidentID   IncidentID        // インシデントID
    Status       IncidentStatus    // 変更後のステータス
    ChangedBy    types.SlackUserID // 変更実施者のSlackユーザーID
    ChangedAt    time.Time         // 変更日時
    Note         string            // 状況メモ(オプション)
}

3. Incident エンティティの拡張

type Incident struct {
    // 既存フィールド
    ID           IncidentID
    Title        string
    Description  string
    Lead         types.SlackUserID  // インシデントリード(SlackユーザーID)
    CreatedAt    time.Time
    UpdatedAt    time.Time
    
    // 新規追加フィールド
    Status           IncidentStatus    // 現在のステータス
    StatusHistories  []StatusHistory   // ステータス変更履歴
    InitialTriage    bool             // Triageから開始したかのフラグ
}

Firestore スキーマ設計

Incidents コレクション

/Incidents/{incidentId}
{
    ID: string
    Title: string
    Description: string
    Lead: string
    Status: string  // 現在のステータス
    InitialTriage: boolean
    CreatedAt: timestamp
    UpdatedAt: timestamp
}

StatusHistories サブコレクション

/Incidents/{incidentId}/StatusHistories/{historyId}
{
    ID: string
    IncidentID: string
    Status: string
    ChangedBy: string  // SlackユーザーID
    ChangedAt: timestamp
    Note: string
}

API設計

GraphQL スキーマ

enum IncidentStatus {
    TRIAGE
    HANDLING
    MONITORING
    CLOSED
}

type StatusHistory {
    id: ID!
    status: IncidentStatus!
    changedBy: User!  # usersから解決
    changedAt: DateTime!
    note: String
}

type Incident {
    id: ID!
    title: String!
    description: String!
    lead: String!
    status: IncidentStatus!
    statusHistories: [StatusHistory!]!
    createdAt: DateTime!
    updatedAt: DateTime!
}

type Mutation {
    updateIncidentStatus(
        incidentId: ID!
        status: IncidentStatus!
        note: String
    ): Incident!
    
    updateIncident(
        incidentId: ID!
        title: String
        description: String
        lead: String
        status: IncidentStatus
    ): Incident!
}

type Query {
    incident(id: ID!): Incident
    incidentStatusHistory(incidentId: ID!): [StatusHistory!]!
}

Slack コマンド処理

/status コマンド

  1. メンション解析でコマンドを検出
  2. インシデントチャンネルの特定
  3. 現在のインシデント情報を取得
  4. ステータス情報を含むメッセージをSlackに投稿
  5. Editボタンを付与

Edit モーダル

  1. 既存のインシデント編集モーダルを拡張
  2. ステータス選択ドロップダウンを追加
  3. 状況メモの入力フィールド(オプション)

Declare モーダル

  1. 既存のインシデント宣言モーダルを拡張
  2. "Start with Triage" チェックボックスを追加
  3. チェック状態に応じて初期ステータスを設定

UseCase層の設計

StatusUseCase (新規)

type StatusUseCase interface {
    // ステータス更新
    UpdateStatus(ctx context.Context, incidentID IncidentID, status IncidentStatus, userID types.SlackUserID) error
    
    // ステータス履歴取得(ユーザー情報を含む)
    GetStatusHistory(ctx context.Context, incidentID IncidentID) ([]StatusHistoryWithUser, error)
    
    // ステータス付きメッセージ投稿
    PostStatusMessage(ctx context.Context, channelID types.ChannelID, incidentID IncidentID) error
}

// ユーザー情報を含むステータス履歴
type StatusHistoryWithUser struct {
    StatusHistory
    User *User // usersから取得したユーザー情報
}

IncidentUseCase の拡張

type IncidentUseCase interface {
    // 既存メソッド...
    
    // インシデント作成(初期ステータス対応)
    CreateIncidentWithStatus(ctx context.Context, params CreateIncidentParams) (*Incident, error)
    
    // インシデント更新(ステータス含む)
    UpdateIncidentWithStatus(ctx context.Context, params UpdateIncidentParams) (*Incident, error)
}

Controller層の設計

Slack Controller の拡張

  1. メンションハンドラー

    • status または s コマンドの検出
    • StatusUseCaseを呼び出してメッセージ投稿
  2. インタラクションハンドラー

    • ステータス編集モーダルの表示
    • モーダルsubmitの処理
    • StatusUseCaseを呼び出してステータス更新
  3. 宣言モーダルハンドラー

    • Triageチェックボックスの状態取得
    • 初期ステータスの設定

GraphQL Resolver の拡張

  1. Query Resolver

    • incident: ステータス履歴を含めて返却
    • incidentStatusHistory: 履歴のみを取得
  2. Mutation Resolver

    • updateIncidentStatus: ステータスのみ更新
    • updateIncident: ステータスを含む全体更新

フロントエンド設計

Component構造

frontend/src/
├── components/
│   ├── IncidentDetail/
│   │   ├── StatusSection.tsx       # ステータスセクション(現在のステータス+履歴)
│   │   └── StatusSelector.tsx      # ステータス選択UI
│   ├── IncidentList/
│   │   └── StatusBadge.tsx        # ステータスバッジ表示
│   └── common/
│       └── StatusIcon.tsx         # ステータスアイコン
├── graphql/
│   └── mutations/
│       └── updateIncidentStatus.ts
└── types/
    └── incident.ts                 # ステータス型定義

ステータス履歴表示UI

interface StatusSectionProps {
    incidentId: string;
    currentStatus: IncidentStatus;
    statusHistories: StatusHistory[];
}

// 右サイドバーにステータスセクション表示
// 構成要素:
// 1. 現在のステータス(大きく表示、色分け)
// 2. ステータス変更ボタン
// 3. シンプルなステータス履歴
//    - 縦並びのエントリ、矢印で遷移を表現
//    - 各エントリ:
//      - 時刻(相対時間 - 例:2h ago)
//      - 新しいステータスバッジ
//      - 行間に上向き矢印(↑)で遷移元を表現
//      - 前のステータスバッジ
//      - 変更者名(@username)
//      - 状況メモ(あれば、グレーのサブテキスト)
//
// レイアウト例:
// ┌─────────────────────┐
// │ 🔴 Handling         │ ← 現在のステータス
// │ [Change Status]     │ ← 変更ボタン
// │  ↑                   │
// │ 2h ago              │
// │ 🔴 Handling         │
// │ @john.doe           │
// │ Started investigation │ ← メモ(グレー)
// │  ↑                  │
// │ 5h ago              │
// │ 🟡 Triage           │
// │ @incident.creator   │
// │ Initial creation    │
// └─────────────────────┘

ステータス変更UI

interface StatusSelectorProps {
    currentStatus: IncidentStatus;
    onStatusChange: (status: IncidentStatus) => void;
}

// ドロップダウンまたはボタングループ
// 現在のステータスをハイライト
// 変更時は確認ダイアログ表示

エラーハンドリング

想定されるエラー

  1. 権限エラー

    • インシデントに関わっていないユーザーからの変更
  2. 状態遷移エラー

    • 無効なステータス遷移(将来的な制約用)
  3. 同時更新エラー

    • 複数ユーザーが同時にステータス変更

エラー処理方針

  • goerr.Wrapで適切なコンテキストを付与
  • ユーザー向けメッセージは明確に
  • ログには詳細情報を記録

テスト設計

ユニットテスト

  1. Domain層

    • ステータス遷移のバリデーション
    • 履歴エンティティの生成
  2. UseCase層

    • ステータス更新ロジック
    • 履歴記録の確認
    • Slackメッセージ生成
  3. Repository層

    • Firestore: モック使用
    • Memory: 実装テスト

統合テスト

  1. Slackワークフロー

    • statusコマンドの処理
    • モーダル表示と更新
  2. GraphQLワークフロー

    • ステータス更新mutation
    • 履歴取得query

E2Eテスト

  1. HTTP E2Eテスト (pkg/controller/http/server_test.go)
    • GraphQL経由でのupdateIncidentStatus mutationの動作確認
    • GraphQL経由でのincidentStatusHistory queryの動作確認
    • HTTPリクエスト/レスポンスの検証
    • ステータス変更と履歴取得の一連のフロー
    • 認証・権限チェックとエラーハンドリング

移行計画

  1. 既存データの移行

    • 既存インシデントにデフォルトステータス(Handling)を設定
    • 初期履歴レコードを作成
  2. 後方互換性

    • ステータスフィールドがない既存データは Handling として扱う
    • 段階的な機能追加が可能な設計

セキュリティ考慮事項

  1. 認証・認可

    • Slack署名の検証
    • セッション管理によるWeb認証
  2. 監査証跡

    • すべてのステータス変更を記録
    • 削除・編集不可の履歴
  3. データ保護

    • HTTPOnly Cookieでセッション管理
    • XSS対策の実施
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment