Skip to main content

progit_plugin_sdk/traits/
integration.rs

1// SPDX-License-Identifier: LSL-1.0
2// Copyright (c) 2025 Markus Maiwald
3
4//! Integration plugin traits
5//!
6//! Traits for plugins that sync with external issue trackers
7//! (Jira, Linear, GitHub Issues, Notion, etc.)
8
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12use super::core::{Issue, Plugin, PluginResult};
13
14/// Result of syncing an issue to an external system
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct SyncResult {
17    /// Local ProGit issue ID
18    pub local_id: String,
19    /// ID in the external system
20    pub external_id: String,
21    /// URL to view in external system (optional)
22    pub external_url: Option<String>,
23    /// Status of this sync operation
24    pub status: SyncStatus,
25}
26
27/// Status of a sync operation
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
29pub enum SyncStatus {
30    /// Issue was created in external system
31    Created,
32    /// Issue was updated in external system
33    Updated,
34    /// Issue was unchanged (no sync needed)
35    Unchanged,
36    /// Sync failed with error message
37    Failed(String),
38    /// Issue was skipped (e.g., filtered out)
39    Skipped,
40}
41
42/// How to resolve conflicts between local and remote issues
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub enum ConflictResolution {
45    /// Use the local version
46    TakeLocal,
47    /// Use the remote version
48    TakeRemote,
49    /// Use a custom merged result
50    Merge(Issue),
51    /// Skip this issue (don't sync)
52    Skip,
53}
54
55/// Information about an integration plugin
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct IntegrationInfo {
58    /// Display name: "Jira", "Linear", "GitHub Issues", etc.
59    pub name: String,
60    /// ASCII/Unicode icon for UI display
61    pub icon: Option<String>,
62    /// Whether this integration supports bidirectional sync
63    pub supports_bidirectional: bool,
64    /// Whether this integration can receive webhooks
65    pub supports_webhooks: bool,
66    /// Authentication method required
67    pub auth_type: AuthType,
68    /// External system base URL (if configured)
69    pub base_url: Option<String>,
70}
71
72/// Authentication methods supported by integrations
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
74pub enum AuthType {
75    /// API token (e.g., Jira, Linear)
76    ApiToken,
77    /// OAuth 2.0 flow
78    OAuth2,
79    /// Basic HTTP auth (username/password)
80    BasicAuth,
81    /// No authentication required
82    None,
83}
84
85/// Link to an issue in an external system
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ExternalLink {
88    /// Provider name: "jira", "linear", "github", etc.
89    pub provider: String,
90    /// ID in the external system
91    pub external_id: String,
92    /// URL to view in external system
93    pub external_url: Option<String>,
94    /// When this link was last synced
95    pub last_synced: String,
96    /// Current sync status
97    pub sync_status: String,
98}
99
100/// Configuration for field mapping between ProGit and external systems
101#[derive(Debug, Clone, Serialize, Deserialize, Default)]
102pub struct FieldMappings {
103    /// Status mappings: local -> external
104    pub status_outbound: HashMap<String, String>,
105    /// Status mappings: external -> local
106    pub status_inbound: HashMap<String, String>,
107    /// Tag/label mappings
108    pub tags: HashMap<String, String>,
109    /// Custom field mappings (external field ID -> local field)
110    pub custom_fields: HashMap<String, String>,
111}
112
113/// Trait for plugins that sync with external issue trackers
114pub trait IntegrationPlugin: Plugin {
115    /// Push issues to external system
116    ///
117    /// Called periodically or on-demand to sync issues TO the external system.
118    /// Returns list of sync results indicating what happened for each issue.
119    fn push_issues(&mut self, issues: &[Issue]) -> PluginResult<Vec<SyncResult>>;
120
121    /// Pull issues from external system
122    ///
123    /// Called to fetch issues FROM the external system.
124    /// Returns issues that should be merged into local storage.
125    fn pull_issues(&mut self) -> PluginResult<Vec<Issue>>;
126
127    /// Resolve conflict between local and remote versions
128    ///
129    /// Called when local and remote versions of an issue have diverged.
130    /// Default implementation uses timestamp-based resolution (newer wins).
131    fn resolve_conflict(
132        &mut self,
133        local: &Issue,
134        remote: &Issue,
135    ) -> PluginResult<ConflictResolution> {
136        // Default: remote wins if newer
137        Ok(if remote.updated > local.updated {
138            ConflictResolution::TakeRemote
139        } else {
140            ConflictResolution::TakeLocal
141        })
142    }
143
144    /// Map local status to external status
145    ///
146    /// Converts ProGit status (e.g., "in-progress") to external system
147    /// status (e.g., "In Progress" for Jira).
148    fn map_status_outbound(&self, status: &str) -> String;
149
150    /// Map external status to local status
151    ///
152    /// Converts external system status to ProGit status.
153    fn map_status_inbound(&self, external_status: &str) -> String;
154
155    /// Get integration metadata
156    ///
157    /// Returns information about this integration for UI display and
158    /// capability detection.
159    fn integration_info(&self) -> IntegrationInfo;
160
161    /// Handle incoming webhook payload
162    ///
163    /// Process a webhook from the external system. Returns issues that
164    /// were affected by the webhook.
165    fn handle_webhook(&mut self, _payload: &serde_json::Value) -> PluginResult<Vec<Issue>> {
166        // Default: no webhook support
167        Ok(vec![])
168    }
169
170    /// Get field mappings configuration
171    ///
172    /// Returns the current field mapping configuration for this integration.
173    fn field_mappings(&self) -> FieldMappings {
174        FieldMappings::default()
175    }
176
177    /// Validate connection to external system
178    ///
179    /// Tests that the integration can connect to the external system
180    /// with the current configuration.
181    fn validate_connection(&mut self) -> PluginResult<bool> {
182        // Default: assume connected
183        Ok(true)
184    }
185}