Commit bd6e6180 authored by Doug Stull's avatar Doug Stull Committed by Douglas Barbosa Alexandre

Add pendo javascript snippet

 - We want to track using 3rd party product pendo.
 - This change will allow it to apply to every page, will need
   to backport the head file changes to CE as well

Skip pendo during testing

- I am unsure if this will cover all testing, but will cover rspec

Fix pendo tracking stub

Use feature flag stub helper

- reuse current helpers to help organize code
- resolve new line issue on pendo partial

Remove binding

Add config for pendo api settings and tests

Remove use of needless constant in pendo tracker

Update to be inline with snowplow implementation

Add pendo to the integration settings admin area
parent 58acb626
......@@ -292,7 +292,9 @@ module ApplicationSettingsHelper
:snowplow_site_id,
:push_event_hooks_limit,
:push_event_activities_limit,
:custom_http_clone_url_root
:custom_http_clone_url_root,
:pendo_enabled,
:pendo_url
]
end
......
......@@ -104,6 +104,11 @@ class ApplicationSetting < ApplicationRecord
hostname: true,
if: :snowplow_enabled
validates :pendo_url,
presence: true,
public_url: true,
if: :pendo_enabled
validates :max_attachment_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
......
......@@ -129,7 +129,9 @@ module ApplicationSettingImplementation
snowplow_cookie_domain: nil,
snowplow_enabled: false,
snowplow_site_id: nil,
custom_http_clone_url_root: nil
custom_http_clone_url_root: nil,
pendo_enabled: false,
pendo_url: nil
}
end
......
- expanded = true if !@application_setting.valid? && @application_setting.errors.any? { |k| k.to_s.start_with?('pendo_') }
%section.settings.as-pendo.no-animate#js-pendo-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
= _('Pendo')
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Configure the %{link} integration.').html_safe % { link: link_to('Pendo', 'https://www.pendo.io/', target: '_blank') }
.settings-content
= form_for @application_setting, url: integrations_admin_application_settings_path(anchor: 'js-pendo-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting) if expanded
%fieldset
.form-group
.form-check
= f.check_box :pendo_enabled, class: 'form-check-input'
= f.label :pendo_enabled, _('Enable pendo tracking'), class: 'form-check-label'
.form-group
= f.label :pendo_url, _('Pendo endpoint'), class: 'label-light'
= f.text_field :pendo_url, class: 'form-control', placeholder: 'https://cdn.pendo.io/agent/static/your-api-key/pendo.js'
= f.submit _('Save changes'), class: 'btn btn-success'
......@@ -28,4 +28,5 @@
.settings-content
= render 'third_party_offers', application_setting: @application_setting
= render_if_exists 'admin/application_settings/snowplow', expanded: expanded_by_default?
= render 'admin/application_settings/snowplow', expanded: expanded_by_default?
= render 'admin/application_settings/pendo'
......@@ -89,4 +89,5 @@
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
= render_if_exists 'layouts/snowplow'
= render 'layouts/snowplow'
= render 'layouts/pendo'
- return unless Gitlab::CurrentSettings.pendo_enabled?
= javascript_tag nonce: true do
:plain
;var trackable = !['1', 'yes'].includes(window.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack);
if (trackable){
(function(p,e,n,d,o){var v,w,x,y,z;o=p[d]=p[d]||{};o._q=[];
v=['initialize','identify','updateOptions','pageLoad'];for(w=0,x=v.length;w<x;++w)(function(m){
o[m]=o[m]||function(){o._q[m===v[0]?'unshift':'push']([m].concat([].slice.call(arguments,0)));};})(v[w]);
y=e.createElement(n);y.async=!0;y.src='#{Gitlab::CurrentSettings.pendo_url}';
z=e.getElementsByTagName(n)[0];z.parentNode.insertBefore(y,z);})(window,document,'script','pendo');
pendo.initialize({
visitor: {
id: '#{current_user&.id}',
}
});};
---
title: Adds Application Settings and ui settings in the integration admin area for Pendo
merge_request: 15086
author:
type: added
class AddPendoEnabledToApplicationSettings < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default :application_settings, :pendo_enabled, :boolean, default: false, allow_null: false
end
def down
remove_column :application_settings, :pendo_enabled
end
end
class AddPendoUrlToApplicationSettings < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :application_settings, :pendo_url, :string, limit: 255
end
end
......@@ -341,6 +341,8 @@ ActiveRecord::Schema.define(version: 2019_10_17_045817) do
t.integer "push_event_hooks_limit", default: 3, null: false
t.integer "push_event_activities_limit", default: 3, null: false
t.string "custom_http_clone_url_root", limit: 511
t.boolean "pendo_enabled", default: false, null: false
t.string "pendo_url", limit: 255
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
......
......@@ -257,7 +257,6 @@ are listed in the descriptions of the relevant settings.
| `housekeeping_incremental_repack_period` | integer | required by: `housekeeping_enabled` | Number of Git pushes after which an incremental `git repack` is run. |
| `html_emails_enabled` | boolean | no | Enable HTML emails. |
| `import_sources` | array of strings | no | Sources to allow project import from, possible values: `github`, `bitbucket`, `bitbucket_server`, `gitlab`, `google_code`, `fogbugz`, `git`, `gitlab_project`, `gitea`, `manifest`, and `phabricator`. |
| `instance_statistics_visibility_private` | boolean | no | When set to `true` Instance statistics will only be available to admins. |
| `local_markdown_version` | integer | no | Increase this value when any cached markdown should be invalidated. |
| `max_artifacts_size` | integer | no | Maximum artifacts size in MB |
......@@ -317,6 +316,8 @@ are listed in the descriptions of the relevant settings.
| `snowplow_cookie_domain` | string | no | The Snowplow cookie domain. (e.g. `.gitlab.com`) |
| `snowplow_enabled` | boolean | no | Enable snowplow tracking. |
| `snowplow_site_id` | string | no | The Snowplow site name / application id. (e.g. `gitlab`) |
| `pendo_url` | string | required by: `pendo_enabled` | The Pendo endpoint url with js snippet. (e.g. `https://cdn.pendo.io/agent/static/your-api-key/pendo.js`) |
| `pendo_enabled` | boolean | no | Enable pendo tracking. |
| `terminal_max_session_time` | integer | no | Maximum time for web terminal websocket connection (in seconds). Set to `0` for unlimited time. |
| `terms` | text | required by: `enforce_terms` | (**Required by:** `enforce_terms`) Markdown content for the ToS. |
| `throttle_authenticated_api_enabled` | boolean | no | (**If enabled, requires:** `throttle_authenticated_api_period_in_seconds` and `throttle_authenticated_api_requests_per_period`) Enable authenticated API request rate limit. Helps reduce request volume (e.g. from crawlers or abusive bots). |
......
......@@ -140,6 +140,10 @@ module API
optional :snowplow_cookie_domain, type: String, desc: 'The Snowplow cookie domain'
optional :snowplow_site_id, type: String, desc: 'The Snowplow site name / application ic'
end
optional :pendo_enabled, type: Grape::API::Boolean, desc: 'Enable Pendo tracking'
given pendo_enabled: ->(val) { val } do
requires :pendo_url, type: String, desc: 'The Pendo url endpoint'
end
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
......
......@@ -6075,6 +6075,9 @@ msgstr ""
msgid "Enable or disable version check and usage ping."
msgstr ""
msgid "Enable pendo tracking"
msgstr ""
msgid "Enable protected paths rate limit"
msgstr ""
......@@ -11666,6 +11669,12 @@ msgstr ""
msgid "Pending"
msgstr ""
msgid "Pendo"
msgstr ""
msgid "Pendo endpoint"
msgstr ""
msgid "People without permission will never get a notification and won't be able to comment."
msgstr ""
......
......@@ -36,4 +36,9 @@ describe ApplicationSettingsHelper do
it_behaves_like 'when HTTP protocol is in use', 'https'
it_behaves_like 'when HTTP protocol is in use', 'http'
context 'with tracking parameters' do
it { expect(visible_attributes).to include(*%i(snowplow_collector_hostname snowplow_cookie_domain snowplow_enabled snowplow_site_id))}
it { expect(visible_attributes).to include(*%i(pendo_enabled pendo_url))}
end
end
......@@ -64,6 +64,36 @@ describe ApplicationSetting do
it { is_expected.not_to allow_value('three').for(:push_event_activities_limit) }
it { is_expected.not_to allow_value(nil).for(:push_event_activities_limit) }
context 'when snowplow is enabled' do
before do
setting.snowplow_enabled = true
end
it { is_expected.not_to allow_value(nil).for(:snowplow_collector_hostname) }
it { is_expected.to allow_value("snowplow.gitlab.com").for(:snowplow_collector_hostname) }
it { is_expected.not_to allow_value('/example').for(:snowplow_collector_hostname) }
end
context 'when snowplow is not enabled' do
it { is_expected.to allow_value(nil).for(:snowplow_collector_hostname) }
end
context 'when pendo is enabled' do
before do
setting.pendo_enabled = true
end
it { is_expected.not_to allow_value(nil).for(:pendo_url) }
it { is_expected.to allow_value(http).for(:pendo_url) }
it { is_expected.to allow_value(https).for(:pendo_url) }
it { is_expected.not_to allow_value(ftp).for(:pendo_url) }
it { is_expected.not_to allow_value('http://127.0.0.1').for(:pendo_url) }
end
context 'when pendo is not enabled' do
it { is_expected.to allow_value(nil).for(:pendo_url) }
end
context "when user accepted let's encrypt terms of service" do
before do
setting.update(lets_encrypt_terms_of_service_accepted: true)
......
......@@ -220,6 +220,54 @@ describe API::Settings, 'Settings' do
end
end
context "pendo tracking settings" do
let(:settings) do
{
pendo_url: "https://pendo.example.com",
pendo_enabled: true
}
end
let(:attribute_names) { settings.keys.map(&:to_s) }
it "includes the attributes in the API" do
get api("/application/settings", admin)
expect(response).to have_gitlab_http_status(200)
attribute_names.each do |attribute|
expect(json_response.keys).to include(attribute)
end
end
it "allows updating the settings" do
put api("/application/settings", admin), params: settings
expect(response).to have_gitlab_http_status(200)
settings.each do |attribute, value|
expect(ApplicationSetting.current.public_send(attribute)).to eq(value)
end
end
context "missing pendo_url value when pendo_enabled is true" do
it "returns a blank parameter error message" do
put api("/application/settings", admin), params: { pendo_enabled: true }
expect(response).to have_gitlab_http_status(400)
expect(json_response["error"]).to eq("pendo_url is missing")
end
it "handles validation errors" do
put api("/application/settings", admin), params: settings.merge({
pendo_url: nil
})
expect(response).to have_gitlab_http_status(400)
message = json_response["message"]
expect(message["pendo_url"]).to include("can't be blank")
end
end
end
context "missing plantuml_url value when plantuml_enabled is true" do
it "returns a blank parameter error message" do
put api("/application/settings", admin), params: { plantuml_enabled: true }
......
......@@ -84,7 +84,7 @@ describe 'layouts/_head' do
allow(Gitlab::CurrentSettings).to receive(:snowplow_collector_hostname).and_return('www.snow.plow')
end
it 'add a snowplow script tag with asset host' do
it 'adds a snowplow script tag with asset host' do
render
expect(rendered).to match('http://test.host/assets/snowplow/')
expect(rendered).to match('window.snowplow')
......@@ -92,6 +92,28 @@ describe 'layouts/_head' do
end
end
context 'when pendo is enabled' do
it 'adds a pendo initialization snippet with url', :aggregate_failures do
allow(Gitlab::CurrentSettings).to receive(:pendo_enabled?).and_return(true)
allow(Gitlab::CurrentSettings).to receive(:pendo_url).and_return('www.pen.do')
render
expect(rendered).to match('pendo.initialize')
expect(rendered).to match('www.pen.do')
end
end
context 'when pendo is not enabled' do
it 'do not add pendo snippet' do
allow(Gitlab::CurrentSettings).to receive(:pendo_enabled?).and_return(false)
render
expect(rendered).not_to match('pendo.initialize')
end
end
context 'when a Piwik config is set' do
let(:piwik_host) { 'piwik.example.com' }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment