diff --git a/src/gitlab.rs b/src/gitlab.rs index 4c15405..04b0da1 100644 --- a/src/gitlab.rs +++ b/src/gitlab.rs @@ -245,6 +245,35 @@ impl GitlabApiClient { .await } + #[instrument(skip(self), fields(project_id, since_timestamp))] + pub async fn get_opened_issues( + &self, + project_id: i64, + since_timestamp: u64, + ) -> Result, GitlabError> { + let path = format!("/api/v4/projects/{project_id}/issues"); + let dt = DateTime::from_timestamp(since_timestamp as i64, 0).unwrap_or_else(|| { + Utc.timestamp_opt(0, 0) + .single() + .expect("Fallback timestamp failed for 0") + }); + let formatted_timestamp_string = dt.to_rfc3339(); + + let query_params_values = [ + ("updated_after", formatted_timestamp_string), + ("state", "opened".to_string()), + ("sort", "asc".to_string()), + ("per_page", "100".to_string()), + ]; + let params: Vec<(&str, &str)> = query_params_values + .iter() + .map(|(k, v)| (*k, v.as_str())) + .collect(); + + self.send_request(Method::GET, &path, Some(¶ms), None::<()>) + .await + } + #[instrument(skip(self), fields(project_id, since_timestamp))] pub async fn get_merge_requests( &self, diff --git a/src/polling.rs b/src/polling.rs index b74005e..62642fa 100644 --- a/src/polling.rs +++ b/src/polling.rs @@ -223,7 +223,8 @@ impl PollingService { // Fetch old issues for stale check (since 0) // We fetch separately because "sort=asc" means we get OLDEST updated issues with 0, // but recent ones with fetch_recent_ts. - let stale_issues = match self.gitlab_client.get_issues(project_id, 0).await { + // We use get_opened_issues to filter by state=opened server-side. + let open_stale_issues = match self.gitlab_client.get_opened_issues(project_id, 0).await { Ok(issues) => issues, Err(e) => { error!( @@ -234,11 +235,6 @@ impl PollingService { } }; - let open_stale_issues: Vec = stale_issues - .into_iter() - .filter(|i| i.state == "opened") - .collect(); - // Task for checking stale issues let stale_check_task = { let project_id_clone = project_id; diff --git a/src/tests/gitlab_tests.rs b/src/tests/gitlab_tests.rs index f2710d3..5330fe2 100644 --- a/src/tests/gitlab_tests.rs +++ b/src/tests/gitlab_tests.rs @@ -1046,3 +1046,42 @@ async fn test_search_files() { assert!(results.contains(&"src/main.rs".to_string())); assert!(results.contains(&"src/utils.rs".to_string())); } + +#[tokio::test] +async fn test_get_opened_issues() { + let mut server = mockito::Server::new_async().await; + let base_url = server.url(); + let settings = Arc::new(create_test_settings(base_url)); + let client = GitlabApiClient::new(settings).unwrap(); + + let mock_issues_response = serde_json::json!([ + { + "id": 1, "iid": 101, "project_id": 1, "title": "Test Issue 1", + "description": "A test issue 1", "state": "opened", + "author": {"id": 1, "username": "tester", "name": "Test User", "avatar_url": null, "web_url": "url"}, + "web_url": "http://example.com/issue/1", "labels": [], "created_at": "2023-01-01T12:00:00Z", "updated_at": "2023-01-02T12:00:00Z" + } + ]); + + let _m = server + .mock("GET", "/api/v4/projects/1/issues") + .match_query(mockito::Matcher::AllOf(vec![ + mockito::Matcher::UrlEncoded( + "updated_after".into(), + "2021-05-03T00:00:00+00:00".into(), + ), + mockito::Matcher::UrlEncoded("state".into(), "opened".into()), + mockito::Matcher::UrlEncoded("sort".into(), "asc".into()), + mockito::Matcher::UrlEncoded("per_page".into(), "100".into()), + ])) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(mock_issues_response.to_string()) + .create_async() + .await; + + let issues = client.get_opened_issues(1, 1620000000).await.unwrap(); + assert_eq!(issues.len(), 1); + assert_eq!(issues[0].title, "Test Issue 1"); + assert_eq!(issues[0].state, "opened"); +}