Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ExpensiveUserImpact
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 8
110
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getDailyTotalViews
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDailyArticleViews
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 newEmpty
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 loadFromJsonArray
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 filterViewCounts
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 jsonSerialize
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 isPageViewDataStale
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace GrowthExperiments\UserImpact;
4
5use DateTime;
6use Exception;
7use MediaWiki\User\UserIdentity;
8use MediaWiki\User\UserIdentityValue;
9use MediaWiki\Utils\MWTimestamp;
10use Wikimedia\Assert\Assert;
11
12/**
13 * Value object representing a user's impact statistics, including those which are
14 * more expensive to retrieve.
15 */
16class ExpensiveUserImpact extends UserImpact {
17
18    /** @var int[] */
19    private array $dailyTotalViews;
20
21    /** @var array */
22    private array $dailyArticleViews;
23
24    /**
25     * @param UserIdentity $user
26     * @param int $receivedThanksCount Number of thanks the user has received. Might exclude
27     *   thanks received a long time ago.
28     * @param int[] $editCountByNamespace Namespace ID => number of edits the user made in some
29     *   namespace. Might exclude edits made a long time ago or many edits ago.
30     * @param int[] $editCountByDay Day => number of edits the user made on that day. Indexed with
31     *   ISO 8601 dates, e.g. '2022-08-25'. Might exclude edits made many edits ago.
32     * @param array<string,int> $editCountByTaskType Number of newcomer task edits per task type.
33     * @param int $revertedEditCount Number of edits by the user that got reverted (determined by
34     * the mw-reverted tag).
35     * @param int $newcomerTaskEditCount Number of edits the user made which have the
36     *   newcomer task tag. Might exclude edits made a long time ago or many edits ago.
37     * @param int|null $lastEditTimestamp Unix timestamp of the user's last edit.
38     * @param int[] $dailyTotalViews Day => number of total pageviews the articles edited by the user
39     *   (on any day) got on that day. Indexed with ISO 8601 dates, e.g. '2022-08-25'.
40     *   Might exclude edits made many days or many edits ago.
41     * @param array $dailyArticleViews @see ::getDailyArticleViews
42     * @param EditingStreak $longestEditingStreak
43     * @param int|null $totalUserEditCount Copy of user.user_editcount
44     */
45    public function __construct(
46        UserIdentity $user,
47        int $receivedThanksCount,
48        array $editCountByNamespace,
49        array $editCountByDay,
50        array $editCountByTaskType,
51        int $revertedEditCount,
52        int $newcomerTaskEditCount,
53        ?int $lastEditTimestamp,
54        array $dailyTotalViews,
55        array $dailyArticleViews,
56        EditingStreak $longestEditingStreak,
57        ?int $totalUserEditCount
58    ) {
59        parent::__construct( $user, $receivedThanksCount, $editCountByNamespace, $editCountByDay,
60            $editCountByTaskType, $revertedEditCount, $newcomerTaskEditCount, $lastEditTimestamp,
61            $longestEditingStreak, $totalUserEditCount );
62        $this->dailyTotalViews = $dailyTotalViews;
63        $this->dailyArticleViews = $dailyArticleViews;
64    }
65
66    /**
67     * Day => number of total pageviews the articles edited by the user (on any day) got on that day.
68     * Indexed with ISO 8601 dates, e.g. '2022-08-25'. The list of days is contiguous, in ascending
69     * order, and ends more or less at the current day (might be a few days off to account for data
70     * collection lags).
71     * Might exclude edits made many days or many edits ago.
72     * @return int[]
73     */
74    public function getDailyTotalViews(): array {
75        return $this->dailyTotalViews;
76    }
77
78    /**
79     * An array with the title's prefixed DBkey as the key. The values contain:
80     * - firstEditDate: The date the user first edited the article, in Y-m-d format.
81     *   If the user made a very high number of total edits, it might just be some edit the
82     *   user made to the article, not necessarily the first.
83     * - newestEdit: The TS_MW timestamp of the newest edit by the user to the article.
84     * - views: An array of page view counts. Each row in the array has a key with the date in
85     *   Y-m-d format and the value is the page view count total for that day.
86     *   The list of days is contiguous, in ascending order, and ends more or less at the current
87     *   day (might be a few days off to account for data collection lags).
88     * - imageUrl: URL of a thumbnail of the article's main image, or null if there's no main image.
89     * @return array[]
90     * @phan-return array<string,array{views:array<string,int>,firstEditDate:string,newestEdit:string,imageUrl:?string}>
91     */
92    public function getDailyArticleViews(): array {
93        return $this->dailyArticleViews;
94    }
95
96    /** @inheritDoc */
97    protected static function newEmpty(): self {
98        return new ExpensiveUserImpact(
99            new UserIdentityValue( 0, '' ),
100            0,
101            [],
102            [],
103            [],
104            0,
105            0,
106            0,
107            [],
108            [],
109            new EditingStreak(),
110            0
111        );
112    }
113
114    /** @inheritDoc */
115    protected function loadFromJsonArray( array $json ): void {
116        parent::loadFromJsonArray( $json );
117
118        Assert::parameterKeyType( 'string', $json['dailyTotalViews'], '$json[\'dailyTotalViews\']' );
119        Assert::parameterElementType( 'integer', $json['dailyTotalViews'], '$json[\'dailyTotalViews\']' );
120        Assert::parameterElementType( 'array', $json['dailyArticleViews'], '$json[\'dailyArticleViews\']' );
121        foreach ( $json['dailyArticleViews'] as $title => $views ) {
122            Assert::parameterKeyType( 'string', $views, '$json[\'dailyArticleViews\'][\'' . $title . '\']' );
123        }
124
125        $this->dailyTotalViews = $json['dailyTotalViews'];
126        $this->dailyArticleViews = $json['dailyArticleViews'];
127    }
128
129    /**
130     * Filter view counts to only include dates with non-zero counts
131     *
132     * @note This is a safeguard filtering. ComputedUserImpactLookup should not generate impact
133     * objects with zeros in the first place.
134     * @see https://phabricator.wikimedia.org/T351898
135     * @param array $views
136     * @return array
137     */
138    private function filterViewCounts( array $views ): array {
139        return array_filter( $views, static function ( $el ) {
140            return $el > 0;
141        } );
142    }
143
144    /** @inheritDoc */
145    public function jsonSerialize(): array {
146        return parent::jsonSerialize() + [
147            'dailyTotalViews' => $this->filterViewCounts( $this->dailyTotalViews ),
148            'dailyArticleViews' => $this->filterViewCounts( $this->dailyArticleViews ),
149        ];
150    }
151
152    /**
153     * @return bool
154     * @throws Exception
155     */
156    public function isPageViewDataStale(): bool {
157        $dailyTotalViews = $this->getDailyTotalViews();
158        if ( $dailyTotalViews === [] ) {
159            return true;
160        }
161
162        $latestPageViewsDateTime = new DateTime( array_key_last( $dailyTotalViews ) );
163        $now = MWTimestamp::getInstance();
164        $diff = $now->timestamp->diff( $latestPageViewsDateTime );
165        // Page view data generation can lag by 24-48 hours.
166        // Consider the data stale if it's from before (UTC) yesterday.
167        return $diff->days > 1;
168    }
169
170}