foreman_statistics 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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +619 -0
  3. data/README.md +40 -0
  4. data/Rakefile +47 -0
  5. data/app/controllers/concerns/foreman_statistics/parameters/trend.rb +20 -0
  6. data/app/controllers/foreman_statistics/api/v2/statistics_controller.rb +33 -0
  7. data/app/controllers/foreman_statistics/api/v2/trends_controller.rb +58 -0
  8. data/app/controllers/foreman_statistics/react_controller.rb +19 -0
  9. data/app/controllers/foreman_statistics/statistics_controller.rb +24 -0
  10. data/app/controllers/foreman_statistics/trends_controller.rb +58 -0
  11. data/app/helpers/foreman_statistics/trends_helper.rb +53 -0
  12. data/app/models/concerns/foreman_statistics/compute_resource_decorations.rb +9 -0
  13. data/app/models/concerns/foreman_statistics/environment_decorations.rb +9 -0
  14. data/app/models/concerns/foreman_statistics/general_setting_decorations.rb +17 -0
  15. data/app/models/concerns/foreman_statistics/hostgroup_decorations.rb +9 -0
  16. data/app/models/concerns/foreman_statistics/model_decorations.rb +9 -0
  17. data/app/models/concerns/foreman_statistics/operatingsystem_decorations.rb +9 -0
  18. data/app/models/concerns/foreman_statistics/setting_decorations.rb +9 -0
  19. data/app/models/foreman_statistics/fact_trend.rb +57 -0
  20. data/app/models/foreman_statistics/foreman_trend.rb +41 -0
  21. data/app/models/foreman_statistics/trend.rb +38 -0
  22. data/app/models/foreman_statistics/trend_counter.rb +11 -0
  23. data/app/services/foreman_statistics/statistics.rb +21 -0
  24. data/app/services/foreman_statistics/statistics/base.rb +39 -0
  25. data/app/services/foreman_statistics/statistics/count_facts.rb +17 -0
  26. data/app/services/foreman_statistics/statistics/count_hosts.rb +9 -0
  27. data/app/services/foreman_statistics/statistics/count_numerical_fact_pair.rb +43 -0
  28. data/app/services/foreman_statistics/statistics/count_puppet_classes.rb +23 -0
  29. data/app/services/foreman_statistics/trend_importer.rb +63 -0
  30. data/app/views/foreman_statistics/api/v2/trends/base.json.rabl +4 -0
  31. data/app/views/foreman_statistics/api/v2/trends/create.json.rabl +3 -0
  32. data/app/views/foreman_statistics/api/v2/trends/index.json.rabl +3 -0
  33. data/app/views/foreman_statistics/api/v2/trends/main.json.rabl +5 -0
  34. data/app/views/foreman_statistics/api/v2/trends/show.json.rabl +3 -0
  35. data/app/views/foreman_statistics/api/v2/trends/update.json.rabl +3 -0
  36. data/app/views/foreman_statistics/layouts/application_react.html.erb +16 -0
  37. data/app/views/foreman_statistics/trends/_empty_data.html.erb +7 -0
  38. data/app/views/foreman_statistics/trends/_fields.html.erb +7 -0
  39. data/app/views/foreman_statistics/trends/_form.html.erb +8 -0
  40. data/app/views/foreman_statistics/trends/_hosts.html.erb +13 -0
  41. data/app/views/foreman_statistics/trends/edit.html.erb +46 -0
  42. data/app/views/foreman_statistics/trends/index.html.erb +39 -0
  43. data/app/views/foreman_statistics/trends/new.html.erb +4 -0
  44. data/app/views/foreman_statistics/trends/show.html.erb +25 -0
  45. data/app/views/foreman_statistics/trends/welcome.html.erb +12 -0
  46. data/config/routes.rb +22 -0
  47. data/db/migrate/20200605153005_migrate_core_types.rb +15 -0
  48. data/db/migrate_foreman/20121012170851_create_trends.rb +25 -0
  49. data/db/migrate_foreman/20121012170936_create_trend_counters.rb +14 -0
  50. data/db/migrate_foreman/20150202094307_add_range_to_trend_counters.rb +6 -0
  51. data/db/migrate_foreman/20181031155025_add_trend_counter_created_at_unique_constraint.rb +9 -0
  52. data/lib/foreman_statistics.rb +4 -0
  53. data/lib/foreman_statistics/engine.rb +104 -0
  54. data/lib/foreman_statistics/version.rb +3 -0
  55. data/lib/tasks/foreman_statistics_tasks.rake +78 -0
  56. data/locale/Makefile +60 -0
  57. data/locale/en/foreman_statistics.po +19 -0
  58. data/locale/foreman_statistics.pot +19 -0
  59. data/locale/gemspec.rb +2 -0
  60. data/test/factories/foreman_statistics_factories.rb +68 -0
  61. data/test/fixtures/permissions.yml +26 -0
  62. data/test/fixtures/settings.yml +6 -0
  63. data/test/functional/foreman_statistics/api/v2/statistics_controller_test.rb +19 -0
  64. data/test/functional/foreman_statistics/api/v2/trends_controller_test.rb +74 -0
  65. data/test/functional/foreman_statistics/statistics_controller_test.rb +23 -0
  66. data/test/functional/foreman_statistics/trends_controller_test.rb +115 -0
  67. data/test/models/foreman_statistics/trend_counter_test.rb +10 -0
  68. data/test/models/foreman_statistics/trend_test.rb +22 -0
  69. data/test/test_plugin_helper.rb +6 -0
  70. data/test/unit/foreman_statistics/access_permissions_test.rb +16 -0
  71. data/test/unit/foreman_statistics/statistics_test.rb +82 -0
  72. data/test/unit/foreman_statistics_test.rb +11 -0
  73. data/test/unit/tasks/foreman_statistics_tasks_test.rb +205 -0
  74. metadata +199 -0
@@ -0,0 +1,40 @@
1
+ ![CI](https://github.com/theforeman/foreman_statistics/workflows/CI/badge.svg)
2
+
3
+ # ForemanStatistics
4
+
5
+ *Introdction here*
6
+
7
+ ## Installation
8
+
9
+ See [How_to_Install_a_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Plugin)
10
+ for how to install Foreman plugins
11
+
12
+ ## Usage
13
+
14
+ *Usage here*
15
+
16
+ ## TODO
17
+
18
+ *Todo list here*
19
+
20
+ ## Contributing
21
+
22
+ Fork and send a Pull Request. Thanks!
23
+
24
+ ## Copyright
25
+
26
+ Copyright (c) *year* *your name*
27
+
28
+ This program is free software: you can redistribute it and/or modify
29
+ it under the terms of the GNU General Public License as published by
30
+ the Free Software Foundation, either version 3 of the License, or
31
+ (at your option) any later version.
32
+
33
+ This program is distributed in the hope that it will be useful,
34
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
35
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36
+ GNU General Public License for more details.
37
+
38
+ You should have received a copy of the GNU General Public License
39
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
40
+
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ForemanStatistics'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+ task default: :test
37
+
38
+ begin
39
+ require 'rubocop/rake_task'
40
+ RuboCop::RakeTask.new
41
+ rescue StandardError => _e
42
+ puts 'Rubocop not loaded.'
43
+ end
44
+
45
+ task :default do
46
+ Rake::Task['rubocop'].execute
47
+ end
@@ -0,0 +1,20 @@
1
+ module ForemanStatistics
2
+ module Parameters::Trend
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def trend_params_filter
7
+ Foreman::ParameterFilter.new(Trend).tap do |filter|
8
+ filter.permit :fact_value, :fact_name,
9
+ :name,
10
+ :trendable_type, :trendable_id,
11
+ :type
12
+ end
13
+ end
14
+ end
15
+
16
+ def trend_params
17
+ self.class.trend_params_filter.filter_params(params, parameter_filter_context)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ module ForemanStatistics
2
+ module Api
3
+ module V2
4
+ class StatisticsController < ::Api::V2::BaseController
5
+ resource_description do
6
+ api_version 'v2'
7
+ api_base_url '/foreman_statistics/api'
8
+ end
9
+
10
+ api :GET, '/statistics/', N_('Get statistics')
11
+
12
+ def index
13
+ @os_count = Host.authorized(:view_hosts).count_distribution :operatingsystem
14
+ @arch_count = Host.authorized(:view_hosts).count_distribution :architecture
15
+ @env_count = Host.authorized(:view_hosts).count_distribution :environment
16
+ @klass_count = Host.authorized(:view_hosts).count_habtm 'puppetclass'
17
+ @cpu_count = FactValue.authorized(:view_facts).my_facts.count_each 'processorcount'
18
+ @model_count = FactValue.authorized(:view_facts).my_facts.count_each 'manufacturer'
19
+ @mem_size = FactValue.authorized(:view_facts).my_facts.mem_average 'memorysize'
20
+ @mem_free = FactValue.authorized(:view_facts).my_facts.mem_average 'memoryfree'
21
+ @swap_size = FactValue.authorized(:view_facts).my_facts.mem_average 'swapsize'
22
+ @swap_free = FactValue.authorized(:view_facts).my_facts.mem_average 'swapfree'
23
+ @mem_totsize = FactValue.authorized(:view_facts).my_facts.mem_sum 'memorysize'
24
+ @mem_totfree = FactValue.authorized(:view_facts).my_facts.mem_sum 'memoryfree'
25
+ render :json => { :os_count => @os_count, :arch_count => @arch_count, :swap_size => @swap_size,
26
+ :env_count => @env_count, :klass_count => @klass_count, :cpu_count => @cpu_count,
27
+ :model_count => @model_count, :mem_size => @mem_size, :mem_free => @mem_free,
28
+ :swap_free => @swap_free, :mem_totsize => @mem_totsize, :mem_totfree => @mem_totfree }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,58 @@
1
+ module ForemanStatistics
2
+ module Api
3
+ module V2
4
+ class TrendsController < ::Api::V2::BaseController
5
+ include ForemanStatistics::Parameters::Trend
6
+
7
+ before_action :find_resource, :only => %i[show destroy]
8
+
9
+ TRENDABLE_TYPES = %w[
10
+ Environment Operatingsystem Model FactName Hostgroup
11
+ ComputeResource
12
+ ].freeze
13
+
14
+ resource_description do
15
+ api_version 'v2'
16
+ api_base_url '/foreman_statistics/api'
17
+ end
18
+
19
+ api :GET, '/trends/', N_('List of trends counters')
20
+ def index
21
+ @trends = resource_scope_for_index
22
+ end
23
+
24
+ api :GET, '/trends/:id/', N_('Show a trend')
25
+ param :id, :identifier, :required => true
26
+ def show; end
27
+
28
+ api :POST, '/trends/', N_('Create a trend counter')
29
+ param :trendable_type, TRENDABLE_TYPES, :required => true
30
+ param :fact_name, String, :required => false
31
+ param :name, String, :required => false
32
+ def create
33
+ @trend = ForemanStatistics::Trend.build_trend(trend_params)
34
+ if @trend.save
35
+ process_success
36
+ else
37
+ process_resource_error
38
+ end
39
+ end
40
+
41
+ api :DELETE, '/trends/:id/', N_('Delete a trend counter')
42
+ param :id, :identifier, :required => true
43
+ def destroy
44
+ process_response @trend.destroy
45
+ end
46
+
47
+ # Overload this method to avoid using search_for method
48
+ def resource_scope_for_index(options = {})
49
+ resource_scope(options).paginate(paginate_options)
50
+ end
51
+
52
+ def resource_scope(options = {})
53
+ @resource_scope ||= scope_for(ForemanStatistics::Trend.types, options)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,19 @@
1
+ module ForemanStatistics
2
+ class ReactController < ::ApplicationController
3
+ layout 'foreman_statistics/layouts/application_react'
4
+
5
+ def index
6
+ render html: nil, layout: true
7
+ end
8
+
9
+ private
10
+
11
+ def controller_permission
12
+ :foreman_statistics
13
+ end
14
+
15
+ def action_permission
16
+ :view
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ module ForemanStatistics
2
+ class StatisticsController < ::ApplicationController
3
+ before_action :find_stat, :only => [:show]
4
+
5
+ def index
6
+ render :json => { :charts => charts.map(&:metadata) }
7
+ end
8
+
9
+ def show
10
+ render :json => @stat.metadata.merge(:data => @stat.calculate.map(&:values))
11
+ end
12
+
13
+ private
14
+
15
+ def find_stat
16
+ @stat = charts.detect { |ch| ch.id.to_s == params[:id] }
17
+ @stat || not_found
18
+ end
19
+
20
+ def charts
21
+ ForemanStatistics::Statistics.charts(Organization.current.try(:id), Location.current.try(:id))
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,58 @@
1
+ module ForemanStatistics
2
+ class TrendsController < ApplicationController
3
+ include Parameters::Trend
4
+
5
+ before_action :find_resource, :only => %i[show edit update destroy]
6
+
7
+ def index
8
+ @trends = Trend.types.includes(:trendable).sort_by { |e| e.type_name.downcase }.paginate(:page => params[:page], :per_page => params[:per_page])
9
+ end
10
+
11
+ def new
12
+ @trend = Trend.new
13
+ end
14
+
15
+ def show
16
+ render 'foreman_statistics/trends/_empty_data' if @trend.values.joins(:trend_counters).empty?
17
+ end
18
+
19
+ def create
20
+ @trend = Trend.build_trend(trend_params)
21
+ if @trend.save
22
+ process_success
23
+ else
24
+ process_error
25
+ end
26
+ end
27
+
28
+ def update
29
+ filter = self.class.trend_params_filter
30
+ trend_attrs = params[:trend].values.map { |t| filter.filter_params(ActionController::Parameters.new(t), parameter_filter_context, :none) }
31
+ @trends = Trend.update(params[:trend].keys, trend_attrs).reject { |p| p.errors.empty? }
32
+ if @trends.empty?
33
+ process_success
34
+ else
35
+ process_error
36
+ end
37
+ end
38
+
39
+ def edit; end
40
+
41
+ def destroy
42
+ if @trend.destroy
43
+ process_success
44
+ else
45
+ process_error
46
+ end
47
+ end
48
+
49
+ def count
50
+ ForemanStatistics::TrendImporter.update!
51
+ redirect_to trends_url
52
+ end
53
+
54
+ def resource_class
55
+ ForemanStatistics::Trend
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,53 @@
1
+ module ForemanStatistics
2
+ module TrendsHelper
3
+ include ::CommonParametersHelper
4
+
5
+ def trendable_types
6
+ options = { _('Environment') => 'Environment', _('Operating system') => 'Operatingsystem',
7
+ _('Model') => 'Model', _('Facts') => 'FactName', _('Host group') => 'Hostgroup', _('Compute resource') => 'ComputeResource' }
8
+ existing = ForemanTrend.types.pluck(:trendable_type)
9
+ options.delete_if { |_k, v| existing.include?(v) }
10
+ end
11
+
12
+ def trend_days_filter(trend)
13
+ form_tag trend, :id => 'days_filter', :method => :get, :class => 'form form-inline' do
14
+ content_tag(:span, (_('Trend of the last %s days.') %
15
+ select(nil, 'range', 1..Setting[:max_trend], { :selected => range },
16
+ { :onchange => "$('#days_filter').submit();$(this).attr('disabled','disabled');;" })).html_safe)
17
+ end
18
+ end
19
+
20
+ def trend_title(trend)
21
+ if trend.fact_value.blank?
22
+ trend.to_label
23
+ else
24
+ "#{trend.type_name} - #{trend.to_label}"
25
+ end
26
+ end
27
+
28
+ def chart_data(trend, from = Setting[:max_trend], _to = Time.now.utc)
29
+ chart_colors = ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92']
30
+ values = trend.values
31
+ labels = {}
32
+ values.includes(:trendable).each { |v| labels[v.id] = [CGI.escapeHTML(v.to_label), trend_path(:id => v)] }
33
+ values.includes(:trend_counters).where(['trend_counters.interval_end > ? or trend_counters.interval_end is null', from])
34
+ .reorder('trend_counters.interval_start')
35
+ .each_with_index.map do |value, idx|
36
+ data = []
37
+ value.trend_counters.each do |counter|
38
+ # cut the left side of the graph
39
+ interval_start = (counter.interval_start || from) > from ? counter.interval_start : from
40
+ next_timestamp = counter.try(:interval_end) || Time.now.utc
41
+ # transform the timestamp values to flot format - from seconds in Ruby to milliseconds in flot
42
+ data << [interval_start.to_i * 1000, counter.count]
43
+ data << [next_timestamp.to_i * 1000 - 1, counter.count]
44
+ end
45
+ { :label => labels[value.id][0], :href => labels[value.id][1], :data => data, :color => chart_colors[idx % chart_colors.size] } unless data.empty?
46
+ end.compact
47
+ end
48
+
49
+ def range
50
+ params['range'].empty? ? Setting[:max_trend] : params['range'].to_i
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,9 @@
1
+ module ForemanStatistics
2
+ module ComputeResourceDecorations
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :trends, :as => :trendable, :class_name => 'ForemanStatistics::ForemanTrend'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ForemanStatistics
2
+ module EnvironmentDecorations
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :trends, :as => :trendable, :class_name => 'ForemanStatistics::ForemanTrend'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ module ForemanStatistics
2
+ module GeneralSettingDecorations
3
+ def self.prepended(base)
4
+ class << base
5
+ prepend ClassMethodsPrepend
6
+ end
7
+ end
8
+
9
+ module ClassMethodsPrepend
10
+ def default_settings
11
+ super.concat([
12
+ set('max_trend', N_('Max days for Trends graphs'), 30, N_('Max trends'))
13
+ ])
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ module ForemanStatistics
2
+ module HostgroupDecorations
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :trends, :as => :trendable, :class_name => 'ForemanStatistics::ForemanTrend'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ForemanStatistics
2
+ module ModelDecorations
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :trends, :as => :trendable, :class_name => 'ForemanStatistics::ForemanTrend'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ForemanStatistics
2
+ module OperatingsystemDecorations
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :trends, :as => :trendable, :class_name => 'ForemanStatistics::ForemanTrend'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ForemanStatistics
2
+ module SettingDecorations
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ Setting::NONZERO_ATTRS.push('max_trend')
7
+ end
8
+ end
9
+ end