Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 141
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
MenteesHandler
0.00% covered (danger)
0.00%
0 / 141
0.00% covered (danger)
0.00%
0 / 3
306
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 run
0.00% covered (danger)
0.00%
0 / 88
0.00% covered (danger)
0.00%
0 / 1
240
 getParamSettings
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace GrowthExperiments\Rest\Handler;
4
5use GrowthExperiments\MentorDashboard\MenteeOverview\MenteeOverviewDataFilter;
6use GrowthExperiments\MentorDashboard\MenteeOverview\MenteeOverviewDataProvider;
7use GrowthExperiments\MentorDashboard\MenteeOverview\StarredMenteesStore;
8use GrowthExperiments\Util;
9use MediaWiki\Cache\LinkBatchFactory;
10use MediaWiki\Rest\HttpException;
11use MediaWiki\Rest\SimpleHandler;
12use MediaWiki\Title\TitleFactory;
13use MediaWiki\Title\TitleParser;
14use MediaWiki\User\UserFactory;
15use MediaWiki\User\UserIdentity;
16use MediaWiki\Utils\MWTimestamp;
17use RequestContext;
18use Wikimedia\Assert\ParameterAssertionException;
19use Wikimedia\ParamValidator\ParamValidator;
20
21/**
22 * Returns information about user's mentees.
23 *
24 * The class intentionally does not check the user is currently
25 * listed as a mentor, because users who were previously mentors may still have
26 * mentees. Users who never were a mentor will receive an empty array anyway.
27 */
28class MenteesHandler extends SimpleHandler {
29    /** @var MenteeOverviewDataProvider */
30    private $dataProvider;
31
32    /** @var StarredMenteesStore */
33    private $starredMenteesStore;
34
35    /** @var UserFactory */
36    private $userFactory;
37
38    /** @var TitleFactory */
39    private $titleFactory;
40
41    /** @var TitleParser */
42    private $titleParser;
43
44    /** @var LinkBatchFactory */
45    private $linkBatchFactory;
46
47    /**
48     * @param MenteeOverviewDataProvider $dataProvider
49     * @param StarredMenteesStore $starredMenteesStore
50     * @param UserFactory $userFactory
51     * @param TitleFactory $titleFactory
52     * @param TitleParser $titleParser
53     * @param LinkBatchFactory $linkBatchFactory
54     */
55    public function __construct(
56        MenteeOverviewDataProvider $dataProvider,
57        StarredMenteesStore $starredMenteesStore,
58        UserFactory $userFactory,
59        TitleFactory $titleFactory,
60        TitleParser $titleParser,
61        LinkBatchFactory $linkBatchFactory
62    ) {
63        $this->dataProvider = $dataProvider;
64        $this->starredMenteesStore = $starredMenteesStore;
65        $this->userFactory = $userFactory;
66        $this->titleFactory = $titleFactory;
67        $this->titleParser = $titleParser;
68        $this->linkBatchFactory = $linkBatchFactory;
69    }
70
71    /**
72     * @return array
73     * @throws HttpException
74     */
75    public function run() {
76        $authority = $this->getAuthority();
77        if ( !$authority->isNamed() ) {
78            throw new HttpException( 'You must be logged in', 403 );
79        }
80        $user = $authority->getUser();
81
82        $params = $this->getValidatedParams();
83        $limit = $params['limit'] ?? 10;
84        $offset = $params['offset'] ?? 0;
85
86        $allData = $this->dataProvider->getFormattedDataForMentor( $user );
87        $dataFilter = new MenteeOverviewDataFilter( $allData );
88        $dataFilter
89            ->limit( $limit )
90            ->offset( $offset );
91
92        if ( $params['prefix'] !== null ) {
93            $dataFilter->prefix( $params['prefix'] );
94        }
95        if ( $params['minedits'] !== null ) {
96            $dataFilter->minEdits( $params['minedits'] );
97        }
98        if ( $params['maxedits'] !== null ) {
99            $dataFilter->maxEdits( $params['maxedits'] );
100        }
101        if ( $params['activedaysago'] !== null ) {
102            $dataFilter->activeDaysAgo( $params['activedaysago'] );
103        }
104        if ( $params['onlystarred'] === true ) {
105            $dataFilter->onlyIds(
106                array_map( static function ( UserIdentity $mentee ) {
107                    return $mentee->getId();
108                }, $this->starredMenteesStore->getStarredMentees( $user ) )
109            );
110        }
111        if ( $params['sortby'] !== null ) {
112            // validation is done by data filter
113            try {
114                $orderRaw = $params['order'] ?? 'desc';
115                if ( $orderRaw === 'desc' ) {
116                    $order = MenteeOverviewDataFilter::SORT_ORDER_DESCENDING;
117                } elseif ( $orderRaw === 'asc' ) {
118                    $order = MenteeOverviewDataFilter::SORT_ORDER_ASCENDING;
119                } else {
120                    throw new HttpException( 'Invalid order', 400 );
121                }
122                $dataFilter->sort( $params['sortby'], $order );
123            } catch ( ParameterAssertionException $e ) {
124                throw new HttpException( 'Invalid sortby', 400 );
125            }
126        }
127
128        $data = $dataFilter->filter();
129        $context = RequestContext::getMain();
130        $nowUnix = (int)MWTimestamp::now( TS_UNIX );
131        $batch = $this->linkBatchFactory->newLinkBatch();
132        $batch->setCaller( __METHOD__ );
133        array_walk( $data, function ( &$menteeData ) use ( $context, $nowUnix, $batch ) {
134            if ( isset( $menteeData['last_active'] ) ) {
135                $menteeData['last_active'] = [
136                    'raw' => $menteeData['last_active'],
137                    'human' => Util::getRelativeTime(
138                        $context,
139                        $nowUnix - (int)MWTimestamp::getInstance(
140                            $menteeData['last_active']
141                        )->getTimestamp( TS_UNIX )
142                    )
143                ];
144            }
145
146            if ( $menteeData['registration'] ) {
147                $menteeData['registration'] = [
148                    'raw' => $menteeData['registration'],
149                    'human' => $context->getLanguage()->sprintfDate(
150                        'Y-m-d',
151                        $menteeData['registration']
152                    )
153                ];
154            }
155
156            if ( $menteeData['username'] ) {
157                $batch->addObj( $this->titleParser->parseTitle(
158                    $menteeData['username'],
159                    NS_USER
160                ) );
161            }
162        } );
163
164        $batch->execute();
165        array_walk( $data, function ( &$menteeData ) {
166            if ( $menteeData['username'] ) {
167                $menteeData['userpage_exists'] = $this->titleFactory->newFromText(
168                    $menteeData['username'],
169                    NS_USER
170                )->isKnown();
171                $menteeData['usertalk_exists'] = $this->titleFactory->newFromText(
172                    $menteeData['username'],
173                    NS_USER_TALK
174                )->isKnown();
175                $menteeData['user_is_hidden'] = $this->userFactory->newFromName(
176                    $menteeData['username']
177                )->isHidden();
178            }
179        } );
180
181        return [
182            'mentees' => array_values( $data ),
183            'totalRows' => $dataFilter->getTotalRows(),
184            'assignedMentees' => count( $allData ),
185            'limit' => $limit,
186            'offset' => $offset
187        ];
188    }
189
190    /** @inheritDoc */
191    public function getParamSettings() {
192        return [
193            'limit' => [
194                self::PARAM_SOURCE => 'query',
195                ParamValidator::PARAM_TYPE => 'integer',
196                ParamValidator::PARAM_REQUIRED => false,
197            ],
198            'offset' => [
199                self::PARAM_SOURCE => 'query',
200                ParamValidator::PARAM_TYPE => 'integer',
201                ParamValidator::PARAM_REQUIRED => false,
202            ],
203            'prefix' => [
204                self::PARAM_SOURCE => 'query',
205                ParamValidator::PARAM_TYPE => 'string',
206                ParamValidator::PARAM_REQUIRED => false,
207            ],
208            'activedaysago' => [
209                self::PARAM_SOURCE => 'query',
210                ParamValidator::PARAM_TYPE => 'integer',
211                ParamValidator::PARAM_REQUIRED => false,
212            ],
213            'minedits' => [
214                self::PARAM_SOURCE => 'query',
215                ParamValidator::PARAM_TYPE => 'integer',
216                ParamValidator::PARAM_REQUIRED => false,
217            ],
218            'maxedits' => [
219                self::PARAM_SOURCE => 'query',
220                ParamValidator::PARAM_TYPE => 'integer',
221                ParamValidator::PARAM_REQUIRED => false,
222            ],
223            'onlystarred' => [
224                self::PARAM_SOURCE => 'query',
225                ParamValidator::PARAM_TYPE => 'boolean',
226                ParamValidator::PARAM_REQUIRED => false,
227            ],
228            'sortby' => [
229                self::PARAM_SOURCE => 'query',
230                ParamValidator::PARAM_TYPE => 'string',
231                ParamValidator::PARAM_REQUIRED => false,
232            ],
233            'order' => [
234                self::PARAM_SOURCE => 'query',
235                ParamValidator::PARAM_TYPE => 'string',
236                ParamValidator::PARAM_REQUIRED => false,
237            ],
238        ];
239    }
240}