summaryrefslogtreecommitdiff
path: root/app/services/namespaces/in_product_marketing_emails_service.rb
blob: 90900698e1ab324d00a1665a26182d658841be8b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# frozen_string_literal: true

module Namespaces
  class InProductMarketingEmailsService
    TRACKS = {
      create: {
        interval_days: [1, 5, 10],
        completed_actions: [:created],
        incomplete_actions: [:git_write]
      },
      team_short: {
        interval_days: [1],
        completed_actions: [:git_write],
        incomplete_actions: [:user_added]
      },
      trial_short: {
        interval_days: [2],
        completed_actions: [:git_write],
        incomplete_actions: [:trial_started]
      },
      admin_verify: {
        interval_days: [3],
        completed_actions: [:git_write],
        incomplete_actions: [:pipeline_created]
      },
      verify: {
        interval_days: [4, 8, 13],
        completed_actions: [:git_write],
        incomplete_actions: [:pipeline_created]
      },
      trial: {
        interval_days: [1, 5, 10],
        completed_actions: [:git_write, :pipeline_created],
        incomplete_actions: [:trial_started]
      },
      team: {
        interval_days: [1, 5, 10],
        completed_actions: [:git_write, :pipeline_created, :trial_started],
        incomplete_actions: [:user_added]
      },
      experience: {
        interval_days: [30],
        completed_actions: [:created, :git_write],
        incomplete_actions: []
      }
    }.freeze

    def self.send_for_all_tracks_and_intervals
      TRACKS.each_key do |track|
        TRACKS[track][:interval_days].each do |interval|
          new(track, interval).execute
        end
      end
    end

    def initialize(track, interval)
      @track = track
      @interval = interval
      @sent_email_records = InProductMarketingEmailRecords.new
    end

    def execute
      raise ArgumentError, "Track #{track} not defined" unless TRACKS.key?(track)

      groups_for_track.each_batch do |groups|
        groups.each do |group|
          send_email_for_group(group)
        end
      end
    end

    private

    attr_reader :track, :interval, :sent_email_records

    def send_email(user, group)
      NotificationService.new.in_product_marketing(user.id, group.id, track, series)
    end

    def send_email_for_group(group)
      users_for_group(group).each do |user|
        if can_perform_action?(user, group)
          send_email(user, group)
          sent_email_records.add(user, track, series)
        end
      end

      sent_email_records.save!
    end

    def groups_for_track
      onboarding_progress_scope = OnboardingProgress
        .completed_actions_with_latest_in_range(completed_actions, range)
        .incomplete_actions(incomplete_actions)

      # Filtering out sub-groups is a temporary fix to prevent calling
      # `.root_ancestor` on groups that are not root groups.
      # See https://gitlab.com/groups/gitlab-org/-/epics/5594 for more information.
      Group
        .top_most
        .with_onboarding_progress
        .merge(onboarding_progress_scope)
        .merge(subscription_scope)
    end

    def subscription_scope
      {}
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def users_for_group(group)
      group.users
        .where(email_opted_in: true)
        .merge(Users::InProductMarketingEmail.without_track_and_series(track, series))
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def can_perform_action?(user, group)
      case track
      when :create, :verify
        user.can?(:create_projects, group)
      when :trial, :trial_short
        user.can?(:start_trial, group)
      when :team, :team_short
        user.can?(:admin_group_member, group)
      when :admin_verify
        user.can?(:admin_group, group)
      when :experience
        true
      end
    end

    def completed_actions
      TRACKS[track][:completed_actions]
    end

    def range
      date = (interval + 1).days.ago
      date.beginning_of_day..date.end_of_day
    end

    def incomplete_actions
      TRACKS[track][:incomplete_actions]
    end

    def series
      TRACKS[track][:interval_days].index(interval)
    end
  end
end

Namespaces::InProductMarketingEmailsService.prepend_mod