foreman_leapp 0.0.6 → 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 (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 {