Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.70% covered (warning)
75.70%
81 / 107
33.33% covered (danger)
33.33%
3 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
DatabaseMentorStore
75.70% covered (warning)
75.70%
81 / 107
33.33% covered (danger)
33.33%
3 / 9
32.26
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 loadMentorUserUncached
95.00% covered (success)
95.00%
19 / 20
0.00% covered (danger)
0.00%
0 / 1
4
 getMenteesByMentor
90.48% covered (success)
90.48%
19 / 21
0.00% covered (danger)
0.00%
0 / 1
5.02
 setMentorForUserReal
64.00% covered (warning)
64.00%
16 / 25
0.00% covered (danger)
0.00%
0 / 1
2.19
 setMentorForUserInternal
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 setMenteeActiveFlag
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 isMenteeActiveUncached
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 markMenteeAsActive
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 markMenteeAsInactive
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace GrowthExperiments\Mentorship\Store;
4
5use IDBAccessObject;
6use JobQueueGroup;
7use MediaWiki\User\UserFactory;
8use MediaWiki\User\UserIdentity;
9use MediaWiki\User\UserIdentityLookup;
10use MediaWiki\User\UserIdentityValue;
11use WANObjectCache;
12use Wikimedia\Rdbms\ILoadBalancer;
13
14class DatabaseMentorStore extends MentorStore {
15
16    private UserFactory $userFactory;
17    private UserIdentityLookup $userIdentityLookup;
18    private JobQueueGroup $jobQueueGroup;
19    private ILoadBalancer $loadBalancer;
20
21    /**
22     * @param WANObjectCache $wanCache
23     * @param UserFactory $userFactory
24     * @param UserIdentityLookup $userIdentityLookup
25     * @param JobQueueGroup $jobQueueGroup
26     * @param ILoadBalancer $loadBalancer
27     * @param bool $wasPosted
28     */
29    public function __construct(
30        WANObjectCache $wanCache,
31        UserFactory $userFactory,
32        UserIdentityLookup $userIdentityLookup,
33        JobQueueGroup $jobQueueGroup,
34        ILoadBalancer $loadBalancer,
35        bool $wasPosted
36    ) {
37        parent::__construct( $wanCache, $wasPosted );
38
39        $this->userFactory = $userFactory;
40        $this->userIdentityLookup = $userIdentityLookup;
41        $this->jobQueueGroup = $jobQueueGroup;
42        $this->loadBalancer = $loadBalancer;
43    }
44
45    /**
46     * @inheritDoc
47     */
48    public function loadMentorUserUncached(
49        UserIdentity $mentee,
50        string $mentorRole,
51        $flags
52    ): ?UserIdentity {
53        if ( ( $flags & IDBAccessObject::READ_LATEST ) == IDBAccessObject::READ_LATEST ) {
54            $db = $this->loadBalancer->getConnection( DB_PRIMARY );
55        } else {
56            $db = $this->loadBalancer->getConnection( DB_REPLICA );
57        }
58        $id = $db->newSelectQueryBuilder()
59            ->select( 'gemm_mentor_id' )
60            ->from( 'growthexperiments_mentor_mentee' )
61            ->where( [
62                'gemm_mentee_id' => $mentee->getId(),
63                'gemm_mentor_role' => $mentorRole,
64            ] )
65            ->recency( $flags )
66            ->caller( __METHOD__ )
67            ->fetchField();
68
69        if ( $id === false ) {
70            // No mentor in the database, return null
71            return null;
72        }
73
74        // Construct & return the user
75        $user = $this->userFactory->newFromId( $id );
76        // Return null if user does not exist
77        $user->load();
78        if ( !$user->getId() ) {
79            return null;
80        }
81        return new UserIdentityValue( $user->getId(), $user->getName() );
82    }
83
84    /**
85     * @inheritDoc
86     */
87    public function getMenteesByMentor(
88        UserIdentity $mentor,
89        string $mentorRole,
90        bool $includeHiddenUsers = false,
91        bool $includeInactiveUsers = true,
92        int $flags = 0
93    ): array {
94        if ( ( $flags & IDBAccessObject::READ_LATEST ) == IDBAccessObject::READ_LATEST ) {
95            $db = $this->loadBalancer->getConnection( DB_PRIMARY );
96        } else {
97            $db = $this->loadBalancer->getConnection( DB_REPLICA );
98        }
99
100        $queryBuilder = $db->newSelectQueryBuilder()
101            ->select( 'gemm_mentee_id' )
102            ->from( 'growthexperiments_mentor_mentee' )
103            ->where( [ 'gemm_mentor_id' => $mentor->getId(), 'gemm_mentor_role' => $mentorRole ] );
104
105        if ( !$includeInactiveUsers ) {
106            $queryBuilder->andWhere( [ 'gemm_mentee_is_active' => true ] );
107        }
108
109        $ids = $queryBuilder->caller( __METHOD__ )->fetchFieldValues();
110
111        if ( $ids === [] ) {
112            return [];
113        }
114
115        $builder = $this->userIdentityLookup
116            ->newSelectQueryBuilder()
117            ->registered()
118            ->named()
119            ->whereUserIds( $ids );
120
121        if ( !$includeHiddenUsers ) {
122            $builder->hidden( false );
123        }
124
125        return iterator_to_array( $builder
126            ->fetchUserIdentities() );
127    }
128
129    /**
130     * Really set a mentor for a given user
131     *
132     * @param UserIdentity $mentee
133     * @param UserIdentity|null $mentor Set to null to drop the relationship
134     * @param string $mentorRole
135     */
136    private function setMentorForUserReal(
137        UserIdentity $mentee,
138        ?UserIdentity $mentor,
139        string $mentorRole
140    ): void {
141        $dbw = $this->loadBalancer->getConnection( DB_PRIMARY );
142        if ( $mentor === null ) {
143            $dbw->newDeleteQueryBuilder()
144                ->deleteFrom( 'growthexperiments_mentor_mentee' )
145                ->where( [
146                    'gemm_mentee_id' => $mentee->getId(),
147                    'gemm_mentor_role' => $mentorRole
148                ] )
149                ->caller( __METHOD__ )
150                ->execute();
151            return;
152        }
153        $dbw->newInsertQueryBuilder()
154            ->insertInto( 'growthexperiments_mentor_mentee' )
155            ->row( [
156                'gemm_mentee_id' => $mentee->getId(),
157                'gemm_mentor_id' => $mentor->getId(),
158                'gemm_mentor_role' => $mentorRole,
159            ] )
160            ->onDuplicateKeyUpdate()
161            ->uniqueIndexFields( [ 'gemm_mentee_id', 'gemm_mentor_role' ] )
162            ->set( [
163                'gemm_mentor_id' => $mentor->getId()
164            ] )
165            ->caller( __METHOD__ )
166            ->execute();
167    }
168
169    /**
170     * @inheritDoc
171     */
172    protected function setMentorForUserInternal(
173        UserIdentity $mentee,
174        ?UserIdentity $mentor,
175        string $mentorRole
176    ): void {
177        if ( $this->wasPosted ) {
178            $this->setMentorForUserReal(
179                $mentee,
180                $mentor,
181                $mentorRole
182            );
183        } else {
184            $this->jobQueueGroup->lazyPush( new SetUserMentorDatabaseJob( [
185                'menteeId' => $mentee->getId(),
186                'mentorId' => $mentor ? $mentor->getId() : null,
187                'roleId' => $mentorRole,
188            ] ) );
189        }
190    }
191
192    /**
193     * Set gemm_mentee_is_active to true/false
194     *
195     * @param UserIdentity $mentee
196     * @param bool $isActive
197     */
198    private function setMenteeActiveFlag(
199        UserIdentity $mentee,
200        bool $isActive
201    ): void {
202        $this->loadBalancer->getConnection( DB_PRIMARY )->newUpdateQueryBuilder()
203            ->update( 'growthexperiments_mentor_mentee' )
204            ->set( [ 'gemm_mentee_is_active' => $isActive ] )
205            ->where( [
206                'gemm_mentee_id' => $mentee->getId(),
207                'gemm_mentor_role' => self::ROLE_PRIMARY,
208            ] )
209            ->caller( __METHOD__ )
210            ->execute();
211        $this->invalidateIsMenteeActive( $mentee );
212    }
213
214    /**
215     * @inheritDoc
216     */
217    protected function isMenteeActiveUncached( UserIdentity $mentee ): ?bool {
218        if ( !$this->isMentee( $mentee ) ) {
219            return null;
220        }
221
222        return (bool)$this->loadBalancer->getConnection( DB_REPLICA )->newSelectQueryBuilder()
223            ->select( 'gemm_mentee_is_active' )
224            ->from( 'growthexperiments_mentor_mentee' )
225            ->where( [
226                'gemm_mentee_id' => $mentee->getId(),
227                'gemm_mentor_role' => self::ROLE_PRIMARY,
228            ] )
229            ->caller( __METHOD__ )
230            ->fetchField();
231    }
232
233    /**
234     * @inheritDoc
235     */
236    public function markMenteeAsActive( UserIdentity $mentee ): void {
237        if ( $this->isMentee( $mentee ) && !$this->isMenteeActive( $mentee ) ) {
238            $this->setMenteeActiveFlag( $mentee, true );
239        }
240    }
241
242    /**
243     * @inheritDoc
244     */
245    public function markMenteeAsInactive( UserIdentity $mentee ): void {
246        if ( $this->isMentee( $mentee ) && $this->isMenteeActive( $mentee ) ) {
247            $this->setMenteeActiveFlag( $mentee, false );
248        }
249    }
250}