Skip to content

Commit

Permalink
Merge 682e735 into 06b4c62
Browse files Browse the repository at this point in the history
  • Loading branch information
ofedoren committed Mar 31, 2023
2 parents 06b4c62 + 682e735 commit 016909b
Show file tree
Hide file tree
Showing 16 changed files with 292 additions and 11 deletions.
25 changes: 24 additions & 1 deletion app/controllers/api/v2/webhooks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class WebhooksController < V2::BaseController
include Api::Version2
include ForemanWebhooks::Controller::Parameters::Webhook

before_action :find_resource, only: %i[show destroy]
before_action :find_resource, only: %i[show destroy test]

api :GET, '/webhooks/', N_('List Webhooks')
param_group :search_and_pagination, ::Api::V2::BaseController
Expand Down Expand Up @@ -70,6 +70,29 @@ def destroy
def events
render json: Webhook.available_events.sort.map { |e| e.delete_suffix(Webhook::EVENT_POSTFIX) }.to_json
end

api :POST, '/webhooks/:id/test', N_('Test a Webhook')
param :id, :identifier, required: true
param :payload, String, N_('Test payload will be sent as is. Cant be a JSON object')
def test
result = @webhook.test(payload: params[:payload])
if result[:status] == :success
respond_with @webhook, responder: ApiResponder, status: :ok
else
render_error('custom_error', status: :unprocessable_entity, locals: { message: result[:message] })
end
end

private

def action_permission
case params[:action]
when 'test'
'edit'
else
super
end
end
end
end
end
16 changes: 16 additions & 0 deletions app/models/webhook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ def deliver(event_name:, payload:)
)
end

def test(payload: nil)
ForemanWebhooks::WebhookService.new(
webhook: self,
headers: rendered_headers(event, {}),
url: rendered_targed_url(event, {}),
event_name: event,
payload: test_payload(payload || '')
).execute
end

def ca_certs_store
store = OpenSSL::X509::Store.new
if ssl_ca_certs.blank?
Expand Down Expand Up @@ -132,4 +142,10 @@ def rendered_targed_url(event_name, payload)
source = Foreman::Renderer::Source::String.new(name: 'HTTP target URL template', content: target_url)
render_source(source, event_name, payload)
end

def test_payload(payload)
return payload if payload.is_a?(String)

payload.to_json
end
end
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
collection do
get :events
end
member do
post :test
end
end
resources :webhook_templates, except: %i[new edit] do
member do
Expand Down
2 changes: 1 addition & 1 deletion lib/foreman_webhooks/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Engine < ::Rails::Engine
permission :create_webhooks, { webhooks: %i[new create],
'api/v2/webhooks': [:create] }, resource_type: 'Webhook'
permission :edit_webhooks, { webhooks: %i[edit update],
'api/v2/webhooks': [:update] }, resource_type: 'Webhook'
'api/v2/webhooks': %i[update test] }, resource_type: 'Webhook'
permission :destroy_webhooks, { webhooks: [:destroy],
'api/v2/webhooks': [:destroy] }, resource_type: 'Webhook'
permission :view_webhook_templates, { webhook_templates: %i[index show auto_complete_search preview export],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import PropTypes from 'prop-types';

import { sprintf, translate as __ } from 'foremanReact/common/I18n';
import ForemanModal from 'foremanReact/components/ForemanModal';
import ForemanForm from 'foremanReact/components/common/forms/ForemanForm';
import { foremanUrl } from 'foremanReact/common/helpers';
import { submitForm } from 'foremanReact/redux/actions/common/forms';
import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks';

import ForemanFormikField from '../../../Webhooks/Components/WebhookForm/Components/ForemanFormikField';

import {
WEBHOOK_TEST_MODAL_ID,
WEBHOOKS_API_PLAIN_PATH,
} from '../../constants';

import './WebhookModal.scss';

const WebhookTestModal = ({ toTest }) => {
const dispatch = useDispatch();

const { id, name } = toTest;
const { setModalClosed: setTestModalClosed } = useForemanModal({
id: WEBHOOK_TEST_MODAL_ID,
});
const initialTestValues = {
payload: '',
};
const errorToast = error =>
sprintf(
__('Webhook test failed: %s'),
error?.response?.data?.error?.message
);

const handleSubmit = (values, actions) => {
dispatch(
submitForm({
url: foremanUrl(`${WEBHOOKS_API_PLAIN_PATH}/${id}/test`),
values: { ...values, controller: 'webhooks' },
item: 'WebhookTest',
message: sprintf(__('Webhook %s test was successful'), name),
method: 'post',
successCallback: () => setTestModalClosed(),
actions,
errorToast,
handleError: () => actions.setSubmitting(false),
})
);
};

return (
<ForemanModal
id={WEBHOOK_TEST_MODAL_ID}
title={`${__('Test')} ${name}`}
backdrop="static"
enforceFocus
className="webhooks-modal"
>
<p>
{sprintf(
__(
'You are about to test %s webhook.' +
'\n' +
'Please, note that this will not contain actual information or render the attached template.' +
'\n' +
'You can specify below a custom payload to test the webhook with.'
),
name
)}
</p>
<ForemanForm
onSubmit={handleSubmit}
initialValues={initialTestValues}
onCancel={setTestModalClosed}
>
<ForemanFormikField
name="payload"
type="textarea"
label={__('Payload')}
labelHelp={__('Will be sent as is')}
placeholder="{&#13;&#10;id: 1,&#13;&#10;name: test&#13;&#10;}"
inputSizeClass="col-md-8"
rows={8}
/>
</ForemanForm>
</ForemanModal>
);
};

WebhookTestModal.propTypes = {
toTest: PropTypes.object,
};

WebhookTestModal.defaultProps = {
toTest: {},
};

export default WebhookTestModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import PropTypes from 'prop-types';
import { translate as __ } from 'foremanReact/common/I18n';
import { ActionButtons } from 'foremanReact/components/common/ActionButtons/ActionButtons';
import { noop } from 'foremanReact/common/helpers';

export const ActionButton = ({
id,
name,
enabled,
canDelete,
webhookActions,
}) => {
const buttons = [];
if (canDelete) {
buttons.push({
title: __('Delete'),
action: {
onClick: () => webhookActions.deleteWebhook(id, name),
id: `webhook-delete-button-${id}`,
},
});
}
buttons.push({
title: __('Test webhook'),
action: {
disabled: !enabled,
onClick: () => {
enabled ? webhookActions.testWebhook(id, name) : noop();
},
id: `webhook-test-button-${id}`,
},
});

return <ActionButtons buttons={buttons} />;
};

ActionButton.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
name: PropTypes.string.isRequired,
canDelete: PropTypes.bool,
webhookActions: PropTypes.shape({
deleteWebhook: PropTypes.func,
testWebhook: PropTypes.func,
}).isRequired,
enabled: PropTypes.bool,
};

ActionButton.defaultProps = {
canDelete: false,
enabled: false,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { cellFormatter } from 'foremanReact/components/common/table';
import { ActionButton } from '../ActionButtons/ActionButton';

const actionCellFormatter = webhookActions => (
_,
{ rowData: { id, name, enabled, canEdit, canDelete } }
) =>
cellFormatter(
canEdit && (
<ActionButton
canDelete={canDelete}
id={id}
name={name}
enabled={enabled}
webhookActions={webhookActions}
/>
)
);

export default actionCellFormatter;
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as enabledCellFormatter } from './enabledCellFormatter';
export { default as nameToEditFormatter } from './nameToEditFormatter';
export { default as actionCellFormatter } from './actionCellFormatter';
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanMod

import WebhookDeleteModal from '../WebhookDeleteModal';
import WebhookEditModal from '../WebhookEditModal';
import WebhookTestModal from '../WebhookTestModal';
import EmptyWebhooksTable from './Components/EmptyWebhooksTable';

import createWebhooksTableSchema from './WebhooksTableSchema';
Expand All @@ -32,10 +33,11 @@ import {
const WebhooksTable = ({
fetchAndPush,
toDelete,
onDeleteClick,
toTest,
toEdit,
onEditClick,
reloadWithSearch,
webhookActions,
}) => {
const webhooks = useSelector(selectWebhooks);
const page = useSelector(selectPage);
Expand Down Expand Up @@ -75,13 +77,14 @@ const WebhooksTable = ({
}}
onCancel={setEditModalClosed}
/>
<WebhookTestModal toTest={toTest} />
<Table
key="webhooks-table"
columns={createWebhooksTableSchema(
fetchAndPush,
sort.by,
sort.order,
onDeleteClick,
webhookActions,
onEditClick
)}
rows={webhooks}
Expand All @@ -94,11 +97,12 @@ const WebhooksTable = ({

WebhooksTable.propTypes = {
fetchAndPush: PropTypes.func.isRequired,
onDeleteClick: PropTypes.func.isRequired,
onEditClick: PropTypes.func.isRequired,
toDelete: PropTypes.object.isRequired,
toTest: PropTypes.object.isRequired,
toEdit: PropTypes.number.isRequired,
reloadWithSearch: PropTypes.func.isRequired,
webhookActions: PropTypes.object.isRequired,
};

export default WebhooksTable;
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import {
column,
sortableColumn,
headerFormatterWithProps,
deleteActionCellFormatter,
cellFormatter,
} from 'foremanReact/components/common/table';

import {
enabledCellFormatter,
nameToEditFormatter,
actionCellFormatter,
} from './Components/Formatters';

const sortControllerFactory = (apiCall, sortBy, sortOrder) => ({
Expand All @@ -24,7 +23,7 @@ const createWebhooksTableSchema = (
apiCall,
by,
order,
onDeleteClick,
webhookActions,
onEditClick
) => {
const sortController = sortControllerFactory(apiCall, by, order);
Expand All @@ -41,7 +40,7 @@ const createWebhooksTableSchema = (
'actions',
__('Actions'),
[headerFormatterWithProps],
[deleteActionCellFormatter(onDeleteClick), cellFormatter]
[actionCellFormatter(webhookActions)]
),
];
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const props = {
onDeleteClick: jest.fn(),
setToDelete: jest.fn(),
setToEdit: jest.fn(),
setToTest: jest.fn(),
reloadWithSearch: jest.fn(),
itemCount: 0,
canCreate: true,
Expand All @@ -20,6 +21,7 @@ const props = {
perPage: 20,
},
toDelete: {},
toTest: {},
toEdit: 0,
};

Expand Down

0 comments on commit 016909b

Please sign in to comment.