Class: N2B::JiraClient

Inherits:
Object
  • Object
show all
Defined in:
lib/n2b/jira_client.rb

Defined Under Namespace

Classes: JiraApiError

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ JiraClient

Returns a new instance of JiraClient.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/n2b/jira_client.rb', line 12

def initialize(config)
  @config = config
  @jira_config = @config['jira'] || {} # Ensure jira key exists

  unless @jira_config['domain'] && @jira_config['email'] && @jira_config['api_key']
    raise ArgumentError, "Jira domain, email, and API key must be configured in N2B settings."
  end
  # Handle domain that may or may not include protocol
  domain = @jira_config['domain'].to_s.strip
  if domain.start_with?('http://') || domain.start_with?('https://')
    # Domain already includes protocol
    @base_url = "#{domain.chomp('/')}/rest/api/3"
  else
    # Domain doesn't include protocol, add https://
    @base_url = "https://#{domain.chomp('/')}/rest/api/3"
  end
end

Instance Method Details

#extract_requirements_from_description(description_string) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/n2b/jira_client.rb', line 140

def extract_requirements_from_description(description_string)
  extracted_lines = []
  in_requirements_section = false

  # Headers that trigger requirement extraction. Case-insensitive.
  # Jira often uses h1, h2, etc. for headers, or bold text.
  # We'll look for lines that *start* with these, possibly after Jira's header markup like "hN. "
  # Or common text like "Acceptance Criteria:", "Requirements:"
  # Also include comment-specific implementation keywords
  requirement_headers_regex = /^(h[1-6]\.\s*)?(Requirements|Acceptance Criteria|Tasks|Key Deliverables|Scope|User Stories|Implementation|Testing|Technical|Additional|Clarification|Comment \d+)/i

  # Regex to identify common list item markers
  _list_item_regex = /^\s*[\*\-\+]\s+/ # Unused but kept for potential future use
  # Regex for lines that look like section headers (to stop capturing)
  # This is a simple heuristic: a line with a few words, ending with a colon, or Jira hN. style
  section_break_regex = /^(h[1-6]\.\s*)?\w+(\s+\w+){0,3}:?\s*$/i


  description_string.to_s.each_line do |line| # Handle nil description_string
    stripped_line = line.strip

    if stripped_line.match?(requirement_headers_regex)
      in_requirements_section = true
      # Add the header itself to the extracted content if desired, or just use it as a trigger
      # For now, let's add the line to give context.
      extracted_lines << stripped_line
      next # Move to the next line
    end

    if in_requirements_section
      # If we encounter another significant header, stop capturing this section
      # (unless it's another requirements header, which is fine)
      if stripped_line.match?(section_break_regex) && !stripped_line.match?(requirement_headers_regex)
        # Check if this new header is one of the requirement types. If so, continue.
        # Otherwise, break. This logic is simplified: if it's any other header, stop.
        is_another_req_header = false # Placeholder for more complex logic if needed
        requirement_headers_regex.match(stripped_line) { is_another_req_header = true }

        unless is_another_req_header
          in_requirements_section = false # Stop capturing
          # Potentially add a separator if concatenating multiple distinct sections later
          # extracted_lines << "---"
          next # Don't include this new non-req header in current section
        else
          # It's another requirement-related header, so add it and continue
          extracted_lines << stripped_line
          next
        end
      end

      # Capture list items or general text within the section
      # For now, we are quite inclusive of lines within a detected section.
      # We could be more strict and only take list_item_regex lines,
      # but often text paragraphs under a heading are relevant too.
      extracted_lines << stripped_line unless stripped_line.empty?
    end
  end

  if extracted_lines.empty?
    # Fallback: return the entire description if no specific sections found
    return description_string.to_s.strip # Handle nil and strip
  else
    # Join extracted lines and clean up excessive newlines
    # Replace 3+ newlines with 2, and 2+ newlines with 2 (effectively max 2 newlines)
    # Also, strip leading/trailing whitespace from the final result.
    return extracted_lines.join("\n").gsub(/\n{3,}/, "\n\n").gsub(/\n{2,}/, "\n\n").strip
  end
end

#fetch_ticket(ticket_key_or_url) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/n2b/jira_client.rb', line 30

def fetch_ticket(ticket_key_or_url)
  domain, ticket_key = parse_ticket_input(ticket_key_or_url)

  unless ticket_key
    raise JiraApiError, "Could not extract ticket key from '#{ticket_key_or_url}'."
  end

  puts "Fetching Jira ticket: #{ticket_key} from domain: #{domain || @jira_config['domain']}"
  puts "Fetching ticket comments for additional context..."

  begin
    # Fetch ticket details
    ticket_path = "/rest/api/3/issue/#{ticket_key}"
    ticket_data = make_api_request('GET', ticket_path)

    # Fetch ticket comments
    comments_path = "/rest/api/3/issue/#{ticket_key}/comment"
    comments_response = make_api_request('GET', comments_path)
    comments_data = comments_response['comments'] || []

    puts "Successfully fetched ticket and #{comments_data.length} comments"

    # Process real data
    return process_ticket_data(ticket_data, comments_data)

  rescue JiraApiError => e
    puts "⚠️  Failed to fetch from Jira API: #{e.message}"
    puts "Falling back to dummy data for development..."
    return fetch_dummy_ticket_data(ticket_key)
  end
end

#test_connectionObject

Add test connection functionality



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/n2b/jira_client.rb', line 113

def test_connection
  puts "🧪 Testing Jira API connection..."

  begin
    # Test 1: Basic authentication
    response = make_api_request('GET', '/myself')
    puts "✅ Authentication successful"
    puts "   Account: #{response['displayName']} (#{response['emailAddress']})"

    # Test 2: Project access
    projects = make_api_request('GET', '/project')
    puts "✅ Can access #{projects.length} projects"

    # Test 3: Comment permissions (try to get comments from any issue)
    if projects.any?
      project_key = projects.first['key']
      puts "✅ Basic permissions verified for project: #{project_key}"
    end

    puts "🎉 Jira connection test successful!"
    true
  rescue => e
    puts "❌ Jira connection test failed: #{e.message}"
    false
  end
end

#update_ticket(ticket_key_or_url, comment) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/n2b/jira_client.rb', line 62

def update_ticket(ticket_key_or_url, comment)
  _domain, ticket_key = parse_ticket_input(ticket_key_or_url) # Use _domain to indicate it's not used here

  unless ticket_key
    raise JiraApiError, "Could not extract ticket key from '#{ticket_key_or_url}' for update."
  end

  puts "Updating Jira ticket #{ticket_key} with analysis comment..."

  # Generate comment using template system
  template_comment = generate_templated_comment(comment)

  if debug_mode?
    puts "🔍 DEBUG: Generated template comment (#{template_comment.length} chars):"
    puts "--- TEMPLATE COMMENT START ---"
    puts template_comment
    puts "--- TEMPLATE COMMENT END ---"
  end

  # Prepare the comment body in Jira's Atlassian Document Format (ADF)
  comment_body = {
    "body" => format_comment_as_adf(template_comment)
  }

  if debug_mode?
    puts "🔍 DEBUG: Formatted ADF comment body:"
    puts "--- ADF BODY START ---"
    puts JSON.pretty_generate(comment_body)
    puts "--- ADF BODY END ---"
  end

  # Make the API call to add a comment
  path = "/rest/api/3/issue/#{ticket_key}/comment"
  puts "🔍 DEBUG: Making API request to: #{path}" if debug_mode?

  _response = make_api_request('POST', path, comment_body)

  puts "✅ Successfully added comment to Jira ticket #{ticket_key}"
  true
rescue JiraApiError => e
  puts "❌ Failed to update Jira ticket #{ticket_key}: #{e.message}"
  if debug_mode?
    puts "🔍 DEBUG: Full error details:"
    puts "  - Ticket key: #{ticket_key}"
    puts "  - Template comment length: #{template_comment&.length || 'nil'}"
    puts "  - Comment body keys: #{comment_body&.keys || 'nil'}"
  end
  false
end