Add per-node first-seen timestamp via NodeBlock join model#258
Draft
Copilot wants to merge 2 commits into
Draft
Conversation
- Create node_blocks migration and model (node_id, block_id, first_seen_at) - Add has_many :node_blocks to Node and Block models - Populate NodeBlock in Block.create_or_update_with and create_headers_only - Add block_first_seen_at to Node#as_json - Add includes(node: :node_blocks) in Chaintip#nodes_for_identical_chaintips - Remove global First seen from blockInfo.jsx - Add per-node first-seen with colour coding in node.jsx Assisted-by: GitHub Copilot Assisted-by: OpenAI GPT-5-Codex Co-authored-by: jonathanbier <42411042+jonathanbier@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Add per-node first seen timestamp to node list
Add per-node first-seen timestamp via NodeBlock join model
Mar 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Previously, only a global
block.created_atandblock.first_seen_by_idexisted — no per-node record of when each node first saw a block. This introduces aNodeBlockjoin model to track per-node first-seen timestamps, surfaces them in the node list UI with colour coding (earliest=green, latest=red, others=black), and removes the now-redundant global "First seen" line fromBlockInfo.Backend
node_blockstable —node_id,block_id,first_seen_at(not null), unique index on[node_id, block_id]NodeBlockmodel — simple join model withbelongs_to :nodeandbelongs_to :blockBlock.create_or_update_with/create_headers_only— callNodeBlock.find_or_create_by(node:, block:)withfirst_seen_at: Time.currentafter block creationNode#as_json— addsblock_first_seen_atby looking up theNodeBlockfor the node's active chaintip blockChaintip#nodes_for_identical_chaintips— adds.includes(node: :node_blocks)to avoid N+1 queries when serialising nodesFrontend
node.jsx— renders per-nodeFirst seen: HH:mm:ss UTCin the right-hand<td>, colour-coded by comparing against the min/maxblock_first_seen_atacross all nodes sharing the same chaintip; single-node groups render in blackblockInfo.jsx— removes the globalFirst seenlineOriginal prompt
Summary
Add a per-node "First seen" timestamp to the node list on the main page. The earliest node to see a block should have the time shown in green, the latest in red, and the rest in black. The global "First seen" line in
BlockInfoshould be removed since it will now be shown per-node.Currently there is no per-node first-seen timestamp — only a global one (
block.created_at) and which node was first (block.first_seen_by_id). This PR introduces a newNodeBlockjoin model to track when each individual node first saw each block.Changes Required
1. New migration: create
node_blockstableCreate
db/migrate/TIMESTAMP_create_node_blocks.rb:2. New model:
app/models/node_block.rb3. Update
app/models/node.rbAdd association:
4. Update
app/models/block.rbAdd association:
Update
create_or_update_withto record per-node first-seen timestamp. AfterBlock.find_or_create_by(...), add:Also update
create_headers_onlysimilarly — afterBlock.create(...), add:5. Update
app/models/node.rbas_jsonAdd
block_first_seen_atto the merged hash inas_json. This requires looking up theNodeBlockfor the node's active chaintip block:6. Update
app/models/chaintip.rbnodes_for_identical_chaintipsThe method currently returns an array of
Nodeobjects. The nodes need to carry theirfirst_seen_atfor the block. The cleanest way is to keep returning Node objects fromas_jsonbut includeblock_first_seen_atvianode.as_json(which is already done via step 5 above — sinceNode#as_jsonwill now includeblock_first_seen_at).Make sure
nodes_for_identical_chaintipseager loads node_blocks to avoid N+1:7. Update
app/javascript/packs/forkMonitorApp/components/blockInfo.jsxRemove the "First seen" line and its
<br/>:Remove lines:
8. Update
app/javascript/packs/forkMonitorApp/components/node.jsxAdd
import 'moment-timezone';after the existingimport Moment from 'react-moment';line.In the right-hand
<td align="right">, add the per-node first-seen time beforeNodeInflation, with colour coding based on min/max across all nodes in the chaintip group.The colour logic needs the list of all nodes'
block_first_seen_atvalues. These are available viathis.props.chaintip.nodes(the sibling nodes). So compute min/max from that array and colour accordingly.Replace the existing right-hand
<td>: