Skip to main content

elizaos_plugin_github/actions/
create_issue.rs

1#![allow(missing_docs)]
2
3use async_trait::async_trait;
4use serde_json::json;
5
6use super::{ActionContext, ActionResult, GitHubAction};
7use crate::error::Result;
8use crate::types::CreateIssueParams;
9use crate::GitHubService;
10
11pub struct CreateIssueAction;
12
13#[async_trait]
14impl GitHubAction for CreateIssueAction {
15    fn name(&self) -> &str {
16        "CREATE_GITHUB_ISSUE"
17    }
18
19    fn description(&self) -> &str {
20        "Creates a new issue in a GitHub repository. Use this to report bugs, request features, or track tasks."
21    }
22
23    fn similes(&self) -> Vec<&str> {
24        vec![
25            "OPEN_ISSUE",
26            "NEW_ISSUE",
27            "FILE_ISSUE",
28            "REPORT_BUG",
29            "CREATE_BUG_REPORT",
30            "SUBMIT_ISSUE",
31        ]
32    }
33
34    async fn validate(&self, context: &ActionContext) -> Result<bool> {
35        let text = context
36            .message
37            .get("content")
38            .and_then(|c| c.get("text"))
39            .and_then(|t| t.as_str())
40            .unwrap_or("")
41            .to_lowercase();
42
43        Ok(text.contains("issue")
44            || text.contains("bug")
45            || text.contains("report")
46            || text.contains("ticket"))
47    }
48
49    async fn handler(
50        &self,
51        context: &ActionContext,
52        service: &GitHubService,
53    ) -> Result<ActionResult> {
54        let text = context
55            .message
56            .get("content")
57            .and_then(|c| c.get("text"))
58            .and_then(|t| t.as_str())
59            .unwrap_or("");
60
61        let title = context
62            .state
63            .get("title")
64            .and_then(|t| t.as_str())
65            .map(|s| s.to_string())
66            .unwrap_or_else(|| text.chars().take(100).collect());
67
68        let params = CreateIssueParams {
69            owner: context.owner.clone(),
70            repo: context.repo.clone(),
71            title,
72            body: Some(text.to_string()),
73            assignees: Vec::new(),
74            labels: Vec::new(),
75            milestone: None,
76        };
77
78        let issue = service.create_issue(params).await?;
79
80        Ok(ActionResult::success(
81            format!("Created issue #{}: {}", issue.number, issue.title),
82            json!({
83                "issue_number": issue.number,
84                "html_url": issue.html_url,
85            }),
86        ))
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use serde_json::json;
94
95    #[tokio::test]
96    async fn test_validate_with_issue_keyword() {
97        let action = CreateIssueAction;
98        let context = ActionContext {
99            message: json!({
100                "content": { "text": "Create an issue for this bug" }
101            }),
102            owner: "test".to_string(),
103            repo: "test".to_string(),
104            state: json!({}),
105        };
106
107        assert!(action.validate(&context).await.unwrap());
108    }
109
110    #[tokio::test]
111    async fn test_validate_without_keywords() {
112        let action = CreateIssueAction;
113        let context = ActionContext {
114            message: json!({
115                "content": { "text": "Hello world" }
116            }),
117            owner: "test".to_string(),
118            repo: "test".to_string(),
119            state: json!({}),
120        };
121
122        assert!(!action.validate(&context).await.unwrap());
123    }
124}