titan-cli 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- titan_cli/core/config.py +3 -1
- titan_cli/core/workflows/__init__.py +2 -1
- titan_cli/core/workflows/project_step_source.py +48 -30
- titan_cli/core/workflows/workflow_filter_service.py +14 -8
- titan_cli/core/workflows/workflow_registry.py +12 -1
- titan_cli/core/workflows/workflow_sources.py +1 -1
- titan_cli/engine/steps/ai_assistant_step.py +42 -7
- titan_cli/engine/workflow_executor.py +6 -1
- titan_cli/ui/tui/screens/workflow_execution.py +8 -28
- titan_cli/ui/tui/textual_components.py +59 -6
- titan_cli/ui/tui/textual_workflow_executor.py +9 -1
- titan_cli/ui/tui/widgets/__init__.py +2 -0
- titan_cli/ui/tui/widgets/step_container.py +70 -0
- {titan_cli-0.1.4.dist-info → titan_cli-0.1.5.dist-info}/METADATA +6 -3
- {titan_cli-0.1.4.dist-info → titan_cli-0.1.5.dist-info}/RECORD +39 -37
- {titan_cli-0.1.4.dist-info → titan_cli-0.1.5.dist-info}/WHEEL +1 -1
- titan_plugin_git/clients/git_client.py +82 -4
- titan_plugin_git/plugin.py +3 -0
- titan_plugin_git/steps/ai_commit_message_step.py +33 -28
- titan_plugin_git/steps/branch_steps.py +18 -37
- titan_plugin_git/steps/commit_step.py +18 -22
- titan_plugin_git/steps/diff_summary_step.py +182 -0
- titan_plugin_git/steps/push_step.py +27 -11
- titan_plugin_git/steps/status_step.py +15 -18
- titan_plugin_git/workflows/commit-ai.yaml +5 -0
- titan_plugin_github/agents/pr_agent.py +15 -2
- titan_plugin_github/steps/ai_pr_step.py +12 -21
- titan_plugin_github/steps/create_pr_step.py +17 -7
- titan_plugin_github/steps/github_prompt_steps.py +52 -0
- titan_plugin_github/steps/issue_steps.py +28 -14
- titan_plugin_github/steps/preview_step.py +11 -0
- titan_plugin_github/utils.py +5 -4
- titan_plugin_github/workflows/create-pr-ai.yaml +5 -0
- titan_plugin_jira/steps/ai_analyze_issue_step.py +8 -3
- titan_plugin_jira/steps/get_issue_step.py +16 -12
- titan_plugin_jira/steps/prompt_select_issue_step.py +22 -9
- titan_plugin_jira/steps/search_saved_query_step.py +21 -19
- {titan_cli-0.1.4.dist-info → titan_cli-0.1.5.dist-info}/entry_points.txt +0 -0
- {titan_cli-0.1.4.dist-info → titan_cli-0.1.5.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
from titan_cli.engine.context import WorkflowContext
|
|
3
3
|
from titan_cli.engine.results import WorkflowResult, Success, Error, Skip
|
|
4
|
-
from titan_cli.ui.tui.widgets import Panel
|
|
5
4
|
from ..agents.issue_generator import IssueGeneratorAgent
|
|
6
5
|
|
|
7
6
|
def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
@@ -12,14 +11,21 @@ def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult
|
|
|
12
11
|
if not ctx.textual:
|
|
13
12
|
return Error("Textual UI context is not available for this step.")
|
|
14
13
|
|
|
14
|
+
# Begin step container
|
|
15
|
+
ctx.textual.begin_step("Categorize and Generate Issue")
|
|
16
|
+
|
|
15
17
|
if not ctx.ai:
|
|
18
|
+
ctx.textual.text("AI client not available", markup="dim")
|
|
19
|
+
ctx.textual.end_step("skip")
|
|
16
20
|
return Skip("AI client not available")
|
|
17
21
|
|
|
18
22
|
issue_body_prompt = ctx.get("issue_body")
|
|
19
23
|
if not issue_body_prompt:
|
|
24
|
+
ctx.textual.text("issue_body not found in context", markup="red")
|
|
25
|
+
ctx.textual.end_step("error")
|
|
20
26
|
return Error("issue_body not found in context")
|
|
21
27
|
|
|
22
|
-
ctx.textual.text("Using AI to categorize and generate issue...", markup="
|
|
28
|
+
ctx.textual.text("Using AI to categorize and generate issue...", markup="dim")
|
|
23
29
|
|
|
24
30
|
try:
|
|
25
31
|
# Get available labels from repository for smart mapping
|
|
@@ -46,12 +52,8 @@ def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult
|
|
|
46
52
|
category = result["category"]
|
|
47
53
|
template_used = result.get("template_used", False)
|
|
48
54
|
|
|
49
|
-
ctx.textual.
|
|
50
|
-
|
|
51
|
-
text=f"Category detected: {category}",
|
|
52
|
-
panel_type="success"
|
|
53
|
-
)
|
|
54
|
-
)
|
|
55
|
+
ctx.textual.text("") # spacing
|
|
56
|
+
ctx.textual.text(f"Category detected: {category}", markup="green")
|
|
55
57
|
|
|
56
58
|
if template_used:
|
|
57
59
|
ctx.textual.text(f"Using template: {category}.md", markup="cyan")
|
|
@@ -63,8 +65,11 @@ def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult
|
|
|
63
65
|
ctx.set("issue_category", category)
|
|
64
66
|
ctx.set("labels", result["labels"])
|
|
65
67
|
|
|
68
|
+
ctx.textual.end_step("success")
|
|
66
69
|
return Success(f"AI-generated issue ({category}) created successfully")
|
|
67
70
|
except Exception as e:
|
|
71
|
+
ctx.textual.text(f"Failed to generate issue: {e}", markup="red")
|
|
72
|
+
ctx.textual.end_step("error")
|
|
68
73
|
return Error(f"Failed to generate issue: {e}")
|
|
69
74
|
|
|
70
75
|
|
|
@@ -75,7 +80,12 @@ def create_issue_steps(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
75
80
|
if not ctx.textual:
|
|
76
81
|
return Error("Textual UI context is not available for this step.")
|
|
77
82
|
|
|
83
|
+
# Begin step container
|
|
84
|
+
ctx.textual.begin_step("Create Issue")
|
|
85
|
+
|
|
78
86
|
if not ctx.github:
|
|
87
|
+
ctx.textual.text("GitHub client not available", markup="red")
|
|
88
|
+
ctx.textual.end_step("error")
|
|
79
89
|
return Error("GitHub client not available")
|
|
80
90
|
|
|
81
91
|
issue_title = ctx.get("issue_title")
|
|
@@ -103,9 +113,13 @@ def create_issue_steps(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
103
113
|
labels = []
|
|
104
114
|
|
|
105
115
|
if not issue_title:
|
|
116
|
+
ctx.textual.text("issue_title not found in context", markup="red")
|
|
117
|
+
ctx.textual.end_step("error")
|
|
106
118
|
return Error("issue_title not found in context")
|
|
107
119
|
|
|
108
120
|
if not issue_body:
|
|
121
|
+
ctx.textual.text("issue_body not found in context", markup="red")
|
|
122
|
+
ctx.textual.end_step("error")
|
|
109
123
|
return Error("issue_body not found in context")
|
|
110
124
|
|
|
111
125
|
# Filter labels to only those that exist in the repository
|
|
@@ -123,21 +137,21 @@ def create_issue_steps(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
123
137
|
pass
|
|
124
138
|
|
|
125
139
|
try:
|
|
140
|
+
ctx.textual.text(f"Creating issue: {issue_title}...", markup="dim")
|
|
126
141
|
issue = ctx.github.create_issue(
|
|
127
142
|
title=issue_title,
|
|
128
143
|
body=issue_body,
|
|
129
144
|
assignees=assignees,
|
|
130
145
|
labels=labels,
|
|
131
146
|
)
|
|
132
|
-
ctx.textual.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
panel_type="success"
|
|
136
|
-
)
|
|
137
|
-
)
|
|
147
|
+
ctx.textual.text("") # spacing
|
|
148
|
+
ctx.textual.text(f"Successfully created issue #{issue.number}", markup="green")
|
|
149
|
+
ctx.textual.end_step("success")
|
|
138
150
|
return Success(
|
|
139
151
|
f"Successfully created issue #{issue.number}",
|
|
140
152
|
metadata={"issue": issue}
|
|
141
153
|
)
|
|
142
154
|
except Exception as e:
|
|
155
|
+
ctx.textual.text(f"Failed to create issue: {e}", markup="red")
|
|
156
|
+
ctx.textual.end_step("error")
|
|
143
157
|
return Error(f"Failed to create issue: {e}")
|
|
@@ -8,10 +8,15 @@ def preview_and_confirm_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
8
8
|
if not ctx.textual:
|
|
9
9
|
return Error("Textual UI context is not available for this step.")
|
|
10
10
|
|
|
11
|
+
# Begin step container
|
|
12
|
+
ctx.textual.begin_step("Preview and Confirm Issue")
|
|
13
|
+
|
|
11
14
|
issue_title = ctx.get("issue_title")
|
|
12
15
|
issue_body = ctx.get("issue_body")
|
|
13
16
|
|
|
14
17
|
if not issue_title or not issue_body:
|
|
18
|
+
ctx.textual.text("issue_title or issue_body not found in context", markup="red")
|
|
19
|
+
ctx.textual.end_step("error")
|
|
15
20
|
return Error("issue_title or issue_body not found in context")
|
|
16
21
|
|
|
17
22
|
# Show preview header
|
|
@@ -33,8 +38,14 @@ def preview_and_confirm_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
33
38
|
|
|
34
39
|
try:
|
|
35
40
|
if not ctx.textual.ask_confirm("Use this AI-generated issue?", default=True):
|
|
41
|
+
ctx.textual.text("User rejected AI-generated issue", markup="yellow")
|
|
42
|
+
ctx.textual.end_step("error")
|
|
36
43
|
return Error("User rejected AI-generated issue")
|
|
37
44
|
except (KeyboardInterrupt, EOFError):
|
|
45
|
+
ctx.textual.text("User cancelled operation", markup="red")
|
|
46
|
+
ctx.textual.end_step("error")
|
|
38
47
|
return Error("User cancelled operation")
|
|
39
48
|
|
|
49
|
+
ctx.textual.text("User confirmed AI-generated issue", markup="green")
|
|
50
|
+
ctx.textual.end_step("success")
|
|
40
51
|
return Success("User confirmed AI-generated issue")
|
titan_plugin_github/utils.py
CHANGED
|
@@ -54,21 +54,22 @@ def calculate_pr_size(diff: str) -> PRSizeEstimation:
|
|
|
54
54
|
files_changed = len(re.findall(file_pattern, diff, re.MULTILINE))
|
|
55
55
|
|
|
56
56
|
# Dynamic character limit based on PR size
|
|
57
|
+
# Increased limits to accommodate PR templates with images/GIFs
|
|
57
58
|
if files_changed <= 3 and diff_lines < 100:
|
|
58
59
|
# Small PR: bug fix, doc update, small feature
|
|
59
|
-
max_chars =
|
|
60
|
+
max_chars = 1500
|
|
60
61
|
pr_size = "small"
|
|
61
62
|
elif files_changed <= 10 and diff_lines < 500:
|
|
62
63
|
# Medium PR: feature, moderate refactor
|
|
63
|
-
max_chars =
|
|
64
|
+
max_chars = 2500
|
|
64
65
|
pr_size = "medium"
|
|
65
66
|
elif files_changed <= 30 and diff_lines < 2000:
|
|
66
67
|
# Large PR: architectural changes, new modules
|
|
67
|
-
max_chars =
|
|
68
|
+
max_chars = 4000
|
|
68
69
|
pr_size = "large"
|
|
69
70
|
else:
|
|
70
71
|
# Very large PR: major refactor, breaking changes
|
|
71
|
-
max_chars =
|
|
72
|
+
max_chars = 6000
|
|
72
73
|
pr_size = "very large"
|
|
73
74
|
|
|
74
75
|
return PRSizeEstimation(
|
|
@@ -19,6 +19,11 @@ steps:
|
|
|
19
19
|
plugin: git
|
|
20
20
|
step: get_current_branch
|
|
21
21
|
|
|
22
|
+
- id: branch_diff_summary
|
|
23
|
+
name: "Show Branch Changes Summary"
|
|
24
|
+
plugin: git
|
|
25
|
+
step: show_branch_diff_summary
|
|
26
|
+
|
|
22
27
|
# AI generates PR title and description from branch commits
|
|
23
28
|
- id: ai_pr_description
|
|
24
29
|
name: "AI PR Description"
|
|
@@ -3,7 +3,6 @@ AI-powered JIRA issue analysis step
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error, Skip
|
|
6
|
-
from titan_cli.ui.tui.widgets import Panel
|
|
7
6
|
from ..messages import msg
|
|
8
7
|
from ..agents import JiraAgent
|
|
9
8
|
from ..formatters import IssueAnalysisMarkdownFormatter
|
|
@@ -32,15 +31,20 @@ def ai_analyze_issue_requirements_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
32
31
|
if not ctx.textual:
|
|
33
32
|
return Error("Textual UI context is not available for this step.")
|
|
34
33
|
|
|
34
|
+
# Begin step container
|
|
35
|
+
ctx.textual.begin_step("AI Analyze Issue")
|
|
36
|
+
|
|
35
37
|
# Check if AI is available
|
|
36
38
|
if not ctx.ai or not ctx.ai.is_available():
|
|
37
|
-
ctx.textual.
|
|
39
|
+
ctx.textual.text(msg.Steps.AIIssue.AI_NOT_CONFIGURED_SKIP, markup="dim")
|
|
40
|
+
ctx.textual.end_step("skip")
|
|
38
41
|
return Skip(msg.Steps.AIIssue.AI_NOT_CONFIGURED)
|
|
39
42
|
|
|
40
43
|
# Get issue to analyze
|
|
41
44
|
issue = ctx.get("jira_issue") or ctx.get("selected_issue")
|
|
42
45
|
if not issue:
|
|
43
|
-
ctx.textual.
|
|
46
|
+
ctx.textual.text(msg.Steps.AIIssue.NO_ISSUE_FOUND, markup="red")
|
|
47
|
+
ctx.textual.end_step("error")
|
|
44
48
|
return Error(msg.Steps.AIIssue.NO_ISSUE_FOUND)
|
|
45
49
|
|
|
46
50
|
# Create JiraAgent instance and analyze issue with loading indicator
|
|
@@ -90,6 +94,7 @@ def ai_analyze_issue_requirements_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
90
94
|
"estimated_effort": analysis.estimated_effort
|
|
91
95
|
})
|
|
92
96
|
|
|
97
|
+
ctx.textual.end_step("success")
|
|
93
98
|
return Success(
|
|
94
99
|
"AI analysis completed",
|
|
95
100
|
metadata={
|
|
@@ -3,7 +3,6 @@ Get JIRA issue details step
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
|
|
6
|
-
from titan_cli.ui.tui.widgets import Panel
|
|
7
6
|
from ..exceptions import JiraAPIError
|
|
8
7
|
from ..messages import msg
|
|
9
8
|
|
|
@@ -26,15 +25,20 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
26
25
|
if not ctx.textual:
|
|
27
26
|
return Error("Textual UI context is not available for this step.")
|
|
28
27
|
|
|
28
|
+
# Begin step container
|
|
29
|
+
ctx.textual.begin_step("Get Full Issue Details")
|
|
30
|
+
|
|
29
31
|
# Check if JIRA client is available
|
|
30
32
|
if not ctx.jira:
|
|
31
|
-
ctx.textual.
|
|
33
|
+
ctx.textual.text(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT, markup="red")
|
|
34
|
+
ctx.textual.end_step("error")
|
|
32
35
|
return Error(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
|
|
33
36
|
|
|
34
37
|
# Get issue key
|
|
35
38
|
issue_key = ctx.get("jira_issue_key")
|
|
36
39
|
if not issue_key:
|
|
37
|
-
ctx.textual.
|
|
40
|
+
ctx.textual.text("JIRA issue key is required", markup="red")
|
|
41
|
+
ctx.textual.end_step("error")
|
|
38
42
|
return Error("JIRA issue key is required")
|
|
39
43
|
|
|
40
44
|
# Get optional expand fields
|
|
@@ -46,12 +50,8 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
46
50
|
issue = ctx.jira.get_ticket(ticket_key=issue_key, expand=expand)
|
|
47
51
|
|
|
48
52
|
# Show success
|
|
49
|
-
ctx.textual.
|
|
50
|
-
|
|
51
|
-
text=msg.Steps.GetIssue.GET_SUCCESS.format(issue_key=issue_key),
|
|
52
|
-
panel_type="success"
|
|
53
|
-
)
|
|
54
|
-
)
|
|
53
|
+
ctx.textual.text("") # spacing
|
|
54
|
+
ctx.textual.text(msg.Steps.GetIssue.GET_SUCCESS.format(issue_key=issue_key), markup="green")
|
|
55
55
|
|
|
56
56
|
# Show issue details
|
|
57
57
|
ctx.textual.text(f" Title: {issue.summary}", markup="cyan")
|
|
@@ -60,6 +60,7 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
60
60
|
ctx.textual.text(f" Assignee: {issue.assignee or 'Unassigned'}")
|
|
61
61
|
ctx.textual.text("")
|
|
62
62
|
|
|
63
|
+
ctx.textual.end_step("success")
|
|
63
64
|
return Success(
|
|
64
65
|
msg.Steps.GetIssue.GET_SUCCESS.format(issue_key=issue_key),
|
|
65
66
|
metadata={"jira_issue": issue}
|
|
@@ -68,14 +69,17 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
68
69
|
except JiraAPIError as e:
|
|
69
70
|
if e.status_code == 404:
|
|
70
71
|
error_msg = msg.Steps.GetIssue.ISSUE_NOT_FOUND.format(issue_key=issue_key)
|
|
71
|
-
ctx.textual.
|
|
72
|
+
ctx.textual.text(error_msg, markup="red")
|
|
73
|
+
ctx.textual.end_step("error")
|
|
72
74
|
return Error(error_msg)
|
|
73
75
|
error_msg = msg.Steps.GetIssue.GET_FAILED.format(e=e)
|
|
74
|
-
ctx.textual.
|
|
76
|
+
ctx.textual.text(error_msg, markup="red")
|
|
77
|
+
ctx.textual.end_step("error")
|
|
75
78
|
return Error(error_msg)
|
|
76
79
|
except Exception as e:
|
|
77
80
|
error_msg = f"Unexpected error getting issue: {e}"
|
|
78
|
-
ctx.textual.
|
|
81
|
+
ctx.textual.text(error_msg, markup="red")
|
|
82
|
+
ctx.textual.end_step("error")
|
|
79
83
|
return Error(error_msg)
|
|
80
84
|
|
|
81
85
|
|
|
@@ -3,7 +3,6 @@ Prompt user to select an issue from search results
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
|
|
6
|
-
from titan_cli.ui.tui.widgets import Panel
|
|
7
6
|
from ..messages import msg
|
|
8
7
|
|
|
9
8
|
|
|
@@ -21,12 +20,19 @@ def prompt_select_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
21
20
|
if not ctx.textual:
|
|
22
21
|
return Error("Textual UI context is not available for this step.")
|
|
23
22
|
|
|
23
|
+
# Begin step container
|
|
24
|
+
ctx.textual.begin_step("Select Issue to Analyze")
|
|
25
|
+
|
|
24
26
|
# Get issues from previous search
|
|
25
27
|
issues = ctx.get("jira_issues")
|
|
26
28
|
if not issues:
|
|
29
|
+
ctx.textual.text(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE, markup="red")
|
|
30
|
+
ctx.textual.end_step("error")
|
|
27
31
|
return Error(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE)
|
|
28
32
|
|
|
29
33
|
if len(issues) == 0:
|
|
34
|
+
ctx.textual.text(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE, markup="red")
|
|
35
|
+
ctx.textual.end_step("error")
|
|
30
36
|
return Error(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE)
|
|
31
37
|
|
|
32
38
|
# Prompt user to select issue (issues already displayed in table from previous step)
|
|
@@ -40,32 +46,37 @@ def prompt_select_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
40
46
|
)
|
|
41
47
|
|
|
42
48
|
if not response or not response.strip():
|
|
49
|
+
ctx.textual.text(msg.Steps.PromptSelectIssue.NO_ISSUE_SELECTED, markup="red")
|
|
50
|
+
ctx.textual.end_step("error")
|
|
43
51
|
return Error(msg.Steps.PromptSelectIssue.NO_ISSUE_SELECTED)
|
|
44
52
|
|
|
45
53
|
# Validate it's a number
|
|
46
54
|
try:
|
|
47
55
|
selected_index = int(response.strip())
|
|
48
56
|
except ValueError:
|
|
57
|
+
ctx.textual.text(f"Invalid input: '{response}' is not a number", markup="red")
|
|
58
|
+
ctx.textual.end_step("error")
|
|
49
59
|
return Error(f"Invalid input: '{response}' is not a number")
|
|
50
60
|
|
|
51
61
|
# Validate it's in range
|
|
52
62
|
if selected_index < 1 or selected_index > len(issues):
|
|
63
|
+
ctx.textual.text(f"Invalid selection: must be between 1 and {len(issues)}", markup="red")
|
|
64
|
+
ctx.textual.end_step("error")
|
|
53
65
|
return Error(f"Invalid selection: must be between 1 and {len(issues)}")
|
|
54
66
|
|
|
55
67
|
# Convert to 0-based index
|
|
56
68
|
selected_issue = issues[selected_index - 1]
|
|
57
69
|
|
|
58
70
|
ctx.textual.text("")
|
|
59
|
-
ctx.textual.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
panel_type="success"
|
|
66
|
-
)
|
|
71
|
+
ctx.textual.text(
|
|
72
|
+
msg.Steps.PromptSelectIssue.ISSUE_SELECTION_CONFIRM.format(
|
|
73
|
+
key=selected_issue.key,
|
|
74
|
+
summary=selected_issue.summary
|
|
75
|
+
),
|
|
76
|
+
markup="green"
|
|
67
77
|
)
|
|
68
78
|
|
|
79
|
+
ctx.textual.end_step("success")
|
|
69
80
|
return Success(
|
|
70
81
|
msg.Steps.PromptSelectIssue.SELECT_SUCCESS.format(key=selected_issue.key),
|
|
71
82
|
metadata={
|
|
@@ -74,6 +85,8 @@ def prompt_select_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
74
85
|
}
|
|
75
86
|
)
|
|
76
87
|
except (KeyboardInterrupt, EOFError):
|
|
88
|
+
ctx.textual.text("User cancelled issue selection", markup="red")
|
|
89
|
+
ctx.textual.end_step("error")
|
|
77
90
|
return Error("User cancelled issue selection")
|
|
78
91
|
|
|
79
92
|
|
|
@@ -3,7 +3,7 @@ Search JIRA issues using saved query from utils registry
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
|
|
6
|
-
from titan_cli.ui.tui.widgets import
|
|
6
|
+
from titan_cli.ui.tui.widgets import Table
|
|
7
7
|
from ..exceptions import JiraAPIError
|
|
8
8
|
from ..messages import msg
|
|
9
9
|
from ..utils import SAVED_QUERIES, IssueSorter
|
|
@@ -57,14 +57,19 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
57
57
|
if not ctx.textual:
|
|
58
58
|
return Error("Textual UI context is not available for this step.")
|
|
59
59
|
|
|
60
|
+
# Begin step container
|
|
61
|
+
ctx.textual.begin_step("Search Open Issues")
|
|
62
|
+
|
|
60
63
|
if not ctx.jira:
|
|
61
|
-
ctx.textual.
|
|
64
|
+
ctx.textual.text(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT, markup="red")
|
|
65
|
+
ctx.textual.end_step("error")
|
|
62
66
|
return Error(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
|
|
63
67
|
|
|
64
68
|
# Get query name
|
|
65
69
|
query_name = ctx.get("query_name")
|
|
66
70
|
if not query_name:
|
|
67
|
-
ctx.textual.
|
|
71
|
+
ctx.textual.text(msg.Steps.Search.QUERY_NAME_REQUIRED, markup="red")
|
|
72
|
+
ctx.textual.end_step("error")
|
|
68
73
|
return Error(msg.Steps.Search.QUERY_NAME_REQUIRED)
|
|
69
74
|
|
|
70
75
|
# Get all predefined queries from utils
|
|
@@ -102,7 +107,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
102
107
|
error_msg += "\n\n" + msg.Steps.Search.ADD_CUSTOM_HINT + "\n"
|
|
103
108
|
error_msg += msg.Steps.Search.CUSTOM_QUERY_EXAMPLE
|
|
104
109
|
|
|
105
|
-
ctx.textual.
|
|
110
|
+
ctx.textual.text(error_msg, markup="red")
|
|
111
|
+
ctx.textual.end_step("error")
|
|
106
112
|
return Error(error_msg)
|
|
107
113
|
|
|
108
114
|
jql = all_queries[query_name]
|
|
@@ -119,7 +125,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
119
125
|
|
|
120
126
|
if not project:
|
|
121
127
|
error_msg = msg.Steps.Search.PROJECT_REQUIRED.format(query_name=query_name, jql=jql)
|
|
122
|
-
ctx.textual.
|
|
128
|
+
ctx.textual.text(error_msg, markup="red")
|
|
129
|
+
ctx.textual.end_step("error")
|
|
123
130
|
return Error(error_msg)
|
|
124
131
|
|
|
125
132
|
jql = jql.format(project=project)
|
|
@@ -142,12 +149,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
142
149
|
issues = ctx.jira.search_tickets(jql=jql, max_results=max_results)
|
|
143
150
|
|
|
144
151
|
if not issues:
|
|
145
|
-
ctx.textual.
|
|
146
|
-
|
|
147
|
-
text=f"No issues found for query: {query_name}",
|
|
148
|
-
panel_type="info"
|
|
149
|
-
)
|
|
150
|
-
)
|
|
152
|
+
ctx.textual.text(f"No issues found for query: {query_name}", markup="dim")
|
|
153
|
+
ctx.textual.end_step("success")
|
|
151
154
|
return Success(
|
|
152
155
|
"No issues found",
|
|
153
156
|
metadata={
|
|
@@ -158,12 +161,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
158
161
|
)
|
|
159
162
|
|
|
160
163
|
# Show results
|
|
161
|
-
ctx.textual.
|
|
162
|
-
|
|
163
|
-
text=f"Found {len(issues)} issues",
|
|
164
|
-
panel_type="success"
|
|
165
|
-
)
|
|
166
|
-
)
|
|
164
|
+
ctx.textual.text("") # spacing
|
|
165
|
+
ctx.textual.text(f"Found {len(issues)} issues", markup="green")
|
|
167
166
|
ctx.textual.text("")
|
|
168
167
|
|
|
169
168
|
# Show detailed table
|
|
@@ -214,6 +213,7 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
214
213
|
ctx.textual.text(f"{i}. {issue.key} - {getattr(issue, 'summary', 'N/A')}")
|
|
215
214
|
ctx.textual.text("")
|
|
216
215
|
|
|
216
|
+
ctx.textual.end_step("success")
|
|
217
217
|
return Success(
|
|
218
218
|
f"Found {len(issues)} issues using query: {query_name}",
|
|
219
219
|
metadata={
|
|
@@ -225,13 +225,15 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
225
225
|
|
|
226
226
|
except JiraAPIError as e:
|
|
227
227
|
error_msg = f"JIRA search failed: {e}"
|
|
228
|
-
ctx.textual.
|
|
228
|
+
ctx.textual.text(error_msg, markup="red")
|
|
229
|
+
ctx.textual.end_step("error")
|
|
229
230
|
return Error(error_msg)
|
|
230
231
|
except Exception as e:
|
|
231
232
|
import traceback
|
|
232
233
|
error_detail = traceback.format_exc()
|
|
233
234
|
error_msg = f"Unexpected error: {e}\n\nTraceback:\n{error_detail}"
|
|
234
|
-
ctx.textual.
|
|
235
|
+
ctx.textual.text(error_msg, markup="red")
|
|
236
|
+
ctx.textual.end_step("error")
|
|
235
237
|
return Error(error_msg)
|
|
236
238
|
|
|
237
239
|
|
|
File without changes
|
|
File without changes
|