foreman_leapp 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -20
  3. data/app/controllers/api/v2/concerns/api_authorizer.rb +27 -0
  4. data/app/controllers/api/v2/preupgrade_reports_controller.rb +21 -2
  5. data/app/controllers/preupgrade_reports_controller.rb +11 -1
  6. data/app/lib/actions/preupgrade_job.rb +2 -1
  7. data/app/models/preupgrade_report.rb +1 -0
  8. data/app/models/preupgrade_report_entry.rb +1 -0
  9. data/app/views/api/v2/preupgrade_report_entries/base.json.rabl +1 -1
  10. data/app/views/api/v2/preupgrade_reports/job_invocation.json.rabl +3 -0
  11. data/app/views/foreman_leapp/job_templates/check.erb +14 -0
  12. data/app/views/foreman_leapp/job_templates/preupgrade.erb +3 -1
  13. data/app/views/foreman_leapp/job_templates/remediation.erb +11 -0
  14. data/app/views/foreman_leapp/job_templates/upgrade.erb +6 -5
  15. data/config/routes.rb +2 -1
  16. data/db/migrate/20200429080939_report_entries_flags.rb +5 -0
  17. data/lib/foreman_leapp/engine.rb +4 -1
  18. data/lib/foreman_leapp/version.rb +1 -1
  19. data/package.json +3 -2
  20. data/test/functional/api/v2/preupgrade_reports_controller_test.rb +89 -6
  21. data/test/functional/preupgrade_reports_controller_test.rb +27 -4
  22. data/webpack/__mocks__/foremanReact/common/I18n.js +1 -1
  23. data/webpack/__mocks__/foremanReact/components/common/EmptyState.js +1 -0
  24. data/webpack/components/PreupgradeReports/PreupgradeReports.js +104 -21
  25. data/webpack/components/PreupgradeReports/PreupgradeReportsActions.js +1 -1
  26. data/webpack/components/PreupgradeReports/PreupgradeReportsHelpers.js +132 -15
  27. data/webpack/components/PreupgradeReports/PreupgradeReportsReducer.js +2 -0
  28. data/webpack/components/PreupgradeReports/PreupgradeReportsSelectors.js +10 -0
  29. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReports.fixtures.js +45 -7
  30. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReports.test.js +17 -1
  31. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReportsHelpers.test.js +43 -3
  32. data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReports.test.js.snap +82 -134
  33. data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReportsHelpers.test.js.snap +409 -2
  34. data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReportsReducer.test.js.snap +21 -1
  35. data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReportsSelectors.test.js.snap +17 -1
  36. data/webpack/components/PreupgradeReports/components/EntriesFilter.js +121 -0
  37. data/webpack/components/PreupgradeReports/components/EntriesFilter.scss +3 -0
  38. data/webpack/components/PreupgradeReports/components/EntriesFilter.test.js +30 -0
  39. data/webpack/components/PreupgradeReports/components/{FixAllButton.js → FixSelectedButton.js} +6 -8
  40. data/webpack/components/PreupgradeReports/components/FixSelectedButton.test.js +15 -0
  41. data/webpack/components/PreupgradeReports/components/NoReports.js +35 -0
  42. data/webpack/components/PreupgradeReports/components/NoReports.test.js +15 -0
  43. data/webpack/components/PreupgradeReports/components/UpgradeAllButton.js +29 -0
  44. data/webpack/components/PreupgradeReports/components/{FixAllButton.test.js → UpgradeAllButton.test.js} +3 -4
  45. data/webpack/components/PreupgradeReports/components/__snapshots__/EntriesFilter.test.js.snap +330 -0
  46. data/webpack/components/PreupgradeReports/components/__snapshots__/{FixAllButton.test.js.snap → FixSelectedButton.test.js.snap} +9 -3
  47. data/webpack/components/PreupgradeReports/components/__snapshots__/NoReports.test.js.snap +19 -0
  48. data/webpack/components/PreupgradeReports/components/__snapshots__/UpgradeAllButton.test.js.snap +29 -0
  49. data/webpack/components/PreupgradeReports/index.js +22 -4
  50. data/webpack/components/PreupgradeReportsList/PreupgradeReportList.scss +37 -0
  51. data/webpack/components/PreupgradeReportsList/__tests__/PreupgradeReportsList.test.js +16 -0
  52. data/webpack/components/PreupgradeReportsList/__tests__/__snapshots__/PreupgradeReportsList.test.js.snap +38 -0
  53. data/webpack/components/PreupgradeReportsList/components/InfoItem.js +1 -1
  54. data/webpack/components/PreupgradeReportsList/components/InhibitorInfoItem.js +33 -0
  55. data/webpack/components/PreupgradeReportsList/components/PreupgradeReportEntry.js +19 -9
  56. data/webpack/components/PreupgradeReportsList/components/PreupgradeReportsListHeader.js +56 -0
  57. data/webpack/components/PreupgradeReportsList/components/SortableHeaderItem.js +50 -0
  58. data/webpack/components/PreupgradeReportsList/components/__tests__/InhibitorInfoItem.test.js +27 -0
  59. data/webpack/components/PreupgradeReportsList/components/__tests__/PreupgradeReportEntry.test.js +2 -0
  60. data/webpack/components/PreupgradeReportsList/components/__tests__/PreupgradeReportsListHeader.test.js +14 -0
  61. data/webpack/components/PreupgradeReportsList/components/__tests__/SortableHeaderItem.test.js +29 -0
  62. data/webpack/components/PreupgradeReportsList/components/__tests__/__snapshots__/InhibitorInfoItem.test.js.snap +32 -0
  63. data/webpack/components/PreupgradeReportsList/components/__tests__/__snapshots__/PreupgradeReportEntry.test.js.snap +23 -17
  64. data/webpack/components/PreupgradeReportsList/components/__tests__/__snapshots__/PreupgradeReportsListHeader.test.js.snap +113 -0
  65. data/webpack/components/PreupgradeReportsList/components/__tests__/__snapshots__/SortableHeaderItem.test.js.snap +36 -0
  66. data/webpack/components/PreupgradeReportsList/components/__tests__/__snapshots__/helpers.test.js.snap +4 -10
  67. data/webpack/components/PreupgradeReportsList/components/__tests__/helpers.test.js +0 -2
  68. data/webpack/components/PreupgradeReportsList/components/foreman_leapp.scss +7 -1
  69. data/webpack/components/PreupgradeReportsList/components/helpers.js +47 -19
  70. data/webpack/components/PreupgradeReportsList/components/images/i_severity-critical.svg +61 -0
  71. data/webpack/components/PreupgradeReportsList/components/images/i_severity-high.svg +61 -0
  72. data/webpack/components/PreupgradeReportsList/components/images/i_severity-low.svg +62 -0
  73. data/webpack/components/PreupgradeReportsList/components/images/i_severity-med.svg +62 -0
  74. data/webpack/components/PreupgradeReportsList/index.js +28 -3
  75. metadata +34 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a75680a7afc44c4a8e90727d2319a53b231748a6db1f8a1c3627c319b3483902
4
- data.tar.gz: 8506877b49e172205eeadb3a898d9ee5e8e1235525155673d52b98efdfb7441f
3
+ metadata.gz: 133a054cb6f2f0aab562ef7c0e5863e844ede2a3124a738594c03033a06225d6
4
+ data.tar.gz: ed23743e31e2a42dcf9c5ddaebae7b2ebe38ac8c46cff49cdd4856df07091fc2
5
5
  SHA512:
6
- metadata.gz: 18a93546178c2252cd530f913d98633e24808dbf66698ddff5d4249d2eec0c9df2513447a304df59cd55e4b54c0bcefb31cae0713dd002ca1895bb78bb19be28
7
- data.tar.gz: 4993f008708cdb78a9defd9cd311cadb7396e75ad16f0827675ab32e0361e5ad5154810d3e8b1d5f9504d1db2849c6aebe9addae16006a9ff8698b70719b2247
6
+ metadata.gz: ca1b768b4c623e9b1d679b0bb67b8bdc882ba74041bf386b81bbb647c186669564fd155f3a1ab8d8df1ff7141535efc044d843c6cd81ae08beba1bf550ad1738
7
+ data.tar.gz: fe21fa9458002c0ae272d6b2738a8abf758b8434a8aad5c31fbf04e3da79ac230a2ab91a6b37a3d3041560f67c249012ec0a035ee2b0d28320fd53e1de45d752
data/README.md CHANGED
@@ -1,32 +1,23 @@
1
1
  # ForemanLeapp
2
2
 
3
- This plugin allows to run inplace upgrades for rhel7 vms in foreman using leapp tool.
4
- For more information about leapp check [github](https://github.com/oamg/leapp) or
5
- [developer docs](https://leapp.readthedocs.io/en/latest/).
3
+ This plugin allows to run inplace upgrades for RHEL 7 hosts in Foreman using Leapp tool.
4
+ For more information about Leapp tool check [github](https://github.com/oamg/leapp) or [developer docs](https://leapp.readthedocs.io/en/latest/).
6
5
 
7
6
  ## Installation
8
7
 
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
8
+ See [Plugins Manual](https://www.theforeman.org/plugins/#2.Installation) for how to install Foreman plugins.
11
9
 
12
10
  ## Usage
13
11
 
14
- The plugin will add 2 remote execution jobs, "Run preupgrade via leapp" and "Run upgrade via leapp". Only
15
- preupgrade reports storage and retrieval has been implemented so far.
12
+ The plugin will add following jobs:
13
+ - Run preupgrade via Leapp
14
+ - Remediation plan
15
+ - Run upgrade via Leapp
16
16
 
17
- After running a preupgrade remote execution job on one or more foreman hosts the report can be retrieved from foreman db.
18
-
19
- The retrieval api looks like:
20
-
21
- - to fetch a specific preupgrade report - GET http://FOREMAN_URL:FOREMAN_PORT/api/v2/preupgrade_reports/REPORT_ID.
22
- - to fetch all reports for specific job invocation - GET http://FOREMAN_URL:FOREMAN_PORT/api/v2/aggregation/JOB_INVOCATION_ID.
23
- - to fetch last preupgrade report per host - GET http://FOREMAN_URL:FOREMAN_PORT/api/v2/preupgrade_reports/hosts/HOST_NAME_OR_ID/last.
24
-
25
- ## TODO
26
-
27
- - Unit tests
28
- - Automate rubocop checks
29
- - Frontend (either from scratch or adapt the react/patternfly/typescript one for cockpit upgrades)
17
+ ## Api
18
+ - `GET /api/preupgrade_reports` List Preupgrade reports
19
+ - `GET /api/preupgrade_reports/:id` Show Preupgrade report
20
+ - `GET /api/job_invocations/:id/preupgrade_reports` List Preupgrade reports for Job invocation
30
21
 
31
22
  ## Contributing
32
23
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiAuthorizer
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_action :hosts_permission
8
+ end
9
+
10
+ private
11
+
12
+ def hosts_permission
13
+ return if User.current.can?('view_hosts')
14
+
15
+ render_error 'access_denied', status: :forbidden,
16
+ locals: { details: _('Missing one of the required permissions: view_hosts'),
17
+ missing_permissions: 'view_hosts' }
18
+ end
19
+
20
+ def resource_scope(_options = {})
21
+ @resource_scope ||= begin
22
+ scope = PreupgradeReport.joins(:host).merge(Host.authorized(:view_hosts, Host))
23
+ scope = scope.where(job_invocation_id: params[:id]) if action_name == 'job_invocation'
24
+ scope
25
+ end
26
+ end
27
+ end
@@ -3,7 +3,9 @@
3
3
  module Api
4
4
  module V2
5
5
  class PreupgradeReportsController < ::Api::V2::BaseController
6
- before_action :find_resource, only: %i[show]
6
+ include ApiAuthorizer
7
+
8
+ layout 'api/v2/layouts/index_layout', except: %i[show]
7
9
 
8
10
  api :GET, '/preupgrade_reports/', N_('List Preupgrade reports')
9
11
  param_group :search_and_pagination, ::Api::V2::BaseController
@@ -13,7 +15,24 @@ module Api
13
15
 
14
16
  api :GET, '/preupgrade_reports/:id', N_('Show Preupgrade report')
15
17
  param :id, :identifier, required: true
16
- def show; end
18
+ def show
19
+ @preupgrade_report = resource_scope.find(params[:id])
20
+ end
21
+
22
+ api :GET, '/job_invocations/:id/preupgrade_reports', N_('List Preupgrade reports for Job invocation')
23
+ param :id, :identifier, required: true
24
+ def job_invocation
25
+ @preupgrade_reports = resource_scope_for_index.where(job_invocation_id: params[:id])
26
+ end
27
+
28
+ private
29
+
30
+ # By overriding path_to_authenticate we can require REX's permission view_job_invocations
31
+ def path_to_authenticate
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' })
35
+ end
17
36
  end
18
37
  end
19
38
  end
@@ -1,7 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class PreupgradeReportsController < ::Api::V2::BaseController
4
+ include ApiAuthorizer
5
+
4
6
  def index
5
- @preupgrade_reports = resource_scope.search_for(*search_options)
7
+ @preupgrade_reports = resource_scope.includes(:preupgrade_report_entries).search_for(*search_options)
8
+ end
9
+
10
+ private
11
+
12
+ # By overriding :path_to_authenticate we can require REX's :view_job_invocations permission
13
+ def path_to_authenticate
14
+ Foreman::AccessControl.normalize_path_hash params.slice(:action, :id, :user_id)
15
+ .merge({ controller: 'job_invocations' })
6
16
  end
7
17
  end
@@ -8,7 +8,8 @@ module Actions
8
8
  end
9
9
 
10
10
  def plan(job_invocation, host, *_args)
11
- return unless ::Helpers::JobHelper.correct_feature?(job_invocation, 'leapp_preupgrade')
11
+ return unless ::Helpers::JobHelper.correct_feature?(job_invocation, 'leapp_preupgrade') ||
12
+ ::Helpers::JobHelper.correct_feature?(job_invocation, 'leapp_remediation_plan')
12
13
 
13
14
  plan_self(host_id: host.id, job_invocation_id: job_invocation.id)
14
15
  end
@@ -27,6 +27,7 @@ class PreupgradeReport < ::Report
27
27
  leapp_run_id: data['leapp_run_id'],
28
28
  summary: entry['summary'],
29
29
  tags: entry['tags'],
30
+ flags: entry['flags'],
30
31
  detail: entry['detail'] }
31
32
  end
32
33
  end
@@ -5,6 +5,7 @@ class PreupgradeReportEntry < ApplicationRecord
5
5
  belongs_to_host
6
6
 
7
7
  serialize :tags, Array
8
+ serialize :flags, Array
8
9
  serialize :detail, JSON
9
10
 
10
11
  validates :preupgrade_report, :host, :hostname, :title, :actor, :audience, :severity, :leapp_run_id, presence: true
@@ -1,4 +1,4 @@
1
1
  object @preupgrade_report_entry
2
2
 
3
3
  attributes :id, :preupgrade_report_id, :host_id, :hostname, :title, :actor, :audience,
4
- :severity, :leapp_run_id, :summary, :tags, :created_at, :updated_at
4
+ :severity, :leapp_run_id, :summary, :tags, :flags, :created_at, :updated_at
@@ -0,0 +1,3 @@
1
+ collection @preupgrade_reports
2
+
3
+ extends 'api/v2/preupgrade_reports/base'
@@ -0,0 +1,14 @@
1
+ <%#
2
+ name: Check Leapp
3
+ description_format: 'Check if Leapp package is installed.'
4
+ kind: job_template
5
+ job_category: Leapp
6
+ provider_type: SSH
7
+ snippet: true
8
+ %>
9
+
10
+ if ! command -v leapp > /dev/null
11
+ then
12
+ echo "Leapp is not installed."
13
+ exit 1
14
+ fi
@@ -7,8 +7,10 @@ provider_type: SSH
7
7
  feature: leapp_preupgrade
8
8
  %>
9
9
 
10
+ <%= render_template 'Check Leapp' %>
11
+
10
12
  leapp preupgrade
11
- [ $? -eq 0 ] || exit 1
12
13
 
13
14
  echo "===leap_upgrade_report_start==="
14
15
  cat /var/log/leapp/leapp-report.json
16
+ rm -f /var/log/leapp/leapp-report.json
@@ -12,6 +12,17 @@ template_inputs:
12
12
  input_type: user
13
13
  value_type: plain
14
14
  description: List of remediation ids
15
+ - name: run_preupgrade
16
+ description: Run preupgrade check again when remediation entries are fixed.
17
+ input_type: user
18
+ required: true
19
+ options: "true\nfalse"
20
+ default: "true"
15
21
  %>
16
22
 
23
+ <%= render_template 'Check Leapp' %>
24
+
17
25
  <%= build_remediation_plan(input('remediation_ids').split(','), @host) %>
26
+ <% if input('run_preupgrade') == 'true' %>
27
+ <%= render_template 'Run preupgrade via Leapp' %>
28
+ <% end %>
@@ -6,18 +6,19 @@ description_format: 'Upgrade RHEL 7 host'
6
6
  provider_type: Ansible
7
7
  feature: leapp_upgrade
8
8
  template_inputs:
9
- - name: reboot
10
- description: reboot the vm automaticaly to continue with the upgrade
9
+ - name: Reboot
10
+ description: Reboot the host automaticaly to continue with the upgrade
11
11
  input_type: user
12
- required: false
13
- options: "false\ntrue"
12
+ required: true
13
+ default: "true"
14
+ options: "true\nfalse"
14
15
  %>
15
16
  ---
16
17
  - hosts: all
17
18
  tasks:
18
19
  - name: Run Leapp Upgrade
19
20
  command: leapp upgrade
20
- <% if input('reboot') == "true" %>
21
+ <% if input('Reboot') == "true" %>
21
22
  - name: Reboot the machine
22
23
  reboot:
23
24
  reboot_timeout: 1800
data/config/routes.rb CHANGED
@@ -4,8 +4,9 @@ Rails.application.routes.draw do
4
4
  resources :preupgrade_reports, :only => %i[index]
5
5
 
6
6
  namespace :api, defaults: { format: 'json' } do
7
- scope '(:apiv)', module: :v2, defaults: { apiv: 'v2'}, apiv: /v2/, constraints: ApiConstraints.new( version: 2, default: true) do
7
+ scope '(:apiv)', module: :v2, defaults: { apiv: 'v2' }, apiv: /v2/, constraints: ApiConstraints.new( version: 2, default: true) do
8
8
  resources :preupgrade_reports, only: %i[index show]
9
+ get 'job_invocations/:id/preupgrade_reports', to: 'preupgrade_reports#job_invocation'
9
10
  end
10
11
  end
11
12
  end
@@ -0,0 +1,5 @@
1
+ class ReportEntriesFlags < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :preupgrade_report_entries, :flags, :text
4
+ end
5
+ end
@@ -33,7 +33,10 @@ module ForemanLeapp
33
33
  partial: 'job_invocations/leapp_preupgrade_report',
34
34
  name: _('Leapp preupgrade report'),
35
35
  id: 'leapp_preupgrade_report',
36
- onlyif: proc { |subject| ::Helpers::JobHelper.correct_feature?(subject, 'leapp_preupgrade') }
36
+ onlyif: proc { |subject|
37
+ ::Helpers::JobHelper.correct_feature?(subject, 'leapp_preupgrade') ||
38
+ ::Helpers::JobHelper.correct_feature?(subject, 'leapp_remediation_plan')
39
+ }
37
40
  end
38
41
  end
39
42
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ForemanLeapp
4
- VERSION = '0.0.6'
4
+ VERSION = '0.1.0'
5
5
  end
data/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "scripts": {
7
7
  "lint": "tfm-lint --plugin -d /webpack",
8
8
  "link-leapp-js": "./script/link_leapp_js.sh",
9
- "test": "tfm-test --plugin",
9
+ "test": "tfm-test --plugin --config jest.config.js",
10
10
  "test:watch": "tfm-test --plugin --watchAll",
11
11
  "test:current": "tfm-test --plugin --watch",
12
12
  "publish-coverage": "tfm-publish-coverage",
@@ -32,10 +32,11 @@
32
32
  "@theforeman/builder": "^4.2.1",
33
33
  "@theforeman/eslint-plugin-foreman": "4.2.1",
34
34
  "@theforeman/stories": "^4.2.1",
35
- "@theforeman/vendor-dev": "^4.2.1",
36
35
  "@theforeman/test": "^4.2.1",
36
+ "@theforeman/vendor-dev": "^4.2.1",
37
37
  "babel-eslint": "^10.0.0",
38
38
  "eslint": "^6.8.0",
39
+ "jest-svg-transformer": "^1.0.0",
39
40
  "prettier": "^1.19.1"
40
41
  },
41
42
  "dependencies": {
@@ -7,24 +7,107 @@ module Api
7
7
  class PreupgradeReportsControllerTest < ActionController::TestCase
8
8
  setup do
9
9
  @host = FactoryBot.create(:host)
10
- @report = FactoryBot.create(:preupgrade_report, host: @host)
10
+ @job_invocation = FactoryBot.create(:job_invocation)
11
+ @report = FactoryBot.create(:preupgrade_report, host: @host, job_invocation: @job_invocation)
11
12
  @entry = FactoryBot.create(:preupgrade_report_entry, host: @host, preupgrade_report: @report)
12
13
  end
13
14
 
14
- test 'should get index' do
15
- get :index, session: set_session_user
15
+ test 'should get :index' do
16
+ get :index
16
17
  assert_response :success
17
- assert_not_empty ActiveSupport::JSON.decode(@response.body)['results']
18
+ assert_not_empty JSON.parse(@response.body)['results']
18
19
  end
19
20
 
20
- test 'should get detail of report and its entries' do
21
+ test 'should get :show' do
21
22
  get :show, params: { id: @report.id }
22
23
  assert_response :success
23
24
 
24
- response = ActiveSupport::JSON.decode(@response.body)
25
+ response = JSON.parse(@response.body)
25
26
  assert_equal response['id'], @report.id
26
27
  assert_not_empty response['preupgrade_report_entries']
27
28
  end
29
+
30
+ test 'should get :job_invocation' do
31
+ get :job_invocation, params: { id: @job_invocation.id }
32
+ assert_response :success
33
+ assert_not_empty JSON.parse(@response.body)['results']
34
+ end
35
+
36
+ context 'with permissions' do
37
+ setup do
38
+ @user = FactoryBot.create(:user, admin: false)
39
+ setup_user('view', 'job_invocations', nil, @user)
40
+ setup_user('view', 'hosts', nil, @user)
41
+ end
42
+
43
+ test 'should get :index' do
44
+ get :index, session: set_session_user(@user)
45
+ assert_response :success
46
+ assert_not_empty JSON.parse(@response.body)['results']
47
+ end
48
+
49
+ test 'should get :show' do
50
+ get :show, params: { id: @report.id }, session: set_session_user(@user)
51
+ assert_response :success
52
+ assert_equal @report.id, JSON.parse(@response.body)['id']
53
+ end
54
+
55
+ test 'should get :job_invocation' do
56
+ get :job_invocation, params: { id: @job_invocation.id }, session: set_session_user(@user)
57
+ assert_response :success
58
+ assert_not_empty JSON.parse(@response.body)['results']
59
+ end
60
+ end
61
+
62
+ context 'without :view_job_invocations' do
63
+ setup do
64
+ @user = FactoryBot.create(:user, admin: false)
65
+ setup_user('view', 'hosts', nil, @user)
66
+ end
67
+
68
+ test 'should not get :index' do
69
+ get :index, session: set_session_user(@user)
70
+ assert_response :forbidden
71
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_job_invocations'
72
+ end
73
+
74
+ test 'should not get :show' do
75
+ get :show, params: { id: @report.id }, session: set_session_user(@user)
76
+ assert_response :forbidden
77
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_job_invocations'
78
+ end
79
+
80
+ test 'should not get :job_invocation' do
81
+ get :job_invocation, params: { id: @job_invocation.id }, session: set_session_user(@user)
82
+ assert_response :forbidden
83
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_job_invocations'
84
+ end
85
+ end
86
+
87
+ context 'without :view_hosts' do
88
+ setup do
89
+ @user = FactoryBot.create(:user, admin: false)
90
+ setup_user('view', 'job_invocations', nil, @user)
91
+ end
92
+
93
+ test 'should not get :index' do
94
+ get :index, session: set_session_user(@user)
95
+ assert_response :forbidden
96
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_hosts'
97
+ end
98
+
99
+ test 'should not get :job_invocation' do
100
+ get :show, params: { id: @report.id }, session: set_session_user(@user)
101
+ assert_response :forbidden
102
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_hosts'
103
+ end
104
+
105
+ test 'should not get :job_invocation' do
106
+ get :job_invocation, params: { id: @job_invocation.id }, session: set_session_user(@user)
107
+ assert_response :forbidden
108
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_hosts'
109
+ end
110
+ end
28
111
  end
29
112
  end
30
113
  end
@@ -4,13 +4,36 @@ require 'test_plugin_helper'
4
4
 
5
5
  class PreupgradeReportsControllerTest < ActionController::TestCase
6
6
  setup do
7
+ @user = FactoryBot.create(:user, admin: false)
7
8
  @host = FactoryBot.create :host
8
9
  FactoryBot.create :preupgrade_report, host: @host
9
10
  end
10
11
 
11
- test 'should get index' do
12
- get :index, session: set_session_user
13
- response = ActiveSupport::JSON.decode(@response.body)
14
- assert_equal @host.id, response['results'].first['host_id']
12
+ test 'should get :index' do
13
+ get :index
14
+ assert_response :success
15
+ assert_not_empty JSON.parse(@response.body)['results']
16
+ end
17
+
18
+ test 'should get :index with :view_job_invocations & :view_hosts' do
19
+ setup_user 'view', 'job_invocations', nil, @user
20
+ setup_user 'view', 'hosts', nil, @user
21
+ get :index, session: set_session_user(@user)
22
+ assert_response :success
23
+ assert_not_empty JSON.parse(@response.body)['results']
24
+ end
25
+
26
+ test 'should not get :index without :view_job_invocations' do
27
+ setup_user 'view', 'hosts', nil, @user
28
+ get :index, session: set_session_user(@user)
29
+ assert_response :forbidden
30
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_job_invocations'
31
+ end
32
+
33
+ test 'should not get :index without :view_hosts' do
34
+ setup_user 'view', 'job_invocations', nil, @user
35
+ get :index, session: set_session_user(@user)
36
+ assert_response :forbidden
37
+ assert_includes JSON.parse(@response.body)['error']['missing_permissions'], 'view_hosts'
15
38
  end
16
39
  end
@@ -1 +1 @@
1
- export const translate = () => jest.fn();
1
+ export const translate = val => val;
@@ -0,0 +1 @@
1
+ export const EmptyStatePattern = () => jest.fn();
@@ -1,24 +1,118 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import MessageBox from 'foremanReact/components/common/MessageBox';
3
3
  import { LoadingState, Row } from 'patternfly-react';
4
4
  import PropTypes from 'prop-types';
5
5
 
6
6
  import PreupgradeReportsList from '../PreupgradeReportsList';
7
- import FixAllButton from './components/FixAllButton';
7
+ import UpgradeAllButton from './components/UpgradeAllButton';
8
+ import EntriesFilter from './components/EntriesFilter';
9
+ import FixSelectedButton from './components/FixSelectedButton';
8
10
 
9
11
  import {
10
12
  flattenEntries,
11
13
  isEmpty,
12
14
  anyEntriesFixable,
15
+ filterEntries,
16
+ idsForInvocationFromEntries,
17
+ sortEntries,
18
+ fixableEntries,
13
19
  } from './PreupgradeReportsHelpers';
14
20
 
21
+ import NoReports from './components/NoReports';
22
+
15
23
  const PreupgradeReports = ({
16
24
  preupgradeReports,
17
- loading,
18
- error,
19
25
  csrfToken,
20
26
  newJobInvocationUrl,
21
27
  }) => {
28
+ const [filterType, setFilterType] = useState('title');
29
+ const [filterValue, setFilterValue] = useState('');
30
+ const [checked, setChecked] = useState([]);
31
+ const [sort, setSort] = useState({ attribute: '', order: 'desc' });
32
+
33
+ const onFilterValueChange = value => {
34
+ setFilterValue(value);
35
+ };
36
+
37
+ const onFilterValueClear = () => setFilterValue('');
38
+
39
+ const onFilterTypeChange = value => {
40
+ onFilterValueClear();
41
+ setFilterType(value);
42
+ };
43
+
44
+ const isSelected = entry => checked.some(item => item.id === entry.id);
45
+
46
+ const anySelected = checked.length > 0;
47
+
48
+ const toggleSelected = (entry, isEntrySelected) => {
49
+ if (isEntrySelected) {
50
+ setChecked(checked.filter(item => item.id !== entry.id));
51
+ } else {
52
+ setChecked([entry, ...checked]);
53
+ }
54
+ };
55
+
56
+ const toggleSelectAll = () => {
57
+ const allFixable = fixableEntries(preupgradeReports);
58
+
59
+ if (checked.length === allFixable.length) {
60
+ setChecked([]);
61
+ } else {
62
+ setChecked(allFixable);
63
+ }
64
+ };
65
+
66
+ const changeSort = value => {
67
+ setSort({ ...sort, ...value });
68
+ };
69
+
70
+ return (
71
+ <React.Fragment>
72
+ <Row>
73
+ <div className="col-md-8">
74
+ <EntriesFilter
75
+ filterType={filterType}
76
+ onFilterTypeChange={onFilterTypeChange}
77
+ filterValue={filterValue}
78
+ onFilterValueChange={onFilterValueChange}
79
+ />
80
+ </div>
81
+ <div className="col-md-4">
82
+ <div className="btn-toolbar pull-right">
83
+ <FixSelectedButton
84
+ postUrl={newJobInvocationUrl}
85
+ disabled={!anyEntriesFixable(preupgradeReports) || !anySelected}
86
+ csrfToken={csrfToken}
87
+ ids={idsForInvocationFromEntries(checked)}
88
+ />
89
+ <UpgradeAllButton
90
+ postUrl={newJobInvocationUrl}
91
+ csrfToken={csrfToken}
92
+ preupgradeReports={preupgradeReports}
93
+ />
94
+ </div>
95
+ </div>
96
+ </Row>
97
+ <PreupgradeReportsList
98
+ allEntries={filterEntries(
99
+ filterType,
100
+ filterValue,
101
+ sortEntries(flattenEntries(preupgradeReports), sort)
102
+ )}
103
+ isSelected={isSelected}
104
+ toggleSelected={toggleSelected}
105
+ sort={sort}
106
+ changeSort={changeSort}
107
+ toggleSelectAll={toggleSelectAll}
108
+ />
109
+ </React.Fragment>
110
+ );
111
+ };
112
+
113
+ const withLoadingState = Component => props => {
114
+ const { error, loading, preupgradeReports, reportsExpected } = props;
115
+
22
116
  if (!isEmpty(error)) {
23
117
  return (
24
118
  <MessageBox
@@ -31,30 +125,19 @@ const PreupgradeReports = ({
31
125
 
32
126
  return (
33
127
  <LoadingState loading={loading}>
34
- <Row>
35
- <div className="title-filter col-md-4">&nbsp;</div>
36
- <div id="title_action" className="col-md-8">
37
- <div className="btn-toolbar pull-right">
38
- <FixAllButton
39
- postUrl={newJobInvocationUrl}
40
- disabled={!anyEntriesFixable(preupgradeReports)}
41
- csrfToken={csrfToken}
42
- preupgradeReports={preupgradeReports}
43
- />
44
- </div>
45
- </div>
46
- </Row>
47
- <PreupgradeReportsList allEntries={flattenEntries(preupgradeReports)} />
128
+ {preupgradeReports.length > 0 ? (
129
+ <Component {...props} />
130
+ ) : (
131
+ <NoReports reportsExpected={reportsExpected} />
132
+ )}
48
133
  </LoadingState>
49
134
  );
50
135
  };
51
136
 
52
137
  PreupgradeReports.propTypes = {
53
138
  preupgradeReports: PropTypes.array.isRequired,
54
- loading: PropTypes.bool.isRequired,
55
- error: PropTypes.object.isRequired,
56
139
  csrfToken: PropTypes.string.isRequired,
57
140
  newJobInvocationUrl: PropTypes.string.isRequired,
58
141
  };
59
142
 
60
- export default PreupgradeReports;
143
+ export default withLoadingState(PreupgradeReports);
@@ -7,7 +7,7 @@ import {
7
7
  PREUPGRADE_REPORTS_FAILURE,
8
8
  } from '../../consts';
9
9
 
10
- export const getPreupgradeReports = url => async dispatch => {
10
+ export const getPreupgradeReportsAction = url => async dispatch => {
11
11
  dispatch({ type: PREUPGRADE_REPORTS_REQUEST });
12
12
 
13
13
  try {