Module: PlanMyStuff::Notifications

Defined in:
lib/plan_my_stuff/notifications.rb

Overview

Central instrumentation helper. Domain classes call PlanMyStuff::Notifications.instrument at mutation points so consuming apps can subscribe for email, webhooks, Slack, etc.

Events are fired under the <event>.plan_my_stuff namespace via ActiveSupport::Notifications (Rails convention: event first, library last - matches sql.active_record, deliver.action_mailer). Subscribers run synchronously.

Constant Summary collapse

EVENT_SUFFIX =
'plan_my_stuff'
SKIPPED_LOG_KEYS =
%i[user timestamp visibility visibility_allowlist].freeze

Class Method Summary collapse

Class Method Details

.build_payload(resource, actor, extra) ⇒ Hash

Builds the payload hash for an event.

Parameters:

  • resource (Object)
  • actor (Object, nil)
  • extra (Hash)

Returns:

  • (Hash)


56
57
58
59
60
61
62
63
64
# File 'lib/plan_my_stuff/notifications.rb', line 56

def build_payload(resource, actor, extra)
  payload = {
    infer_resource_key(resource) => resource,
    :user => actor,
    :timestamp => Time.current,
  }
  payload.merge!(visibility_fields(resource))
  payload.merge(extra)
end

.infer_resource_key(resource) ⇒ Symbol

Maps a resource object to its payload key.

Parameters:

  • resource (Object)

Returns:

  • (Symbol)


72
73
74
75
76
77
78
79
# File 'lib/plan_my_stuff/notifications.rb', line 72

def infer_resource_key(resource)
  case resource
  when PlanMyStuff::Issue then :issue
  when PlanMyStuff::Comment then :comment
  when PlanMyStuff::BaseProjectItem then :project_item
  else :resource
  end
end

.instrument(event, resource, user: nil, **extra) ⇒ void

This method returns an undefined value.

Fires <event>.plan_my_stuff with a normalized payload.

Parameters:

  • event (String)

    e.g. ‘issue_created’

  • resource (Object)

    domain object (Issue, Comment, ProjectItem, …)

  • user (Object, nil) (defaults to: nil)

    explicit actor; falls back to config.current_user

  • extra (Hash)

    additional payload entries (changes:, labels:, user_ids:, …)



30
31
32
33
34
35
# File 'lib/plan_my_stuff/notifications.rb', line 30

def instrument(event, resource, user: nil, **extra)
  actor = user || resolve_current_user
  payload = build_payload(resource, actor, extra)
  log(event, payload)
  ActiveSupport::Notifications.instrument("#{event}.#{EVENT_SUFFIX}", payload)
end

.log(event, payload) ⇒ void

This method returns an undefined value.

Emits a debug log line for the event. No-op when no logger is available (e.g. outside Rails).

Parameters:

  • event (String)
  • payload (Hash)


114
115
116
117
118
119
# File 'lib/plan_my_stuff/notifications.rb', line 114

def log(event, payload)
  logger = rails_logger
  return if logger.nil?

  logger.debug { "[PlanMyStuff] #{event}.#{EVENT_SUFFIX} #{log_fields(payload)}" }
end

.log_fields(payload) ⇒ String

Parameters:

  • payload (Hash)

Returns:

  • (String)


133
134
135
136
137
138
139
140
141
142
# File 'lib/plan_my_stuff/notifications.rb', line 133

def log_fields(payload)
  fields = []
  fields << "user=#{payload[:user].inspect}" if payload.key?(:user)
  payload.each do |key, value|
    next if SKIPPED_LOG_KEYS.include?(key)

    fields << "#{key}=#{value.inspect}"
  end
  fields.join(' ')
end

.rails_loggerLogger?

Returns:

  • (Logger, nil)


122
123
124
125
126
127
# File 'lib/plan_my_stuff/notifications.rb', line 122

def rails_logger
  return unless defined?(Rails)
  return unless Rails.respond_to?(:logger)

  Rails.logger
end

.resolve_current_userObject?

Invokes config.current_user if it responds to call.

Returns:

  • (Object, nil)


41
42
43
44
45
46
# File 'lib/plan_my_stuff/notifications.rb', line 41

def resolve_current_user
  resolver = PlanMyStuff.configuration.current_user
  return if resolver.nil?

  resolver.respond_to?(:call) ? resolver.call : resolver
end

.visibility_fields(resource) ⇒ Hash

Extracts visibility + allowlist from Issue/Comment resources. Returns an empty hash for resources without visibility.

Parameters:

  • resource (Object)

Returns:

  • (Hash)


88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/plan_my_stuff/notifications.rb', line 88

def visibility_fields(resource)
  case resource
  when PlanMyStuff::Issue
    {
      visibility: resource..visibility,
      visibility_allowlist: Array.wrap(resource..visibility_allowlist),
    }
  when PlanMyStuff::Comment
    parent_allowlist = resource.issue ? resource.issue..visibility_allowlist : []
    {
      visibility: resource.visibility&.to_s,
      visibility_allowlist: Array.wrap(parent_allowlist),
    }
  else
    {}
  end
end