heya 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/LICENSE +633 -57
- data/README.md +19 -5
- data/app/models/heya/campaign_membership.rb +81 -0
- data/lib/generators/heya/install/templates/migration.rb.tt +1 -0
- data/lib/heya.rb +1 -42
- data/lib/heya/active_record_extension.rb +37 -0
- data/lib/heya/campaigns/base.rb +9 -18
- data/lib/heya/campaigns/queries.rb +10 -93
- data/lib/heya/campaigns/scheduler.rb +17 -16
- data/lib/heya/config.rb +0 -1
- data/lib/heya/engine.rb +0 -11
- data/lib/heya/version.rb +1 -1
- metadata +8 -11
- data/lib/heya/license.rb +0 -179
- data/lib/heya/license/boundary.rb +0 -64
- data/lib/heya/license/encryptor.rb +0 -122
- data/license_key.pub +0 -9
data/README.md
CHANGED
|
@@ -58,10 +58,10 @@ end
|
|
|
58
58
|
```ruby
|
|
59
59
|
OnboardingCampaign.add(user)
|
|
60
60
|
```
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
Add the following to your `User` model to send them the campaign
|
|
63
63
|
when they first signup:
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
```ruby
|
|
66
66
|
after_create_commit do
|
|
67
67
|
OnboardingCampaign.add(self)
|
|
@@ -353,7 +353,7 @@ To remove a user from a campaign:
|
|
|
353
353
|
OnboardingCampaign.remove(user)
|
|
354
354
|
```
|
|
355
355
|
|
|
356
|
-
Adding users to campaigns from Rails opens up some interesting automation possibilities--for instance, you can start or stop campaigns from `ActiveRecord` callbacks, or in response to other events that you're already tracking in your application. [See here for a list of ideas](#).
|
|
356
|
+
Adding users to campaigns from Rails opens up some interesting automation possibilities--for instance, you can start or stop campaigns from `ActiveRecord` callbacks, or in response to other events that you're already tracking in your application. [See here for a list of ideas](#automation-ideas).
|
|
357
357
|
|
|
358
358
|
Because Heya stacks campaigns by default (meaning it will never send more than one at a time), you can also queue up several campaigns for a user, and they'll receive them in order:
|
|
359
359
|
|
|
@@ -543,6 +543,13 @@ Yep. Use the `restart` option to resend a campaign to a user (if they are alread
|
|
|
543
543
|
Yep. By default, Heya sends campaigns ain order of `priority`. Use the `concurrent` option to send campaigns concurrently.
|
|
544
544
|
</details>
|
|
545
545
|
|
|
546
|
+
## Upgrading Heya
|
|
547
|
+
Heya adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and
|
|
548
|
+
should be considered **unstable** until version 1.0.0. Always check
|
|
549
|
+
[CHANGELOG.md](./CHANGELOG.md) prior to upgrading (breaking changes will always
|
|
550
|
+
be called out there). Upgrade instructions for breaking changes are in
|
|
551
|
+
[UPGRADING.md](./UPGRADING.md).
|
|
552
|
+
|
|
546
553
|
## Roadmap
|
|
547
554
|
See [here](https://github.com/honeybadger-io/heya/projects/1) for things we're
|
|
548
555
|
considering adding to Heya.
|
|
@@ -550,10 +557,17 @@ considering adding to Heya.
|
|
|
550
557
|
## Contributing
|
|
551
558
|
1. Fork it.
|
|
552
559
|
2. Create a topic branch `git checkout -b my_branch`
|
|
553
|
-
3. Make your changes and add an entry to
|
|
560
|
+
3. Make your changes and add an entry to [CHANGELOG.md](CHANGELOG.md).
|
|
554
561
|
4. Commit your changes `git commit -am "Boom"`
|
|
555
562
|
5. Push to your branch `git push origin my_branch`
|
|
556
563
|
6. Send a [pull request](https://github.com/honeybadger-io/heya/pulls)
|
|
557
564
|
|
|
565
|
+
## Releasing
|
|
566
|
+
1. `gem install gem-release`
|
|
567
|
+
2. `gem bump -v [version] -t -r`
|
|
568
|
+
3. Update unreleased heading in [CHANGELOG.md](./CHANGELOG.md) (TODO: automate
|
|
569
|
+
this in gem-release command)
|
|
570
|
+
4. `git push origin master --tags`
|
|
571
|
+
|
|
558
572
|
## License
|
|
559
|
-
|
|
573
|
+
Heya is licensed under the [AGPL](./LICENSE).
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Heya
|
|
2
4
|
class CampaignMembership < ApplicationRecord
|
|
3
5
|
belongs_to :user, polymorphic: true
|
|
@@ -5,5 +7,84 @@ module Heya
|
|
|
5
7
|
before_create do
|
|
6
8
|
self.last_sent_at = Time.now
|
|
7
9
|
end
|
|
10
|
+
|
|
11
|
+
scope :with_steps, -> {
|
|
12
|
+
joins(
|
|
13
|
+
%(INNER JOIN "heya_steps" ON "heya_steps".gid = "heya_campaign_memberships".step_gid)
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
scope :active, -> {
|
|
18
|
+
priority_gids = Heya.config.campaigns.priority.map { |c| (c.is_a?(String) ? c.constantize : c).gid }
|
|
19
|
+
where(<<~SQL, priority_gids: priority_gids)
|
|
20
|
+
"heya_campaign_memberships".concurrent = TRUE
|
|
21
|
+
OR "heya_campaign_memberships"."campaign_gid" IN (
|
|
22
|
+
SELECT
|
|
23
|
+
"active_membership"."campaign_gid"
|
|
24
|
+
FROM
|
|
25
|
+
"heya_campaign_memberships" as "active_membership"
|
|
26
|
+
WHERE
|
|
27
|
+
"active_membership"."concurrent" = FALSE
|
|
28
|
+
AND
|
|
29
|
+
(
|
|
30
|
+
"active_membership".user_type = "heya_campaign_memberships".user_type
|
|
31
|
+
AND
|
|
32
|
+
"active_membership".user_id = "heya_campaign_memberships".user_id
|
|
33
|
+
)
|
|
34
|
+
ORDER BY
|
|
35
|
+
array_position(ARRAY[:priority_gids], "active_membership".campaign_gid::text) ASC,
|
|
36
|
+
"active_membership".created_at ASC
|
|
37
|
+
LIMIT 1
|
|
38
|
+
)
|
|
39
|
+
SQL
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
scope :upcoming, -> {
|
|
43
|
+
with_steps
|
|
44
|
+
.active
|
|
45
|
+
.order(
|
|
46
|
+
Arel.sql(
|
|
47
|
+
%("heya_campaign_memberships".last_sent_at + make_interval(secs := "heya_steps".wait) DESC)
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
scope :to_process, ->(now: Time.now, user: nil) {
|
|
53
|
+
upcoming
|
|
54
|
+
.where(<<~SQL, now: now.utc, user_type: user&.class&.base_class&.name, user_id: user&.id)
|
|
55
|
+
("heya_campaign_memberships".last_sent_at <= (TIMESTAMP :now - make_interval(secs := "heya_steps".wait)))
|
|
56
|
+
AND (
|
|
57
|
+
(:user_type IS NULL OR :user_id IS NULL)
|
|
58
|
+
OR (
|
|
59
|
+
"heya_campaign_memberships".user_type = :user_type
|
|
60
|
+
AND
|
|
61
|
+
"heya_campaign_memberships".user_id = :user_id
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
SQL
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
def self.migrate_next_step!
|
|
68
|
+
find_each do |membership|
|
|
69
|
+
campaign = GlobalID::Locator.locate(membership.campaign_gid)
|
|
70
|
+
receipt = campaign && CampaignReceipt.where(user: membership.user, step_gid: campaign.steps.map(&:gid)).order("created_at desc").first
|
|
71
|
+
|
|
72
|
+
next_step = if receipt
|
|
73
|
+
last_step = GlobalID::Locator.locate(receipt.step_gid)
|
|
74
|
+
current_index = campaign.steps.index(last_step)
|
|
75
|
+
campaign.steps[current_index + 1]
|
|
76
|
+
else
|
|
77
|
+
campaign&.steps&.first
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if next_step
|
|
81
|
+
membership.update(step_gid: next_step.gid)
|
|
82
|
+
else
|
|
83
|
+
membership.destroy
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
CampaignReceipt.where(sent_at: nil).destroy_all
|
|
88
|
+
end
|
|
8
89
|
end
|
|
9
90
|
end
|
|
@@ -4,6 +4,7 @@ class CreateHeyaTables < ActiveRecord::Migration[<%= ActiveRecord::VERSION::MAJO
|
|
|
4
4
|
t.references :user, null: false, polymorphic: true, index: false
|
|
5
5
|
|
|
6
6
|
t.string :campaign_gid, null: false
|
|
7
|
+
t.string :step_gid, null: false
|
|
7
8
|
t.boolean :concurrent, null: false, default: false
|
|
8
9
|
|
|
9
10
|
t.datetime :last_sent_at, null: false
|
data/lib/heya.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "heya/version"
|
|
4
|
+
require "heya/active_record_extension"
|
|
4
5
|
require "heya/engine"
|
|
5
6
|
require "heya/config"
|
|
6
|
-
require "heya/license"
|
|
7
7
|
require "heya/campaigns/action"
|
|
8
8
|
require "heya/campaigns/actions/email"
|
|
9
9
|
require "heya/campaigns/actions/block"
|
|
@@ -46,45 +46,4 @@ module Heya
|
|
|
46
46
|
return user.send(segment) if segment.is_a?(Symbol)
|
|
47
47
|
segment.call(user)
|
|
48
48
|
end
|
|
49
|
-
|
|
50
|
-
def verify_license!
|
|
51
|
-
unless File.file?(config.license_file)
|
|
52
|
-
puts(<<-NOTICE.strip_heredoc)
|
|
53
|
-
This copy of Heya is licensed for non-commercial non-profit, or 30-day trial usage only.
|
|
54
|
-
For a commercial use license, please visit https://www.heya.email
|
|
55
|
-
NOTICE
|
|
56
|
-
return
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
begin
|
|
60
|
-
license = License.import(File.read(config.license_file))
|
|
61
|
-
rescue License::ImportError
|
|
62
|
-
warn(<<-NOTICE.strip_heredoc)
|
|
63
|
-
Your Heya license is invalid.
|
|
64
|
-
If you need support, please visit https://www.heya.email
|
|
65
|
-
NOTICE
|
|
66
|
-
return
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
if license.expired?
|
|
70
|
-
warn(<<-NOTICE.strip_heredoc)
|
|
71
|
-
Your Heya license has expired.
|
|
72
|
-
To update your license, please visit https://www.heya.email
|
|
73
|
-
NOTICE
|
|
74
|
-
return
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
if (max_user_count = license.restrictions[:user_count]&.to_i)
|
|
78
|
-
user_count = config.user_type.constantize.count
|
|
79
|
-
if user_count > max_user_count
|
|
80
|
-
warn(<<-NOTICE.strip_heredoc)
|
|
81
|
-
Your app exceeds the number of users for your Heya license.
|
|
82
|
-
To upgrade your license, please visit https://www.heya.email
|
|
83
|
-
NOTICE
|
|
84
|
-
end
|
|
85
|
-
return # rubocop:disable Style/RedundantReturn
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Valid license
|
|
89
|
-
end
|
|
90
49
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/relation"
|
|
4
|
+
|
|
5
|
+
module Heya
|
|
6
|
+
module ActiveRecordRelationExtension
|
|
7
|
+
TABLE_REGEXP = /heya_steps/
|
|
8
|
+
|
|
9
|
+
def build_arel(aliases)
|
|
10
|
+
arel = super(aliases)
|
|
11
|
+
|
|
12
|
+
if table_name == "heya_campaign_memberships" && arel.to_sql =~ TABLE_REGEXP
|
|
13
|
+
# https://www.postgresql.org/docs/9.4/queries-values.html
|
|
14
|
+
values = Heya
|
|
15
|
+
.campaigns.reduce([]) { |steps, campaign| steps | campaign.steps }
|
|
16
|
+
.map { |step|
|
|
17
|
+
ActiveRecord::Base.sanitize_sql_array(
|
|
18
|
+
["(?, ?)", step.gid, step.wait.to_i]
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if values.any?
|
|
23
|
+
arel.with(
|
|
24
|
+
Arel::Nodes::As.new(
|
|
25
|
+
Arel::Table.new(:heya_steps),
|
|
26
|
+
Arel::Nodes::SqlLiteral.new("(SELECT * FROM (VALUES #{values.join(", ")}) AS heya_steps (gid,wait))")
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
arel
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
ActiveRecord::Relation.prepend(ActiveRecordRelationExtension)
|
|
37
|
+
end
|
data/lib/heya/campaigns/base.rb
CHANGED
|
@@ -41,12 +41,15 @@ module Heya
|
|
|
41
41
|
.delete_all
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
if (step = steps.first)
|
|
45
|
+
membership.create! do |m|
|
|
46
|
+
m.concurrent = concurrent
|
|
47
|
+
m.step_gid = step.gid
|
|
48
|
+
end
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
if send_now && step.wait == 0
|
|
51
|
+
Scheduler.new.run(user: user)
|
|
52
|
+
end
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
true
|
|
@@ -57,18 +60,6 @@ module Heya
|
|
|
57
60
|
true
|
|
58
61
|
end
|
|
59
62
|
|
|
60
|
-
def users
|
|
61
|
-
base_class = user_class.base_class
|
|
62
|
-
user_class
|
|
63
|
-
.joins(
|
|
64
|
-
sanitize_sql_array([
|
|
65
|
-
"inner join heya_campaign_memberships on heya_campaign_memberships.user_type = ? and heya_campaign_memberships.user_id = #{base_class.table_name}.id and heya_campaign_memberships.campaign_gid = ?",
|
|
66
|
-
base_class.name,
|
|
67
|
-
gid
|
|
68
|
-
])
|
|
69
|
-
).all
|
|
70
|
-
end
|
|
71
|
-
|
|
72
63
|
def user_class
|
|
73
64
|
@user_class ||= self.class.user_type.constantize
|
|
74
65
|
end
|
|
@@ -105,7 +96,7 @@ module Heya
|
|
|
105
96
|
instance
|
|
106
97
|
end
|
|
107
98
|
|
|
108
|
-
delegate :steps, :add, :remove, :
|
|
99
|
+
delegate :steps, :add, :remove, :gid, :user_class, :handle_exception, to: :instance
|
|
109
100
|
|
|
110
101
|
def default(**params)
|
|
111
102
|
self.__defaults = __defaults.merge(params).freeze
|
|
@@ -3,97 +3,16 @@
|
|
|
3
3
|
module Heya
|
|
4
4
|
module Campaigns
|
|
5
5
|
module Queries
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
AND m.position > coalesce((SELECT m.position FROM heya_campaign_receipts AS r
|
|
11
|
-
INNER JOIN steps AS m ON m.step_gid = r.step_gid
|
|
12
|
-
AND m.campaign_gid = :campaign_gid
|
|
13
|
-
WHERE r.user_type = heya_campaign_memberships.user_type
|
|
14
|
-
AND r.user_id = heya_campaign_memberships.user_id
|
|
15
|
-
ORDER BY m.position DESC
|
|
16
|
-
LIMIT 1), -1)
|
|
17
|
-
ORDER BY m.position ASC
|
|
18
|
-
LIMIT 1
|
|
19
|
-
) = :step_gid
|
|
20
|
-
SQL
|
|
21
|
-
|
|
22
|
-
ACTIVE_CAMPAIGN_SUBQUERY = <<~SQL
|
|
23
|
-
(WITH heya_campaigns AS (SELECT * FROM (VALUES :campaigns_values) AS campaigns (campaign_gid,position))
|
|
24
|
-
SELECT memberships.campaign_gid FROM heya_campaign_memberships AS memberships
|
|
25
|
-
INNER JOIN heya_campaigns AS campaigns
|
|
26
|
-
ON campaigns.campaign_gid = memberships.campaign_gid
|
|
27
|
-
WHERE memberships.user_type = heya_campaign_memberships.user_type
|
|
28
|
-
AND memberships.user_id = heya_campaign_memberships.user_id
|
|
29
|
-
AND memberships.concurrent = FALSE
|
|
30
|
-
ORDER BY campaigns.position DESC, memberships.created_at ASC
|
|
31
|
-
LIMIT 1
|
|
32
|
-
) = :campaign_gid
|
|
33
|
-
SQL
|
|
34
|
-
|
|
35
|
-
# Given a campaign and a step, {Queries::UsersForStep} returns the
|
|
36
|
-
# users who should complete the step.
|
|
37
|
-
UsersForStep = ->(campaign, step) {
|
|
38
|
-
wait_threshold = Time.now.utc - step.wait
|
|
39
|
-
|
|
40
|
-
# Safeguard to make sure we never complete the same step twice.
|
|
41
|
-
receipt_query = CampaignReceipt
|
|
42
|
-
.select("heya_campaign_receipts.user_id")
|
|
43
|
-
.where(user_type: campaign.user_class.name)
|
|
44
|
-
.where("heya_campaign_receipts.step_gid = ?", step.gid)
|
|
45
|
-
|
|
46
|
-
# https://www.postgresql.org/docs/9.4/queries-values.html
|
|
47
|
-
steps_values = campaign.steps.map { |m|
|
|
48
|
-
ActiveRecord::Base.sanitize_sql_array(
|
|
49
|
-
["(?, ?, ?)", m.gid, campaign.gid, m.position]
|
|
50
|
-
)
|
|
51
|
-
}.join(", ")
|
|
52
|
-
|
|
53
|
-
priority = Heya.config.campaigns.priority.reverse.map { |c| c.is_a?(String) ? c : c.name }
|
|
54
|
-
campaigns_values = Heya.campaigns.map { |c|
|
|
55
|
-
ActiveRecord::Base.sanitize_sql_array(
|
|
56
|
-
["(?, ?)", c.gid, priority.index(c.name) || -1]
|
|
57
|
-
)
|
|
58
|
-
}.join(", ")
|
|
59
|
-
|
|
60
|
-
users = campaign.users
|
|
61
|
-
users
|
|
62
|
-
.where.not(id: receipt_query)
|
|
63
|
-
.where(NEXT_STEP_SUBQUERY.gsub(":steps_values", steps_values), {
|
|
64
|
-
campaign_gid: campaign.gid,
|
|
65
|
-
step_gid: step.gid
|
|
66
|
-
})
|
|
67
|
-
.merge(
|
|
68
|
-
users
|
|
69
|
-
.where("heya_campaign_memberships.concurrent = ?", true)
|
|
70
|
-
.or(
|
|
71
|
-
users.where(ACTIVE_CAMPAIGN_SUBQUERY.gsub(":campaigns_values", campaigns_values), {
|
|
72
|
-
campaign_gid: campaign.gid
|
|
73
|
-
})
|
|
74
|
-
)
|
|
75
|
-
)
|
|
76
|
-
.where(
|
|
77
|
-
"heya_campaign_memberships.last_sent_at <= ?", wait_threshold
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
# Given a campaign and a step, {Queries::UsersCompletedStep}
|
|
82
|
-
# returns the users who have completed the step.
|
|
83
|
-
UsersCompletedStep = ->(campaign, step) {
|
|
84
|
-
receipt_query = CampaignReceipt
|
|
85
|
-
.select("heya_campaign_receipts.user_id")
|
|
86
|
-
.where(user_type: campaign.user_class.name)
|
|
87
|
-
.where("heya_campaign_receipts.step_gid = ?", step.gid)
|
|
88
|
-
|
|
89
|
-
campaign.users
|
|
90
|
-
.where(id: receipt_query)
|
|
6
|
+
# {Queries::MembershipsToProcess} returns the CampaignMembership records
|
|
7
|
+
# which should be processed by the scheduler.
|
|
8
|
+
MembershipsToProcess = ->(user: nil) {
|
|
9
|
+
Heya::CampaignMembership.to_process(user: user)
|
|
91
10
|
}
|
|
92
11
|
|
|
93
|
-
# Given a campaign and a user, {Queries::
|
|
12
|
+
# Given a campaign and a user, {Queries::MembershipsForUpdate}
|
|
94
13
|
# returns the user's campaign memberships which should be updated
|
|
95
14
|
# concurrently.
|
|
96
|
-
|
|
15
|
+
MembershipsForUpdate = ->(campaign, user) {
|
|
97
16
|
membership = CampaignMembership.where(user: user, campaign_gid: campaign.gid).first
|
|
98
17
|
if membership.concurrent?
|
|
99
18
|
CampaignMembership
|
|
@@ -104,14 +23,12 @@ module Heya
|
|
|
104
23
|
end
|
|
105
24
|
}
|
|
106
25
|
|
|
107
|
-
# Given a campaign, {Queries::
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
OrphanedCampaignMemberships = ->(campaign) {
|
|
26
|
+
# Given a campaign, {Queries::OrphanedMemberships} returns the campaign
|
|
27
|
+
# memberships which are on steps have been removed from the campaign.
|
|
28
|
+
OrphanedMemberships = ->(campaign) {
|
|
111
29
|
CampaignMembership
|
|
112
30
|
.where(campaign_gid: campaign.gid)
|
|
113
|
-
.where(
|
|
114
|
-
.where.not(user_id: campaign.users.select("id"))
|
|
31
|
+
.where.not(step_gid: campaign.steps.map(&:gid))
|
|
115
32
|
}
|
|
116
33
|
end
|
|
117
34
|
end
|
|
@@ -11,36 +11,37 @@ module Heya
|
|
|
11
11
|
# 3. Create CampaignReceipt (excludes user in subsequent steps)
|
|
12
12
|
# 4. Process job
|
|
13
13
|
class Scheduler
|
|
14
|
-
def run
|
|
14
|
+
def run(user: nil)
|
|
15
15
|
Heya.campaigns.each do |campaign|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
campaign.steps.each do |step|
|
|
19
|
-
Queries::UsersForStep.call(campaign, step).find_each do |user|
|
|
20
|
-
self.class.process(campaign, step, user)
|
|
21
|
-
end
|
|
16
|
+
if campaign.steps.any?
|
|
17
|
+
Queries::OrphanedMemberships.call(campaign).update_all(step_gid: campaign.steps.first.gid)
|
|
22
18
|
end
|
|
19
|
+
end
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
Queries::MembershipsToProcess.call(user: user).find_each do |membership|
|
|
22
|
+
step = GlobalID::Locator.locate(membership.step_gid)
|
|
23
|
+
campaign = GlobalID::Locator.locate(membership.campaign_gid)
|
|
24
|
+
process(campaign, step, membership.user)
|
|
25
|
+
current_index = campaign.steps.index(step)
|
|
26
|
+
if (next_step = campaign.steps[current_index + 1])
|
|
27
|
+
membership.update(step_gid: next_step.gid)
|
|
28
|
+
else
|
|
29
|
+
membership.destroy
|
|
29
30
|
end
|
|
30
31
|
end
|
|
31
32
|
end
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def process(campaign, step, user)
|
|
34
37
|
ActiveRecord::Base.transaction do
|
|
35
38
|
return if CampaignReceipt.where(user: user, step_gid: step.gid).exists?
|
|
36
39
|
|
|
37
40
|
if step.in_segment?(user)
|
|
38
41
|
now = Time.now.utc
|
|
39
|
-
Queries::
|
|
42
|
+
Queries::MembershipsForUpdate.call(campaign, user).update_all(last_sent_at: now)
|
|
40
43
|
CampaignReceipt.create!(user: user, step_gid: step.gid, sent_at: now)
|
|
41
44
|
step.action.new(user: user, step: step).deliver_later
|
|
42
|
-
else
|
|
43
|
-
CampaignReceipt.create!(user: user, step_gid: step.gid)
|
|
44
45
|
end
|
|
45
46
|
end
|
|
46
47
|
end
|