Giter Club home page Giter Club logo

heya's People

Contributors

800a7b32 avatar acallaghan avatar dependabot-preview[bot] avatar dependabot[bot] avatar depfu[bot] avatar feliperaul avatar hirowatari avatar joshuap avatar montekaka avatar rathboma avatar retsef avatar stympy avatar subzero10 avatar yenwod avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

heya's Issues

Support for multitenant schemas in Active Record extension

This is an issue that was originally reported in #145 and I want to document it here for future users. PRs are welcome.

TLDR: if you're getting a similar error (see below), check to see if you're using a gem that is using custom schemas, such as the apartment gem. Heya does not currently support schemas.

Here's the error message that you may encounter if you're running into this issue:

$ rails heya:scheduler 
rails aborted!
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  relation "heya_steps" does not exist
LINE 1: ...M "public"."heya_campaign_memberships" INNER JOIN "heya_step...
                                                             ^
Caused by:
PG::UndefinedTable: ERROR:  relation "heya_steps" does not exist
LINE 1: ...M "public"."heya_campaign_memberships" INNER JOIN "heya_step...
                                                             ^
Tasks: TOP => heya:scheduler
(See full trace by running task with --trace)

Here's one possible explanation:

It could be that I'm using the Apartment gem for multi-tenant schemas, I think the two are conflicting - the "public"."heya_campaign_memberships" table in the public schema, but the "heya_steps" table is being defined without a schema. I think this is it anyway.

How to deal with multiple user models?

Hey folks,

I have both Customer and User models, each represents a different type of user (I know, I know).

The docs let me set a different user model, but is there a way to use heya with two user models at the same time?

Something like this?

# config/initializers/heya.rb
Heya.configure do |config|
  config.user_types = ["MyUser", "MyCustomer"]
end

Defining email subject in locale files throws exception on Rails 7 startup

Hi,

I'm trying to implement Heya in my app running Rails 7.0.6. In setting up campaigns, I've opted to put my email subjects in translation files, as suggested in the docs. However, when I do this, the validation for a subject throws an error during the Rails boot up:

/Users/michael/.asdf/installs/ruby/3.2.1/lib/ruby/gems/3.2.0/bundler/gems/heya-c6dee57eb6e4/lib/heya/campaigns/actions/email.rb:12:in `validate_step': "subject" is required (ArgumentError)

            raise ArgumentError.new(%("subject" is required))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Putting a debugger in campaigns/actions/email.rb, I found that at the time that validation is happening, Rails hasn't yet loaded the locale files. As a result this, on line 11 of that file, returns false:

I18n.exists?("#{step.campaign_name.underscore}.#{step.name.underscore}.subject")

I know my locales do eventually get loaded, because if I comment out the steps of my campaigns and open up a Rails console, that same line above returns true at that point.

Of course I can define the subjects in line with my steps, but I'd rather keep everything in locale files.

Depfu Error: No dependency files found

Hello,

We've tried to activate or update your repository on Depfu and couldn't find any supported dependency files. If we were to guess, we would say that this is not actually a project Depfu supports and has probably been activated by error.

Monorepos

Please note that Depfu currently only searches for your dependency files in the root folder. We do support monorepos and non-root files, but don't auto-detect them. If that's the case with this repo, please send us a quick email with the folder you want Depfu to work on and we'll set it up right away!

How to deactivate the project

  • Go to the Settings page of either your own account or the organization you've used
  • Go to "Installed Integrations"
  • Click the "Configure" button on the Depfu integration
  • Remove this repo (honeybadger-io/heya) from the list of accessible repos.

Please note that using the "All Repositories" setting doesn't make a lot of sense with Depfu.

If you think that this is a mistake

Please let us know by sending an email to [email protected].


This is an automated issue by Depfu. You're getting it because someone configured Depfu to automatically update dependencies on this project.

Install generator fails if a campaign already exists

I installed heya in a new project but ran rails g heya:campaign before rails g heya:install (so a campaign was created first). Subsequently running the install generator produced the following error:

ฮป  app heya โœ“ rails g heya:install
/Users/josh/Code/hook-relay/app/app/campaigns/signup_onboarding_campaign.rb:1:in `<main>': uninitialized constant ApplicationCampaign
Did you mean?  ApplicationNotification (NameError)
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/zeitwerk-2.4.0/lib/zeitwerk/kernel.rb:34:in `require'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/dependencies/zeitwerk_integration.rb:49:in `require_dependency'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/heya-0.3.0/lib/heya/engine.rb:15:in `block (2 levels) in <class:Engine>'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/heya-0.3.0/lib/heya/engine.rb:14:in `each'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/heya-0.3.0/lib/heya/engine.rb:14:in `block in <class:Engine>'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:428:in `instance_exec'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:428:in `block in make_lambda'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:200:in `block (2 levels) in halting'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:605:in `block (2 levels) in default_terminator'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:604:in `catch'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:604:in `block in default_terminator'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:201:in `block in halting'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:513:in `block in invoke_before'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:513:in `each'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:513:in `invoke_before'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:134:in `run_callbacks'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/reloader.rb:88:in `prepare!'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/reloader.rb:47:in `block in <class:Reloader>'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:428:in `instance_exec'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:428:in `block in make_lambda'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:273:in `block in simple'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:517:in `block in invoke_after'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:517:in `each'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:517:in `invoke_after'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/callbacks.rb:136:in `run_callbacks'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/execution_wrapper.rb:111:in `run!'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/reloader.rb:114:in `run!'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/reloader.rb:53:in `block (2 levels) in reload!'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/reloader.rb:52:in `tap'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/reloader.rb:52:in `block in reload!'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/execution_wrapper.rb:88:in `wrap'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/reloader.rb:51:in `reload!'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/spring-2.1.1/lib/spring/application.rb:168:in `serve'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/spring-2.1.1/lib/spring/application.rb:145:in `block in run'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/spring-2.1.1/lib/spring/application.rb:139:in `loop'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/spring-2.1.1/lib/spring/application.rb:139:in `run'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/spring-2.1.1/lib/spring/application/boot.rb:19:in `<top (required)>'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:72:in `require'
	from /Users/josh/.asdf/installs/ruby/2.7.1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:72:in `require'
	from -e:1:in `<main>'

Url helpers returning NameError

First of all, great gem! Really enjoyed exploring the code.

I'm facing NameErrors on every single url helper I try to use inside a template.

For instance, a simple <% link_to "Home", root_url %> returns undefined local variable or method root_url' for #ActionView::Base:0x0000000005f668`.

Maybe Heya::Campaigns::Base should include Rails.application.routes.url_helpers ?

Thanks!

Require default & campaign segments to match when adding users

You don't want to add a user to a campaign if they don't match at least the default_segment and the campaign segment (which means they aren't eligible for messages anyway). Campaigns::Base#add should check these segments and return false if the user doesn't match. This will simplify adding new users to campaigns because you can do i.e. OnboardingCampaign.add(user) without checking twice that the user should actually receive the campaign (the segments can do that).

Send first email immediately when user is added

With a minimum scheduler interval of 10 minutes, it can take up to 10 minutes for the first email to arrive even though for welcome emails you want them to be sent immediately. I think I could send the first email immediately in Heya::Campaigns::Base#add when wait == 0.

New install error: relation "heya_steps" does not exist

Hey there, I've been integrating Heya with our app, and came across this issue. I have added the gem and run the installation commands and migration. I then setup my first campaign.

However, when I run the campaign scheduling rake task, I get this error:

$ rails heya:scheduler 
rails aborted!
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  relation "heya_steps" does not exist
LINE 1: ...M "public"."heya_campaign_memberships" INNER JOIN "heya_step...
                                                             ^
Caused by:
PG::UndefinedTable: ERROR:  relation "heya_steps" does not exist
LINE 1: ...M "public"."heya_campaign_memberships" INNER JOIN "heya_step...
                                                             ^

Tasks: TOP => heya:scheduler
(See full trace by running task with --trace)

A --trace led me to the lines:

lib/heya/campaigns/scheduler.rb:21:in `run'
lib/tasks/heya_tasks.rake:6:in `block (2 levels) in <main>'

It looks like these files are referring to a table called heya_steps, and referenced in a few places like:

scope :with_steps, -> {
  joins(
    %(INNER JOIN "heya_steps" ON "heya_steps".gid = "heya_campaign_memberships".step_gid)
  )
}

This table isn't in the generator for the installation migration, so not sure where this should come from? Any ideas would be welcome!

I'm using Rails 6.1, Ruby 3.0.1 and postrges 13.3

Thanks in advance,
Andy

Concurrent campaigns

If you want to send a user two campaigns simultaneously, you can do so with the concurrent option:

FlashSaleCampaign.add(user, concurrent: true)

Install generator

With the introduction of Heya.configure in #18 and #19, we should generate a default config file at config/initializers/heya.rb when installing, in addition to copying migrations. From the docs:

  1. Then execute:
bundle
rails heya:install db:migrate

This will do 3 things:

  1. Copy Heya's migration files to db/migrate
  2. Copy Heya's default initializer to config/initializers/heya.rb
  3. Run local migrations

Test failure on master: email_address_with_name

It looks like Rails < 6.1 does not support email_address_with_name. I'm not sure why the tests didn't run on the PR (I thought they had).

@feliperaul any idea of the best way to fix this?

Error:
[175](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:175)
Heya::Campaigns::Actions::EmailTest#test_to_field:
[176](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:176)
NoMethodError: undefined method `email_address_with_name' for #<Heya::CampaignMailer:0x0000555d9c176228>
[177](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:177)
    /home/runner/work/heya/heya/app/mailers/heya/campaign_mailer.rb:60:in `to_address'
[178](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:178)
    /home/runner/work/heya/heya/app/mailers/heya/campaign_mailer.rb:29:in `build'
[179](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:179)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionpack-6.0.4.4/lib/abstract_controller/base.rb:195:in `process_action'
[180](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:180)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionpack-6.0.4.4/lib/abstract_controller/callbacks.rb:42:in `block in process_action'
[181](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:181)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.4.4/lib/active_support/callbacks.rb:101:in `run_callbacks'
[182](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:182)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionpack-6.0.4.4/lib/abstract_controller/callbacks.rb:41:in `process_action'
[183](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:183)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionpack-6.0.4.4/lib/abstract_controller/base.rb:136:in `process'
[184](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:184)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionmailer-6.0.4.4/lib/action_mailer/rescuable.rb:25:in `block in process'
[185](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:185)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionmailer-6.0.4.4/lib/action_mailer/rescuable.rb:17:in `handle_exceptions'
[186](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:186)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionmailer-6.0.4.4/lib/action_mailer/rescuable.rb:24:in `process'
[187](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:187)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionview-6.0.4.4/lib/action_view/rendering.rb:39:in `process'
[188](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:188)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionmailer-6.0.4.4/lib/action_mailer/base.rb:637:in `block in process'
[189](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:189)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.4.4/lib/active_support/notifications.rb:180:in `block in instrument'
[190](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:190)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.4.4/lib/active_support/notifications/instrumenter.rb:24:in `instrument'
[191](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:191)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.4.4/lib/active_support/notifications.rb:180:in `instrument'
[192](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:192)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionmailer-6.0.4.4/lib/action_mailer/base.rb:636:in `process'
[193](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:193)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionmailer-6.0.4.4/lib/action_mailer/parameterized.rb:143:in `block in processed_mailer'
[194](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:194)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionmailer-6.0.4.4/lib/action_mailer/parameterized.rb:141:in `tap'
[195](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:195)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionmailer-6.0.4.4/lib/action_mailer/parameterized.rb:141:in `processed_mailer'
[196](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:196)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionmailer-6.0.4.4/lib/action_mailer/message_delivery.rb:30:in `__getobj__'
[197](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:197)
    /opt/hostedtoolcache/Ruby/2.7.5/x64/lib/ruby/2.7.0/delegate.rb:80:in `method_missing'
[198](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:198)
    /home/runner/work/heya/heya/test/lib/heya/campaigns/actions/email_test.rb:36:in `block (2 levels) in <class:EmailTest>'
[199](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:199)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/activejob-6.0.4.4/lib/active_job/test_helper.rb:584:in `perform_enqueued_jobs'
[200](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:200)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/actionmailer-6.0.4.4/lib/action_mailer/test_helper.rb:37:in `assert_emails'
[201](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:201)
    /home/runner/work/heya/heya/test/lib/heya/campaigns/actions/email_test.rb:35:in `block in <class:EmailTest>'
[202](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:202)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest/test.rb:98:in `block (3 levels) in run'
[203](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:203)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest/test.rb:195:in `capture_exceptions'
[204](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:204)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest/test.rb:95:in `block (2 levels) in run'
[205](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:205)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:281:in `time_it'
[206](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:206)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest/test.rb:94:in `block in run'
[207](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:207)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:376:in `on_signal'
[208](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:208)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest/test.rb:221:in `with_info_handler'
[209](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:209)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest/test.rb:93:in `run'
[210](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:210)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:1042:in `run_one_method'
[211](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:211)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:350:in `run_one_method'
[212](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:212)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:337:in `block (2 levels) in run'
[213](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:213)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:336:in `each'
[214](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:214)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:336:in `block in run'
[215](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:215)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:376:in `on_signal'
[216](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:216)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:363:in `with_info_handler'
[217](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:217)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:335:in `run'
[218](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:218)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/railties-6.0.4.4/lib/rails/test_unit/line_filtering.rb:10:in `run'
[219](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:219)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:169:in `block in __run'
[220](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:220)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:169:in `map'
[221](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:221)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:169:in `__run'
[222](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:222)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:146:in `run'
[223](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:223)
    /home/runner/work/heya/heya/gemfiles/vendor/bundle/ruby/2.7.0/gems/minitest-5.15.0/lib/minitest.rb:73:in `block in autorun'
[224](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:224)

[225](https://github.com/honeybadger-io/heya/runs/5043231699?check_suite_focus=true#step:7:225)
rails test /home/runner/work/heya/heya/test/lib/heya/campaigns/actions/email_test.rb:25

Reduce the number of queries in scheduler

Every time the scheduler runs, it executes 1 query per message to find the contacts that should receive the message. These queries contain the bulk of the scheduling logic. If we could determine which messages have results before running them, we could skip checking the messages that don't. For instance, if the scheduler runs every 5 minutes, but the delay for the message is 1 hour, then we may not need to check it that frequently. I'm not sure if this will work in practice. :)

cannot use with rails 7

@joshuap

Bundler could not find compatible versions for gem "rails":
  In Gemfile:
    rails (~> 7.0.0)

    heya was resolved to 0.6.0, which depends on
      rails (>= 5.2.3, < 6.2.0)

More customised scheduling

Hey, given all of our customers are in the same timezone and we know the best times to message people for an optimal response rate, is it possible to have all campaign messages delivered within specific windows (or at least queued within those windows).

For example, we're happy emails being queued/sent from 9am-10am Tuesday to Thursday, but sending one of our customers/prospects an email at 10pm on a Friday will be either ignored or worst case cause them to potentially unsubscribe.

Wondered if this use-case had been considered.

Enhancement

I noticed that the migrations are tied to a User and that the initializer asks for which object will be used on the campaigns. In my case, the users will send campaigns to Contacts. It would be nice to be able to specify in the generator the resource that will be used so the migration is created correctly, something like rails generate heya:install -r Contact

Perform actions with ActiveJob

The scheduler currently processes actions synchronously, but I want to process them with Sidekiq. Hopefully I can just integrate with ActiveJob so that people can use whatever background worker they're already using.

Question: permantent / evergreen campaigns, AKA how to not destroy the membership at the last step?

Imagine you have an EvergreenCampaign that you subscribe your new users.

You want to keep adding content to the bottom of this campaign, and make so that all your current users also receive it.

However, subscribers memberships are removed when they reach the last step.

Since this is an EverGreen campaign (one that you probably subscribed with concurrent: true), how to make so that nobody gets unsubscribed at the end, being permanently in the state of "waiting for the next step" and, if one new step is created, existing users receive it?

Remove people from campaigns when they no longer match campaign segments

If they don't match campaign segments, they won't match any steps. Removing them early means that if they get re-added to a campaign once the do match again, they'll continue to receive emails where they left off. Currently, the system will just skip every step until they exit the campaign anyway.

How to use a mail helper?

In ActiveMailer I use helper to specify some helpers I can use inside my templates, like this:

class ApplicationMailer < ActionMailer::Base
  layout 'mailer'
  
  helper :email
end

How do I do this in Heya?

Campaigns::Base shouldn't eager-load database records

The engine currently loads app/campaigns/*.rb when the app boots:

https://github.com/honeybadger-io/heya/blob/master/lib/heya/engine.rb#L5

In turn, each campaign will find or create the campaign and message rows that are needed to track message receipts:

https://github.com/honeybadger-io/heya/blob/master/lib/heya/campaigns/base.rb

This causes commands to fail when the tables haven't been created yet (i.e., when running migrations).

I did this so that the campaigns and messages would be immediately available in dev, since otherwise campaigns a required lazily, when they are first referenced.

There's probably a saner way to handle all of this (for instance, maybe the campaigns/messages shouldn't be created until the scheduler runs).

Document opt-outs w/ mailkick

We don't have built-in opt-outs, but it's super easy to add with the mailkick gem.

A related note to self: I'm wondering if there's a way to customize the Campaigns::Base#user SQL from the Rails application/campaign so that I can optionally exclude unsubscribed users in scheduler queries. The difference there vs. segment is that they won't move through campaigns at all if they're unsubscribed, and if they resubscribe they'd pick up exactly where they left off.

Symbolized segments

From the new docs:

class ActivationCampaign < Heya::Campaigns::Base
  step :activate, segment: ->(user) { user.inactive? }
end

When you're checking the value of a single method on the user, the segment can be simplified to the symbol version:

class ActivationCampaign < Heya::Campaigns::Base
  step :activate, segment: :inactive?
end

When the segment is a symbol, we should call the method directly on the user instead of calling it as a proc.

Global user type config

From the docs:

Note: Heya doesn't store a copy of your user data; instead, it reads from your existing User model (it never writes). If you have a different user model, change the user_type configuration option in config/initializers/heya.rb.

# config/initializers/heya.rb
Heya.configure do |config|
  config.user_type = 'MyUser'
end

Add params to campaign memberships

Given a campaign TrialConversionCampaign, I want to be able to specify a trial end date when adding users to campaigns, so that I can reference the date in campaign emails (i.e. "Your trial is ending on 2020-02-26"). These values are unique to each user in the campaign, but may not be persisted on the user object--in this case, the value is part of an event that is triggering the campaign for the user. It would be nice to be able to add/override params like this:

TrialConversionCampaign.add(user, params: {
  ending_on: "2020-02-26"
})

I think this could be accomplished by adding a params jsonb/hstore/etc. column to the heya_campaign_memberships table. A nice extra would be to transparently support Global IDs for referencing objects.

Add broadcasts

We have some code that we use at Honeybadger for sending broadcasts with Heya:

https://gist.github.com/stympy/3d76041365a61085ddcf01a3461f65f1

We should bring that into Heya, and also have a web UI for sending broadcasts... perhaps a mountable app like sidekiq has. Maybe a basic V1 could just have a text box for taking the markdown content of the email, and then some way to specify a list of recipients. An ActiveRecord query to run via eval? A list of user ids? Suggestions are welcome. :)

Snooze emails for N days

In addition to an unsubscribe option, a snooze link would allow people who don't want to hear from you right now (i.e. because they are sick, on vacation, or dealing with a global economic crisis...) to postpone emails for a number of days/weeks/months.

Similar to #39, this could be accomplished by a mailkick-like addon, and/or overriding Campaigns::Base#user.

`WARN -- : Scoped order is ignored, it's forced to be batch order` when running running `bin/rails heya:scheduler`

Is it a problem that I am getting this error when running bin/rails heya:scheduler:

WARN -- : Scoped order is ignored, it's forced to be batch order

Or can I ignore that error message? I currently have an active campaign that had an issue with one of the campaign views, so no one has received an email in over a week.

When I try to run them manually with bin/rails heya:scheduler it appears that nothing is sending?

What happens when you have an active campaign and the mailer can't send after you fix the issue? Will it skip a step and pick up with the next one or should it send the backlogged step?

Trying to figure out where to start troubleshooting this issue on production.

Campaign generator

From the docs:

rails generate heya:campaign "User Onboarding" first second third

This will do three things:

  • Create the file app/campaigns/user_onboarding_campaign.rb
  • Create the directory app/views/heya/campaign_mailer/user_onboarding_campaign/
  • Create email templates inside of app/views/heya/campaign_mailer/user_onboarding_campaign/

Here's the campaign that the above command generates:

class UserOnboardingCampaign < Heya::Campaigns::Base
  step :first, wait: 0.days,
    subject: 'First subject',

  step :second,
    subject: 'Second subject',

  step :third,
    subject: 'Third subject'
end

I'm not sure how the User concern should be added to the app's User model yet.

Campaigns UI

  1. I think that one of the more powerful aspects of Heya is the ability to add users to campaigns anywhere in the Rails app, in response to any event (bypassing the complex rules that must be created in traditional UIs).
  2. For that to work, each campaign must be represented in the code via a unique class. Currently, the only way to create a campaign is by creating such a class, which is great.
  3. I have been considering how we might allow users to also (optionally) create campaigns from a UI. The benefit of a UI would be that you could edit the campaigns without deploying the app.

Here's an idea that just occurred to me: we could require users to deploy the app when creating campaigns, but then allow them to manage messages and other features from the UI. For instance, instead of creating steps inside the campaign, you could declare that this campaign will be managed elsewhere:

class SignupFirstRunCampaign < Heya::Campaigns::UI
  # The step method will not work here. It will simply create the campaign. Messages must be added in the UI.
end

Campaigns created in this way could be paused by default, requiring you to activate them via the UI to start sending.

Campaigns that are managed in Ruby would likewise be locked from editing via the UI.

Option for a strong copy-left license like the AGPL instead of PPL?

Hey all, I work on the Houdini Project, a nonprofit online fundraising suite built on Ruby on Rails. I was super excited finding a Gem that could use for nonprofit email campaigns like Heya. Unfortunately, your license prevents us from doing so. We use the Affero GPL license for our work which requires that anyone making an instance of the Houdini Project available via a network provide the corresponding code under the AGPL or a compatible license. The AGPL doesn't have restrictions for commercial use, like all free software licenses recognized by the Free Software Foundation or open source licenses recognized by the Open Source Initiative, so we can't use Heya. (I'd be happy to get you in touch with the President of the OSI if you'd like to discuss why the PPL isn't an open source license)

I don't get the impression that you intended for Heya to not be used in software like ours so what I propose is a compromise: dual licensing under the AGPL and PPL. Any software can be licensed under multiple licenses by its copyright holders and the user can choose which license they would like to use it under. The AGPL, while fully open source and free, is not popular among most large companies, like Google, Facebook, Amazon, etc. It's primarily used by smaller companies and nonprofits for software like Mastodon and CiviCRM. The reason is that big companies don't like that it requires you to open source all of the software its combined with. For companies like that, they don't want to reveal any of their internal software. On the other hand, nonprofits and small companies like mine (2 people) who primarily work with nonprofits and committed to open source are willing to do so.

Dual licensing Heya allows a user to make a choice: either open source the Rails software you're adding Heya into or don't open source and choose to accept the restrictions of the PPL. The largest companies will, by and large, pick the PPL every day. Some smaller companies will too. Nonprofits and companies like mine who provide fundraising services for nonprofits are going to be more willing to use

Is this something you'd be interested in? Relicensing isn't much of an effort as long as you don't have many outside contributors yet and I'd be happy to help.

Install generator should generate campaigns/application_campaign.rb

Currently the campaigns/application_campaign.rb file is generated when you use the campaign generator. The campaign generator should probably create that file if it doesn't exist, but it would be nice if the install generator also created it, since it stores some default config.

Campaign testing approach

According to the Rails testing guide:

The goals of testing your mailer classes are to ensure that:

  • emails are being processed (created and sent)
  • the email content is correct (subject, sender, body, etc)
  • the right emails are being sent at the right times

12.1.1 From All Sides

There are two aspects of testing your mailer, the unit tests and the functional tests. In the unit tests, you run the mailer in isolation with tightly controlled inputs and compare the output to a known value (a fixture.) In the functional tests you don't so much test the minute details produced by the mailer; instead, we test that our controllers and models are using the mailer in the right way. You test to prove that the right email was sent at the right time.

There likewise could be two aspects of testing Heya campaigns: unit and functional.

Unit Testing

I think I have a good approach for unit testing emails, using a similar API to Rails (this is currently working):

RSpec.describe SignupFirstRunCampaign, type: :campaign do
  describe "welcome" do
    it do
      user = FactoryBot.create(:user)
      email = SignupFirstRunCampaign.welcome(user)

      assert_emails 1 do
        email.deliver_now
      end

      assert_equal ["[email protected]"], email.from
      assert_equal [user.email], email.to
      assert_equal "Welcome to Honeybadger! ๐Ÿ‘‹", email.subject
      assert_ican email
    end
  end
end

Functional Testing

Unlike ActionMailer, Hey campaign emails are sent from Heya's scheduler rather than arbitrary places like Rails controllers. It would be nice to be able to test that the correct emails are sent at the correct times, similar to how we do in the internal scheduler tests. Something along these lines (this is pseudo-code):

    test "it sends campaign emails on time" do
      user = users(:first)
      OnboardingCampaign.add(user)

      assert_step OnboardingCampaign, :step_one, user

      refute_step OnboardingCampaign, :step_two, user

      TimeCop.travel(1.day.from_now)

      assert_step OnboardingCampaign, :step_two, user
    end

I'm not actually sure if this falls under functional testing or unit testing, since it's not really testing the integration between Heya and application components. In any case, I'm looking for feedback on it--would something like this be a good addition?

I18n support

I want to be able to store email subjects in my locales file.

Minitest or RSpec?

I'm currently using the default Minitest setup generated by Rails because:

  1. I wanted to keep extra dependencies to a minimum
  2. I wanted to see what it's like having a less magical testing setup; i.e. keep the tests fairly flat without a ton of nested contexts or code sharing, and with more basic assertions

I think we could still accomplish 2) with RSpec, it just requires more discipline. I'm OK with Minitest + Fixtures for now, but I'm also willing to switch to RSpec if you would prefer, @stympy. Do you prefer one over the other? (I think I actually prefer Minitest's assertions to RSpec's, i.e. assert true vs. expect(true).to be_truthy)

Example of Minitest format: https://github.com/honeybadger-io/heya/blob/master/test/models/heya/campaign_test.rb

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.