mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-04 22:16:08 +00:00
Merge pull request #12676 from dacook/add-affiliate_sales_data-connected_app-12550
Add "Affiliate Sales Data" connected app option
This commit is contained in:
@@ -5,12 +5,12 @@ module Admin
|
||||
def create
|
||||
authorize! :admin, enterprise
|
||||
|
||||
app = ConnectedApp.create!(enterprise_id: enterprise.id)
|
||||
attributes = {}
|
||||
attributes[:type] = connected_app_params[:type] if connected_app_params[:type]
|
||||
|
||||
ConnectAppJob.perform_later(
|
||||
app, spree_current_user.spree_api_key,
|
||||
channel: SessionChannel.for_request(request),
|
||||
)
|
||||
app = ConnectedApp.create!(enterprise_id: enterprise.id, **attributes)
|
||||
app.connect(api_key: spree_current_user.spree_api_key,
|
||||
channel: SessionChannel.for_request(request))
|
||||
|
||||
render_panel
|
||||
end
|
||||
@@ -18,15 +18,9 @@ module Admin
|
||||
def destroy
|
||||
authorize! :admin, enterprise
|
||||
|
||||
app = enterprise.connected_apps.first
|
||||
app = enterprise.connected_apps.find(params.require(:id))
|
||||
app.destroy
|
||||
|
||||
WebhookDeliveryJob.perform_later(
|
||||
app.data["destroy"],
|
||||
"disconnect-app",
|
||||
nil
|
||||
)
|
||||
|
||||
render_panel
|
||||
end
|
||||
|
||||
@@ -39,5 +33,9 @@ module Admin
|
||||
def render_panel
|
||||
redirect_to "#{edit_admin_enterprise_path(enterprise)}#/connected_apps_panel"
|
||||
end
|
||||
|
||||
def connected_app_params
|
||||
params.permit(:type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,8 +19,8 @@ class ConnectAppJob < ApplicationJob
|
||||
|
||||
selector = "#connected-app-discover-regen.enterprise_#{enterprise.id}"
|
||||
html = ApplicationController.render(
|
||||
partial: "admin/enterprises/form/connected_apps",
|
||||
locals: { enterprise: },
|
||||
partial: "admin/enterprises/form/connected_apps/discover_regen",
|
||||
locals: { enterprise:, connected_app: enterprise.connected_apps.discover_regen.first },
|
||||
)
|
||||
|
||||
cable_ready[channel].morph(selector:, html:).broadcast
|
||||
|
||||
@@ -5,7 +5,31 @@
|
||||
# Here we store keys and links to access the app.
|
||||
class ConnectedApp < ApplicationRecord
|
||||
belongs_to :enterprise
|
||||
after_destroy :disconnect
|
||||
|
||||
scope :discover_regen, -> { where(type: "ConnectedApp") }
|
||||
scope :affiliate_sales_data, -> { where(type: "ConnectedApps::AffiliateSalesData") }
|
||||
|
||||
scope :connecting, -> { where(data: nil) }
|
||||
scope :ready, -> { where.not(data: nil) }
|
||||
|
||||
def connecting?
|
||||
data.nil?
|
||||
end
|
||||
|
||||
def ready?
|
||||
!connecting?
|
||||
end
|
||||
|
||||
def connect(api_key:, channel:)
|
||||
ConnectAppJob.perform_later(self, api_key, channel:)
|
||||
end
|
||||
|
||||
def disconnect
|
||||
WebhookDeliveryJob.perform_later(
|
||||
data["destroy"],
|
||||
"disconnect-app",
|
||||
nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
13
app/models/connected_apps/affiliate_sales_data.rb
Normal file
13
app/models/connected_apps/affiliate_sales_data.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# An enterprise can opt-in for their data to be included in the affiliate_sales_data endpoint
|
||||
#
|
||||
module ConnectedApps
|
||||
class AffiliateSalesData < ConnectedApp
|
||||
def connect(_opts)
|
||||
update! data: true # not-nil value indicates it is ready
|
||||
end
|
||||
|
||||
def disconnect; end
|
||||
end
|
||||
end
|
||||
@@ -156,6 +156,12 @@ module Spree
|
||||
self.disabled_at = value == '1' ? Time.zone.now : nil
|
||||
end
|
||||
|
||||
def affiliate_enterprises
|
||||
return [] unless Flipper.enabled?(:affiliate_sales_data, self)
|
||||
|
||||
Enterprise.joins(:connected_apps).merge(ConnectedApps::AffiliateSalesData.ready)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def password_required?
|
||||
|
||||
@@ -1,30 +1,4 @@
|
||||
%div{ id: "connected-app-discover-regen", class: "enterprise_#{enterprise.id}" }
|
||||
.connected-app__head
|
||||
%div
|
||||
%h3= t ".title"
|
||||
%p= t ".tagline"
|
||||
%div
|
||||
- if enterprise.connected_apps.empty?
|
||||
= button_to t(".enable"), admin_enterprise_connected_apps_path(enterprise.id), method: :post, disabled: !managed_by_user?(enterprise)
|
||||
-# This is only seen by super-admins:
|
||||
%em= t(".need_to_be_manager") unless managed_by_user?(enterprise)
|
||||
- elsif enterprise.connected_apps.connecting.present?
|
||||
%button{ disabled: true }
|
||||
%i.spinner.fa.fa-spin.fa-circle-o-notch
|
||||
|
||||
= t ".loading"
|
||||
- else
|
||||
= button_to t(".disable"), admin_enterprise_connected_app_path(0, enterprise_id: enterprise.id), method: :delete
|
||||
|
||||
.connected-app__connection
|
||||
- if enterprise.connected_apps.ready.present?
|
||||
.connected-app__note
|
||||
- link = enterprise.connected_apps[0].data["link"]
|
||||
%p= t ".note"
|
||||
%div
|
||||
%a{ href: link, target: "_blank", class: "button secondary" }
|
||||
= t ".link_label"
|
||||
|
||||
%hr
|
||||
.connected-app__description
|
||||
= t ".description_html"
|
||||
= render partial: "/admin/enterprises/form/connected_apps/discover_regen",
|
||||
locals: { enterprise:, connected_app: enterprise.connected_apps.discover_regen.first }
|
||||
= render partial: "/admin/enterprises/form/connected_apps/affiliate_sales_data",
|
||||
locals: { enterprise:, connected_app: enterprise.connected_apps.affiliate_sales_data.first }
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
%section.connected_app
|
||||
.connected-app__head
|
||||
%div
|
||||
%h3= t ".title"
|
||||
%p= t ".tagline"
|
||||
%div
|
||||
- if connected_app.nil?
|
||||
= button_to t(".enable"), admin_enterprise_connected_apps_path(enterprise.id, type: "ConnectedApps::AffiliateSalesData"), method: :post, disabled: !managed_by_user?(enterprise)
|
||||
-# This is only seen by super-admins:
|
||||
%em= t(".need_to_be_manager") unless managed_by_user?(enterprise)
|
||||
- else
|
||||
= button_to t(".disable"), admin_enterprise_connected_app_path(connected_app.id, enterprise_id: enterprise.id), method: :delete
|
||||
%hr
|
||||
.connected-app__description
|
||||
= t ".description_html"
|
||||
@@ -0,0 +1,30 @@
|
||||
%section.connected_app{ id: "connected-app-discover-regen", class: "enterprise_#{enterprise.id}" }
|
||||
.connected-app__head
|
||||
%div
|
||||
%h3= t ".title"
|
||||
%p= t ".tagline"
|
||||
%div
|
||||
- if connected_app.nil?
|
||||
= button_to t(".enable"), admin_enterprise_connected_apps_path(enterprise.id), method: :post, disabled: !managed_by_user?(enterprise)
|
||||
-# This is only seen by super-admins:
|
||||
%em= t(".need_to_be_manager") unless managed_by_user?(enterprise)
|
||||
- elsif connected_app&.connecting?
|
||||
%button{ disabled: true }
|
||||
%i.spinner.fa.fa-spin.fa-circle-o-notch
|
||||
|
||||
= t ".loading"
|
||||
- else
|
||||
= button_to t(".disable"), admin_enterprise_connected_app_path(connected_app.id, enterprise_id: enterprise.id), method: :delete
|
||||
|
||||
.connected-app__connection
|
||||
- if connected_app&.ready?
|
||||
.connected-app__note
|
||||
- link = connected_app.data["link"]
|
||||
%p= t ".note"
|
||||
%div
|
||||
%a{ href: link, target: "_blank", class: "button secondary" }
|
||||
= t ".link_label"
|
||||
|
||||
%hr
|
||||
.connected-app__description
|
||||
= t ".description_html"
|
||||
@@ -2,10 +2,18 @@
|
||||
max-width: 615px;
|
||||
}
|
||||
|
||||
.connected_app {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
border-top: 1px solid $color-border;
|
||||
}
|
||||
}
|
||||
|
||||
.connected-app__head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1em;
|
||||
margin: 1em 0;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 1em;
|
||||
|
||||
@@ -1358,28 +1358,45 @@ en:
|
||||
custom_tab_content: "Content for custom tab"
|
||||
connected_apps:
|
||||
legend: "Connected apps"
|
||||
title: "Discover Regenerative"
|
||||
tagline: "Allow Discover Regenerative to publish your enterprise information."
|
||||
enable: "Allow data sharing"
|
||||
disable: "Stop sharing"
|
||||
loading: "Loading"
|
||||
need_to_be_manager: "Only managers can connect apps."
|
||||
note: |
|
||||
Your Open Food Network account is connected to Discover Regenerative.
|
||||
Add or update information on your Discover Regenerative listing here.
|
||||
link_label: "Manage listing"
|
||||
description_html: |
|
||||
<p>
|
||||
Eligible producers can showcase their regenerative credentials,
|
||||
farming practices and more through a profile listing.
|
||||
Simplifying how buyers can find regenerative produce and connect
|
||||
with producers of interest.
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://about.openfoodnetwork.org.au/regen-produce-portal/"
|
||||
target="_blank"><b>Learn more about Discover Regenerative</b>
|
||||
<i class="icon-external-link"></i></a>
|
||||
</p>
|
||||
affiliate_sales_data:
|
||||
title: "INRAE / UFC QUE CHOISIR Research"
|
||||
tagline: "Allow this research project to access your orders data anonymously"
|
||||
enable: "Allow data sharing"
|
||||
disable: "Stop sharing"
|
||||
loading: "Loading"
|
||||
need_to_be_manager: "Only managers can connect apps."
|
||||
description_html: |
|
||||
<p>
|
||||
INRAE and UFC QUE CHOISIR are teaming up to study food prices in short food systems and compare them with prices in the supermarket, for a given set of products. The data that is used by INRAE is mixed with data coming from other short food chain platforms in France. No individual product prices will be publicly disclosed through this project.
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://apropos.coopcircuits.fr/"
|
||||
target="_blank"><b>Learn more about this research project</b>
|
||||
<i class="icon-external-link"></i></a>
|
||||
</p>
|
||||
discover_regen:
|
||||
title: "Discover Regenerative"
|
||||
tagline: "Allow Discover Regenerative to publish your enterprise information."
|
||||
enable: "Allow data sharing"
|
||||
disable: "Stop sharing"
|
||||
loading: "Loading"
|
||||
need_to_be_manager: "Only managers can connect apps."
|
||||
note: |
|
||||
Your Open Food Network account is connected to Discover Regenerative.
|
||||
Add or update information on your Discover Regenerative listing here.
|
||||
link_label: "Manage listing"
|
||||
description_html: |
|
||||
<p>
|
||||
Eligible producers can showcase their regenerative credentials,
|
||||
farming practices and more through a profile listing.
|
||||
Simplifying how buyers can find regenerative produce and connect
|
||||
with producers of interest.
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://about.openfoodnetwork.org.au/regen-produce-portal/"
|
||||
target="_blank"><b>Learn more about Discover Regenerative</b>
|
||||
<i class="icon-external-link"></i></a>
|
||||
</p>
|
||||
actions:
|
||||
edit_profile: Settings
|
||||
properties: Properties
|
||||
|
||||
5
db/migrate/20240715072649_add_type_to_connected_apps.rb
Normal file
5
db/migrate/20240715072649_add_type_to_connected_apps.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddTypeToConnectedApps < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :connected_apps, :type, :string, default: "ConnectedApp", null: false
|
||||
end
|
||||
end
|
||||
@@ -68,6 +68,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_07_18_150852) do
|
||||
t.json "data"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "type", default: "ConnectedApp", null: false
|
||||
t.index ["enterprise_id"], name: "index_connected_apps_on_enterprise_id"
|
||||
end
|
||||
|
||||
|
||||
@@ -12,6 +12,18 @@ namespace :ofn do
|
||||
enterprise.destroy
|
||||
end
|
||||
|
||||
namespace :enterprises do
|
||||
desc "Activate connected app type for ALL enterprises"
|
||||
task :activate_connected_app_type, [:type] => :environment do |_task, args|
|
||||
Enterprise.find_each do |enterprise|
|
||||
next if enterprise.connected_apps.public_send(args.type.underscore).exists?
|
||||
|
||||
"ConnectedApps::#{args.type.camelize}".constantize.new(enterprise:).connect({})
|
||||
puts "Enterprise #{enterprise.id} connected."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
namespace :dev do
|
||||
desc 'export enterprises to CSV'
|
||||
task export_enterprises: :environment do
|
||||
|
||||
@@ -20,4 +20,26 @@ RSpec.describe 'enterprises.rake' do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ':enterprises' do
|
||||
describe ':activate_connected_app_type' do
|
||||
it 'updates only disconnected enterprises' do
|
||||
# enterprise with affiliate sales data
|
||||
enterprise_asd = create(:enterprise)
|
||||
enterprise_asd.connected_apps.create type: 'ConnectedApps::AffiliateSalesData'
|
||||
# enterprise with different type
|
||||
enterprise_diff = create(:enterprise)
|
||||
enterprise_diff.connected_apps.create
|
||||
|
||||
expect {
|
||||
Rake.application.invoke_task(
|
||||
"ofn:enterprises:activate_connected_app_type[affiliate_sales_data]"
|
||||
)
|
||||
}.to change { ConnectedApps::AffiliateSalesData.count }.by(1)
|
||||
|
||||
expect(enterprise_asd.connected_apps.affiliate_sales_data.count).to eq 1
|
||||
expect(enterprise_diff.connected_apps.affiliate_sales_data.count).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -268,4 +268,34 @@ RSpec.describe Spree::User do
|
||||
expect(user.disabled).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe "#affiliate_enterprises" do
|
||||
let(:user) { create(:user) }
|
||||
let(:affiliate_enterprise) { create(:enterprise) }
|
||||
let(:other_connected_enterprise) { create(:enterprise) }
|
||||
let(:other_enterprise) { create(:enterprise) }
|
||||
subject{ user.affiliate_enterprises }
|
||||
|
||||
before do
|
||||
ConnectedApps::AffiliateSalesData.create(enterprise: affiliate_enterprise, data: true)
|
||||
ConnectedApp.create(enterprise: other_connected_enterprise, data: true)
|
||||
end
|
||||
|
||||
context "user does not have feature" do
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context "user has feature affiliate_sales_data" do
|
||||
before do
|
||||
Flipper.enable_actor(:affiliate_sales_data, user)
|
||||
user.reload
|
||||
end
|
||||
|
||||
it "includes only affiliate enterprises" do
|
||||
is_expected.to include affiliate_enterprise
|
||||
is_expected.not_to include other_connected_enterprise
|
||||
is_expected.not_to include other_enterprise
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,36 +28,91 @@ RSpec.describe "Connected Apps", feature: :connected_apps, vcr: true do
|
||||
expect(page).to have_content "CONNECTED APPS"
|
||||
end
|
||||
|
||||
it "can be enabled and disabled" do
|
||||
visit edit_admin_enterprise_path(enterprise)
|
||||
describe "Discover Regenerative" do
|
||||
let(:section_heading) { self.class.description }
|
||||
|
||||
scroll_to :bottom
|
||||
click_link "Connected apps"
|
||||
expect(page).to have_content "Discover Regenerative"
|
||||
it "can be enabled and disabled" do
|
||||
visit edit_admin_enterprise_path(enterprise)
|
||||
|
||||
click_button "Allow data sharing"
|
||||
expect(page).not_to have_button "Allow data sharing"
|
||||
expect(page).to have_button "Loading", disabled: true
|
||||
scroll_to :bottom
|
||||
click_link "Connected apps"
|
||||
|
||||
perform_enqueued_jobs(only: ConnectAppJob)
|
||||
expect(page).not_to have_button "Loading", disabled: true
|
||||
expect(page).to have_content "account is connected"
|
||||
expect(page).to have_link "Manage listing"
|
||||
within section_containing_heading do
|
||||
click_button "Allow data sharing"
|
||||
end
|
||||
|
||||
click_button "Stop sharing"
|
||||
expect(page).to have_button "Allow data sharing"
|
||||
expect(page).not_to have_button "Stop sharing"
|
||||
expect(page).not_to have_content "account is connected"
|
||||
expect(page).not_to have_link "Manage listing"
|
||||
# (page is reloaded so we need to evaluate within block again)
|
||||
within section_containing_heading do
|
||||
expect(page).not_to have_button "Allow data sharing"
|
||||
expect(page).to have_button "Loading", disabled: true
|
||||
|
||||
perform_enqueued_jobs(only: ConnectAppJob)
|
||||
|
||||
expect(page).not_to have_button "Loading", disabled: true
|
||||
expect(page).to have_content "account is connected"
|
||||
expect(page).to have_link "Manage listing"
|
||||
|
||||
click_button "Stop sharing"
|
||||
end
|
||||
|
||||
within section_containing_heading do
|
||||
expect(page).to have_button "Allow data sharing"
|
||||
expect(page).not_to have_button "Stop sharing"
|
||||
expect(page).not_to have_content "account is connected"
|
||||
expect(page).not_to have_link "Manage listing"
|
||||
end
|
||||
end
|
||||
|
||||
it "can't be enabled by non-manager" do
|
||||
login_as create(:admin_user)
|
||||
|
||||
visit "#{edit_admin_enterprise_path(enterprise)}#/connected_apps_panel"
|
||||
|
||||
within section_containing_heading do
|
||||
expect(page).to have_button("Allow data sharing", disabled: true)
|
||||
expect(page).to have_content "Only managers can connect apps."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "can't be enabled by non-manager" do
|
||||
login_as create(:admin_user)
|
||||
describe "Affiliate Sales Data" do
|
||||
let(:section_heading) { "INRAE / UFC QUE CHOISIR Research" }
|
||||
|
||||
visit "#{edit_admin_enterprise_path(enterprise)}#/connected_apps_panel"
|
||||
expect(page).to have_content "Discover Regenerative"
|
||||
it "can be enabled and disabled" do
|
||||
visit edit_admin_enterprise_path(enterprise)
|
||||
|
||||
expect(page).to have_button("Allow data sharing", disabled: true)
|
||||
expect(page).to have_content "Only managers can connect apps."
|
||||
scroll_to :bottom
|
||||
click_link "Connected apps"
|
||||
|
||||
within section_containing_heading do
|
||||
click_button "Allow data sharing"
|
||||
end
|
||||
|
||||
# (page is reloaded so we need to evaluate within block again)
|
||||
within section_containing_heading do
|
||||
expect(page).not_to have_button "Allow data sharing"
|
||||
click_button "Stop sharing"
|
||||
end
|
||||
|
||||
within section_containing_heading do
|
||||
expect(page).to have_button "Allow data sharing"
|
||||
expect(page).not_to have_button "Stop sharing"
|
||||
end
|
||||
end
|
||||
|
||||
it "can't be enabled by non-manager" do
|
||||
login_as create(:admin_user)
|
||||
|
||||
visit "#{edit_admin_enterprise_path(enterprise)}#/connected_apps_panel"
|
||||
|
||||
within section_containing_heading do
|
||||
expect(page).to have_button("Allow data sharing", disabled: true)
|
||||
expect(page).to have_content "Only managers can connect apps."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def section_containing_heading(heading = section_heading)
|
||||
page.find("h3", text: heading).ancestor("section")
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user