Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(platform-stats): provide number of recently created wikis and users #617

Merged
merged 13 commits into from
Jul 5, 2023
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# api

## 8x.14.0 - TBD
- Collect signup and wiki creation rate metrics with platform summary job

## 8x.13.0 - 22 June 2023
- Force lowercase domain names on wiki creation

Expand Down
47 changes: 31 additions & 16 deletions app/Jobs/PlatformStatsSummaryJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
namespace App\Jobs;

use App\Wiki;
use App\User;
use Illuminate\Database\DatabaseManager;
use PDO;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Illuminate\Notifications\Notifiable;
use App\Notifications\PlatformStatsSummaryNotification;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\App;

/*
Expand All @@ -30,24 +28,39 @@
class PlatformStatsSummaryJob extends Job
{
private $inactiveThreshold;
private $creationRateRanges;

private $platformSummaryStatsVersion = "v1";
public function __construct() {
$this->inactiveThreshold = Config::get('wbstack.platform_summary_inactive_threshold');
$this->creationRateRanges = Config::get('wbstack.platform_summary_creation_rate_ranges');
}

private function isNullOrEmpty( $value ): bool {
return is_null($value) || intVal($value) === 0;
}

public function prepareStats( array $allStats, $wikis ): array {
public function getCreationStats(): array {
$result = [];
$now = Carbon::now();
foreach ($this->creationRateRanges as $range) {
$limit = $now->clone()->sub(new \DateInterval($range));
$wikis = Wiki::where('created_at', '>=', $limit)->count();
$result['wikis_created_'.$range] = $wikis;
$users = User::where('created_at', '>=', $limit)->count();
$result['users_created_'.$range] = $users;
}
return $result;
}

$deletedWikis = [];
$activeWikis= [];
$inactive = [];
$emptyWikis = [];
public function prepareStats( array $allStats, $wikis): array {

$deletedWikis = [];
$activeWikis = [];
$inactive = [];
$emptyWikis = [];
$nonDeletedStats = [];

$currentTime = Carbon::now()->timestamp;

foreach( $wikis as $wiki ) {
Expand All @@ -60,7 +73,6 @@ public function prepareStats( array $allStats, $wikis ): array {
$wikiDb = $wiki->wikiDb()->first();

if( !$wikiDb ) {

Log::error(__METHOD__ . ": Could not find WikiDB for {$wiki->domain}");
continue;
}
Expand All @@ -71,7 +83,7 @@ public function prepareStats( array $allStats, $wikis ): array {
Log::warning(__METHOD__ . ": Could not find stats for {$wiki->domain}");
continue;
}

$stats = $allStats[$found_key];

// is it empty?
Expand All @@ -86,7 +98,7 @@ public function prepareStats( array $allStats, $wikis ): array {
if(!is_null($stats['lastEdit'])){
$lastTimestamp = intVal($stats['lastEdit']);
$diff = $currentTime - $lastTimestamp;

if ($diff >= $this->inactiveThreshold) {
$inactive[] = $wiki;
continue;
Expand All @@ -95,7 +107,7 @@ public function prepareStats( array $allStats, $wikis ): array {

$activeWikis[] = $wiki;
}

$totalNonDeletedUsers = array_sum(array_column($nonDeletedStats, 'users'));
$totalNonDeletedActiveUsers = array_sum(array_column($nonDeletedStats, 'active_users'));
$totalNonDeletedPages = array_sum(array_column($nonDeletedStats, 'pages'));
Expand Down Expand Up @@ -133,11 +145,11 @@ public function handle( DatabaseManager $manager ): void
$mediawikiPdo = $mwConn->getPdo();

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);

// prepare the first query
$statement = $pdo->prepare($this->wikiStatsQuery);
$statement->execute();

// produces the stats query
$result = $statement->fetchAll(PDO::FETCH_ASSOC)[0];
$query = array_values($result)[0];
Expand All @@ -146,9 +158,12 @@ public function handle( DatabaseManager $manager ): void
$allStats = $mediawikiPdo->query($query)->fetchAll(PDO::FETCH_ASSOC);
$summary = $this->prepareStats( $allStats, $wikis );

$creationStats = $this->getCreationStats();
$summary = array_merge($summary, $creationStats);

$manager->purge('mw');
$manager->purge('mysql');

// Output to be scraped from logs
if( !App::runningUnitTests() ) {
print( json_encode($summary) . PHP_EOL );
Expand Down Expand Up @@ -183,7 +198,7 @@ public function handle( DatabaseManager $manager ): void
"

) SEPARATOR ' UNION ALL ')

FROM apidb.wiki_dbs
LEFT JOIN apidb.wikis ON wiki_dbs.wiki_id = wikis.id;

Expand Down
3 changes: 3 additions & 0 deletions config/wbstack.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
'wiki_max_per_user' => env('WBSTACK_MAX_PER_USER', false),

'platform_summary_inactive_threshold' => env('WBSTACK_SUMMARY_INACTIVE_THRESHOLD', 60 * 60 * 24 * 90),
'platform_summary_creation_rate_ranges' => array_filter(
explode(',', env('WBSTACK_SUMMARY_CREATION_RATE_RANGES', ''))
),

'elasticsearch_host' => env('ELASTICSEARCH_HOST', false),
'elasticsearch_enabled_by_default' => env('WBSTACK_ELASTICSEARCH_ENABLED_BY_DEFAULT', false),
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
<env name="CACHE_DRIVER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="PLATFORM_MW_BACKEND_HOST" value="mediawiki-139-app-backend.default.svc.cluster.default"/>
<env name="WBSTACK_SUMMARY_CREATION_RATE_RANGES" value="PT24H,P30D"/>
m90 marked this conversation as resolved.
Show resolved Hide resolved
</php>
</phpunit>
72 changes: 54 additions & 18 deletions tests/Jobs/PlatformStatsSummaryJobTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Tests\Jobs;

use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\User;
use App\Wiki;
Expand All @@ -16,28 +16,29 @@

class PlatformStatsSummaryJobTest extends TestCase
{
use DatabaseTransactions;
use RefreshDatabase;

private $numWikis = 5;
private $wikis = [];
private $users = [];

private $db_prefix = "somecoolprefix";
private $db_name = "some_cool_db_name";

protected function setUp(): void {
parent::setUp();
for($n = 0; $n < $this->numWikis; $n++ ) {
for ($n = 0; $n < $this->numWikis; $n++) {
DB::connection('mysql')->getPdo()->exec("DROP DATABASE IF EXISTS {$this->db_name}{$n};");
}
$this->seedWikis();
$this->manager = $this->app->make('db');
$this->wikis = [];
$this->users = [];
}

protected function tearDown(): void {
foreach($this->wikis as $wiki) {
$wiki['wiki']->wikiDb()->forceDelete();
$wiki['wiki']->forceDelete();
}
Wiki::query()->delete();
User::query()->delete();
WikiManager::query()->delete();
WikiDb::query()->delete();
Comment on lines +38 to +41
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure why this is still needed when RefreshDatabase is used. If it's not there, each test in this suite will see items created by its preceding siblings.

Copy link
Contributor

@deer-wmde deer-wmde Jul 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds familiar - I think that even may be the reason why we have this target in the Makefile to run tests with a clean database https://github.com/wbstack/api/blob/main/Makefile#L11

(I'm not trying to say this is a good thing - something doesn't seem to work there)

edit: nevermind, I think that was introduced for a different case (running the same test again, not several tests in the same test case)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found another trait called DatabaseMigrations: https://laravel.com/docs/8.x/dusk#migrations

It seems we don't need the extended tearDown method when using this trait. (at least the test completes successfully repeatedly with it for me)

I'm not 100% certain this is the right thing to do, as I found this in a browser automation testing section in the laravel docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what Dusk even is? It might be worth spending some time in removing all statefulness from all tests at some point. It's kind of suboptimal this bites me every single time I add a feature and spend a lot of time debugging why my new test now breaks another test. Ideally, we could even aim for having the tests run using an in-memory database so that we can just run them locally without a database proper.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There has been work on this here #559 but I'm not sure why it was abandoned.

parent::tearDown();
}

Expand All @@ -55,15 +56,14 @@ private function seedWikis() {
$wikiDb = WikiDb::whereName($this->db_name.$n)->first();
$wikiDb->update( ['wiki_id' => $wiki->id] );

$this->wikis[] = [
'user' => $user,
'wiki' => Wiki::whereId($wiki->id)->with('wikidb')->first()
];
$this->wikis[] = $wiki;
$this->users[] = $user;
}

}
public function testQueryGetsStats()
{
$this->seedWikis();
$manager = $this->app->make('db');

$mockJob = $this->createMock(Job::class);
Expand All @@ -83,16 +83,15 @@ public function testGroupings()
$job = new PlatformStatsSummaryJob();
$job->setJob($mockJob);

$testWikis = [
$wikis = [
Wiki::factory()->create( [ 'deleted_at' => null, 'domain' => 'wiki1.com' ] ),
Wiki::factory()->create( [ 'deleted_at' => null, 'domain' => 'wiki2.com' ] ),
Wiki::factory()->create( [ 'deleted_at' => Carbon::now()->subDays(90)->timestamp, 'domain' => 'wiki3.com' ] ),
Wiki::factory()->create( [ 'deleted_at' => null, 'domain' => 'wiki4.com' ] )

];

foreach($testWikis as $wiki) {
$wikiDB = WikiDb::create([
foreach($wikis as $wiki) {
WikiDb::create([
'name' => 'mwdb_asdasfasfasf' . $wiki->id,
'user' => 'asdasd',
'password' => 'asdasfasfasf',
Expand Down Expand Up @@ -147,7 +146,7 @@ public function testGroupings()
];


$groups = $job->prepareStats($stats, $testWikis);
$groups = $job->prepareStats($stats, $wikis);

$this->assertEquals(
[
Expand All @@ -165,6 +164,43 @@ public function testGroupings()
$groups,
);
}
function testCreationStats() {
$mockJob = $this->createMock(Job::class);
$mockJob->expects($this->never())->method('fail');

$job = new PlatformStatsSummaryJob();
$job->setJob($mockJob);

Wiki::factory()->create([
'created_at' => Carbon::now()->subHours(1)
]);
Wiki::factory()->create([
'created_at' => Carbon::now()->subDays(2)
]);
Wiki::factory()->create([
'created_at' => Carbon::now()->subDays(90)
]);
User::factory()->create([
'created_at' => Carbon::now()->subHours(1)
]);
User::factory()->create([
'created_at' => Carbon::now()->subHours(2)
]);
User::factory()->create([
'created_at' => Carbon::now()->subDays(200)
]);

$stats = $job->getCreationStats();

$this->assertEquals(
[
'wikis_created_PT24H' => 1,
'wikis_created_P30D' => 2,
'users_created_PT24H' => 2,
'users_created_P30D' => 2,
],
$stats,
);

}
}
Loading