foreman_webhooks 5.0.3 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +0 -15
  3. data/app/models/webhook.rb +1 -1
  4. data/lib/foreman_webhooks/engine.rb +1 -1
  5. data/lib/foreman_webhooks/version.rb +1 -1
  6. data/webpack/ForemanWebhooks/Routes/ForemanWebhooksRoutes.js +1 -1
  7. data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/WebhookFormTabs.js +4 -6
  8. data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookTable/Components/Formatters/__tests__/actionCellFormatter.test.js +109 -0
  9. data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookTable/Components/Formatters/__tests__/enabledCellFormatter.test.js +38 -0
  10. data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookTable/Components/Formatters/__tests__/nameToEditFormatter.test.js +82 -0
  11. data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookTable/Components/Formatters/actionCellFormatter.js +23 -0
  12. data/webpack/ForemanWebhooks/Routes/Webhooks/{WebhooksIndexPage/Components/WebhooksTable/Components/EnabledCell.js → Components/WebhookTable/Components/Formatters/enabledCellFormatter.js} +6 -1
  13. data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookTable/Components/Formatters/nameToEditFormatter.js +33 -0
  14. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhookCreateModal.js +1 -0
  15. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhookDeleteModal.js +5 -1
  16. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhookEditModal.js +1 -0
  17. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhookTestModal.js +1 -2
  18. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/WebhooksIndexPage.js +120 -16
  19. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/__tests__/WebhooksIndexPage.test.js +130 -22
  20. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/__tests__/integration.test.js +10 -3
  21. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/__tests__/mocks/MockForemanTableIndexPage.js +37 -0
  22. data/webpack/ForemanWebhooks/Routes/Webhooks/constants.js +3 -3
  23. data/webpack/test_setup.js +3 -0
  24. metadata +18 -96
  25. data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/FieldConstructor.js +0 -312
  26. data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/__tests__/FieldConstructor.test.js +0 -216
  27. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/EmptyWebhooksTable/index.js +0 -29
  28. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/__tests__/__snapshots__/enabledCellFormatter.test.js.snap +0 -7
  29. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/__tests__/enabledCellFormatter.test.js +0 -7
  30. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/actionCellFormatter.js +0 -20
  31. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/enabledCellFormatter.js +0 -6
  32. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/index.js +0 -3
  33. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/nameToEditFormatter.js +0 -19
  34. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/NameToEditCell.js +0 -42
  35. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/__tests__/EnabledCell.test.js +0 -14
  36. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/__tests__/__snapshots__/EnabledCell.test.js.snap +0 -5
  37. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/WebhooksTable.js +0 -121
  38. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/WebhooksTableSchema.js +0 -48
  39. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/__tests__/WebhooksTable.test.js +0 -64
  40. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/__tests__/__snapshots__/WebhooksTable.test.js.snap +0 -250
  41. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/index.js +0 -76
  42. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/__tests__/__snapshots__/WebhooksIndexPage.test.js.snap +0 -17
  43. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/index.js +0 -3
  44. data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext.js +0 -1
  45. data/webpack/__mocks__/foremanReact/common/HOC.js +0 -30
  46. data/webpack/__mocks__/foremanReact/common/I18n.js +0 -7
  47. data/webpack/__mocks__/foremanReact/common/helpers.js +0 -7
  48. data/webpack/__mocks__/foremanReact/common/urlHelpers.js +0 -8
  49. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js +0 -2
  50. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js +0 -10
  51. data/webpack/__mocks__/foremanReact/components/ForemanModal/index.js +0 -23
  52. data/webpack/__mocks__/foremanReact/components/Layout/LayoutActions.js +0 -2
  53. data/webpack/__mocks__/foremanReact/components/Loading/Loading.js +0 -2
  54. data/webpack/__mocks__/foremanReact/components/Loading/index.js +0 -3
  55. data/webpack/__mocks__/foremanReact/components/PF4/TableIndexPage/TableIndexPage.js +0 -10
  56. data/webpack/__mocks__/foremanReact/components/Pagination/index.js +0 -4
  57. data/webpack/__mocks__/foremanReact/components/common/ActionButtons/ActionButtons.js +0 -3
  58. data/webpack/__mocks__/foremanReact/components/common/EmptyState.js +0 -5
  59. data/webpack/__mocks__/foremanReact/components/common/forms/ForemanForm.js +0 -9
  60. data/webpack/__mocks__/foremanReact/components/common/forms/FormField.js +0 -3
  61. data/webpack/__mocks__/foremanReact/components/common/table.js +0 -26
  62. data/webpack/__mocks__/foremanReact/constants.js +0 -24
  63. data/webpack/__mocks__/foremanReact/history.js +0 -3
  64. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +0 -6
  65. data/webpack/__mocks__/foremanReact/redux/API/index.js +0 -10
  66. data/webpack/__mocks__/foremanReact/redux/actions/common/forms.js +0 -1
  67. data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +0 -8
  68. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +0 -10
  69. /data/webpack/ForemanWebhooks/Routes/Webhooks/{WebhooksIndexPage/Components/WebhooksTable → Components/WebhookTable}/Components/ActionButtons/ActionButton.js +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25c2a83a7c2015b3e3d0b184beb4ce534c27bbe90cbd9fb301d138d7e183e5c5
4
- data.tar.gz: b0a16fa70ffde8610190a3c0718046d4c0dedf25633a21bfb5ba341efde778ce
3
+ metadata.gz: 0b5922c7ae2630c642e151ea99d59e8fce67ca342a93bd595a3c80d8837db0c7
4
+ data.tar.gz: c7d0bc6894477642639612f3d7b68974ea90dff25fabb91c3d684316fcd12230
5
5
  SHA512:
6
- metadata.gz: ce79fa03fd4dccce1a495510bbeded83546b3330dde622fa5b4d03ef61d8eb1f0d752c72257ab36f6fcc28cf7b1c8164fb500c764914d6f06d0712ce756c38cb
7
- data.tar.gz: 5a949f01600d04a667e710107e2275f58e104ced6a5317c90a21285851cb7a9e39a85770cdd34b9c6e4bed029fe9d705b4bfd31e1cd9680b1eceac1456bb6488
6
+ metadata.gz: 3f8c365cbf5d75887f69a3ffe3c9949e4fdfff3ca3129f12432747dda722ddf017892f700eb712353a2d805e07a53ff1f82f278afe8db3ac92ef6bec802bb76a
7
+ data.tar.gz: 34717dcb661c339a41377fbaa2c40603d0821bc5a0949f80abfd828497c49c9778543cd87d4505207d2e1b92cc9f684e7adca9c90eca4d854d6267dcce28a964
data/Rakefile CHANGED
@@ -6,21 +6,6 @@ begin
6
6
  rescue LoadError
7
7
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
8
8
  end
9
- begin
10
- require 'rdoc/task'
11
- rescue LoadError
12
- require 'rdoc/rdoc'
13
- require 'rake/rdoctask'
14
- RDoc::Task = Rake::RDocTask
15
- end
16
-
17
- RDoc::Task.new(:rdoc) do |rdoc|
18
- rdoc.rdoc_dir = 'rdoc'
19
- rdoc.title = 'ForemanWebhooks'
20
- rdoc.options << '--line-numbers'
21
- rdoc.rdoc_files.include('README.rdoc')
22
- rdoc.rdoc_files.include('lib/**/*.rb')
23
- end
24
9
 
25
10
  APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
26
11
 
@@ -11,7 +11,7 @@ class Webhook < ApplicationRecord
11
11
 
12
12
  graphql_type 'ForemanWebhooks::Types::Webhook'
13
13
 
14
- EVENT_POSTFIX = ".#{Foreman::Observable::DEFAULT_NAMESPACE}"
14
+ EVENT_POSTFIX = ".#{Foreman::Observable::DEFAULT_NAMESPACE}".freeze
15
15
 
16
16
  DEFAULT_PAYLOAD_TEMPLATE = 'Webhook Template - Payload Default'
17
17
 
@@ -14,7 +14,7 @@ module ForemanWebhooks
14
14
  initializer 'foreman_webhooks.register_plugin', before: :finisher_hook do |app|
15
15
  app.reloader.to_prepare do
16
16
  Foreman::Plugin.register :foreman_webhooks do
17
- requires_foreman '>= 3.18'
17
+ requires_foreman '>= 5.0'
18
18
  register_gettext
19
19
 
20
20
  apipie_documented_controllers ["#{ForemanWebhooks::Engine.root}/app/controllers/api/v2/*.rb"]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ForemanWebhooks
4
- VERSION = "5.0.3"
4
+ VERSION = "6.0.0"
5
5
  end
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import ConnectedWebhooksIndexPage from './Webhooks/WebhooksIndexPage';
2
+ import ConnectedWebhooksIndexPage from './Webhooks/WebhooksIndexPage/WebhooksIndexPage';
3
3
 
4
4
  const ForemanWebhooksRoutes = [
5
5
  {
@@ -2,10 +2,10 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Tabs, Tab, TabTitleText } from '@patternfly/react-core';
4
4
 
5
+ import FieldConstructor from 'foremanReact/components/common/FieldConstructor/FieldConstructor';
5
6
  import { sprintf, translate as __ } from 'foremanReact/common/I18n';
6
7
 
7
8
  import './WebhookFormTabs.css';
8
- import FieldConstructor from './FieldConstructor';
9
9
 
10
10
  const WebhookFormTabs = ({
11
11
  inputValues,
@@ -48,7 +48,7 @@ const WebhookFormTabs = ({
48
48
  type="select"
49
49
  label={__('Subscribe to')}
50
50
  required
51
- allowClear={false}
51
+ allowClear
52
52
  options={availableEvents}
53
53
  isLoading={isEventsLoading}
54
54
  placeholder={__('Start typing to search...')}
@@ -93,7 +93,7 @@ const WebhookFormTabs = ({
93
93
  type="select"
94
94
  label={__('Template')}
95
95
  required
96
- allowClear={false}
96
+ allowClear
97
97
  options={webhookTemplates}
98
98
  isLoading={isTemplatesLoading}
99
99
  placeholder={__('Start typing to search...')}
@@ -105,7 +105,7 @@ const WebhookFormTabs = ({
105
105
  type="select"
106
106
  label={__('HTTP Method')}
107
107
  required
108
- allowClear={false}
108
+ allowClear
109
109
  options={httpMethods}
110
110
  placeholder={__('Start typing to search...')}
111
111
  />
@@ -173,7 +173,6 @@ const WebhookFormTabs = ({
173
173
  placeholder={__(
174
174
  "Optional CAs in PEM format concatenated to verify the receiver's SSL certificate"
175
175
  )}
176
- inputSizeClass="col-md-8"
177
176
  rows={8}
178
177
  setValue={updateFieldValue}
179
178
  />
@@ -200,7 +199,6 @@ const WebhookFormTabs = ({
200
199
  label={__('HTTP Headers')}
201
200
  labelHelp={__('Optional. Must be a JSON object (ERB allowed)')}
202
201
  placeholder='{&#13;&#10;"X-Shellhook-Arg-1": "value",&#13;&#10;"X-Shellhook-Arg-2": "<%= @object.id %>"&#13;&#10;}'
203
- inputSizeClass="col-md-8"
204
202
  rows={8}
205
203
  setValue={updateFieldValue}
206
204
  />
@@ -0,0 +1,109 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+
5
+ import actionCellFormatter from '../actionCellFormatter';
6
+
7
+ jest.mock(
8
+ 'foremanReact/components/common/table',
9
+ () => ({
10
+ cellFormatter: content => (
11
+ <div data-testid="cell-formatter-mock">
12
+ {content === false ? null : content}
13
+ </div>
14
+ ),
15
+ }),
16
+ { virtual: true }
17
+ );
18
+
19
+ jest.mock('../../ActionButtons/ActionButton', () => {
20
+ // eslint-disable-next-line global-require
21
+ const PropTypes = require('prop-types');
22
+
23
+ const ActionButton = ({ id, name, canDelete }) => (
24
+ <span
25
+ data-testid="action-button"
26
+ data-id={id}
27
+ data-name={name}
28
+ data-can-delete={String(canDelete)}
29
+ />
30
+ );
31
+ ActionButton.propTypes = {
32
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
33
+ name: PropTypes.string.isRequired,
34
+ canDelete: PropTypes.bool,
35
+ };
36
+ ActionButton.defaultProps = {
37
+ canDelete: false,
38
+ };
39
+
40
+ return { ActionButton };
41
+ });
42
+
43
+ describe('actionCellFormatter', () => {
44
+ const webhookActions = {
45
+ deleteWebhook: jest.fn(),
46
+ testWebhook: jest.fn(),
47
+ };
48
+
49
+ beforeEach(() => {
50
+ jest.clearAllMocks();
51
+ });
52
+
53
+ it('wraps the action control in cellFormatter when rowData is present and the row is editable', () => {
54
+ const formatter = actionCellFormatter(webhookActions);
55
+ const row = { id: 10, name: 'Wh', can_edit: true, can_delete: true };
56
+
57
+ render(formatter(null, { rowData: row }));
58
+
59
+ expect(screen.getByTestId('cell-formatter-mock')).toBeInTheDocument();
60
+ const action = screen.getByTestId('action-button');
61
+ expect(action).toHaveAttribute('data-id', '10');
62
+ expect(action).toHaveAttribute('data-name', 'Wh');
63
+ expect(action).toHaveAttribute('data-can-delete', 'true');
64
+ });
65
+
66
+ it('passes canDelete from can_delete when rowData is present', () => {
67
+ const formatter = actionCellFormatter(webhookActions);
68
+ const row = { id: 2, name: 'A', can_edit: true, can_delete: false };
69
+
70
+ render(formatter(null, { rowData: row }));
71
+
72
+ expect(screen.getByTestId('action-button')).toHaveAttribute(
73
+ 'data-can-delete',
74
+ 'false'
75
+ );
76
+ });
77
+
78
+ it('renders falsy content through cellFormatter when rowData is present but the row is not editable', () => {
79
+ const formatter = actionCellFormatter(webhookActions);
80
+ const row = { id: 5, name: 'B', can_edit: false };
81
+
82
+ render(formatter(null, { rowData: row }));
83
+
84
+ expect(screen.getByTestId('cell-formatter-mock')).toBeInTheDocument();
85
+ expect(screen.queryByTestId('action-button')).not.toBeInTheDocument();
86
+ });
87
+
88
+ it('returns the action control without cellFormatter when rowData is omitted', () => {
89
+ const formatter = actionCellFormatter(webhookActions);
90
+ const row = { id: 20, name: 'Direct', can_edit: true, canDelete: true };
91
+
92
+ render(formatter(row));
93
+
94
+ expect(screen.queryByTestId('cell-formatter-mock')).not.toBeInTheDocument();
95
+ expect(screen.getByTestId('action-button')).toHaveAttribute(
96
+ 'data-id',
97
+ '20'
98
+ );
99
+ });
100
+
101
+ it('returns nothing when rowData is omitted and the row is not editable', () => {
102
+ const formatter = actionCellFormatter(webhookActions);
103
+ const row = { id: 1, name: 'X', can_edit: false };
104
+
105
+ const { container } = render(formatter(row));
106
+
107
+ expect(container).toBeEmptyDOMElement();
108
+ });
109
+ });
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+
5
+ import enabledCellFormatter from '../enabledCellFormatter';
6
+
7
+ jest.mock('@patternfly/react-icons', () => ({
8
+ CheckIcon: () => <span data-testid="check-icon" />,
9
+ BanIcon: () => <span data-testid="ban-icon" />,
10
+ }));
11
+
12
+ const renderCell = (value, extra) => {
13
+ const formatter = enabledCellFormatter();
14
+ const element = formatter(value, extra);
15
+ return render(element);
16
+ };
17
+
18
+ describe('enabledCellFormatter', () => {
19
+ it('uses the cell value when row context includes rowData and value is true', () => {
20
+ renderCell(true, { rowData: { id: 1, enabled: false } });
21
+ expect(screen.getByTestId('check-icon')).toBeInTheDocument();
22
+ });
23
+
24
+ it('uses the cell value when row context includes rowData and value is false', () => {
25
+ renderCell(false, { rowData: { id: 1, enabled: true } });
26
+ expect(screen.getByTestId('ban-icon')).toBeInTheDocument();
27
+ });
28
+
29
+ it('uses value.enabled when rowData is not present and enabled is true', () => {
30
+ renderCell({ enabled: true });
31
+ expect(screen.getByTestId('check-icon')).toBeInTheDocument();
32
+ });
33
+
34
+ it('uses value.enabled when rowData is not present and enabled is false', () => {
35
+ renderCell({ enabled: false });
36
+ expect(screen.getByTestId('ban-icon')).toBeInTheDocument();
37
+ });
38
+ });
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+
5
+ import nameToEditFormatter from '../nameToEditFormatter';
6
+
7
+ jest.mock('@patternfly/react-core', () => {
8
+ // eslint-disable-next-line global-require
9
+ const PropTypes = require('prop-types');
10
+
11
+ const Button = ({ children, onClick, isDisabled }) => (
12
+ <button type="button" onClick={onClick} disabled={isDisabled}>
13
+ {children}
14
+ </button>
15
+ );
16
+ Button.propTypes = {
17
+ children: PropTypes.node,
18
+ onClick: PropTypes.func,
19
+ isDisabled: PropTypes.bool,
20
+ };
21
+ Button.defaultProps = {
22
+ children: null,
23
+ onClick: () => {},
24
+ isDisabled: false,
25
+ };
26
+
27
+ return { Button };
28
+ });
29
+
30
+ describe('nameToEditFormatter', () => {
31
+ it('renders the cell value as the label and calls the handler with the row id when can_edit is true', () => {
32
+ const onEdit = jest.fn();
33
+ const formatter = nameToEditFormatter(onEdit);
34
+ const row = { id: 42, name: 'Other', can_edit: true };
35
+
36
+ render(formatter('Shown name', { rowData: row }));
37
+
38
+ expect(screen.getByText('Shown name')).toBeInTheDocument();
39
+ fireEvent.click(screen.getByText('Shown name'));
40
+ expect(onEdit).toHaveBeenCalledTimes(1);
41
+ expect(onEdit).toHaveBeenCalledWith(42);
42
+ });
43
+
44
+ it('prefers canEdit over can_edit when both are set', () => {
45
+ const onEdit = jest.fn();
46
+ const formatter = nameToEditFormatter(onEdit);
47
+
48
+ render(
49
+ formatter('Label', {
50
+ rowData: { id: 1, name: 'N', canEdit: true, can_edit: false },
51
+ })
52
+ );
53
+
54
+ fireEvent.click(screen.getByText('Label'));
55
+ expect(onEdit).toHaveBeenCalledWith(1);
56
+ });
57
+
58
+ it('does not call the handler when the row cannot be edited', () => {
59
+ const onEdit = jest.fn();
60
+ const formatter = nameToEditFormatter(onEdit);
61
+
62
+ render(
63
+ formatter('Read only', {
64
+ rowData: { id: 7, name: 'X', can_edit: false },
65
+ })
66
+ );
67
+
68
+ const control = screen.getByText('Read only');
69
+ expect(control.closest('button')).toBeDisabled();
70
+ fireEvent.click(control);
71
+ expect(onEdit).not.toHaveBeenCalled();
72
+ });
73
+
74
+ it('uses row.name as the label when rowData is not passed and value is the row object', () => {
75
+ const formatter = nameToEditFormatter(jest.fn());
76
+ const row = { id: 3, name: 'From row', can_edit: false };
77
+
78
+ render(formatter(row));
79
+
80
+ expect(screen.getByText('From row')).toBeInTheDocument();
81
+ });
82
+ });
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { cellFormatter } from 'foremanReact/components/common/table';
3
+ import { ActionButton } from '../ActionButtons/ActionButton';
4
+
5
+ const actionCellFormatter = webhookActions => (value, extra) => {
6
+ const row = extra?.rowData ?? value;
7
+ const canEdit = row.canEdit ?? row.can_edit;
8
+ const canDelete = row.canDelete ?? row.can_delete;
9
+ const { id, name } = row;
10
+
11
+ const content = canEdit && (
12
+ <ActionButton
13
+ canDelete={canDelete}
14
+ id={id}
15
+ name={name}
16
+ webhookActions={webhookActions}
17
+ />
18
+ );
19
+
20
+ return extra?.rowData != null ? cellFormatter(content) : content;
21
+ };
22
+
23
+ export default actionCellFormatter;
@@ -13,4 +13,9 @@ EnabledCell.defaultProps = {
13
13
  condition: false,
14
14
  };
15
15
 
16
- export default EnabledCell;
16
+ const enabledCellFormatter = () => (value, extra) => {
17
+ const condition = extra?.rowData != null ? value : value?.enabled;
18
+ return <EnabledCell condition={Boolean(condition)} />;
19
+ };
20
+
21
+ export default enabledCellFormatter;
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { Button } from '@patternfly/react-core';
3
+
4
+ const nameToEditFormatter = onClick => (value, extra) => {
5
+ const row = extra?.rowData ?? value;
6
+ const canEdit = row.canEdit ?? row.can_edit;
7
+ const { id } = row;
8
+ const label = extra?.rowData != null ? value : row.name;
9
+
10
+ return canEdit ? (
11
+ <Button
12
+ ouiaId="name-edit-active-button"
13
+ variant="link"
14
+ isInline
15
+ component="span"
16
+ onClick={() => onClick(id)}
17
+ >
18
+ {label}
19
+ </Button>
20
+ ) : (
21
+ <Button
22
+ ouiaId="name-edit-disabled-button"
23
+ variant="link"
24
+ isInline
25
+ isDisabled
26
+ component="span"
27
+ >
28
+ {label}
29
+ </Button>
30
+ );
31
+ };
32
+
33
+ export default nameToEditFormatter;
@@ -42,6 +42,7 @@ const WebhookCreateModal = ({ onSuccess, onCancel, isOpen }) => {
42
42
  params: { ...values, controller: 'webhooks' },
43
43
  successToast: () => __('Webhook was successfully created.'),
44
44
  handleSuccess: () => {
45
+ setIsSubmitting(false);
45
46
  onSuccess();
46
47
  },
47
48
  handleError: () => setIsSubmitting(false),
@@ -14,6 +14,7 @@ const WebhookDeleteModal = ({ toDelete, onSuccess, modalState }) => {
14
14
 
15
15
  const [isSubmitting, setIsSubmitting] = useState(false);
16
16
  const dispatch = useDispatch();
17
+
17
18
  const handleSubmit = () => {
18
19
  setIsSubmitting(true);
19
20
  dispatch(
@@ -25,7 +26,10 @@ const WebhookDeleteModal = ({ toDelete, onSuccess, modalState }) => {
25
26
  errorToast: response =>
26
27
  // eslint-disable-next-line camelcase
27
28
  response?.response?.data?.error?.full_messages?.[0] || response,
28
- handleSuccess: onSuccess,
29
+ handleSuccess: () => {
30
+ setIsSubmitting(false);
31
+ onSuccess();
32
+ },
29
33
  handleError: () => setIsSubmitting(false),
30
34
  })
31
35
  );
@@ -69,6 +69,7 @@ const WebhookEditModal = ({ toEdit, onSuccess, modalState }) => {
69
69
  params: { ...values, controller: 'webhooks' },
70
70
  successToast: () => __('Webhook was successfully updated.'),
71
71
  handleSuccess: () => {
72
+ setIsSubmitting(false);
72
73
  onSuccess();
73
74
  },
74
75
  handleError: () => setIsSubmitting(false),
@@ -12,6 +12,7 @@ import {
12
12
  ModalVariant,
13
13
  } from '@patternfly/react-core';
14
14
 
15
+ import FieldConstructor from 'foremanReact/components/common/FieldConstructor/FieldConstructor';
15
16
  import { APIActions } from 'foremanReact/redux/API';
16
17
  import {
17
18
  WEBHOOK_TEST_MODAL_ID,
@@ -19,7 +20,6 @@ import {
19
20
  } from '../../constants';
20
21
 
21
22
  import './WebhookModal.scss';
22
- import FieldConstructor from '../../Components/WebhookForm/Components/FieldConstructor';
23
23
 
24
24
  const WebhookTestModal = ({ toTest, modalState }) => {
25
25
  const dispatch = useDispatch();
@@ -80,7 +80,6 @@ const WebhookTestModal = ({ toTest, modalState }) => {
80
80
  label={__('Payload')}
81
81
  labelHelp={__('Will be sent as is')}
82
82
  placeholder="{&#13;&#10;id: 1,&#13;&#10;name: test&#13;&#10;}"
83
- inputSizeClass="col-md-8"
84
83
  rows={8}
85
84
  value={value.payload}
86
85
  setValue={(key, val) => {
@@ -4,12 +4,25 @@ import { useSelector, useDispatch } from 'react-redux';
4
4
  import TableIndexPage from 'foremanReact/components/PF4/TableIndexPage/TableIndexPage';
5
5
  import { translate as __ } from 'foremanReact/common/I18n';
6
6
 
7
- import { WEBHOOKS_API_PATH, WEBHOOKS_API_REQUEST_KEY } from '../constants';
7
+ import {
8
+ WEBHOOKS_API_REQUEST_KEY,
9
+ WEBHOOKS_API_PLAIN_PATH,
10
+ } from '../constants';
8
11
 
9
- import { selectSearch } from '../WebhooksPageSelectors';
12
+ import {
13
+ selectPage,
14
+ selectPerPage,
15
+ selectSearch,
16
+ selectSubtotal,
17
+ } from '../WebhooksPageSelectors';
10
18
 
11
- import WebhooksTable from './Components/WebhooksTable';
12
19
  import WebhookCreateModal from './Components/WebhookCreateModal';
20
+ import WebhookDeleteModal from './Components/WebhookDeleteModal';
21
+ import WebhookEditModal from './Components/WebhookEditModal';
22
+ import WebhookTestModal from './Components/WebhookTestModal';
23
+ import nameToEditFormatter from '../Components/WebhookTable/Components/Formatters/nameToEditFormatter';
24
+ import enabledCellFormatter from '../Components/WebhookTable/Components/Formatters/enabledCellFormatter';
25
+ import actionCellFormatter from '../Components/WebhookTable/Components/Formatters/actionCellFormatter';
13
26
 
14
27
  import { reloadWithSearch, fetchAndPush } from '../WebhooksPageActions';
15
28
 
@@ -17,16 +30,100 @@ const WebhooksIndexPage = () => {
17
30
  const dispatch = useDispatch();
18
31
 
19
32
  const search = useSelector(selectSearch);
33
+ const page = useSelector(selectPage);
34
+ const perPage = useSelector(selectPerPage);
35
+ const itemCount = useSelector(selectSubtotal);
20
36
 
21
37
  const [toDelete, setToDelete] = useState({});
22
38
  const [toTest, setToTest] = useState({});
23
39
  const [toEdit, setToEdit] = useState(0);
24
40
  const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
41
+ const [isTestModalOpen, setIsTestModalOpen] = useState(false);
42
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
43
+ const [isEditModalOpen, setIsEditModalOpen] = useState(false);
25
44
 
26
45
  const openModal = () => {
27
46
  setIsCreateModalOpen(true);
28
47
  };
29
48
 
49
+ const onEditClick = rowData => {
50
+ setToEdit(rowData);
51
+ setIsEditModalOpen(true);
52
+ };
53
+
54
+ const onTestClick = rowData => {
55
+ setToTest(rowData);
56
+ setIsTestModalOpen(true);
57
+ };
58
+
59
+ const onDeleteClick = rowData => {
60
+ setToDelete(rowData);
61
+ setIsDeleteModalOpen(true);
62
+ };
63
+
64
+ const webhookActions = {
65
+ deleteWebhook: (id, name) => {
66
+ onDeleteClick({ id, name });
67
+ },
68
+ testWebhook: (id, name) => {
69
+ onTestClick({ id, name });
70
+ },
71
+ };
72
+
73
+ const modalsStates = {
74
+ testModal: {
75
+ isOpen: isTestModalOpen,
76
+ closeModal: () => {
77
+ setToTest({});
78
+ setIsTestModalOpen(false);
79
+ },
80
+ },
81
+ deleteModal: {
82
+ isOpen: isDeleteModalOpen,
83
+ closeModal: () => {
84
+ setToDelete({});
85
+ setIsDeleteModalOpen(false);
86
+ },
87
+ },
88
+ editModal: {
89
+ isOpen: isEditModalOpen,
90
+ closeModal: () => {
91
+ setToEdit(0);
92
+ setIsEditModalOpen(false);
93
+ },
94
+ },
95
+ };
96
+
97
+ const columns = {
98
+ name: {
99
+ title: __('Name'),
100
+ wrapper: nameToEditFormatter(onEditClick),
101
+ isSorted: true,
102
+ },
103
+ target_url: {
104
+ title: __('Target URL'),
105
+ isSorted: true,
106
+ },
107
+ enabled: {
108
+ title: __('Enabled'),
109
+ wrapper: enabledCellFormatter(),
110
+ isSorted: true,
111
+ },
112
+ actions: {
113
+ title: __('Actions'),
114
+ wrapper: actionCellFormatter(webhookActions),
115
+ },
116
+ };
117
+
118
+ const onDeleteSuccess = () => {
119
+ modalsStates.deleteModal.closeModal();
120
+ const currentPage = page;
121
+ const maxPage = Math.ceil((itemCount - 1) / perPage);
122
+ dispatch(
123
+ fetchAndPush({ page: maxPage < currentPage ? maxPage : currentPage })
124
+ );
125
+ };
126
+
30
127
  return (
31
128
  <>
32
129
  <WebhookCreateModal
@@ -37,24 +134,31 @@ const WebhooksIndexPage = () => {
37
134
  }}
38
135
  onCancel={() => setIsCreateModalOpen(false)}
39
136
  />
137
+ <WebhookDeleteModal
138
+ toDelete={toDelete}
139
+ onSuccess={onDeleteSuccess}
140
+ modalState={modalsStates.deleteModal}
141
+ />
142
+ <WebhookEditModal
143
+ toEdit={toEdit}
144
+ onSuccess={() => {
145
+ modalsStates.editModal.closeModal();
146
+ dispatch(reloadWithSearch(search));
147
+ }}
148
+ modalState={modalsStates.editModal}
149
+ />
150
+ <WebhookTestModal toTest={toTest} modalState={modalsStates.testModal} />
151
+
40
152
  <TableIndexPage
41
153
  header={__('Webhooks')}
42
154
  controller="webhooks"
43
- apiUrl={WEBHOOKS_API_PATH}
155
+ apiUrl={WEBHOOKS_API_PLAIN_PATH}
44
156
  apiOptions={{ key: WEBHOOKS_API_REQUEST_KEY }}
45
157
  customCreateAction={() => openModal}
46
- >
47
- <WebhooksTable
48
- fetchAndPush={params => dispatch(fetchAndPush(params))}
49
- toDelete={toDelete}
50
- setToDelete={setToDelete}
51
- toEdit={toEdit}
52
- setToEdit={setToEdit}
53
- toTest={toTest}
54
- setToTest={setToTest}
55
- reloadWithSearch={query => dispatch(reloadWithSearch(query))}
56
- />
57
- </TableIndexPage>
158
+ columns={columns}
159
+ id="webhooks-table"
160
+ key="webhooks-table"
161
+ />
58
162
  </>
59
163
  );
60
164
  };