Update repository activity after pushes

79b3087d96d9 · AtlantisPleb · · parent 1877a03672c7

Update repository activity after pushes

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified lib/openagents/forge/pushes.ex
  • modified lib/openagents/repositories.ex
  • modified lib/openagents_web/live/repository_index_live.ex
  • modified test/openagents/forge/git_http_test.exs
  • modified test/openagents_web/live/repository_live_test.exs

Diff

5 files changed, +86 -2

lib/openagents/forge/pushes.ex modified +23 -1

@@ -20,7 +20,7 @@ defmodule OpenAgents.Forge.Pushes do

20 20
21 21
  require Logger
22 22
23
  alias OpenAgents.Analytics
23
  alias OpenAgents.{Analytics, Repositories}
24 24
  alias OpenAgents.Forge.{GitHTTP, PushReceipt, Repos, Sync, WAL}
25 25
  alias OpenAgents.Repo
26 26
  alias OpenAgents.Repositories.Repository

@@ -58,6 +58,7 @@ defmodule OpenAgents.Forge.Pushes do

58 58
        case persist(repo, body, refs_after, principal) do
59 59
          {:ok, seq} ->
60 60
            Repos.record_applied_seq!(repo, seq)
61
            record_repository_activity(repo)
61 62
62 63
            capture_push_received(
63 64
              repo,

@@ -98,6 +99,27 @@ defmodule OpenAgents.Forge.Pushes do

98 99
99 100
  defp capture_push_received(_repo, :error, _before, _after, _started_at), do: :ok
100 101
102
  # Repository timestamps are a derived product projection, not part of the
103
  # WAL acknowledgment barrier. A database outage must not turn a persisted
104
  # push into an apparent client failure, because retrying would duplicate a
105
  # push the forge already accepted.
106
  defp record_repository_activity(repo) do
107
    case Repositories.record_push_activity(repo) do
108
      :ok ->
109
        :ok
110
111
      {:error, :repository_not_found} ->
112
        :ok
113
    end
114
  rescue
115
    error ->
116
      Logger.warning(
117
        "forge_push_repository_activity_failed code=#{OpenAgents.OperationalLog.code(error)}"
118
      )
119
120
      :ok
121
  end
122
101 123
  # ── WAL persist (ack barrier) ───────────────────────────────────────────
102 124
103 125
  defp persist(repo, body, refs_after, principal) do
lib/openagents/repositories.ex modified +19

@@ -500,6 +500,25 @@ defmodule OpenAgents.Repositories do

500 500
    )
501 501
  end
502 502
503
  @doc "Records accepted Git activity and refreshes repository-list subscribers."
504
  def record_push_activity(storage_key, occurred_at \\ DateTime.utc_now())
505
      when is_binary(storage_key) do
506
    case Repo.update_all(
507
           from(repository in Repository,
508
             where: repository.storage_key == ^storage_key,
509
             select: repository.id
510
           ),
511
           set: [updated_at: occurred_at]
512
         ) do
513
      {1, [repository_id]} ->
514
        broadcast_repository_change(repository_id)
515
        :ok
516
517
      {0, []} ->
518
        {:error, :repository_not_found}
519
    end
520
  end
521
503 522
  defp provisioning_topic(repository_id), do: "repository:" <> repository_id
504 523
  defp repository_changes_topic, do: "repositories:changes"
505 524
lib/openagents_web/live/repository_index_live.ex modified +4 -1

@@ -240,7 +240,10 @@ defmodule OpenAgentsWeb.RepositoryIndexLive do

240 240
        <.badge :if={@repository.lifecycle_state != "ready"} variant={state_variant(@repository)}>
241 241
          {@repository.lifecycle_state}
242 242
        </.badge>
243
        <span class="ml-auto shrink-0 text-xs text-muted-foreground">
243
        <span
244
          id={"#{@id}-updated"}
245
          class="ml-auto shrink-0 text-xs text-muted-foreground"
246
        >
244 247
          Updated {relative_time(@repository.updated_at)}
245 248
        </span>
246 249
      </div>
test/openagents/forge/git_http_test.exs modified +16

@@ -108,6 +108,16 @@ defmodule OpenAgents.Forge.GitHTTPTest do

108 108
    user: user
109 109
  } do
110 110
    Phoenix.PubSub.subscribe(OpenAgents.PubSub, "forge:pushes")
111
    Repositories.subscribe_repository_changes()
112
113
    stale_at = DateTime.add(DateTime.utc_now(), -86_400, :second)
114
115
    Repo.update_all(
116
      from(candidate in OpenAgents.Repositories.Repository,
117
        where: candidate.id == ^repository.id
118
      ),
119
      set: [updated_at: stale_at]
120
    )
111 121
112 122
    work = seed_clone!(base, url)
113 123
    commit_and_push!(work, "hello.txt", "hello forge\n", "first commit")

@@ -132,6 +142,12 @@ defmodule OpenAgents.Forge.GitHTTPTest do

132 142
    assert_receive {:forge_push, %{repo: storage_key, wal_seq: 0}}, 2_000
133 143
    assert storage_key == repository.storage_key
134 144
145
    assert_receive {:repository_changed, repository_id}, 2_000
146
    assert repository_id == repository.id
147
148
    refreshed_repository = Repo.get!(OpenAgents.Repositories.Repository, repository.id)
149
    assert DateTime.after?(refreshed_repository.updated_at, stale_at)
150
135 151
    assert %AuditEvent{event_type: "repository.git.write", repository_id: repository_id} =
136 152
             Repo.get_by(AuditEvent,
137 153
               event_type: "repository.git.write",
test/openagents_web/live/repository_live_test.exs modified +24

@@ -53,6 +53,30 @@ defmodule OpenAgentsWeb.RepositoryLiveTest do

53 53
    assert has_element?(view, "#repositories-#{repository.id}-stage")
54 54
  end
55 55
56
  test "repository index shows accepted push activity without a reload", %{conn: conn} do
57
    user = github_user("repository-live-push-activity", "push-activity-owner")
58
59
    assert {:ok, repository, :created} =
60
             Repositories.create_user_repository(
61
               user,
62
               %{name: "active-repository", visibility: "private"},
63
               "push-activity-repository"
64
             )
65
66
    stale_at = DateTime.add(DateTime.utc_now(), -86_400, :second)
67
68
    repository
69
    |> Ecto.Changeset.change(updated_at: stale_at)
70
    |> Repo.update!()
71
72
    {:ok, view, _html} = live(log_in(conn, user), ~p"/repositories")
73
    timestamp = "#repositories-#{repository.id}-updated"
74
75
    assert has_element?(view, timestamp, "Updated 1d ago")
76
    assert :ok = Repositories.record_push_activity(repository.storage_key)
77
    assert has_element?(view, timestamp, "Updated just now")
78
  end
79
56 80
  test "repository index removes a repository deleted after mount", %{conn: conn} do
57 81
    user = github_user("repository-live-removal", "removal-owner")
58 82

This page updates live while a promote is in flight · changelog