ossert 0.1.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 +7 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.rubocop_todo.yml +44 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +199 -0
- data/Rakefile +12 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config/classifiers.yml +153 -0
- data/config/descriptions.yml +45 -0
- data/config/sidekiq.rb +15 -0
- data/config/stats.yml +198 -0
- data/config/translations.yml +44 -0
- data/db/backups/.keep +0 -0
- data/db/migrate/001_create_projects.rb +22 -0
- data/db/migrate/002_create_exceptions.rb +14 -0
- data/db/migrate/003_add_meta_to_projects.rb +14 -0
- data/db/migrate/004_add_timestamps_to_projects.rb +12 -0
- data/db/migrate/005_create_classifiers.rb +19 -0
- data/lib/ossert/classifiers/decision_tree.rb +112 -0
- data/lib/ossert/classifiers/growing/check.rb +172 -0
- data/lib/ossert/classifiers/growing/classifier.rb +175 -0
- data/lib/ossert/classifiers/growing.rb +163 -0
- data/lib/ossert/classifiers.rb +14 -0
- data/lib/ossert/config.rb +24 -0
- data/lib/ossert/fetch/bestgems.rb +98 -0
- data/lib/ossert/fetch/github.rb +536 -0
- data/lib/ossert/fetch/rubygems.rb +80 -0
- data/lib/ossert/fetch.rb +142 -0
- data/lib/ossert/presenters/project.rb +202 -0
- data/lib/ossert/presenters/project_v2.rb +117 -0
- data/lib/ossert/presenters.rb +8 -0
- data/lib/ossert/project.rb +144 -0
- data/lib/ossert/quarters_store.rb +164 -0
- data/lib/ossert/rake_tasks.rb +6 -0
- data/lib/ossert/reference.rb +87 -0
- data/lib/ossert/repositories.rb +138 -0
- data/lib/ossert/saveable.rb +153 -0
- data/lib/ossert/stats/agility_quarter.rb +62 -0
- data/lib/ossert/stats/agility_total.rb +71 -0
- data/lib/ossert/stats/base.rb +113 -0
- data/lib/ossert/stats/community_quarter.rb +28 -0
- data/lib/ossert/stats/community_total.rb +24 -0
- data/lib/ossert/stats.rb +32 -0
- data/lib/ossert/tasks/database.rake +179 -0
- data/lib/ossert/tasks/ossert.rake +52 -0
- data/lib/ossert/version.rb +4 -0
- data/lib/ossert/workers/fetch.rb +21 -0
- data/lib/ossert/workers/fetch_bestgems_page.rb +32 -0
- data/lib/ossert/workers/refresh_fetch.rb +22 -0
- data/lib/ossert/workers/sync_rubygems.rb +0 -0
- data/lib/ossert/workers.rb +11 -0
- data/lib/ossert.rb +63 -0
- data/ossert.gemspec +47 -0
- metadata +396 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Ossert
|
3
|
+
module Stats
|
4
|
+
class Base
|
5
|
+
class << self
|
6
|
+
attr_accessor :section, :section_type
|
7
|
+
|
8
|
+
def config
|
9
|
+
@config ||= ::Settings['stats'][section][section_type]
|
10
|
+
end
|
11
|
+
|
12
|
+
def attributes
|
13
|
+
@attributes ||= config['attributes']
|
14
|
+
end
|
15
|
+
|
16
|
+
def uniq_attributes
|
17
|
+
@uniq_attributes ||= config['uniq_attributes'].to_a
|
18
|
+
end
|
19
|
+
|
20
|
+
def absolute_attributes
|
21
|
+
@absolute_attributes ||= config['absolute_attributes'].to_a
|
22
|
+
end
|
23
|
+
|
24
|
+
def attributes_names
|
25
|
+
@attributes_names ||= attributes.keys
|
26
|
+
end
|
27
|
+
|
28
|
+
def metrics
|
29
|
+
@metrics ||= config['metrics']
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_attributes_accessors
|
33
|
+
attr_accessor(*attributes_names)
|
34
|
+
end
|
35
|
+
|
36
|
+
def define_ints(*attributes)
|
37
|
+
Array.wrap(attributes).each do |metric|
|
38
|
+
define_method("#{metric}_int") { public_send(metric).to_i }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def define_counts(*attributes)
|
43
|
+
Array.wrap(attributes).each do |metric|
|
44
|
+
define_method("#{metric}_count") { public_send(metric).count }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def define_percent(attributes, default_value: 0)
|
49
|
+
attributes.to_h.each do |metric, total|
|
50
|
+
define_method("#{metric}_percent") do
|
51
|
+
total_count = public_send(total).count
|
52
|
+
return default_value if total_count.zero?
|
53
|
+
|
54
|
+
((public_send(metric).count.to_d / total_count.to_d) * 100).round(2)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def <<(other_stats)
|
61
|
+
self.class.attributes_names.each do |attr|
|
62
|
+
next unless other_value = other_stats.instance_variable_get("@#{attr}")
|
63
|
+
new_value = other_value
|
64
|
+
new_value += instance_variable_get("@#{attr}") unless self.class.absolute_attributes.include?(attr)
|
65
|
+
new_value.uniq! if self.class.uniq_attributes.include?(attr)
|
66
|
+
|
67
|
+
instance_variable_set("@#{attr}", new_value)
|
68
|
+
end
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize
|
73
|
+
self.class.attributes.each do |var, type|
|
74
|
+
instance_variable_set("@#{var}", Kernel.const_get(type).new) if type
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def median(values, default_value: 0)
|
79
|
+
values = Array(values).sort
|
80
|
+
return default_value if (count = values.count).zero?
|
81
|
+
|
82
|
+
middle_idx = values.count / 2
|
83
|
+
return values[middle_idx] if count.odd?
|
84
|
+
|
85
|
+
(values[middle_idx - 1] + values[middle_idx]) / 2
|
86
|
+
end
|
87
|
+
|
88
|
+
def metric_values
|
89
|
+
self.class.metrics.map { |metric| public_send(metric).to_f }
|
90
|
+
end
|
91
|
+
|
92
|
+
def metrics_to_hash
|
93
|
+
self.class.metrics.each_with_object({}) do |var, result|
|
94
|
+
value = send(var)
|
95
|
+
value.uniq! if self.class.uniq_attributes.include?(var)
|
96
|
+
result[var] = value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_hash
|
101
|
+
self.class.attributes_names.each_with_object({}) do |var, result|
|
102
|
+
value = send(var)
|
103
|
+
value.uniq! if self.class.uniq_attributes.include?(var)
|
104
|
+
result[var] = value
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_json
|
109
|
+
MultiJson.dump(self)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Ossert
|
3
|
+
module Stats
|
4
|
+
class CommunityQuarter < Base
|
5
|
+
self.section = 'community'
|
6
|
+
self.section_type = 'quarter'
|
7
|
+
create_attributes_accessors
|
8
|
+
|
9
|
+
define_counts(
|
10
|
+
:users_creating_issues, :users_commenting_issues, :users_creating_pr,
|
11
|
+
:users_commenting_pr, :contributors, :stargazers, :forks,
|
12
|
+
:users_involved
|
13
|
+
)
|
14
|
+
|
15
|
+
def users_involved_no_stars_count
|
16
|
+
(users_involved - stargazers).count
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_downloads_count
|
20
|
+
delta_downloads
|
21
|
+
end
|
22
|
+
|
23
|
+
def download_divergence
|
24
|
+
(delta_downloads.to_f / (1 + total_downloads.to_f)) * 100.0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Ossert
|
3
|
+
module Stats
|
4
|
+
class CommunityTotal < Base
|
5
|
+
self.section = 'community'
|
6
|
+
self.section_type = 'total'
|
7
|
+
create_attributes_accessors
|
8
|
+
|
9
|
+
define_counts(
|
10
|
+
:users_creating_issues, :users_commenting_issues, :users_creating_pr,
|
11
|
+
:users_commenting_pr, :contributors, :watchers, :stargazers, :forks,
|
12
|
+
:users_involved, :dependants
|
13
|
+
)
|
14
|
+
|
15
|
+
def users_involved_no_stars_count
|
16
|
+
(users_involved - stargazers).count
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_downloads_count
|
20
|
+
total_downloads
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/ossert/stats.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'ossert/stats/base'
|
3
|
+
require 'ossert/stats/agility_total'
|
4
|
+
require 'ossert/stats/agility_quarter'
|
5
|
+
require 'ossert/stats/community_total'
|
6
|
+
require 'ossert/stats/community_quarter'
|
7
|
+
|
8
|
+
module Ossert
|
9
|
+
module Stats
|
10
|
+
PER_QUARTER_TOO_LONG = (5.years / 1.day).to_i
|
11
|
+
PER_YEAR_TOO_LONG = PER_QUARTER_TOO_LONG / 4
|
12
|
+
|
13
|
+
def guess_section_by_metric(metric)
|
14
|
+
found_section = :not_found
|
15
|
+
section_by_metric.each do |section, metrics|
|
16
|
+
next unless metrics.include? metric
|
17
|
+
found_section = section
|
18
|
+
break
|
19
|
+
end
|
20
|
+
found_section
|
21
|
+
end
|
22
|
+
module_function :guess_section_by_metric
|
23
|
+
|
24
|
+
def section_by_metric
|
25
|
+
@section_by_metric ||= {
|
26
|
+
agility: AgilityTotal.metrics + AgilityQuarter.metrics,
|
27
|
+
community: CommunityTotal.metrics + CommunityQuarter.metrics
|
28
|
+
}
|
29
|
+
end
|
30
|
+
module_function :section_by_metric
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
namespace :db do
|
3
|
+
require 'sequel'
|
4
|
+
Sequel.extension :migration
|
5
|
+
|
6
|
+
namespace :test do
|
7
|
+
task :prepare do
|
8
|
+
test_database_url = ENV.fetch('TEST_DATABASE_URL')
|
9
|
+
database_name = test_database_url.split('/').last
|
10
|
+
|
11
|
+
DB = Sequel.connect(test_database_url)
|
12
|
+
|
13
|
+
sh "dropdb #{database_name}" do
|
14
|
+
# Ignore errors
|
15
|
+
end
|
16
|
+
|
17
|
+
sh "createdb #{database_name}" do
|
18
|
+
# Ignore errors
|
19
|
+
end
|
20
|
+
|
21
|
+
Sequel::Migrator.run(DB, File.expand_path('../../../../db/migrate', __FILE__))
|
22
|
+
Rake::Task['db:version'].execute
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
task :migrate => :load_config do
|
27
|
+
Sequel::Migrator.run(DB, File.expand_path('../../../../db/migrate', __FILE__))
|
28
|
+
Rake::Task['db:version'].execute
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'Prints current schema version'
|
32
|
+
task :version do
|
33
|
+
version = if DB.tables.include?(:schema_info)
|
34
|
+
DB[:schema_info].first[:version]
|
35
|
+
end || 0
|
36
|
+
|
37
|
+
puts "Schema Version: #{version}"
|
38
|
+
end
|
39
|
+
|
40
|
+
task :load_config do
|
41
|
+
DB = Sequel.connect(ENV.fetch('DATABASE_URL'))
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'Create the database, load the schema, and initialize with the seed data (db:reset to also drop the db first)'
|
45
|
+
task :setup do
|
46
|
+
Rake::Task['db:create'].invoke
|
47
|
+
Rake::Task['db:load_config'].invoke
|
48
|
+
|
49
|
+
Sequel::Migrator.run(DB, File.expand_path('../../../../db/migrate', __FILE__))
|
50
|
+
Rake::Task['db:version'].execute
|
51
|
+
end
|
52
|
+
|
53
|
+
task :drop do
|
54
|
+
sh "dropdb #{ENV.fetch('DATABASE_URL').split('/').last}" do
|
55
|
+
# Ignore errors
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
task :create do
|
60
|
+
sh "createdb #{ENV.fetch('DATABASE_URL').split('/').last}" do
|
61
|
+
# Ignore errors
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
desc 'Dumps the database to backups'
|
66
|
+
task :dump, [:fmt] do |_, args|
|
67
|
+
dump_fmt = args.fmt || 'c' # or 'p', 't', 'd'
|
68
|
+
dump_sfx = suffix_for_format dump_fmt
|
69
|
+
backup_dir = backup_directory true
|
70
|
+
cmd = nil
|
71
|
+
with_config do |app, db_url|
|
72
|
+
file_name = Time.now.strftime('%Y%m%d%H%M%S') + '_' + app + '_db.' + dump_sfx
|
73
|
+
cmd = "pg_dump #{db_url} --no-owner --no-acl -F #{dump_fmt} -v -f #{backup_dir}/#{file_name}"
|
74
|
+
end
|
75
|
+
puts cmd
|
76
|
+
sh cmd do
|
77
|
+
# Ignore errors
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
desc 'Show the existing database backups'
|
82
|
+
task :list_backups do
|
83
|
+
backup_dir = backup_directory
|
84
|
+
puts backup_dir.to_s
|
85
|
+
exec "/bin/ls -lht #{backup_dir}"
|
86
|
+
end
|
87
|
+
|
88
|
+
desc 'Restores the database from a backup using PATTERN'
|
89
|
+
task :restore, [:pat] do |_, args|
|
90
|
+
puts 'Please pass a pattern to the task' unless args.pat.present?
|
91
|
+
cmd = nil
|
92
|
+
with_config do |_, db_url|
|
93
|
+
cmd = command_for_files args.pat, db_url
|
94
|
+
end
|
95
|
+
unless cmd.nil?
|
96
|
+
Rake::Task['db:drop'].invoke
|
97
|
+
Rake::Task['db:create'].invoke
|
98
|
+
puts cmd
|
99
|
+
exec "#{cmd} || exit 0"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def command_for_files(pattern, db_url)
|
104
|
+
files = Dir.glob("#{backup_directory}/*#{pattern}*")
|
105
|
+
case files.size
|
106
|
+
when 0
|
107
|
+
puts "No backups found for the pattern '#{pattern}'"
|
108
|
+
when 1
|
109
|
+
command_for_file files.first, db_url
|
110
|
+
else
|
111
|
+
puts "Too many files match the pattern '#{pattern}': #{files.join("\n ")} "
|
112
|
+
puts 'Try a more specific pattern'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def command_for_file(file, db_url)
|
117
|
+
return puts("No recognized dump file suffix: #{file}") unless (fmt = format_for_file(file)).present?
|
118
|
+
"pg_restore -d '#{db_url}' -F #{fmt} -v -c #{file}"
|
119
|
+
end
|
120
|
+
|
121
|
+
namespace :restore do
|
122
|
+
desc 'Restores the database from latest backup'
|
123
|
+
task :last do
|
124
|
+
cmd = nil
|
125
|
+
with_config do |_, db_url|
|
126
|
+
file = Dir.glob("#{backup_directory}/*").max_by { |f| File.mtime(f) }
|
127
|
+
if file
|
128
|
+
fmt = format_for_file file
|
129
|
+
if fmt.nil?
|
130
|
+
puts "No recognized dump file suffix: #{file}"
|
131
|
+
else
|
132
|
+
cmd = "pg_restore -d '#{db_url}' -F #{fmt} -v #{file}"
|
133
|
+
end
|
134
|
+
else
|
135
|
+
puts 'No backups found'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
unless cmd.nil?
|
139
|
+
Rake::Task['db:drop'].invoke
|
140
|
+
Rake::Task['db:create'].invoke
|
141
|
+
puts cmd
|
142
|
+
exec "#{cmd} || exit 0"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def suffix_for_format(suffix)
|
150
|
+
case suffix
|
151
|
+
when 'c' then 'dump'
|
152
|
+
when 'p' then 'sql'
|
153
|
+
when 't' then 'tar'
|
154
|
+
when 'd' then 'dir'
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def format_for_file(file)
|
159
|
+
case file
|
160
|
+
when /\.dump$/ then 'c'
|
161
|
+
when /\.sql$/ then 'p'
|
162
|
+
when /\.dir$/ then 'd'
|
163
|
+
when /\.tar$/ then 't'
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def backup_directory(create = false)
|
168
|
+
backup_dir = 'db/backups'
|
169
|
+
if create && !Dir.exist?(backup_dir)
|
170
|
+
puts "Creating #{backup_dir} .."
|
171
|
+
FileUtils.mkdir_p(backup_dir)
|
172
|
+
end
|
173
|
+
backup_dir
|
174
|
+
end
|
175
|
+
|
176
|
+
def with_config
|
177
|
+
yield 'ossert', ENV.fetch('DATABASE_URL')
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
namespace :ossert do
|
3
|
+
namespace :cache do
|
4
|
+
desc 'Reset data cache'
|
5
|
+
task :reset do
|
6
|
+
::Ossert.init
|
7
|
+
::Classifier.dataset.delete
|
8
|
+
Ossert::Classifiers::Growing.new.train
|
9
|
+
true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Collect reference projects'
|
14
|
+
task :collect_referencies do
|
15
|
+
puts 'Run collecting process'
|
16
|
+
time = Benchmark.realtime do
|
17
|
+
::Ossert.init
|
18
|
+
::Project.db.transaction do
|
19
|
+
Ossert::Project.cleanup_referencies!
|
20
|
+
reference_projects = Ossert::Reference.prepare_projects!
|
21
|
+
Ossert::Reference.process_references(reference_projects)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
puts "Collecting process finished in #{time.round(3)} sec."
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'Invoke data updates for stale projects'
|
29
|
+
task :refresh_data do
|
30
|
+
require './config/sidekiq.rb'
|
31
|
+
Ossert::Workers::RefreshFetch.perform_async
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Add or replace project name exception'
|
35
|
+
task :exception, [:name, :github_name] do |_, args|
|
36
|
+
raise 'Arguments name and GitHub name expected' unless args.name.present? && args.github_name.present?
|
37
|
+
exceptions_repo = ExceptionsRepo.new(Ossert.rom)
|
38
|
+
if exceptions_repo[args.name]
|
39
|
+
exceptions_repo.update(
|
40
|
+
args.name,
|
41
|
+
name: args.name,
|
42
|
+
github_name: args.github_name
|
43
|
+
)
|
44
|
+
else
|
45
|
+
exceptions_repo.create(
|
46
|
+
name: args.name,
|
47
|
+
github_name: args.github_name
|
48
|
+
)
|
49
|
+
end
|
50
|
+
puts "Exception '#{args.name}' => '#{args.github_name}' saved!"
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Ossert
|
3
|
+
module Workers
|
4
|
+
class Fetch
|
5
|
+
include Sidekiq::Worker
|
6
|
+
include Process
|
7
|
+
sidekiq_options unique: :until_executed,
|
8
|
+
unique_expiration: 1.hour,
|
9
|
+
retry: 3
|
10
|
+
|
11
|
+
def perform(name, reference = Ossert::Saveable::UNUSED_REFERENCE)
|
12
|
+
puts "Fetching data for: '#{name}' (ref: #{reference})"
|
13
|
+
pid = fork do
|
14
|
+
Ossert.init
|
15
|
+
Ossert::Project.fetch_all(name, reference)
|
16
|
+
end
|
17
|
+
waitpid(pid)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Ossert
|
3
|
+
module Workers
|
4
|
+
class FetchBestgemsPage
|
5
|
+
include Sidekiq::Worker
|
6
|
+
include Process
|
7
|
+
|
8
|
+
sidekiq_options retry: 3,
|
9
|
+
unique: :until_executed
|
10
|
+
|
11
|
+
def perform(pages, type = :total)
|
12
|
+
pages = Array(pages)
|
13
|
+
bestgems_page_processor = Kernel.const_get(
|
14
|
+
"Ossert::Fetch::Bestgems#{type.to_s.capitalize}Stat"
|
15
|
+
)
|
16
|
+
pages.each do |page|
|
17
|
+
puts "Processing Bestgems page: '#{page}'"
|
18
|
+
bestgems_page_processor.process_page(page) do |_, _, gem_name|
|
19
|
+
puts "Processing project: '#{gem_name}'"
|
20
|
+
pid = fork do
|
21
|
+
Ossert.init
|
22
|
+
next(puts("Skipping project: '#{gem_name}'")) if Ossert::Project.exist?(gem_name)
|
23
|
+
Ossert::Project.fetch_all(gem_name)
|
24
|
+
sleep(10)
|
25
|
+
end
|
26
|
+
waitpid(pid)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Ossert
|
3
|
+
module Workers
|
4
|
+
class RefreshFetch
|
5
|
+
include Sidekiq::Worker
|
6
|
+
include Process
|
7
|
+
sidekiq_options unique: :until_executed,
|
8
|
+
unique_expiration: 1.hour,
|
9
|
+
retry: 3
|
10
|
+
|
11
|
+
def perform
|
12
|
+
pid = fork do
|
13
|
+
Ossert.init
|
14
|
+
::Project.select(:name, :reference).where('updated_at < ?', 1.week.ago).paged_each do |project|
|
15
|
+
Ossert::Workers::Fetch.perform_async(project.name, project.reference)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
waitpid(pid)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
File without changes
|
data/lib/ossert.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'ossert/version'
|
3
|
+
require 'sequel'
|
4
|
+
require 'active_support/all' # remove later, we use only quarters and index_by here
|
5
|
+
require 'json'
|
6
|
+
require 'oj'
|
7
|
+
|
8
|
+
require 'ossert/config'
|
9
|
+
Ossert::Config.load(:stats, :classifiers, :translations, :descriptions)
|
10
|
+
|
11
|
+
require 'ossert/stats'
|
12
|
+
require 'ossert/quarters_store'
|
13
|
+
require 'ossert/saveable'
|
14
|
+
require 'ossert/presenters'
|
15
|
+
require 'ossert/project'
|
16
|
+
require 'ossert/fetch'
|
17
|
+
require 'ossert/reference'
|
18
|
+
require 'ossert/classifiers'
|
19
|
+
require 'ossert/workers'
|
20
|
+
|
21
|
+
module Ossert
|
22
|
+
def init(database_url = nil)
|
23
|
+
Sequel.connect(database_url || ENV.fetch('DATABASE_URL'))
|
24
|
+
require 'ossert/repositories'
|
25
|
+
end
|
26
|
+
module_function :init
|
27
|
+
|
28
|
+
def description(key)
|
29
|
+
descriptions.fetch(key.to_s, "Description for '#{key}' - not found")
|
30
|
+
end
|
31
|
+
alias descr description
|
32
|
+
module_function :descr
|
33
|
+
|
34
|
+
def descriptions
|
35
|
+
@descriptions ||= ::Settings['descriptions']
|
36
|
+
end
|
37
|
+
module_function :descriptions
|
38
|
+
|
39
|
+
def translate(key)
|
40
|
+
translations.fetch(key.to_s, "Translation for '#{key}' - not found")
|
41
|
+
end
|
42
|
+
alias t translate
|
43
|
+
module_function :t
|
44
|
+
|
45
|
+
def translations
|
46
|
+
@translations ||= ::Settings['translations']
|
47
|
+
end
|
48
|
+
module_function :translations
|
49
|
+
|
50
|
+
NO_GITHUB_NAME = '__unknown__'
|
51
|
+
|
52
|
+
class TooLong
|
53
|
+
def self.new
|
54
|
+
Ossert::Stats::PER_YEAR_TOO_LONG
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Zero
|
59
|
+
def self.new
|
60
|
+
0
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/ossert.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'ossert/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'ossert'
|
9
|
+
spec.version = Ossert::VERSION
|
10
|
+
spec.authors = ['Sergey Dolganov']
|
11
|
+
spec.email = ['sclinede@gmail.com']
|
12
|
+
|
13
|
+
spec.summary = 'Write a short summary, because Rubygems requires one.'
|
14
|
+
spec.description = 'Write a longer description or delete this line.'
|
15
|
+
spec.homepage = 'https://github.com/sclinede/ossert'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_runtime_dependency 'octokit', '~> 4.0'
|
24
|
+
|
25
|
+
spec.add_runtime_dependency 'redis'
|
26
|
+
spec.add_runtime_dependency 'redis-namespace'
|
27
|
+
spec.add_runtime_dependency 'sidekiq'
|
28
|
+
spec.add_runtime_dependency 'sidekiq-unique-jobs'
|
29
|
+
|
30
|
+
spec.add_runtime_dependency 'activesupport' # TODO: remove dependency later
|
31
|
+
spec.add_runtime_dependency 'oj'
|
32
|
+
spec.add_runtime_dependency 'multi_json'
|
33
|
+
spec.add_runtime_dependency 'sequel'
|
34
|
+
spec.add_runtime_dependency 'pg'
|
35
|
+
spec.add_runtime_dependency 'nokogiri'
|
36
|
+
spec.add_runtime_dependency 'graphr'
|
37
|
+
spec.add_runtime_dependency 'rake', '~> 10.0'
|
38
|
+
|
39
|
+
spec.add_development_dependency 'memory_profiler'
|
40
|
+
spec.add_development_dependency 'rubocop'
|
41
|
+
spec.add_development_dependency 'bundler', '~> 1.11'
|
42
|
+
spec.add_development_dependency 'pry'
|
43
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
44
|
+
spec.add_development_dependency 'simplecov'
|
45
|
+
spec.add_development_dependency 'webmock'
|
46
|
+
spec.add_development_dependency 'vcr'
|
47
|
+
end
|