From a48a724c41bac7dab92230d62e62077995237ad5 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 1 Nov 2026 18:11:57 +0100 Subject: [PATCH 1/2] fix: count stars and replies on an organization's commits as reaching the organization, not the member who posted them --- app/Queries/BulkActivityQuery.php | 27 ++++++++++---------- tests/Unit/Queries/BulkActivityQueryTest.php | 23 +++++++++++++++++ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/app/Queries/BulkActivityQuery.php b/app/Queries/BulkActivityQuery.php index 8e48ea812..e0fe87c12 100644 --- a/app/Queries/BulkActivityQuery.php +++ b/app/Queries/BulkActivityQuery.php @@ -39,9 +39,10 @@ /** * The accounts that followed, starred, replied or posted the most in the last days, the busiest first, bots left - * out, as long as they reached a threshold no normal use does: a rate a day times the days. Newcomers are the people - * reached who signed up in that time too, which tells an account working through every new signup from an active - * member. Deleted commits still count: what was removed was still sent. + * out, as long as they reached a threshold no normal use does: a rate a day times the days. Whoever is reached is the + * commit's author, so an organization rather than the member who posted for it. Newcomers are the people reached who + * signed up in that time too, which tells an account working through every new signup from an active member. + * Deleted commits still count: what was removed was still sent. * * - Follows: people followed, leaving out the follows imported from GitHub, created in the transaction that records * either side's import, so the 10 seconds before it. @@ -74,28 +75,28 @@ public function get(int $days, int $limit = 10): array limit ? SQL, [$since, $bots, $since, $thresholds['follows']['total'], $limit]), 'stars' => DB::select(<<<'SQL' - select s.user_id, count(*) as count, count(distinct c.user_id) as reach, - count(distinct c.user_id) filter (where author.created_at >= ?) as newcomers + select s.user_id, count(*) as count, count(distinct c.author_id) as reach, + count(distinct c.author_id) filter (where author.created_at >= ?) as newcomers from stars s join users starrer on starrer.id = s.user_id join commits c on c.id = s.commit_id - join users author on author.id = c.user_id - where starrer.github_id <> all(?::text[]) and s.created_at >= ? + left join users author on author.id = c.author_id + where starrer.github_id <> all(?::text[]) and s.created_at >= ? and c.author_id is not null group by s.user_id - having count(distinct c.user_id) >= ? + having count(distinct c.author_id) >= ? order by reach desc, count desc, s.user_id limit ? SQL, [$since, $bots, $since, $thresholds['stars']['total'], $limit]), 'replies' => DB::select(<<<'SQL' - select r.user_id, count(*) as count, count(distinct p.user_id) as reach, - count(distinct p.user_id) filter (where author.created_at >= ?) as newcomers + select r.user_id, count(*) as count, count(distinct p.author_id) as reach, + count(distinct p.author_id) filter (where author.created_at >= ?) as newcomers from commits r join users replier on replier.id = r.user_id join commits p on p.id = r.parent_id - join users author on author.id = p.user_id - where replier.github_id <> all(?::text[]) and r.created_at >= ? and p.user_id <> r.user_id + left join users author on author.id = p.author_id + where replier.github_id <> all(?::text[]) and r.created_at >= ? and p.author_id <> r.user_id group by r.user_id - having count(distinct p.user_id) >= ? + having count(distinct p.author_id) >= ? order by reach desc, count desc, r.user_id limit ? SQL, [$since, $bots, $since, $thresholds['replies']['total'], $limit]), diff --git a/tests/Unit/Queries/BulkActivityQueryTest.php b/tests/Unit/Queries/BulkActivityQueryTest.php index a6971f101..0a8098937 100644 --- a/tests/Unit/Queries/BulkActivityQueryTest.php +++ b/tests/Unit/Queries/BulkActivityQueryTest.php @@ -4,6 +4,7 @@ use App\Actions\CreateTomFriendship; use App\Models\Commit; +use App\Models\Organization; use App\Models\User; use App\Queries\BulkActivityQuery; use Illuminate\Database\Eloquent\Collection; @@ -83,6 +84,28 @@ ->sequence(fn ($row) => $row->user->id->toBe($mass->id)->reach->toBe(20)->count->toBe(20)); }); +it("counts stars and replies on an organization's commits as reaching the organization, not the members who posted them", function (): void { + $organization = Organization::factory()->create(); + $commits = ($this->people)(30, '2026-10-23 00:00:00')->map(fn (User $member): Commit => Commit::factory()->for($member)->create([ + 'author_id' => $organization->id, + 'created_at' => '2026-10-22 00:00:00', + ])); + $fan = User::factory()->create(); + $mass = User::factory()->create(); + + $commits->each(function (Commit $commit) use ($fan): void { + $commit->stars()->attach($fan, ['created_at' => '2026-10-23 01:00:00', 'updated_at' => '2026-10-23 01:00:00']); + Commit::factory()->for($fan)->for($commit, 'parent')->create(['created_at' => '2026-10-23 01:00:00']); + }); + ($this->star)($mass, ($this->people)(29)); + $commits->first()->stars()->attach($mass, ['created_at' => '2026-10-23 01:00:00', 'updated_at' => '2026-10-23 01:00:00']); + + expect(($this->activity)()['stars']) + ->toHaveCount(1) + ->sequence(fn ($row) => $row->user->id->toBe($mass->id)->reach->toBe(30)->newcomers->toBe(0)) + ->and(($this->activity)()['replies'])->toBe([]); +}); + it('lists the accounts that posted 15 times a day', function (): void { $mass = User::factory()->create(); $prolific = User::factory()->create(); From a2accbd3a7ceb393b4c4ff590faef8e24d3e427c Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 1 Nov 2026 21:17:42 +0100 Subject: [PATCH 2/2] fix: still count stars and replies on the commits of a deleted organization, which reach no one --- app/Queries/BulkActivityQuery.php | 4 ++-- tests/Unit/Queries/BulkActivityQueryTest.php | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/Queries/BulkActivityQuery.php b/app/Queries/BulkActivityQuery.php index e0fe87c12..808c0ab5c 100644 --- a/app/Queries/BulkActivityQuery.php +++ b/app/Queries/BulkActivityQuery.php @@ -81,7 +81,7 @@ public function get(int $days, int $limit = 10): array join users starrer on starrer.id = s.user_id join commits c on c.id = s.commit_id left join users author on author.id = c.author_id - where starrer.github_id <> all(?::text[]) and s.created_at >= ? and c.author_id is not null + where starrer.github_id <> all(?::text[]) and s.created_at >= ? group by s.user_id having count(distinct c.author_id) >= ? order by reach desc, count desc, s.user_id @@ -94,7 +94,7 @@ public function get(int $days, int $limit = 10): array join users replier on replier.id = r.user_id join commits p on p.id = r.parent_id left join users author on author.id = p.author_id - where replier.github_id <> all(?::text[]) and r.created_at >= ? and p.author_id <> r.user_id + where replier.github_id <> all(?::text[]) and r.created_at >= ? and p.author_id is distinct from r.user_id group by r.user_id having count(distinct p.author_id) >= ? order by reach desc, count desc, r.user_id diff --git a/tests/Unit/Queries/BulkActivityQueryTest.php b/tests/Unit/Queries/BulkActivityQueryTest.php index 0a8098937..65d0e3070 100644 --- a/tests/Unit/Queries/BulkActivityQueryTest.php +++ b/tests/Unit/Queries/BulkActivityQueryTest.php @@ -106,6 +106,22 @@ ->and(($this->activity)()['replies'])->toBe([]); }); +it('still counts stars and replies on the commits of a deleted organization, which reach no one', function (): void { + $starrer = User::factory()->create(); + $replier = User::factory()->create(); + $orphan = Commit::factory()->create(['author_id' => null, 'created_at' => '2026-10-22 00:00:00']); + + ($this->star)($starrer, ($this->people)(30)); + $orphan->stars()->attach($starrer, ['created_at' => '2026-10-23 01:00:00', 'updated_at' => '2026-10-23 01:00:00']); + ($this->reply)($replier, ($this->people)(20)); + Commit::factory()->for($replier)->for($orphan, 'parent')->create(['created_at' => '2026-10-23 01:00:00']); + + expect(($this->activity)()['stars']) + ->sequence(fn ($row) => $row->user->id->toBe($starrer->id)->reach->toBe(30)->count->toBe(31)) + ->and(($this->activity)()['replies']) + ->sequence(fn ($row) => $row->user->id->toBe($replier->id)->reach->toBe(20)->count->toBe(21)); +}); + it('lists the accounts that posted 15 times a day', function (): void { $mass = User::factory()->create(); $prolific = User::factory()->create();