diff --git a/app/components/admin/cookies/vendors/edit_component.html.erb b/app/components/admin/cookies/vendors/edit_component.html.erb
new file mode 100644
index 000000000..7f55fac75
--- /dev/null
+++ b/app/components/admin/cookies/vendors/edit_component.html.erb
@@ -0,0 +1,5 @@
+<%= back_link_to admin_settings_path(anchor: "tab-cookies-consent") %>
+
+<%= header %>
+
+<%= render Admin::Cookies::Vendors::FormComponent.new(vendor) %>
diff --git a/app/components/admin/cookies/vendors/edit_component.rb b/app/components/admin/cookies/vendors/edit_component.rb
new file mode 100644
index 000000000..a791c6505
--- /dev/null
+++ b/app/components/admin/cookies/vendors/edit_component.rb
@@ -0,0 +1,15 @@
+class Admin::Cookies::Vendors::EditComponent < ApplicationComponent
+ include Header
+
+ attr_reader :vendor
+
+ def initialize(vendor)
+ @vendor = vendor
+ end
+
+ private
+
+ def title
+ t("admin.cookies.vendors.edit.title")
+ end
+end
diff --git a/app/components/admin/cookies/vendors/form_component.html.erb b/app/components/admin/cookies/vendors/form_component.html.erb
new file mode 100644
index 000000000..1d51fbbbe
--- /dev/null
+++ b/app/components/admin/cookies/vendors/form_component.html.erb
@@ -0,0 +1,10 @@
+<%= form_for [:admin, vendor], html: { class: "vendor-form" } do |f| %>
+ <%= render "shared/errors", resource: vendor %>
+
+ <%= f.text_field :name, hint: t("admin.cookies.vendors.form.name.help_text") %>
+ <%= f.text_area :description, hint: t("admin.cookies.vendors.form.description.help_text"), rows: 4 %>
+ <%= f.text_field :cookie, hint: t("admin.cookies.vendors.form.cookie.help_text") %>
+ <%= f.text_area :script, hint: t("admin.cookies.vendors.form.script.help_text"), rows: 10 %>
+
+ <%= f.submit %>
+<% end %>
diff --git a/app/components/admin/cookies/vendors/form_component.rb b/app/components/admin/cookies/vendors/form_component.rb
new file mode 100644
index 000000000..44dc4dc04
--- /dev/null
+++ b/app/components/admin/cookies/vendors/form_component.rb
@@ -0,0 +1,7 @@
+class Admin::Cookies::Vendors::FormComponent < ApplicationComponent
+ attr_reader :vendor
+
+ def initialize(vendor)
+ @vendor = vendor
+ end
+end
diff --git a/app/components/admin/cookies/vendors/new_component.html.erb b/app/components/admin/cookies/vendors/new_component.html.erb
new file mode 100644
index 000000000..7f55fac75
--- /dev/null
+++ b/app/components/admin/cookies/vendors/new_component.html.erb
@@ -0,0 +1,5 @@
+<%= back_link_to admin_settings_path(anchor: "tab-cookies-consent") %>
+
+<%= header %>
+
+<%= render Admin::Cookies::Vendors::FormComponent.new(vendor) %>
diff --git a/app/components/admin/cookies/vendors/new_component.rb b/app/components/admin/cookies/vendors/new_component.rb
new file mode 100644
index 000000000..f96fcec0a
--- /dev/null
+++ b/app/components/admin/cookies/vendors/new_component.rb
@@ -0,0 +1,15 @@
+class Admin::Cookies::Vendors::NewComponent < ApplicationComponent
+ include Header
+
+ attr_reader :vendor
+
+ def initialize(vendor)
+ @vendor = vendor
+ end
+
+ private
+
+ def title
+ t("admin.cookies.vendors.new.title")
+ end
+end
diff --git a/app/components/admin/cookies/vendors/row_component.html.erb b/app/components/admin/cookies/vendors/row_component.html.erb
new file mode 100644
index 000000000..cd08c5b45
--- /dev/null
+++ b/app/components/admin/cookies/vendors/row_component.html.erb
@@ -0,0 +1,5 @@
+
+ | <%= vendor.name %> |
+ <%= vendor.cookie %> |
+ <%= render Admin::TableActionsComponent.new(vendor) %> |
+
diff --git a/app/components/admin/cookies/vendors/row_component.rb b/app/components/admin/cookies/vendors/row_component.rb
new file mode 100644
index 000000000..10e7ac2e2
--- /dev/null
+++ b/app/components/admin/cookies/vendors/row_component.rb
@@ -0,0 +1,9 @@
+class Admin::Cookies::Vendors::RowComponent < ApplicationComponent
+ with_collection_parameter :vendor
+
+ attr_reader :vendor
+
+ def initialize(vendor:)
+ @vendor = vendor
+ end
+end
diff --git a/app/components/admin/cookies/vendors/table_component.html.erb b/app/components/admin/cookies/vendors/table_component.html.erb
new file mode 100644
index 000000000..741dd207d
--- /dev/null
+++ b/app/components/admin/cookies/vendors/table_component.html.erb
@@ -0,0 +1,22 @@
+<%= t("admin.cookies.vendors.third_party_cookies") %>
+
+<% if vendors.any? %>
+
+
+
+ | <%= attribute_name(:name) %> |
+ <%= attribute_name(:cookie) %> |
+ <%= t("admin.shared.actions") %> |
+
+
+
+ <%= render Admin::Cookies::Vendors::RowComponent.with_collection(vendors) %>
+
+
+<% else %>
+
+ <%= t("admin.cookies.vendors.empty") %>
+
+<% end %>
+
+<%= link_to t("admin.cookies.vendors.create_button"), new_admin_cookies_vendor_path, class: "button" %>
diff --git a/app/components/admin/cookies/vendors/table_component.rb b/app/components/admin/cookies/vendors/table_component.rb
new file mode 100644
index 000000000..bd897ce58
--- /dev/null
+++ b/app/components/admin/cookies/vendors/table_component.rb
@@ -0,0 +1,11 @@
+class Admin::Cookies::Vendors::TableComponent < ApplicationComponent
+ private
+
+ def vendors
+ ::Cookies::Vendor.all
+ end
+
+ def attribute_name(attribute)
+ ::Cookies::Vendor.human_attribute_name(attribute)
+ end
+end
diff --git a/app/components/admin/settings/cookies_consent_tab_component.html.erb b/app/components/admin/settings/cookies_consent_tab_component.html.erb
index 7bb2e9bfe..88fbedbdb 100644
--- a/app/components/admin/settings/cookies_consent_tab_component.html.erb
+++ b/app/components/admin/settings/cookies_consent_tab_component.html.erb
@@ -4,3 +4,5 @@
<%= render Admin::Settings::RowComponent.new("feature.cookies_consent", type: :feature, tab: tab) %>
<%= render Admin::Settings::RowComponent.new("cookies_consent.more_info_link", type: :text, tab: tab) %>
<% end %>
+
+<%= render Admin::Cookies::Vendors::TableComponent.new %>
diff --git a/app/controllers/admin/cookies/vendors_controller.rb b/app/controllers/admin/cookies/vendors_controller.rb
new file mode 100644
index 000000000..a011936d2
--- /dev/null
+++ b/app/controllers/admin/cookies/vendors_controller.rb
@@ -0,0 +1,38 @@
+class Admin::Cookies::VendorsController < Admin::BaseController
+ load_and_authorize_resource :vendor, class: "::Cookies::Vendor"
+
+ def create
+ if @vendor.save
+ redirect_to admin_settings_path(anchor: "tab-cookies-consent"),
+ notice: t("admin.cookies.vendors.create.notice")
+ else
+ render :new
+ end
+ end
+
+ def update
+ if @vendor.update(vendor_params)
+ redirect_to admin_settings_path(anchor: "tab-cookies-consent"),
+ notice: t("admin.cookies.vendors.update.notice")
+ else
+ render :edit
+ end
+ end
+
+ def destroy
+ @vendor.destroy!
+
+ redirect_to admin_settings_path(anchor: "tab-cookies-consent"),
+ notice: t("admin.cookies.vendors.destroy.notice")
+ end
+
+ private
+
+ def vendor_params
+ params.require(:cookies_vendor).permit(allowed_params)
+ end
+
+ def allowed_params
+ [:name, :description, :cookie, :script]
+ end
+end
diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb
index 5c9fb6dee..9e9c123ce 100644
--- a/app/models/abilities/administrator.rb
+++ b/app/models/abilities/administrator.rb
@@ -142,6 +142,8 @@ module Abilities
can :manage, LocalCensusRecord
can [:create, :read], LocalCensusRecords::Import
+ can :manage, Cookies::Vendor
+
if Rails.application.config.multitenancy && Tenant.default?
can [:create, :read, :update, :hide, :restore], Tenant
end
diff --git a/app/models/cookies.rb b/app/models/cookies.rb
new file mode 100644
index 000000000..6661769bd
--- /dev/null
+++ b/app/models/cookies.rb
@@ -0,0 +1,5 @@
+module Cookies
+ def self.table_name_prefix
+ "cookies_"
+ end
+end
diff --git a/app/models/cookies/vendor.rb b/app/models/cookies/vendor.rb
new file mode 100644
index 000000000..198f9ce6a
--- /dev/null
+++ b/app/models/cookies/vendor.rb
@@ -0,0 +1,4 @@
+class Cookies::Vendor < ApplicationRecord
+ validates :name, presence: true
+ validates :cookie, presence: true, uniqueness: true, format: { with: /\A[a-zA-Z0-9\_]+\Z/ }
+end
diff --git a/app/views/admin/cookies/vendors/edit.html.erb b/app/views/admin/cookies/vendors/edit.html.erb
new file mode 100644
index 000000000..09e16f2cb
--- /dev/null
+++ b/app/views/admin/cookies/vendors/edit.html.erb
@@ -0,0 +1 @@
+<%= render Admin::Cookies::Vendors::EditComponent.new(@vendor) %>
diff --git a/app/views/admin/cookies/vendors/new.html.erb b/app/views/admin/cookies/vendors/new.html.erb
new file mode 100644
index 000000000..7e92b901e
--- /dev/null
+++ b/app/views/admin/cookies/vendors/new.html.erb
@@ -0,0 +1 @@
+<%= render Admin::Cookies::Vendors::NewComponent.new(@vendor) %>
diff --git a/config/locales/en/activerecord.yml b/config/locales/en/activerecord.yml
index 068a4dde0..17f39b6dc 100644
--- a/config/locales/en/activerecord.yml
+++ b/config/locales/en/activerecord.yml
@@ -151,6 +151,9 @@ en:
votation_type:
one: Votation type
other: Votation types
+ cookies/vendor:
+ one: cookie vendor
+ other: cookie vendors
attributes:
budget:
name: "Name"
@@ -534,6 +537,10 @@ en:
votation_type/vote_type:
unique: Unique answer
multiple: Multiple answers
+ cookies/vendor:
+ name: Vendor name
+ cookie: Cookie name
+ script: Javascript code
errors:
models:
user:
diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml
index fc6d35e6d..f51a3daf6 100644
--- a/config/locales/en/admin.yml
+++ b/config/locales/en/admin.yml
@@ -1808,3 +1808,27 @@ en:
tags: "Tags"
tags_description: "Generates automatic tags on all items that can be tagged on."
title: "AI / Machine learning"
+ cookies:
+ vendors:
+ empty: No vendors found
+ third_party_cookies: Third party cookies
+ create_button: Create cookie vendor
+ create:
+ notice: Cookie vendor created successfully
+ new:
+ title: New cookie vendor
+ edit:
+ title: Edit cookie vendor
+ update:
+ notice: Cookie vendor updated successfully
+ destroy:
+ notice: Cookie vendor deleted successfully
+ form:
+ name:
+ help_text: This information will be publicly visible.
+ cookie:
+ help_text: This information will be used internally. The cookie name must be unique, and it can only contain letters, digits and underscores.
+ description:
+ help_text: This information will be publicly visible.
+ script:
+ help_text: This is the script to run when this cookie is accepted by the user. You can enter vanilla Javascript code here and introduce references to other application javascript.
diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml
index 9bab05b5a..a2ba75682 100644
--- a/config/locales/es/activerecord.yml
+++ b/config/locales/es/activerecord.yml
@@ -151,6 +151,9 @@ es:
votation_type:
one: Tipo de votación
other: Tipos de votación
+ cookies/vendor:
+ one: proveedor de cookies
+ other: proveedores de cookies
attributes:
budget:
name: "Nombre"
@@ -534,6 +537,10 @@ es:
votation_type/vote_type:
unique: Respuesta única
multiple: Respuesta múltiple
+ cookies/vendor:
+ name: Nombre del proveedor
+ cookie: Nombre de la cookie
+ script: Código javascript
errors:
models:
user:
diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml
index 74f91a71f..3e2c0067b 100644
--- a/config/locales/es/admin.yml
+++ b/config/locales/es/admin.yml
@@ -1808,3 +1808,27 @@ es:
tags: "Etiquetas"
tags_description: "Genera etiquetas automáticas para todos los elementos que pueden ser etiquetados."
title: "IA / Machine learning"
+ cookies:
+ vendors:
+ empty: No se han encontrado cookies de terceros
+ third_party_cookies: Cookies de terceros
+ create_button: Nueva cookie
+ create:
+ notice: Cookie creada correctamente
+ new:
+ title: Nueva cookie
+ edit:
+ title: Editar cookie
+ update:
+ notice: Cookie actualizada correctamente
+ destroy:
+ notice: Cookie eliminada correctamente
+ form:
+ name:
+ help_text: Esta información será visible públicamente.
+ cookie:
+ help_text: Esta información se utilizará internamente. El nombre de la cookie debe ser único, solo puede contener letras, dígitos y guiones bajos.
+ description:
+ help_text: Esta información será visible públicamente.
+ script:
+ help_text: Este es el script que se ejecutará cuando esta cookie sea aceptada por el usuario. Puede introducir código Javascript aquí e introducir referencias a otro javascript de la aplicación.
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index ed300820a..e0eec7867 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -304,6 +304,10 @@ namespace :admin do
post :execute, on: :collection
delete :cancel, on: :collection
end
+
+ namespace :cookies do
+ resources :vendors, except: [:index, :show]
+ end
end
end
diff --git a/db/migrate/20240108110154_create_cookies_vendors.rb b/db/migrate/20240108110154_create_cookies_vendors.rb
new file mode 100644
index 000000000..e4bc42cd2
--- /dev/null
+++ b/db/migrate/20240108110154_create_cookies_vendors.rb
@@ -0,0 +1,14 @@
+class CreateCookiesVendors < ActiveRecord::Migration[6.1]
+ def change
+ create_table :cookies_vendors do |t|
+ t.string :name
+ t.text :description
+ t.string :cookie
+ t.text :script
+
+ t.timestamps
+
+ t.index :cookie, unique: true
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c30aad673..e3b541dc0 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -454,6 +454,16 @@ ActiveRecord::Schema[7.0].define(version: 2024_10_26_112901) do
t.datetime "updated_at", precision: nil, null: false
end
+ create_table "cookies_vendors", force: :cascade do |t|
+ t.string "name"
+ t.text "description"
+ t.string "cookie"
+ t.text "script"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["cookie"], name: "index_cookies_vendors_on_cookie", unique: true
+ end
+
create_table "dashboard_actions", id: :serial, force: :cascade do |t|
t.string "title", limit: 80
t.text "description"
diff --git a/spec/factories/cookies.rb b/spec/factories/cookies.rb
new file mode 100644
index 000000000..b3ac24e62
--- /dev/null
+++ b/spec/factories/cookies.rb
@@ -0,0 +1,7 @@
+FactoryBot.define do
+ factory :cookies_vendor, class: "Cookies::Vendor" do
+ name { "Vendor name" }
+ sequence(:cookie) { |n| "vendor_cookie_#{n}" }
+ description { "Vendor description" }
+ end
+end
diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb
index f75aac370..24b10a9cc 100644
--- a/spec/models/abilities/administrator_spec.rb
+++ b/spec/models/abilities/administrator_spec.rb
@@ -172,6 +172,10 @@ describe Abilities::Administrator do
it { should be_able_to(:manage, Widget::Card) }
+ it { should be_able_to(:create, Cookies::Vendor) }
+ it { should be_able_to(:update, Cookies::Vendor) }
+ it { should be_able_to(:destroy, Cookies::Vendor) }
+
describe "tenants" do
context "with multitenancy disabled" do
before { allow(Rails.application.config).to receive(:multitenancy).and_return(false) }
diff --git a/spec/models/abilities/common_spec.rb b/spec/models/abilities/common_spec.rb
index 9f80ee67c..f79295d13 100644
--- a/spec/models/abilities/common_spec.rb
+++ b/spec/models/abilities/common_spec.rb
@@ -372,4 +372,8 @@ describe Abilities::Common do
it { should_not be_able_to(:read, SDG::Manager) }
it { should_not be_able_to(:create, SDG::Manager) }
it { should_not be_able_to(:delete, SDG::Manager) }
+
+ it { should_not be_able_to(:create, Cookies::Vendor) }
+ it { should_not be_able_to(:update, Cookies::Vendor) }
+ it { should_not be_able_to(:destroy, Cookies::Vendor) }
end
diff --git a/spec/models/abilities/moderator_spec.rb b/spec/models/abilities/moderator_spec.rb
index 9dd4f4c0c..cddaa8c8f 100644
--- a/spec/models/abilities/moderator_spec.rb
+++ b/spec/models/abilities/moderator_spec.rb
@@ -113,4 +113,8 @@ describe Abilities::Moderator do
it { should_not be_able_to(:read, SDG::Manager) }
it { should_not be_able_to(:create, SDG::Manager) }
it { should_not be_able_to(:delete, SDG::Manager) }
+
+ it { should_not be_able_to(:create, Cookies::Vendor) }
+ it { should_not be_able_to(:update, Cookies::Vendor) }
+ it { should_not be_able_to(:destroy, Cookies::Vendor) }
end
diff --git a/spec/models/abilities/valuator_spec.rb b/spec/models/abilities/valuator_spec.rb
index 87b52f472..8e64833fc 100644
--- a/spec/models/abilities/valuator_spec.rb
+++ b/spec/models/abilities/valuator_spec.rb
@@ -51,4 +51,8 @@ describe Abilities::Valuator do
it { should_not be_able_to(:read, SDG::Manager) }
it { should_not be_able_to(:create, SDG::Manager) }
it { should_not be_able_to(:delete, SDG::Manager) }
+
+ it { should_not be_able_to(:create, Cookies::Vendor) }
+ it { should_not be_able_to(:update, Cookies::Vendor) }
+ it { should_not be_able_to(:destroy, Cookies::Vendor) }
end
diff --git a/spec/models/cookies/vendor_spec.rb b/spec/models/cookies/vendor_spec.rb
new file mode 100644
index 000000000..3a56f098d
--- /dev/null
+++ b/spec/models/cookies/vendor_spec.rb
@@ -0,0 +1,43 @@
+require "rails_helper"
+
+describe Cookies::Vendor do
+ let(:cookies_vendor) { build(:cookies_vendor) }
+
+ it "is valid" do
+ expect(cookies_vendor).to be_valid
+ end
+
+ it "is not valid without a name" do
+ cookies_vendor.name = nil
+
+ expect(cookies_vendor).not_to be_valid
+ end
+
+ it "is not valid without the cookie name" do
+ cookies_vendor.cookie = nil
+
+ expect(cookies_vendor).not_to be_valid
+ end
+
+ it "is not valid when cookie_name contains whitespaces, special characters" do
+ cookies_vendor.cookie = "cookie vendor name"
+
+ expect(cookies_vendor).not_to be_valid
+
+ cookies_vendor.cookie = "cookie_vendor/name"
+
+ expect(cookies_vendor).not_to be_valid
+
+ cookies_vendor.cookie = "cookie_vendor_name"
+
+ expect(cookies_vendor).to be_valid
+ end
+
+ it "is not valid when the cookie name already exists" do
+ create(:cookies_vendor, cookie: "existing_name")
+
+ cookies_vendor.cookie = "existing_name"
+
+ expect(cookies_vendor).not_to be_valid
+ end
+end
diff --git a/spec/system/admin/cookies/vendors_spec.rb b/spec/system/admin/cookies/vendors_spec.rb
new file mode 100644
index 000000000..fda94914b
--- /dev/null
+++ b/spec/system/admin/cookies/vendors_spec.rb
@@ -0,0 +1,62 @@
+require "rails_helper"
+
+describe "Admin cookies vendors", :admin do
+ describe "Index" do
+ scenario "Shows existing cookies and links to actions" do
+ create(:cookies_vendor, name: "Third party", cookie: "third_party")
+ visit admin_settings_path(anchor: "tab-cookies-consent")
+
+ expect(page).to have_content "Third party"
+ expect(page).to have_content "third_party"
+ expect(page).to have_link "Edit"
+ expect(page).to have_button "Delete"
+ expect(page).to have_link "Create cookie vendor"
+ end
+ end
+
+ describe "Create" do
+ scenario "Shows a notice and the new cookie after creation" do
+ visit admin_settings_path(anchor: "tab-cookies-consent")
+
+ click_link "Create cookie vendor"
+ fill_in "Vendor name", with: "Vendor name"
+ fill_in "Cookie name", with: "vendor_cookie"
+ fill_in "Description", with: "Cookie details"
+ click_button "Create cookie vendor"
+
+ expect(page).to have_content "Cookie vendor created successfully"
+ expect(page).to have_content "Vendor name"
+ expect(page).to have_content "vendor_cookie"
+ end
+ end
+
+ describe "Update" do
+ scenario "Shows a notice and the cookie changes after update" do
+ create(:cookies_vendor, name: "Third party", cookie: "third_party")
+ visit admin_settings_path(anchor: "tab-cookies-consent")
+
+ click_link "Edit"
+ fill_in "Vendor name", with: "Cool Company Name"
+ click_button "Update cookie vendor"
+
+ expect(page).to have_content "Cookie vendor updated successfully"
+ expect(page).to have_content "Cool Company Name"
+ end
+ end
+
+ describe "Destroy" do
+ scenario "Shows a notice and removes cookie" do
+ create(:cookies_vendor, name: "Analitics cookie", cookie: "analitics_cookie")
+ visit admin_settings_path(anchor: "tab-cookies-consent")
+
+ expect(page).to have_content "Analitics cookie"
+ expect(page).to have_content "analitics_cookie"
+
+ accept_confirm { click_button "Delete Analitics cookie" }
+
+ expect(page).to have_content "Cookie vendor deleted successfully"
+ expect(page).not_to have_content "Analitics cookie"
+ expect(page).not_to have_content "analitics_cookie"
+ end
+ end
+end