titan-cli 0.1.5__py3-none-any.whl → 0.1.7__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/workflows/project_step_source.py +52 -7
- titan_cli/core/workflows/workflow_filter_service.py +6 -4
- titan_cli/engine/__init__.py +5 -1
- titan_cli/engine/results.py +31 -1
- titan_cli/engine/steps/ai_assistant_step.py +18 -18
- titan_cli/engine/workflow_executor.py +7 -2
- titan_cli/ui/tui/screens/plugin_config_wizard.py +16 -0
- titan_cli/ui/tui/screens/workflow_execution.py +22 -24
- titan_cli/ui/tui/screens/workflows.py +8 -4
- titan_cli/ui/tui/textual_components.py +293 -189
- titan_cli/ui/tui/textual_workflow_executor.py +30 -2
- titan_cli/ui/tui/theme.py +34 -5
- titan_cli/ui/tui/widgets/__init__.py +15 -0
- titan_cli/ui/tui/widgets/multiline_input.py +32 -0
- titan_cli/ui/tui/widgets/prompt_choice.py +138 -0
- titan_cli/ui/tui/widgets/prompt_input.py +74 -0
- titan_cli/ui/tui/widgets/prompt_selection_list.py +150 -0
- titan_cli/ui/tui/widgets/prompt_textarea.py +87 -0
- titan_cli/ui/tui/widgets/styled_option_list.py +107 -0
- titan_cli/ui/tui/widgets/text.py +51 -130
- {titan_cli-0.1.5.dist-info → titan_cli-0.1.7.dist-info}/METADATA +5 -10
- {titan_cli-0.1.5.dist-info → titan_cli-0.1.7.dist-info}/RECORD +54 -41
- {titan_cli-0.1.5.dist-info → titan_cli-0.1.7.dist-info}/WHEEL +1 -1
- titan_plugin_git/clients/git_client.py +59 -2
- titan_plugin_git/plugin.py +10 -0
- titan_plugin_git/steps/ai_commit_message_step.py +8 -8
- titan_plugin_git/steps/branch_steps.py +6 -6
- titan_plugin_git/steps/checkout_step.py +66 -0
- titan_plugin_git/steps/commit_step.py +3 -3
- titan_plugin_git/steps/create_branch_step.py +131 -0
- titan_plugin_git/steps/diff_summary_step.py +11 -13
- titan_plugin_git/steps/pull_step.py +70 -0
- titan_plugin_git/steps/push_step.py +3 -3
- titan_plugin_git/steps/restore_original_branch_step.py +97 -0
- titan_plugin_git/steps/save_current_branch_step.py +82 -0
- titan_plugin_git/steps/status_step.py +23 -13
- titan_plugin_git/workflows/commit-ai.yaml +4 -3
- titan_plugin_github/steps/ai_pr_step.py +90 -22
- titan_plugin_github/steps/create_pr_step.py +8 -8
- titan_plugin_github/steps/github_prompt_steps.py +13 -13
- titan_plugin_github/steps/issue_steps.py +14 -15
- titan_plugin_github/steps/preview_step.py +8 -8
- titan_plugin_github/workflows/create-pr-ai.yaml +1 -11
- titan_plugin_jira/messages.py +12 -0
- titan_plugin_jira/plugin.py +4 -0
- titan_plugin_jira/steps/ai_analyze_issue_step.py +6 -6
- titan_plugin_jira/steps/get_issue_step.py +7 -7
- titan_plugin_jira/steps/list_versions_step.py +133 -0
- titan_plugin_jira/steps/prompt_select_issue_step.py +8 -9
- titan_plugin_jira/steps/search_jql_step.py +191 -0
- titan_plugin_jira/steps/search_saved_query_step.py +13 -13
- titan_plugin_jira/utils/__init__.py +1 -1
- {titan_cli-0.1.5.dist-info/licenses → titan_cli-0.1.7.dist-info}/LICENSE +0 -0
- {titan_cli-0.1.5.dist-info → titan_cli-0.1.7.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
List available versions (fixVersions) for a JIRA project
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
|
|
6
|
+
from ..exceptions import JiraAPIError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def list_versions_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
10
|
+
"""
|
|
11
|
+
List unreleased versions for a JIRA project.
|
|
12
|
+
|
|
13
|
+
Filters and returns only versions that are not yet released,
|
|
14
|
+
sorted by name in descending order (most recent first).
|
|
15
|
+
|
|
16
|
+
Inputs (from ctx.data):
|
|
17
|
+
project_key (str, optional): Project key. If not provided, uses default_project from JIRA plugin config.
|
|
18
|
+
|
|
19
|
+
Outputs (saved to ctx.data):
|
|
20
|
+
versions (list): List of unreleased version names
|
|
21
|
+
versions_full (list): List of full unreleased version objects
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Success: Unreleased versions listed
|
|
25
|
+
Error: Failed to fetch versions or project_key not configured
|
|
26
|
+
|
|
27
|
+
Example usage in workflow:
|
|
28
|
+
```yaml
|
|
29
|
+
- id: list_versions
|
|
30
|
+
plugin: jira
|
|
31
|
+
step: list_versions
|
|
32
|
+
params:
|
|
33
|
+
project_key: "MYPROJECT"
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
if not ctx.textual:
|
|
37
|
+
return Error("Textual UI context is not available for this step.")
|
|
38
|
+
|
|
39
|
+
if not ctx.jira:
|
|
40
|
+
return Error("JIRA client not available in context")
|
|
41
|
+
|
|
42
|
+
# Begin step container
|
|
43
|
+
ctx.textual.begin_step("List Project Versions")
|
|
44
|
+
|
|
45
|
+
# Get project key from context or fall back to default_project from JIRA client
|
|
46
|
+
project_key = ctx.get("project_key")
|
|
47
|
+
if not project_key:
|
|
48
|
+
# Try to use default project from JIRA client config
|
|
49
|
+
if hasattr(ctx.jira, 'project_key') and ctx.jira.project_key:
|
|
50
|
+
project_key = ctx.jira.project_key
|
|
51
|
+
ctx.textual.dim_text(f"Using default project from JIRA config: {project_key}")
|
|
52
|
+
else:
|
|
53
|
+
ctx.textual.error_text("project_key is required but not provided")
|
|
54
|
+
ctx.textual.dim_text("Set project_key in workflow params or configure default_project in JIRA plugin")
|
|
55
|
+
ctx.textual.end_step("error")
|
|
56
|
+
return Error("project_key is required. Provide it in workflow params or configure default_project in JIRA plugin.")
|
|
57
|
+
|
|
58
|
+
# Show fetching message
|
|
59
|
+
ctx.textual.dim_text(f"Fetching versions for project: {project_key}")
|
|
60
|
+
ctx.textual.text("")
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Get project details which includes versions
|
|
64
|
+
project = ctx.jira.get_project(project_key)
|
|
65
|
+
versions = project.get("versions", [])
|
|
66
|
+
|
|
67
|
+
if not versions:
|
|
68
|
+
ctx.textual.panel(
|
|
69
|
+
f"No versions found for project {project_key}", panel_type="info"
|
|
70
|
+
)
|
|
71
|
+
ctx.textual.end_step("skip")
|
|
72
|
+
return Success(
|
|
73
|
+
"No versions found", metadata={"versions": [], "versions_full": []}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Filter only unreleased versions for release notes workflow
|
|
77
|
+
unreleased_versions = [v for v in versions if not v.get("released", False)]
|
|
78
|
+
|
|
79
|
+
# Sort unreleased by name descending (most recent first)
|
|
80
|
+
unreleased_versions.sort(key=lambda v: v.get("name", ""), reverse=True)
|
|
81
|
+
|
|
82
|
+
# Use only unreleased versions
|
|
83
|
+
sorted_versions = unreleased_versions
|
|
84
|
+
|
|
85
|
+
# Extract version names
|
|
86
|
+
version_names = [v.get("name", "") for v in sorted_versions]
|
|
87
|
+
|
|
88
|
+
# Show success panel
|
|
89
|
+
ctx.textual.text("")
|
|
90
|
+
ctx.textual.panel(
|
|
91
|
+
f"Found {len(sorted_versions)} unreleased versions",
|
|
92
|
+
panel_type="success",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Show versions list
|
|
96
|
+
ctx.textual.text("")
|
|
97
|
+
ctx.textual.bold_primary_text("Unreleased Versions:")
|
|
98
|
+
ctx.textual.text("")
|
|
99
|
+
|
|
100
|
+
for v in sorted_versions[:20]: # Show first 20
|
|
101
|
+
name = v.get("name", "")
|
|
102
|
+
description = v.get("description", "")
|
|
103
|
+
desc_text = f" - {description[:50]}" if description else ""
|
|
104
|
+
ctx.textual.primary_text(f" • {name}{desc_text}")
|
|
105
|
+
|
|
106
|
+
if len(sorted_versions) > 20:
|
|
107
|
+
ctx.textual.dim_text(
|
|
108
|
+
f" ... and {len(sorted_versions) - 20} more"
|
|
109
|
+
)
|
|
110
|
+
ctx.textual.text("")
|
|
111
|
+
|
|
112
|
+
ctx.textual.end_step("success")
|
|
113
|
+
return Success(
|
|
114
|
+
f"Found {len(sorted_versions)} unreleased versions",
|
|
115
|
+
metadata={"versions": version_names, "versions_full": sorted_versions},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
except JiraAPIError as e:
|
|
119
|
+
error_msg = f"Failed to fetch versions: {e}"
|
|
120
|
+
ctx.textual.panel(error_msg, panel_type="error")
|
|
121
|
+
ctx.textual.end_step("error")
|
|
122
|
+
return Error(error_msg)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
import traceback
|
|
125
|
+
|
|
126
|
+
error_detail = traceback.format_exc()
|
|
127
|
+
error_msg = f"Unexpected error: {e}\n\nTraceback:\n{error_detail}"
|
|
128
|
+
ctx.textual.panel(error_msg, panel_type="error")
|
|
129
|
+
ctx.textual.end_step("error")
|
|
130
|
+
return Error(error_msg)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
__all__ = ["list_versions_step"]
|
|
@@ -26,12 +26,12 @@ def prompt_select_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
26
26
|
# Get issues from previous search
|
|
27
27
|
issues = ctx.get("jira_issues")
|
|
28
28
|
if not issues:
|
|
29
|
-
ctx.textual.
|
|
29
|
+
ctx.textual.error_text(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE)
|
|
30
30
|
ctx.textual.end_step("error")
|
|
31
31
|
return Error(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE)
|
|
32
32
|
|
|
33
33
|
if len(issues) == 0:
|
|
34
|
-
ctx.textual.
|
|
34
|
+
ctx.textual.error_text(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE)
|
|
35
35
|
ctx.textual.end_step("error")
|
|
36
36
|
return Error(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE)
|
|
37
37
|
|
|
@@ -46,7 +46,7 @@ def prompt_select_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
46
46
|
)
|
|
47
47
|
|
|
48
48
|
if not response or not response.strip():
|
|
49
|
-
ctx.textual.
|
|
49
|
+
ctx.textual.error_text(msg.Steps.PromptSelectIssue.NO_ISSUE_SELECTED)
|
|
50
50
|
ctx.textual.end_step("error")
|
|
51
51
|
return Error(msg.Steps.PromptSelectIssue.NO_ISSUE_SELECTED)
|
|
52
52
|
|
|
@@ -54,13 +54,13 @@ def prompt_select_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
54
54
|
try:
|
|
55
55
|
selected_index = int(response.strip())
|
|
56
56
|
except ValueError:
|
|
57
|
-
ctx.textual.
|
|
57
|
+
ctx.textual.error_text(f"Invalid input: '{response}' is not a number")
|
|
58
58
|
ctx.textual.end_step("error")
|
|
59
59
|
return Error(f"Invalid input: '{response}' is not a number")
|
|
60
60
|
|
|
61
61
|
# Validate it's in range
|
|
62
62
|
if selected_index < 1 or selected_index > len(issues):
|
|
63
|
-
ctx.textual.
|
|
63
|
+
ctx.textual.error_text(f"Invalid selection: must be between 1 and {len(issues)}")
|
|
64
64
|
ctx.textual.end_step("error")
|
|
65
65
|
return Error(f"Invalid selection: must be between 1 and {len(issues)}")
|
|
66
66
|
|
|
@@ -68,12 +68,11 @@ def prompt_select_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
68
68
|
selected_issue = issues[selected_index - 1]
|
|
69
69
|
|
|
70
70
|
ctx.textual.text("")
|
|
71
|
-
ctx.textual.
|
|
71
|
+
ctx.textual.success_text(
|
|
72
72
|
msg.Steps.PromptSelectIssue.ISSUE_SELECTION_CONFIRM.format(
|
|
73
73
|
key=selected_issue.key,
|
|
74
74
|
summary=selected_issue.summary
|
|
75
|
-
)
|
|
76
|
-
markup="green"
|
|
75
|
+
)
|
|
77
76
|
)
|
|
78
77
|
|
|
79
78
|
ctx.textual.end_step("success")
|
|
@@ -85,7 +84,7 @@ def prompt_select_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
85
84
|
}
|
|
86
85
|
)
|
|
87
86
|
except (KeyboardInterrupt, EOFError):
|
|
88
|
-
ctx.textual.
|
|
87
|
+
ctx.textual.error_text("User cancelled issue selection")
|
|
89
88
|
ctx.textual.end_step("error")
|
|
90
89
|
return Error("User cancelled issue selection")
|
|
91
90
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Search JIRA issues using custom JQL query
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
|
|
6
|
+
from titan_cli.ui.tui.widgets import Table
|
|
7
|
+
from ..exceptions import JiraAPIError
|
|
8
|
+
from ..messages import msg
|
|
9
|
+
from ..utils import IssueSorter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def search_jql_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
13
|
+
"""
|
|
14
|
+
Search JIRA issues using a custom JQL query.
|
|
15
|
+
|
|
16
|
+
This is a generic search step that accepts raw JQL and allows variable substitution.
|
|
17
|
+
Use this when you need a specific query that isn't covered by saved queries.
|
|
18
|
+
|
|
19
|
+
Inputs (from ctx.data):
|
|
20
|
+
jql (str): JQL query string (supports variable substitution with ${var_name})
|
|
21
|
+
max_results (int, optional): Maximum number of results (default: 100)
|
|
22
|
+
|
|
23
|
+
Outputs (saved to ctx.data):
|
|
24
|
+
jira_issues (list): List of JiraTicket objects
|
|
25
|
+
jira_issue_count (int): Number of issues found
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Success: Issues found
|
|
29
|
+
Error: Search failed or JQL not provided
|
|
30
|
+
|
|
31
|
+
Variable substitution:
|
|
32
|
+
You can use ${variable_name} in the JQL and it will be replaced with values from ctx.data.
|
|
33
|
+
Example: "project = ${project_key} AND fixVersion = ${fix_version}"
|
|
34
|
+
|
|
35
|
+
Example usage in workflow:
|
|
36
|
+
```yaml
|
|
37
|
+
- id: search_issues
|
|
38
|
+
plugin: jira
|
|
39
|
+
step: search_jql
|
|
40
|
+
params:
|
|
41
|
+
jql: "project = MYPROJECT AND status = 'In Progress' ORDER BY created DESC"
|
|
42
|
+
max_results: 50
|
|
43
|
+
|
|
44
|
+
# With variable substitution:
|
|
45
|
+
- id: search_release
|
|
46
|
+
plugin: jira
|
|
47
|
+
step: search_jql
|
|
48
|
+
params:
|
|
49
|
+
jql: "project = ${project_key} AND fixVersion = ${fix_version} ORDER BY created DESC"
|
|
50
|
+
```
|
|
51
|
+
"""
|
|
52
|
+
if not ctx.textual:
|
|
53
|
+
return Error("Textual UI context is not available for this step.")
|
|
54
|
+
|
|
55
|
+
# Begin step container
|
|
56
|
+
ctx.textual.begin_step("Search JIRA Issues")
|
|
57
|
+
|
|
58
|
+
if not ctx.jira:
|
|
59
|
+
ctx.textual.error_text(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
|
|
60
|
+
ctx.textual.end_step("error")
|
|
61
|
+
return Error(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
|
|
62
|
+
|
|
63
|
+
# Get JQL query
|
|
64
|
+
jql = ctx.get("jql")
|
|
65
|
+
if not jql:
|
|
66
|
+
error_msg = "JQL query is required but not provided"
|
|
67
|
+
ctx.textual.error_text(error_msg)
|
|
68
|
+
ctx.textual.dim_text("Provide 'jql' parameter in workflow step")
|
|
69
|
+
ctx.textual.end_step("error")
|
|
70
|
+
return Error(error_msg)
|
|
71
|
+
|
|
72
|
+
# Perform variable substitution: replace ${var} with ctx.data values
|
|
73
|
+
# This allows dynamic queries like "project = ${project_key}"
|
|
74
|
+
import re
|
|
75
|
+
|
|
76
|
+
def replace_var(match):
|
|
77
|
+
var_name = match.group(1)
|
|
78
|
+
value = ctx.get(var_name)
|
|
79
|
+
if value is None:
|
|
80
|
+
# Variable not found in context, keep original placeholder
|
|
81
|
+
return match.group(0)
|
|
82
|
+
return str(value)
|
|
83
|
+
|
|
84
|
+
# Replace ${variable} patterns
|
|
85
|
+
jql = re.sub(r'\$\{([^}]+)\}', replace_var, jql)
|
|
86
|
+
|
|
87
|
+
# Show which query is being executed
|
|
88
|
+
ctx.textual.text("")
|
|
89
|
+
ctx.textual.bold_text("Executing JQL Query:")
|
|
90
|
+
ctx.textual.dim_text(f" {jql}")
|
|
91
|
+
ctx.textual.text("")
|
|
92
|
+
|
|
93
|
+
# Get max results
|
|
94
|
+
max_results = ctx.get("max_results", 100)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
# Execute search with loading indicator
|
|
98
|
+
# Request ALL fields including custom fields
|
|
99
|
+
with ctx.textual.loading("Searching JIRA issues..."):
|
|
100
|
+
issues = ctx.jira.search_tickets(jql=jql, max_results=max_results, fields=["*all"])
|
|
101
|
+
|
|
102
|
+
if not issues:
|
|
103
|
+
ctx.textual.dim_text("No issues found")
|
|
104
|
+
ctx.textual.end_step("success")
|
|
105
|
+
return Success(
|
|
106
|
+
"No issues found",
|
|
107
|
+
metadata={
|
|
108
|
+
"jira_issues": [],
|
|
109
|
+
"issues": [], # Alias for compatibility
|
|
110
|
+
"jira_issue_count": 0
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Show results
|
|
115
|
+
ctx.textual.text("") # spacing
|
|
116
|
+
ctx.textual.success_text(f"Found {len(issues)} issues")
|
|
117
|
+
ctx.textual.text("")
|
|
118
|
+
|
|
119
|
+
# Show detailed table
|
|
120
|
+
ctx.textual.bold_text("Found Issues:")
|
|
121
|
+
ctx.textual.text("")
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
# Sort issues intelligently
|
|
125
|
+
sorter = IssueSorter()
|
|
126
|
+
sorted_issues = sorter.sort(issues)
|
|
127
|
+
|
|
128
|
+
# Prepare table data with row numbers for selection
|
|
129
|
+
headers = ["#", "Key", "Status", "Summary", "Assignee", "Type", "Priority"]
|
|
130
|
+
rows = []
|
|
131
|
+
for i, issue in enumerate(sorted_issues, 1):
|
|
132
|
+
assignee = issue.assignee or "Unassigned"
|
|
133
|
+
status = issue.status or "Unknown"
|
|
134
|
+
priority = issue.priority or "Unknown"
|
|
135
|
+
issue_type = issue.issue_type or "Unknown"
|
|
136
|
+
summary = (issue.summary or "No summary")[:60]
|
|
137
|
+
|
|
138
|
+
rows.append([
|
|
139
|
+
str(i),
|
|
140
|
+
issue.key,
|
|
141
|
+
status,
|
|
142
|
+
summary,
|
|
143
|
+
assignee,
|
|
144
|
+
issue_type,
|
|
145
|
+
priority
|
|
146
|
+
])
|
|
147
|
+
|
|
148
|
+
# Render table using textual widget
|
|
149
|
+
ctx.textual.mount(
|
|
150
|
+
Table(
|
|
151
|
+
headers=headers,
|
|
152
|
+
rows=rows,
|
|
153
|
+
title=f"Issues (sorted by {sorter.get_sort_description()})"
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Use sorted issues for downstream steps
|
|
158
|
+
issues = sorted_issues
|
|
159
|
+
except Exception as e:
|
|
160
|
+
# If table rendering fails, show error but continue with raw issue list
|
|
161
|
+
ctx.textual.error_text(f"Error rendering table: {e}")
|
|
162
|
+
ctx.textual.primary_text(f"Found {len(issues)} issues (showing raw data)")
|
|
163
|
+
for i, issue in enumerate(issues, 1):
|
|
164
|
+
ctx.textual.text(f"{i}. {issue.key} - {getattr(issue, 'summary', 'N/A')}")
|
|
165
|
+
ctx.textual.text("")
|
|
166
|
+
|
|
167
|
+
ctx.textual.end_step("success")
|
|
168
|
+
return Success(
|
|
169
|
+
f"Found {len(issues)} issues",
|
|
170
|
+
metadata={
|
|
171
|
+
"jira_issues": issues,
|
|
172
|
+
"issues": issues, # Alias for compatibility
|
|
173
|
+
"jira_issue_count": len(issues)
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
except JiraAPIError as e:
|
|
178
|
+
error_msg = f"JIRA search failed: {e}"
|
|
179
|
+
ctx.textual.error_text(error_msg)
|
|
180
|
+
ctx.textual.end_step("error")
|
|
181
|
+
return Error(error_msg)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
import traceback
|
|
184
|
+
error_detail = traceback.format_exc()
|
|
185
|
+
error_msg = f"Unexpected error: {e}\n\nTraceback:\n{error_detail}"
|
|
186
|
+
ctx.textual.error_text(error_msg)
|
|
187
|
+
ctx.textual.end_step("error")
|
|
188
|
+
return Error(error_msg)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
__all__ = ["search_jql_step"]
|
|
@@ -61,14 +61,14 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
61
61
|
ctx.textual.begin_step("Search Open Issues")
|
|
62
62
|
|
|
63
63
|
if not ctx.jira:
|
|
64
|
-
ctx.textual.
|
|
64
|
+
ctx.textual.error_text(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
|
|
65
65
|
ctx.textual.end_step("error")
|
|
66
66
|
return Error(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
|
|
67
67
|
|
|
68
68
|
# Get query name
|
|
69
69
|
query_name = ctx.get("query_name")
|
|
70
70
|
if not query_name:
|
|
71
|
-
ctx.textual.
|
|
71
|
+
ctx.textual.error_text(msg.Steps.Search.QUERY_NAME_REQUIRED)
|
|
72
72
|
ctx.textual.end_step("error")
|
|
73
73
|
return Error(msg.Steps.Search.QUERY_NAME_REQUIRED)
|
|
74
74
|
|
|
@@ -107,7 +107,7 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
107
107
|
error_msg += "\n\n" + msg.Steps.Search.ADD_CUSTOM_HINT + "\n"
|
|
108
108
|
error_msg += msg.Steps.Search.CUSTOM_QUERY_EXAMPLE
|
|
109
109
|
|
|
110
|
-
ctx.textual.
|
|
110
|
+
ctx.textual.error_text(error_msg)
|
|
111
111
|
ctx.textual.end_step("error")
|
|
112
112
|
return Error(error_msg)
|
|
113
113
|
|
|
@@ -125,7 +125,7 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
125
125
|
|
|
126
126
|
if not project:
|
|
127
127
|
error_msg = msg.Steps.Search.PROJECT_REQUIRED.format(query_name=query_name, jql=jql)
|
|
128
|
-
ctx.textual.
|
|
128
|
+
ctx.textual.error_text(error_msg)
|
|
129
129
|
ctx.textual.end_step("error")
|
|
130
130
|
return Error(error_msg)
|
|
131
131
|
|
|
@@ -136,8 +136,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
136
136
|
source_label = "Custom" if is_custom else "Predefined"
|
|
137
137
|
|
|
138
138
|
ctx.textual.text("")
|
|
139
|
-
ctx.textual.
|
|
140
|
-
ctx.textual.
|
|
139
|
+
ctx.textual.bold_text(f"Using {source_label} Query: {query_name}")
|
|
140
|
+
ctx.textual.dim_text(f" JQL: {jql}")
|
|
141
141
|
ctx.textual.text("")
|
|
142
142
|
|
|
143
143
|
# Get max results
|
|
@@ -149,7 +149,7 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
149
149
|
issues = ctx.jira.search_tickets(jql=jql, max_results=max_results)
|
|
150
150
|
|
|
151
151
|
if not issues:
|
|
152
|
-
ctx.textual.
|
|
152
|
+
ctx.textual.dim_text(f"No issues found for query: {query_name}")
|
|
153
153
|
ctx.textual.end_step("success")
|
|
154
154
|
return Success(
|
|
155
155
|
"No issues found",
|
|
@@ -162,11 +162,11 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
162
162
|
|
|
163
163
|
# Show results
|
|
164
164
|
ctx.textual.text("") # spacing
|
|
165
|
-
ctx.textual.
|
|
165
|
+
ctx.textual.success_text(f"Found {len(issues)} issues")
|
|
166
166
|
ctx.textual.text("")
|
|
167
167
|
|
|
168
168
|
# Show detailed table
|
|
169
|
-
ctx.textual.
|
|
169
|
+
ctx.textual.bold_text("Found Issues:")
|
|
170
170
|
ctx.textual.text("")
|
|
171
171
|
|
|
172
172
|
try:
|
|
@@ -207,8 +207,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
207
207
|
issues = sorted_issues
|
|
208
208
|
except Exception as e:
|
|
209
209
|
# If table rendering fails, show error but continue with raw issue list
|
|
210
|
-
ctx.textual.
|
|
211
|
-
ctx.textual.
|
|
210
|
+
ctx.textual.error_text(f"Error rendering table: {e}")
|
|
211
|
+
ctx.textual.primary_text(f"Found {len(issues)} issues (showing raw data)")
|
|
212
212
|
for i, issue in enumerate(issues, 1):
|
|
213
213
|
ctx.textual.text(f"{i}. {issue.key} - {getattr(issue, 'summary', 'N/A')}")
|
|
214
214
|
ctx.textual.text("")
|
|
@@ -225,14 +225,14 @@ 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.error_text(error_msg)
|
|
229
229
|
ctx.textual.end_step("error")
|
|
230
230
|
return Error(error_msg)
|
|
231
231
|
except Exception as e:
|
|
232
232
|
import traceback
|
|
233
233
|
error_detail = traceback.format_exc()
|
|
234
234
|
error_msg = f"Unexpected error: {e}\n\nTraceback:\n{error_detail}"
|
|
235
|
-
ctx.textual.
|
|
235
|
+
ctx.textual.error_text(error_msg)
|
|
236
236
|
ctx.textual.end_step("error")
|
|
237
237
|
return Error(error_msg)
|
|
238
238
|
|
|
File without changes
|
|
File without changes
|