gitlab_exception_notification 0.0.1 → 1.0.0
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.
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 5605432556fe4818136d9665283a4cdc2539df00
         | 
| 4 | 
            +
              data.tar.gz: d6e6c3db460a7b0042426ed2effe0cd4e35d41d9
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: e39f635df1ecf5bec0c02398b1b2ed7c2f8e1a4b65c6c47ab526df220db91d8fa433961a7c6bdbfc23846050e4aa67c94f0080f2b9ce8e88ed4e21ec32f75a43
         | 
| 7 | 
            +
              data.tar.gz: c5455fef59681269060cd1867fe423e4dcdcc5348d0ae490e2f3d89d72e66a45e5de9b94f94a2c04e1d5fc8aec1b83fb657d53ae464b1b0dfd8997ec72fdf2b2
         | 
| @@ -1,2 +1,16 @@ | |
| 1 1 | 
             
            module GitlabExceptionNotification
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              require "gitlab"
         | 
| 4 | 
            +
              require 'digest'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              REJECT_HEADERS = /HTTP_COOKIE|(rack.*)|(action_dispatch.*)/
         | 
| 7 | 
            +
              SLINE = "
         | 
| 8 | 
            +
              "
         | 
| 9 | 
            +
              STAB = SLINE + "    "
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              PER_PAGE = 40
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              require 'gitlab_exception_notification/issue'
         | 
| 14 | 
            +
              require 'gitlab_exception_notification/gitlab_notifier'
         | 
| 15 | 
            +
             | 
| 2 16 | 
             
            end
         | 
| @@ -1,139 +1,24 @@ | |
| 1 1 |  | 
| 2 | 
            -
            require "gitlab"
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            REJECT_HEADERS = /HTTP_COOKIE|(rack.*)|(action_dispatch.*)/
         | 
| 5 | 
            -
            SLINE = "
         | 
| 6 | 
            -
            "
         | 
| 7 | 
            -
            STAB = SLINE + "    "
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            PER_PAGE = 40
         | 
| 10 2 |  | 
| 11 3 | 
             
            module ExceptionNotifier
         | 
| 12 4 | 
             
              class GitlabNotifier
         | 
| 13 5 | 
             
                def initialize(options)
         | 
| 14 | 
            -
                   | 
| 15 | 
            -
                  @client = Gitlab.client(endpoint: 'http://gitlab.42.fr/api/v3', private_token: options[:private_token])
         | 
| 16 | 
            -
                  @project_id = @client.project_search(options[:project_name]).first.id
         | 
| 17 | 
            -
                  @issues = get_all_issues
         | 
| 18 | 
            -
                end
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                def issue_exists?(exception)
         | 
| 21 | 
            -
                  @issues = get_all_issues
         | 
| 22 | 
            -
                  rest = @issues.select do |i|
         | 
| 23 | 
            -
                    i.title == issue_title(exception) and i.description and i.description[exception.backtrace.first]
         | 
| 24 | 
            -
                  end
         | 
| 25 | 
            -
                  (rest.count > 0 ? rest.first.id : false)
         | 
| 26 | 
            -
                end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                def get_all_issues
         | 
| 29 | 
            -
                  page = 1
         | 
| 30 | 
            -
                  i = @client.issues(@project_id, per_page: PER_PAGE, page: page, order_by: :updated_at)
         | 
| 31 | 
            -
                  @issues = i
         | 
| 32 | 
            -
                  while i.count == PER_PAGE
         | 
| 33 | 
            -
                    i = @client.issues(@project_id, per_page: PER_PAGE, page: page, order_by: :updated_at)
         | 
| 34 | 
            -
                    @issues += i
         | 
| 35 | 
            -
                    page += 1
         | 
| 36 | 
            -
                  end
         | 
| 37 | 
            -
                  return @issues.flatten
         | 
| 38 | 
            -
                end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                def update_issue(id, exception)
         | 
| 41 | 
            -
                  issue = @client.issue(@project_id, id)
         | 
| 42 | 
            -
                  last = issue.updated_at.to_date
         | 
| 43 | 
            -
                  if last < 1.hour.ago        
         | 
| 44 | 
            -
                    begin
         | 
| 45 | 
            -
                      @client.edit_issue(@project_id, id, {state_event: "reopen"})
         | 
| 46 | 
            -
                      iss = @client.edit_issue(@project_id, id, {title: "#{issue.title}"})
         | 
| 47 | 
            -
                    rescue Exception => e
         | 
| 48 | 
            -
                      p "An error occured: #{e.inspect}"
         | 
| 49 | 
            -
                    end
         | 
| 50 | 
            -
                  else
         | 
| 51 | 
            -
                    body = ":fire: This issue occured again #{Time.current}.
         | 
| 52 | 
            -
                    \n#### Summary:\n
         | 
| 53 | 
            -
                    #{issue_summary(exception).map { |k, v|  "- #{k}: #{v}"}.join(SLINE)}
         | 
| 54 | 
            -
                    "
         | 
| 55 | 
            -
                    @client.reopen_issue(@project_id, id)
         | 
| 56 | 
            -
                    @client.create_issue_note @project_id, id, body
         | 
| 57 | 
            -
                  end
         | 
| 58 | 
            -
                end
         | 
| 59 | 
            -
                  
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                # The issue title
         | 
| 62 | 
            -
                def issue_title exception
         | 
| 63 | 
            -
                  title = []
         | 
| 64 | 
            -
                  title << "#{@kontroller.controller_name}##{@kontroller.action_name}" if @kontroller
         | 
| 65 | 
            -
                  title << "(#{exception.class})"
         | 
| 66 | 
            -
                  title << (exception.message.length > 120 ? exception.message[0..120] + "..." : exception.message)
         | 
| 67 | 
            -
                  title.join(' ')
         | 
| 68 | 
            -
                end
         | 
| 69 | 
            -
             | 
| 70 | 
            -
                def issue_summary exception
         | 
| 71 | 
            -
                  {
         | 
| 72 | 
            -
                    'URL': @request.url,
         | 
| 73 | 
            -
                    'HTTP Method': @request.request_method,
         | 
| 74 | 
            -
                    'IP address': @request.remote_ip,
         | 
| 75 | 
            -
                    'Parameters': md_hash(@request.filtered_parameters, STAB),
         | 
| 76 | 
            -
                    'Timestamp': Time.current,
         | 
| 77 | 
            -
                    'Server': Socket.gethostname,
         | 
| 78 | 
            -
                    'Rails root': (defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : nil),
         | 
| 79 | 
            -
                    'Process': $$,
         | 
| 80 | 
            -
                    'session data': md_hash(@request.session.to_hash, STAB),
         | 
| 81 | 
            -
                  }
         | 
| 6 | 
            +
                  @options = options
         | 
| 82 7 | 
             
                end
         | 
| 83 8 |  | 
| 84 | 
            -
                def issue_description exception
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                  # Get a 'mardowned' backtrace
         | 
| 87 | 
            -
                  m_backtrace = "```#{SLINE} #{exception.backtrace.join(SLINE)}#{SLINE}```"
         | 
| 88 | 
            -
             | 
| 89 | 
            -
                  # Get the concerned file
         | 
| 90 | 
            -
                  file = exception.backtrace.first
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                  description = ["#{exception.message} #{@kontroller ? 'in controller ' + @kontroller.controller_name + '#' + @kontroller.action_name : ''}"]
         | 
| 93 | 
            -
                  description << "File: #{file}"
         | 
| 94 | 
            -
                  {
         | 
| 95 | 
            -
                    'Summary': issue_summary(exception).map { |k, v|  "- #{k}: #{v}"}.join(SLINE),
         | 
| 96 | 
            -
                    'session id': @request.ssl? ? "[FILTERED]" : (@request.session['session_id'] || (@request.env["rack.session.options"] and @request.env["rack.session.options"][:id])).inspect,
         | 
| 97 | 
            -
                    'data': @data,
         | 
| 98 | 
            -
                    'backtrace': m_backtrace,
         | 
| 99 | 
            -
                    'request headers': md_hash(@request.headers),
         | 
| 100 | 
            -
                    'environment': md_hash(@env.reject{|k, v| (REJECT_HEADERS =~ k).nil? })
         | 
| 101 | 
            -
                  }.reject{|k, v| v.nil? or v.blank?}.each do |k, v|
         | 
| 102 | 
            -
                    description << "--------------------------------"
         | 
| 103 | 
            -
                    description << "#### #{k.to_s.humanize}: "
         | 
| 104 | 
            -
                    description << v.to_s
         | 
| 105 | 
            -
                  end
         | 
| 106 | 
            -
                  description.join("\n\n")
         | 
| 107 | 
            -
                end
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                def md_hash hash, pre = ""
         | 
| 110 | 
            -
                  hash.map { |k, v|  "#{pre}- **#{k}**: `#{v}`"}.join(SLINE)
         | 
| 111 | 
            -
                end
         | 
| 112 | 
            -
             | 
| 113 | 
            -
                def create_issue(exception)
         | 
| 114 | 
            -
                  @client.create_issue(@project_id, issue_title(exception), {description: issue_description(exception)})
         | 
| 115 | 
            -
                end
         | 
| 116 9 |  | 
| 117 10 | 
             
                def exception_notification(env, exception, options={})
         | 
| 118 | 
            -
                   | 
| 119 | 
            -
                   | 
| 120 | 
            -
             | 
| 121 | 
            -
                  @kontroller = env['action_controller.instance'] || MissingController.new
         | 
| 122 | 
            -
                  @request    = ActionDispatch::Request.new(env)
         | 
| 123 | 
            -
                  @sections   = @options[:sections]
         | 
| 124 | 
            -
                  @data       = (env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
         | 
| 125 | 
            -
                  @sections   = @sections + %w(data) unless @data.empty?
         | 
| 126 | 
            -
             | 
| 127 | 
            -
                  if issue_id = issue_exists?(exception)
         | 
| 128 | 
            -
                    update_issue(issue_id, exception)
         | 
| 11 | 
            +
                  issue = GitlabExceptionNotification::Issue.new(env, exception, options)
         | 
| 12 | 
            +
                  if issue_id = issue.exists?
         | 
| 13 | 
            +
                    issue.update(issue_id)
         | 
| 129 14 | 
             
                  else
         | 
| 130 | 
            -
                     | 
| 15 | 
            +
                    issue.create
         | 
| 131 16 | 
             
                  end
         | 
| 132 17 | 
             
                end
         | 
| 133 18 |  | 
| 134 19 | 
             
                def call(exception, options={})
         | 
| 135 20 | 
             
                  env = options[:env] || {}
         | 
| 136 | 
            -
                  exception_notification(env, exception, options)
         | 
| 21 | 
            +
                  exception_notification(env, exception, @options.merge(options))
         | 
| 137 22 | 
             
                end
         | 
| 138 23 | 
             
              end
         | 
| 139 24 | 
             
            end
         | 
| @@ -0,0 +1,142 @@ | |
| 1 | 
            +
            module GitlabExceptionNotification
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              class Issue
         | 
| 4 | 
            +
                
         | 
| 5 | 
            +
                def initialize(env, exception, options={})
         | 
| 6 | 
            +
                  @env        = env
         | 
| 7 | 
            +
                  @exception  = exception
         | 
| 8 | 
            +
                  @options    = options.reverse_merge(env['exception_notifier.options'] || {})
         | 
| 9 | 
            +
                  @kontroller = env['action_controller.instance'] || MissingController.new
         | 
| 10 | 
            +
                  @request    = ActionDispatch::Request.new(env)
         | 
| 11 | 
            +
                  @data       = (env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
         | 
| 12 | 
            +
                  @digest     = digest
         | 
| 13 | 
            +
                  @client = Gitlab.client(endpoint: 'http://gitlab.42.fr/api/v3', private_token: options[:private_token])
         | 
| 14 | 
            +
                  @project_id = @client.project_search(options[:project_name]).first.id
         | 
| 15 | 
            +
                  @issues     = self.all
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def create
         | 
| 19 | 
            +
                  @client.create_issue(@project_id, title, {description: description})
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def update(id)
         | 
| 23 | 
            +
                  issue = @client.issue(@project_id, id)
         | 
| 24 | 
            +
                  last = issue.updated_at.to_date
         | 
| 25 | 
            +
                  if last < 1.hour.ago
         | 
| 26 | 
            +
                    begin
         | 
| 27 | 
            +
                      @client.edit_issue(@project_id, id, {state_event: "reopen"})
         | 
| 28 | 
            +
                      iss = @client.edit_issue(@project_id, id, {title: increment_title(issue)})
         | 
| 29 | 
            +
                    rescue Exception => e
         | 
| 30 | 
            +
                      p "An error occured: #{e.inspect}"
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  else
         | 
| 33 | 
            +
                    body = ":fire: This issue occured again #{Time.current}.
         | 
| 34 | 
            +
                    \n#### Summary:\n
         | 
| 35 | 
            +
                    #{summary.map { |k, v|  "- #{k}: #{v}"}.join(SLINE)}
         | 
| 36 | 
            +
                    "
         | 
| 37 | 
            +
                    @client.reopen_issue(@project_id, id)
         | 
| 38 | 
            +
                    @client.create_issue_note @project_id, id, body
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def is_same_exception? issue
         | 
| 43 | 
            +
                  return false if issue.nil? or issue.description.nil?
         | 
| 44 | 
            +
                  issue.description.split(SLINE).last.strip == @digest
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def exists?
         | 
| 48 | 
            +
                  # @issues = self.all
         | 
| 49 | 
            +
                  rest = @issues.select do |i|
         | 
| 50 | 
            +
                    is_same_exception?(i)
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                  (rest.count > 0 ? rest.first.id : false)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def all
         | 
| 56 | 
            +
                  page = 1
         | 
| 57 | 
            +
                  i = @client.issues(@project_id, per_page: PER_PAGE, page: page, order_by: :updated_at)
         | 
| 58 | 
            +
                  @issues = i
         | 
| 59 | 
            +
                  while i.count == PER_PAGE
         | 
| 60 | 
            +
                    i = @client.issues(@project_id, per_page: PER_PAGE, page: page, order_by: :updated_at)
         | 
| 61 | 
            +
                    @issues += i
         | 
| 62 | 
            +
                    page += 1
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                  return @issues.flatten
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                # =====================================================================================
         | 
| 68 | 
            +
                #                                       Formatters
         | 
| 69 | 
            +
                # =====================================================================================
         | 
| 70 | 
            +
             | 
| 71 | 
            +
             | 
| 72 | 
            +
                # The issue title
         | 
| 73 | 
            +
                def title
         | 
| 74 | 
            +
                  t = []
         | 
| 75 | 
            +
                  t << "#{@kontroller.controller_name}##{@kontroller.action_name}" if @kontroller
         | 
| 76 | 
            +
                  t << "(#{@exception.class})"
         | 
| 77 | 
            +
                  t << (@exception.message.length > 120 ? @exception.message[0..120] + "..." : @exception.message)
         | 
| 78 | 
            +
                  t.join(' ')
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def summary
         | 
| 82 | 
            +
                  {
         | 
| 83 | 
            +
                    'URL': @request.url,
         | 
| 84 | 
            +
                    'HTTP Method': @request.request_method,
         | 
| 85 | 
            +
                    'IP address': @request.remote_ip,
         | 
| 86 | 
            +
                    'Parameters': md_hash(@request.filtered_parameters, STAB),
         | 
| 87 | 
            +
                    'Timestamp': Time.current,
         | 
| 88 | 
            +
                    'Server': Socket.gethostname,
         | 
| 89 | 
            +
                    'Rails root': (defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : nil),
         | 
| 90 | 
            +
                    'Process': $$,
         | 
| 91 | 
            +
                    'session data': md_hash(@request.session.to_hash, STAB),
         | 
| 92 | 
            +
                  }
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def description
         | 
| 96 | 
            +
                  # Get a 'mardowned' backtrace
         | 
| 97 | 
            +
                  m_backtrace = "```#{SLINE} #{@exception.backtrace.join(SLINE)}#{SLINE}```"
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  # Get the concerned file
         | 
| 100 | 
            +
                  file = @exception.backtrace.first
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  d = ["#{@exception.message} #{@kontroller ? 'in controller ' + @kontroller.controller_name + '#' + @kontroller.action_name : ''}"]
         | 
| 103 | 
            +
                  d << "File: #{file}"
         | 
| 104 | 
            +
                  {
         | 
| 105 | 
            +
                    'Summary': summary.map { |k, v|  "- #{k}: #{v}"}.join(SLINE),
         | 
| 106 | 
            +
                    'session id': @request.ssl? ? "[FILTERED]" : (@request.session['session_id'] || (@request.env["rack.session.options"] and @request.env["rack.session.options"][:id])).inspect,
         | 
| 107 | 
            +
                    'data': @data,
         | 
| 108 | 
            +
                    'backtrace': m_backtrace,
         | 
| 109 | 
            +
                    'request headers': md_hash(@request.headers),
         | 
| 110 | 
            +
                    'environment': md_hash(@env.reject{|k, v| (REJECT_HEADERS =~ k).nil? })
         | 
| 111 | 
            +
                  }.reject{|k, v| v.nil? or v.blank?}.each do |k, v|
         | 
| 112 | 
            +
                    d << "--------------------------------"
         | 
| 113 | 
            +
                    d << "#### #{k.to_s.humanize}: "
         | 
| 114 | 
            +
                    d << v.to_s
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
                  d << @digest
         | 
| 117 | 
            +
                  d.join(SLINE)
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                # =====================================================================================
         | 
| 121 | 
            +
                #                                       Utilities
         | 
| 122 | 
            +
                # =====================================================================================
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                protected
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                def increment_title issue
         | 
| 127 | 
            +
                  count = ((issue.title =~ /^\([0-9]+\).*/) == 0 ? issue.title.gsub(/^\(([0-9]*)\)(.*)/,'\1').to_i + 1 : 1)
         | 
| 128 | 
            +
                  new_title = ((issue.title =~ /^\([0-9]+\).*/) == 0 ? issue.title.gsub(/^\(([0-9]*)\)(.*)/, '(' + count.to_s + ')\2') : "(#{count.to_s}) #{issue.title}")
         | 
| 129 | 
            +
                  return new_title
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                def md_hash hash, pre = ""
         | 
| 133 | 
            +
                  hash.map { |k, v|  "#{pre}- **#{k}**: `#{v}`"}.join(SLINE)
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
                
         | 
| 136 | 
            +
                def digest
         | 
| 137 | 
            +
                  "EXC" + Digest::SHA256.hexdigest(@exception.to_s + @exception.backtrace.first.split(":in").first.to_s)
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
             | 
| 141 | 
            +
              end
         | 
| 142 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: gitlab_exception_notification
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0 | 
| 4 | 
            +
              version: 1.0.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Andre Aubin
         | 
| @@ -19,7 +19,7 @@ dependencies: | |
| 19 19 | 
             
                    version: '4.2'
         | 
| 20 20 | 
             
                - - ">="
         | 
| 21 21 | 
             
                  - !ruby/object:Gem::Version
         | 
| 22 | 
            -
                    version: 4.2. | 
| 22 | 
            +
                    version: 4.2.0
         | 
| 23 23 | 
             
              type: :runtime
         | 
| 24 24 | 
             
              prerelease: false
         | 
| 25 25 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| @@ -29,7 +29,27 @@ dependencies: | |
| 29 29 | 
             
                    version: '4.2'
         | 
| 30 30 | 
             
                - - ">="
         | 
| 31 31 | 
             
                  - !ruby/object:Gem::Version
         | 
| 32 | 
            -
                    version: 4.2. | 
| 32 | 
            +
                    version: 4.2.0
         | 
| 33 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 34 | 
            +
              name: exception_notification
         | 
| 35 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 36 | 
            +
                requirements:
         | 
| 37 | 
            +
                - - "~>"
         | 
| 38 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 39 | 
            +
                    version: '4.0'
         | 
| 40 | 
            +
                - - ">="
         | 
| 41 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 42 | 
            +
                    version: 4.0.1
         | 
| 43 | 
            +
              type: :runtime
         | 
| 44 | 
            +
              prerelease: false
         | 
| 45 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 46 | 
            +
                requirements:
         | 
| 47 | 
            +
                - - "~>"
         | 
| 48 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 49 | 
            +
                    version: '4.0'
         | 
| 50 | 
            +
                - - ">="
         | 
| 51 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 52 | 
            +
                    version: 4.0.1
         | 
| 33 53 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 34 54 | 
             
              name: gitlab
         | 
| 35 55 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -62,6 +82,7 @@ files: | |
| 62 82 | 
             
            - Rakefile
         | 
| 63 83 | 
             
            - lib/gitlab_exception_notification.rb
         | 
| 64 84 | 
             
            - lib/gitlab_exception_notification/gitlab_notifier.rb
         | 
| 85 | 
            +
            - lib/gitlab_exception_notification/issue.rb
         | 
| 65 86 | 
             
            - lib/gitlab_exception_notification/version.rb
         | 
| 66 87 | 
             
            - test/dummy/README.rdoc
         | 
| 67 88 | 
             
            - test/dummy/Rakefile
         |