Files
gh-dapi-claude-code-marketp…/skills/bugsnag/bugsnag_api_client.rb
2025-11-29 18:17:37 +08:00

370 lines
13 KiB
Ruby

#!/usr/bin/env ruby
require 'bugsnag/api'
class BugsnagApiClient
def initialize
@api_key = ENV.fetch('BUGSNAG_DATA_API_KEY')
@project_id = ENV['BUGSNAG_PROJECT_ID'] # Optional - needed only for error-specific commands
validate_api_key
configure_api
end
def list_errors(limit: 20, status: nil, severity: nil)
errors_data = fetch_errors(limit: limit, status: status, severity: severity)
format_errors_list(errors_data)
rescue Bugsnag::Api::Error => e
handle_api_error(e, "получении списка ошибок")
end
def get_error_details(error_id)
require_project_id!
response = Bugsnag::Api.client.error(@project_id, error_id)
format_error_details(response)
rescue Bugsnag::Api::Error => e
handle_api_error(e, "получении деталей ошибки")
end
def resolve_error(error_id)
require_project_id!
# Try to resolve via API first
begin
Bugsnag::Api.client.update_errors(@project_id, [error_id], "resolve")
"✅ Ошибка `#{error_id}` успешно отмечена как выполненная!"
rescue Bugsnag::Api::Error => e
# Fallback to adding a resolution comment
begin
comment_text = "🔧 **MARKED AS RESOLVED** - Эта ошибка была помечена как выполненная через Bugsnag skill."
Bugsnag::Api.client.create_comment(@project_id, error_id, comment_text)
"✅ Ошибка `#{error_id}` помечена как выполненная через комментарий. Пожалуйста, закройте ошибку вручную в Bugsnag dashboard."
rescue Bugsnag::Api::Error => comment_error
handle_api_error(comment_error, "пометки ошибки как выполненной")
end
end
end
def add_comment(error_id, message)
require_project_id!
Bugsnag::Api.client.create_comment(@project_id, error_id, message)
"✅ Комментарий успешно добавлен к ошибке `#{error_id}`"
rescue Bugsnag::Api::Error => e
handle_api_error(e, "добавлении комментария")
end
def get_error_events(error_id, limit: 10)
require_project_id!
options = {}
options[:limit] = limit if limit
response = Bugsnag::Api.client.error_events(@project_id, error_id, options)
format_events_list(response)
rescue Bugsnag::Api::Error => e
handle_api_error(e, "получении событий ошибки")
end
def analyze_errors
errors_data = fetch_errors(limit: 50)
errors = normalize_errors_array(errors_data)
analyze_error_patterns(errors)
end
def list_organizations
response = Bugsnag::Api.client.organizations
format_organizations_list(response)
rescue Bugsnag::Api::Error => e
handle_api_error(e, "получении списка организаций")
end
def list_projects
# Get all organizations first
orgs = Bugsnag::Api.client.organizations
all_projects = []
# Get projects for each organization
orgs.each do |org|
org_projects = Bugsnag::Api.client.projects(org['id'])
all_projects.concat(org_projects)
rescue Bugsnag::Api::Error
# Skip this org if error, continue with others
next
end
format_projects_list(all_projects)
rescue Bugsnag::Api::Error => e
handle_api_error(e, "получении списка проектов")
end
def list_comments(error_id)
require_project_id!
response = Bugsnag::Api.client.comments(@project_id, error_id)
format_comments_list(response)
rescue Bugsnag::Api::Error => e
handle_api_error(e, "получении комментариев")
end
private
def fetch_errors(limit: 20, status: nil, severity: nil)
require_project_id!
options = {}
options[:limit] = limit if limit
options[:status] = status if status
options[:severity] = severity if severity
Bugsnag::Api.client.errors(@project_id, nil, options)
end
def normalize_errors_array(errors_data)
errors_data.is_a?(Array) ? errors_data : errors_data['errors'] || []
end
def validate_api_key
unless @api_key
raise "Missing required environment variable: BUGSNAG_DATA_API_KEY"
end
end
def require_project_id!
unless @project_id
raise "Missing required environment variable: BUGSNAG_PROJECT_ID\n\n" \
"This command requires a project ID. Set it with:\n" \
"export BUGSNAG_PROJECT_ID='your_project_id'\n\n" \
"Use 'projects' command to list available project IDs."
end
end
def configure_api
Bugsnag::Api.configure do |config|
config.auth_token = @api_key
end
end
def handle_api_error(error, operation)
case error
when Bugsnag::Api::ClientError
"❌ Ошибка при #{operation}: ошибка клиента API - #{error.message}"
when Bugsnag::Api::ServerError
"❌ Ошибка при #{operation}: ошибка сервера Bugsnag - #{error.message}"
when Bugsnag::Api::InternalServerError
"❌ Ошибка при #{operation}: внутренняя ошибка сервера Bugsnag - #{error.message}"
else
"❌ Ошибка при #{operation}: #{error.message}"
end
end
def format_errors_list(errors_data)
errors = errors_data.is_a?(Array) ? errors_data : errors_data['errors'] || []
output = ["📋 Найдено ошибок: #{errors.length}\n"]
errors.each do |error|
status_emoji = case error['status']
when 'open' then '❌'
when 'resolved' then '✅'
when 'ignored' then '🚫'
else '❓'
end
output << "#{status_emoji} **#{error['error_class']}** (#{error['events']} событий)"
output << " ID: `#{error['id']}`"
output << " Severity: #{error['severity']}"
output << " Первое появление: #{error['first_seen']}"
output << " Последнее: #{error['last_seen']}"
output << " URL: #{error['url']}" if error['url']
output << ""
end
output.join("\n")
end
def format_error_details(error_data)
error = error_data['error'] || error_data
output = []
output << "🔍 **Детали ошибки:** #{error['error_class']}"
output << ""
output << "**Основная информация:**"
output << "• ID: `#{error['id']}`"
output << "• Статус: #{error['status']}"
output << "• Критичность: #{error['severity']}"
output << "• Событий: #{error['events']}"
output << "• Пользователи затронуто: #{error['users']}"
output << ""
if error['first_seen'] && error['last_seen']
output << "**Временные рамки:**"
output << "• Первое появление: #{error['first_seen']}"
output << "• Последнее: #{error['last_seen']}"
output << ""
end
output << "**Контекст:**"
output << "• App Version: #{error.dig('app', 'version') || 'N/A'}"
output << "• Release Stage: #{error.dig('app', 'releaseStage') || 'N/A'}"
output << "• Language: #{error['language'] || 'N/A'}"
output << "• Framework: #{error['framework'] || 'N/A'}"
output << ""
if error['url']
output << "**URL:** #{error['url']}"
output << ""
end
if error['message']
output << "**Сообщение:**"
output << "```"
output << error['message']
output << "```"
output << ""
end
output
end
def format_events_list(events_data)
events = events_data['events'] || []
output = ["📊 События ошибки (#{events.length}):\n"]
events.each_with_index do |event, index|
output << "**Событие #{index + 1}:**"
output << "• ID: `#{event['id']}`"
output << "• Время: #{event['receivedAt']}"
output << "• App Version: #{event['app']['releaseStage'] || 'N/A'}"
output << "• OS: #{event['device']['osName'] || 'N/A'} #{event['device']['osVersion'] || ''}"
if event['user']
output << "• Пользователь: #{event['user']['name'] || event['user']['id'] || 'N/A'}"
end
if event['message']
output << "• Сообщение: #{event['message']}"
end
output << ""
end
output.join("\n")
end
def analyze_error_patterns(errors)
critical_errors = errors.select { |e| e['severity'] == 'error' && e['status'] == 'open' }
warnings = errors.select { |e| e['severity'] == 'warning' && e['status'] == 'open' }
output = ["📈 **Анализ ошибок в проекте:**\n"]
output << "🔴 **Критичные ошибки (#{critical_errors.length}):**"
if critical_errors.any?
critical_errors.first(5).each do |error|
events_count = error['events'] || error['events_count'] || 0
output << "#{error['error_class']} - #{events_count} событий (ID: #{error['id']})"
end
else
output << "• Нет критичных ошибок!"
end
output << ""
output << "🟡 **Предупреждения (#{warnings.length}):**"
if warnings.any?
warnings.first(5).each do |error|
events_count = error['events'] || error['events_count'] || 0
output << "#{error['error_class']} - #{events_count} событий (ID: #{error['id']})"
end
else
output << "• Нет предупреждений!"
end
output << ""
# Частые паттерны ошибок
error_classes = errors.group_by { |e| e['error_class'] }
frequent_errors = error_classes.select { |klass, errs| errs.length > 1 }
if frequent_errors.any?
output << "🔄 **Повторяющиеся паттерны:**"
frequent_errors.each do |error_class, errors|
total_events = errors.sum { |e| e['events'] || e['events_count'] || 0 }
output << "#{error_class}: #{errors.length} экземпляров, #{total_events} событий"
end
end
output.join("\n")
end
def format_organizations_list(orgs_data)
orgs = orgs_data.is_a?(Array) ? orgs_data : orgs_data['organizations'] || []
output = ["🏢 Доступные организации: #{orgs.length}\n"]
orgs.each_with_index do |org, index|
output << "#{index + 1}. **#{org['name']}** (ID: `#{org['id']}`)"
output << " Создана: #{org['created_at']}" if org['created_at']
output << " Коллабораторов: #{org['collaborators_count']}" if org['collaborators_count']
output << " Проектов: #{org['projects_count']}" if org['projects_count']
output << " URL: #{org['url']}" if org['url']
output << ""
end
output.join("\n")
end
def format_projects_list(projects_data)
projects = projects_data.is_a?(Array) ? projects_data : projects_data['projects'] || []
output = ["📦 Доступные проекты: #{projects.length}\n"]
projects.each_with_index do |project, index|
output << "#{index + 1}. **#{project['name']}** (ID: `#{project['id']}`)"
output << " Тип: #{project['type']}" if project['type']
output << " Открытых ошибок: #{project['open_error_count']}" if project['open_error_count']
output << " Коллабораторов: #{project['collaborators_count']}" if project['collaborators_count']
if project['release_stages'] && project['release_stages'].any?
output << " Стадии: #{project['release_stages'].join(', ')}"
end
output << " URL: #{project['url']}" if project['url']
output << ""
end
output.join("\n")
end
def format_comments_list(comments_data)
comments = comments_data.is_a?(Array) ? comments_data : comments_data['comments'] || []
output = ["💬 Комментарии (#{comments.length}):\n"]
if comments.empty?
output << "Нет комментариев для этой ошибки."
return output.join("\n")
end
comments.each_with_index do |comment, index|
output << "**Комментарий #{index + 1}:**"
output << "• ID: `#{comment['id']}`"
# Author info
author = if comment['user'] && comment['user']['name']
comment['user']['name']
elsif comment['user'] && comment['user']['email']
comment['user']['email']
else
'Unknown'
end
output << "• Автор: #{author}"
output << "• Время: #{comment['created_at']}" if comment['created_at']
output << "• Текст: #{comment['message']}" if comment['message']
output << ""
end
output.join("\n")
end
end