add submission tracking events
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
@@ -8,6 +8,8 @@ class SubmittersSendEmailController < ApplicationController
|
|||||||
|
|
||||||
SubmitterMailer.invitation_email(submitter).deliver_later!
|
SubmitterMailer.invitation_email(submitter).deliver_later!
|
||||||
|
|
||||||
|
SubmissionEvent.create!(submitter:, event_type: 'send_email')
|
||||||
|
|
||||||
submitter.sent_at ||= Time.current
|
submitter.sent_at ||= Time.current
|
||||||
submitter.save!
|
submitter.save!
|
||||||
|
|
||||||
|
|||||||
@@ -493,10 +493,44 @@ export default {
|
|||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.recalculateButtonDisabledKey = Math.random()
|
this.recalculateButtonDisabledKey = Math.random()
|
||||||
|
|
||||||
|
this.maybeTrackEmailClick()
|
||||||
|
this.trackViewForm()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
t,
|
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) {
|
goToStep (step, scrollToArea = false, clickUpload = false) {
|
||||||
this.currentStep = this.stepFields.indexOf(step)
|
this.currentStep = this.stepFields.indexOf(step)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ class SendSubmitterInvitationEmailJob < ApplicationJob
|
|||||||
def perform(submitter)
|
def perform(submitter)
|
||||||
SubmitterMailer.invitation_email(submitter).deliver_now!
|
SubmitterMailer.invitation_email(submitter).deliver_now!
|
||||||
|
|
||||||
|
SubmissionEvent.create!(submitter:, event_type: 'send_email')
|
||||||
|
|
||||||
submitter.sent_at ||= Time.current
|
submitter.sent_at ||= Time.current
|
||||||
submitter.save
|
submitter.save
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class Submission < ApplicationRecord
|
|||||||
belongs_to :created_by_user, class_name: 'User', optional: true
|
belongs_to :created_by_user, class_name: 'User', optional: true
|
||||||
|
|
||||||
has_many :submitters, dependent: :destroy
|
has_many :submitters, dependent: :destroy
|
||||||
|
has_many :submission_events, dependent: :destroy
|
||||||
|
|
||||||
serialize :template_fields, JSON
|
serialize :template_fields, JSON
|
||||||
serialize :template_schema, JSON
|
serialize :template_schema, JSON
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -44,6 +44,7 @@ class Submitter < ApplicationRecord
|
|||||||
has_many_attached :attachments
|
has_many_attached :attachments
|
||||||
|
|
||||||
has_many :document_generation_events, dependent: :destroy
|
has_many :document_generation_events, dependent: :destroy
|
||||||
|
has_many :submission_events, dependent: :destroy
|
||||||
|
|
||||||
def status
|
def status
|
||||||
if completed_at?
|
if completed_at?
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<% else %>
|
<% else %>
|
||||||
<p>Hi there,</p>
|
<p>Hi there,</p>
|
||||||
<%= simple_format(@message) %>
|
<%= simple_format(@message) %>
|
||||||
<p><%= link_to 'Submit Form', submit_form_url(slug: @submitter.slug) %></p>
|
<p><%= link_to 'Submit Form', submit_form_url(slug: @submitter.slug, t: SubmissionEvents.build_tracking_param(@submitter, 'click_email')) %></p>
|
||||||
<p>Please contact us by replying to this email if you didn't request this.</p>
|
<p>Please contact us by replying to this email if you didn't request this.</p>
|
||||||
<p>
|
<p>
|
||||||
Thanks,<br><%= @current_account.name %>
|
Thanks,<br><%= @current_account.name %>
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
namespace :api, defaults: { format: :json } do
|
namespace :api, defaults: { format: :json } do
|
||||||
resources :attachments, only: %i[create]
|
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 :submissions, only: %i[create]
|
||||||
resources :templates, only: %i[update show index] do
|
resources :templates, only: %i[update show index] do
|
||||||
resources :submissions, only: %i[create]
|
resources :submissions, only: %i[create]
|
||||||
|
|||||||
@@ -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
|
||||||
+15
-1
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
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"
|
t.index ["account_id"], name: "index_encrypted_configs_on_account_id"
|
||||||
end
|
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|
|
create_table "submissions", force: :cascade do |t|
|
||||||
t.bigint "template_id", null: false
|
t.bigint "template_id", null: false
|
||||||
t.bigint "created_by_user_id"
|
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 "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||||
add_foreign_key "document_generation_events", "submitters"
|
add_foreign_key "document_generation_events", "submitters"
|
||||||
add_foreign_key "encrypted_configs", "accounts"
|
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", "templates"
|
||||||
add_foreign_key "submissions", "users", column: "created_by_user_id"
|
add_foreign_key "submissions", "users", column: "created_by_user_id"
|
||||||
add_foreign_key "submitters", "submissions"
|
add_foreign_key "submitters", "submissions"
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ module ReplaceEmailVariables
|
|||||||
|
|
||||||
def build_submitter_link(submitter)
|
def build_submitter_link(submitter)
|
||||||
Rails.application.routes.url_helpers.submit_form_url(
|
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
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -7,7 +7,11 @@ module Submitters
|
|||||||
module_function
|
module_function
|
||||||
|
|
||||||
def call(submitter, params, request)
|
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)
|
update_submitter!(submitter, params, request)
|
||||||
|
|
||||||
@@ -32,6 +36,8 @@ module Submitters
|
|||||||
submitter.completed_at = Time.current
|
submitter.completed_at = Time.current
|
||||||
submitter.ip = request.remote_ip
|
submitter.ip = request.remote_ip
|
||||||
submitter.ua = request.user_agent
|
submitter.ua = request.user_agent
|
||||||
|
|
||||||
|
SubmissionEvents.create_with_tracking_data(submitter, 'complete_form', request)
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter.save!
|
submitter.save!
|
||||||
|
|||||||
Reference in New Issue
Block a user