notificare 0.1.0.alpha.1

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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +899 -0
  4. data/app/assets/stylesheets/active_job/notificare/engine.css +425 -0
  5. data/app/controllers/active_job/notificare/application_controller.rb +7 -0
  6. data/app/controllers/active_job/notificare/executions_controller.rb +41 -0
  7. data/app/controllers/active_job/notificare/notifications_controller.rb +72 -0
  8. data/app/helpers/active_job/notificare/view_helpers.rb +43 -0
  9. data/app/models/active_job/notificare/application_record.rb +7 -0
  10. data/app/models/active_job/notificare/execution.rb +20 -0
  11. data/app/models/active_job/notificare/notification.rb +50 -0
  12. data/app/views/active_job/notificare/_notification.html.erb +19 -0
  13. data/app/views/active_job/notificare/_notifications.html.erb +7 -0
  14. data/app/views/active_job/notificare/_progress.html.erb +17 -0
  15. data/app/views/active_job/notificare/executions/index.html.erb +75 -0
  16. data/app/views/active_job/notificare/executions/show.html.erb +66 -0
  17. data/app/views/active_job/notificare/notifications/clear.turbo_stream.erb +3 -0
  18. data/app/views/active_job/notificare/notifications/dismiss.turbo_stream.erb +1 -0
  19. data/app/views/active_job/notificare/notifications/read.turbo_stream.erb +3 -0
  20. data/app/views/layouts/active_job/notificare/application.html.erb +42 -0
  21. data/config/locales/en.yml +7 -0
  22. data/config/routes.rb +13 -0
  23. data/lib/active_job/notificare/concern.rb +78 -0
  24. data/lib/active_job/notificare/engine.rb +28 -0
  25. data/lib/active_job/notificare/progress_handle.rb +23 -0
  26. data/lib/active_job/notificare/projection.rb +145 -0
  27. data/lib/active_job/notificare/recipient.rb +39 -0
  28. data/lib/active_job/notificare/step_dsl.rb +42 -0
  29. data/lib/active_job/notificare/version.rb +5 -0
  30. data/lib/active_job/notificare.rb +14 -0
  31. data/lib/generators/active_job/notificare/install/install_generator.rb +56 -0
  32. data/lib/generators/active_job/notificare/install/templates/_notification.html.erb.tt +19 -0
  33. data/lib/generators/active_job/notificare/install/templates/_notifications.html.erb.tt +7 -0
  34. data/lib/generators/active_job/notificare/install/templates/_progress.html.erb.tt +17 -0
  35. data/lib/generators/active_job/notificare/install/templates/create_active_job_notificare_tables.rb.tt +36 -0
  36. data/lib/generators/active_job/notificare/install/templates/initializer.rb.tt +24 -0
  37. data/lib/generators/active_job/notificare/scaffold/scaffold_generator.rb +74 -0
  38. data/lib/generators/active_job/notificare/scaffold/templates/controller.rb.tt +31 -0
  39. data/lib/generators/active_job/notificare/scaffold/templates/index.html.erb.tt +26 -0
  40. data/lib/generators/active_job/notificare/scaffold/templates/locale.en.yml.tt +18 -0
  41. data/lib/generators/active_job/notificare/scaffold/templates/show.html.erb.tt +39 -0
  42. data/lib/notificare.rb +4 -0
  43. metadata +118 -0
@@ -0,0 +1,56 @@
1
+ require "rails/generators"
2
+ require "rails/generators/active_record"
3
+
4
+ module ActiveJob
5
+ module Notificare
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ include Rails::Generators::Migration
9
+
10
+ source_root File.expand_path("templates", __dir__)
11
+
12
+ def self.next_migration_number(dirname)
13
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
14
+ end
15
+
16
+ def create_migration_file
17
+ migration_template(
18
+ "create_active_job_notificare_tables.rb.tt",
19
+ "db/migrate/create_active_job_notificare_tables.rb"
20
+ )
21
+ end
22
+
23
+ def create_initializer
24
+ template "initializer.rb.tt", "config/initializers/active_job_notificare.rb"
25
+ end
26
+
27
+ def append_route_comment
28
+ route "# mount ActiveJob::Notificare::Engine => \"/notificare\""
29
+ end
30
+
31
+ def create_view_partials
32
+ template "_progress.html.erb.tt", "app/views/active_job/notificare/_progress.html.erb"
33
+ template "_notifications.html.erb.tt", "app/views/active_job/notificare/_notifications.html.erb"
34
+ template "_notification.html.erb.tt", "app/views/active_job/notificare/_notification.html.erb"
35
+ end
36
+
37
+ private
38
+
39
+ def json_column_type
40
+ case ActiveRecord::Base.connection.adapter_name.downcase
41
+ when /postgresql/, /postgis/
42
+ "jsonb"
43
+ when /mysql/
44
+ "json"
45
+ else
46
+ "text"
47
+ end
48
+ end
49
+
50
+ def migration_version
51
+ "[#{ActiveRecord::Migration.current_version}]"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,19 @@
1
+ <%%= turbo_frame_tag dom_id(notification) do %>
2
+ <div class="notificare-notification<%%= notification.read? ? "" : " notificare-notification--unread" %>">
3
+ <strong class="notificare-notification__title"><%%= notification.title %></strong>
4
+ <%% if notification.description.present? %>
5
+ <p class="notificare-notification__description"><%%= notification.description %></p>
6
+ <%% end %>
7
+ <div class="notificare-notification__actions">
8
+ <%% unless notification.read? %>
9
+ <%%= button_to t("active_job.notificare.notifications.mark_as_read"), notificare.read_notification_path(notification), method: :patch %>
10
+ <%% end %>
11
+ <%%= button_to t("active_job.notificare.notifications.dismiss"), notificare.dismiss_notification_path(notification), method: :patch %>
12
+ <%% if notification.actions.present? %>
13
+ <%% notification.actions.each do |action| %>
14
+ <%%= link_to action["label"], action["url"] %>
15
+ <%% end %>
16
+ <%% end %>
17
+ </div>
18
+ </div>
19
+ <%% end %>
@@ -0,0 +1,7 @@
1
+ <%%= turbo_stream_from "active_job_notifications", recipient.to_gid_param %>
2
+ <div id="active_job_notifications" class="notificare-inbox">
3
+ <%%= button_to t(".clear_all"), notificare.clear_notifications_path, method: :delete %>
4
+ <%% notifications.each do |notification| %>
5
+ <%%= render "active_job/notificare/notification", notification: notification %>
6
+ <%% end %>
7
+ </div>
@@ -0,0 +1,17 @@
1
+ <%%= turbo_stream_from "active_job_progress", execution.job_id %>
2
+ <div id="<%%= dom_id(execution) %>" class="notificare-progress">
3
+ <%% if execution.progress_total.present? %>
4
+ <progress class="notificare-progress__bar" value="<%%= execution.progress_current %>" max="<%%= execution.progress_total %>"></progress>
5
+ <span class="notificare-progress__label"><%%= execution.progress_current %>/<%%= execution.progress_total %> (<%%= (execution.progress_current.to_f / execution.progress_total * 100).round %>%)</span>
6
+ <%% if execution.current_step.present? %>
7
+ <span class="notificare-progress__step"><%%= execution.current_step %></span>
8
+ <%% end %>
9
+ <%% else %>
10
+ <%% if !execution.failed? %>
11
+ <span class="notificare-progress__spinner" role="status" aria-label="Loading..."></span>
12
+ <%% end %>
13
+ <%% if execution.current_step.present? %>
14
+ <span class="notificare-progress__step"><%%= execution.current_step %></span>
15
+ <%% end %>
16
+ <%% end %>
17
+ </div>
@@ -0,0 +1,36 @@
1
+ class CreateActiveJobNotificareTables < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :active_job_executions do |t|
4
+ t.string :job_id, null: false
5
+ t.string :job_class, null: false
6
+ t.string :status, null: false, default: "enqueued"
7
+ t.string :current_step
8
+ t.integer :progress_current, null: false, default: 0
9
+ t.integer :progress_total
10
+ t.datetime :started_at
11
+ t.datetime :completed_at
12
+ t.text :error
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :active_job_executions, :job_id, unique: true
17
+ add_index :active_job_executions, :job_class
18
+
19
+ create_table :active_job_notifications do |t|
20
+ t.string :recipient_type, null: false
21
+ t.string :recipient_id, null: false
22
+ t.string :job_id
23
+ t.string :event_type, null: false
24
+ t.string :title, null: false
25
+ t.text :description
26
+ t.<%= json_column_type %> :metadata
27
+ t.<%= json_column_type %> :actions
28
+ t.datetime :read_at
29
+ t.datetime :dismissed_at
30
+ t.timestamps
31
+ end
32
+
33
+ add_index :active_job_notifications, :recipient_id
34
+ add_index :active_job_notifications, :job_id
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ # ActiveJob::Notificare configuration
2
+ #
3
+ # Generated by: rails generate active_job:notificare:install
4
+ #
5
+ # Notificare is a projection layer over ActiveJob::Continuation. Including
6
+ # `ActiveJob::Notificare` in a job pulls in Continuation, the step DSL, and the
7
+ # notification primitives.
8
+ #
9
+ # ActiveJob::Notificare.configure do |config|
10
+ # # How long to retain completed and failed execution records.
11
+ # # Default: 7.days — Set to nil to keep indefinitely.
12
+ # # config.execution_retention = 7.days
13
+ #
14
+ # # Broadcast real-time progress updates via Turbo Streams.
15
+ # # Requires Action Cable to be configured in your application.
16
+ # # config.broadcast_progress = true
17
+ #
18
+ # # Broadcast real-time notification updates via Turbo Streams.
19
+ # # config.broadcast_notifications = true
20
+ #
21
+ # # Mount path for the engine admin UI.
22
+ # # Default: "/notificare"
23
+ # # config.mount_path = "/notificare"
24
+ # end
@@ -0,0 +1,74 @@
1
+ require "rails/generators"
2
+
3
+ module ActiveJob
4
+ module Notificare
5
+ module Generators
6
+ class ScaffoldGenerator < Rails::Generators::NamedBase
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ class_option :controller, type: :string,
10
+ desc: "Override the controller class name (e.g. --controller=MyImportsController)"
11
+ class_option :prefix, type: :string,
12
+ desc: "Override the route and view directory prefix (e.g. --prefix=my_imports)"
13
+
14
+ def validate_job_class
15
+ klass = class_name.constantize
16
+ unless klass.ancestors.include?(::ActiveJob::Notificare)
17
+ say_status :error,
18
+ "#{class_name} does not include ActiveJob::Notificare. " \
19
+ "Add `include ActiveJob::Notificare` to the job class and re-run the generator.",
20
+ :red
21
+ @invalid = true
22
+ end
23
+ rescue NameError
24
+ say_status :error,
25
+ "#{class_name} could not be loaded. " \
26
+ "Make sure the job class exists and includes `include ActiveJob::Notificare`.",
27
+ :red
28
+ @invalid = true
29
+ end
30
+
31
+ def create_controller
32
+ return if @invalid
33
+ template "controller.rb.tt", "app/controllers/#{prefix}_controller.rb"
34
+ end
35
+
36
+ def create_views
37
+ return if @invalid
38
+ template "index.html.erb.tt", "app/views/#{prefix}/index.html.erb"
39
+ template "show.html.erb.tt", "app/views/#{prefix}/show.html.erb"
40
+ end
41
+
42
+ def create_locale
43
+ return if @invalid
44
+ template "locale.en.yml.tt", "config/locales/active_job_notificare_#{prefix}.en.yml"
45
+ end
46
+
47
+ def print_routes_snippet
48
+ return if @invalid
49
+ say "\nPaste into config/routes.rb:\n\n"
50
+ say " resources :#{prefix}, only: [:index, :show]", :green
51
+ say ""
52
+ end
53
+
54
+ private
55
+
56
+ def resource_name
57
+ file_name.sub(/_job$/, "")
58
+ end
59
+
60
+ def plural_resource_name
61
+ resource_name.pluralize
62
+ end
63
+
64
+ def prefix
65
+ options[:prefix] || plural_resource_name
66
+ end
67
+
68
+ def scaffold_controller_class_name
69
+ options[:controller] || "#{plural_resource_name.camelize}Controller"
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,31 @@
1
+ class <%= scaffold_controller_class_name %> < ApplicationController
2
+ def index
3
+ # Scope executions to job runs that produced at least one notification for this recipient.
4
+ job_ids = ActiveJob::Notificare::Notification
5
+ .where(recipient: current_recipient)
6
+ .select(:job_id)
7
+ .distinct
8
+ @executions = ActiveJob::Notificare::Execution
9
+ .where(job_class: "<%= class_name %>", job_id: job_ids)
10
+ .recent
11
+ end
12
+
13
+ def show
14
+ @execution = ActiveJob::Notificare::Execution
15
+ .where(job_class: "<%= class_name %>")
16
+ .find(params[:id])
17
+ @notifications = ActiveJob::Notificare::Notification
18
+ .where(recipient: current_recipient, job_id: @execution.job_id)
19
+ .visible
20
+ rescue ActiveRecord::RecordNotFound
21
+ head :not_found
22
+ end
23
+
24
+ private
25
+
26
+ # TODO: replace with however your app exposes the signed-in user/account.
27
+ def current_recipient
28
+ current_notificare_recipient || current_user
29
+ end
30
+ helper_method :current_recipient
31
+ end
@@ -0,0 +1,26 @@
1
+ <%# Subscribes this page to live notification updates for the current user.
2
+ New notifications arrive as Turbo Stream broadcasts without a page reload. %>
3
+ <%%= turbo_stream_from "active_job_notifications", current_recipient.to_gid_param %>
4
+
5
+ <h1><%%= t(".title") %></h1>
6
+
7
+ <%% if @executions.any? %>
8
+ <%% @executions.each do |execution| %>
9
+ <div>
10
+ <%%= active_job_notificare(execution) %>
11
+ <p>
12
+ <%%= t(".status_label") %>: <%%= execution.status %>
13
+ <%% if execution.started_at %>
14
+ &mdash; <%%= t(".started_label") %>: <%%= execution.started_at.strftime("%Y-%m-%d %H:%M") %>
15
+ <%% end %>
16
+ </p>
17
+ <%%= link_to t(".details"), <%= resource_name %>_path(execution) %>
18
+ </div>
19
+ <%% end %>
20
+ <%% else %>
21
+ <p><%%= t(".empty") %></p>
22
+ <%% end %>
23
+
24
+ <%# Renders the recipient's notification inbox with live updates and
25
+ inline Mark-as-read / Dismiss actions via Turbo Streams. %>
26
+ <%%= active_job_notifications(for: current_recipient) %>
@@ -0,0 +1,18 @@
1
+ en:
2
+ <%= prefix %>:
3
+ index:
4
+ title: "<%= plural_resource_name.titleize %>"
5
+ empty: "No <%= plural_resource_name.humanize.downcase %> found."
6
+ status_label: "Status"
7
+ started_label: "Started"
8
+ details: "Details"
9
+ show:
10
+ back: "← <%= plural_resource_name.titleize %>"
11
+ title: "<%= resource_name.humanize %> details"
12
+ status_label: "Status"
13
+ started_label: "Started"
14
+ completed_label: "Completed"
15
+ error_label: "Error"
16
+ progress_heading: "Progress"
17
+ notifications_heading: "Notifications"
18
+ no_notifications: "No notifications for this run yet."
@@ -0,0 +1,39 @@
1
+ <%%= link_to t(".back"), <%= prefix %>_path %>
2
+
3
+ <h1><%%= t(".title") %></h1>
4
+ <p class="job-id"><%%= @execution.job_id %></p>
5
+
6
+ <dl>
7
+ <dt><%%= t(".status_label") %></dt>
8
+ <dd><%%= @execution.status %></dd>
9
+
10
+ <dt><%%= t(".started_label") %></dt>
11
+ <dd><%%= @execution.started_at&.strftime("%Y-%m-%d %H:%M:%S") || "—" %></dd>
12
+
13
+ <dt><%%= t(".completed_label") %></dt>
14
+ <dd><%%= @execution.completed_at&.strftime("%Y-%m-%d %H:%M:%S") || "—" %></dd>
15
+
16
+ <%% if @execution.error.present? %>
17
+ <dt><%%= t(".error_label") %></dt>
18
+ <dd><%%= @execution.error %></dd>
19
+ <%% end %>
20
+ </dl>
21
+
22
+ <%# Live progress widget. Subscribes to turbo_stream "active_job_progress", job_id — the
23
+ progress bar / spinner updates automatically while the job is running. %>
24
+ <h2><%%= t(".progress_heading") %></h2>
25
+ <%%= active_job_notificare(@execution) %>
26
+
27
+ <%# Per-run notification list. Subscribes to the recipient's notification stream so new
28
+ notifications for this job run appear without a page reload. %>
29
+ <h2><%%= t(".notifications_heading") %></h2>
30
+ <%%= turbo_stream_from "active_job_notifications", current_recipient.to_gid_param %>
31
+ <div id="<%%= dom_id(@execution, :notifications) %>">
32
+ <%% if @notifications.any? %>
33
+ <%% @notifications.each do |notification| %>
34
+ <%%= render "active_job/notificare/notification", notification: notification %>
35
+ <%% end %>
36
+ <%% else %>
37
+ <p><%%= t(".no_notifications") %></p>
38
+ <%% end %>
39
+ </div>
data/lib/notificare.rb ADDED
@@ -0,0 +1,4 @@
1
+ require "active_job/notificare"
2
+
3
+ module Notificare
4
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: notificare
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.alpha.1
5
+ platform: ruby
6
+ authors:
7
+ - Gabriel Quaresma
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: railties
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '8.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activejob
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '8.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '8.1'
40
+ description: 'Notificare (Romanian: ''to notify'') is a Rails engine built on top
41
+ of ActiveJob::Continuation. It adds a persisted projection of running-job progress,
42
+ a durable user-facing notification inbox, and a Hotwire UI scaffold — turning Continuation''s
43
+ resumable steps into a state machine that drives notifications without manual broadcast
44
+ plumbing.'
45
+ email:
46
+ - j.quaresmasantos_98@hotmail.com
47
+ executables: []
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - LICENSE
52
+ - README.md
53
+ - app/assets/stylesheets/active_job/notificare/engine.css
54
+ - app/controllers/active_job/notificare/application_controller.rb
55
+ - app/controllers/active_job/notificare/executions_controller.rb
56
+ - app/controllers/active_job/notificare/notifications_controller.rb
57
+ - app/helpers/active_job/notificare/view_helpers.rb
58
+ - app/models/active_job/notificare/application_record.rb
59
+ - app/models/active_job/notificare/execution.rb
60
+ - app/models/active_job/notificare/notification.rb
61
+ - app/views/active_job/notificare/_notification.html.erb
62
+ - app/views/active_job/notificare/_notifications.html.erb
63
+ - app/views/active_job/notificare/_progress.html.erb
64
+ - app/views/active_job/notificare/executions/index.html.erb
65
+ - app/views/active_job/notificare/executions/show.html.erb
66
+ - app/views/active_job/notificare/notifications/clear.turbo_stream.erb
67
+ - app/views/active_job/notificare/notifications/dismiss.turbo_stream.erb
68
+ - app/views/active_job/notificare/notifications/read.turbo_stream.erb
69
+ - app/views/layouts/active_job/notificare/application.html.erb
70
+ - config/locales/en.yml
71
+ - config/routes.rb
72
+ - lib/active_job/notificare.rb
73
+ - lib/active_job/notificare/concern.rb
74
+ - lib/active_job/notificare/engine.rb
75
+ - lib/active_job/notificare/progress_handle.rb
76
+ - lib/active_job/notificare/projection.rb
77
+ - lib/active_job/notificare/recipient.rb
78
+ - lib/active_job/notificare/step_dsl.rb
79
+ - lib/active_job/notificare/version.rb
80
+ - lib/generators/active_job/notificare/install/install_generator.rb
81
+ - lib/generators/active_job/notificare/install/templates/_notification.html.erb.tt
82
+ - lib/generators/active_job/notificare/install/templates/_notifications.html.erb.tt
83
+ - lib/generators/active_job/notificare/install/templates/_progress.html.erb.tt
84
+ - lib/generators/active_job/notificare/install/templates/create_active_job_notificare_tables.rb.tt
85
+ - lib/generators/active_job/notificare/install/templates/initializer.rb.tt
86
+ - lib/generators/active_job/notificare/scaffold/scaffold_generator.rb
87
+ - lib/generators/active_job/notificare/scaffold/templates/controller.rb.tt
88
+ - lib/generators/active_job/notificare/scaffold/templates/index.html.erb.tt
89
+ - lib/generators/active_job/notificare/scaffold/templates/locale.en.yml.tt
90
+ - lib/generators/active_job/notificare/scaffold/templates/show.html.erb.tt
91
+ - lib/notificare.rb
92
+ homepage: https://github.com/joaoGabriel55/notificare
93
+ licenses:
94
+ - MIT
95
+ metadata:
96
+ homepage_uri: https://github.com/joaoGabriel55/notificare
97
+ source_code_uri: https://github.com/joaoGabriel55/notificare
98
+ changelog_uri: https://github.com/joaoGabriel55/notificare/blob/v0.1.0.alpha.1/CHANGELOG.md
99
+ bug_tracker_uri: https://github.com/joaoGabriel55/notificare/issues
100
+ rubygems_mfa_required: 'true'
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '3.3'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '3.5'
114
+ requirements: []
115
+ rubygems_version: 3.6.7
116
+ specification_version: 4
117
+ summary: Progress tracking and notification inbox for ActiveJob::Continuation
118
+ test_files: []