foreman_leapp 3.4.0 → 4.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 (23) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/preupgrade_report_entries_controller.rb +120 -0
  3. data/app/controllers/api/v2/preupgrade_reports_controller.rb +2 -2
  4. data/app/models/preupgrade_report_entry.rb +73 -1
  5. data/app/views/api/v2/preupgrade_report_entries/index.json.rabl +4 -0
  6. data/app/views/api/v2/preupgrade_reports/show.json.rabl +2 -5
  7. data/config/routes.rb +17 -3
  8. data/lib/foreman_leapp/engine.rb +1 -1
  9. data/lib/foreman_leapp/version.rb +1 -1
  10. data/test/functional/api/v2/preupgrade_report_entries_controller_test.rb +258 -0
  11. data/test/functional/api/v2/preupgrade_reports_controller_test.rb +7 -5
  12. data/test/models/preupgrade_report_entry_test.rb +47 -0
  13. data/webpack/components/PreupgradeReports/PreupgradeReports.js +11 -4
  14. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReports.fixtures.js +43 -0
  15. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReports.test.js +151 -46
  16. data/webpack/components/PreupgradeReports/components/NoReports.test.js +26 -12
  17. data/webpack/components/PreupgradeReportsTable/PreupgradeReportsTable.scss +9 -1
  18. data/webpack/components/PreupgradeReportsTable/PreupgradeReportsTableHelpers.js +164 -0
  19. data/webpack/components/PreupgradeReportsTable/__tests__/PreupgradeReportsTable.test.js +245 -38
  20. data/webpack/components/PreupgradeReportsTable/index.js +337 -238
  21. metadata +7 -4
  22. data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReports.test.js.snap +0 -121
  23. data/webpack/components/PreupgradeReports/components/__snapshots__/NoReports.test.js.snap +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7e5030bef562d53e8f4212fa76732f0901ec4b827620a5f07028fd11bcf36d4
4
- data.tar.gz: eac05d03ce3b746a996751d2b5affb5ea1fabea4d74dd8899f5771b5bc69f8ec
3
+ metadata.gz: 51cdabcb8df392e354e77bbe82ab0e3fc4d2c62d993a129668b9367a78453eb7
4
+ data.tar.gz: ca1c8c041d52581f987d5a3d1a964ff3aeea89290000e0faf55488bd6b4f4a82
5
5
  SHA512:
6
- metadata.gz: 77e129433f0175d3951ccc870e66bda4f37930d888a83704142e80d95490e7a9c76b134ab3337c293e03c15bc9c770e8835ba9bec8e11e32d66087ff0a23a5a4
7
- data.tar.gz: 29c9135b5faf4f6564c71d24cc989a6adc3d4a67c731a5bfca38d3618ec75745f9f1492295356386a7204971d53853558bf5a6d335f5252f55bc0d557bb42f7d
6
+ metadata.gz: eadf4d4d462b5fa9a9c838946ccca5a7a764e3bcb97f0ee457682f5d43c24a239f9900986d0c088dacb1c51b9d62e1d2800e1b7c2147debf86f465e7fe82bf6c
7
+ data.tar.gz: 8abd151cbd535508601fda873b41b74548055420d8d1cad40454d3f47cfa0761de40bca70ca91474993a8583be174fac6395e66ef69a5c334f93bb131e31f69a
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V2
5
+ class PreupgradeReportEntriesController < ::Api::V2::BaseController
6
+ include ApiAuthorizer
7
+ include Foreman::Controller::AutoCompleteSearch
8
+
9
+ skip_before_action :store_redirect_to_url, :reset_redirect_to_url, raise: false
10
+ before_action :find_preupgrade_report, if: -> { params[:preupgrade_report_id].present? }
11
+
12
+ api :GET, '/preupgrade_reports/:preupgrade_report_id/preupgrade_report_entries',
13
+ N_('List entries for a specific preupgrade report')
14
+ api :GET, '/preupgrade_report_entries', N_('List all preupgrade report entries')
15
+ param :preupgrade_report_id, :identifier, required: false,
16
+ desc: N_('ID of the preupgrade report')
17
+ param_group :search_and_pagination, ::Api::V2::BaseController
18
+ def index
19
+ @total = resource_scope.count
20
+ @preupgrade_report_entries = resource_scope_for_index
21
+ @subtotal = @preupgrade_report_entries.total_entries
22
+ end
23
+
24
+ api :GET, '/preupgrade_report_entries/auto_complete_search',
25
+ N_('Search autocomplete for preupgrade report entries')
26
+ param :search, String, required: false, desc: N_('Search string')
27
+ param :preupgrade_report_id, :identifier, required: false, desc: N_('ID of the preupgrade report')
28
+
29
+ api :POST, '/preupgrade_reports/:preupgrade_report_id/preupgrade_report_entries/bulk_remediate',
30
+ N_('Trigger a remediation job for selected preupgrade report entries')
31
+ param :search, String, required: false, desc: N_('Search string')
32
+ param :excluded_ids, Array, required: false, desc: N_('Array of excluded entry IDs')
33
+ def bulk_remediate
34
+ entries = filtered_remediation_entries
35
+ remediation_ids = entries.pluck(:id)
36
+
37
+ if remediation_ids.empty?
38
+ return render json: { error: _('No fixable entries found matching the selection.') },
39
+ status: :unprocessable_entity
40
+ end
41
+
42
+ composer = JobInvocationComposer.for_feature(
43
+ 'leapp_remediation_plan',
44
+ target_host_ids(entries),
45
+ { 'remediation_ids' => remediation_ids.join(',') }
46
+ )
47
+ composer.trigger!
48
+ job_invocation = composer.job_invocation
49
+
50
+ render json: { id: job_invocation.id }
51
+ rescue StandardError => e
52
+ Foreman::Logging.exception('Failed to trigger bulk remediation job', e)
53
+ render json: { error: _('An unexpected error occurred while creating the remediation job.') },
54
+ status: :internal_server_error
55
+ end
56
+
57
+ protected
58
+
59
+ def model_of_controller
60
+ PreupgradeReportEntry
61
+ end
62
+
63
+ def resource_scope(_options = {})
64
+ scope = if @preupgrade_report
65
+ @preupgrade_report.preupgrade_report_entries
66
+ else
67
+ PreupgradeReportEntry
68
+ end
69
+ scope.joins(:host).merge(Host.authorized(:view_hosts, Host))
70
+ end
71
+
72
+ def resource_scope_for_index(options = {})
73
+ order_str = params[:order].to_s
74
+ return super unless order_str.match?(/^(severity|risk_factor)\b/i)
75
+ direction = order_str.match?(/desc$/i) ? 'DESC' : 'ASC'
76
+
77
+ resource_scope(options)
78
+ .search_for(params[:search].to_s)
79
+ .order_by_severity(direction)
80
+ .paginate(paginate_options)
81
+ end
82
+
83
+ private
84
+
85
+ def filtered_remediation_entries
86
+ combined_search = [params[:search].presence, 'fix_type = command'].compact.join(' AND ')
87
+ entries = resource_scope.search_for(combined_search)
88
+ entries = entries.where.not(id: params[:excluded_ids]) if params[:excluded_ids].present?
89
+ entries
90
+ end
91
+
92
+ def target_host_ids(entries)
93
+ host_ids = entries.pluck(:host_id).uniq.compact
94
+ host_ids = [@preupgrade_report.host_id].compact if host_ids.empty? && @preupgrade_report
95
+ host_ids
96
+ end
97
+
98
+ def find_preupgrade_report
99
+ @preupgrade_report = PreupgradeReport
100
+ .joins(:host)
101
+ .merge(Host.authorized(:view_hosts, Host))
102
+ .find(params[:preupgrade_report_id])
103
+ end
104
+
105
+ def path_to_authenticate
106
+ auth_action = case action_name
107
+ when 'auto_complete_search'
108
+ 'index'
109
+ when 'bulk_remediate'
110
+ 'create'
111
+ else
112
+ action_name
113
+ end
114
+ path_params = params.slice(:id, :user_id)
115
+ .merge(action: auth_action, controller: 'api/v2/job_invocations')
116
+ Foreman::AccessControl.normalize_path_hash(path_params)
117
+ end
118
+ end
119
+ end
120
+ end
@@ -30,8 +30,8 @@ module Api
30
30
  # By overriding path_to_authenticate we can require REX's permission view_job_invocations
31
31
  def path_to_authenticate
32
32
  params['action'] = 'show' if params['action'] == 'job_invocation'
33
- Foreman::AccessControl.normalize_path_hash params.slice(:action, :id, :user_id)
34
- .merge({ controller: 'api/v2/job_invocations' })
33
+ path_params = params.slice(:action, :id, :user_id).merge(controller: 'api/v2/job_invocations')
34
+ Foreman::AccessControl.normalize_path_hash(path_params)
35
35
  end
36
36
  end
37
37
  end
@@ -8,9 +8,81 @@ class PreupgradeReportEntry < ApplicationRecord
8
8
  serialize :flags, Array
9
9
  serialize :detail, JSON
10
10
 
11
- validates :preupgrade_report, :host, :hostname, :title, :actor, :audience, :severity, :leapp_run_id, presence: true
11
+ validates :preupgrade_report, :host, :hostname, :title, :actor,
12
+ :audience, :severity, :leapp_run_id, presence: true
13
+
14
+ scoped_search on: :title, complete_value: true
15
+ scoped_search on: :hostname, complete_value: true
16
+ scoped_search on: :summary
17
+
18
+ scoped_search on: :severity,
19
+ rename: :risk_factor,
20
+ complete_value: { high: 'high', medium: 'medium', low: 'low', info: 'info' }
21
+
22
+ scoped_search on: :flags,
23
+ rename: :inhibitor,
24
+ only_explicit: true,
25
+ operators: ['=', '!='],
26
+ complete_value: { yes: 'yes', no: 'no' },
27
+ ext_method: :search_yes_no_fields
28
+
29
+ scoped_search on: :detail,
30
+ rename: :has_remediation,
31
+ only_explicit: true,
32
+ operators: ['=', '!='],
33
+ complete_value: { yes: 'yes', no: 'no' },
34
+ ext_method: :search_yes_no_fields
35
+
36
+ scoped_search on: :detail,
37
+ rename: :fix_type,
38
+ only_explicit: true,
39
+ operators: ['=', '!='],
40
+ complete_value: { hint: 'hint', command: 'command' },
41
+ ext_method: :search_fix_type
42
+
43
+ # info(1) < low(2) < medium(3) < high(4)
44
+ scope :order_by_severity, lambda { |direction = 'ASC'|
45
+ dir = direction.to_s.casecmp('DESC').zero? ? 'DESC' : 'ASC'
46
+ order(Arel.sql(
47
+ "CASE preupgrade_report_entries.severity " \
48
+ "WHEN 'high' THEN 4 WHEN 'medium' THEN 3 " \
49
+ "WHEN 'low' THEN 2 WHEN 'info' THEN 1 ELSE 0 END #{dir}"
50
+ ))
51
+ }
12
52
 
13
53
  def self.remediation_details(remediation_ids, host)
14
54
  where(id: remediation_ids, host: host).where.not(detail: nil).pluck(:detail)
15
55
  end
56
+
57
+ def self.search_yes_no_fields(key, operator, value)
58
+ column, term = case key.to_sym
59
+ when :inhibitor then %w[flags inhibitor]
60
+ when :has_remediation then %w[detail remediations]
61
+ else raise Foreman::Exception,
62
+ N_("Unknown search key '%s' for search_yes_no_fields") % key
63
+ end
64
+
65
+ if (operator == '=' && value == 'yes') || (operator == '!=' && value == 'no')
66
+ { conditions: "preupgrade_report_entries.#{column} LIKE '%%#{term}%%'" }
67
+ else
68
+ { conditions: "(preupgrade_report_entries.#{column} NOT LIKE '%%#{term}%%' OR " \
69
+ "preupgrade_report_entries.#{column} IS NULL)" }
70
+ end
71
+ end
72
+
73
+ def self.search_fix_type(_key, operator, value)
74
+ unless %w[hint command].include?(value.to_s.downcase)
75
+ raise Foreman::Exception, N_("Unknown fix_type value '%s' for search_fix_type") % value
76
+ end
77
+
78
+ safe_value = sanitize_sql_like(value.to_s.downcase)
79
+ like_op = (operator == '=') ? 'LIKE' : 'NOT LIKE'
80
+
81
+ conditions = "(preupgrade_report_entries.detail #{like_op} " \
82
+ "'%%\"type\":\"#{safe_value}\"%%' OR " \
83
+ "preupgrade_report_entries.detail #{like_op} " \
84
+ "'%%\"type\": \"#{safe_value}\"%%')"
85
+ conditions = "(#{conditions} OR preupgrade_report_entries.detail IS NULL)" if operator == '!='
86
+ { conditions: conditions }
87
+ end
16
88
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ collection @preupgrade_report_entries
4
+ extends 'api/v2/preupgrade_report_entries/base'
@@ -1,7 +1,4 @@
1
- object @preupgrade_report
1
+ # frozen_string_literal: true
2
2
 
3
+ object @preupgrade_report
3
4
  extends 'api/v2/preupgrade_reports/base'
4
-
5
- child :preupgrade_report_entries do
6
- extends 'api/v2/preupgrade_report_entries/base'
7
- end
data/config/routes.rb CHANGED
@@ -1,12 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Rails.application.routes.draw do
4
- resources :preupgrade_reports, :only => %i[index]
4
+ resources :preupgrade_reports, only: %i[index]
5
5
 
6
6
  namespace :api, defaults: { format: 'json' } do
7
7
  scope '(:apiv)', module: :v2, defaults: { apiv: 'v2' }, apiv: /v2/,
8
- constraints: ApiConstraints.new( version: 2, default: true) do
9
- resources :preupgrade_reports, only: %i[index show]
8
+ constraints: ApiConstraints.new(version: 2, default: true) do
9
+ resources :preupgrade_report_entries, only: %i[index] do
10
+ collection do
11
+ get :auto_complete_search
12
+ end
13
+ end
14
+
15
+ resources :preupgrade_reports, only: %i[index show] do
16
+ resources :preupgrade_report_entries, only: %i[index] do
17
+ collection do
18
+ get :auto_complete_search
19
+ post :bulk_remediate
20
+ end
21
+ end
22
+ end
23
+
10
24
  get 'job_invocations/:id/preupgrade_reports', to: 'preupgrade_reports#job_invocation'
11
25
  end
12
26
  end
@@ -17,7 +17,7 @@ module ForemanLeapp
17
17
  initializer 'foreman_leapp.register_plugin', before: :finisher_hook do |app|
18
18
  app.reloader.to_prepare do
19
19
  Foreman::Plugin.register :foreman_leapp do
20
- requires_foreman '>= 3.16'
20
+ requires_foreman '>= 5.0'
21
21
  register_gettext
22
22
 
23
23
  apipie_documented_controllers ["#{ForemanLeapp::Engine.root}/app/controllers/api/v2/*.rb"]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ForemanLeapp
4
- VERSION = '3.4.0'
4
+ VERSION = '4.1.0'
5
5
  end
@@ -0,0 +1,258 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ module Api
6
+ module V2
7
+ # rubocop:disable Metrics/ClassLength
8
+ class PreupgradeReportEntriesControllerTest < ActionController::TestCase
9
+ setup do
10
+ @host = FactoryBot.create(:host)
11
+ @job_invocation = FactoryBot.create(:job_invocation)
12
+ @report = FactoryBot.create(:preupgrade_report, host: @host, job_invocation: @job_invocation)
13
+ @entry = FactoryBot.create(:preupgrade_report_entry, host: @host, preupgrade_report: @report)
14
+ end
15
+
16
+ test 'should get :index' do
17
+ get :index, params: { preupgrade_report_id: @report.id }
18
+ assert_response :success
19
+
20
+ body = JSON.parse(@response.body)
21
+ assert_not_empty body['results']
22
+ assert_equal @entry.id, body['results'].first['id']
23
+ assert_equal 1, body['total']
24
+ assert_equal 1, body['subtotal']
25
+ end
26
+
27
+ test ':index response includes expected entry fields' do
28
+ get :index, params: { preupgrade_report_id: @report.id }
29
+ assert_response :success
30
+
31
+ entry_json = JSON.parse(@response.body)['results'].first
32
+ %w[id title hostname severity host_id preupgrade_report_id flags detail].each do |field|
33
+ assert entry_json.key?(field), "Expected field '#{field}' to be present"
34
+ end
35
+ end
36
+
37
+ test ':index returns 404 for non-existent preupgrade_report_id' do
38
+ get :index, params: { preupgrade_report_id: 0 }
39
+ assert_response :not_found
40
+ end
41
+
42
+ test ':index does not return entries belonging to a different report' do
43
+ other_report = FactoryBot.create(:preupgrade_report, host: @host,
44
+ job_invocation: @job_invocation)
45
+ other_entry = FactoryBot.create(:preupgrade_report_entry, host: @host,
46
+ preupgrade_report: other_report)
47
+
48
+ get :index, params: { preupgrade_report_id: @report.id }
49
+ assert_response :success
50
+
51
+ ids = JSON.parse(@response.body)['results'].map { |e| e['id'] }
52
+ assert_includes ids, @entry.id
53
+ assert_not_includes ids, other_entry.id
54
+ end
55
+
56
+ test ':index supports pagination' do
57
+ FactoryBot.create_list(:preupgrade_report_entry, 4, host: @host, preupgrade_report: @report)
58
+
59
+ get :index, params: { preupgrade_report_id: @report.id, per_page: 2, page: 1 }
60
+ assert_response :success
61
+
62
+ body = JSON.parse(@response.body)
63
+ assert_equal 2, body['results'].size
64
+ assert_equal 5, body['total']
65
+ end
66
+
67
+ test ':index second page returns correct entries' do
68
+ FactoryBot.create_list(:preupgrade_report_entry, 4, host: @host, preupgrade_report: @report)
69
+
70
+ get :index, params: { preupgrade_report_id: @report.id, per_page: 2, page: 2 }
71
+ assert_response :success
72
+ assert_equal 2, JSON.parse(@response.body)['results'].size
73
+ end
74
+
75
+ test ':index supports search by title' do
76
+ other = FactoryBot.create(:preupgrade_report_entry, host: @host, preupgrade_report: @report,
77
+ title: 'Unique xqz title for search test')
78
+
79
+ get :index, params: { preupgrade_report_id: @report.id,
80
+ search: "title = \"#{other.title}\"" }
81
+ assert_response :success
82
+
83
+ body = JSON.parse(@response.body)
84
+ assert_equal 1, body['results'].size
85
+ assert_equal other.id, body['results'].first['id']
86
+ end
87
+
88
+ test ':index supports order parameter' do
89
+ FactoryBot.create(:preupgrade_report_entry, host: @host,
90
+ preupgrade_report: @report, title: 'AAAA sorts first')
91
+ FactoryBot.create(:preupgrade_report_entry, host: @host,
92
+ preupgrade_report: @report, title: 'ZZZZ sorts last')
93
+
94
+ get :index, params: { preupgrade_report_id: @report.id, order: 'title ASC' }
95
+ assert_response :success
96
+
97
+ titles = JSON.parse(@response.body)['results'].map { |e| e['title'] }
98
+ assert titles.index('AAAA sorts first') < titles.index('ZZZZ sorts last')
99
+ end
100
+
101
+ test ':index supports semantic risk_factor ordering' do
102
+ FactoryBot.create(:preupgrade_report_entry, host: @host,
103
+ preupgrade_report: @report, severity: 'high')
104
+ FactoryBot.create(:preupgrade_report_entry, host: @host,
105
+ preupgrade_report: @report, severity: 'info')
106
+
107
+ get :index, params: { preupgrade_report_id: @report.id, order: 'risk_factor ASC' }
108
+ assert_response :success
109
+
110
+ severities = JSON.parse(@response.body)['results'].map { |e| e['severity'] }
111
+ assert severities.index('info') < severities.index('high'),
112
+ "Expected 'info' to sort before 'high' in semantic ASC order"
113
+ end
114
+
115
+ test 'should get :auto_complete_search' do
116
+ get :auto_complete_search, params: { search: 'title' }
117
+ assert_response :success
118
+ assert_kind_of Array, JSON.parse(@response.body)
119
+ end
120
+
121
+ test ':auto_complete_search returns non-blank string suggestions' do
122
+ get :auto_complete_search, params: { search: '' }
123
+ assert_response :success
124
+ results = JSON.parse(@response.body)
125
+ assert_kind_of Array, results
126
+ assert results.none?(&:blank?), 'Autocomplete results should not contain blank values'
127
+ end
128
+
129
+ test ':auto_complete_search scopes suggestions to report when preupgrade_report_id given' do
130
+ get :auto_complete_search,
131
+ params: { search: '', preupgrade_report_id: @report.id }
132
+ assert_response :success
133
+ assert_kind_of Array, JSON.parse(@response.body)
134
+ end
135
+
136
+ context 'with permissions' do
137
+ setup do
138
+ @user = FactoryBot.create(:user, admin: false)
139
+ setup_user('view', 'job_invocations', nil, @user)
140
+ setup_user('view', 'hosts', nil, @user)
141
+ end
142
+
143
+ test 'should get :index' do
144
+ get :index, params: { preupgrade_report_id: @report.id }, session: set_session_user(@user)
145
+ assert_response :success
146
+ assert_not_empty JSON.parse(@response.body)['results']
147
+ end
148
+
149
+ test 'should get :auto_complete_search' do
150
+ get :auto_complete_search, params: { search: '' }, session: set_session_user(@user)
151
+ assert_response :success
152
+ end
153
+ end
154
+
155
+ context 'without :view_job_invocations' do
156
+ setup do
157
+ @user = FactoryBot.create(:user, admin: false)
158
+ setup_user('view', 'hosts', nil, @user)
159
+ end
160
+
161
+ test 'should not get :index' do
162
+ get :index, params: { preupgrade_report_id: @report.id }, session: set_session_user(@user)
163
+ assert_response :forbidden
164
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'],
165
+ 'view_job_invocations'
166
+ end
167
+
168
+ test 'should not get :auto_complete_search' do
169
+ get :auto_complete_search, params: { search: '' }, session: set_session_user(@user)
170
+ assert_response :forbidden
171
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'],
172
+ 'view_job_invocations'
173
+ end
174
+ end
175
+
176
+ context 'without :view_hosts' do
177
+ setup do
178
+ @user = FactoryBot.create(:user, admin: false)
179
+ setup_user('view', 'job_invocations', nil, @user)
180
+ end
181
+
182
+ test 'should not get :index' do
183
+ get :index, params: { preupgrade_report_id: @report.id }, session: set_session_user(@user)
184
+ assert_response :forbidden
185
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_hosts'
186
+ end
187
+
188
+ test 'should not get :auto_complete_search' do
189
+ get :auto_complete_search, params: { search: '' }, session: set_session_user(@user)
190
+ assert_response :forbidden
191
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_hosts'
192
+ end
193
+ end
194
+
195
+ context 'POST bulk_remediate' do
196
+ setup do
197
+ @host = FactoryBot.create(:host)
198
+ @report = FactoryBot.create(:preupgrade_report, host: @host)
199
+
200
+ # Fixable entries
201
+ fixable_detail = { 'remediations' => [{ 'type' => 'command', 'context' => ['yum', 'update'] }] }
202
+ @entry1 = FactoryBot.create(:preupgrade_report_entry,
203
+ preupgrade_report: @report, host: @host, detail: fixable_detail)
204
+ @entry2 = FactoryBot.create(:preupgrade_report_entry,
205
+ preupgrade_report: @report, host: @host, detail: fixable_detail)
206
+ @entry3 = FactoryBot.create(:preupgrade_report_entry,
207
+ preupgrade_report: @report, host: @host, detail: fixable_detail)
208
+
209
+ @unfixable = FactoryBot.create(:preupgrade_report_entry, preupgrade_report: @report, host: @host, detail: {})
210
+ @composer = mock('composer')
211
+ @dummy_job = FactoryBot.create(:job_invocation)
212
+ @composer.stubs(:trigger!)
213
+ @composer.stubs(:job_invocation).returns(@dummy_job)
214
+ end
215
+
216
+ test 'triggers job invocation for all fixable entries' do
217
+ expected_ids = [@entry1.id, @entry2.id, @entry3.id].join(',')
218
+
219
+ JobInvocationComposer.expects(:for_feature)
220
+ .with('leapp_remediation_plan', [@host.id], { 'remediation_ids' => expected_ids })
221
+ .returns(@composer)
222
+
223
+ post :bulk_remediate, params: { preupgrade_report_id: @report.id }
224
+ assert_response :success
225
+ json = ActiveSupport::JSON.decode(response.body)
226
+ assert_equal @dummy_job.id, json['id']
227
+ end
228
+
229
+ test 'respects the excluded_ids parameter' do
230
+ expected_ids = [@entry1.id, @entry3.id].join(',')
231
+
232
+ JobInvocationComposer.expects(:for_feature)
233
+ .with('leapp_remediation_plan', [@host.id], { 'remediation_ids' => expected_ids })
234
+ .returns(@composer)
235
+
236
+ post :bulk_remediate, params: {
237
+ preupgrade_report_id: @report.id,
238
+ excluded_ids: [@entry2.id],
239
+ }
240
+ assert_response :success
241
+ end
242
+
243
+ test 'returns error if no fixable entries match' do
244
+ JobInvocationComposer.expects(:for_feature).never
245
+
246
+ post :bulk_remediate, params: {
247
+ preupgrade_report_id: @report.id,
248
+ excluded_ids: [@entry1.id, @entry2.id, @entry3.id],
249
+ }
250
+ assert_response :unprocessable_entity
251
+ json = ActiveSupport::JSON.decode(response.body)
252
+ assert_equal 'No fixable entries found matching the selection.', json['error']
253
+ end
254
+ end
255
+ end
256
+ # rubocop:enable Metrics/ClassLength
257
+ end
258
+ end
@@ -22,9 +22,9 @@ module Api
22
22
  get :show, params: { id: @report.id }
23
23
  assert_response :success
24
24
 
25
- response = JSON.parse(@response.body)
26
- assert_equal response['id'], @report.id
27
- assert_not_empty response['preupgrade_report_entries']
25
+ body = JSON.parse(@response.body)
26
+ assert_equal @report.id, body['id']
27
+ assert_nil body['results']
28
28
  end
29
29
 
30
30
  test 'should get :job_invocation' do
@@ -49,7 +49,9 @@ module Api
49
49
  test 'should get :show' do
50
50
  get :show, params: { id: @report.id }, session: set_session_user(@user)
51
51
  assert_response :success
52
- assert_equal @report.id, JSON.parse(@response.body)['id']
52
+ json = ActiveSupport::JSON.decode(@response.body)
53
+ assert_equal @report.id, json['id']
54
+ assert_nil json['results']
53
55
  end
54
56
 
55
57
  test 'should get :job_invocation' do
@@ -96,7 +98,7 @@ module Api
96
98
  assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_hosts'
97
99
  end
98
100
 
99
- test 'should not get :job_invocation' do
101
+ test 'should not get :show' do
100
102
  get :show, params: { id: @report.id }, session: set_session_user(@user)
101
103
  assert_response :forbidden
102
104
  assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_hosts'
@@ -33,4 +33,51 @@ class PreupgradeReportEntryTest < ActiveSupport::TestCase
33
33
  assert_equal details.size, 0
34
34
  end
35
35
  end
36
+
37
+ describe 'scoped_search' do
38
+ it 'filters correctly by has_remediation and inhibitor' do
39
+ host = FactoryBot.create(:host)
40
+ report = FactoryBot.create(:preupgrade_report)
41
+
42
+ entry1 = FactoryBot.create(:preupgrade_report_entry, preupgrade_report: report, host: host,
43
+ flags: ['inhibitor'],
44
+ detail: { 'remediations' => [{ 'type' => 'hint' }] })
45
+ entry2 = FactoryBot.create(:preupgrade_report_entry, preupgrade_report: report, host: host,
46
+ flags: [],
47
+ detail: nil)
48
+
49
+ assert_includes PreupgradeReportEntry.search_for('inhibitor = yes'), entry1
50
+ assert_not_includes PreupgradeReportEntry.search_for('inhibitor = yes'), entry2
51
+ assert_includes PreupgradeReportEntry.search_for('has_remediation = no'), entry2
52
+ assert_not_includes PreupgradeReportEntry.search_for('has_remediation = no'), entry1
53
+ end
54
+
55
+ it 'filters correctly by risk_factor (renamed from severity)' do
56
+ host = FactoryBot.create(:host)
57
+ report = FactoryBot.create(:preupgrade_report)
58
+
59
+ high_entry = FactoryBot.create(:preupgrade_report_entry, preupgrade_report: report,
60
+ host: host, severity: 'high')
61
+ info_entry = FactoryBot.create(:preupgrade_report_entry, preupgrade_report: report,
62
+ host: host, severity: 'info')
63
+
64
+ assert_includes PreupgradeReportEntry.search_for('risk_factor = high'), high_entry
65
+ assert_not_includes PreupgradeReportEntry.search_for('risk_factor = high'), info_entry
66
+ end
67
+
68
+ it 'orders semantically via order_by_severity scope' do
69
+ host = FactoryBot.create(:host)
70
+ report = FactoryBot.create(:preupgrade_report)
71
+ FactoryBot.create(:preupgrade_report_entry, preupgrade_report: report, host: host, severity: 'high')
72
+ FactoryBot.create(:preupgrade_report_entry, preupgrade_report: report, host: host, severity: 'info')
73
+ FactoryBot.create(:preupgrade_report_entry, preupgrade_report: report, host: host, severity: 'medium')
74
+ FactoryBot.create(:preupgrade_report_entry, preupgrade_report: report, host: host, severity: 'low')
75
+
76
+ asc = PreupgradeReportEntry.order_by_severity('ASC').map(&:severity)
77
+ assert_equal %w[info low medium high], asc
78
+
79
+ desc = PreupgradeReportEntry.order_by_severity('DESC').map(&:severity)
80
+ assert_equal %w[high medium low info], desc
81
+ end
82
+ end
36
83
  end
@@ -1,5 +1,7 @@
1
1
  import React, { useState } from 'react';
2
- import MessageBox from 'foremanReact/components/common/MessageBox';
2
+ import { Icon } from '@patternfly/react-core';
3
+ import { ExclamationCircleIcon } from '@patternfly/react-icons';
4
+ import EmptyState from 'foremanReact/components/common/EmptyState';
3
5
  import { LoadingState, Row } from 'patternfly-react';
4
6
  import PropTypes from 'prop-types';
5
7
  import { sprintf, translate as __ } from 'foremanReact/common/I18n';
@@ -116,10 +118,15 @@ const withLoadingState = Component => componentProps => {
116
118
 
117
119
  if (!isEmpty(error)) {
118
120
  return (
119
- <MessageBox
121
+ <EmptyState
120
122
  key="preupgrade-reports-error"
121
- icontype="error-circle-o"
122
- msg={sprintf(__('Could not retrieve data: %(status)s - %(msg)s'), {
123
+ variant="xs"
124
+ icon={
125
+ <Icon iconSize="lg">
126
+ <ExclamationCircleIcon />
127
+ </Icon>
128
+ }
129
+ header={sprintf(__('Could not retrieve data: %(status)s - %(msg)s'), {
123
130
  status: error.statusText,
124
131
  msg: error.errorMsg,
125
132
  })}