codestats-metrics-reporter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f3db1ab76d14e2f36cc268490015859d4d4045fb
4
+ data.tar.gz: c7e0e4722f67ff55293612b6df51e69b2cd64cbe
5
+ SHA512:
6
+ metadata.gz: 73e6bd79548f92f926af62aff06dd381ceb6d04e0a7ec494027c644c0ca1fda8ed037596becfedddc0bc7ed55a42b972501f916f37e841cc98342aadd12b08ff
7
+ data.tar.gz: fbbb2f78ce15882b6ae7cfd949ba650b51eee672d72c45e75bbc490a947e5b96e604727a91673b3141708b74d5017c4ec3a83ecc5d7b64c436e7b8057081fb71
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,7 @@
1
+ # This is the configuration used to check the rubocop source code.
2
+
3
+ Documentation:
4
+ Enabled: false
5
+
6
+ LineLength:
7
+ Max: 99
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.5
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in codestats-metrics-reporter.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2016 Wolox S.A.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ Code Stats Metrics Reporter
2
+ ===========================
3
+
4
+ Ruby tasks gem to report metrics to [Code Stats](https://github.com/Wolox/codestats) from a Continous Integration service.
5
+
6
+ This gem is still alpha stage and it is not pushed to [Ruby Gems](https://rubygems.org/). It include those metrics that suit [Wolox](http://wolox.co) technologies. The idea es to leave this gem as a generic interface to [Code Stats](https://github.com/Wolox/codestats) that parses metrics from a folder. So you can generate a separate gem per metric that will leave metrics in that folder. The invocation would be something like this:
7
+
8
+ ```bash
9
+ bundle exec simplecov-code-stats-metric
10
+ bundle exec rubycritic-code-stats-metric
11
+ bundle exec code-stats-metrics-reporter
12
+ ```
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'codestats-metrics-reporter'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ ```bash
25
+ bundle
26
+ ```
27
+
28
+ Or install it yourself as:
29
+
30
+ ```bash
31
+ gem install codestats-metrics-reporter
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ The idea is to install this gem in your project and to let your Continous Integration to run certain scripts to push your metrics to your self-hosted Code Stats.
37
+
38
+ The default configuration for your metrics is in the [default.yml](config/default.yml), but you can create the `.codestats.yml` and replace those values. Because this gem accepts any kind of metric for any kind of language, framework, all the metrics are disabled by default, feel free to enable them by adding:
39
+
40
+ ```ruby
41
+ enabled: true
42
+ ```
43
+
44
+ to your `.codestats.yml`. For example:
45
+
46
+ ```ruby
47
+ metrics:
48
+ simplecov:
49
+ enabled: true
50
+ ```
51
+
52
+ then you need to add to your CI file the following command after your build is success:
53
+
54
+ ```ruby
55
+ bundle exec codestats-metrics-reporter
56
+ ```
57
+
58
+ and if you have the right configuration, you will see your metric value in Code Stats under your branch name.
59
+
60
+ ## Development
61
+
62
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
63
+
64
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
65
+
66
+ ## Contributing
67
+
68
+ 1. Fork it
69
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
70
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
71
+ 4. Run rspec tests (`bundle exec rspec spec -fd`)
72
+ 5. Run rubocop lint (`bundle exec rubocop spec lib bin`)
73
+ 6. Push your branch (`git push origin my-new-feature`)
74
+ 7. Create a new Pull Request
75
+
76
+ Feel free to add a new Issue by clicking [here](https://github.com/Wolox/codestats-metrics-reporter/issues/new) if you find a bug, idea of improvement, etc.
77
+
78
+ ## About
79
+
80
+ This project is maintained by:
81
+
82
+ - [Esteban Guido Pintos](https://github.com/epintos)
83
+
84
+ and it is written by [Wolox](http://www.wolox.com.ar) under the [LICENSE](LICENSE) license.
85
+
86
+
87
+ ![Wolox](https://raw.githubusercontent.com/Wolox/press-kit/master/logos/logo_banner.png)
88
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
3
+
4
+ require 'code_stats/metrics/reporter'
5
+ require 'benchmark'
6
+
7
+ cli = CodeStats::Metrics::Reporter::CLI.new
8
+ result = 0
9
+
10
+ time = Benchmark.realtime do
11
+ result = cli.run
12
+ end
13
+
14
+ puts "Finished in #{time} seconds"
15
+ exit result
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'code_stats/metrics/reporter'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ require 'pry'
11
+ Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'code_stats/metrics/reporter/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'codestats-metrics-reporter'
8
+ spec.version = CodeStats::Metrics::Reporter::VERSION
9
+ spec.authors = ['Esteban Pintos']
10
+ spec.email = ['esteban.pintos@wolox.com.ar']
11
+
12
+ spec.summary = %q{Report metrics to CodeStats}
13
+ spec.homepage = %q{https://github.com/Wolox/codestats-metrics-reporter}
14
+ spec.description = %q{Gem that will let you control your code quality by reporting custom metrics to [CodeStats](https://github.com/Wolox/codestats)}
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.require_paths = ['lib']
19
+ spec.executables = Dir['bin/*'].map{ |f| File.basename(f) }
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.10'
22
+ spec.add_development_dependency 'rspec'
23
+ spec.add_development_dependency 'pry'
24
+ spec.add_development_dependency 'rubocop'
25
+ spec.add_dependency 'rake', '>= 0.8'
26
+ spec.add_dependency 'httparty'
27
+ spec.add_dependency 's3_uploader'
28
+ spec.add_dependency 'oga'
29
+ end
@@ -0,0 +1,53 @@
1
+ config:
2
+ token: <%= ENV['CODE_STATS_TOKEN'] %>
3
+ ci: 'CIRCLECI'
4
+ url: <%= ENV['CODE_STATS_URL'] %>
5
+ metrics:
6
+ rubycritic:
7
+ name: 'RubyCritic'
8
+ enabled: false
9
+ minimum: 70
10
+ upload_report: true
11
+ report_dir: 'tmp/rubycritic'
12
+ uploader_key: <%= ENV['CODE_STATS_S3_KEY'] %>
13
+ uploader_secret: <%= ENV['CODE_STATS_S3_SECRET_KEY'] %>
14
+ uploader_region: <%= ENV['CODE_STATS_S3_REGION'] %>
15
+ uploader_bucket: <%= ENV['CODE_STATS_S3_BUCKET'] %>
16
+ simplecov:
17
+ name: 'SimpleCov'
18
+ enabled: false
19
+ minimum: 70
20
+ slather:
21
+ name: 'Slather'
22
+ enabled: false
23
+ minimum: 70
24
+ upload_report: true
25
+ report_dir: 'coverage'
26
+ uploader_key: <%= ENV['CODE_STATS_S3_KEY'] %>
27
+ uploader_secret: <%= ENV['CODE_STATS_S3_SECRET_KEY'] %>
28
+ uploader_region: <%= ENV['CODE_STATS_S3_REGION'] %>
29
+ uploader_bucket: <%= ENV['CODE_STATS_S3_BUCKET'] %>
30
+ escomplex:
31
+ name: 'Js Maintainability'
32
+ location: 'code-quality-report/stats.json'
33
+ enabled: false
34
+ minimum: 70
35
+ karma_coverage:
36
+ name: 'Code Coverage'
37
+ location: 'test/unit/coverage/cobertura-coverage.xml'
38
+ enabled: false
39
+ minimum: 20
40
+ android_jacoco:
41
+ name: 'Test Coverage'
42
+ location: 'app/build/reports/jacoco/testStageDebugUnitTestCoverage/testStageDebugUnitTestCoverage.xml'
43
+ enabled: false
44
+ minimum: 60
45
+ build_base_url: ''
46
+ build_report_file_url: 'jacoco'
47
+ android_quality:
48
+ name: 'Code Quality'
49
+ location: 'app/build/outputs/lint-results.xml'
50
+ enabled: false
51
+ minimum: 70
52
+ build_base_url: ''
53
+ source_dir: 'app'
@@ -0,0 +1,13 @@
1
+ require 'code_stats/metrics/reporter/ci'
2
+ require 'code_stats/metrics/reporter/config_store'
3
+ require 'code_stats/metrics/reporter/config_loader'
4
+ require 'code_stats/metrics/reporter/metric_config'
5
+ require 'code_stats/metrics/reporter/cli'
6
+ require 'code_stats/metrics/reporter/rubycritic'
7
+ require 'code_stats/metrics/reporter/simplecov'
8
+ require 'code_stats/metrics/reporter/slather'
9
+ require 'code_stats/metrics/reporter/karma_coverage'
10
+ require 'code_stats/metrics/reporter/escomplex'
11
+ require 'code_stats/metrics/reporter/version'
12
+ require 'code_stats/metrics/reporter/android_jacoco'
13
+ require 'code_stats/metrics/reporter/android_quality'
@@ -0,0 +1,66 @@
1
+ require 'json'
2
+
3
+ module CodeStats
4
+ module Metrics
5
+ module Reporter
6
+ class AndroidJacoco
7
+ class << self
8
+ def generate_data(metric, config_store)
9
+ @config_store = config_store
10
+ @metric = metric
11
+ {
12
+ metric_name: metric.data['name'],
13
+ value: parse_coverage,
14
+ minimum: metric.data['minimum'],
15
+ url: url
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ def parse_coverage
22
+ doc = Oga.parse_xml(File.read(@metric.data['location']))
23
+ covered = parse_covered(doc)
24
+ covered * 100 / (parse_missed(doc) + covered)
25
+ end
26
+
27
+ def parse_missed(doc)
28
+ doc.xpath('/report/counter').map { |c| c.get('missed').to_f }.inject(0, :+)
29
+ end
30
+
31
+ def parse_covered(doc)
32
+ doc.xpath('/report/counter').map { |c| c.get('covered').to_f }.inject(0, :+)
33
+ end
34
+
35
+ def url
36
+ return if invalid_url_params?
37
+ "#{build_base_url}/#{repository_name}/#{build_identifier}/#{build_report_file_url}"
38
+ end
39
+
40
+ def invalid_url_params?
41
+ build_base_url.nil? ||
42
+ build_identifier.nil? ||
43
+ repository_name.nil? ||
44
+ build_report_file_url.nil?
45
+ end
46
+
47
+ def build_base_url
48
+ @metric.data['build_base_url']
49
+ end
50
+
51
+ def build_report_file_url
52
+ @metric.data['build_report_file_url']
53
+ end
54
+
55
+ def build_identifier
56
+ Ci.data(@config_store.ci)[:build_identifier]
57
+ end
58
+
59
+ def repository_name
60
+ Ci.data(@config_store.ci)[:repository_name]
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,113 @@
1
+ require 'json'
2
+
3
+ module CodeStats
4
+ module Metrics
5
+ module Reporter
6
+ class AndroidQuality
7
+ class << self
8
+ EXTENSIONS = %w(xml java).freeze
9
+
10
+ def generate_data(metric, config_store)
11
+ @config_store = config_store
12
+ @metric = metric
13
+ {
14
+ metric_name: metric.data['name'],
15
+ value: parse_quality,
16
+ minimum: metric.data['minimum'],
17
+ url: url
18
+ }
19
+ end
20
+
21
+ private
22
+
23
+ def parse_quality
24
+ issues = parse_issues(parse_xml)
25
+ issues_map = add_issues_to_files(issues, initialize_issues_map)
26
+ average_scores(score_files(issues_map))
27
+ end
28
+
29
+ def parse_xml
30
+ xml = File.read(@metric.data['location'])
31
+ Oga.parse_xml(xml)
32
+ end
33
+
34
+ def parse_issues(doc)
35
+ doc.xpath('//issue').map do |i|
36
+ {
37
+ location: i.xpath('location').first.get('file'),
38
+ severity: i.get('severity'),
39
+ priority: i.get('priority').to_i,
40
+ category: i.get('category'),
41
+ summary: i.get('summary')
42
+ }
43
+ end
44
+ end
45
+
46
+ def initialize_issues_map
47
+ source_files.map { |f| { file: f, issues: [] } }
48
+ end
49
+
50
+ # Add issues to each file
51
+ def add_issues_to_files(issues, issues_map)
52
+ issues.each do |issue|
53
+ source = issues_map.find { |i| i[:file] == issue[:location] }
54
+ source[:issues].push(issue) if source
55
+ end
56
+ issues_map
57
+ end
58
+
59
+ # Give a score to each file
60
+ def score_files(issues_map)
61
+ issues_map.each do |f|
62
+ f[:score] = f[:issues].inject(100) { |a, e| a - e[:priority]**2 }
63
+ f[:score] = 0 if f[:score] < 0
64
+ end
65
+ issues_map
66
+ end
67
+
68
+ # Return the average of scores
69
+ def average_scores(issues_map)
70
+ issues_map.inject(0) { |a, e| a + e[:score] } / issues_map.size
71
+ end
72
+
73
+ def source_files
74
+ files = Dir.glob("#{source_dir}/**/src/**/*.{#{EXTENSIONS.join(',')}}")
75
+ files + Dir.glob("#{source_dir}/**/*.gradle")
76
+ end
77
+
78
+ def source_dir
79
+ @metric.data['source_dir']
80
+ end
81
+
82
+ def url
83
+ return if invalid_url_params?
84
+ "#{build_base_url}/#{repository_name}/#{build_identifier}/#{build_report_file_url}"
85
+ end
86
+
87
+ def invalid_url_params?
88
+ build_base_url.nil? ||
89
+ build_identifier.nil? ||
90
+ repository_name.nil? ||
91
+ build_report_file_url.nil?
92
+ end
93
+
94
+ def build_base_url
95
+ @metric.data['build_base_url']
96
+ end
97
+
98
+ def build_report_file_url
99
+ @metric.data['build_report_file_url']
100
+ end
101
+
102
+ def build_identifier
103
+ Ci.data(@config_store.ci)[:build_identifier]
104
+ end
105
+
106
+ def repository_name
107
+ Ci.data(@config_store.ci)[:repository_name]
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,38 @@
1
+ module CodeStats
2
+ module Metrics
3
+ module Reporter
4
+ class Ci
5
+ def self.data(service)
6
+ respond_to?(service.downcase) ? send(service.downcase) : {}
7
+ end
8
+
9
+ def self.travis
10
+ {
11
+ name: 'travis-ci',
12
+ build_identifier: ENV['TRAVIS_JOB_ID'],
13
+ pull_request: ENV['TRAVIS_PULL_REQUEST'],
14
+ repository_name: ENV['TRAVIS_REPO_SLUG'].split('/')[1]
15
+ }
16
+ end
17
+
18
+ def self.circleci
19
+ {
20
+ name: 'circleci',
21
+ build_identifier: ENV['CIRCLE_BUILD_NUM'],
22
+ branch: ENV['CIRCLE_BRANCH'],
23
+ repository_name: ENV['CIRCLE_PROJECT_REPONAME']
24
+ }
25
+ end
26
+
27
+ def self.jenkins
28
+ {
29
+ name: 'jenkins',
30
+ build_identifier: ENV['BUILD_NUMBER'],
31
+ branch: ENV['ghprbSourceBranch'],
32
+ repository_name: ENV['JOB_NAME']
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,68 @@
1
+ require 'httparty'
2
+
3
+ module CodeStats
4
+ module Metrics
5
+ module Reporter
6
+ class CLI
7
+ SUCCESS_CODE = 0
8
+ ERROR_CODE = 2
9
+ attr_reader :config_store
10
+
11
+ def initialize
12
+ @config_store = ConfigStore.new
13
+ end
14
+
15
+ def run
16
+ config_store.metrics_configs.each do |metric_config|
17
+ process_and_report_metric(metric_config, config_store)
18
+ end
19
+ SUCCESS_CODE
20
+ rescue StandardError => e
21
+ puts "Message: #{e.message} - Backtrace: #{e.backtrace}"
22
+ ERROR_CODE
23
+ end
24
+
25
+ private
26
+
27
+ def process_and_report_metric(metric_config, config_store)
28
+ puts "Processing #{metric_config.data['name']} metric"
29
+ data = generate_metric_data(metric_config, config_store)
30
+ return if data.nil?
31
+ post_report_metric(data)
32
+ puts "Sending #{metric_config.data['name']} data"
33
+ end
34
+
35
+ def generate_metric_data(metric_config, config_store)
36
+ Object.const_get(
37
+ "CodeStats::Metrics::Reporter::#{generate_class_name(metric_config)}"
38
+ ).generate_data(metric_config, config_store)
39
+ end
40
+
41
+ def generate_class_name(metric_config)
42
+ metric_config.data['metric'].split('_').map(&:capitalize).join
43
+ end
44
+
45
+ def post_report_metric(data)
46
+ HTTParty.post(
47
+ "#{config_store.url}api/v1/metrics",
48
+ body: metric_data(data),
49
+ headers: { 'Authorization' => config_store.token.to_s }
50
+ )
51
+ end
52
+
53
+ def metric_data(data)
54
+ {
55
+ metric: {
56
+ branch_name: Ci.data(config_store.ci)[:branch],
57
+ name: data[:metric_name],
58
+ value: data[:value],
59
+ url: data[:url],
60
+ minimum: data[:minimum],
61
+ pull_request_number: Ci.data(config_store.ci)[:pull_request]
62
+ }
63
+ }
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,65 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+
4
+ module CodeStats
5
+ module Metrics
6
+ module Reporter
7
+ class ConfigLoader
8
+ FILE_NAME = '.codestats.yml'.freeze
9
+ CODE_STATS_HOME = File.realpath(File.join(File.dirname(__FILE__), '..', '..', '..', '..'))
10
+ DEFAULT_FILE = File.join(CODE_STATS_HOME, 'config', 'default.yml')
11
+
12
+ class << self
13
+ def load_file
14
+ load_default_file
15
+ load_user_file
16
+ @user_data || @default_data
17
+ end
18
+
19
+ def load_default_file
20
+ default_file = load_yml_file(DEFAULT_FILE)
21
+ return if default_file.nil?
22
+ @default_data = default_file['config'].merge(metrics_configs: [])
23
+ load_default_metrics_configs(default_file['metrics'])
24
+ end
25
+
26
+ def load_user_file
27
+ user_file = load_yml_file(File.realpath(FILE_NAME))
28
+ return if user_file.nil?
29
+ @user_data = @default_data.merge(user_file['config'])
30
+ @user_data[:metrics_configs] = []
31
+ load_user_metrics_configs(user_file['metrics']) unless user_file['metrics'].nil?
32
+ end
33
+
34
+ def load_yml_file(path)
35
+ return unless File.exist?(path)
36
+ yaml_code = IO.read(path, encoding: 'UTF-8')
37
+ YAML.load(ERB.new(yaml_code).result)
38
+ end
39
+
40
+ def load_default_metrics_configs(metrics)
41
+ metrics.each do |metric, metric_data|
42
+ @default_data[:metrics_configs] << MetricConfig.new(
43
+ metric_data.merge('metric' => metric)
44
+ )
45
+ end
46
+ end
47
+
48
+ def load_user_metrics_configs(user_metrics)
49
+ @default_data[:metrics_configs].each do |metric_default_config|
50
+ user_metric_data = user_metrics[metric_default_config.data['metric']]
51
+ next unless metric_enabled?(user_metric_data)
52
+ @user_data[:metrics_configs] << MetricConfig.new(
53
+ metric_default_config.data.merge(user_metric_data)
54
+ )
55
+ end
56
+ end
57
+
58
+ def metric_enabled?(user_metric_data)
59
+ !user_metric_data.nil? && user_metric_data['enabled']
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,16 @@
1
+ module CodeStats
2
+ module Metrics
3
+ module Reporter
4
+ class ConfigStore
5
+ attr_reader :metrics_configs, :ci, :token, :url
6
+ def initialize
7
+ configs = ConfigLoader.load_file
8
+ @metrics_configs = configs[:metrics_configs]
9
+ @ci = configs['ci']
10
+ @token = configs['token']
11
+ @url = configs['url']
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,59 @@
1
+ require 'json'
2
+
3
+ module CodeStats
4
+ module Metrics
5
+ module Reporter
6
+ class Escomplex
7
+ class << self
8
+ MAINTAINABILITY_MAX = 171.0
9
+ def generate_data(metric, config_store)
10
+ @config_store = config_store
11
+ @metric = metric
12
+ {
13
+ metric_name: @metric.data['name'],
14
+ minimum: @metric.data['minimum'],
15
+ value: maintainability,
16
+ url: url
17
+ }
18
+ end
19
+
20
+ private
21
+
22
+ def url
23
+ return if invalid_url_params?
24
+ "#{build_base_url}/#{repository_name}/#{build_identifier}/#{build_report_file_url}"
25
+ end
26
+
27
+ def invalid_url_params?
28
+ build_base_url.nil? ||
29
+ build_identifier.nil? ||
30
+ repository_name.nil? ||
31
+ build_report_file_url.nil?
32
+ end
33
+
34
+ def build_base_url
35
+ @metric.data['build_base_url']
36
+ end
37
+
38
+ def build_report_file_url
39
+ @metric.data['build_report_file_url']
40
+ end
41
+
42
+ def build_identifier
43
+ Ci.data(@config_store.ci)[:build_identifier]
44
+ end
45
+
46
+ def repository_name
47
+ Ci.data(@config_store.ci)[:repository_name]
48
+ end
49
+
50
+ # Maintanibility is measured from 0 to MAINTAINABILITY_MAX. higher is better
51
+ def maintainability
52
+ json = JSON.parse(File.read(@metric.data['location']))
53
+ json['maintainability'] * 100 / MAINTAINABILITY_MAX
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,57 @@
1
+ require 'json'
2
+
3
+ module CodeStats
4
+ module Metrics
5
+ module Reporter
6
+ class KarmaCoverage
7
+ class << self
8
+ def generate_data(metric, config_store)
9
+ @metric = metric
10
+ @config_store = config_store
11
+ {
12
+ metric_name: @metric.data['name'],
13
+ value: parse_coverage,
14
+ minimum: @metric.data['minimum'],
15
+ url: url
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ def parse_coverage
22
+ xml = File.read(@metric.data['location'])
23
+ Oga.parse_xml(xml).xpath('coverage')[0].get('line-rate').to_f * 100
24
+ end
25
+
26
+ def url
27
+ return if invalid_url_params?
28
+ "#{build_base_url}/#{repository_name}/#{build_identifier}/#{build_report_file_url}"
29
+ end
30
+
31
+ def invalid_url_params?
32
+ build_base_url.nil? ||
33
+ build_identifier.nil? ||
34
+ repository_name.nil? ||
35
+ build_report_file_url.nil?
36
+ end
37
+
38
+ def build_base_url
39
+ @metric.data['build_base_url']
40
+ end
41
+
42
+ def build_report_file_url
43
+ @metric.data['build_report_file_url']
44
+ end
45
+
46
+ def build_identifier
47
+ Ci.data(@config_store.ci)[:build_identifier]
48
+ end
49
+
50
+ def repository_name
51
+ Ci.data(@config_store.ci)[:repository_name]
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,12 @@
1
+ module CodeStats
2
+ module Metrics
3
+ module Reporter
4
+ class MetricConfig
5
+ attr_reader :data
6
+ def initialize(args = {})
7
+ @data = args
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,65 @@
1
+ require 's3_uploader'
2
+ require 'pathname'
3
+
4
+ module CodeStats
5
+ module Metrics
6
+ module Reporter
7
+ class Rubycritic
8
+ class << self
9
+ def generate_data(metric, config_store)
10
+ @config_store = config_store
11
+ @metric = metric
12
+ return if upload_report? && !valid_uploader_data?
13
+ {
14
+ metric_name: @metric.data['name'],
15
+ value: generate_score_file['score'],
16
+ url: (uploader_url if upload_report?),
17
+ minimum: @metric.data['minimum']
18
+ }
19
+ end
20
+
21
+ private
22
+
23
+ def uploader_url
24
+ "https://s3.amazonaws.com/#{bucket}/rubycritic/#{project}/#{branch}/overview.html"
25
+ end
26
+
27
+ def generate_score_file
28
+ base_dir = Pathname.new(@metric.data['report_dir'])
29
+ build_uploader.upload(File.realpath(base_dir).to_s, bucket) if upload_report?
30
+ JSON.parse(File.read(base_dir.join('report.json')))
31
+ end
32
+
33
+ def upload_report?
34
+ @metric.data['upload_report']
35
+ end
36
+
37
+ def valid_uploader_data?
38
+ %w(uploader_key uploader_secret uploader_region uploader_bucket).all? do |value|
39
+ !@metric.data[value].nil?
40
+ end
41
+ end
42
+
43
+ def build_uploader
44
+ S3Uploader::Uploader.new(s3_key: @metric.data['uploader_key'],
45
+ s3_secret: @metric.data['uploader_secret'],
46
+ destination_dir: "rubycritic/#{project}/#{branch}",
47
+ region: @metric.data['uploader_region'])
48
+ end
49
+
50
+ def project
51
+ Ci.data(@config_store.ci)[:repository_name]
52
+ end
53
+
54
+ def branch
55
+ Ci.data(@config_store.ci)[:branch]
56
+ end
57
+
58
+ def bucket
59
+ @metric.data['uploader_bucket']
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,21 @@
1
+ require 'json'
2
+
3
+ module CodeStats
4
+ module Metrics
5
+ module Reporter
6
+ class Simplecov
7
+ class << self
8
+ def generate_data(metric, _config_store)
9
+ json = JSON.parse(File.read('coverage/.last_run.json'))
10
+ code_coverage = json['result']['covered_percent']
11
+ {
12
+ metric_name: metric.data['name'],
13
+ value: code_coverage,
14
+ minimum: metric.data['minimum']
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,73 @@
1
+ require 's3_uploader'
2
+ require 'pathname'
3
+ require 'oga'
4
+
5
+ module CodeStats
6
+ module Metrics
7
+ module Reporter
8
+ class Slather
9
+ class << self
10
+ def generate_data(metric, config_store)
11
+ @config_store = config_store
12
+ @metric = metric
13
+ return if upload_report? && !valid_uploader_data?
14
+ {
15
+ metric_name: @metric.data['name'],
16
+ value: generate_score_file,
17
+ url: (uploader_url if upload_report?),
18
+ minimum: @metric.data['minimum']
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ def uploader_url
25
+ "https://s3.amazonaws.com/#{bucket}/slather/#{project}/#{id}/index.html"
26
+ end
27
+
28
+ def generate_score_file
29
+ base_dir = Pathname.new(@metric.data['report_dir'])
30
+ build_uploader.upload(File.realpath(base_dir).to_s, bucket) if upload_report?
31
+ html = File.read(base_dir.join('index.html'))
32
+ parse_coverage(html)
33
+ end
34
+
35
+ def upload_report?
36
+ @metric.data['upload_report']
37
+ end
38
+
39
+ def parse_coverage(html)
40
+ coverage_header = Oga.parse_html(html).xpath('html/body/div/div/h4').text
41
+ total_coverage = coverage_header.match(/\d+.\d+/)
42
+ total_coverage ? total_coverage[0].to_f : 0.0
43
+ end
44
+
45
+ def valid_uploader_data?
46
+ %w(uploader_key uploader_secret uploader_region uploader_bucket).all? do |value|
47
+ !@metric.data[value].nil?
48
+ end
49
+ end
50
+
51
+ def build_uploader
52
+ S3Uploader::Uploader.new(s3_key: @metric.data['uploader_key'],
53
+ s3_secret: @metric.data['uploader_secret'],
54
+ destination_dir: "slather/#{project}/#{id}",
55
+ region: @metric.data['uploader_region'])
56
+ end
57
+
58
+ def project
59
+ Ci.data(@config_store.ci)[:repository_name]
60
+ end
61
+
62
+ def id
63
+ Ci.data(@config_store.ci)[:branch] || Ci.data(@config_store.ci)[:pull_request]
64
+ end
65
+
66
+ def bucket
67
+ @metric.data['uploader_bucket']
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,7 @@
1
+ module CodeStats
2
+ module Metrics
3
+ module Reporter
4
+ VERSION = '0.1.0'.freeze
5
+ end
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,188 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: codestats-metrics-reporter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Esteban Pintos
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0.8'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: httparty
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: s3_uploader
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: oga
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Gem that will let you control your code quality by reporting custom metrics
126
+ to [CodeStats](https://github.com/Wolox/codestats)
127
+ email:
128
+ - esteban.pintos@wolox.com.ar
129
+ executables:
130
+ - codestats-metrics-reporter
131
+ - console
132
+ - setup
133
+ extensions: []
134
+ extra_rdoc_files: []
135
+ files:
136
+ - ".gitignore"
137
+ - ".rspec"
138
+ - ".rubocop.yml"
139
+ - ".travis.yml"
140
+ - Gemfile
141
+ - LICENSE
142
+ - README.md
143
+ - Rakefile
144
+ - bin/codestats-metrics-reporter
145
+ - bin/console
146
+ - bin/setup
147
+ - codestats-metrics-reporter.gemspec
148
+ - config/default.yml
149
+ - lib/code_stats/metrics/reporter.rb
150
+ - lib/code_stats/metrics/reporter/android_jacoco.rb
151
+ - lib/code_stats/metrics/reporter/android_quality.rb
152
+ - lib/code_stats/metrics/reporter/ci.rb
153
+ - lib/code_stats/metrics/reporter/cli.rb
154
+ - lib/code_stats/metrics/reporter/config_loader.rb
155
+ - lib/code_stats/metrics/reporter/config_store.rb
156
+ - lib/code_stats/metrics/reporter/escomplex.rb
157
+ - lib/code_stats/metrics/reporter/karma_coverage.rb
158
+ - lib/code_stats/metrics/reporter/metric_config.rb
159
+ - lib/code_stats/metrics/reporter/rubycritic.rb
160
+ - lib/code_stats/metrics/reporter/simplecov.rb
161
+ - lib/code_stats/metrics/reporter/slather.rb
162
+ - lib/code_stats/metrics/reporter/version.rb
163
+ homepage: https://github.com/Wolox/codestats-metrics-reporter
164
+ licenses:
165
+ - MIT
166
+ metadata: {}
167
+ post_install_message:
168
+ rdoc_options: []
169
+ require_paths:
170
+ - lib
171
+ required_ruby_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ required_rubygems_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ requirements: []
182
+ rubyforge_project:
183
+ rubygems_version: 2.2.2
184
+ signing_key:
185
+ specification_version: 4
186
+ summary: Report metrics to CodeStats
187
+ test_files: []
188
+ has_rdoc: