SM-08 remove GTM portal analytics path
This commit is contained in:
@@ -26,12 +26,6 @@ const { replaceInstallationName } = useBranding();
|
||||
// Provider keys and formats, mirroring Portal::ANALYTICS_CONFIG_FORMATS. Admin-only
|
||||
// (also enforced on the backend) since these inject tracking scripts into public pages.
|
||||
const ANALYTICS_PROVIDERS = [
|
||||
{
|
||||
key: 'gtm_container_id',
|
||||
i18nKey: 'GTM',
|
||||
icon: 'i-logos-google-tag-manager',
|
||||
format: /^GTM-[A-Z0-9]+$/,
|
||||
},
|
||||
{
|
||||
key: 'ga4_measurement_id',
|
||||
i18nKey: 'GA4',
|
||||
|
||||
@@ -48,7 +48,6 @@ module PortalConfigSchema
|
||||
'analytics' => {
|
||||
'type' => %w[object null],
|
||||
'properties' => {
|
||||
'gtm_container_id' => { 'type' => %w[string null] },
|
||||
'ga4_measurement_id' => { 'type' => %w[string null] },
|
||||
'hotjar_site_id' => { 'type' => %w[string null] },
|
||||
'plausible_domain' => { 'type' => %w[string null] },
|
||||
|
||||
@@ -56,7 +56,6 @@ class Portal < ApplicationRecord
|
||||
# Analytics id fields and the format each must match. Formats keep values safe to
|
||||
# interpolate into markup. Add a provider here and its snippet in _portal_analytics.html.erb.
|
||||
ANALYTICS_CONFIG_FORMATS = {
|
||||
'gtm_container_id' => /\AGTM-[A-Z0-9]+\z/,
|
||||
'ga4_measurement_id' => /\AG-[A-Z0-9]+\z/,
|
||||
'hotjar_site_id' => /\A\d+\z/,
|
||||
'plausible_domain' => /\A[a-z0-9]([a-z0-9.-]*[a-z0-9])?\z/i,
|
||||
@@ -71,7 +70,9 @@ class Portal < ApplicationRecord
|
||||
|
||||
def analytics
|
||||
value = config_value('analytics')
|
||||
value.is_a?(Hash) ? value : {}
|
||||
return {} unless value.is_a?(Hash)
|
||||
|
||||
value.deep_stringify_keys.slice(*ANALYTICS_CONFIG_FORMATS.keys)
|
||||
end
|
||||
|
||||
# Reader per analytics id (e.g. portal.ga4_measurement_id) so the snippet partials stay simple.
|
||||
@@ -159,11 +160,18 @@ class Portal < ApplicationRecord
|
||||
|
||||
def normalize_config
|
||||
self.config = persisted_config.merge((config || {}).deep_stringify_keys)
|
||||
normalize_analytics_config
|
||||
config['allowed_locales'] = allowed_locale_codes
|
||||
config['default_locale'] = default_locale
|
||||
config['draft_locales'] = draft_locale_codes
|
||||
end
|
||||
|
||||
def normalize_analytics_config
|
||||
return unless config['analytics'].is_a?(Hash)
|
||||
|
||||
config['analytics'] = config['analytics'].deep_stringify_keys.slice(*ANALYTICS_CONFIG_FORMATS.keys)
|
||||
end
|
||||
|
||||
def validate_config
|
||||
denied_keys = config.keys - CONFIG_JSON_KEYS
|
||||
errors.add(:config, "in portal on #{denied_keys.join(',')} is not supported.") if denied_keys.any?
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
<%# Analytics tags. Every value below is validated against Portal::ANALYTICS_CONFIG_FORMATS, %>
|
||||
<%# so it is restricted to a safe shape (no quotes/angle brackets) before interpolation. %>
|
||||
<% if @portal.gtm_container_id.present? %>
|
||||
<!-- Google Tag Manager -->
|
||||
<script>
|
||||
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','<%= j @portal.gtm_container_id %>');
|
||||
</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
<% end %>
|
||||
<% if @portal.ga4_measurement_id.present? %>
|
||||
<!-- Google Analytics 4 -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=<%= @portal.ga4_measurement_id %>"></script>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<% if @portal.gtm_container_id.present? %>
|
||||
<!-- Google Tag Manager (noscript) -->
|
||||
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=<%= @portal.gtm_container_id %>"
|
||||
title="Google Tag Manager" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||
<!-- End Google Tag Manager (noscript) -->
|
||||
<% end %>
|
||||
<% if @portal.meta_pixel_id.present? %>
|
||||
<!-- Meta Pixel (noscript) -->
|
||||
<noscript><img height="1" width="1" style="display:none"
|
||||
|
||||
@@ -190,6 +190,15 @@ RSpec.describe 'Api::V1::Accounts::Portals', type: :request do
|
||||
expect(portal.reload.config['analytics']).to eq('ga4_measurement_id' => 'G-ADMIN12345')
|
||||
end
|
||||
|
||||
it 'rejects administrators from configuring Google Tag Manager' do
|
||||
put "/api/v1/accounts/#{account.id}/portals/#{portal.slug}",
|
||||
params: { portal: { config: { analytics: { gtm_container_id: 'GTM-LEGACY123' } } } },
|
||||
headers: admin.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(portal.reload.analytics).to eq({})
|
||||
end
|
||||
|
||||
it 'preserves drafted locales when draft_locales is omitted' do
|
||||
portal.update!(config: { allowed_locales: %w[en es fr], draft_locales: ['es'], default_locale: 'en' })
|
||||
|
||||
|
||||
@@ -61,6 +61,31 @@ RSpec.describe Portal do
|
||||
expect(portal.custom_domain).to be_nil
|
||||
end
|
||||
|
||||
it 'does not expose legacy Google Tag Manager configuration' do
|
||||
portal.assign_attributes(config: {
|
||||
'analytics' => {
|
||||
'gtm_container_id' => 'GTM-LEGACY123',
|
||||
'ga4_measurement_id' => 'G-VALID123'
|
||||
}
|
||||
})
|
||||
|
||||
expect(portal.analytics).to eq('ga4_measurement_id' => 'G-VALID123')
|
||||
end
|
||||
|
||||
it 'scrubs legacy Google Tag Manager configuration during normalization' do
|
||||
portal.assign_attributes(config: {
|
||||
'allowed_locales' => ['en'],
|
||||
'default_locale' => 'en',
|
||||
'analytics' => {
|
||||
'gtm_container_id' => 'GTM-LEGACY123',
|
||||
'ga4_measurement_id' => 'G-VALID123'
|
||||
}
|
||||
})
|
||||
|
||||
expect(portal).to be_valid
|
||||
expect(portal.config['analytics']).to eq('ga4_measurement_id' => 'G-VALID123')
|
||||
end
|
||||
|
||||
context 'with locale_translations' do
|
||||
it 'allows valid locale translations' do
|
||||
portal.update(config: { allowed_locales: %w[en es], default_locale: 'en',
|
||||
|
||||
Reference in New Issue
Block a user