foreman_discovery 16.3.5 → 17.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/discovered_hosts_controller.rb +1 -0
  3. data/app/controllers/discovered_hosts_controller.rb +24 -35
  4. data/app/controllers/discovery_rules_controller.rb +12 -1
  5. data/app/helpers/discovered_hosts_helper.rb +1 -1
  6. data/app/helpers/discovery_rules_helper.rb +1 -0
  7. data/app/models/discovery_rule.rb +10 -5
  8. data/app/services/foreman_discovery/fact_to_category_resolver.rb +106 -0
  9. data/app/services/foreman_discovery/ui_notifications/failed_discovery.rb +34 -0
  10. data/app/services/foreman_discovery/ui_notifications/new_host.rb +2 -1
  11. data/app/views/discovered_hosts/_discovered_hosts_list.html.erb +44 -40
  12. data/app/views/discovery_rules/clone.erb +3 -0
  13. data/app/views/discovery_rules/index.html.erb +4 -0
  14. data/app/views/discovery_rules/welcome.html.erb +15 -0
  15. data/app/views/foreman_discovery/debian_kexec.erb +1 -1
  16. data/app/views/foreman_discovery/redhat_kexec.erb +1 -1
  17. data/config/routes.rb +2 -0
  18. data/db/seeds.d/80_discovery_ui_notification.rb +11 -5
  19. data/lib/foreman_discovery/engine.rb +3 -7
  20. data/lib/foreman_discovery/version.rb +1 -1
  21. data/locale/ca/LC_MESSAGES/foreman_discovery.mo +0 -0
  22. data/locale/ca/foreman_discovery.edit.po +103 -70
  23. data/locale/ca/foreman_discovery.po +36 -9
  24. data/locale/de/LC_MESSAGES/foreman_discovery.mo +0 -0
  25. data/locale/de/foreman_discovery.edit.po +109 -76
  26. data/locale/de/foreman_discovery.po +45 -18
  27. data/locale/en/LC_MESSAGES/foreman_discovery.mo +0 -0
  28. data/locale/en/foreman_discovery.edit.po +98 -66
  29. data/locale/en/foreman_discovery.po +31 -4
  30. data/locale/en_GB/LC_MESSAGES/foreman_discovery.mo +0 -0
  31. data/locale/en_GB/foreman_discovery.edit.po +107 -74
  32. data/locale/en_GB/foreman_discovery.po +36 -9
  33. data/locale/es/LC_MESSAGES/foreman_discovery.mo +0 -0
  34. data/locale/es/foreman_discovery.edit.po +109 -76
  35. data/locale/es/foreman_discovery.po +70 -41
  36. data/locale/foreman_discovery.pot +142 -97
  37. data/locale/fr/LC_MESSAGES/foreman_discovery.mo +0 -0
  38. data/locale/fr/foreman_discovery.edit.po +151 -118
  39. data/locale/fr/foreman_discovery.po +76 -49
  40. data/locale/gl/LC_MESSAGES/foreman_discovery.mo +0 -0
  41. data/locale/gl/foreman_discovery.edit.po +101 -68
  42. data/locale/gl/foreman_discovery.po +32 -5
  43. data/locale/it/LC_MESSAGES/foreman_discovery.mo +0 -0
  44. data/locale/it/foreman_discovery.edit.po +107 -74
  45. data/locale/it/foreman_discovery.po +44 -17
  46. data/locale/ja/LC_MESSAGES/foreman_discovery.mo +0 -0
  47. data/locale/ja/foreman_discovery.edit.po +136 -103
  48. data/locale/ja/foreman_discovery.po +79 -54
  49. data/locale/ko/LC_MESSAGES/foreman_discovery.mo +0 -0
  50. data/locale/ko/foreman_discovery.edit.po +107 -74
  51. data/locale/ko/foreman_discovery.po +43 -16
  52. data/locale/pt_BR/LC_MESSAGES/foreman_discovery.mo +0 -0
  53. data/locale/pt_BR/foreman_discovery.edit.po +109 -76
  54. data/locale/pt_BR/foreman_discovery.po +69 -39
  55. data/locale/ru/LC_MESSAGES/foreman_discovery.mo +0 -0
  56. data/locale/ru/foreman_discovery.edit.po +112 -79
  57. data/locale/ru/foreman_discovery.po +43 -16
  58. data/locale/sv_SE/LC_MESSAGES/foreman_discovery.mo +0 -0
  59. data/locale/sv_SE/foreman_discovery.edit.po +101 -68
  60. data/locale/sv_SE/foreman_discovery.po +34 -7
  61. data/locale/zh_CN/LC_MESSAGES/foreman_discovery.mo +0 -0
  62. data/locale/zh_CN/foreman_discovery.edit.po +153 -120
  63. data/locale/zh_CN/foreman_discovery.po +114 -90
  64. data/locale/zh_TW/LC_MESSAGES/foreman_discovery.mo +0 -0
  65. data/locale/zh_TW/foreman_discovery.edit.po +107 -74
  66. data/locale/zh_TW/foreman_discovery.po +43 -16
  67. data/package.json +6 -6
  68. data/test/functional/api/v2/discovered_hosts_controller_test.rb +9 -0
  69. data/test/functional/discovery_rules_controller_test.rb +6 -1
  70. data/test/integration/discovered_hosts_test.rb +53 -5
  71. data/test/test_helper_discovery.rb +5 -0
  72. data/test/unit/discovery_rule_test.rb +24 -2
  73. data/test/unit/fact_to_category_resolver_test.rb +41 -0
  74. data/test/unit/ui_notifications/destroy_host_test.rb +2 -9
  75. data/test/unit/ui_notifications/new_host_test.rb +3 -3
  76. data/webpack/__mocks__/foremanReact/common/I18n.js +3 -0
  77. data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
  78. data/webpack/__mocks__/foremanReact/common/index.js +5 -0
  79. data/webpack/__mocks__/foremanReact/components/common/EmptyState/DefaultEmptyState.js +69 -0
  80. data/webpack/__mocks__/foremanReact/components/common/EmptyState/EmptyStatePattern.js +77 -0
  81. data/webpack/__mocks__/foremanReact/components/common/EmptyState/EmptyStatePropTypes.js +29 -0
  82. data/webpack/__mocks__/foremanReact/components/common/EmptyState/index.js +5 -0
  83. data/webpack/index.js +9 -8
  84. data/webpack/src/ForemanDiscovery/DiscoveredHosts/Components/EmptyState/EmptyState.js +7 -7
  85. data/webpack/src/ForemanDiscovery/DiscoveredHosts/Components/EmptyState/__test__/EmptyState.test.js +12 -0
  86. data/webpack/src/ForemanDiscovery/DiscoveredHosts/Components/EmptyState/__test__/__snapshots__/EmptyState.test.js.snap +16 -0
  87. data/webpack/src/ForemanDiscovery/DiscoveredHosts/Components/EmptyState/index.js +1 -1
  88. data/webpack/src/ForemanDiscovery/DiscoveredHosts/index.js +3 -3
  89. data/webpack/src/ForemanDiscovery/DiscoveryRules/Components/EmptyState/EmptyState.js +34 -0
  90. data/webpack/src/ForemanDiscovery/DiscoveryRules/Components/EmptyState/index.js +1 -0
  91. data/webpack/src/ForemanDiscovery/DiscoveryRules/Components/__test__/EmptyState.test.js +19 -0
  92. data/webpack/src/ForemanDiscovery/DiscoveryRules/Components/__test__/__snapshots__/EmptyState.test.js.snap +22 -0
  93. data/webpack/src/ForemanDiscovery/DiscoveryRules/index.js +6 -0
  94. data/webpack/src/reducers.js +0 -2
  95. metadata +32 -10
@@ -3,6 +3,7 @@ require 'integration_test_helper'
3
3
 
4
4
  class DiscoveredHostsTest < IntegrationTestWithJavascript
5
5
  let(:discovered_host) { FactoryBot.create(:discovered_host, :with_facts) }
6
+ let(:subnet) { FactoryBot.create(:subnet_ipv4, :network => "192.168.100.0") }
6
7
  let(:discovered_hosts) { Host::Discovered.all }
7
8
 
8
9
  setup do
@@ -17,8 +18,9 @@ class DiscoveredHostsTest < IntegrationTestWithJavascript
17
18
  Host::Discovered.destroy_all
18
19
  end
19
20
 
20
- describe 'Multiple host Reboot' do
21
- test 'triggers reboot on all discovered_hosts' do
21
+ describe 'Perform host Reboot' do
22
+ test 'triggers reboot on a single discovered_host' do
23
+ Host::Discovered.any_instance.stubs(:subnet).returns(subnet)
22
24
  Host::Discovered.any_instance
23
25
  .expects(:reboot)
24
26
  .at_least(discovered_hosts.count)
@@ -29,24 +31,70 @@ class DiscoveredHostsTest < IntegrationTestWithJavascript
29
31
  assert page.has_text?('The following hosts are about to be changed')
30
32
  page.find_button('Submit').click
31
33
  end
34
+
35
+ test 'triggers reboot on all discovered_hosts' do
36
+ Host::Discovered.any_instance.stubs(:subnet).returns(subnet)
37
+ Host::Discovered.any_instance
38
+ .expects(:reboot)
39
+ .at_least(discovered_hosts.count)
40
+ select_all_hosts
41
+ page.find_link('Select Action').click
42
+ page.find_link('Reboot').click
43
+ wait_for_ajax
44
+ assert page.has_text?('The following hosts are about to be changed')
45
+ page.find_button('Submit').click
46
+ end
47
+
48
+ test 'shows warning for all hosts with missing subnet' do
49
+ select_all_hosts
50
+ page.find_link('Select Action').click
51
+ page.find_link('Auto Provision').click
52
+ wait_for_ajax
53
+ assert page.has_text?('The following hosts are about to be changed')
54
+ page.find_button('Submit').click
55
+ wait_for_ajax
56
+ assert page.has_text?("Discovered hosts reported from unknown subnet")
57
+ end
32
58
  end
33
59
 
34
- describe 'Multiple host Autoprovision' do
35
- test 'converts all discovered to managed hosts' do
60
+ describe 'Perform host Autoprovision' do
61
+ test 'converts single discovered to managed host' do
62
+ Host::Discovered.any_instance.stubs(:subnet).returns(subnet)
36
63
  select_host_checkbox(discovered_host.id)
37
64
  page.find_link('Select Action').click
38
65
  page.find_link('Auto Provision').click
39
66
  wait_for_ajax
40
67
  assert page.has_text?('The following hosts are about to be changed')
41
68
  page.find_button('Submit').click
69
+ end
70
+
71
+ test 'converts all discovered to managed hosts' do
72
+ Host::Discovered.any_instance.stubs(:subnet).returns(subnet)
73
+ select_all_hosts
74
+ page.find_link('Select Action').click
75
+ page.find_link('Auto Provision').click
76
+ wait_for_ajax
77
+ assert page.has_text?('The following hosts are about to be changed')
78
+ page.find_button('Submit').click
42
79
  wait_for_ajax
43
80
  assert page.has_text?('Discovered hosts are provisioning now')
44
81
  end
82
+
83
+ test 'shows warning for all hosts with missing subnet' do
84
+ select_all_hosts
85
+ page.find_link('Select Action').click
86
+ page.find_link('Auto Provision').click
87
+ wait_for_ajax
88
+ assert page.has_text?('The following hosts are about to be changed')
89
+ page.find_button('Submit').click
90
+ wait_for_ajax
91
+ assert page.has_text?("Discovered hosts reported from unknown subnet")
92
+ end
45
93
  end
46
94
 
47
95
  describe 'Delete hosts' do
48
96
  test 'it removes all hosts' do
49
- select_host_checkbox(discovered_host.id)
97
+ select_all_hosts
50
98
  page.find_link('Select Action').click
51
99
  page.find_link('Delete').click
52
100
  wait_for_ajax
@@ -172,6 +172,11 @@ def discovered_notification_blueprint
172
172
  name: 'new_discovered_host')
173
173
  end
174
174
 
175
+ def failed_discovery_blueprint
176
+ @blueprint ||= FactoryBot.create(:notification_blueprint,
177
+ name: 'failed_discovery')
178
+ end
179
+
175
180
  def parse_json_fixture(filename, remove_root_element = false)
176
181
  raw = JSON.parse(File.read(File.expand_path(File.dirname(__FILE__) + "/facts/#{filename}.json")))
177
182
  remove_root_element ? raw['facts'] : raw
@@ -155,7 +155,7 @@ class DiscoveryRuleTest < ActiveSupport::TestCase
155
155
  :search => "cpu_count > 1",
156
156
  :hostgroup_id => @hostgroup.id
157
157
  refute_valid rule
158
- assert_equal "Host group organization #{organization_one.name} must also be associated to the discovery rule", rule.errors[:organizations].first
158
+ assert_equal "Host group organization #{organization_one.name} must also be associated to the discovery rule", rule.errors[:base].first
159
159
  end
160
160
 
161
161
  test "should enforce hostgroup organizations and locations" do
@@ -168,7 +168,7 @@ class DiscoveryRuleTest < ActiveSupport::TestCase
168
168
  :organization_ids => [organization_one.id],
169
169
  :location_ids => [location_one.id]
170
170
  refute_valid rule
171
- assert_equal "Host group location #{loc.name} must also be associated to the discovery rule", rule.errors[:locations].first
171
+ assert_equal "Host group location #{loc.name} must also be associated to the discovery rule", rule.errors[:base].first
172
172
  end
173
173
 
174
174
  context 'auditing related to discovery rule' do
@@ -218,4 +218,26 @@ class DiscoveryRuleTest < ActiveSupport::TestCase
218
218
  assert_equal DiscoveryRule::STEP, second_new.priority - first_new.priority
219
219
  end
220
220
  end
221
+
222
+ context 'suggest next priority' do
223
+ setup do
224
+ @organization = FactoryBot.create(:organization)
225
+ @location = FactoryBot.create(:location)
226
+ end
227
+
228
+ test 'when there exists a discovery_rule of the given organization' do
229
+ hostgroup = FactoryBot.create(:hostgroup, organizations: [@organization], locations: [@location] )
230
+ discovery_rule = FactoryBot.create(:discovery_rule,
231
+ priority: rand(100),
232
+ hostgroup: hostgroup,
233
+ organizations: [@organization],
234
+ locations: [@location])
235
+
236
+ assert_equal DiscoveryRule.suggest_priority(@organization), (discovery_rule.priority + DiscoveryRule::STEP)
237
+ end
238
+
239
+ test 'when there is no discovery_rule of the given organization' do
240
+ assert_equal DiscoveryRule.suggest_priority(@organization), DiscoveryRule::STEP
241
+ end
242
+ end
221
243
  end
@@ -0,0 +1,41 @@
1
+ require_relative '../test_plugin_helper'
2
+
3
+ class FactToCategoryResolverTest < ActiveSupport::TestCase
4
+ class FakePrimaryInterface < OpenStruct
5
+ def subnet
6
+ nil
7
+ end
8
+ end
9
+
10
+ setup do
11
+ interfaces = [{
12
+ identifier: 'eth0',
13
+ mac: 'aa:bb:cc:dd:ee:f1',
14
+ ip: '192.168.1.1',
15
+ }.with_indifferent_access]
16
+ facts = {
17
+ :macaddress_eth0 => 'aa:bb:cc:dd:ee:f1',
18
+ :ipaddress_eth0 => '192.168.1.1',
19
+ manufacturer: 'TEST-Creator',
20
+ hardwaremodel: 'TEST_X64',
21
+ bios_vendor: 'TEST_BIOS',
22
+ }.with_indifferent_access
23
+
24
+ @host = Host::Discovered.new(name: 'dummy')
25
+ @host.stubs(:facts_hash).returns(facts)
26
+ @host.stubs(:interfaces).returns(interfaces)
27
+ @host.stubs(:primary_interface).returns(FakePrimaryInterface.new)
28
+ @resolver = ForemanDiscovery::FactToCategoryResolver.new(@host)
29
+ end
30
+
31
+ test 'resolve facts to right category' do
32
+ categories = @resolver.categories
33
+ hardware_category = categories[2]
34
+ network_category = categories[3]
35
+ software_category = categories[4]
36
+
37
+ assert_equal hardware_category['hardwaremodel'], 'TEST_X64'
38
+ assert_equal network_category['ipaddress_eth0'], '192.168.1.1'
39
+ assert_equal software_category['bios_vendor'], 'TEST_BIOS'
40
+ end
41
+ end
@@ -21,13 +21,6 @@ class DestroyHostNotificationTest < ActiveSupport::TestCase
21
21
  ForemanDiscovery::UINotifications::NewHost.deliver!(host)
22
22
  end
23
23
  assert_equal 1, blueprint.notifications.count
24
- assert_no_difference('blueprint.notifications.count') do
25
- host = FactoryBot.create(:discovered_host)
26
- ForemanDiscovery::UINotifications::NewHost.deliver!(host)
27
- end
28
- assert_no_difference('blueprint.notifications.count') do
29
- Host::Discovered.all.last.destroy
30
- end
31
24
  Host::Discovered.destroy_all
32
25
  assert_equal 0, blueprint.notifications.count
33
26
  end
@@ -55,9 +48,9 @@ class DestroyHostNotificationTest < ActiveSupport::TestCase
55
48
  ForemanDiscovery::UINotifications::NewHost.deliver!(host1)
56
49
  host2 = FactoryBot.create(:discovered_host)
57
50
  ForemanDiscovery::UINotifications::NewHost.deliver!(host2)
58
- assert_equal 1, blueprint.notifications.count
51
+ assert_equal 2, blueprint.notifications.count
59
52
  new_host = ::ForemanDiscovery::HostConverter.to_managed(host1, false, false)
60
53
  assert new_host.save!
61
- assert_equal 1, blueprint.notifications.count
54
+ assert_equal 2, blueprint.notifications.count
62
55
  end
63
56
  end
@@ -16,14 +16,14 @@ class NewHostNotificationTest < ActiveSupport::TestCase
16
16
  end
17
17
  end
18
18
 
19
- test 'multiple discovered hosts should generate only one notification' do
19
+ test 'multiple discovered hosts should generate multiple notifications' do
20
20
  host1 = FactoryBot.create :discovered_host
21
21
  ForemanDiscovery::UINotifications::NewHost.deliver!(host1)
22
22
  expired_at = blueprint.notifications.first.expired_at
23
23
  Time.any_instance.stubs(:utc).returns(expired_at + 1.hour)
24
24
  host2 = FactoryBot.create :discovered_host
25
25
  ForemanDiscovery::UINotifications::NewHost.deliver!(host2)
26
- assert_equal 1, blueprint.notifications.count
27
- assert_not_equal expired_at, blueprint.notifications.first.expired_at
26
+ assert_equal 2, blueprint.notifications.count
27
+ assert_not_equal expired_at, blueprint.notifications.last.expired_at
28
28
  end
29
29
  end
@@ -0,0 +1,3 @@
1
+ export const translate = s => s;
2
+
3
+ export const ngettext = s => s;
@@ -0,0 +1 @@
1
+ export const foremanUrl = path => `${window.URL_PREFIX}${path}`;
@@ -0,0 +1,5 @@
1
+ import EmptyStatePattern from '../components/common/EmptyState/EmptyStatePattern';
2
+ import DefaultEmptyState from '../components/common/EmptyState/DefaultEmptyState';
3
+
4
+ export default DefaultEmptyState;
5
+ export { EmptyStatePattern };
@@ -0,0 +1,69 @@
1
+ import React from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import { push } from 'connected-react-router';
4
+ import { Button } from '@patternfly/react-core';
5
+ import EmptyStatePattern from './EmptyStatePattern';
6
+ import { defaultEmptyStatePropTypes } from './EmptyStatePropTypes';
7
+
8
+ const DefaultEmptyState = props => {
9
+ const {
10
+ icon,
11
+ iconType,
12
+ header,
13
+ description,
14
+ documentation,
15
+ action,
16
+ secondaryActions,
17
+ } = props;
18
+
19
+ const dispatch = useDispatch();
20
+ const actionButtonClickHandler = ({ url, onClick }) => {
21
+ if (onClick) onClick();
22
+ else if (url) dispatch(push(url));
23
+ };
24
+
25
+ const ActionButton = action ? (
26
+ <Button
27
+ component="a"
28
+ onClick={() => actionButtonClickHandler(action)}
29
+ variant="primary"
30
+ >
31
+ {action.title}
32
+ </Button>
33
+ ) : null;
34
+
35
+ const SecondaryButton = secondaryActions
36
+ ? secondaryActions.map(({ title, url, onClick }) => (
37
+ <Button
38
+ component="a"
39
+ key={`sec-button-${title}`}
40
+ onClick={() => actionButtonClickHandler({ url, onClick })}
41
+ variant="secondary"
42
+ >
43
+ {title}
44
+ </Button>
45
+ ))
46
+ : null;
47
+
48
+ return (
49
+ <EmptyStatePattern
50
+ icon={icon}
51
+ iconType={iconType}
52
+ header={header}
53
+ description={description}
54
+ documentation={documentation}
55
+ action={ActionButton}
56
+ secondaryActions={SecondaryButton}
57
+ />
58
+ );
59
+ };
60
+
61
+ DefaultEmptyState.propTypes = defaultEmptyStatePropTypes;
62
+
63
+ DefaultEmptyState.defaultProps = {
64
+ icon: 'add-circle-o',
65
+ secondaryActions: [],
66
+ iconType: 'pf',
67
+ };
68
+
69
+ export default DefaultEmptyState;
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import { Icon } from 'patternfly-react';
3
+ import {
4
+ Title,
5
+ EmptyState,
6
+ EmptyStateVariant,
7
+ EmptyStateBody,
8
+ EmptyStateSecondaryActions,
9
+ } from '@patternfly/react-core';
10
+ import { emptyStatePatternPropTypes } from './EmptyStatePropTypes';
11
+ import { translate as __ } from '../../../common/I18n';
12
+
13
+ const EmptyStatePattern = props => {
14
+ const {
15
+ documentation,
16
+ action,
17
+ secondaryActions,
18
+ iconType,
19
+ icon,
20
+ header,
21
+ description,
22
+ } = props;
23
+
24
+ const DocumentationBlock = () => {
25
+ if (!documentation) {
26
+ return null;
27
+ }
28
+ // The documentation prop can also be a customized node
29
+ if (React.isValidElement(documentation)) {
30
+ return documentation;
31
+ }
32
+ const {
33
+ label = __('For more information please see '), // eslint-disable-line react/prop-types
34
+ buttonLabel = __('documentation'), // eslint-disable-line react/prop-types
35
+ url, // eslint-disable-line react/prop-types
36
+ } = documentation;
37
+ return (
38
+ <span>
39
+ {label}
40
+ <a href={url}>{buttonLabel}</a>
41
+ </span>
42
+ );
43
+ };
44
+
45
+ return (
46
+ <EmptyState variant={EmptyStateVariant.xl}>
47
+ <span className="empty-state-icon">
48
+ {/* TODO: Add pf4 icons, Redmine issue: #30865 */}
49
+ <Icon name={icon} type={iconType} size="2x" />
50
+ </span>
51
+ <Title headingLevel="h5" size="4xl">
52
+ {header}
53
+ </Title>
54
+ <EmptyStateBody>
55
+ <div className="empty-state-description">{description}</div>
56
+ <DocumentationBlock />
57
+ </EmptyStateBody>
58
+ {action}
59
+ <EmptyStateSecondaryActions>
60
+ {secondaryActions}
61
+ </EmptyStateSecondaryActions>
62
+ </EmptyState>
63
+ );
64
+ };
65
+
66
+ EmptyStatePattern.propTypes = emptyStatePatternPropTypes;
67
+
68
+ EmptyStatePattern.defaultProps = {
69
+ icon: 'add-circle-o',
70
+ secondaryActions: [],
71
+ documentation: {
72
+ url: '#',
73
+ },
74
+ iconType: 'pf',
75
+ };
76
+
77
+ export default EmptyStatePattern;
@@ -0,0 +1,29 @@
1
+ import PropTypes from 'prop-types';
2
+
3
+ export const actionButtonPropTypes = {
4
+ title: PropTypes.node.isRequired,
5
+ url: PropTypes.string,
6
+ onChange: PropTypes.func,
7
+ };
8
+
9
+ export const emptyStatePatternPropTypes = {
10
+ icon: PropTypes.string.isRequired,
11
+ header: PropTypes.string.isRequired,
12
+ documentation: PropTypes.oneOfType([
13
+ PropTypes.shape({
14
+ label: PropTypes.string,
15
+ buttonLabel: PropTypes.string,
16
+ url: PropTypes.string.isRequired,
17
+ }),
18
+ PropTypes.node,
19
+ ]),
20
+ description: PropTypes.string.isRequired,
21
+ action: PropTypes.node,
22
+ secondaryActions: PropTypes.node,
23
+ };
24
+
25
+ export const defaultEmptyStatePropTypes = {
26
+ ...emptyStatePatternPropTypes,
27
+ action: PropTypes.shape(actionButtonPropTypes),
28
+ secondaryActions: PropTypes.arrayOf(PropTypes.shape(actionButtonPropTypes)),
29
+ };
@@ -0,0 +1,5 @@
1
+ import EmptyStatePattern from './EmptyStatePattern';
2
+ import DefaultEmptyState from './DefaultEmptyState';
3
+
4
+ export default DefaultEmptyState;
5
+ export { EmptyStatePattern };
data/webpack/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  /* eslint import/no-unresolved: [2, { ignore: [foremanReact/*] }] */
2
2
  /* eslint-disable import/no-extraneous-dependencies */
3
3
  /* eslint-disable import/extensions */
4
- import componentRegistry from "foremanReact/components/componentRegistry";
5
- import { registerReducer } from "foremanReact/common/MountingService";
6
- import reducers from "./src/reducers";
7
- import DiscoveredHosts from "./src/ForemanDiscovery/DiscoveredHosts";
4
+ import componentRegistry from 'foremanReact/components/componentRegistry';
5
+ import { registerReducer } from 'foremanReact/common/MountingService';
6
+ import reducers from './src/reducers';
7
+ import DiscoveredHosts from './src/ForemanDiscovery/DiscoveredHosts';
8
+ import DiscoveryRules from './src/ForemanDiscovery/DiscoveryRules';
8
9
 
9
10
  // register reducers
10
11
  Object.entries(reducers).forEach(([key, reducer]) =>
@@ -12,7 +13,7 @@ Object.entries(reducers).forEach(([key, reducer]) =>
12
13
  );
13
14
 
14
15
  // register components for erb mounting
15
- componentRegistry.register({
16
- name: "DiscoveredHosts",
17
- type: DiscoveredHosts,
18
- });
16
+ componentRegistry.registerMultiple([
17
+ { name: 'DiscoveredHosts', type: DiscoveredHosts },
18
+ { name: 'DiscoveryRules', type: DiscoveryRules },
19
+ ]);
@@ -1,11 +1,11 @@
1
- import React from "react";
2
- import PropTypes from "prop-types";
3
- import { translate as __ } from "foremanReact/common/I18n";
4
- import ForemanEmptyState from "foremanReact/components/common/EmptyState";
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import ForemanEmptyState from 'foremanReact/components/common/EmptyState';
5
5
 
6
- const EmptyState = (props) => {
6
+ const EmptyState = props => {
7
7
  const description = __(
8
- "No discovered hosts found in this context. This page shows discovered bare-metal or virtual nodes waiting to be provisioned."
8
+ 'No discovered hosts found in this context. This page shows discovered bare-metal or virtual nodes waiting to be provisioned.'
9
9
  );
10
10
  const documentation = {
11
11
  url: props.docUrl,
@@ -22,7 +22,7 @@ const EmptyState = (props) => {
22
22
  };
23
23
 
24
24
  EmptyState.propTypes = {
25
- docUrl: PropTypes.string,
25
+ docUrl: PropTypes.string.isRequired,
26
26
  };
27
27
 
28
28
  export default EmptyState;
@@ -0,0 +1,12 @@
1
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
+
3
+ import ForemanEmptyState from '../EmptyState';
4
+
5
+ const fixtures = {
6
+ 'render with Props': { docUrl: 'https://foreman.example.com' },
7
+ };
8
+
9
+ describe('ForemanEmptyState', () => {
10
+ describe('rendering', () =>
11
+ testComponentSnapshotsWithFixtures(ForemanEmptyState, fixtures));
12
+ });
@@ -0,0 +1,16 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`ForemanEmptyState rendering render with Props 1`] = `
4
+ <DefaultEmptyState
5
+ description="No discovered hosts found in this context. This page shows discovered bare-metal or virtual nodes waiting to be provisioned."
6
+ documentation={
7
+ Object {
8
+ "url": "https://foreman.example.com",
9
+ }
10
+ }
11
+ header="Foreman Discovery"
12
+ icon="gears"
13
+ iconType="fa"
14
+ secondaryActions={Array []}
15
+ />
16
+ `;
@@ -1 +1 @@
1
- export { default } from "./EmptyState";
1
+ export { default } from './EmptyState';
@@ -1,6 +1,6 @@
1
- import React from "react";
2
- import EmptyState from "./Components/EmptyState";
1
+ import React from 'react';
2
+ import EmptyState from './Components/EmptyState';
3
3
 
4
- const DiscoveredHosts = ({ docUrl }) => <EmptyState docUrl={docUrl} />;
4
+ const DiscoveredHosts = props => <EmptyState {...props} />;
5
5
 
6
6
  export default DiscoveredHosts;
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import ForemanEmptyState from 'foremanReact/components/common/EmptyState';
5
+ import { foremanUrl } from 'foremanReact/common/helpers';
6
+
7
+ const EmptyState = props => {
8
+ const action = {
9
+ title: __('Create Rule'),
10
+ url: foremanUrl('/discovery_rules/new'),
11
+ };
12
+ const description = __(
13
+ 'No Discovery Rules found in this context. Create Discovery Rules to perform automated provisioning on Discovered Hosts'
14
+ );
15
+ const documentation = {
16
+ url: props.docUrl,
17
+ };
18
+ return (
19
+ <ForemanEmptyState
20
+ header="Discovery Rules"
21
+ description={description}
22
+ icon="gavel"
23
+ iconType="fa"
24
+ documentation={documentation}
25
+ action={action}
26
+ />
27
+ );
28
+ };
29
+
30
+ EmptyState.propTypes = {
31
+ docUrl: PropTypes.string.isRequired,
32
+ };
33
+
34
+ export default EmptyState;
@@ -0,0 +1 @@
1
+ export { default } from './EmptyState';
@@ -0,0 +1,19 @@
1
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
+ import ForemanEmptyState from '../EmptyState';
3
+
4
+ const action = {
5
+ title: 'action-title',
6
+ url: `action-url`,
7
+ };
8
+
9
+ const fixtures = {
10
+ 'render with Props': {
11
+ docUrl: 'https://foreman.example.com',
12
+ action,
13
+ },
14
+ };
15
+
16
+ describe('ForemanEmptyState', () => {
17
+ describe('rendering', () =>
18
+ testComponentSnapshotsWithFixtures(ForemanEmptyState, fixtures));
19
+ });
@@ -0,0 +1,22 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`ForemanEmptyState rendering render with Props 1`] = `
4
+ <DefaultEmptyState
5
+ action={
6
+ Object {
7
+ "title": "Create Rule",
8
+ "url": "/discovery_rules/new",
9
+ }
10
+ }
11
+ description="No Discovery Rules found in this context. Create Discovery Rules to perform automated provisioning on Discovered Hosts"
12
+ documentation={
13
+ Object {
14
+ "url": "https://foreman.example.com",
15
+ }
16
+ }
17
+ header="Discovery Rules"
18
+ icon="gavel"
19
+ iconType="fa"
20
+ secondaryActions={Array []}
21
+ />
22
+ `;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import EmptyState from './Components/EmptyState';
3
+
4
+ const DiscoveryRules = props => <EmptyState {...props} />;
5
+
6
+ export default DiscoveryRules;
@@ -1,5 +1,3 @@
1
- import { combineReducers } from "redux";
2
-
3
1
  const reducers = {
4
2
  foremanDiscovery: {},
5
3
  };