Skip to content

Commit

Permalink
6. Add webhook endpoints to user developer settings screen
Browse files Browse the repository at this point in the history
Allowing creation and deleting via the user association.
It probably won't be much effort to allow editing and multiple records, but I cut it down to the minimum needed to start with.

I couldn't find a way to test a failure in the destroy method, but decided to keep the condition because I thought it was worth having.
  • Loading branch information
dacook committed Feb 7, 2023
1 parent c56b078 commit 9c6c9db
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 2 deletions.
49 changes: 49 additions & 0 deletions app/controllers/webhook_endpoints_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

class WebhookEndpointsController < ::BaseController
before_action :load_resource, only: :destroy

def create
webhook_endpoint = spree_current_user.webhook_endpoints.create(webhook_endpoint_params)

if webhook_endpoint.save
flash[:success] = t('.success')
else
flash[:error] = t('.error')
end
respond_to do |format|
format.html { redirect_to redirect_path }
end
end

def destroy
if @webhook_endpoint.destroy
flash[:success] = t('.success')
else
flash[:error] = t('.error')
end
respond_to do |format|
format.html { redirect_to redirect_path }
end
end

def load_resource
@webhook_endpoint = WebhookEndpoint.where(user_id: spree_current_user.id).find(params[:id])
end

def webhook_endpoint_params
params.require(:webhook_endpoint).permit(::PermittedAttributes::WebhookEndpoint.attributes)
end

def redirect_path
if request.referer.blank? || request.referer.include?(spree.account_path)
developer_settings_path
else
request.referer
end
end

def developer_settings_path
"#{spree.account_path}#/developer_settings"
end
end
1 change: 1 addition & 0 deletions app/models/spree/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class User < ApplicationRecord
has_many :webhook_endpoints, dependent: :destroy

accepts_nested_attributes_for :enterprise_roles, allow_destroy: true
accepts_nested_attributes_for :webhook_endpoints

accepts_nested_attributes_for :bill_address
accepts_nested_attributes_for :ship_address
Expand Down
5 changes: 4 additions & 1 deletion app/services/permitted_attributes/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ def call(extra_permitted_attributes = [])
private

def permitted_attributes
[:email, :password, :password_confirmation, :disabled]
[
:email, :password, :password_confirmation, :disabled,
{ webhook_endpoints_attributes: [:id, :url] },
]
end
end
end
11 changes: 11 additions & 0 deletions app/services/permitted_attributes/webhook_endpoint.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module PermittedAttributes
class WebhookEndpoint
def self.attributes
[
:url
]
end
end
end
1 change: 1 addition & 0 deletions app/views/spree/users/_developer_settings.html.haml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
%script{ type: "text/ng-template", id: "account/developer_settings.html" }
%h3= t('.title')
= render partial: 'api_keys'
= render partial: 'webhook_endpoints'
27 changes: 27 additions & 0 deletions app/views/spree/users/_webhook_endpoints.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
%section{ id: "webhook_endpoints" }
%hr
%h3= t('.title')
%p= t('.description')

%table{width: "100%"}
%thead
%tr
%th= t('.event_type.header')
%th= t('.url.header')
%tbody
- @user.webhook_endpoints.each do |webhook_endpoint|
%tr
%td= t('.event_types.order_cycle_opened') # For now, we only support one type.
%td= webhook_endpoint.url
%td.actions
= button_to I18n.t(:delete), account_webhook_endpoint_path(webhook_endpoint), method: :delete,
data: { confirm: I18n.t(:are_you_sure)} if webhook_endpoint.persisted?

-# Create new
- if @user.webhook_endpoints.empty?
%tr
%td= t('.event_types.order_cycle_opened') # For now, we only support one type.
%td
= form_for(@user.webhook_endpoints.build, url: account_webhook_endpoints_path) do |f|
= f.url_field :url, placeholder: t('.url.create_placeholder'), required: true
= f.submit t(:create), :class => 'button primary'
18 changes: 18 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3545,6 +3545,14 @@ See the %{link} to find out more about %{sitename}'s features and to start using
previous: "Previous"
last: "Last"

webhook_endpoints:
create:
success: Webhook endpoint successfully created
error: Webhook endpoint failed to create
destroy:
success: Webhook endpoint successfully deleted
error: Webhook endpoint failed to delete

spree:
order_updated: "Order Updated"
add_country: "Add country"
Expand Down Expand Up @@ -4336,6 +4344,16 @@ See the %{link} to find out more about %{sitename}'s features and to start using
api_keys:
regenerate_key: "Regenerate Key"
title: API key
webhook_endpoints:
title: Webhook Endpoints
description: Events in the system may trigger webhooks to external systems.
event_types:
order_cycle_opened: Order Cycle Opened
event_type:
header: Event type
url:
header: Endpoint URL
create_placeholder: Enter the URL of the remote webhook endpoint
developer_settings:
title: Developer Settings
form:
Expand Down
5 changes: 4 additions & 1 deletion config/routes/spree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@
put '/password/change' => 'user_passwords#update', :as => :update_password
end

resource :account, :controller => 'users'
resource :account, :controller => 'users' do
resources :webhook_endpoints, only: [:create, :destroy], controller: '/webhook_endpoints'
end

match '/admin/orders/bulk_cancel' => 'admin/orders#bulk_cancel', :as => "admin_bulk_cancel", via: :post

match '/admin/orders/bulk_management' => 'admin/orders#bulk_management', :as => "admin_bulk_order_management", via: :get
Expand Down
67 changes: 67 additions & 0 deletions spec/controllers/webhook_endpoints_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: false

require 'spec_helper'
require 'open_food_network/order_cycle_permissions'

describe WebhookEndpointsController, type: :controller do
let(:user) { create(:admin_user) }

before { allow(controller).to receive(:spree_current_user) { user } }

describe "#create" do
it "creates a webhook_endpoint" do
expect {
spree_post :create, { url: "https://url" }
}.to change {
user.webhook_endpoints.count
}.by(1)

expect(flash[:success]).to be_present
expect(flash[:error]).to be_blank
expect(user.webhook_endpoints.first.url).to eq "https://url"
end

it "shows error if parameters not specified" do
expect {
spree_post :create, { url: "" }
}.to_not change {
user.webhook_endpoints.count
}

expect(flash[:success]).to be_blank
expect(flash[:error]).to be_present
end

it "redirects back to referrer" do
spree_post :create, { url: "https://url" }

expect(response).to redirect_to "/account#/developer_settings"
end
end

describe "#destroy" do
let!(:webhook_endpoint) { user.webhook_endpoints.create(url: "https://url") }

it "destroys a webhook_endpoint" do
webhook_endpoint2 = user.webhook_endpoints.create!(url: "https://url2")

expect {
spree_delete :destroy, { id: webhook_endpoint.id }
}.to change {
user.webhook_endpoints.count
}.by(-1)

expect(flash[:success]).to be_present
expect(flash[:error]).to be_blank

expect{ webhook_endpoint.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect(webhook_endpoint2.reload).to be_present
end

it "redirects back to developer settings tab" do
spree_delete :destroy, id: webhook_endpoint.id

expect(response).to redirect_to "/account#/developer_settings"
end
end
end
16 changes: 16 additions & 0 deletions spec/system/consumer/account/developer_settings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@
expect(page).to have_content "Key generated"
expect(page).to have_input "api_key", with: user.reload.spree_api_key
end

describe "Webhook Endpoints" do
it "creates a new webhook endpoint and deletes it" do
within "#webhook_endpoints" do
fill_in "webhook_endpoint_url", with: "https://url"

click_button I18n.t(:create)
expect(page.document).to have_content I18n.t('webhook_endpoints.create.success')
expect(page).to have_content "https://url"

click_button I18n.t(:delete)
expect(page.document).to have_content I18n.t('webhook_endpoints.destroy.success')
expect(page).to_not have_content "https://url"
end
end
end
end
end

Expand Down

0 comments on commit 9c6c9db

Please sign in to comment.