Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions app/Helper/MWTimestampHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,12 @@
namespace App\Helper;

use Carbon\CarbonImmutable;
use Carbon\Exceptions\InvalidFormatException;

class MWTimestampHelper {
private const MWTimestampFormat = 'YmdHis';

public static function getCarbonFromMWTimestamp(string $MWTimestamp): CarbonImmutable {
$carbon = CarbonImmutable::createFromFormat(self::MWTimestampFormat, $MWTimestamp);
if ($carbon === null) {
throw new InvalidFormatException('Unable to create Carbon object');
}

return $carbon;
}
Expand Down
3 changes: 3 additions & 0 deletions app/Http/Controllers/ConversionMetricController.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ public function index(Request $request) {
$daysSinceLastEdit = null;

if ($wikiLastEditedTime !== null) {
// Cast to int to avoid fractional-day values in CSV/JSON and keep cutoff logic predictable.
Comment thread
dati18 marked this conversation as resolved.
Outdated
$daysSinceLastEdit = (int) $wikiLastEditedTime->diffInDays($current_date, false);
}

if ($daysSinceLastEdit !== null && $daysSinceLastEdit >= 90) {
// Keep exported duration as whole days for stable reporting.
Comment thread
dati18 marked this conversation as resolved.
Outdated
$time_before_wiki_abandoned_days = (int) $wiki->created_at->diffInDays($wikiLastEditedTime, false);
}
if ($wikiFirstEditedTime !== null) {
// Keep exported duration as whole days for stable reporting.
Comment thread
dati18 marked this conversation as resolved.
Outdated
$time_to_engage_days = (int) $wiki->created_at->diffInDays($wikiFirstEditedTime, false);
}
$wiki_number_of_editors = $wiki->wikiSiteStats()->first()['activeusers'] ?? null;
Expand Down
1 change: 1 addition & 0 deletions app/Jobs/PlatformStatsSummaryJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public function prepareStats(array $allStats, $wikis): array {
// is it edited in the last 90 days?
if (!is_null($stats['lastEdit'])) {
$lastTimestamp = MWTimestampHelper::getCarbonFromMWTimestamp(intval($stats['lastEdit']));
// Carbon can return fractional seconds; cast to int so threshold checks use whole-second precision.
Comment thread
dati18 marked this conversation as resolved.
Outdated
$diff = (int) $lastTimestamp->diffInSeconds($currentTime, true);

if ($diff <= $this->inactiveThreshold) {
Expand Down
1 change: 1 addition & 0 deletions app/Jobs/SendEmptyWikiNotificationsJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public function checkIfWikiIsOldAndEmpty(Wiki $wiki) {
$emptyDaysThreshold = config('wbstack.wiki_empty_notification_threshold');
$createdAt = $wiki->created_at;
$now = CarbonImmutable::now();
// Normalize to whole days so comparisons with the integer config threshold stay deterministic.
Comment thread
dati18 marked this conversation as resolved.
Outdated
$emptyWikiDays = (int) $createdAt->diffInDays($now, true);

$firstEdited = $wiki->wikiLifecycleEvents->first_edited;
Expand Down
46 changes: 46 additions & 0 deletions tests/Jobs/PlatformStatsSummaryJobTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,50 @@ public function testCreationStats() {
);

}

public function testPrepareStatsTreatsSecondPrecisionTimestampAtThresholdAsActive() {
$currentTime = CarbonImmutable::now();

$wiki = Wiki::factory()->create(['deleted_at' => null, 'domain' => 'thresholdtest.com']);
WikiDb::create([
'name' => 'mwdb_threshold_' . $wiki->id,
'user' => 'user',
'password' => 'password',
'version' => 'version',
'prefix' => 'prefix',
'wiki_id' => $wiki->id,
]);

Http::fake([
$this->mwBackendHost . '/w/api.php?action=query&list=allpages&apnamespace=122&apcontinue=&aplimit=max&format=json' => Http::response([
'query' => ['allpages' => []],
], 200),
$this->mwBackendHost . '/w/api.php?action=query&list=allpages&apnamespace=120&apcontinue=&aplimit=max&format=json' => Http::response([
'query' => ['allpages' => []],
], 200),
]);

$job = new PlatformStatsSummaryJob;
(function ($resolver): void {
$this->mwHostResolver = $resolver;
})->call($job, $this->mockMwHostResolver);
Comment thread
dati18 marked this conversation as resolved.

$groups = $job->prepareStats([
[
'wiki' => 'thresholdtest.com',
'edits' => 1,
'pages' => 1,
'users' => 1,
'active_users' => 1,
'lastEdit' => MWTimestampHelper::getMWTimestampFromCarbon(
$currentTime->subSeconds(config('wbstack.platform_summary_inactive_threshold'))
),
'first100UsingOauth' => '0',
'platform_summary_version' => 'v1',
],
], [$wiki]);

$this->assertSame(1, $groups['edited_last_90_days']);
$this->assertSame(0, $groups['not_edited_last_90_days']);
}
}
20 changes: 19 additions & 1 deletion tests/Jobs/SendEmptyWikiNotificationsJobTest.php
Comment thread
dati18 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function testEmptyWikiNotificationsSendNotification() {
Notification::fake();
$user = User::factory()->create(['verified' => true]);
$wiki = Wiki::factory()->create(['created_at' => $thresholdDaysAgo]);
$manager = WikiManager::factory()->create(['wiki_id' => $wiki->id, 'user_id' => $user->id]);
WikiManager::factory()->create(['wiki_id' => $wiki->id, 'user_id' => $user->id]);
Comment thread
dati18 marked this conversation as resolved.
$wiki->wikiLifecycleEvents()->updateOrCreate(['first_edited' => null]);

$job = new SendEmptyWikiNotificationsJob;
Expand All @@ -50,6 +50,24 @@ public function testEmptyWikiNotificationsSendNotification() {
);
}

// empty wikis, that are almost old enough (29 days and 23 hrs)
public function testEmptyWikiNotificationsNotSendNotification() {
Comment thread
dati18 marked this conversation as resolved.
Outdated
$thresholdDaysAgo = Carbon::now()
->subDays((config('wbstack.wiki_empty_notification_threshold') - 1))
->subHours(23)
->toDateTimeString();

Notification::fake();
$user = User::factory()->create(['verified' => true]);
$wiki = Wiki::factory()->create(['created_at' => $thresholdDaysAgo]);
WikiManager::factory()->create(['wiki_id' => $wiki->id, 'user_id' => $user->id]);
$wiki->wikiLifecycleEvents()->updateOrCreate(['first_edited' => null]);

$job = new SendEmptyWikiNotificationsJob;
$this->assertFalse($job->checkIfWikiIsOldAndEmpty($wiki));
$job->handle();
Comment thread
dati18 marked this conversation as resolved.
}

// fresh wiki that does not have lifecycle event records yet
public function testEmptyWikiNotificationsFreshWiki() {
$now = Carbon::now()->toDateTimeString();
Expand Down
37 changes: 35 additions & 2 deletions tests/Routes/Wiki/ConversionMetricTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ private function createTestWiki($name, $createdWeeksAgo, $firstEditedWeeksAgo, $
$wiki->created_at = $current_date->subWeeks($createdWeeksAgo);
$events = $wiki->wikiLifecycleEvents();
$update = [];
if ($lastEditedWeeksAgo) {
if ($lastEditedWeeksAgo !== null) {
$update['last_edited'] = $current_date->subWeeks($lastEditedWeeksAgo);
}
if ($firstEditedWeeksAgo) {
if ($firstEditedWeeksAgo !== null) {
Comment thread
dati18 marked this conversation as resolved.
Outdated
$update['first_edited'] = $current_date->subWeeks($firstEditedWeeksAgo);
}
$events->updateOrCreate($update);
Expand Down Expand Up @@ -134,6 +134,39 @@ public function testDownloadJson() {
);
}

public function testDownloadJsonTruncatesFractionalDayDiffs() {
$currentDate = CarbonImmutable::now();
$createdAt = $currentDate->subDays(200)->subHours(12); // 200.5 days ago
$firstEditedAt = $createdAt->addDays(1)->addHours(12); // 1.5 days after
$lastEditedAt = $currentDate->subDays(100); // 100 days ago

$wiki = Wiki::factory()->create([
'domain' => 'fractional.days.cloud',
'sitename' => 'Fractional Days Site',
]);
WikiSiteStats::factory()->create([
'wiki_id' => $wiki->id,
'pages' => 77,
'activeusers' => 2,
]);
$wiki->created_at = $createdAt;
$wiki->wikiLifecycleEvents()->updateOrCreate([
'first_edited' => $firstEditedAt,
'last_edited' => $lastEditedAt,
]);
$wiki->save();

$response = $this->getJson($this->route);

$response->assertStatus(200);
$response->assertJsonFragment([
'domain' => 'fractional.days.cloud',
'time_to_engage_days' => 1,
'time_before_wiki_abandoned_days' => 100,
'number_of_active_editors' => 2,
]);
}

public function testFunctionalWithMissingLifecycleEventsandStats() {
$wiki = Wiki::factory()->create([
'domain' => 'very.new.wikibase.cloud', 'sitename' => 'bsite',
Expand Down
Loading