diff --git a/README.md b/README.md index 10ba738..ca29dcd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # ErrbitGitlabPlugin -TODO: Write a gem description +This gem adds Gitlab issue tracker support to Errbit. + +Version 1.0 is compatible with Errbit 0.4. + +For (much) older versions of Errbit, try the 0.x gem versions or consider +updating your Errbit installation ## Installation @@ -18,7 +23,20 @@ Or install it yourself as: ## Usage -TODO: Write usage instructions here +Simply add your Gitlab URL, private token and project name to your errbit app. + +You can find your private token by clicking on "Account" in your Gitlab profile settings. + +Upon saving the app, the gem will automatically test if your entered +credentials are valid and inform you otherwise. + +## Troubleshoot + +> I entered my Gitlab settings and the tracker was saved, but I cannot create issues + +The Gitlab API (at least on gitlab.com) does not seem to accept POST requests when using +a non-encrypted connection. Therefore, please make sure to use `https://` when +using gitlab.com as URL. ## Contributing diff --git a/app/views/errbit_gitlab_plugin/issue.md.erb b/app/views/errbit_gitlab_plugin/issue.md.erb new file mode 100644 index 0000000..3944a94 --- /dev/null +++ b/app/views/errbit_gitlab_plugin/issue.md.erb @@ -0,0 +1,43 @@ +[See this exception on Errbit](<%= app_problem_url problem.app, problem %>) + +<% if notice = problem.notices.first %> +# <%= notice.message.lines.map(&:strip).join(' ') %> + +## Summary ## +<% if notice.request['url'].present? %> +### URL ### +<%= notice.request['url'] %> +<% end %> +### Where ### + <%= notice.where %> + +### Occured ### + <%= notice.created_at.to_s(:precise) %> + +### Similar ### + <%= [notice.problem.notices_count - 1, 1].max %> + +## Params ## +```ruby +<%= pretty_hash(notice.params).html_safe %> +``` + +## Session ## +```ruby +<%= pretty_hash(notice.session).html_safe %> +``` + +## Backtrace ## +``` +<% notice.backtrace.lines.each do |line| %><%= format '%4d:', line.number %> <%= line.file_relative %> -> **<%= line.method %>** +<% end %> +``` + +<% if notice.env_vars.present? %> +## Environment ## +| Key | Value | +|------------|------------| +<% notice.env_vars.each do |key, val| %>| <%= key %> | <%= val %> | +<% end %> +<% end %> +<% end %> diff --git a/errbit_gitlab_plugin.gemspec b/errbit_gitlab_plugin.gemspec index 75bfbeb..fb86fe3 100644 --- a/errbit_gitlab_plugin.gemspec +++ b/errbit_gitlab_plugin.gemspec @@ -18,8 +18,8 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] - spec.add_runtime_dependency 'errbit_plugin', '~> 0.4', '>= 0.4.0' - spec.add_runtime_dependency 'gitlab', '~> 3.0.0', '>= 3.0.0' + spec.add_runtime_dependency 'errbit_plugin', '~> 0.5', '>= 0.5.0' + spec.add_runtime_dependency 'gitlab', '~> 3.4', '>= 3.4.0' spec.add_development_dependency 'bundler', '~> 1.3' spec.add_development_dependency 'rake', '~> 0' diff --git a/lib/errbit_gitlab_plugin.rb b/lib/errbit_gitlab_plugin.rb index dab326e..123fc04 100644 --- a/lib/errbit_gitlab_plugin.rb +++ b/lib/errbit_gitlab_plugin.rb @@ -4,7 +4,11 @@ module ErrbitGitlabPlugin def self.root - File.expand_path '../..', __FILE__ + Pathname.new File.expand_path('../..', __FILE__) + end + + def self.read_static_file(file) + File.read(root.join('static', file)) end end diff --git a/lib/errbit_gitlab_plugin/issue_tracker.rb b/lib/errbit_gitlab_plugin/issue_tracker.rb index 50f1346..d503eb2 100644 --- a/lib/errbit_gitlab_plugin/issue_tracker.rb +++ b/lib/errbit_gitlab_plugin/issue_tracker.rb @@ -4,25 +4,24 @@ module ErrbitGitlabPlugin class IssueTracker < ErrbitPlugin::IssueTracker LABEL = 'gitlab' - NOTE = '' - - FIELDS = [ - [:account, { - :label => "Gitlab URL", - :placeholder => "e.g. https://example.net" - }], - [:api_token, { - :placeholder => "API Token for your account" - }], - [:project_id, { - :label => "Ticket Project ID (use Number)", - :placeholder => "Gitlab Project where issues will be created" - }], - [:alt_project_id, { - :label => "Project Name (namespace/project)", - :placeholder => "Gitlab Project where issues will be created" - }] - ] + NOTE = "Creating issues may take some time as the actual project ID has to be looked up using the Gitlab API.
+ If you are using gitlab.com as installation, please make sure to use 'https://', otherwise, their API + will not accept some of the our commands." + + FIELDS = { + endpoint: { + label: 'Gitlab URL', + placeholder: 'The URL to your gitlab installation or the public gitlab server, e.g. https://www.gitlab.com' + }, + api_token: { + label: 'API Token', + placeholder: "Your account's API token (see Profile -> Account)" + }, + path_with_namespace: { + label: 'Project name', + placeholder: 'E.g. your_username/your_project' + } + } def self.label LABEL @@ -32,66 +31,148 @@ def self.note NOTE end + # + # Form fields that will be presented to the administrator when setting up + # or editing the errbit app. The values we collect will be available for use + # later when we have an instance of this class. + # def self.fields FIELDS end - def self.body_template - @body_template ||= ERB.new(File.read( - File.join( - ErrbitGitlabPlugin.root, 'views', 'gitlab_issues_body.txt.erb' - ) - )) + # + # Icons to be displayed for this issue tracker + # + def self.icons + @icons ||= { + create: ['image/png', ErrbitGitlabPlugin.read_static_file('gitlab_create.png')], + goto: ['image/png', ErrbitGitlabPlugin.read_static_file('gitlab_goto.png')], + inactive: ['image/png', ErrbitGitlabPlugin.read_static_file('gitlab_inactive.png')] + } end - def self.summary_template - @summary_template ||= ERB.new(File.read( - File.join( - ErrbitGitlabPlugin.root, 'views', 'gitlab_issues_summary.txt.erb' - ) - )) + # + # Used to pass an own template to errbit's issue rendering. + # The rendered template is then passed to any #create_issue call. + # + def render_body_args + ['errbit_gitlab_plugin/issue', :formats => [:md]] end + # + # @return [String] the URL to the given project's issues section + # def url - sprintf('%s/%s/issues', params['account'], params['alt_project_id']) + format '%s/%s/issues', options[:endpoint], options[:path_with_namespace] end def configured? - params['project_id'].present? && params['api_token'].present? + self.class.fields.keys.all? { |field_name| options[field_name].present? } end - def comments_allowed?; false; end + def comments_allowed? + true + end + # Called to validate user input. Just return a hash of errors if there are any def errors - errors = [] - if self.class.fields.detect {|f| params[f[0]].blank? } - errors << [:base, 'You must specify your Gitlab URL, API token, Project ID and Project Name'] + errs = [] + + # Make sure that every field is filled out + self.class.fields.except(:project_id).each_with_object({}) do |(field_name, field_options), h| + if options[field_name].blank? + errs << "#{field_options[:label]} must be present" + end + end + + # We can only perform the other tests if the necessary values are at least present + return {:base => errs.to_sentence} unless errs.size.zero? + + # Check if the given endpoint actually exists + unless gitlab_endpoint_exists?(options[:endpoint]) + errs << 'No Gitlab installation was found under the given URL' + return {:base => errs.to_sentence} + end + + # Check if a user by the given token exists + unless gitlab_user_exists?(options[:endpoint], options[:api_token]) + errs << 'No user with the given API token was found' + return {:base => errs.to_sentence} + end + + # Check if there is a project with the given name on the server + unless gitlab_project_id(options[:endpoint], options[:api_token], options[:path_with_namespace]) + errs << "A project named '#{options[:path_with_namespace]}' could not be found on the server. + Please make sure to enter it exactly as it appears in your address bar in Gitlab (case sensitive)" + return {:base => errs.to_sentence} + end + + {} + end + + def create_issue(title, body, reported_by = nil) + ticket = with_gitlab do |g| + g.create_issue(gitlab_project_id, title, description: body, labels: 'errbit') end - errors + + format('%s/%s', url, ticket.id) end - def create_issue(problem, reported_by = nil) - Gitlab.configure do |config| - config.endpoint = sprintf('%s/api/v3', params['account']) - config.private_token = params['api_token'] - config.user_agent = 'Errbit User Agent' + private + + # + # Tries to find a project with the given name in the given Gitlab installation + # and returns its ID (if any) + # + def gitlab_project_id(gitlab_url = options[:endpoint], token = options[:api_token], project = options[:path_with_namespace]) + @project_id ||= with_gitlab(gitlab_url, token) do |g| + g.projects.detect { |p| p.path_with_namespace == project }.try(:id) end + end - title = "[#{ problem.environment }][#{ problem.where }] #{problem.message.to_s.truncate(100)}" - description_summary = self.class.summary_template.result(binding) - description_body = self.class.body_template.result(binding) + # + # @return [String] a formatted APIv3 URL for the given +gitlab_url+ + # + def gitlab_endpoint(gitlab_url) + format '%s/api/v3', gitlab_url + end - ticket = Gitlab.create_issue(params['project_id'], title, { - :description => description_summary, - :labels => "errbit" - }) + # + # Checks whether there is a gitlab installation + # at the given +gitlab_url+ + # + def gitlab_endpoint_exists?(gitlab_url) + with_gitlab(gitlab_url, 'Iamsecret') do |g| + g.user + end + rescue Gitlab::Error::Unauthorized + true + rescue Exception + false + end - Gitlab.create_issue_note(params['project_id'], ticket.id, description_body) + # + # Checks whether a user with the given +token+ exists + # in the gitlab installation located at +gitlab_url+ + # + def gitlab_user_exists?(gitlab_url, private_token) + with_gitlab(gitlab_url, private_token) do |g| + g.user + end + + true + rescue Gitlab::Error::Unauthorized + false + end - problem.update_attributes( - :issue_link => sprintf("%s/%s", url, ticket.id), - :issue_type => self.class.label - ) + # + # Connects to the gitlab installation at +gitlab_url+ + # using the given +private_token+ and executes the given block + # + def with_gitlab(gitlab_url = options[:endpoint], private_token = options[:api_token]) + yield Gitlab.client(endpoint: gitlab_endpoint(gitlab_url), + private_token: private_token, + user_agent: 'Errbit User Agent') end end end diff --git a/lib/errbit_gitlab_plugin/version.rb b/lib/errbit_gitlab_plugin/version.rb index eb04894..4766800 100644 --- a/lib/errbit_gitlab_plugin/version.rb +++ b/lib/errbit_gitlab_plugin/version.rb @@ -1,3 +1,3 @@ module ErrbitGitlabPlugin - VERSION = "0.1.0" + VERSION = '1.0.0' end diff --git a/vendor/assets/images/gitlab_create.png b/static/gitlab_create.png similarity index 100% rename from vendor/assets/images/gitlab_create.png rename to static/gitlab_create.png diff --git a/vendor/assets/images/gitlab_goto.png b/static/gitlab_goto.png similarity index 100% rename from vendor/assets/images/gitlab_goto.png rename to static/gitlab_goto.png diff --git a/vendor/assets/images/gitlab_inactive.png b/static/gitlab_inactive.png similarity index 100% rename from vendor/assets/images/gitlab_inactive.png rename to static/gitlab_inactive.png diff --git a/views/gitlab_issues_body.txt.erb b/views/gitlab_issues_body.txt.erb deleted file mode 100644 index 61d7a4b..0000000 --- a/views/gitlab_issues_body.txt.erb +++ /dev/null @@ -1,29 +0,0 @@ -<% if notice = problem.notices.first %> -## Params ## -``` -<%= pretty_hash(notice.params) %> -``` - -## Session ## -``` -<%= pretty_hash(notice.session) %> -``` - -## Backtrace ## -``` -<% notice.backtrace_lines.each do |line| %><%= line.number %>: <%= line.file_relative %> -> **<%= line.method %>** -<% end %> -``` - -## Environment ## - - -<% for key, val in notice.env_vars %> - - - - -<% end %> -
<%= key %>:<%= val %>
-<% end %> - diff --git a/views/gitlab_issues_summary.txt.erb b/views/gitlab_issues_summary.txt.erb deleted file mode 100644 index 6a7fbe9..0000000 --- a/views/gitlab_issues_summary.txt.erb +++ /dev/null @@ -1,17 +0,0 @@ -[See this exception on Errbit](<%= problem.url %> "See this exception on Errbit") -<% if notice = problem.notices.first %> -# <%= notice.message %> # -## Summary ## -<% if notice.request['url'].present? %> -### URL ### -[<%= notice.request['url'] %>](<%= notice.request['url'] %>)" -<% end %> -### Where ### -<%= notice.where %> - -### Occured ### -<%= notice.created_at.to_s(:micro) %> - -### Similar ### -<%= (notice.problem.notices_count - 1).to_s %> -<% end %>