diff --git a/crates/tabby-common/src/config.rs b/crates/tabby-common/src/config.rs index a5e83c1cc5d4..fe43cc9a8122 100644 --- a/crates/tabby-common/src/config.rs +++ b/crates/tabby-common/src/config.rs @@ -71,7 +71,7 @@ impl Config { } } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct RepositoryConfig { pub git_url: String, } diff --git a/ee/tabby-db/src/integrations.rs b/ee/tabby-db/src/integrations.rs index f3a0a7191450..9e58df25e8a5 100644 --- a/ee/tabby-db/src/integrations.rs +++ b/ee/tabby-db/src/integrations.rs @@ -86,7 +86,7 @@ impl DbConn { }; let res = query!( - "UPDATE integrations SET display_name = ?, access_token = ?, api_base = ?, updated_at = DATETIME('now'), synced = false WHERE id = ? AND kind = ?;", + "UPDATE integrations SET display_name = $1, access_token = $2, api_base = $3, updated_at = DATETIME('now'), synced = false, error = IIF(access_token != $2 OR api_base != $3, NULL, error) WHERE id = $4 AND kind = $5;", display_name, access_token, api_base, diff --git a/ee/tabby-webserver/src/service/background_job/mod.rs b/ee/tabby-webserver/src/service/background_job/mod.rs index b2fd76767991..29a18c25312e 100644 --- a/ee/tabby-webserver/src/service/background_job/mod.rs +++ b/ee/tabby-webserver/src/service/background_job/mod.rs @@ -19,6 +19,7 @@ use self::{ db::DbMaintainanceJob, scheduler::SchedulerJob, third_party_integration::SyncIntegrationJob, }; +#[derive(PartialEq, Debug)] pub enum BackgroundJobEvent { Scheduler(RepositoryConfig), SyncThirdPartyRepositories(ID), diff --git a/ee/tabby-webserver/src/service/integration.rs b/ee/tabby-webserver/src/service/integration.rs index 24fc8f32faea..9a96fca57292 100644 --- a/ee/tabby-webserver/src/service/integration.rs +++ b/ee/tabby-webserver/src/service/integration.rs @@ -62,6 +62,12 @@ impl IntegrationService for IntegrationServiceImpl { access_token: Option, api_base: Option, ) -> Result<()> { + let integration = self.get_integration(id.clone()).await?; + let access_token_is_changed = access_token + .as_ref() + .is_some_and(|token| token != &integration.access_token); + let api_base_is_changed = integration.api_base != api_base; + self.db .update_integration( id.as_rowid()?, @@ -71,6 +77,13 @@ impl IntegrationService for IntegrationServiceImpl { api_base, ) .await?; + + if access_token_is_changed || api_base_is_changed { + let _ = self + .background_job + .send(BackgroundJobEvent::SyncThirdPartyRepositories(id.clone())); + } + Ok(()) } @@ -201,4 +214,89 @@ mod tests { .len() ); } + + #[tokio::test] + async fn test_update_integration_should_reset_status() { + let (background, mut recv) = tokio::sync::mpsc::unbounded_channel(); + let db = DbConn::new_in_memory().await.unwrap(); + let integration = Arc::new(create(db, background)); + + let id = integration + .create_integration(IntegrationKind::Github, "gh".into(), "token".into(), None) + .await + .unwrap(); + + // Test event is sent to re-sync provider after provider is created + let event = recv.recv().await.unwrap(); + assert_eq!( + event, + BackgroundJobEvent::SyncThirdPartyRepositories(id.clone()) + ); + + // Test integration status is failed after updating sync status with an error + integration + .update_integration_sync_status(id.clone(), Some("error".into())) + .await + .unwrap(); + + let provider = integration.get_integration(id.clone()).await.unwrap(); + + assert_eq!(provider.status, IntegrationStatus::Failed); + + // Test integration status is not changed if token has not been updated + integration + .update_integration( + id.clone(), + IntegrationKind::Github, + "gh".into(), + Some("token".into()), + None, + ) + .await + .unwrap(); + + let provider = integration.get_integration(id.clone()).await.unwrap(); + + assert_eq!(provider.status, IntegrationStatus::Failed); + + // Test integration status is pending after updating token + integration + .update_integration( + id.clone(), + IntegrationKind::Github, + "gh".into(), + Some("token2".into()), + None, + ) + .await + .unwrap(); + + let provider = integration.get_integration(id.clone()).await.unwrap(); + + assert_eq!(provider.status, IntegrationStatus::Pending); + + // Test event is sent to re-sync provider after credentials are updated + let event = recv.recv().await.unwrap(); + assert_eq!( + event, + BackgroundJobEvent::SyncThirdPartyRepositories(id.clone()) + ); + + // Test sync event is not sent if no fields are updated + integration + .update_integration( + id.clone(), + IntegrationKind::Github, + "gh".into(), + Some("token2".into()), + None, + ) + .await + .unwrap(); + + assert_eq!( + recv.try_recv(), + Err(tokio::sync::mpsc::error::TryRecvError::Empty) + ); + } }