foreman_host_reports 0.0.3 → 0.0.4

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/host_reports_controller.rb +5 -2
  3. data/app/controllers/concerns/foreman_host_reports/controller/parameters/host_report.rb +2 -1
  4. data/app/models/host_report.rb +1 -31
  5. data/app/models/report_keyword.rb +5 -0
  6. data/app/views/api/v2/host_reports/main.json.rabl +2 -1
  7. data/db/migrate/20211011141813_change_status_column.rb +10 -0
  8. data/db/seeds.d/60-reports_feature.rb +2 -0
  9. data/lib/foreman_host_reports/version.rb +1 -1
  10. data/package.json +10 -10
  11. data/test/controllers/api/v2/host_reports_controller_test.rb +43 -20
  12. data/test/factories/foreman_host_reports_factories.rb +4 -1
  13. data/webpack/__mocks__/foremanReact/common/HOC.js +30 -0
  14. data/webpack/__mocks__/foremanReact/common/I18n.js +7 -0
  15. data/webpack/__mocks__/foremanReact/common/helpers.js +7 -0
  16. data/webpack/__mocks__/foremanReact/common/urlHelpers.js +8 -0
  17. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js +2 -0
  18. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js +10 -0
  19. data/webpack/__mocks__/foremanReact/components/ForemanModal/index.js +23 -0
  20. data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +4 -0
  21. data/webpack/__mocks__/foremanReact/components/common/EmptyState.js +5 -0
  22. data/webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js +7 -0
  23. data/webpack/__mocks__/foremanReact/components/common/forms/ForemanForm.js +9 -0
  24. data/webpack/__mocks__/foremanReact/components/common/forms/FormField.js +3 -0
  25. data/webpack/__mocks__/foremanReact/components/common/table.js +26 -0
  26. data/webpack/__mocks__/foremanReact/constants.js +24 -0
  27. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +6 -0
  28. data/webpack/__mocks__/foremanReact/redux/API/index.js +10 -0
  29. data/webpack/__mocks__/foremanReact/redux/actions/common/forms.js +1 -0
  30. data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +8 -0
  31. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
  32. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/components/ExportButton/ExportButton.js +10 -0
  33. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/statusFormatter.js +9 -3
  34. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/StatusCell.js +24 -66
  35. data/webpack/src/Router/HostReports/IndexPage/__tests__/HostReportsIndexPage.test.js +40 -0
  36. data/webpack/src/Router/HostReports/IndexPage/__tests__/__snapshots__/HostReportsIndexPage.test.js.snap +67 -0
  37. metadata +27 -3
  38. data/webpack/test_setup.js +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df2c9864c95d5918c25782bc4fd90c8b19dbf3417bb7f0b00d1350ca55adc399
4
- data.tar.gz: 4c7d17ca2b8dbb6ffda51dbf35d92c83ae523dc7b295067076f3d9579dd5da82
3
+ metadata.gz: 488222c4e90b62231ba01365932c00ccb2619ea7f8225757bf84c423cd56b13a
4
+ data.tar.gz: 39f11ab9a01e780bff2105f09726f95b564d45169b6c9c2466e9846f0433aff8
5
5
  SHA512:
6
- metadata.gz: 41eb977481bf368bbcafdc37adad69a0cb85f3b61d052e549af5aadcaa35e33746317663f25ffbdbb34e4cf9583b315d52ccaed031334be946adcd8cd23bd907
7
- data.tar.gz: 26ff05b79fbf665b9858c11eebbe5082ed479afc0bbd3810efa0950fb10fde3e51c2e092e431b1bbf52749a57f044da777e3cdf5bf2ed7fac223c9cb703998ed
6
+ metadata.gz: 9970feb6d0dd2593977eded7cade385a1a17743da6af330f3c5fb1c338dd62a63716b07b19bc5fd7fef1b22bd20147d08d9fc41639f570c6c2b43cd02100a36d
7
+ data.tar.gz: 76f80c8de18583d372beee125c791f8eef7fd41bbcb501c318951ab7708a311eb6905f3e94b5df841700c9c2f15a1157985cb23d8ea5661aac7f45d738286879
@@ -11,7 +11,7 @@ module Api
11
11
  before_action :find_resource, only: %i[destroy]
12
12
  before_action :resolve_ids, only: %i[create]
13
13
 
14
- add_smart_proxy_filters :create, features: proc { HostReport.authorized_smart_proxy_features }
14
+ add_smart_proxy_filters :create, features: ['Reports']
15
15
 
16
16
  api :GET, '/host_reports/', N_('List host reports')
17
17
  param_group :search_and_pagination, ::Api::V2::BaseController
@@ -34,7 +34,10 @@ module Api
34
34
  param :host, String, required: true, desc: N_("Hostname of the report's host origin")
35
35
  param :format, HostReport.formats.keys, required: false, desc: N_('Format of the report, e.g. Ansible')
36
36
  param :reported_at, String, required: true, desc: N_('UTC time of the report')
37
- param :status, Integer, required: true, desc: N_('Bitfield with arbitrary amount of status counters')
37
+ param :applied, Integer, required: false, desc: N_('Number of applied resources or tasks')
38
+ param :failed, Integer, required: false, desc: N_('Number of failed resources or tasks')
39
+ param :pending, Integer, required: false, desc: N_('Number of pending resources or tasks')
40
+ param :other, Integer, required: false, desc: N_('Number of other resources or tasks')
38
41
  param :body, String, required: true, desc: N_('String with JSON formatted body of the report')
39
42
  param :proxy, String, required: false, desc: N_('Hostname of the proxy processed the report')
40
43
  param :keywords, Array, of: String, required: false, desc: N_('A list of keywords to associate with the report for better searching')
@@ -10,7 +10,8 @@ module ForemanHostReports
10
10
  def host_report_params_filter
11
11
  Foreman::ParameterFilter.new(::HostReport).tap do |filter|
12
12
  # body is permitted in controller
13
- filter.permit :format, :version, :host, :proxy, :reported_at, :status, :proxy_id, :host_id
13
+ filter.permit :format, :version, :host, :proxy, :reported_at,
14
+ :proxy_id, :host_id, :applied, :failed, :pending, :other
14
15
  filter.permit :keywords => []
15
16
  end
16
17
  end
@@ -10,7 +10,7 @@ class HostReport < ApplicationRecord
10
10
  has_one :organization, through: :host
11
11
  has_one :location, through: :host
12
12
 
13
- validates :host_id, :reported_at, :status, presence: true
13
+ validates :host_id, :reported_at, presence: true
14
14
 
15
15
  enum format: {
16
16
  # plain text report (no processing)
@@ -20,10 +20,6 @@ class HostReport < ApplicationRecord
20
20
  ansible: 2,
21
21
  }.freeze
22
22
 
23
- STATUS = {
24
- plain: %w[debug normal warning error],
25
- }.freeze
26
-
27
23
  scoped_search relation: :host, on: :name, complete_value: true, rename: :host, aliases: %i[host_name]
28
24
  scoped_search relation: :proxy, on: :name, complete_value: true, rename: :proxy
29
25
  scoped_search relation: :organization, on: :name, complete_value: true, rename: :organization
@@ -45,36 +41,10 @@ class HostReport < ApplicationRecord
45
41
 
46
42
  default_scope -> { order('reported_at DESC') }
47
43
 
48
- # TODO: temporary until we decide what will status bitfiled be exactly
49
- # rubocop:disable Style/RescueModifier
50
- def status
51
- @status ||= case format
52
- when 'puppet'
53
- JSON.parse(body)['metrics']['resources']['values'] rescue []
54
- when 'ansible'
55
- JSON.parse(body)['status'] rescue {}
56
- else
57
- super
58
- end
59
- end
60
- # rubocop:enable Style/RescueModifier
61
-
62
44
  def report_keywords
63
45
  ReportKeyword.where(id: report_keyword_ids)
64
46
  end
65
47
 
66
- def self.authorized_smart_proxy_features
67
- @authorized_smart_proxy_features ||= %w[Puppet Ansible]
68
- end
69
-
70
- def self.register_smart_proxy_feature(feature)
71
- @authorized_smart_proxy_features = (authorized_smart_proxy_features + [feature]).uniq
72
- end
73
-
74
- def self.unregister_smart_proxy_feature(feature)
75
- @authorized_smart_proxy_features -= [feature]
76
- end
77
-
78
48
  def self.search_by_keyword(_key, operator, value)
79
49
  conditions = sanitize_sql_for_conditions(["report_keywords.name #{operator} ?", value_to_sql(operator, value)])
80
50
  keyword_ids = ReportKeyword.where(conditions).distinct.pluck(:id)
@@ -8,4 +8,9 @@ class ReportKeyword < ApplicationRecord
8
8
  def self.klass
9
9
  self
10
10
  end
11
+
12
+ # Needed for ActiveRecord to simulate ActiveRecord::Associations::CollectionProxy
13
+ def self.macro
14
+ :has_many
15
+ end
11
16
  end
@@ -5,7 +5,8 @@ object @host_report
5
5
  extends 'api/v2/host_reports/base'
6
6
  extends 'api/v2/layouts/permissions'
7
7
 
8
- attributes :format, :host_id, :proxy_id, :reported_at, :status
8
+ attributes :format, :host_id, :proxy_id, :reported_at, :applied, :failed,
9
+ :pending, :other
9
10
 
10
11
  node(:host_name) do |report|
11
12
  report.host.name
@@ -0,0 +1,10 @@
1
+ class ChangeStatusColumn < ActiveRecord::Migration[6.0]
2
+ def change
3
+ remove_column :host_reports, :status, :bigint
4
+
5
+ add_column :host_reports, :applied, :integer, default: 0
6
+ add_column :host_reports, :failed, :integer, default: 0
7
+ add_column :host_reports, :pending, :integer, default: 0
8
+ add_column :host_reports, :other, :integer, default: 0
9
+ end
10
+ end
@@ -0,0 +1,2 @@
1
+ f = Feature.where(name: 'Reports').first_or_create
2
+ raise "Unable to create host reports proxy feature: #{format_errors f}" if f.nil? || f.errors.any?
@@ -1,3 +1,3 @@
1
1
  module ForemanHostReports
2
- VERSION = '0.0.3'.freeze
2
+ VERSION = '0.0.4'.freeze
3
3
  end
data/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "lint": "tfm-lint --plugin -d /webpack",
8
- "test": "tfm-test --config jest.config.js",
8
+ "test": "tfm-test --plugin",
9
9
  "test:watch": "tfm-test --plugin --watchAll",
10
10
  "test:current": "tfm-test --plugin --watch",
11
11
  "publish-coverage": "tfm-publish-coverage",
@@ -21,25 +21,25 @@
21
21
  "url": "http://projects.theforeman.org/projects/foreman_host_reports/issues"
22
22
  },
23
23
  "peerDependencies": {
24
- "@theforeman/vendor": ">= 6.0.0"
24
+ "@theforeman/vendor": "^8.15.0"
25
25
  },
26
26
  "dependencies": {
27
- "react-intl": "^2.8.0",
28
27
  "react-json-tree": "^0.11.0"
29
28
  },
30
29
  "devDependencies": {
31
30
  "@babel/core": "^7.7.0",
32
31
  "@sheerun/mutationobserver-shim": "^0.3.3",
33
- "@theforeman/builder": "^8.4.1",
34
- "@theforeman/eslint-plugin-foreman": "8.4.1",
35
- "@theforeman/find-foreman": "^8.4.1",
36
- "@theforeman/stories": "^8.4.1",
37
- "@theforeman/test": "^8.4.1",
38
- "@theforeman/vendor-dev": "^8.4.1",
32
+ "@theforeman/builder": "^8.15.0",
33
+ "@theforeman/eslint-plugin-foreman": "^8.15.0",
34
+ "@theforeman/find-foreman": "^8.15.0",
35
+ "@theforeman/stories": "^8.15.0",
36
+ "@theforeman/test": "^8.15.0",
37
+ "@theforeman/vendor-dev": "^8.15.0",
39
38
  "babel-eslint": "^10.0.3",
40
39
  "eslint": "^6.7.2",
41
40
  "prettier": "^1.19.1",
42
41
  "stylelint-config-standard": "^18.0.0",
43
- "stylelint": "^9.3.0"
42
+ "stylelint": "^9.3.0",
43
+ "jed": "^1.1.1"
44
44
  }
45
45
  }
@@ -1,6 +1,15 @@
1
1
  require 'test_plugin_helper'
2
2
 
3
3
  class Api::V2::HostReportsControllerTest < ActionController::TestCase
4
+ setup do
5
+ @proxy = FactoryBot.create(:smart_proxy,
6
+ url: "http://reports.foreman.example.com",
7
+ features: [FactoryBot.create(:feature, name: 'Reports')])
8
+ Resolv.any_instance.stubs(:getnames).returns([URI.parse(@proxy.url).host])
9
+ SmartProxy.any_instance.stubs(:with_features).returns([@proxy])
10
+ ProxyAPI::V2::Features.any_instance.stubs(:features).returns({ "reports" => { "state" => "running" } })
11
+ end
12
+
4
13
  context 'when user does not have permission to view hosts' do
5
14
  let :host_report do
6
15
  as_admin { FactoryBot.create(:host_report) }
@@ -38,7 +47,7 @@ class Api::V2::HostReportsControllerTest < ActionController::TestCase
38
47
  post :create, params: {
39
48
  host_report: {
40
49
  host: host.name, body: report_body, reported_at: Time.current,
41
- status: 0
50
+ applied: 5, failed: 1, pending: 1, other: 0
42
51
  },
43
52
  }, session: set_session_user
44
53
  assert_response :success
@@ -50,12 +59,30 @@ class Api::V2::HostReportsControllerTest < ActionController::TestCase
50
59
  assert_response :unprocessable_entity
51
60
  end
52
61
 
62
+ test 'store statuses' do
63
+ User.current = nil
64
+ post :create, params: {
65
+ host_report: {
66
+ host: host.name, body: report_body, reported_at: Time.current,
67
+ keywords: %w[HasError HasFailedResource],
68
+ applied: 5, failed: 1, pending: 1, other: 6
69
+ },
70
+ }, session: set_session_user
71
+ report = ActiveSupport::JSON.decode(@response.body)
72
+ assert_response :created
73
+ assert_equal 5, report['applied']
74
+ assert_equal 1, report['failed']
75
+ assert_equal 1, report['pending']
76
+ assert_equal 6, report['other']
77
+ end
78
+
53
79
  test 'assign keywords' do
54
80
  User.current = nil
55
81
  post :create, params: {
56
82
  host_report: {
57
83
  host: host.name, body: report_body, reported_at: Time.current,
58
- status: 0, keywords: %w[HasError HasFailedResource]
84
+ keywords: %w[HasError HasFailedResource],
85
+ applied: 5, failed: 1, pending: 1, other: 0
59
86
  },
60
87
  }, session: set_session_user
61
88
  report = ActiveSupport::JSON.decode(@response.body)
@@ -69,14 +96,16 @@ class Api::V2::HostReportsControllerTest < ActionController::TestCase
69
96
  post :create, params: {
70
97
  host_report: {
71
98
  host: host.name, body: report_body, reported_at: Time.current,
72
- status: 0, keywords: %w[HasError HasFailedResource]
99
+ keywords: %w[HasError HasFailedResource],
100
+ applied: 5, failed: 1, pending: 1, other: 0
73
101
  },
74
102
  }, session: set_session_user
75
103
 
76
104
  post :create, params: {
77
105
  host_report: {
78
106
  host: host.name, body: report_body, reported_at: Time.current,
79
- status: 0, keywords: %w[HasError HasFailedResource]
107
+ keywords: %w[HasError HasFailedResource],
108
+ applied: 5, failed: 1, pending: 1, other: 0
80
109
  },
81
110
  }, session: set_session_user
82
111
  end
@@ -90,7 +119,7 @@ class Api::V2::HostReportsControllerTest < ActionController::TestCase
90
119
  post :create, params: {
91
120
  host_report: {
92
121
  host: host.name, body: report_body, reported_at: Time.current,
93
- status: 0
122
+ applied: 5, failed: 1, pending: 1, other: 0
94
123
  },
95
124
  }
96
125
  assert_nil @controller.detected_proxy
@@ -101,18 +130,13 @@ class Api::V2::HostReportsControllerTest < ActionController::TestCase
101
130
  Setting[:restrict_registered_smart_proxies] = true
102
131
  Setting[:require_ssl_smart_proxies] = false
103
132
 
104
- stub_smart_proxy_v2_features
105
- proxy = smart_proxies(:puppetmaster)
106
- as_admin { proxy.update_attribute(:url, 'http://configreports.foreman') }
107
- proxy_host = URI.parse(proxy.url).host
108
- Resolv.any_instance.stubs(:getnames).returns([proxy_host])
109
133
  post :create, params: {
110
134
  host_report: {
111
135
  host: host.name, body: report_body, reported_at: Time.current,
112
- status: 0
136
+ applied: 5, failed: 1, pending: 1, other: 0
113
137
  },
114
138
  }
115
- assert_equal proxy, @controller.detected_proxy
139
+ assert_equal @proxy, @controller.detected_proxy
116
140
  assert_response :created
117
141
  end
118
142
 
@@ -124,7 +148,7 @@ class Api::V2::HostReportsControllerTest < ActionController::TestCase
124
148
  post :create, params: {
125
149
  host_report: {
126
150
  host: host.name, body: report_body, reported_at: Time.current,
127
- status: 0
151
+ applied: 5, failed: 1, pending: 1, other: 0
128
152
  },
129
153
  }
130
154
  assert_response :forbidden
@@ -135,12 +159,12 @@ class Api::V2::HostReportsControllerTest < ActionController::TestCase
135
159
  Setting[:require_ssl_smart_proxies] = true
136
160
 
137
161
  @request.env['HTTPS'] = 'on'
138
- @request.env['SSL_CLIENT_S_DN'] = 'CN=else.where'
162
+ @request.env['SSL_CLIENT_S_DN'] = 'CN=reports.foreman.example.com'
139
163
  @request.env['SSL_CLIENT_VERIFY'] = 'SUCCESS'
140
164
  post :create, params: {
141
165
  host_report: {
142
166
  host: host.name, body: report_body, reported_at: Time.current,
143
- status: 0
167
+ applied: 5, failed: 1, pending: 1, other: 0
144
168
  },
145
169
  }
146
170
  assert_response :created
@@ -156,7 +180,7 @@ class Api::V2::HostReportsControllerTest < ActionController::TestCase
156
180
  post :create, params: {
157
181
  host_report: {
158
182
  host: host.name, body: report_body, reported_at: Time.current,
159
- status: 0
183
+ applied: 5, failed: 1, pending: 1, other: 0
160
184
  },
161
185
  }
162
186
  assert_response :forbidden
@@ -172,7 +196,7 @@ class Api::V2::HostReportsControllerTest < ActionController::TestCase
172
196
  post :create, params: {
173
197
  host_report: {
174
198
  host: host.name, body: report_body, reported_at: Time.current,
175
- status: 0
199
+ applied: 5, failed: 1, pending: 1, other: 0
176
200
  },
177
201
  }
178
202
  assert_response :forbidden
@@ -187,7 +211,7 @@ class Api::V2::HostReportsControllerTest < ActionController::TestCase
187
211
  post :create, params: {
188
212
  host_report: {
189
213
  host: host.name, body: report_body, reported_at: Time.current,
190
- status: 0
214
+ applied: 5, failed: 1, pending: 1, other: 0
191
215
  },
192
216
  }
193
217
  assert_response :forbidden
@@ -199,11 +223,10 @@ class Api::V2::HostReportsControllerTest < ActionController::TestCase
199
223
  Setting[:require_ssl_smart_proxies] = true
200
224
  SETTINGS[:require_ssl] = false
201
225
 
202
- Resolv.any_instance.stubs(:getnames).returns(['else.where'])
203
226
  post :create, params: {
204
227
  host_report: {
205
228
  host: host.name, body: report_body, reported_at: Time.current,
206
- status: 0
229
+ applied: 5, failed: 1, pending: 1, other: 0
207
230
  },
208
231
  }
209
232
  assert_response :created
@@ -2,7 +2,10 @@ FactoryBot.define do
2
2
  factory :host_report do
3
3
  host
4
4
  reported_at { Time.now.utc }
5
- status { 0 }
5
+ applied { 0 }
6
+ failed { 0 }
7
+ pending { 0 }
8
+ other { 0 }
6
9
  body { 'report data' }
7
10
  end
8
11
 
@@ -0,0 +1,30 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+
3
+ export const withRenderHandler = ({ Component }) => componentProps => (
4
+ <Component {...componentProps} />
5
+ );
6
+
7
+ export const callOnMount = callback => WrappedComponent => componentProps => {
8
+ callback(componentProps);
9
+
10
+ return <WrappedComponent {...componentProps} />;
11
+ };
12
+
13
+ export const callOnPopState = callback => WrappedComponent => componentProps => {
14
+ const didMount = useRef(false);
15
+ const {
16
+ history: {
17
+ action,
18
+ location: { search },
19
+ },
20
+ } = componentProps;
21
+ useEffect(() => {
22
+ if (action === 'POP' && didMount.current) {
23
+ callback(componentProps);
24
+ } else {
25
+ didMount.current = true;
26
+ }
27
+ }, [search, action, componentProps]);
28
+
29
+ return <WrappedComponent {...componentProps} />;
30
+ };
@@ -0,0 +1,7 @@
1
+ export { sprintf } from 'jed';
2
+
3
+ export const translate = s => s;
4
+
5
+ export const ngettext = s => s;
6
+
7
+ export const documentLocale = () => 'en';
@@ -0,0 +1,7 @@
1
+ export const getURIQuery = jest.fn(() => ({}));
2
+
3
+ export const deepPropsToCamelCase = jest.fn(props => props);
4
+
5
+ export const foremanUrl = jest.fn(() => '');
6
+
7
+ export const noop = Function.prototype;
@@ -0,0 +1,8 @@
1
+ export const getURIsearch = () => 'a=b';
2
+
3
+ export const getParams = () => ({
4
+ page: 1,
5
+ perPage: 20,
6
+ searchQuery: '',
7
+ sort: {},
8
+ });
@@ -0,0 +1,2 @@
1
+ const ForemanModalActions = () => jest.fn();
2
+ export default ForemanModalActions;
@@ -0,0 +1,10 @@
1
+ const modalOpen = true;
2
+ const setModalOpen = jest.fn();
3
+ const setModalClosed = jest.fn();
4
+
5
+ export const useForemanModal = jest.fn(() => ({
6
+ modalOpen,
7
+ setModalOpen,
8
+ setModalClosed,
9
+ }));
10
+ export default useForemanModal;
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const ForemanModal = ({ children }) => <div className="modal">{children}</div>;
5
+ ForemanModal.Header = ({ children }) => (
6
+ <div className="modal-header">{children}</div>
7
+ );
8
+ ForemanModal.Footer = ({ children }) => (
9
+ <div className="modal-footer">{children}</div>
10
+ );
11
+
12
+ ForemanModal.propTypes = {
13
+ children: PropTypes.node,
14
+ };
15
+
16
+ ForemanModal.defaultProps = {
17
+ children: [],
18
+ };
19
+
20
+ ForemanModal.Header.propTypes = ForemanModal.propTypes;
21
+ ForemanModal.Footer.propTypes = ForemanModal.propTypes;
22
+
23
+ export default ForemanModal;
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+
3
+ const PaginationWrapper = () => <></>;
4
+ export default PaginationWrapper;
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+
3
+ export const DefaultEmptyState = () => (
4
+ <div className="empty-state-description" />
5
+ );
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ const RelativeDateTime = (date, defaultValue, context) => (
4
+ <span>{defaultValue || date}</span>
5
+ );
6
+
7
+ export default RelativeDateTime;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+
3
+ const ForemanForm = () => (
4
+ <React.Fragment>
5
+ <form />
6
+ </React.Fragment>
7
+ );
8
+
9
+ export default ForemanForm;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+
3
+ export const ForemanField = props => <div />;
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+
3
+ export const Table = () => <div className="table" />;
4
+ export const createTableReducer = jest.fn(controller => controller);
5
+ export const cellFormatter = cell => cell;
6
+ export const deleteActionCellFormatter = cell => cell;
7
+ export const sortableColumn = jest.fn();
8
+ export const column = (
9
+ property,
10
+ label,
11
+ headFormat,
12
+ cellFormat,
13
+ headProps = {},
14
+ cellProps = {}
15
+ ) => ({
16
+ property,
17
+ header: {
18
+ label,
19
+ props: headProps,
20
+ formatters: headFormat,
21
+ },
22
+ cell: {
23
+ props: cellProps,
24
+ formatters: cellFormat,
25
+ },
26
+ });
@@ -0,0 +1,24 @@
1
+ export const STATUS = {
2
+ PENDING: 'PENDING',
3
+ RESOLVED: 'RESOLVED',
4
+ ERROR: 'ERROR',
5
+ };
6
+
7
+ export const getControllerSearchProps = (
8
+ controller,
9
+ id = 'searchBar',
10
+ canCreateBookmarks = true
11
+ ) => ({
12
+ controller,
13
+ autocomplete: {
14
+ id,
15
+ searchQuery: '',
16
+ url: `${controller}/auto_complete_search`,
17
+ useKeyShortcuts: true,
18
+ },
19
+ bookmarks: {
20
+ url: '/api/bookmarks',
21
+ canCreateBookmarks,
22
+ documentationUrl: `4.1.5Searching`,
23
+ },
24
+ });
@@ -0,0 +1,6 @@
1
+ export const selectAPIResponse = (state, key) =>
2
+ selectAPIByKey(state, key).response;
3
+
4
+ export const selectAPIStatus = (state, key) => 'PENDING';
5
+ export const selectAPIByKey = (state, key) => state.API[key];
6
+ export const selectAPIError = (state, key) => ({ error: `${key} ERRROR` });
@@ -0,0 +1,10 @@
1
+ export const API = {
2
+ get: jest.fn(),
3
+ put: jest.fn(),
4
+ post: jest.fn(),
5
+ delete: jest.fn(),
6
+ patch: jest.fn(),
7
+ };
8
+
9
+ export const get = data => ({ type: 'get-some-type', ...data });
10
+ export const post = data => ({ type: 'post-some-type', ...data });
@@ -0,0 +1 @@
1
+ export const submitForm = jest.fn(() => '');
@@ -0,0 +1,8 @@
1
+ export const addToast = toast => ({
2
+ type: 'TOASTS_ADD',
3
+ payload: {
4
+ message: toast,
5
+ },
6
+ });
7
+
8
+ export default addToast;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const PageLayout = ({ children }) => <div>{children}</div>;
5
+
6
+ PageLayout.propTypes = {
7
+ children: PropTypes.node.isRequired,
8
+ };
9
+
10
+ export default PageLayout;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { Button } from 'patternfly-react';
3
+
4
+ const ExportButton = props => (
5
+ <Button className="export-csv" href="#" title="Export">
6
+ Export
7
+ </Button>
8
+ );
9
+
10
+ export default ExportButton;
@@ -2,8 +2,14 @@ import React from 'react';
2
2
 
3
3
  import StatusCell from '../StatusCell';
4
4
 
5
- const statusFormatter = () => (value, { rowData: { format, status } }) => (
6
- <StatusCell format={format} value={value} />
7
- );
5
+ const statusFormatter = () => (_, { rowData }) => {
6
+ const statuses = {
7
+ applied: rowData.applied,
8
+ failed: rowData.failed,
9
+ pending: rowData.pending,
10
+ other: rowData.other,
11
+ };
12
+ return <StatusCell statuses={statuses} />;
13
+ };
8
14
 
9
15
  export default statusFormatter;
@@ -4,74 +4,32 @@ import { capitalize } from 'lodash';
4
4
 
5
5
  import './StatusCell.scss';
6
6
 
7
- const StatusCell = ({ format, value }) => {
8
- switch (format) {
9
- case 'puppet':
10
- return (
11
- <td>
12
- <ul className="status-list">
13
- {value.map(status => {
14
- let style = '';
15
- switch (status[0]) {
16
- case 'failed':
17
- style = 'label-danger';
18
- break;
19
- case 'failed_to_restart':
20
- style = 'label-warning';
21
- break;
22
- default:
23
- style = 'label-info';
24
- }
25
- if (!status[2]) style = 'label-default';
26
- return (
27
- <li key={status[0]}>
28
- {`${status[1]}: `}
29
- <span className={`label ${style}`}>{status[2]}</span>
30
- </li>
31
- );
32
- })}
33
- </ul>
34
- </td>
35
- );
36
- case 'ansible':
37
- return (
38
- <td>
39
- <ul className="status-list">
40
- {Object.keys(value).map(status => {
41
- let style = '';
42
- switch (status) {
43
- case 'failed':
44
- style = 'label-danger';
45
- break;
46
- case 'skipped':
47
- style = 'label-warning';
48
- break;
49
- default:
50
- style = 'label-info';
51
- }
52
- if (!value[status]) style = 'label-default';
53
- return (
54
- <li key={status}>
55
- {`${capitalize(status)}: `}
56
- <span className={`label ${style}`}>{value[status]}</span>
57
- </li>
58
- );
59
- })}
60
- </ul>
61
- </td>
62
- );
63
- default:
64
- return <td>N/A</td>;
65
- }
66
- };
7
+ const StatusCell = ({ statuses }) => (
8
+ <td>
9
+ <ul className="status-list">
10
+ {Object.keys(statuses).map(status => {
11
+ let style = '';
12
+ switch (status) {
13
+ case 'failed':
14
+ style = 'label-danger';
15
+ break;
16
+ default:
17
+ style = 'label-info';
18
+ }
19
+ if (!statuses[status]) style = 'label-default';
20
+ return (
21
+ <li key={status}>
22
+ {`${capitalize(status)}: `}
23
+ <span className={`label ${style}`}>{statuses[status]}</span>
24
+ </li>
25
+ );
26
+ })}
27
+ </ul>
28
+ </td>
29
+ );
67
30
 
68
31
  StatusCell.propTypes = {
69
- format: PropTypes.string,
70
- value: PropTypes.any.isRequired,
71
- };
72
-
73
- StatusCell.defaultProps = {
74
- format: 'plain',
32
+ statuses: PropTypes.object.isRequired,
75
33
  };
76
34
 
77
35
  export default StatusCell;
@@ -0,0 +1,40 @@
1
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
+ import HostReportsIndexPage from '../IndexPage';
3
+
4
+ const fixtures = {
5
+ 'render with minimal props': {
6
+ search: '',
7
+ history: { location: { search: '' } },
8
+ hostId: '1',
9
+ fetchAndPush: jest.fn(),
10
+ reloadWithSearch: jest.fn(),
11
+ isLoading: false,
12
+ hasError: false,
13
+ hasData: true,
14
+ itemCount: 1,
15
+ canCreate: true,
16
+ sort: {},
17
+ page: 1,
18
+ perPage: 20,
19
+ reports: [
20
+ {
21
+ id: '1',
22
+ hostId: '1',
23
+ hostName: 'foreman.example.com',
24
+ proxyId: '1',
25
+ proxyName: 'foreman.example.com',
26
+ format: 'plain',
27
+ reportedAt: '2021-01-19T12:34:02.841645028Z',
28
+ applied: 0,
29
+ failed: 0,
30
+ pending: 0,
31
+ other: 0,
32
+ },
33
+ ],
34
+ },
35
+ };
36
+
37
+ describe('HostReportsIndexPage', () => {
38
+ describe('redering', () =>
39
+ testComponentSnapshotsWithFixtures(HostReportsIndexPage, fixtures));
40
+ });
@@ -0,0 +1,67 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`HostReportsIndexPage redering render with minimal props 1`] = `
4
+ <PageLayout
5
+ header="Host Reports"
6
+ isLoading={false}
7
+ onBookmarkClick={[MockFunction]}
8
+ onSearch={[MockFunction]}
9
+ searchProps={
10
+ Object {
11
+ "autocomplete": Object {
12
+ "id": "searchBar",
13
+ "searchQuery": "",
14
+ "url": "host_reports/auto_complete_search",
15
+ "useKeyShortcuts": true,
16
+ },
17
+ "bookmarks": Object {
18
+ "canCreateBookmarks": true,
19
+ "documentationUrl": "4.1.5Searching",
20
+ "url": "/api/bookmarks",
21
+ },
22
+ "controller": "host_reports",
23
+ }
24
+ }
25
+ searchQuery=""
26
+ searchable={true}
27
+ toolbarButtons={
28
+ <ExportButton
29
+ title="Export"
30
+ url="/api/v2/host_reports/export"
31
+ />
32
+ }
33
+ >
34
+ <WrappedHostReportsTable
35
+ fetchAndPush={[MockFunction]}
36
+ hostId="1"
37
+ itemCount={1}
38
+ pagination={
39
+ Object {
40
+ "page": 1,
41
+ "perPage": 20,
42
+ }
43
+ }
44
+ reloadWithSearch={[MockFunction]}
45
+ results={
46
+ Array [
47
+ Object {
48
+ "applied": 0,
49
+ "failed": 0,
50
+ "format": "plain",
51
+ "hostId": "1",
52
+ "hostName": "foreman.example.com",
53
+ "id": "1",
54
+ "other": 0,
55
+ "pending": 0,
56
+ "proxyId": "1",
57
+ "proxyName": "foreman.example.com",
58
+ "reportedAt": "2021-01-19T12:34:02.841645028Z",
59
+ },
60
+ ]
61
+ }
62
+ setToDelete={[Function]}
63
+ sort={Object {}}
64
+ toDelete={Object {}}
65
+ />
66
+ </PageLayout>
67
+ `;
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_host_reports
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lukas Zapletal
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-18 00:00:00.000000000 Z
11
+ date: 2021-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdoc
@@ -48,6 +48,8 @@ files:
48
48
  - config/routes.rb
49
49
  - db/migrate/20210112183526_add_host_reports.rb
50
50
  - db/migrate/20210616133601_create_report_keywords.rb
51
+ - db/migrate/20211011141813_change_status_column.rb
52
+ - db/seeds.d/60-reports_feature.rb
51
53
  - lib/foreman_host_reports.rb
52
54
  - lib/foreman_host_reports/engine.rb
53
55
  - lib/foreman_host_reports/version.rb
@@ -62,6 +64,26 @@ files:
62
64
  - test/snapshots/foreman-web.json
63
65
  - test/test_plugin_helper.rb
64
66
  - test/unit/foreman_host_reports_test.rb
67
+ - webpack/__mocks__/foremanReact/common/HOC.js
68
+ - webpack/__mocks__/foremanReact/common/I18n.js
69
+ - webpack/__mocks__/foremanReact/common/helpers.js
70
+ - webpack/__mocks__/foremanReact/common/urlHelpers.js
71
+ - webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js
72
+ - webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js
73
+ - webpack/__mocks__/foremanReact/components/ForemanModal/index.js
74
+ - webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js
75
+ - webpack/__mocks__/foremanReact/components/common/EmptyState.js
76
+ - webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js
77
+ - webpack/__mocks__/foremanReact/components/common/forms/ForemanForm.js
78
+ - webpack/__mocks__/foremanReact/components/common/forms/FormField.js
79
+ - webpack/__mocks__/foremanReact/components/common/table.js
80
+ - webpack/__mocks__/foremanReact/constants.js
81
+ - webpack/__mocks__/foremanReact/redux/API/APISelectors.js
82
+ - webpack/__mocks__/foremanReact/redux/API/index.js
83
+ - webpack/__mocks__/foremanReact/redux/actions/common/forms.js
84
+ - webpack/__mocks__/foremanReact/redux/actions/toasts.js
85
+ - webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js
86
+ - webpack/__mocks__/foremanReact/routes/common/PageLayout/components/ExportButton/ExportButton.js
65
87
  - webpack/global_index.js
66
88
  - webpack/global_test_setup.js
67
89
  - webpack/index.js
@@ -87,6 +109,8 @@ files:
87
109
  - webpack/src/Router/HostReports/IndexPage/IndexPageActions.js
88
110
  - webpack/src/Router/HostReports/IndexPage/IndexPageHelpers.js
89
111
  - webpack/src/Router/HostReports/IndexPage/IndexPageSelectors.js
112
+ - webpack/src/Router/HostReports/IndexPage/__tests__/HostReportsIndexPage.test.js
113
+ - webpack/src/Router/HostReports/IndexPage/__tests__/__snapshots__/HostReportsIndexPage.test.js.snap
90
114
  - webpack/src/Router/HostReports/IndexPage/constants.js
91
115
  - webpack/src/Router/HostReports/IndexPage/index.js
92
116
  - webpack/src/Router/HostReports/ShowPage/Components/HostReportMetrics/HostReportMetrics.scss
@@ -101,7 +125,6 @@ files:
101
125
  - webpack/src/Router/HostReports/ShowPage/index.js
102
126
  - webpack/src/Router/HostReports/constants.js
103
127
  - webpack/src/Router/routes.js
104
- - webpack/test_setup.js
105
128
  homepage: https://github.com/theforeman/foreman_host_reports
106
129
  licenses:
107
130
  - GPL-3.0
@@ -131,3 +154,4 @@ test_files:
131
154
  - test/controllers/api/v2/host_reports_controller_test.rb
132
155
  - test/snapshots/foreman-web.json
133
156
  - test/test_plugin_helper.rb
157
+ - webpack/src/Router/HostReports/IndexPage/__tests__/HostReportsIndexPage.test.js
@@ -1,17 +0,0 @@
1
- import 'core-js/shim';
2
- import 'regenerator-runtime/runtime';
3
- import MutationObserver from '@sheerun/mutationobserver-shim';
4
-
5
- import { configure } from 'enzyme';
6
- import Adapter from 'enzyme-adapter-react-16';
7
-
8
- configure({ adapter: new Adapter() });
9
-
10
- // Mocking translation function
11
- global.__ = text => text; // eslint-disable-line
12
-
13
- // Mocking locales to prevent unnecessary fallback messages
14
- window.locales = { en: { domain: 'app', locale_data: { app: { '': {} } } } };
15
-
16
- // see https://github.com/testing-library/dom-testing-library/releases/tag/v7.0.0
17
- window.MutationObserver = MutationObserver;