From bd5774eeb2b68aba85c0118ebd3b3567987e93bc Mon Sep 17 00:00:00 2001 From: mauricius Date: Thu, 30 Jul 2020 10:17:31 +0200 Subject: [PATCH] Fix count unique opens per period in Postgres --- .../PostgresMessageTenantRepository.php | 4 +- .../MessageTenantRepositoryTest.php | 187 ++++++++++++++++++ 2 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Repositories/MessageTenantRepositoryTest.php diff --git a/src/Repositories/Messages/PostgresMessageTenantRepository.php b/src/Repositories/Messages/PostgresMessageTenantRepository.php index 327b218d..42f18c1b 100644 --- a/src/Repositories/Messages/PostgresMessageTenantRepository.php +++ b/src/Repositories/Messages/PostgresMessageTenantRepository.php @@ -15,12 +15,12 @@ class PostgresMessageTenantRepository extends BaseMessageTenantRepository public function countUniqueOpensPerPeriod(int $workspaceId, string $sourceType, int $sourceId, int $intervalInSeconds): Collection { return DB::table('messages') - ->select(DB::raw("COUNT(*) as open_count, MIN(opened_at) as opened_at, round(extract('epoch' from opened_at) / $intervalInSeconds) * $intervalInSeconds as period_start")) + ->selectRaw("COUNT(*) as open_count, MIN(opened_at) as opened_at, to_char(to_timestamp(floor(extract('epoch' from opened_at) / $intervalInSeconds) * $intervalInSeconds),'YYYY-MM-DD HH24:MI:SS') as period_start") ->where('workspace_id', $workspaceId) ->where('source_type', $sourceType) ->where('source_id', $sourceId) ->whereNotNull('opened_at') - ->groupBy(DB::raw("round(extract('epoch' from opened_at) / " . $intervalInSeconds . ")")) + ->groupByRaw("floor(extract('epoch' from opened_at) / $intervalInSeconds)") ->orderBy('opened_at') ->get(); } diff --git a/tests/Unit/Repositories/MessageTenantRepositoryTest.php b/tests/Unit/Repositories/MessageTenantRepositoryTest.php new file mode 100644 index 00000000..028b5419 --- /dev/null +++ b/tests/Unit/Repositories/MessageTenantRepositoryTest.php @@ -0,0 +1,187 @@ +repository = app()->make(MessageTenantRepositoryInterface::class); + } + + /** @test */ + function it_should_not_count_messages_that_have_not_been_opened_yet() + { + $workspace = factory(Workspace::class)->create(); + + $campaign = factory(Campaign::class)->create([ + 'workspace_id' => $workspace->id + ]); + + factory(Message::class, 2)->create([ + 'workspace_id' => $workspace->id, + 'source_id' => $campaign->id, + ]); + + $data = $this->repository->countUniqueOpensPerPeriod($workspace->id, get_class($campaign), $campaign->id, CarbonInterval::day()->totalSeconds); + + $this->assertInstanceOf(Collection::class, $data); + $this->assertTrue($data->isEmpty()); + } + + /** @test */ + function it_should_count_messages_that_have_been_opened_grouped_by_day_period() + { + $opened_at = CarbonImmutable::create(2020, 05, 9, 20); + + $workspace = factory(Workspace::class)->create(); + + $campaign = factory(Campaign::class)->create([ + 'workspace_id' => $workspace->id + ]); + + // 20 - 21 - 22 - 23 + foreach (range(0, 3) as $i) { + factory(Message::class)->create([ + 'workspace_id' => $workspace->id, + 'source_id' => $campaign->id, + 'opened_at' => $opened_at->addHours($i) + ]); + } + + // 24 + $next_opened_at = $opened_at->addHours(4); + + factory(Message::class)->create([ + 'workspace_id' => $workspace->id, + 'source_id' => $campaign->id, + 'opened_at' => $next_opened_at + ]); + + $data = $this->repository->countUniqueOpensPerPeriod($workspace->id, get_class($campaign), $campaign->id, CarbonInterval::day()->totalSeconds); + + $this->assertEquals(2, $data->count()); + + $this->assertEquals(4, $data->first()->open_count); + $this->assertEquals($opened_at->toDateTimeString(), $data->first()->opened_at); + $this->assertEquals($opened_at->startOfDay()->toDateTimeString(), $data->first()->period_start); + + $this->assertEquals(1, $data->last()->open_count); + $this->assertEquals($next_opened_at->toDateTimeString(), $data->last()->opened_at); + $this->assertEquals($next_opened_at->startOfDay()->toDateTimeString(), $data->last()->period_start); + } + + /** @test */ + function it_should_count_messages_that_have_been_opened_grouped_by_two_hours_period() + { + $opened_at = CarbonImmutable::create(2020, 05, 9, 20); + + $workspace = factory(Workspace::class)->create(); + + $campaign = factory(Campaign::class)->create([ + 'workspace_id' => $workspace->id + ]); + + // 20 - 21 - 22 - 23 + foreach (range(0, 3) as $i) { + factory(Message::class)->create([ + 'workspace_id' => $workspace->id, + 'source_id' => $campaign->id, + 'opened_at' => $opened_at->addHours($i) + ]); + } + + // 24 + $next_opened_at = $opened_at->addHours(4); + + factory(Message::class)->create([ + 'workspace_id' => $workspace->id, + 'source_id' => $campaign->id, + 'opened_at' => $next_opened_at + ]); + + $data = $this->repository->countUniqueOpensPerPeriod($workspace->id, get_class($campaign), $campaign->id, CarbonInterval::hours(2)->totalSeconds); + + $this->assertEquals(3, $data->count()); + + // 20 + $this->assertEquals(2, $data[0]->open_count); + $this->assertEquals($opened_at->toDateTimeString(), $data[0]->opened_at); + $this->assertEquals($opened_at->toDateTimeString(), $data[0]->period_start); + + // 22 + $this->assertEquals(2, $data[0]->open_count); + $this->assertEquals($opened_at->addHours(2)->toDateTimeString(), $data[1]->opened_at); + $this->assertEquals($opened_at->addHours(2)->toDateTimeString(), $data[1]->period_start); + + // 24 + $this->assertEquals(1, $data[2]->open_count); + $this->assertEquals($next_opened_at->toDateTimeString(), $data[2]->opened_at); + $this->assertEquals($next_opened_at->startOfDay()->toDateTimeString(), $data[2]->period_start); + } + + /** @test */ + function it_should_count_messages_that_have_been_opened_grouped_by_hour_period() + { + $opened_at = CarbonImmutable::create(2020, 05, 9, 20); + + $workspace = factory(Workspace::class)->create(); + + $campaign = factory(Campaign::class)->create([ + 'workspace_id' => $workspace->id + ]); + + // 20 - 21 - 22 - 23 + foreach (range(0, 3) as $i) { + factory(Message::class)->create([ + 'workspace_id' => $workspace->id, + 'source_id' => $campaign->id, + 'opened_at' => $opened_at->addHours($i) + ]); + } + + // 24 + $next_opened_at = $opened_at->addHours(4); + + factory(Message::class)->create([ + 'workspace_id' => $workspace->id, + 'source_id' => $campaign->id, + 'opened_at' => $next_opened_at + ]); + + $data = $this->repository->countUniqueOpensPerPeriod($workspace->id, get_class($campaign), $campaign->id, CarbonInterval::hour()->totalSeconds); + + $this->assertEquals(5, $data->count()); + + foreach (range(0, 3) as $i) { + $this->assertEquals(1, $data[$i]->open_count); + $this->assertEquals($opened_at->addHours($i)->toDateTimeString(), $data[$i]->period_start); + } + + $this->assertEquals(1, $data->last()->open_count); + $this->assertEquals($next_opened_at->toDateTimeString(), $data->last()->opened_at); + $this->assertEquals($next_opened_at->startOfDay()->toDateTimeString(), $data->last()->period_start); + } +}