diff --git a/config/initializers/ahoy.rb b/config/initializers/ahoy.rb
new file mode 100644
index 000000000..8403a9160
--- /dev/null
+++ b/config/initializers/ahoy.rb
@@ -0,0 +1,8 @@
+class Ahoy::Store < Ahoy::DatabaseStore
+end
+
+# set to true for JavaScript tracking
+Ahoy.api = true
+
+# set to false to disable geocode tracking
+Ahoy.geocode = false
diff --git a/db/migrate/20200617183058_create_ahoy_visits_and_events.rb b/db/migrate/20200617183058_create_ahoy_visits_and_events.rb
new file mode 100644
index 000000000..df00e5b23
--- /dev/null
+++ b/db/migrate/20200617183058_create_ahoy_visits_and_events.rb
@@ -0,0 +1,28 @@
+class CreateAhoyVisitsAndEvents < ActiveRecord::Migration[6.0]
+ # This migration includes a subset of the available fields provided by ahoy.
+ # For the full list of options, please see https://github.com/ankane/ahoy.
+
+ def change
+ create_table :ahoy_visits do |t|
+ t.string :visit_token
+ t.string :visitor_token
+
+ t.references :user
+ t.timestamp :started_at
+ end
+
+ add_index :ahoy_visits, [:visit_token], unique: true
+
+ create_table :ahoy_events do |t|
+ t.references :visit
+ t.references :user
+
+ t.string :name
+ t.jsonb :properties
+ t.timestamp :time
+ end
+
+ add_index :ahoy_events, [:name, :time]
+ add_index :ahoy_events, :properties, using: :gin, opclass: :jsonb_path_ops
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2c9cbcf8c..ee58423a3 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,11 +10,23 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_06_17_014509) do
+ActiveRecord::Schema.define(version: 2020_06_17_183058) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
+ create_table "ahoy_events", force: :cascade do |t|
+ t.string "name"
+ t.jsonb "properties"
+ t.datetime "time"
+ t.bigint "user_id"
+ t.bigint "visit_id"
+ t.index ["name", "time"], name: "index_ahoy_events_on_name_and_time"
+ t.index ["properties"], name: "index_ahoy_events_on_properties", opclass: :jsonb_path_ops, using: :gin
+ t.index ["user_id"], name: "index_ahoy_events_on_user_id"
+ t.index ["visit_id"], name: "index_ahoy_events_on_visit_id"
+ end
+
create_table "ahoy_messages", id: :serial, force: :cascade do |t|
t.datetime "clicked_at"
t.text "content"
@@ -37,6 +49,15 @@ ActiveRecord::Schema.define(version: 2020_06_17_014509) do
t.index ["user_id", "user_type"], name: "index_ahoy_messages_on_user_id_and_user_type"
end
+ create_table "ahoy_visits", force: :cascade do |t|
+ t.datetime "started_at"
+ t.bigint "user_id"
+ t.string "visit_token"
+ t.string "visitor_token"
+ t.index ["user_id"], name: "index_ahoy_visits_on_user_id"
+ t.index ["visit_token"], name: "index_ahoy_visits_on_visit_token", unique: true
+ end
+
create_table "api_secrets", force: :cascade do |t|
t.datetime "created_at", null: false
t.string "description", null: false
diff --git a/package.json b/package.json
index 9267ab3ea..85daad57a 100644
--- a/package.json
+++ b/package.json
@@ -128,6 +128,7 @@
"@babel/preset-env": "^7.9.6",
"@github/clipboard-copy-element": "^1.1.2",
"@rails/webpacker": "5.1.1",
+ "ahoy.js": "^0.3.6",
"babel-preset-preact": "^2.0.0",
"chart.js": "^2.9.3",
"clipboard-polyfill": "^2.8.6",
diff --git a/spec/factories/broadcasts.rb b/spec/factories/broadcasts.rb
index 7b5787c27..9f3791705 100644
--- a/spec/factories/broadcasts.rb
+++ b/spec/factories/broadcasts.rb
@@ -68,5 +68,9 @@ FactoryBot.define do
type_of { "Announcement" }
processed_html { "
Hello, World!
" }
end
+
+ trait :with_tracking do
+ processed_html { "Sloan here again! 👋 DEV is a friendly community. Why not introduce yourself by leaving a comment in
the welcome thread!" }
+ end
end
end
diff --git a/spec/system/notifications/notifications_page_spec.rb b/spec/system/notifications/notifications_page_spec.rb
index 8e5fb6df6..5277496d6 100644
--- a/spec/system/notifications/notifications_page_spec.rb
+++ b/spec/system/notifications/notifications_page_spec.rb
@@ -85,4 +85,54 @@ RSpec.describe "Notifications page", type: :system, js: true do
validate_reply(comment.id)
end
end
+
+ context "with welcome notifications" do
+ let(:mascot_account) { create(:user) }
+
+ before do
+ allow(Notification).to receive(:send_welcome_notification).and_call_original
+ allow(User).to receive(:mascot_account).and_return(mascot_account)
+ allow(SiteConfig).to receive(:staff_user_id).and_return(mascot_account.id)
+ alex.update!(created_at: 1.day.ago)
+ end
+
+ context "without tracking enabled" do
+ before do
+ create(:welcome_broadcast)
+ Broadcasts::WelcomeNotification::Generator.call(alex.id)
+ sidekiq_perform_enqueued_jobs
+ end
+
+ it "renders the notification" do
+ visit "/notifications"
+
+ expect(page).to have_css(".broadcast-content")
+ expect(page).to have_css("#welcome_notification_welcome_thread")
+ end
+
+ it "does not track events" do
+ visit "/notifications"
+ click_link("the welcome thread")
+
+ expect(page).to have_current_path("/welcome")
+ expect(Ahoy::Event.count).to eq(0)
+ end
+ end
+
+ context "with tracking enabled" do
+ before do
+ create(:welcome_broadcast, :with_tracking)
+ Broadcasts::WelcomeNotification::Generator.call(alex.id)
+ sidekiq_perform_enqueued_jobs
+ end
+
+ it "tracks events" do
+ visit "/notifications"
+ click_link("the welcome thread")
+
+ expect(page).to have_current_path("/welcome")
+ expect(Ahoy::Event.count).to eq(1)
+ end
+ end
+ end
end
diff --git a/vendor/cache/ahoy_matey-3.0.4.gem b/vendor/cache/ahoy_matey-3.0.4.gem
new file mode 100644
index 000000000..b8e93cd96
Binary files /dev/null and b/vendor/cache/ahoy_matey-3.0.4.gem differ
diff --git a/vendor/cache/device_detector-1.0.3.gem b/vendor/cache/device_detector-1.0.3.gem
new file mode 100644
index 000000000..0ba4b4127
Binary files /dev/null and b/vendor/cache/device_detector-1.0.3.gem differ
diff --git a/vendor/cache/geocoder-1.6.3.gem b/vendor/cache/geocoder-1.6.3.gem
new file mode 100644
index 000000000..0882a4845
Binary files /dev/null and b/vendor/cache/geocoder-1.6.3.gem differ
diff --git a/yarn.lock b/yarn.lock
index 20da8f328..dd18580df 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2550,6 +2550,13 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
+ahoy.js@^0.3.6:
+ version "0.3.6"
+ resolved "https://registry.yarnpkg.com/ahoy.js/-/ahoy.js-0.3.6.tgz#ca27cb816a1de1f537616e4ea3213950259cd821"
+ integrity sha512-9X1g5xJioXk+AY5ti1LIlHExZ+8SjhuQOzAAXkNAZrZ6lmp88lFW7IeHADfii1/p5Cf3azg9hF2dk8Zsu4OfWA==
+ dependencies:
+ object-to-formdata ">=3.0.0"
+
airbnb-js-shims@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-2.2.1.tgz#db481102d682b98ed1daa4c5baa697a05ce5c040"
@@ -10057,6 +10064,11 @@ object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+object-to-formdata@>=3.0.0:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/object-to-formdata/-/object-to-formdata-3.0.9.tgz#40e3314522345789259738811c0222b7d6e556e9"
+ integrity sha512-1JDHRFSpk6wzhPBAAqdVncdvbjZ+bjB0tioruNdKn8UyudBBXiBxRa7PJyvYqp4ioEKX98dEOX9Fw1RxA+vLzg==
+
object-visit@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"