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
type IncidentStatus string
const (
IncidentStatusTriage IncidentStatus = "triage"
IncidentStatusHandling IncidentStatus = "handling"
IncidentStatusMonitoring IncidentStatus = "monitoring"
IncidentStatusClosed IncidentStatus = "closed"
)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 // 状況メモ(オプション)
}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から開始したかのフラグ
}/Incidents/{incidentId}
{
ID: string
Title: string
Description: string
Lead: string
Status: string // 現在のステータス
InitialTriage: boolean
CreatedAt: timestamp
UpdatedAt: timestamp
}
/Incidents/{incidentId}/StatusHistories/{historyId}
{
ID: string
IncidentID: string
Status: string
ChangedBy: string // SlackユーザーID
ChangedAt: timestamp
Note: string
}
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に投稿
- Editボタンを付与
- 既存のインシデント編集モーダルを拡張
- ステータス選択ドロップダウンを追加
- 状況メモの入力フィールド(オプション)
- 既存のインシデント宣言モーダルを拡張
- "Start with Triage" チェックボックスを追加
- チェック状態に応じて初期ステータスを設定
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から取得したユーザー情報
}type IncidentUseCase interface {
// 既存メソッド...
// インシデント作成(初期ステータス対応)
CreateIncidentWithStatus(ctx context.Context, params CreateIncidentParams) (*Incident, error)
// インシデント更新(ステータス含む)
UpdateIncidentWithStatus(ctx context.Context, params UpdateIncidentParams) (*Incident, error)
}-
メンションハンドラー
statusまたはsコマンドの検出- StatusUseCaseを呼び出してメッセージ投稿
-
インタラクションハンドラー
- ステータス編集モーダルの表示
- モーダルsubmitの処理
- StatusUseCaseを呼び出してステータス更新
-
宣言モーダルハンドラー
- Triageチェックボックスの状態取得
- 初期ステータスの設定
-
Query Resolver
- incident: ステータス履歴を含めて返却
- incidentStatusHistory: 履歴のみを取得
-
Mutation Resolver
- updateIncidentStatus: ステータスのみ更新
- updateIncident: ステータスを含む全体更新
frontend/src/
├── components/
│ ├── IncidentDetail/
│ │ ├── StatusSection.tsx # ステータスセクション(現在のステータス+履歴)
│ │ └── StatusSelector.tsx # ステータス選択UI
│ ├── IncidentList/
│ │ └── StatusBadge.tsx # ステータスバッジ表示
│ └── common/
│ └── StatusIcon.tsx # ステータスアイコン
├── graphql/
│ └── mutations/
│ └── updateIncidentStatus.ts
└── types/
└── incident.ts # ステータス型定義
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 │
// └─────────────────────┘interface StatusSelectorProps {
currentStatus: IncidentStatus;
onStatusChange: (status: IncidentStatus) => void;
}
// ドロップダウンまたはボタングループ
// 現在のステータスをハイライト
// 変更時は確認ダイアログ表示-
権限エラー
- インシデントに関わっていないユーザーからの変更
-
状態遷移エラー
- 無効なステータス遷移(将来的な制約用)
-
同時更新エラー
- 複数ユーザーが同時にステータス変更
- goerr.Wrapで適切なコンテキストを付与
- ユーザー向けメッセージは明確に
- ログには詳細情報を記録
-
Domain層
- ステータス遷移のバリデーション
- 履歴エンティティの生成
-
UseCase層
- ステータス更新ロジック
- 履歴記録の確認
- Slackメッセージ生成
-
Repository層
- Firestore: モック使用
- Memory: 実装テスト
-
Slackワークフロー
- statusコマンドの処理
- モーダル表示と更新
-
GraphQLワークフロー
- ステータス更新mutation
- 履歴取得query
- HTTP E2Eテスト (
pkg/controller/http/server_test.go)- GraphQL経由でのupdateIncidentStatus mutationの動作確認
- GraphQL経由でのincidentStatusHistory queryの動作確認
- HTTPリクエスト/レスポンスの検証
- ステータス変更と履歴取得の一連のフロー
- 認証・権限チェックとエラーハンドリング
-
既存データの移行
- 既存インシデントにデフォルトステータス(Handling)を設定
- 初期履歴レコードを作成
-
後方互換性
- ステータスフィールドがない既存データは Handling として扱う
- 段階的な機能追加が可能な設計
-
認証・認可
- Slack署名の検証
- セッション管理によるWeb認証
-
監査証跡
- すべてのステータス変更を記録
- 削除・編集不可の履歴
-
データ保護
- HTTPOnly Cookieでセッション管理
- XSS対策の実施