Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 43 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
ExpensiveUserImpact | |
0.00% |
0 / 43 |
|
0.00% |
0 / 8 |
110 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getDailyTotalViews | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDailyArticleViews | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newEmpty | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
loadFromJsonArray | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
filterViewCounts | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
jsonSerialize | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
isPageViewDataStale | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\UserImpact; |
4 | |
5 | use DateTime; |
6 | use Exception; |
7 | use MediaWiki\User\UserIdentity; |
8 | use MediaWiki\User\UserIdentityValue; |
9 | use MediaWiki\Utils\MWTimestamp; |
10 | use Wikimedia\Assert\Assert; |
11 | |
12 | /** |
13 | * Value object representing a user's impact statistics, including those which are |
14 | * more expensive to retrieve. |
15 | */ |
16 | class 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 | } |