diff --git a/app/controllers/api/submitter_email_clicks_controller.rb b/app/controllers/api/submitter_email_clicks_controller.rb new file mode 100644 index 00000000..a9d09a28 --- /dev/null +++ b/app/controllers/api/submitter_email_clicks_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Api + class SubmitterEmailClicksController < ApiBaseController + skip_before_action :authenticate_user! + + def create + submitter = Submitter.find_by!(slug: params[:submitter_slug]) + + if params[:t] == SubmissionEvents.build_tracking_param(submitter, 'click_email') + SubmissionEvents.create_with_tracking_data(submitter, 'click_email', request) + end + + render json: {} + end + end +end diff --git a/app/controllers/api/submitter_form_views_controller.rb b/app/controllers/api/submitter_form_views_controller.rb new file mode 100644 index 00000000..ae0d00fd --- /dev/null +++ b/app/controllers/api/submitter_form_views_controller.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Api + class SubmitterFormViewsController < ApiBaseController + skip_before_action :authenticate_user! + + def create + submitter = Submitter.find_by!(slug: params[:submitter_slug]) + + SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request) + + render json: {} + end + end +end diff --git a/app/controllers/submitters_send_email_controller.rb b/app/controllers/submitters_send_email_controller.rb index 8a714537..3dfc1067 100644 --- a/app/controllers/submitters_send_email_controller.rb +++ b/app/controllers/submitters_send_email_controller.rb @@ -8,6 +8,8 @@ class SubmittersSendEmailController < ApplicationController SubmitterMailer.invitation_email(submitter).deliver_later! + SubmissionEvent.create!(submitter:, event_type: 'send_email') + submitter.sent_at ||= Time.current submitter.save! diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index 87f64b9e..19d885b9 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -493,10 +493,44 @@ export default { this.$nextTick(() => { this.recalculateButtonDisabledKey = Math.random() + + this.maybeTrackEmailClick() + this.trackViewForm() }) }, methods: { t, + maybeTrackEmailClick () { + const queryParams = new URLSearchParams(window.location.search) + + if (queryParams.has('t')) { + fetch(this.baseUrl + '/api/submitter_email_clicks', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + t: queryParams.get('t'), + submitter_slug: this.submitterSlug + }) + }) + + queryParams.delete('t') + const newUrl = [window.location.pathname, queryParams.toString()].filter(Boolean).join('?') + window.history.replaceState({}, document.title, newUrl) + } + }, + trackViewForm () { + fetch(this.baseUrl + '/api/submitter_form_views', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + submitter_slug: this.submitterSlug + }) + }) + }, goToStep (step, scrollToArea = false, clickUpload = false) { this.currentStep = this.stepFields.indexOf(step) diff --git a/app/jobs/send_submitter_invitation_email_job.rb b/app/jobs/send_submitter_invitation_email_job.rb index 2bc4fddb..d53863fa 100644 --- a/app/jobs/send_submitter_invitation_email_job.rb +++ b/app/jobs/send_submitter_invitation_email_job.rb @@ -4,6 +4,8 @@ class SendSubmitterInvitationEmailJob < ApplicationJob def perform(submitter) SubmitterMailer.invitation_email(submitter).deliver_now! + SubmissionEvent.create!(submitter:, event_type: 'send_email') + submitter.sent_at ||= Time.current submitter.save end diff --git a/app/models/submission.rb b/app/models/submission.rb index 1283c7b5..830a1343 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -32,6 +32,7 @@ class Submission < ApplicationRecord belongs_to :created_by_user, class_name: 'User', optional: true has_many :submitters, dependent: :destroy + has_many :submission_events, dependent: :destroy serialize :template_fields, JSON serialize :template_schema, JSON diff --git a/app/models/submission_event.rb b/app/models/submission_event.rb new file mode 100644 index 00000000..3756b382 --- /dev/null +++ b/app/models/submission_event.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: submission_events +# +# id :bigint not null, primary key +# data :text not null +# event_timestamp :datetime not null +# event_type :string not null +# created_at :datetime not null +# updated_at :datetime not null +# submission_id :bigint not null +# submitter_id :bigint +# +# Indexes +# +# index_submission_events_on_submission_id (submission_id) +# index_submission_events_on_submitter_id (submitter_id) +# +# Foreign Keys +# +# fk_rails_... (submission_id => submissions.id) +# fk_rails_... (submitter_id => submitters.id) +# +class SubmissionEvent < ApplicationRecord + belongs_to :submission + belongs_to :submitter, optional: true + + attribute :data, :string, default: -> { {} } + attribute :event_timestamp, :datetime, default: -> { Time.current } + + serialize :data, JSON + + before_validation :set_submission_id, on: :create + + enum :event_type, { + send_email: 'send_email', + send_sms: 'send_sms', + open_email: 'open_email', + click_email: 'click_email', + click_sms: 'click_sms', + start_form: 'start_form', + view_form: 'view_form', + complete_form: 'complete_form' + }, scope: false + + private + + def set_submission_id + self.submission_id = submitter&.submission_id + end +end diff --git a/app/models/submitter.rb b/app/models/submitter.rb index e5a43573..f95eea33 100644 --- a/app/models/submitter.rb +++ b/app/models/submitter.rb @@ -44,6 +44,7 @@ class Submitter < ApplicationRecord has_many_attached :attachments has_many :document_generation_events, dependent: :destroy + has_many :submission_events, dependent: :destroy def status if completed_at? diff --git a/app/views/submitter_mailer/invitation_email.html.erb b/app/views/submitter_mailer/invitation_email.html.erb index 4f1048e0..a9ca8e26 100644 --- a/app/views/submitter_mailer/invitation_email.html.erb +++ b/app/views/submitter_mailer/invitation_email.html.erb @@ -3,7 +3,7 @@ <% else %>
Hi there,
<%= simple_format(@message) %> -<%= link_to 'Submit Form', submit_form_url(slug: @submitter.slug) %>
+<%= link_to 'Submit Form', submit_form_url(slug: @submitter.slug, t: SubmissionEvents.build_tracking_param(@submitter, 'click_email')) %>
Please contact us by replying to this email if you didn't request this.
Thanks,
<%= @current_account.name %>
diff --git a/config/routes.rb b/config/routes.rb
index 688a5464..d49f50fe 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -31,6 +31,8 @@ Rails.application.routes.draw do
namespace :api, defaults: { format: :json } do
resources :attachments, only: %i[create]
+ resources :submitter_email_clicks, only: %i[create]
+ resources :submitter_form_views, only: %i[create]
resources :submissions, only: %i[create]
resources :templates, only: %i[update show index] do
resources :submissions, only: %i[create]
diff --git a/db/migrate/20230910084410_create_submission_events.rb b/db/migrate/20230910084410_create_submission_events.rb
new file mode 100644
index 00000000..4423139a
--- /dev/null
+++ b/db/migrate/20230910084410_create_submission_events.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class CreateSubmissionEvents < ActiveRecord::Migration[7.0]
+ def change
+ create_table :submission_events do |t|
+ t.references :submission, null: false, foreign_key: true, index: true
+ t.references :submitter, null: true, foreign_key: true, index: true
+ t.text :data, null: false
+ t.string :event_type, null: false
+ t.datetime :event_timestamp, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c16b0b99..0ecb94c8 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_09_09_213212) do
+ActiveRecord::Schema[7.0].define(version: 2023_09_10_084410) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -91,6 +91,18 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_09_213212) do
t.index ["account_id"], name: "index_encrypted_configs_on_account_id"
end
+ create_table "submission_events", force: :cascade do |t|
+ t.bigint "submission_id", null: false
+ t.bigint "submitter_id"
+ t.text "data", null: false
+ t.string "event_type", null: false
+ t.datetime "event_timestamp", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["submission_id"], name: "index_submission_events_on_submission_id"
+ t.index ["submitter_id"], name: "index_submission_events_on_submitter_id"
+ end
+
create_table "submissions", force: :cascade do |t|
t.bigint "template_id", null: false
t.bigint "created_by_user_id"
@@ -178,6 +190,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_09_213212) do
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "document_generation_events", "submitters"
add_foreign_key "encrypted_configs", "accounts"
+ add_foreign_key "submission_events", "submissions"
+ add_foreign_key "submission_events", "submitters"
add_foreign_key "submissions", "templates"
add_foreign_key "submissions", "users", column: "created_by_user_id"
add_foreign_key "submitters", "submissions"
diff --git a/lib/replace_email_variables.rb b/lib/replace_email_variables.rb
index d6f88f64..0a4e6e3b 100644
--- a/lib/replace_email_variables.rb
+++ b/lib/replace_email_variables.rb
@@ -45,7 +45,9 @@ module ReplaceEmailVariables
def build_submitter_link(submitter)
Rails.application.routes.url_helpers.submit_form_url(
- slug: submitter.slug, **Docuseal.default_url_options
+ slug: submitter.slug,
+ t: SubmissionEvents.build_tracking_param(submitter, 'click_email'),
+ **Docuseal.default_url_options
)
end
diff --git a/lib/submission_events.rb b/lib/submission_events.rb
new file mode 100644
index 00000000..a804a5f3
--- /dev/null
+++ b/lib/submission_events.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module SubmissionEvents
+ TRACKING_PARAM_LENGTH = 6
+
+ module_function
+
+ def build_tracking_param(submitter, event_type = 'click_email')
+ Base64.urlsafe_encode64(
+ [submitter.slug, event_type, Rails.application.secrets.secret_key_base].join(':')
+ ).first(TRACKING_PARAM_LENGTH)
+ end
+
+ def create_with_tracking_data(submitter, event_type, request)
+ SubmissionEvent.create!(submitter:, event_type:, data: {
+ ip: request.remote_ip,
+ ua: request.user_agent,
+ sid: request.session.id.to_s,
+ uid: request.env['warden'].user(:user)&.id
+ }.compact_blank)
+ end
+end
diff --git a/lib/submitters/submit_values.rb b/lib/submitters/submit_values.rb
index 8385d81c..a8e79adb 100644
--- a/lib/submitters/submit_values.rb
+++ b/lib/submitters/submit_values.rb
@@ -7,7 +7,11 @@ module Submitters
module_function
def call(submitter, params, request)
- Submissions.update_template_fields!(submitter.submission) if submitter.submission.template_fields.blank?
+ if submitter.submission.template_fields.blank?
+ Submissions.update_template_fields!(submitter.submission)
+
+ SubmissionEvents.create_with_tracking_data(submitter, 'start_form', request)
+ end
update_submitter!(submitter, params, request)
@@ -32,6 +36,8 @@ module Submitters
submitter.completed_at = Time.current
submitter.ip = request.remote_ip
submitter.ua = request.user_agent
+
+ SubmissionEvents.create_with_tracking_data(submitter, 'complete_form', request)
end
submitter.save!