Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.20% covered (success)
90.20%
46 / 51
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
FunctionsByTestsPager
90.20% covered (success)
90.20%
46 / 51
75.00% covered (warning)
75.00%
3 / 4
10.09
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getTypeFilter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getQueryInfo
85.29% covered (warning)
85.29%
29 / 34
0.00% covered (danger)
0.00%
0 / 1
6.11
 formatRow
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * WikiLambda FunctionsByTestsPager extends AbstractZObjectPager by
4 * adding filter conditions to the base table of all zobjects and
5 * their preferred labels given by AbstractZObjectPager::getQueryInfo
6 *
7 * @file
8 * @ingroup Extensions
9 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
10 * @license MIT
11 */
12
13namespace MediaWiki\Extension\WikiLambda\Pagers;
14
15use MediaWiki\Context\IContextSource;
16use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry;
17use MediaWiki\Extension\WikiLambda\ZObjectStore;
18use Wikimedia\Rdbms\Subquery;
19
20/**
21 * Pages Functions filtered by their testing status and quality.
22 */
23class FunctionsByTestsPager extends AbstractZObjectPager {
24
25    /**
26     * @param IContextSource|null $context Context.
27     * @param ZObjectStore $zObjectStore
28     * @param array $languageZids
29     * @param bool|null $excludePreDefined
30     * @param array $filters [ min, max, connected, pending, pass, fail ]
31     */
32    public function __construct(
33        $context, $zObjectStore, $languageZids, $excludePreDefined = null,
34        private readonly array $filters = []
35    ) {
36        parent::__construct(
37            $context, $zObjectStore, $languageZids, AbstractZObjectPager::ORDER_BY_NAME, $excludePreDefined
38        );
39    }
40
41    /**
42     * This pager only lists functions; the type filter is pushed down into
43     * the preferred-labels ranking subquery rather than applied over its
44     * results (T430853).
45     *
46     * @return string|null
47     */
48    protected function getTypeFilter(): ?string {
49        return ZTypeRegistry::Z_FUNCTION;
50    }
51
52    /**
53     * Gets the base conditions from the parent class and adds the
54     * additional conditions for this pager, depending on the filters.
55     * This pager inner joins the preferredLabels table returned by the
56     * AbstractZObjectPager with a table with all function ids and their
57     * relevant test counts:
58     * - all_tests: All the tests created for each function.
59     * - connected_tests: Number of connected tests for each function.
60     * - failing_tests: Number of tests failing for each function
61     *   (against at least one connected implementation)
62     * - matching_tests: Number of tests that match the conditions passed
63     *   as input in the Request: connected status and failure/success status.
64     *
65     * @return array
66     */
67    public function getQueryInfo() {
68        // Get base queryInfo from parent
69        $queryInfo = parent::getQueryInfo();
70
71        $min = $this->filters[ 'min' ];
72        $max = $this->filters[ 'max' ];
73        $connected = $this->filters[ 'connected' ];
74        $pending = $this->filters[ 'pending' ];
75        $pass = $this->filters[ 'pass' ];
76        $fail = $this->filters[ 'fail' ];
77
78        $testStatus = $this->getZObjectStore()->getTestStatusQuery();
79        $testFilters = [];
80        // Connection status filter:
81        // * Add where clause only if one value is selected
82        // * If both true or both false, leave unfiltered
83        if ( $connected !== $pending ) {
84            $testFilters[] = 'is_connected = ' . (int)$connected;
85        }
86        // Test results filter:
87        // * Add where clause only if one value is selected
88        // * If both true or both false, leave unfiltered
89        if ( $pass !== $fail ) {
90            $testFilters[] = 'is_passing = ' . (int)$pass;
91        }
92
93        // Conditional to count matching tests, if no filters count 1 per entry
94        $matchingConditional = 1;
95        if ( count( $testFilters ) > 0 ) {
96            $matchingConditional = $this->getDatabase()->conditional( $testFilters, '1', 'NULL' );
97        }
98        // Table with all function ids and their test counts:
99        $filteredFunctions = $this->getDatabase()->newSelectQueryBuilder()
100            ->select( [
101                'function_zid',
102                'all_tests',
103                'connected_tests' => 'COUNT( CASE WHEN is_connected = 1 THEN 1 END )',
104                'failing_tests' => 'COUNT( CASE WHEN is_passing = 0 THEN 1 END )',
105                'matching_tests' => 'COUNT( ' . $matchingConditional . ' )'
106            ] )
107            ->from( new Subquery( $testStatus ), 'filtered_functions' )
108            ->groupBy( [ 'function_zid', 'all_tests' ] );
109
110        // Add additional data to parent queryInfo
111        // 1. Return all test counts in the main select
112        array_push( $queryInfo[ 'fields' ], 'all_tests', 'matching_tests', 'connected_tests', 'failing_tests' );
113        // 2. Join filteredFunctions with preferredLabels
114        $queryInfo[ 'tables' ][ 'tests' ] = new Subquery( $filteredFunctions->getSQL() );
115        $queryInfo[ 'join_conds' ][ 'tests' ] = [ 'LEFT JOIN', 'wlzl_zobject_zid = tests.function_zid' ];
116        // 3. Return functions for which matching_tests count is less than max.
117        if ( $max > -1 ) {
118            $queryInfo[ 'conds' ][] = "matching_tests <= $max";
119        }
120        if ( $min > 0 ) {
121            $queryInfo[ 'conds' ][] = "matching_tests >= $min";
122        }
123
124        return $queryInfo;
125    }
126
127    /**
128     * @param \stdClass $row
129     * @return string
130     */
131    public function formatRow( $row ) {
132        $zid = $row->wlzl_zobject_zid;
133        $label = wfEscapeWikiText( $row->wlzl_label );
134
135        $functionInfo = "# [[$zid|$label]] ($zid)";
136
137        $tests = $row->all_tests;
138        $fail = $row->failing_tests;
139        $conn = $row->connected_tests;
140
141        $testsMsg = $this->msg( 'wikilambda-special-functionsbytests-row-tests' )->params( $tests )->text();
142        $failMsg = $this->msg( 'wikilambda-special-functionsbytests-row-failing' )->params( $fail )->text();
143        $connMsg = $this->msg( 'wikilambda-special-functionsbytests-row-connected' )->params( $conn )->text();
144
145        $testInfo = "$testsMsg"
146            . "<span class='ext-wikilambda-special-tests-connected'>$connMsg</span>"
147            . ( (int)$fail == 0 ? '' : ", <span class='ext-wikilambda-special-tests-failing'>$failMsg</span>" );
148
149        return $functionInfo . ' - ' . $this->msg( 'parentheses' )->params( $testInfo )->text() . "\n";
150    }
151}