Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
ff42b38
+ prp quality changes pie chart in ec generalstats
agoujot Jun 27, 2025
a863827
Merge branch 'main' into ecprpchanges
agoujot Jul 14, 2025
5fbe22d
compile
agoujot Jul 14, 2025
8dd215d
Merge branch 'main' into ecprpchanges
agoujot Jul 17, 2025
6e737bc
compile
agoujot Jul 17, 2025
9bcab4a
Merge branch 'main' into ecprpchanges
agoujot Jul 21, 2025
fc4cd54
compile
agoujot Jul 21, 2025
949b3ff
Merge branch 'main' into ecprpchanges
agoujot Aug 6, 2025
c434248
compile
agoujot Aug 6, 2025
10949d6
Merge branch 'x-tools:main' into ecprpchanges
agoujot Aug 19, 2025
6ee93c7
add column in pages; take quality changes from the api; (and other th…
agoujot Aug 19, 2025
9835557
Merge branch 'main' into ecprpchanges
agoujot Aug 19, 2025
dde5474
commpile
agoujot Aug 19, 2025
8ed29c1
lints
agoujot Aug 19, 2025
7303495
PC: add pie chart; rm unused method (migrated to project)
agoujot Aug 20, 2025
54ae97b
refactor some ugly duplication on pa selects
agoujot Aug 20, 2025
c378fac
clean up getTopEditsAllNamespaces and fetch prp quality in both
agoujot Aug 20, 2025
01f086d
lint
agoujot Aug 20, 2025
55486be
add column to TE and add proper sort keys for quality
agoujot Aug 20, 2025
64f584c
add in wikitext, tsv, csv
agoujot Aug 20, 2025
d791813
add tests for most of EditCounter; a few other tweaks
agoujot Aug 20, 2025
dd23577
a few more tests and finish removing an unused method
agoujot Aug 20, 2025
a28e61d
code coverage for AutoEdits
agoujot Aug 20, 2025
14db2fb
fix
agoujot Aug 20, 2025
462ee05
these two lines did have coverage; try explicit false
agoujot Aug 20, 2025
80614ba
as always, the cache is god and the devil
agoujot Aug 20, 2025
880df65
+forgotten paren
agoujot Aug 20, 2025
302d6cc
code coverage for Project.php; rewrite testEditData cleaner with prov…
agoujot Aug 21, 2025
2b77a63
try to fix and finish Project.php coverage
agoujot Aug 21, 2025
c52008d
code coverage for TopEdits
agoujot Aug 21, 2025
82f10ad
fixed test for te not opted in
agoujot Aug 21, 2025
fe716d2
do test model-side caching in EC
agoujot Aug 21, 2025
e1cd739
do test PA in Project; fix EC average test case
agoujot Aug 21, 2025
cd88cc7
coverage for UserRights
agoujot Aug 21, 2025
737ec27
finish coverage for UserRights
agoujot Aug 21, 2025
b24cd58
coverage for Pages.php
agoujot Aug 21, 2025
0559fef
coverage for a few small classes
agoujot Aug 21, 2025
b37bcb3
coverage for blame
agoujot Aug 21, 2025
8bf740a
lints
agoujot Aug 21, 2025
dd3887a
fix coverage of asof caching in blame
agoujot Aug 21, 2025
3951249
coverage for Edit.php
agoujot Aug 21, 2025
619bd75
fix test for edit wikifystring with section
agoujot Aug 21, 2025
07987ee
lint
agoujot Aug 21, 2025
cd1a6f6
codeCoverageignore the admin score
agoujot Aug 21, 2025
725f0bd
coverage for admin stats
agoujot Aug 22, 2025
cd8a7d8
finish admin stats coverage
agoujot Aug 22, 2025
120ccf9
code coverage for page.php
agoujot Aug 23, 2025
48cb2cb
coverage for User.php
agoujot Aug 23, 2025
af8b259
lint
agoujot Aug 23, 2025
2182462
lint
agoujot Aug 23, 2025
cbc888f
coverage for Authorship.php
agoujot Aug 23, 2025
e498358
coverage for PageAssessments.php
agoujot Aug 23, 2025
451c77f
lint
agoujot Aug 23, 2025
48f9e28
start work on pageinfo coverage
agoujot Aug 23, 2025
cc7a773
lint
agoujot Aug 23, 2025
62a9e50
lint
agoujot Aug 23, 2025
fb85638
continue pageinfo coverage
agoujot Aug 23, 2025
1596490
try and finish pageinfo coverage
agoujot Aug 24, 2025
e5db1b1
try and finish pageinfo coverage
agoujot Aug 24, 2025
b4e83a0
I think it's done
agoujot Aug 24, 2025
89bdbb9
quick xtoolshttpexception coverage
agoujot Aug 24, 2025
bab4f18
Merge branch 'main' into ecprpchanges
agoujot Aug 27, 2025
468f560
compile
agoujot Aug 27, 2025
e10812c
Merge branch 'main' into ecprpchanges
agoujot Oct 26, 2025
3866cee
test autoeditcount in EC
agoujot Oct 26, 2025
003fba8
try to fix tests
agoujot Oct 26, 2025
9786b15
ok, try again
agoujot Jan 4, 2026
25883cb
phew, fix merge conflicts and fix tests
agoujot Jan 10, 2026
cf4a78c
fix pages query by removing distinct
agoujot Jan 10, 2026
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
19 changes: 19 additions & 0 deletions assets/css/_darkmode.scss
Original file line number Diff line number Diff line change
Expand Up @@ -329,4 +329,23 @@ $tooltip-shadow: #333;
filter:invert(1) hue-rotate(180deg);
}

.prp-qualitytext {
color:white; /* needed to have enough contrast */
}
.prp-quality0 {
background-color:#55585e;
}
.prp-quality1 {
background-color:#971602;
}
.prp-quality2 {
background-color:#40408c;
}
.prp-quality3 {
background-color:#5C5C00;
}
.prp-quality4 {
background-color:#00662e;
}

}
24 changes: 23 additions & 1 deletion assets/css/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,29 @@ a.help-icon {
clear: both;
}


.prp-qualitytext {
border-radius: 3px;
padding: 0 2px;
}

.prp-quality0 {
background-color:#ddd;
}
.prp-quality1 {
background-color:#ffabab;
}
.prp-quality2 {
background-color:#bbbbff;
}
.prp-quality3 {
background-color:#ffe867;
}
.prp-quality4 {
background-color:#90ff90;
}

.link-loading {
pointer-events: none;
font-style: italic;
}
}
4 changes: 4 additions & 0 deletions assets/css/editcounter.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
height: auto;
width: auto;
}

div.chart-wrapper.qualitychangechart {
float:none;
}

.top-project-edit-counts td {
white-space: nowrap;
Expand Down
2 changes: 2 additions & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@
"priority": "Priority",
"project": "Project",
"projects": "Projects",
"proofreadpage-quality": "Page status",
"proofreadpage-qualitychanges": "Proportions of ProofreadPage quality changes",
"prose": "Prose",
"protect": "Protect",
"proxy-check": "Proxy check",
Expand Down
2 changes: 2 additions & 0 deletions i18n/qqq.json
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@
"priority": "Label for the priority of a 'bug' in a wiki page that needs fixing.\n{{Identical|Priority}}",
"project": "Header for the part of the interface that lets the user select which project they want to view statistics for.\n{{Identical|Project}}",
"projects": "Label for a number of projects.\n{{Identical|Projects}}",
"proofreadpage-quality": "Label for the transcription status of a page, as recorded by Extension:ProofreadPage.",
"proofreadpage-qualitychanges": "Label for a chart of the proportions of the different quality changes.",
"prose": "Term used to describe the text content of an article.",
"protect": "Name of the MediaWiki log action 'protect', as in page protection.\n{{Identical|Protect}}",
"proxy-check": "Label for link to the Proxy Check tool on Toolforge. The tool can tell you if the given IP address is a proxy or not.",
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/build/entrypoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"/build/app.1d427a43.js"
],
"css": [
"/build/app.7692d209.css"
"/build/app.ed7ba741.css"
]
}
}
Expand Down
2 changes: 1 addition & 1 deletion public/build/manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"build/app.css": "/build/app.7692d209.css",
"build/app.css": "/build/app.ed7ba741.css",
"build/app.js": "/build/app.1d427a43.js",
"build/runtime.js": "/build/runtime.c217f8c4.js",
"build/852.96913092.js": "/build/852.96913092.js",
Expand Down
1 change: 1 addition & 0 deletions src/Model/AdminScore.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/**
* An AdminScore provides scores of logged actions and on-wiki activity made by a user,
* to measure if they would be suitable as an administrator.
* @codeCoverageIgnore
*/
class AdminScore extends Model {
/**
Expand Down
14 changes: 8 additions & 6 deletions src/Model/Authorship.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ private function getTargetRevId( ?string $target ): ?int {
/**
* Domains of supported wikis.
* @return string[]
* Is a constant.
* @codeCoverageIgnore
*/
public function getSupportedWikis(): array {
return self::SUPPORTED_PROJECTS;
Expand Down Expand Up @@ -100,18 +102,18 @@ public function getError(): ?string {

/**
* Get the total number of authors.
* @return int
* @return int|null
*/
public function getTotalAuthors(): int {
return $this->data['totalAuthors'];
public function getTotalAuthors(): int|null {
return $this->data['totalAuthors'] ?? null;
}

/**
* Get the total number of characters added.
* @return int
* @return int|null
*/
public function getTotalCount(): int {
return $this->data['totalCount'];
public function getTotalCount(): int|null {
return $this->data['totalCount'] ?? null;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/Model/AutoEdits.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ public function getToolCounts(): array {
/**
* Get a list of all available tools for the Project.
* @return array
* Just passes along a repository result.
* @codeCoverageIgnore
*/
public function getAllTools(): array {
return $this->repository->getTools( $this->project );
Expand Down
111 changes: 65 additions & 46 deletions src/Model/EditCounter.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ class EditCounter extends Model {
protected array $timeCardData;

/**
* Revision size data, with keys 'average_size', 'large_edits' and 'small_edits'.
* Various data on the last 5000 edits.
* @var string[] As returned by the DB, unconverted to int or float
*/
protected array $editSizeData;
protected array $editData;

/**
* Duration of the longest block in seconds; -1 if indefinite,
Expand Down Expand Up @@ -139,15 +139,17 @@ public function getThanksReceived(): int {
* @param bool $blocksOnly Whether to include only blocks, and not reblocks and unblocks.
* @return array
*/
protected function getBlocks( string $type, bool $blocksOnly = true ): array {
public function getBlocks( string $type, bool $blocksOnly = true ): array {
if ( isset( $this->blocks[$type] ) && is_array( $this->blocks[$type] ) ) {
return $this->blocks[$type];
$blocks = $this->blocks[$type];
} else {
$method = "getBlocks" . ucfirst( $type );
$blocks = $this->repository->$method( $this->project, $this->user );
$this->blocks[$type] = $blocks;
}
$method = "getBlocks" . ucfirst( $type );
$blocks = $this->repository->$method( $this->project, $this->user );
$this->blocks[$type] = $blocks;

// Filter out unblocks unless requested.
// Expressly don't store this.
if ( $blocksOnly ) {
$blocks = array_filter( $blocks, static function ( $block ) {
return $block['log_action'] === 'block' || $block['log_action'] === 'reblock';
Expand Down Expand Up @@ -352,7 +354,7 @@ public function getLongestBlockSeconds() {
// Reset the last block, as it has now been accounted for.
$lastBlock = [ null, null ];
}
} elseif ( $block['log_action'] === 'reblock' && -1 !== $lastBlock[1] ) {
} elseif ( $block['log_action'] === 'reblock' && $lastBlock[1] !== -1 ) {
// The last block was modified.
// $lastBlock is left unchanged if its duration was indefinite.

Expand Down Expand Up @@ -726,27 +728,28 @@ public function timeCard(): array {
foreach ( $totals as $total ) {
$max = max( $max, $total['value'] );
}
foreach ( $totals as &$total ) {
$total['scale'] = round( ( $total['value'] / $max ) * 20 );
foreach ( $totals as $index => $total ) {
$totals[$index]['scale'] = round( ( $total['value'] / $max ) * 20 );
}

// Fill in zeros for timeslots that have no values.
$sortedTotals = [];
$index = 0;
$sortedIndex = 0;
foreach ( range( 1, 7 ) as $day ) {
foreach ( range( 0, 23 ) as $hour ) {
if ( isset( $totals[$index] ) && (int)$totals[$index]['hour'] === $hour ) {
$sortedTotals[$sortedIndex] = $totals[$index];
if ( isset( $totals[$index] )
&& (int)$totals[$index]['day_of_week'] === $day
&& (int)$totals[$index]['hour'] === $hour
) {
$sortedTotals[] = $totals[$index];
$index++;
} else {
$sortedTotals[$sortedIndex] = [
$sortedTotals[] = [
'day_of_week' => $day,
'hour' => $hour,
'value' => 0,
];
}
$sortedIndex++;
}
}

Expand All @@ -765,10 +768,12 @@ public function monthCounts( ?DateTime $currentTime = null ): array {
return $this->monthCounts;
}

// @codeCoverageIgnoreStart
// Set to current month if we're not unit-testing
if ( !( $currentTime instanceof DateTime ) ) {
$currentTime = new DateTime( 'last day of this month' );
}
// @codeCoverageIgnoreEnd

$totals = $this->repository->getMonthCounts( $this->project, $this->user );
$out = [
Expand Down Expand Up @@ -837,8 +842,6 @@ public function monthCountsWithNamespaces( ?DateTime $currentTime = null ): arra
* string[] - Modified $out filled with month stats,
* DateTime - timestamp of first edit
* ]
* Tests covered in self::monthCounts().
* @codeCoverageIgnore
*/
private function fillInMonthCounts( array $out, array $totals, DateTime $firstEdit ): array {
foreach ( $totals as $total ) {
Expand All @@ -861,8 +864,6 @@ private function fillInMonthCounts( array $out, array $totals, DateTime $firstEd
* @param array $out
* @param DatePeriod $dateRange From first edit to present.
* @return array Modified $out filled with month stats.
* Tests covered in self::monthCounts().
* @codeCoverageIgnore
*/
private function fillInMonthTotalsAndLabels( array $out, DatePeriod $dateRange ): array {
foreach ( $dateRange as $monthObj ) {
Expand Down Expand Up @@ -958,15 +959,15 @@ public function yearTotals( ?DateTime $currentTime = null ): array {
}

/**
* Get average edit size, and number of large and small edits.
* @return array
* Get average edit size, number of large and small edits, and change tags.
* @return array With keys "sizes", "average_size", "small_edits", "large_edits", "tag_lists".
*/
public function getEditSizeData(): array {
if ( !isset( $this->editSizeData ) ) {
$this->editSizeData = $this->repository
->getEditSizeData( $this->project, $this->user );
public function getEditData(): array {
if ( !isset( $this->editData ) ) {
$this->editData = $this->repository
->getEditData( $this->project, $this->user );
}
return $this->editSizeData;
return $this->editData;
}

/**
Expand All @@ -979,33 +980,51 @@ public function countLast5000(): int {
}

/**
* Get the number of edits under 20 bytes of the user's past 5000 edits.
* @return int
*/
public function countSmallEdits(): int {
$editSizeData = $this->getEditSizeData();
return isset( $editSizeData['small_edits'] ) ? (int)$editSizeData['small_edits'] : 0;
}

/**
* Get the total number of edits over 1000 bytes of the user's past 5000 edits.
* @return int
* Get the ProofreadPage tagged quality changes in the last 5000 edits.
* @return int[] With keys 0, 1, 2, 3, 4, and 'total'.
*/
public function countLargeEdits(): int {
$editSizeData = $this->getEditSizeData();
return isset( $editSizeData['large_edits'] ) ? (int)$editSizeData['large_edits'] : 0;
public function countQualityChanges(): array {
$editData = $this->getEditData();
$res = [
0 => 0,
1 => 0,
2 => 0,
3 => 0,
4 => 0,
'total' => 0,
];
if ( !isset( $editData['tag_lists'] ) ) {
return $res;
}
$tagLists = $editData['tag_lists'];
foreach ( $tagLists as $list ) {
if ( $list !== null ) {
$found = false;
foreach ( $list as $tag ) {
if ( preg_match( '/^proofreadpage\-quality[0-4]$/', $tag ) ) {
$res[intval( substr( $tag, -1 ) )] += 1;
$found = true;
break;
}
}
if ( $found ) {
$res['total'] += 1;
}
}
}
return $res;
}

/**
* Get the number of edits that have automated tags in the user's past 5000 edits.
* @return int
*/
public function countAutoEdits(): int {
$editSizeData = $this->getEditSizeData();
if ( !isset( $editSizeData['tag_lists'] ) ) {
$editData = $this->getEditData();
if ( !isset( $editData['tag_lists'] ) ) {
return 0;
}
$tags = json_decode( $editSizeData['tag_lists'] );
$tags = $editData['tag_lists'];
$autoTags = $this->autoEditsHelper->getTags( $this->project );
return count(
// Number
Expand All @@ -1032,9 +1051,9 @@ public function countAutoEdits(): int {
* @return float Size in bytes.
*/
public function averageEditSize(): float {
$editSizeData = $this->getEditSizeData();
if ( isset( $editSizeData['average_size'] ) ) {
return round( (float)$editSizeData['average_size'], 3 );
$editData = $this->getEditData();
if ( isset( $editData['average_size'] ) ) {
return round( (float)$editData['average_size'], 3 );
} else {
return 0;
}
Expand Down
2 changes: 2 additions & 0 deletions src/Model/LargestPages.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public function getExcludePattern(): string {
/**
* Get the largest pages on the project.
* @return Page[]
* Just returns a repository result.
* @codeCoverageIgnore
*/
public function getResults(): array {
return $this->repository->getData(
Expand Down
3 changes: 3 additions & 0 deletions src/Model/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,11 @@ public function setRepository( Repository $repository ): Model {
*/
public function getRepository(): Repository {
if ( !isset( $this->repository ) ) {
// Untestable, Model cannot be directly instantiated and all subclasses set it in __construct.
// @codeCoverageIgnoreStart
$msg = sprintf( 'The $repository property for class %s must be set before using.', static::class );
throw new Exception( $msg );
// @codeCoverageIgnoreEnd
}
return $this->repository;
}
Expand Down
Loading
Loading