foreman_rh_cloud 3.0.19 → 3.0.23

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -5
  3. data/app/controllers/api/v2/rh_cloud/inventory_controller.rb +50 -0
  4. data/app/controllers/concerns/inventory_upload/report_actions.rb +26 -0
  5. data/app/controllers/concerns/inventory_upload/task_actions.rb +25 -0
  6. data/app/controllers/foreman_inventory_upload/reports_controller.rb +3 -1
  7. data/app/controllers/foreman_inventory_upload/tasks_controller.rb +5 -13
  8. data/app/controllers/foreman_inventory_upload/uploads_controller.rb +4 -4
  9. data/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +18 -7
  10. data/app/controllers/insights_cloud/hits_controller.rb +42 -1
  11. data/app/controllers/insights_cloud/settings_controller.rb +1 -1
  12. data/app/controllers/insights_cloud/tasks_controller.rb +1 -2
  13. data/app/helpers/foreman_insights_host_helper.rb +19 -0
  14. data/app/models/insights_client_report_status.rb +58 -0
  15. data/app/models/inventory_sync/inventory_status.rb +6 -0
  16. data/app/models/setting/rh_cloud.rb +5 -5
  17. data/app/services/foreman_rh_cloud/cloud_auth.rb +12 -0
  18. data/app/services/foreman_rh_cloud/cloud_connector.rb +1 -1
  19. data/app/services/foreman_rh_cloud/cloud_request.rb +14 -0
  20. data/app/services/foreman_rh_cloud/cloud_request_forwarder.rb +1 -6
  21. data/app/services/foreman_rh_cloud/remediations_retriever.rb +75 -0
  22. data/app/services/foreman_rh_cloud/template_renderer_helper.rb +22 -0
  23. data/app/subscribers/foreman_rh_cloud/insights_subscriber.rb +9 -0
  24. data/app/views/job_templates/rh_cloud_remediations.erb +14 -0
  25. data/config/package-lock.json.plugin +32774 -0
  26. data/config/routes.rb +20 -0
  27. data/db/migrate/20210404000001_change_resolutions.foreman_rh_cloud.rb +10 -0
  28. data/db/seeds.d/179_ui_notifications.rb +11 -0
  29. data/db/seeds.d/50_job_templates.rb +14 -0
  30. data/lib/foreman_inventory_upload.rb +5 -1
  31. data/lib/foreman_inventory_upload/async/generate_all_reports_job.rb +8 -2
  32. data/lib/foreman_inventory_upload/generators/fact_helpers.rb +19 -0
  33. data/lib/foreman_inventory_upload/generators/queries.rb +3 -2
  34. data/lib/foreman_inventory_upload/generators/slice.rb +6 -6
  35. data/lib/foreman_inventory_upload/generators/tags.rb +8 -6
  36. data/lib/foreman_rh_cloud.rb +18 -0
  37. data/lib/foreman_rh_cloud/engine.rb +40 -2
  38. data/lib/foreman_rh_cloud/version.rb +1 -1
  39. data/lib/insights_cloud.rb +12 -0
  40. data/lib/insights_cloud/async/insights_full_sync.rb +31 -22
  41. data/lib/insights_cloud/async/insights_generate_notifications.rb +58 -0
  42. data/lib/insights_cloud/async/insights_resolutions_sync.rb +66 -0
  43. data/lib/insights_cloud/async/insights_rules_sync.rb +15 -24
  44. data/lib/insights_cloud/async/insights_scheduled_sync.rb +1 -1
  45. data/lib/inventory_sync/async/inventory_full_sync.rb +2 -1
  46. data/lib/inventory_sync/async/inventory_hosts_sync.rb +6 -2
  47. data/lib/inventory_sync/async/inventory_scheduled_sync.rb +29 -0
  48. data/lib/inventory_sync/async/query_inventory_job.rb +1 -4
  49. data/lib/tasks/insights.rake +4 -12
  50. data/lib/tasks/rh_cloud_inventory.rake +20 -5
  51. data/package.json +1 -1
  52. data/test/controllers/inventory_upload/api/inventory_controller_test.rb +53 -0
  53. data/test/factories/insights_factories.rb +22 -0
  54. data/test/factories/inventory_upload_factories.rb +1 -1
  55. data/test/jobs/insights_full_sync_test.rb +17 -8
  56. data/test/jobs/insights_resolutions_sync_test.rb +77 -0
  57. data/test/jobs/insights_rules_sync_test.rb +8 -3
  58. data/test/jobs/inventory_full_sync_test.rb +4 -1
  59. data/test/jobs/inventory_hosts_sync_test.rb +265 -0
  60. data/test/jobs/inventory_scheduled_sync_test.rb +22 -0
  61. data/test/models/insights_client_report_status_test.rb +77 -0
  62. data/test/test_plugin_helper.rb +2 -0
  63. data/test/unit/rh_cloud_http_proxy_test.rb +4 -4
  64. data/test/unit/services/foreman_rh_cloud/remediations_retriever_test.rb +49 -0
  65. data/test/unit/services/foreman_rh_cloud/template_renderer_helper_test.rb +28 -0
  66. data/test/unit/slice_generator_test.rb +66 -29
  67. data/test/unit/tags_generator_test.rb +10 -0
  68. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModal.js +1 -1
  69. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/__tests__/__snapshots__/FullScreenModal.test.js.snap +1 -1
  70. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/fullScreenModal.scss +14 -16
  71. data/webpack/ForemanInventoryUpload/Components/InventorySettings/AdvancedSetting/AdvancedSettingsConstants.js +5 -3
  72. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/PageDescription/PageDescription.js +26 -2
  73. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/PageDescription/__tests__/__snapshots__/PageDescription.test.js.snap +24 -2
  74. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/SyncButtonActions.js +28 -63
  75. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/integrations.test.js.snap +2 -3
  76. data/webpack/ForemanInventoryUpload/Components/Terminal/Terminal.js +1 -1
  77. data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/Terminal.test.js +1 -1
  78. data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/__snapshots__/Terminal.test.js.snap +2 -2
  79. data/webpack/ForemanInventoryUpload/Components/Terminal/terminal.scss +25 -27
  80. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +1 -1
  81. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableActions.js +19 -19
  82. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableSelectors.js +3 -0
  83. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTableActions.test.js.snap +14 -14
  84. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediateButton.js +60 -0
  85. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationActions.js +12 -0
  86. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +43 -0
  87. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +101 -0
  88. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.scss +9 -0
  89. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModalFooter.js +43 -0
  90. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationTableConstants.js +38 -0
  91. data/webpack/InsightsCloudSync/Components/RemediationModal/Resolutions.js +55 -0
  92. data/webpack/InsightsCloudSync/Components/RemediationModal/index.js +34 -0
  93. data/webpack/InsightsCloudSync/Components/__tests__/__snapshots__/NoTokenEmptyState.test.js.snap +20 -13
  94. data/webpack/InsightsCloudSync/InsightsCloudSync.js +11 -3
  95. data/webpack/InsightsCloudSync/InsightsCloudSync.scss +5 -0
  96. data/webpack/InsightsCloudSync/InsightsCloudSyncActions.js +44 -20
  97. data/webpack/InsightsCloudSync/InsightsCloudSyncConstants.js +2 -0
  98. data/webpack/InsightsCloudSync/__snapshots__/InsightsCloudSync.test.js.snap +9 -6
  99. data/webpack/InsightsCloudSync/__tests__/__snapshots__/InsightsCloudSyncActions.test.js.snap +11 -7
  100. data/webpack/common/ForemanTasks/ForemanTasksActions.js +64 -0
  101. data/webpack/common/ForemanTasks/ForemanTasksHelpers.js +7 -0
  102. data/webpack/common/ForemanTasks/index.js +1 -0
  103. data/webpack/{InsightsCloudSync/Components/InsightsTable/components → common/table}/EmptyState.js +0 -0
  104. data/webpack/common/table/helpers.js +7 -0
  105. metadata +49 -4
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { isEmpty } from 'lodash';
4
+ import { Button, Popover } from '@patternfly/react-core';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import { foremanUrl } from '../../../ForemanRhCloudHelpers';
7
+
8
+ const RemediateButton = ({ isExperimentalMode, selectedIds, toggleModal }) => {
9
+ const [isVisible, setVisible] = React.useState(true);
10
+
11
+ const popoverContent = __(
12
+ `To use this feature, please enable <a href=${foremanUrl(
13
+ '/settings?search=name+%3D+lab_features'
14
+ )}>Show Experimental Labs</a> in settings.`
15
+ );
16
+
17
+ let button = (
18
+ <Button
19
+ variant="primary"
20
+ isDisabled={isEmpty(selectedIds)}
21
+ onClick={() => {
22
+ if (!isExperimentalMode) {
23
+ setVisible(value => !value);
24
+ } else {
25
+ toggleModal();
26
+ }
27
+ }}
28
+ >
29
+ {__('Remediate')}
30
+ </Button>
31
+ );
32
+
33
+ if (!isExperimentalMode) {
34
+ button = (
35
+ <Popover
36
+ isVisible={isVisible}
37
+ aria-label={__('Please enable lab features to use this button')}
38
+ bodyContent={
39
+ <div dangerouslySetInnerHTML={{ __html: popoverContent }} />
40
+ }
41
+ >
42
+ {button}
43
+ </Popover>
44
+ );
45
+ }
46
+ return button;
47
+ };
48
+
49
+ RemediateButton.propTypes = {
50
+ selectedIds: PropTypes.object,
51
+ isExperimentalMode: PropTypes.bool,
52
+ toggleModal: PropTypes.func.isRequired,
53
+ };
54
+
55
+ RemediateButton.defaultProps = {
56
+ selectedIds: {},
57
+ isExperimentalMode: false,
58
+ };
59
+
60
+ export default RemediateButton;
@@ -0,0 +1,12 @@
1
+ import { get } from 'foremanReact/redux/API';
2
+ import {
3
+ REMEDIATIONS_API_KEY,
4
+ REMEDIATIONS_PATH,
5
+ } from './RemediationTableConstants';
6
+
7
+ export const fetchRemediations = ({ selectedIds, isAllSelected, query }) =>
8
+ get({
9
+ key: REMEDIATIONS_API_KEY,
10
+ url: REMEDIATIONS_PATH,
11
+ params: { ids: Object.keys(selectedIds), isAllSelected, query },
12
+ });
@@ -0,0 +1,43 @@
1
+ /* eslint-disable camelcase */
2
+ import React from 'react';
3
+ import { orderBy } from 'lodash';
4
+ import Resolutions from './Resolutions';
5
+
6
+ export const modifyRows = (remediations, setResolutions, setHostsIds) => {
7
+ if (remediations.length === 0) return [];
8
+
9
+ const resolutionToSubmit = [];
10
+ const hostsIdsToSubmit = new Set();
11
+ const modifiedRemediations = orderBy(
12
+ remediations.asMutable(),
13
+ [r => r.resolutions?.length || 0],
14
+ ['desc']
15
+ ).map(({ id, host_id, hostname, title, resolutions, reboot }) => {
16
+ hostsIdsToSubmit.add(host_id);
17
+ const selectedResolution = resolutions[0]?.id;
18
+ resolutionToSubmit.push({
19
+ hit_id: id,
20
+ resolution_id: selectedResolution /** defaults to the first resolution if many */,
21
+ });
22
+ return {
23
+ cells: [
24
+ hostname,
25
+ title,
26
+ <div>
27
+ <Resolutions
28
+ hit_id={id}
29
+ resolutions={resolutions}
30
+ setResolutions={setResolutions}
31
+ selectedResolution={selectedResolution}
32
+ />
33
+ </div>,
34
+ reboot,
35
+ ],
36
+ id,
37
+ };
38
+ });
39
+
40
+ setResolutions(resolutionToSubmit);
41
+ setHostsIds(Array.from(hostsIdsToSubmit));
42
+ return modifiedRemediations;
43
+ };
@@ -0,0 +1,101 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps */
2
+ import React, { useEffect } from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import { Table, TableHeader, TableBody } from '@patternfly/react-table';
5
+ import { Modal, ModalVariant } from '@patternfly/react-core';
6
+ import { STATUS } from 'foremanReact/constants';
7
+ import { translate as __ } from 'foremanReact/common/I18n';
8
+ import { columns } from './RemediationTableConstants';
9
+ import { modifyRows } from './RemediationHelpers';
10
+ import ModalFooter from './RemediationModalFooter';
11
+ import TableEmptyState from '../../../common/table/EmptyState';
12
+ import './RemediationModal.scss';
13
+ import RemediateButton from './RemediateButton';
14
+
15
+ const RemediationModal = ({
16
+ selectedIds,
17
+ fetchRemediations,
18
+ remediations,
19
+ status,
20
+ error,
21
+ isAllSelected,
22
+ query,
23
+ isExperimentalMode,
24
+ }) => {
25
+ const [rows, setRows] = React.useState([]);
26
+ const [open, setOpen] = React.useState(false);
27
+ const [resolutions, setResolutions] = React.useState([]);
28
+ const [hostsIds, setHostsIds] = React.useState([]);
29
+ const toggleModal = () => setOpen(prevValue => !prevValue);
30
+
31
+ useEffect(() => {
32
+ if (open) fetchRemediations({ selectedIds, isAllSelected, query });
33
+ }, [open]);
34
+
35
+ useEffect(() => {
36
+ const modifiedRows =
37
+ status === STATUS.PENDING
38
+ ? []
39
+ : modifyRows(remediations, setResolutions, setHostsIds);
40
+ setRows(modifiedRows);
41
+ }, [remediations, status]);
42
+
43
+ return (
44
+ <React.Fragment>
45
+ <RemediateButton
46
+ isExperimentalMode={isExperimentalMode}
47
+ selectedIds={selectedIds}
48
+ toggleModal={toggleModal}
49
+ />{' '}
50
+ <Modal
51
+ id="remediation-modal"
52
+ appendTo={document.body}
53
+ variant={ModalVariant.large}
54
+ title={__('Remediation summary')}
55
+ isOpen={open}
56
+ onClose={toggleModal}
57
+ footer={
58
+ <ModalFooter
59
+ toggleModal={toggleModal}
60
+ resolutions={resolutions}
61
+ hostsIds={hostsIds}
62
+ />
63
+ }
64
+ >
65
+ <Table
66
+ className="remediations-table"
67
+ aria-label="remediations Table"
68
+ cells={columns}
69
+ rows={rows}
70
+ >
71
+ <TableHeader />
72
+ <TableBody />
73
+ </Table>
74
+ <TableEmptyState status={status} error={error} />
75
+ </Modal>
76
+ </React.Fragment>
77
+ );
78
+ };
79
+
80
+ RemediationModal.propTypes = {
81
+ selectedIds: PropTypes.object,
82
+ fetchRemediations: PropTypes.func.isRequired,
83
+ remediations: PropTypes.array,
84
+ status: PropTypes.string,
85
+ error: PropTypes.string,
86
+ isAllSelected: PropTypes.bool,
87
+ query: PropTypes.string,
88
+ isExperimentalMode: PropTypes.bool,
89
+ };
90
+
91
+ RemediationModal.defaultProps = {
92
+ selectedIds: {},
93
+ remediations: [],
94
+ status: null,
95
+ error: null,
96
+ isAllSelected: false,
97
+ query: null,
98
+ isExperimentalMode: false,
99
+ };
100
+
101
+ export default RemediationModal;
@@ -0,0 +1,9 @@
1
+ #remediation-modal {
2
+ height: 80vh;
3
+
4
+ .resolution-radio {
5
+ label {
6
+ margin-top: 5px;
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Button } from '@patternfly/react-core';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import { JOB_INVOCATION_PATH } from './RemediationTableConstants';
6
+
7
+ const ModalFooter = ({ toggleModal, resolutions, hostsIds }) => {
8
+ let token = document.querySelector('meta[name="csrf-token"]');
9
+ token = token?.content || '';
10
+ return (
11
+ <form action={JOB_INVOCATION_PATH} method="post">
12
+ <Button type="submit" key="confirm" variant="primary">
13
+ {__('Remediate')}
14
+ </Button>
15
+ <Button key="cancel" variant="link" onClick={toggleModal}>
16
+ {__('Cancel')}
17
+ </Button>
18
+ <input type="hidden" name="feature" value="rh_cloud_remediate_hosts" />
19
+ <input type="hidden" name="authenticity_token" value={token} />
20
+ <input
21
+ type="hidden"
22
+ name="inputs[hit_remediation_pairs]"
23
+ value={JSON.stringify(resolutions)}
24
+ />
25
+ {hostsIds.map(id => (
26
+ <input type="hidden" name="host_ids[]" key={id} value={id} />
27
+ ))}
28
+ </form>
29
+ );
30
+ };
31
+
32
+ ModalFooter.propTypes = {
33
+ toggleModal: PropTypes.func.isRequired,
34
+ resolutions: PropTypes.array,
35
+ hostsIds: PropTypes.array,
36
+ };
37
+
38
+ ModalFooter.defaultProps = {
39
+ resolutions: [],
40
+ hostsIds: [],
41
+ };
42
+
43
+ export default ModalFooter;
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { cellWidth } from '@patternfly/react-table';
3
+ import { CheckCircleIcon } from '@patternfly/react-icons';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import { foremanUrl } from '../../../ForemanRhCloudHelpers';
6
+
7
+ export const rebootFormatter = ({ title: reboot }) => ({
8
+ children: reboot ? <CheckCircleIcon color="green" /> : __('No'),
9
+ });
10
+
11
+ export const columns = [
12
+ {
13
+ sortKey: 'hostname',
14
+ title: __('Hostname'),
15
+ transforms: [cellWidth(20)],
16
+ },
17
+ {
18
+ title: __('Recommendation'),
19
+ transforms: [cellWidth(35)],
20
+ },
21
+ {
22
+ title: __('Resolution'),
23
+ transforms: [cellWidth(30)],
24
+ },
25
+ {
26
+ title: __('Reboot Required'),
27
+ transforms: [cellWidth(15)],
28
+ cellTransforms: [rebootFormatter],
29
+ },
30
+ ];
31
+
32
+ export const REMEDIATIONS_PATH = foremanUrl('/insights_cloud/hits/resolutions');
33
+
34
+ export const JOB_INVOCATION_PATH = foremanUrl('/job_invocations/new');
35
+
36
+ export const REMEDIATIONS_API_KEY = 'INSIGHTS_REMEDIATIONS';
37
+
38
+ export const SUBMIT_RESOLUTIONS = 'SUBMIT_INSIGHTS_RESOLUTIONS';
@@ -0,0 +1,55 @@
1
+ /* eslint-disable camelcase */
2
+ import React from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import { Radio } from '@patternfly/react-core';
5
+
6
+ const Resolutions = ({
7
+ resolutions,
8
+ setResolutions,
9
+ selectedResolution,
10
+ hit_id,
11
+ }) => {
12
+ const [checkedID, setCheckedID] = React.useState(selectedResolution);
13
+
14
+ if (resolutions.length === 1) return <>{resolutions[0].description}</>;
15
+
16
+ return (
17
+ <>
18
+ {resolutions.map(({ id: resolution_id, description }) => (
19
+ <Radio
20
+ key={resolution_id}
21
+ className="resolution-radio"
22
+ id={resolution_id}
23
+ isChecked={resolution_id === checkedID}
24
+ onChange={() =>
25
+ setResolutions(stateRes =>
26
+ stateRes.map(res => {
27
+ if (hit_id === res.hit_id) {
28
+ setCheckedID(resolution_id);
29
+ return { ...res, resolution_id };
30
+ }
31
+ return res;
32
+ })
33
+ )
34
+ }
35
+ label={description}
36
+ />
37
+ ))}
38
+ </>
39
+ );
40
+ };
41
+
42
+ Resolutions.propTypes = {
43
+ setResolutions: PropTypes.func.isRequired,
44
+ resolutions: PropTypes.array,
45
+ hit_id: PropTypes.number,
46
+ selectedResolution: PropTypes.number,
47
+ };
48
+
49
+ Resolutions.defaultProps = {
50
+ resolutions: [],
51
+ hit_id: null,
52
+ selectedResolution: null,
53
+ };
54
+
55
+ export default Resolutions;
@@ -0,0 +1,34 @@
1
+ import { bindActionCreators } from 'redux';
2
+ import { connect } from 'react-redux';
3
+ import {
4
+ selectAPIResponse,
5
+ selectAPIStatus,
6
+ selectAPIErrorMessage,
7
+ } from 'foremanReact/redux/API/APISelectors';
8
+ import * as actions from './RemediationActions';
9
+ import RemediationModal from './RemediationModal';
10
+ import { REMEDIATIONS_API_KEY } from './RemediationTableConstants';
11
+ import {
12
+ selectExperimental,
13
+ selectIsAllSelected,
14
+ selectSearch,
15
+ selectSelectedIds,
16
+ } from '../InsightsTable/InsightsTableSelectors';
17
+
18
+ // map state to props
19
+ const mapStateToProps = state => ({
20
+ selectedIds: selectSelectedIds(state),
21
+ remediations: selectAPIResponse(state, REMEDIATIONS_API_KEY).hits || [],
22
+ status: selectAPIStatus(state, REMEDIATIONS_API_KEY),
23
+ error: selectAPIErrorMessage(state, REMEDIATIONS_API_KEY),
24
+ itemCount: selectAPIResponse(state, REMEDIATIONS_API_KEY).itemCount || 0,
25
+ isAllSelected: selectIsAllSelected(state),
26
+ query: selectSearch(state),
27
+ isExperimentalMode: selectExperimental(state),
28
+ });
29
+
30
+ // map action dispatchers to props
31
+ const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
32
+
33
+ // export connected component
34
+ export default connect(mapStateToProps, mapDispatchToProps)(RemediationModal);
@@ -190,21 +190,28 @@ exports[`NoTokenEmptyState render 1`] = `
190
190
  onClick={[Function]}
191
191
  variant="primary"
192
192
  >
193
- <button
194
- aria-disabled={true}
195
- aria-label={null}
196
- className="pf-c-button pf-m-primary pf-m-disabled"
197
- data-ouia-component-id="OUIA-Generated-Button-primary-1"
198
- data-ouia-component-type="PF4/Button"
199
- data-ouia-safe={true}
200
- disabled={true}
193
+ <ButtonBase
194
+ innerRef={null}
195
+ isDisabled={true}
201
196
  onClick={[Function]}
202
- role={null}
203
- tabIndex={null}
204
- type="button"
197
+ variant="primary"
205
198
  >
206
- Save setting and sync recommendations
207
- </button>
199
+ <button
200
+ aria-disabled={true}
201
+ aria-label={null}
202
+ className="pf-c-button pf-m-primary pf-m-disabled"
203
+ data-ouia-component-id="OUIA-Generated-Button-primary-1"
204
+ data-ouia-component-type="PF4/Button"
205
+ data-ouia-safe={true}
206
+ disabled={true}
207
+ onClick={[Function]}
208
+ role={null}
209
+ tabIndex={null}
210
+ type="button"
211
+ >
212
+ Save setting and sync recommendations
213
+ </button>
214
+ </ButtonBase>
208
215
  </Button>
209
216
  </div>
210
217
  </div>