foreman_rh_cloud 14.0.2 → 14.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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/foreman_rh_cloud/locale/fr/foreman_rh_cloud.js +61 -61
- data/app/assets/javascripts/foreman_rh_cloud/locale/ja/foreman_rh_cloud.js +68 -68
- data/app/assets/javascripts/foreman_rh_cloud/locale/ka/foreman_rh_cloud.js +9 -9
- data/app/assets/javascripts/foreman_rh_cloud/locale/ko/foreman_rh_cloud.js +60 -60
- data/app/assets/javascripts/foreman_rh_cloud/locale/zh_CN/foreman_rh_cloud.js +60 -60
- data/app/controllers/concerns/insights_cloud/candlepin_proxies_extensions.rb +23 -0
- data/app/controllers/concerns/insights_cloud/package_profile_upload_extensions.rb +9 -0
- data/app/controllers/foreman_inventory_upload/accounts_controller.rb +1 -1
- data/app/controllers/foreman_inventory_upload/api/tasks_controller.rb +2 -2
- data/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +9 -2
- data/app/models/concerns/rh_cloud_host.rb +35 -3
- data/app/models/insights_client_report_status.rb +9 -1
- data/app/models/inventory_sync/inventory_status.rb +16 -4
- data/app/services/foreman_rh_cloud/insights_api_forwarder.rb +12 -5
- data/lib/foreman_inventory_upload/async/create_missing_insights_facets.rb +8 -2
- data/lib/foreman_rh_cloud/engine.rb +1 -0
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/lib/insights_cloud/async/vmaas_reposcan_sync.rb +23 -8
- data/lib/inventory_sync/async/inventory_full_sync.rb +39 -3
- data/locale/fr/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
- data/locale/fr/foreman_rh_cloud.po +66 -65
- data/locale/ja/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
- data/locale/ja/foreman_rh_cloud.po +72 -70
- data/locale/ka/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
- data/locale/ka/foreman_rh_cloud.po +11 -10
- data/locale/ko/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
- data/locale/ko/foreman_rh_cloud.po +64 -63
- data/locale/zh_CN/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
- data/locale/zh_CN/foreman_rh_cloud.po +65 -64
- data/package.json +1 -1
- data/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +40 -0
- data/test/controllers/insights_cloud/candlepin_proxies_extensions_test.rb +70 -0
- data/test/jobs/insights_client_status_aging_test.rb +40 -0
- data/test/jobs/inventory_full_sync_test.rb +212 -0
- data/test/models/insights_client_report_status_test.rb +109 -0
- data/test/models/inventory_sync/inventory_status_test.rb +85 -0
- data/test/unit/lib/insights_cloud/async/vmaas_reposcan_sync_test.rb +80 -25
- data/test/unit/rh_cloud_host_test.rb +214 -0
- data/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js +6 -1
- data/webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js +45 -6
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/SyncButtonActions.js +8 -2
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/integrations.test.js.snap +1 -0
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/integrations.test.js +1 -0
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/components/Toast.js +43 -17
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/components/__tests__/Toast.test.js +82 -0
- data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +7 -4
- data/webpack/InsightsCloudSync/Components/InsightsTable/table.scss +9 -0
- data/webpack/InsightsHostDetailsTab/NewHostDetailsTab.js +44 -14
- data/webpack/InsightsHostDetailsTab/__tests__/NewHostDetailsTab.test.js +154 -0
- metadata +9 -2
|
@@ -188,4 +188,218 @@ class RhCloudHostTest < ActiveSupport::TestCase
|
|
|
188
188
|
assert_equal local_uuid, @host.insights_uuid
|
|
189
189
|
end
|
|
190
190
|
end
|
|
191
|
+
|
|
192
|
+
context 'scoped search on insights_uuid' do
|
|
193
|
+
setup do
|
|
194
|
+
@org = FactoryBot.create(:organization)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
teardown do
|
|
198
|
+
ForemanRhCloud.unstub(:with_iop_smart_proxy?)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
test 'searches insights_facet.uuid in non-IoP mode with = operator' do
|
|
202
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
|
203
|
+
host1 = FactoryBot.create(:host, :managed, organization: @org)
|
|
204
|
+
host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'insights-uuid-123')
|
|
205
|
+
host2 = FactoryBot.create(:host, :managed, organization: @org)
|
|
206
|
+
host2.insights = FactoryBot.create(:insights_facet, host_id: host2.id, uuid: 'insights-uuid-456')
|
|
207
|
+
|
|
208
|
+
results = Host::Managed.search_for('insights_uuid = insights-uuid-123')
|
|
209
|
+
|
|
210
|
+
assert_includes results, host1
|
|
211
|
+
assert_not_includes results, host2
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
test 'searches subscription_facet.uuid in IoP mode with = operator' do
|
|
215
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
216
|
+
host1 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
|
|
217
|
+
host2 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
|
|
218
|
+
|
|
219
|
+
# Even if insights_facet has different UUID, should use subscription_facet UUID
|
|
220
|
+
host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'stale-123')
|
|
221
|
+
|
|
222
|
+
results = Host::Managed.search_for("insights_uuid = #{host1.subscription_facet.uuid}")
|
|
223
|
+
|
|
224
|
+
assert_includes results, host1
|
|
225
|
+
assert_not_includes results, host2
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
test 'searches with ^ operator (IN) in non-IoP mode' do
|
|
229
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
|
230
|
+
host1 = FactoryBot.create(:host, :managed, organization: @org)
|
|
231
|
+
host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'uuid-1')
|
|
232
|
+
host2 = FactoryBot.create(:host, :managed, organization: @org)
|
|
233
|
+
host2.insights = FactoryBot.create(:insights_facet, host_id: host2.id, uuid: 'uuid-2')
|
|
234
|
+
host3 = FactoryBot.create(:host, :managed, organization: @org)
|
|
235
|
+
host3.insights = FactoryBot.create(:insights_facet, host_id: host3.id, uuid: 'uuid-3')
|
|
236
|
+
|
|
237
|
+
results = Host::Managed.search_for('insights_uuid ^ (uuid-1,uuid-2)')
|
|
238
|
+
|
|
239
|
+
assert_includes results, host1
|
|
240
|
+
assert_includes results, host2
|
|
241
|
+
assert_not_includes results, host3
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
test 'searches with ^ operator (IN) in IoP mode - THE BUG FIX' do
|
|
245
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
246
|
+
host1 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
|
|
247
|
+
host2 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
|
|
248
|
+
host3 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
|
|
249
|
+
|
|
250
|
+
# Create insights facets with stale UUIDs to verify we're using subscription_facet
|
|
251
|
+
host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'stale-1')
|
|
252
|
+
host2.insights = FactoryBot.create(:insights_facet, host_id: host2.id, uuid: 'stale-2')
|
|
253
|
+
host3.insights = FactoryBot.create(:insights_facet, host_id: host3.id, uuid: 'stale-3')
|
|
254
|
+
|
|
255
|
+
uuid1 = host1.subscription_facet.uuid
|
|
256
|
+
uuid2 = host2.subscription_facet.uuid
|
|
257
|
+
|
|
258
|
+
# This is the search query that remediation modal creates
|
|
259
|
+
results = Host::Managed.search_for("insights_uuid ^ (#{uuid1},#{uuid2})")
|
|
260
|
+
|
|
261
|
+
# Should find hosts by subscription_facet UUID, not insights_facet UUID
|
|
262
|
+
assert_includes results, host1
|
|
263
|
+
assert_includes results, host2
|
|
264
|
+
assert_not_includes results, host3
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
test 'searches with !^ operator (NOT IN) in non-IoP mode' do
|
|
268
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
|
269
|
+
host1 = FactoryBot.create(:host, :managed, organization: @org)
|
|
270
|
+
host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'uuid-1')
|
|
271
|
+
host2 = FactoryBot.create(:host, :managed, organization: @org)
|
|
272
|
+
host2.insights = FactoryBot.create(:insights_facet, host_id: host2.id, uuid: 'uuid-2')
|
|
273
|
+
host3 = FactoryBot.create(:host, :managed, organization: @org)
|
|
274
|
+
host3.insights = FactoryBot.create(:insights_facet, host_id: host3.id, uuid: 'uuid-3')
|
|
275
|
+
|
|
276
|
+
results = Host::Managed.search_for('insights_uuid !^ (uuid-1,uuid-2)')
|
|
277
|
+
|
|
278
|
+
assert_not_includes results, host1
|
|
279
|
+
assert_not_includes results, host2
|
|
280
|
+
assert_includes results, host3
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
test 'searches with !^ operator (NOT IN) in IoP mode' do
|
|
284
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
285
|
+
host1 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
|
|
286
|
+
host2 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
|
|
287
|
+
host3 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
|
|
288
|
+
|
|
289
|
+
uuid1 = host1.subscription_facet.uuid
|
|
290
|
+
uuid2 = host2.subscription_facet.uuid
|
|
291
|
+
|
|
292
|
+
results = Host::Managed.search_for("insights_uuid !^ (#{uuid1},#{uuid2})")
|
|
293
|
+
|
|
294
|
+
assert_not_includes results, host1
|
|
295
|
+
assert_not_includes results, host2
|
|
296
|
+
assert_includes results, host3
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
test 'handles hosts without facets in non-IoP mode' do
|
|
300
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
|
301
|
+
host_without_facet = FactoryBot.create(:host, :managed, organization: @org)
|
|
302
|
+
host_with_facet = FactoryBot.create(:host, :managed, organization: @org)
|
|
303
|
+
host_with_facet.insights = FactoryBot.create(:insights_facet, host_id: host_with_facet.id, uuid: 'uuid-1')
|
|
304
|
+
|
|
305
|
+
results = Host::Managed.search_for('insights_uuid = uuid-1')
|
|
306
|
+
|
|
307
|
+
assert_includes results, host_with_facet
|
|
308
|
+
assert_not_includes results, host_without_facet
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
test 'handles hosts without subscription_facet in IoP mode' do
|
|
312
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
313
|
+
host_without_sub = FactoryBot.create(:host, :managed, organization: @org)
|
|
314
|
+
host_with_sub = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
|
|
315
|
+
|
|
316
|
+
uuid = host_with_sub.subscription_facet.uuid
|
|
317
|
+
|
|
318
|
+
results = Host::Managed.search_for("insights_uuid = #{uuid}")
|
|
319
|
+
|
|
320
|
+
assert_includes results, host_with_sub
|
|
321
|
+
assert_not_includes results, host_without_sub
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
test 'mode changes are reflected in searches' do
|
|
325
|
+
host1 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
|
|
326
|
+
host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'insights-uuid-abc')
|
|
327
|
+
insights_uuid = 'insights-uuid-abc'
|
|
328
|
+
subscription_uuid = host1.subscription_facet.uuid
|
|
329
|
+
|
|
330
|
+
# Non-IoP mode: should find by insights_facet UUID
|
|
331
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
|
332
|
+
results = Host::Managed.search_for("insights_uuid = #{insights_uuid}")
|
|
333
|
+
assert_includes results, host1
|
|
334
|
+
|
|
335
|
+
# IoP mode: should find by subscription_facet UUID
|
|
336
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
337
|
+
results = Host::Managed.search_for("insights_uuid = #{subscription_uuid}")
|
|
338
|
+
assert_includes results, host1
|
|
339
|
+
|
|
340
|
+
# Should NOT find by old insights_facet UUID in IoP mode
|
|
341
|
+
results = Host::Managed.search_for("insights_uuid = #{insights_uuid}")
|
|
342
|
+
assert_not_includes results, host1
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
test 'scoped search for user_omitted inventory status works' do
|
|
347
|
+
host1 = FactoryBot.create(:host, :managed)
|
|
348
|
+
host2 = FactoryBot.create(:host, :managed)
|
|
349
|
+
host3 = FactoryBot.create(:host, :managed)
|
|
350
|
+
|
|
351
|
+
# Create different inventory statuses
|
|
352
|
+
InventorySync::InventoryStatus.create!(
|
|
353
|
+
host_id: host1.id,
|
|
354
|
+
status: InventorySync::InventoryStatus::SYNC,
|
|
355
|
+
reported_at: Time.zone.now
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
InventorySync::InventoryStatus.create!(
|
|
359
|
+
host_id: host2.id,
|
|
360
|
+
status: InventorySync::InventoryStatus::DISCONNECT,
|
|
361
|
+
reported_at: Time.zone.now
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
InventorySync::InventoryStatus.create!(
|
|
365
|
+
host_id: host3.id,
|
|
366
|
+
status: InventorySync::InventoryStatus::USER_OMITTED,
|
|
367
|
+
reported_at: Time.zone.now
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Search for user_omitted status
|
|
371
|
+
results = Host.search_for('insights_inventory_sync_status = user_omitted')
|
|
372
|
+
result_ids = results.pluck(:id)
|
|
373
|
+
|
|
374
|
+
assert_includes result_ids, host3.id, 'Host with USER_OMITTED status should be in search results'
|
|
375
|
+
assert_not_includes result_ids, host1.id, 'Host with SYNC status should not be in search results'
|
|
376
|
+
assert_not_includes result_ids, host2.id, 'Host with DISCONNECT status should not be in search results'
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
test 'scoped search for user_omitted insights client report status works' do
|
|
380
|
+
host1 = FactoryBot.create(:host, :managed)
|
|
381
|
+
host2 = FactoryBot.create(:host, :managed)
|
|
382
|
+
host3 = FactoryBot.create(:host, :managed)
|
|
383
|
+
|
|
384
|
+
# Create different insights client report statuses
|
|
385
|
+
status1 = host1.get_status(InsightsClientReportStatus)
|
|
386
|
+
status1.status = InsightsClientReportStatus::REPORTING
|
|
387
|
+
status1.save!
|
|
388
|
+
|
|
389
|
+
status2 = host2.get_status(InsightsClientReportStatus)
|
|
390
|
+
status2.status = InsightsClientReportStatus::NO_REPORT
|
|
391
|
+
status2.save!
|
|
392
|
+
|
|
393
|
+
status3 = host3.get_status(InsightsClientReportStatus)
|
|
394
|
+
status3.status = InsightsClientReportStatus::USER_OMITTED
|
|
395
|
+
status3.save!
|
|
396
|
+
|
|
397
|
+
# Search for user_omitted status
|
|
398
|
+
results = Host.search_for('insights_client_report_status = user_omitted')
|
|
399
|
+
result_ids = results.pluck(:id)
|
|
400
|
+
|
|
401
|
+
assert_includes result_ids, host3.id, 'Host with USER_OMITTED status should be in search results'
|
|
402
|
+
assert_not_includes result_ids, host1.id, 'Host with REPORTING status should not be in search results'
|
|
403
|
+
assert_not_includes result_ids, host2.id, 'Host with NO_REPORT status should not be in search results'
|
|
404
|
+
end
|
|
191
405
|
end
|
|
@@ -10,7 +10,12 @@ const CVEsHostDetailsTab = ({ systemId }) => {
|
|
|
10
10
|
const module = './SystemDetailTable';
|
|
11
11
|
return (
|
|
12
12
|
<div className="rh-cloud-insights-vulnerability-host-details-component vulnerability">
|
|
13
|
-
<ScalprumComponent
|
|
13
|
+
<ScalprumComponent
|
|
14
|
+
key={systemId}
|
|
15
|
+
scope={scope}
|
|
16
|
+
module={module}
|
|
17
|
+
systemId={systemId}
|
|
18
|
+
/>
|
|
14
19
|
</div>
|
|
15
20
|
);
|
|
16
21
|
};
|
|
@@ -11,14 +11,25 @@ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
|
|
|
11
11
|
useForemanPermissions: () => new Set(['view_vulnerability']),
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
|
-
jest.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
const mockUnmountTracker = jest.fn();
|
|
15
|
+
jest.mock('@scalprum/react-core', () => {
|
|
16
|
+
const ReactMock = require('react');
|
|
17
|
+
return {
|
|
18
|
+
ScalprumComponent: jest.fn(props => {
|
|
19
|
+
ReactMock.useEffect(() => mockUnmountTracker, []);
|
|
20
|
+
return (
|
|
21
|
+
<div data-testid="mock-scalprum-component">{JSON.stringify(props)}</div>
|
|
22
|
+
);
|
|
23
|
+
}),
|
|
24
|
+
ScalprumProvider: jest.fn(({ children }) => <div>{children}</div>),
|
|
25
|
+
};
|
|
26
|
+
});
|
|
20
27
|
|
|
21
28
|
describe('CVEsHostDetailsTabWrapper', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
jest.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
22
33
|
it('renders without crashing', () => {
|
|
23
34
|
const { container } = render(
|
|
24
35
|
<CVEsHostDetailsTabWrapper
|
|
@@ -31,4 +42,32 @@ describe('CVEsHostDetailsTabWrapper', () => {
|
|
|
31
42
|
)
|
|
32
43
|
).toBeTruthy();
|
|
33
44
|
});
|
|
45
|
+
|
|
46
|
+
it('remounts ScalprumComponent when systemId changes', () => {
|
|
47
|
+
const { ScalprumComponent } = require('@scalprum/react-core');
|
|
48
|
+
|
|
49
|
+
const { rerender } = render(
|
|
50
|
+
<CVEsHostDetailsTabWrapper
|
|
51
|
+
response={{ subscription_facet_attributes: { uuid: 'uuid-host-A' } }}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(mockUnmountTracker).not.toHaveBeenCalled();
|
|
56
|
+
expect(ScalprumComponent).toHaveBeenLastCalledWith(
|
|
57
|
+
expect.objectContaining({ systemId: 'uuid-host-A' }),
|
|
58
|
+
expect.anything()
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
rerender(
|
|
62
|
+
<CVEsHostDetailsTabWrapper
|
|
63
|
+
response={{ subscription_facet_attributes: { uuid: 'uuid-host-B' } }}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(mockUnmountTracker).toHaveBeenCalledTimes(1);
|
|
68
|
+
expect(ScalprumComponent).toHaveBeenLastCalledWith(
|
|
69
|
+
expect.objectContaining({ systemId: 'uuid-host-B' }),
|
|
70
|
+
expect.anything()
|
|
71
|
+
);
|
|
72
|
+
});
|
|
34
73
|
});
|
data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/SyncButtonActions.js
CHANGED
|
@@ -39,14 +39,20 @@ export const setupInventorySyncTaskPolling = (id, dispatch) =>
|
|
|
39
39
|
key: INVENTORY_SYNC_TASK_UPDATE,
|
|
40
40
|
onTaskSuccess: ({
|
|
41
41
|
output: {
|
|
42
|
-
host_statuses: { sync, disconnect },
|
|
42
|
+
host_statuses: { sync, disconnect, user_omitted: userOmitted },
|
|
43
43
|
},
|
|
44
44
|
}) =>
|
|
45
45
|
dispatch(
|
|
46
46
|
addToast({
|
|
47
47
|
sticky: true,
|
|
48
48
|
type: 'success',
|
|
49
|
-
message:
|
|
49
|
+
message: (
|
|
50
|
+
<Toast
|
|
51
|
+
syncHosts={sync}
|
|
52
|
+
disconnectHosts={disconnect}
|
|
53
|
+
userOmittedHosts={userOmitted}
|
|
54
|
+
/>
|
|
55
|
+
),
|
|
50
56
|
})
|
|
51
57
|
),
|
|
52
58
|
dispatch,
|
data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/components/Toast.js
CHANGED
|
@@ -1,33 +1,55 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
2
3
|
import PropTypes from 'prop-types';
|
|
3
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
4
|
-
import { foremanUrl } from '../../../../../../ForemanRhCloudHelpers';
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
6
|
+
const statusSearchParams = statusName =>
|
|
7
|
+
`/new/hosts?search=insights_inventory_sync_status+%3D+${statusName}&page=1`;
|
|
8
|
+
const DISCONNECT = 'disconnect';
|
|
9
|
+
const SYNC = 'sync';
|
|
10
|
+
const USER_OMITTED = 'user_omitted';
|
|
11
|
+
const HostsWithStatusLink = ({ statusName, children }) => (
|
|
12
|
+
<Link to={statusSearchParams(statusName)}>{children}</Link>
|
|
13
|
+
);
|
|
14
|
+
HostsWithStatusLink.propTypes = {
|
|
15
|
+
statusName: PropTypes.string.isRequired,
|
|
16
|
+
children: PropTypes.node.isRequired,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const Toast = ({ syncHosts, disconnectHosts, userOmittedHosts }) => {
|
|
20
|
+
const totalHosts = syncHosts + disconnectHosts + userOmittedHosts;
|
|
8
21
|
return (
|
|
9
22
|
<span>
|
|
10
23
|
<p>
|
|
11
|
-
{__('
|
|
12
|
-
<
|
|
24
|
+
{__('Registered hosts in organization: ')}
|
|
25
|
+
<Link to="/new/hosts?search=set%3F+subscription_uuid&page=1">
|
|
26
|
+
{totalHosts}
|
|
27
|
+
</Link>
|
|
13
28
|
</p>
|
|
14
29
|
<p>
|
|
15
|
-
{__('
|
|
16
|
-
<
|
|
30
|
+
{__('Uploaded and present on console.redhat.com Inventory service: ')}
|
|
31
|
+
<HostsWithStatusLink statusName={SYNC}>{syncHosts}</HostsWithStatusLink>
|
|
17
32
|
</p>
|
|
18
33
|
<p>
|
|
19
|
-
{__('
|
|
20
|
-
<
|
|
34
|
+
{__('Not present on console.redhat.com Inventory service: ')}
|
|
35
|
+
<HostsWithStatusLink statusName={DISCONNECT}>
|
|
36
|
+
{disconnectHosts}
|
|
37
|
+
</HostsWithStatusLink>
|
|
21
38
|
</p>
|
|
39
|
+
{!!userOmittedHosts && (
|
|
40
|
+
<p>
|
|
41
|
+
{__(
|
|
42
|
+
'Excluded from upload to console.redhat.com Inventory service because host_registration_insights_inventory parameter value is false: '
|
|
43
|
+
)}
|
|
44
|
+
<HostsWithStatusLink statusName={USER_OMITTED}>
|
|
45
|
+
{userOmittedHosts}
|
|
46
|
+
</HostsWithStatusLink>
|
|
47
|
+
</p>
|
|
48
|
+
)}
|
|
22
49
|
<p>
|
|
23
|
-
{__(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
target="_blank"
|
|
27
|
-
rel="noopener noreferrer"
|
|
28
|
-
>
|
|
29
|
-
{__('hosts page')}
|
|
30
|
-
</a>
|
|
50
|
+
{__(
|
|
51
|
+
'You can review this information later by looking at the Inventory status of each host.'
|
|
52
|
+
)}
|
|
31
53
|
</p>
|
|
32
54
|
</span>
|
|
33
55
|
);
|
|
@@ -36,6 +58,10 @@ const Toast = ({ syncHosts, disconnectHosts }) => {
|
|
|
36
58
|
Toast.propTypes = {
|
|
37
59
|
syncHosts: PropTypes.number.isRequired,
|
|
38
60
|
disconnectHosts: PropTypes.number.isRequired,
|
|
61
|
+
userOmittedHosts: PropTypes.number,
|
|
62
|
+
};
|
|
63
|
+
Toast.defaultProps = {
|
|
64
|
+
userOmittedHosts: 0,
|
|
39
65
|
};
|
|
40
66
|
|
|
41
67
|
export default Toast;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shallow } from '@theforeman/test';
|
|
3
|
+
import Toast from '../Toast';
|
|
4
|
+
|
|
5
|
+
describe('Toast', () => {
|
|
6
|
+
it('renders with all three status counts including user_omitted', () => {
|
|
7
|
+
const wrapper = shallow(
|
|
8
|
+
<Toast syncHosts={5} disconnectHosts={3} userOmittedHosts={2} />
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const links = wrapper.find('HostsWithStatusLink');
|
|
12
|
+
expect(links).toHaveLength(3);
|
|
13
|
+
|
|
14
|
+
// Check the children (numbers) of each link
|
|
15
|
+
expect(
|
|
16
|
+
links
|
|
17
|
+
.at(0)
|
|
18
|
+
.children()
|
|
19
|
+
.text()
|
|
20
|
+
).toBe('5');
|
|
21
|
+
expect(
|
|
22
|
+
links
|
|
23
|
+
.at(1)
|
|
24
|
+
.children()
|
|
25
|
+
.text()
|
|
26
|
+
).toBe('3');
|
|
27
|
+
expect(
|
|
28
|
+
links
|
|
29
|
+
.at(2)
|
|
30
|
+
.children()
|
|
31
|
+
.text()
|
|
32
|
+
).toBe('2');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('does not render user_omitted section when count is 0', () => {
|
|
36
|
+
const wrapper = shallow(
|
|
37
|
+
<Toast syncHosts={5} disconnectHosts={3} userOmittedHosts={0} />
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Should have only 2 HostsWithStatusLink components (sync and disconnect)
|
|
41
|
+
const links = wrapper.find('HostsWithStatusLink');
|
|
42
|
+
expect(links).toHaveLength(2);
|
|
43
|
+
|
|
44
|
+
// Should not contain the user_omitted explanation text
|
|
45
|
+
expect(wrapper.text()).not.toContain(
|
|
46
|
+
'host_registration_insights_inventory parameter value is false'
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('renders without crashing when userOmittedHosts is not provided (default)', () => {
|
|
51
|
+
const wrapper = shallow(<Toast syncHosts={5} disconnectHosts={3} />);
|
|
52
|
+
|
|
53
|
+
// Should use default value of 0, so only 2 links
|
|
54
|
+
const links = wrapper.find('HostsWithStatusLink');
|
|
55
|
+
expect(links).toHaveLength(2);
|
|
56
|
+
|
|
57
|
+
// Verify the count values
|
|
58
|
+
expect(
|
|
59
|
+
links
|
|
60
|
+
.at(0)
|
|
61
|
+
.children()
|
|
62
|
+
.text()
|
|
63
|
+
).toBe('5');
|
|
64
|
+
expect(
|
|
65
|
+
links
|
|
66
|
+
.at(1)
|
|
67
|
+
.children()
|
|
68
|
+
.text()
|
|
69
|
+
).toBe('3');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('renders correct status links for each category', () => {
|
|
73
|
+
const wrapper = shallow(
|
|
74
|
+
<Toast syncHosts={5} disconnectHosts={3} userOmittedHosts={2} />
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const links = wrapper.find('HostsWithStatusLink');
|
|
78
|
+
expect(links.at(0).prop('statusName')).toBe('sync');
|
|
79
|
+
expect(links.at(1).prop('statusName')).toBe('disconnect');
|
|
80
|
+
expect(links.at(2).prop('statusName')).toBe('user_omitted');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -58,6 +58,8 @@ const InsightsTable = ({
|
|
|
58
58
|
if (hideHost) setColumns(getColumnsWithoutHostname());
|
|
59
59
|
}, [hits, selectedIds, hideHost]);
|
|
60
60
|
|
|
61
|
+
const hasSelectableRows = rows.some(row => !row.disableCheckbox);
|
|
62
|
+
|
|
61
63
|
return (
|
|
62
64
|
<React.Fragment>
|
|
63
65
|
<SelectAllAlert
|
|
@@ -71,10 +73,11 @@ const InsightsTable = ({
|
|
|
71
73
|
className="rh-cloud-recommendations-table"
|
|
72
74
|
ouiaId="rh-cloud-recommendations-table"
|
|
73
75
|
aria-label="Recommendations Table"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
{...(hasSelectableRows && {
|
|
77
|
+
onSelect: (_event, isSelected, rowId) =>
|
|
78
|
+
onTableSelect(isSelected, rowId, rows, selectedIds),
|
|
79
|
+
})}
|
|
80
|
+
canSelectAll={hasSelectableRows}
|
|
78
81
|
sortBy={{
|
|
79
82
|
index: getSortColumnIndex(columns, sortBy),
|
|
80
83
|
direction: sortOrder,
|
|
@@ -33,13 +33,28 @@ const NewHostDetailsTab = ({ hostName, router }) => {
|
|
|
33
33
|
const hits = useSelector(selectHits);
|
|
34
34
|
const isIop = useIopConfig();
|
|
35
35
|
|
|
36
|
-
useEffect(
|
|
36
|
+
useEffect(
|
|
37
|
+
() => () => {
|
|
38
|
+
// Preserve hash when clearing search params to prevent tab navigation bugs
|
|
39
|
+
if (router && typeof router.replace === 'function') {
|
|
40
|
+
const replaceOptions = { search: null };
|
|
41
|
+
if (router.location && router.location.hash) {
|
|
42
|
+
replaceOptions.hash = router.location.hash;
|
|
43
|
+
}
|
|
44
|
+
router.replace(replaceOptions);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
[router]
|
|
48
|
+
);
|
|
37
49
|
|
|
38
50
|
const onSearch = q => dispatch(fetchInsights({ query: q, page: 1 }));
|
|
39
51
|
|
|
40
52
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
41
|
-
const onSatInsightsClick = () =>
|
|
42
|
-
router.push
|
|
53
|
+
const onSatInsightsClick = () => {
|
|
54
|
+
if (router && typeof router.push === 'function') {
|
|
55
|
+
router.push({ pathname: '/foreman_rh_cloud/insights_cloud' });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
43
58
|
|
|
44
59
|
const dropdownItems = [
|
|
45
60
|
<DropdownItem key="insights-link" ouiaId="insights-link">
|
|
@@ -113,17 +128,32 @@ NewHostDetailsTab.defaultProps = {
|
|
|
113
128
|
const scope = 'advisor';
|
|
114
129
|
const module = './SystemDetailWrapped';
|
|
115
130
|
|
|
116
|
-
const IopInsightsTab = props =>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
const IopInsightsTab = props => {
|
|
132
|
+
// eslint-disable-next-line camelcase
|
|
133
|
+
const systemId = props.response?.subscription_facet_attributes?.uuid;
|
|
134
|
+
return (
|
|
135
|
+
<div className="advisor">
|
|
136
|
+
<ScalprumComponent
|
|
137
|
+
key={systemId || props.hostName}
|
|
138
|
+
scope={scope}
|
|
139
|
+
module={module}
|
|
140
|
+
IopRemediationModal={RemediationModal}
|
|
141
|
+
generateRuleUrl={generateRuleUrl}
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
IopInsightsTab.propTypes = {
|
|
149
|
+
hostName: PropTypes.string,
|
|
150
|
+
response: PropTypes.object,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
IopInsightsTab.defaultProps = {
|
|
154
|
+
hostName: '',
|
|
155
|
+
response: {},
|
|
156
|
+
};
|
|
127
157
|
|
|
128
158
|
const IopInsightsTabWrapped = props => {
|
|
129
159
|
const permissions = useInsightsPermissions();
|