use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
#[derive(Deserialize, Debug, Clone)]
pub struct GitLabLabelDetail {
pub name: String,
pub color: String,
}
#[derive(Deserialize, Debug, Clone)]
pub struct GitLabUser {
pub username: String,
pub name: String,
}
#[derive(Deserialize, Debug, Clone)]
pub struct GitLabMilestone {
pub title: String,
pub due_date: Option<String>,
}
#[allow(dead_code)]
#[derive(Deserialize, Debug, Clone)]
pub struct GitLabCommit {
pub id: String,
pub short_id: String,
pub title: String,
pub message: Option<String>,
pub author_name: String,
pub author_email: String,
pub authored_date: String,
pub committer_name: String,
pub committer_email: String,
pub committed_date: String,
#[serde(default)]
pub parent_ids: Vec<String>,
pub web_url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum GitlabMrState {
#[default]
Opened,
Merged,
Closed,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
pub enum MergeabilityStatus {
Mergeable,
Conflict,
NeedsRebase,
NotOpen,
Draft,
DiscussionsNotResolved,
CiMustPass,
CiStillRunning,
NotApproved,
RequestedChanges,
#[default]
Unknown,
}
#[derive(Deserialize, Debug, Clone)]
pub struct GitLabMr {
pub title: String,
pub state: Option<GitlabMrState>,
pub description: Option<String>,
pub author: Option<GitLabUser>,
pub assignee: Option<GitLabUser>,
pub reviewers: Option<Vec<GitLabUser>>,
pub merged_by: Option<GitLabUser>,
pub merged_at: Option<String>,
pub milestone: Option<GitLabMilestone>,
pub merge_commit_sha: Option<String>,
pub squash_commit_sha: Option<String>,
pub web_url: Option<String>,
pub labels: Option<Vec<String>>,
pub updated_at: Option<String>,
pub source_branch: Option<String>,
pub target_branch: Option<String>,
pub merge_status: Option<String>,
pub detailed_merge_status: Option<String>,
pub has_conflicts: Option<bool>,
pub user_notes_count: Option<u32>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct GitLabRef {
pub name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SavedMr {
pub id: String,
pub title: String,
pub sha: Option<String>,
pub found_branches: HashSet<String>,
pub description: Option<String>,
pub author: Option<String>,
pub assignee: Option<String>,
#[serde(default)]
pub reviewers: Vec<String>,
pub milestone: Option<String>,
#[serde(default)]
pub milestone_due_date: Option<String>,
pub web_url: Option<String>,
pub labels: Option<Vec<String>>,
#[serde(default)]
pub updated_at: Option<String>,
#[serde(default)]
pub source_branch: Option<String>,
#[serde(default)]
pub target_branch: Option<String>,
#[serde(default)]
pub state: GitlabMrState,
#[serde(default)]
pub merged_by: Option<String>,
#[serde(default)]
pub merged_at: Option<String>,
#[serde(default)]
pub pipelines: Vec<Pipeline>,
#[serde(default)]
pub user_notes_count: u32,
#[serde(default)]
pub flagged: bool,
#[serde(default)]
pub linked_ticket: Option<gitlab_tracker_core::LinkedTicket>,
#[serde(default)]
pub diff_stats: Option<DiffStats>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SavedState {
pub mrs: Vec<SavedMr>,
#[serde(default, skip_serializing)]
pub branches: Vec<String>,
#[serde(default)]
pub last_known_branches: HashMap<String, HashSet<String>>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum MrStatus {
Loading,
Error,
MergedIn(HashSet<String>),
}
#[derive(Clone, Debug)]
pub struct TrackedMr {
pub id: String,
pub title: String,
pub status: MrStatus,
pub sha: Option<String>,
pub description: String,
pub author: String,
pub assignee: String,
pub reviewers: Vec<String>,
pub milestone: String,
pub milestone_due_date: Option<String>,
pub web_url: String,
pub labels: Vec<String>,
pub updated_at: Option<String>,
pub source_branch: String,
pub target_branch: String,
pub state: GitlabMrState,
pub merged_by: Option<String>,
pub merged_at: Option<String>,
pub mergeability: MergeabilityStatus,
pub pipelines: Vec<Pipeline>,
pub recently_updated: bool,
pub user_notes_count: u32,
pub flagged: bool,
pub linked_ticket: Option<gitlab_tracker_core::LinkedTicket>,
pub diff_stats: Option<DiffStats>,
}
#[derive(Debug, Clone)]
pub struct MrLoadedData {
pub id: String,
pub title: String,
pub sha: Option<String>,
pub branches: HashSet<String>,
pub description: String,
pub author: String,
pub assignee: String,
pub reviewers: Vec<String>,
pub milestone: String,
pub milestone_due_date: Option<String>,
pub web_url: String,
pub labels: Vec<String>,
pub updated_at: Option<String>,
pub source_branch: String,
pub target_branch: String,
pub state: GitlabMrState,
pub merged_by: Option<String>,
pub merged_at: Option<String>,
pub mergeability: MergeabilityStatus,
pub pipelines: Vec<Pipeline>,
pub user_notes_count: u32,
pub diff_stats: Option<DiffStats>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct DiffStats {
pub files_changed: u32,
pub additions: u32,
pub deletions: u32,
#[serde(default)]
pub commits_count: u32,
}
impl DiffStats {
pub fn difficulty(&self, profile: &DifficultyProfile) -> f64 {
let total_lines = (self.additions + self.deletions) as f64;
let weighted = total_lines * 0.8 + self.files_changed as f64 * 0.2;
let range = (profile.hard_threshold - profile.easy_threshold) as f64;
if range <= 0.0 {
return 1.0;
}
let score = (weighted - profile.easy_threshold as f64) / range;
score.clamp(0.0, 1.0)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DifficultyProfile {
pub name: String,
pub easy_threshold: u32,
pub hard_threshold: u32,
}
impl Default for DifficultyProfile {
fn default() -> Self {
Self {
name: "Generic".to_string(),
easy_threshold: 200,
hard_threshold: 1000,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum PipelineState {
Created,
Pending,
Running,
Success,
Failed,
Canceled,
Skipped,
#[serde(other)]
#[default]
Unknown,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PipelineJob {
#[serde(default)]
pub name: String,
#[serde(default)]
pub stage: String,
#[serde(default)]
pub status: String,
pub duration: Option<f64>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Pipeline {
pub id: u64,
#[serde(default)]
pub status: PipelineState,
pub created_at: Option<String>,
#[serde(default)]
pub jobs: Vec<PipelineJob>,
}
pub enum AppEvent {
MrLoaded(Box<MrLoadedData>),
MrFailed {
id: String,
error: String,
},
GitlabLabelsLoaded(Vec<GitLabLabelDetail>),
MilestonesLoaded(Vec<GitLabMilestone>),
MilestoneMrsLoaded {
milestone_title: String,
mr_ids: Vec<String>,
},
TrackerTicketLoaded {
mr_id: String,
ticket: Box<gitlab_tracker_core::LinkedTicket>,
},
ActivitiesLoaded(Vec<gitlab_tracker_core::Activity>),
TimeEntriesLoaded {
entries: Vec<gitlab_tracker_core::TimeEntry>,
},
TimeLogSubmitted {
mr_id: String,
ticket_id: String,
},
TimeLogFailed {
error: String,
},
Tick,
}