railwatch 0.2.0.pre1 → 0.2.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 +8 -1
- data/app/controllers/railwatch/saved_views_controller.rb +1 -1
- data/app/models/railwatch/comment.rb +1 -1
- data/app/models/railwatch/issue.rb +1 -1
- data/app/models/railwatch/issue_activity.rb +1 -1
- data/app/models/railwatch/saved_view.rb +2 -2
- data/db/railwatch_migrate/20260918120000_widen_host_user_ids.rb +71 -0
- data/docs/embedded.md +11 -1
- data/lib/railwatch/embedded.rb +4 -1
- data/lib/railwatch/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e971a5ebd07d60e1ce62861a43de0d35961d1f12e439fc07a8e384e5fe7cd032
|
|
4
|
+
data.tar.gz: e3f9c39e07cc99599587b910ef777d8ce473f917bd85238414d5cfc7cc3892c1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e5bed88aee6554e1eb97f7e109a08f67b96ca286c378c8bcaba0c4284ab2f9b8b047ea5ceae073ff3c0ac800ed0e0386ebc7b0b25f5a66f0b144d042be62d59
|
|
7
|
+
data.tar.gz: 1ef06458ea5d88cd739def97a3e05b2755b8b0ba9c8666be531cc1e6e986f774f7c2e963a91c9affb9815f2770bab5e2f1b643a5477b41e2b766ad831948a2b9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.2.0 (2026-09-18)
|
|
4
4
|
|
|
5
5
|
- Embedded mode: `bin/rails generate railwatch:install --local` keeps
|
|
6
6
|
every record in two SQLite databases the app owns (`railwatch` for
|
|
@@ -166,6 +166,13 @@
|
|
|
166
166
|
the bundle yet (a `LoadError` rather than `AdapterNotSpecified`), which
|
|
167
167
|
is the state a PostgreSQL app is in between the installer adding
|
|
168
168
|
`gem "sqlite3"` and the `bundle install` that follows.
|
|
169
|
+
- Host user ids are opaque. The columns behind comments, issue activity,
|
|
170
|
+
saved views and issue assignment were integers, which assumed every
|
|
171
|
+
application numbers its users: an app with UUID primary keys could not
|
|
172
|
+
write a comment at all, and one with ids past the signed range could
|
|
173
|
+
not either. They are strings now, a `dashboard_user` resolver may
|
|
174
|
+
return whatever shape the app already uses, and rows written as numbers
|
|
175
|
+
before the change still match the same person after it.
|
|
169
176
|
- The embedded dashboard authenticates the way Mission Control Jobs
|
|
170
177
|
does: HTTP Basic is on and closed by default, so with no credentials
|
|
171
178
|
every dashboard page answers 401 (with a note saying what to run), the
|
|
@@ -37,7 +37,7 @@ module Railwatch
|
|
|
37
37
|
# dashboard_user resolver returns one identity for everybody is unaffected
|
|
38
38
|
# either way: one owner, one editor.
|
|
39
39
|
def editable?(view)
|
|
40
|
-
view.viewer_id == Viewer.user.id
|
|
40
|
+
view.viewer_id.to_s == Viewer.user.id.to_s
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def fallback = application_environment_overview_path(application, environment)
|
|
@@ -22,7 +22,7 @@ module Railwatch
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def assignee=(user)
|
|
25
|
-
self.assignee_id = user&.id
|
|
25
|
+
self.assignee_id = user&.id&.to_s
|
|
26
26
|
end
|
|
27
27
|
belongs_to :merged_into, class_name: "Issue", optional: true
|
|
28
28
|
has_many :merged_issues, class_name: "Issue", foreign_key: :merged_into_id, inverse_of: :merged_into, dependent: :nullify
|
|
@@ -33,14 +33,14 @@ module Railwatch
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def user=(u)
|
|
36
|
-
self.viewer_id = u&.id
|
|
36
|
+
self.viewer_id = u&.id&.to_s
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
validates :name, presence: true, length: { maximum: 80 }
|
|
40
40
|
validates :page, inclusion: { in: PAGES }
|
|
41
41
|
|
|
42
42
|
scope :pinned, -> { where(pinned: true) }
|
|
43
|
-
scope :visible_to, ->(user) { where(shared: true).or(where(viewer_id: user.id)) }
|
|
43
|
+
scope :visible_to, ->(user) { where(shared: true).or(where(viewer_id: user.id.to_s)) }
|
|
44
44
|
|
|
45
45
|
# The props every environment page shares, ready for the sidebar and the
|
|
46
46
|
# Views menu: one entry per view this user is allowed to see.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The ids on comments, issue activity, saved views and issue assignment are
|
|
4
|
+
# whatever the host's `dashboard_user` resolver returned. They were integer
|
|
5
|
+
# columns, which quietly assumed every application numbers its users. An app
|
|
6
|
+
# with UUID primary keys could not store a comment at all, and one with ids
|
|
7
|
+
# past the signed range could not either.
|
|
8
|
+
#
|
|
9
|
+
# Nothing joins on these columns and nothing indexes them; they are an opaque
|
|
10
|
+
# handle the host gave us and hands back. A string says that, and costs
|
|
11
|
+
# nothing in a database that is a few thousand rows of authored content.
|
|
12
|
+
#
|
|
13
|
+
# Existing values convert cleanly: SQLite rewrites 7 as "7", and the models
|
|
14
|
+
# compare with to_s on both sides, so a row written before this migration
|
|
15
|
+
# still matches the same viewer after it.
|
|
16
|
+
class WidenHostUserIds < ActiveRecord::Migration[8.1]
|
|
17
|
+
COLUMNS = {
|
|
18
|
+
railwatch_issues: :assignee_id,
|
|
19
|
+
railwatch_comments: :viewer_id,
|
|
20
|
+
railwatch_issue_activities: :viewer_id,
|
|
21
|
+
railwatch_saved_views: :viewer_id
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# SQLite rebuilds a table to change a column type, and a rebuild inside a
|
|
25
|
+
# transaction ignores `PRAGMA foreign_keys = OFF`. This database declares no
|
|
26
|
+
# foreign keys, so there is nothing to cascade, but the rebuild is kept out
|
|
27
|
+
# of the migrator's transaction anyway: it is the cheap half of a habit
|
|
28
|
+
# whose expensive half is deleted rows.
|
|
29
|
+
disable_ddl_transaction!
|
|
30
|
+
|
|
31
|
+
def up
|
|
32
|
+
COLUMNS.each do |table, column|
|
|
33
|
+
null = column == :viewer_id && table == :railwatch_saved_views ? false : true
|
|
34
|
+
change_column table, column, :string, limit: 255, null: null
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Reversible only while every stored id still looks like a number. Once a
|
|
39
|
+
# host with UUIDs (or emails, or anything else) has written one, there is
|
|
40
|
+
# no integer to go back to: the column would either refuse the value or
|
|
41
|
+
# quietly coerce it to something that is no longer that person. Say so and
|
|
42
|
+
# stop, rather than losing the identity on the way down. These tables are
|
|
43
|
+
# authored content, a few thousand rows at most, so reading them is cheap.
|
|
44
|
+
INTEGERISH = /\A-?\d+\z/
|
|
45
|
+
MAX_REPORTED = 3
|
|
46
|
+
|
|
47
|
+
def down
|
|
48
|
+
blocking = COLUMNS.filter_map do |table, column|
|
|
49
|
+
next unless connection.table_exists?(table)
|
|
50
|
+
|
|
51
|
+
values = connection.select_values(
|
|
52
|
+
"SELECT DISTINCT #{connection.quote_column_name(column)} FROM #{connection.quote_table_name(table)} " \
|
|
53
|
+
"WHERE #{connection.quote_column_name(column)} IS NOT NULL"
|
|
54
|
+
)
|
|
55
|
+
offenders = values.reject { |value| INTEGERISH.match?(value.to_s) }
|
|
56
|
+
"#{table}.#{column} (#{offenders.first(MAX_REPORTED).join(', ')}#{"..." if offenders.size > MAX_REPORTED})" if offenders.any?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if blocking.any?
|
|
60
|
+
raise ActiveRecord::IrreversibleMigration,
|
|
61
|
+
"cannot narrow host user ids back to integers: #{blocking.join('; ')}. " \
|
|
62
|
+
"Those ids came from this application's own dashboard_user resolver and have no integer form; " \
|
|
63
|
+
"rolling back would discard them."
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
COLUMNS.each do |table, column|
|
|
67
|
+
null = column == :viewer_id && table == :railwatch_saved_views ? false : true
|
|
68
|
+
change_column table, column, :integer, null: null
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/docs/embedded.md
CHANGED
|
@@ -184,7 +184,7 @@ on the WebSocket handshake, a `dashboard_user` resolver is consulted,
|
|
|
184
184
|
an undeclared gate is refused. The channel carries an ingest ping (a
|
|
185
185
|
timestamp and per-type counts) and never telemetry records.
|
|
186
186
|
|
|
187
|
-
### Naming the operator
|
|
187
|
+
### Naming the operator, and what an id may be
|
|
188
188
|
|
|
189
189
|
Comments, saved views and issue activity record who did them. Give the
|
|
190
190
|
initializer a resolver and the dashboard shows that person instead of a
|
|
@@ -197,6 +197,16 @@ c.dashboard_user = ->(request) do
|
|
|
197
197
|
end
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
+
The `id` may be anything your application already uses: an integer, a
|
|
201
|
+
UUID, a ULID, an email. It is stored as an opaque string and handed back
|
|
202
|
+
to you; nothing joins on it and nothing parses it, because the engine has
|
|
203
|
+
no user table to check it against. Comments, saved views, issue activity
|
|
204
|
+
and assignment all key off whatever you return, so a person keeps their
|
|
205
|
+
own views and their name on their own comments however you identify them.
|
|
206
|
+
|
|
207
|
+
Returning `nil` refuses the request, which is what makes this an
|
|
208
|
+
authorisation rule as well as a label.
|
|
209
|
+
|
|
200
210
|
## The writer process
|
|
201
211
|
|
|
202
212
|
Puma forks one Railwatch writer from its master when `config/puma.rb`
|
data/lib/railwatch/embedded.rb
CHANGED
|
@@ -34,7 +34,10 @@ module Railwatch
|
|
|
34
34
|
def plan = "embedded"
|
|
35
35
|
def retention_days = Railwatch.config.retention_days
|
|
36
36
|
def auto_resolve_after_days = 14
|
|
37
|
-
|
|
37
|
+
# Whoever the host's resolver named for this request, not the anonymous
|
|
38
|
+
# placeholder: an embedded install has no member list, so the account's
|
|
39
|
+
# one "member" is the person looking at it.
|
|
40
|
+
def users = [ User.current || User.default ]
|
|
38
41
|
def members = users
|
|
39
42
|
def memberships = NONE
|
|
40
43
|
def integrations = NONE
|
data/lib/railwatch/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: railwatch
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.0
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cole Robertson
|
|
@@ -204,6 +204,7 @@ files:
|
|
|
204
204
|
- db/railwatch_migrate/20260916000000_create_railwatch_tables.rb
|
|
205
205
|
- db/railwatch_migrate/20260917000000_create_railwatch_maintenance_tasks.rb
|
|
206
206
|
- db/railwatch_migrate/20260917120000_create_railwatch_followup_receipts.rb
|
|
207
|
+
- db/railwatch_migrate/20260918120000_widen_host_user_ids.rb
|
|
207
208
|
- db/railwatch_telemetry_migrate/20260903000001_create_telemetry.rb
|
|
208
209
|
- db/railwatch_telemetry_migrate/20260903000002_rename_tenant_to_app_tenant.rb
|
|
209
210
|
- db/railwatch_telemetry_migrate/20260903000003_add_statement_count_to_transactions.rb
|