foreman_leapp 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8841245ebb39f45ba92ea5b955a3398434dc103a860c50f2930019c0ee102a4e
4
- data.tar.gz: 9a13e0df52eb412eecff7dfa1adff514670ada9dec259a9e349eef1b5ec9e092
3
+ metadata.gz: ecb63f3a728352dd6759763bc7d4509a62c94e82ed4f75220779a2287f0584f4
4
+ data.tar.gz: 2e72adbd90f3b9e5008829add44066a28808c2ab8a6c01d1a5a085337a70e0bb
5
5
  SHA512:
6
- metadata.gz: 37b092dbdd94f413763b9301c29c552a55cc44a0a0881462fe228d0f1b3510f750145b20eb55b459bc6acf09212b40381657749eaf0590a49024dbd79c9b097a
7
- data.tar.gz: f93aca5a49a5c5f76fc36759310c0372f7e56b418ee2bad597461700ba9055544c9bdcb0b5ffab9561eddf7121dc0ad80af0061ee5557944f2d71b2af2927be2
6
+ metadata.gz: 8b925115f15c6c097bab69fb476eacb476d4cbfb54d1557abd41109720ac0e55d2cab0a155cdbc0aa8a40c0d7f5a303041083b33bac32ad83ec3af8e266b18d4
7
+ data.tar.gz: 8d92474522623af8c6f1e9fe6e29a93ad991d662dd9b66964b86c6ee1f8d3dcdd63523f272e04cd667d1454131ffbac92990efde2adbd7c7be81118ae43aa1fb
data/Rakefile CHANGED
@@ -6,21 +6,6 @@ begin
6
6
  rescue LoadError
7
7
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
8
8
  end
9
- begin
10
- require 'rdoc/task'
11
- rescue LoadError
12
- require 'rdoc/rdoc'
13
- require 'rake/rdoctask'
14
- RDoc::Task = Rake::RDocTask
15
- end
16
-
17
- RDoc::Task.new(:rdoc) do |rdoc|
18
- rdoc.rdoc_dir = 'rdoc'
19
- rdoc.title = 'ForemanLeapp'
20
- rdoc.options << '--line-numbers'
21
- rdoc.rdoc_files.include('README.rdoc')
22
- rdoc.rdoc_files.include('lib/**/*.rb')
23
- end
24
9
 
25
10
  APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
26
11
 
@@ -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.3.0'
4
+ VERSION = '4.0.0'
5
5
  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
  })}
@@ -60,6 +60,49 @@ export const preupgradeReports = [
60
60
  },
61
61
  ];
62
62
 
63
+ export const preupgradeReportsWithFixableEntries = [
64
+ {
65
+ hostId: 5,
66
+ entries: [
67
+ {
68
+ title: 'Fixable entry',
69
+ severity: 'high',
70
+ id: 100,
71
+ hostId: 5,
72
+ hostname: 'host.example.com',
73
+ flags: [],
74
+ detail: {
75
+ remediations: [{ type: 'command', context: ['echo', 'fix'] }],
76
+ },
77
+ },
78
+ {
79
+ title: 'Not fixable entry',
80
+ severity: 'low',
81
+ id: 101,
82
+ hostId: 5,
83
+ hostname: 'host.example.com',
84
+ flags: [],
85
+ },
86
+ ],
87
+ },
88
+ {
89
+ hostId: 6,
90
+ entries: [
91
+ {
92
+ title: 'Another fixable entry',
93
+ severity: 'medium',
94
+ id: 102,
95
+ hostId: 6,
96
+ hostname: 'foo.example.com',
97
+ flags: [],
98
+ detail: {
99
+ remediations: [{ type: 'command', context: ['echo', 'fix2'] }],
100
+ },
101
+ },
102
+ ],
103
+ },
104
+ ];
105
+
63
106
  export const reportsWithRemediations = [
64
107
  {
65
108
  hostId: 5,
@@ -1,54 +1,159 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
1
+ import React from 'react';
2
+ import { Provider } from 'react-redux';
3
+ import configureMockStore from 'redux-mock-store';
4
+ import { fireEvent, render, screen } from '@testing-library/react';
5
+ import '@testing-library/jest-dom';
2
6
 
3
7
  import PreupgradeReports from '../PreupgradeReports';
8
+ import {
9
+ preupgradeReports,
10
+ preupgradeReportsWithFixableEntries,
11
+ } from './PreupgradeReports.fixtures';
4
12
 
5
- import { preupgradeReports } from './PreupgradeReports.fixtures';
13
+ jest.mock('foremanReact/components/Pagination', () => {
14
+ const MockPagination = () => <div data-testid="pagination">Pagination</div>;
15
+ return MockPagination;
16
+ });
17
+
18
+ jest.mock('../../PreupgradeReportsList/components/images/i_severity-high.svg', () => 'severity-high.svg');
19
+ jest.mock('../../PreupgradeReportsList/components/images/i_severity-med.svg', () => 'severity-med.svg');
20
+ jest.mock('../../PreupgradeReportsList/components/images/i_severity-low.svg', () => 'severity-low.svg');
21
+
22
+ const mockStore = configureMockStore([]);
6
23
 
7
24
  const csrfToken = 'xyz';
8
25
  const newJobInvocationUrl = '/job_invocations/new';
9
- const getPreupgradeReports = () => {};
10
-
11
- const fixtures = {
12
- 'should render when loaded with reports': {
13
- loading: false,
14
- error: {},
15
- preupgradeReports,
16
- csrfToken,
17
- newJobInvocationUrl,
18
- getPreupgradeReports,
19
- reportsExpected: true,
20
- },
21
- 'should render when loaded without reports': {
22
- loading: false,
23
- error: {},
24
- preupgradeReports: [],
25
- csrfToken,
26
- newJobInvocationUrl,
27
- getPreupgradeReports,
28
- reportsExpected: true,
29
- },
30
- 'should render when loading': {
31
- loading: true,
32
- error: {},
33
- preupgradeReports: [],
34
- csrfToken,
35
- newJobInvocationUrl,
36
- getPreupgradeReports,
37
- reportsExpected: false,
38
- },
39
- 'should render error': {
40
- loading: false,
41
- error: {
42
- statusText: 'Internal server error',
43
- errorMsg: 'Well, this is embarassing',
44
- },
45
- preupgradeReports: [],
46
- csrfToken,
47
- newJobInvocationUrl,
48
- getPreupgradeReports,
49
- reportsExpected: false,
50
- },
26
+
27
+ const defaultProps = {
28
+ loading: false,
29
+ error: {},
30
+ preupgradeReports,
31
+ csrfToken,
32
+ newJobInvocationUrl,
33
+ reportsExpected: true,
51
34
  };
52
35
 
53
- describe('PreupgradeReports', () =>
54
- testComponentSnapshotsWithFixtures(PreupgradeReports, fixtures));
36
+ const renderComponent = (props = {}) =>
37
+ render(
38
+ <Provider store={mockStore({})}>
39
+ <PreupgradeReports {...defaultProps} {...props} />
40
+ </Provider>
41
+ );
42
+
43
+ const getFixSelectedForm = () =>
44
+ screen.getByRole('button', { name: 'Fix Selected' }).closest('form');
45
+
46
+ describe('PreupgradeReports', () => {
47
+ // withLoadingState wraps content in patternfly-react LoadingState, which
48
+ // delays showing the spinner by 300ms and always schedules that timeout on
49
+ // mount — even when loading=false. Without fake timers the timeout fires
50
+ // after the test unmounts and React logs a setState-on-unmounted warning.
51
+ beforeEach(() => {
52
+ jest.useFakeTimers();
53
+ });
54
+
55
+ afterEach(() => {
56
+ jest.runOnlyPendingTimers();
57
+ jest.useRealTimers();
58
+ });
59
+
60
+ it('renders report entries when loaded with reports', () => {
61
+ renderComponent();
62
+
63
+ expect(screen.getByText('Fix me!')).toBeInTheDocument();
64
+ expect(screen.getByText('I am broken too')).toBeInTheDocument();
65
+ expect(screen.getByText('Octocat is not happy')).toBeInTheDocument();
66
+ });
67
+
68
+ it('renders empty state when loaded without reports', () => {
69
+ renderComponent({ preupgradeReports: [] });
70
+
71
+ expect(
72
+ screen.getByRole('heading', {
73
+ name: 'No Preupgrade Report Available',
74
+ level: 5,
75
+ })
76
+ ).toBeInTheDocument();
77
+ expect(
78
+ screen.getByText(
79
+ 'The preupgrade report could not be generated, check the job details for the reason'
80
+ )
81
+ ).toBeInTheDocument();
82
+ });
83
+
84
+ it('renders loading state while data is being fetched', () => {
85
+ renderComponent({
86
+ loading: true,
87
+ preupgradeReports: [],
88
+ reportsExpected: false,
89
+ });
90
+
91
+ // LoadingState renders nothing until its 300ms timeout elapses.
92
+ jest.advanceTimersByTime(300);
93
+
94
+ expect(screen.getByText('Loading')).toBeInTheDocument();
95
+ expect(
96
+ screen.queryByRole('heading', {
97
+ name: 'No Preupgrade Report Available',
98
+ })
99
+ ).not.toBeInTheDocument();
100
+ });
101
+
102
+ it('renders error empty state when data retrieval fails', () => {
103
+ renderComponent({
104
+ error: {
105
+ statusText: 'Internal server error',
106
+ errorMsg: 'Unexpected error',
107
+ },
108
+ preupgradeReports: [],
109
+ reportsExpected: false,
110
+ });
111
+
112
+ expect(
113
+ screen.getByRole('heading', {
114
+ name: 'Could not retrieve data: Internal server error - Unexpected error',
115
+ level: 5,
116
+ })
117
+ ).toBeInTheDocument();
118
+ });
119
+
120
+ it('disables Fix Selected when no fixable entries are selected', () => {
121
+ renderComponent({ preupgradeReports: preupgradeReportsWithFixableEntries });
122
+
123
+ expect(screen.getByRole('button', { name: 'Fix Selected' })).toBeDisabled();
124
+ });
125
+
126
+ it('enables Fix Selected and passes selected entry ids when a fixable entry is selected', () => {
127
+ renderComponent({ preupgradeReports: preupgradeReportsWithFixableEntries });
128
+
129
+ const [, fixableEntryCheckbox] = screen.getAllByRole('checkbox');
130
+
131
+ fireEvent.click(fixableEntryCheckbox);
132
+
133
+ expect(screen.getByRole('button', { name: 'Fix Selected' })).toBeEnabled();
134
+ expect(
135
+ getFixSelectedForm().querySelector('input[name="inputs[remediation_ids]"]')
136
+ ).toHaveValue('100');
137
+ });
138
+
139
+ it('selects all fixable entries when header checkbox is clicked', () => {
140
+ renderComponent({ preupgradeReports: preupgradeReportsWithFixableEntries });
141
+
142
+ const [selectAllCheckbox] = screen.getAllByRole('checkbox');
143
+
144
+ fireEvent.click(selectAllCheckbox);
145
+
146
+ expect(screen.getByRole('button', { name: 'Fix Selected' })).toBeEnabled();
147
+
148
+ const form = getFixSelectedForm();
149
+
150
+ expect(
151
+ form.querySelector('input[name="inputs[remediation_ids]"]')
152
+ ).toHaveValue('100,102');
153
+ expect(
154
+ [...form.querySelectorAll('input[name="host_ids[]"]')].map(
155
+ input => input.value
156
+ )
157
+ ).toEqual(['5', '6']);
158
+ });
159
+ });
@@ -9,6 +9,7 @@ exports[`NoReports should render when reports expected 1`] = `
9
9
  icon="warning-triangle-o"
10
10
  iconType="pf"
11
11
  secondaryActions={Array []}
12
+ variant="xl"
12
13
  />
13
14
  `;
14
15
 
@@ -21,5 +22,6 @@ exports[`NoReports should render when reports not expected 1`] = `
21
22
  icon="in-progress"
22
23
  iconType="pf"
23
24
  secondaryActions={Array []}
25
+ variant="xl"
24
26
  />
25
27
  `;
@@ -3,3 +3,12 @@
3
3
  white-space: pre-wrap;
4
4
  }
5
5
  }
6
+
7
+ .leapp-expanded-tbody {
8
+ border: 1px solid var(--pf-v5-global--BorderColor--100);
9
+ box-shadow: var(--pf-v5-global--BoxShadow--sm);
10
+
11
+ tr:first-child > td {
12
+ background-color: var(--pf-v5-global--primary-color--light-background, #e7f1fa);
13
+ }
14
+ }