Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 71
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
QualityGateDecorator
0.00% covered (danger)
0.00%
0 / 71
0.00% covered (danger)
0.00%
0 / 9
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
 suggest
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
30
 filter
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isImageRecommendationDailyTaskLimitExceeded
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getImageRecommendationTasksDoneByUserForCurrentDay
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 isLinkRecommendationDailyTaskLimitExceeded
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getLinkRecommendationTasksDoneByUserForCurrentDay
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 isSectionImageRecommendationDailyTaskLimitExceeded
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getSectionImageRecommendationTasksDoneByUserForCurrentDay
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace GrowthExperiments\NewcomerTasks\TaskSuggester;
4
5use GrowthExperiments\NewcomerTasks\AddImage\ImageRecommendationSubmissionLogFactory;
6use GrowthExperiments\NewcomerTasks\AddLink\LinkRecommendationSubmissionLogFactory;
7use GrowthExperiments\NewcomerTasks\AddSectionImage\SectionImageRecommendationSubmissionLogFactory;
8use GrowthExperiments\NewcomerTasks\CampaignConfig;
9use GrowthExperiments\NewcomerTasks\ConfigurationLoader\ConfigurationLoader;
10use GrowthExperiments\NewcomerTasks\Task\TaskSet;
11use GrowthExperiments\NewcomerTasks\Task\TaskSetFilters;
12use GrowthExperiments\NewcomerTasks\TaskType\ImageRecommendationTaskType;
13use GrowthExperiments\NewcomerTasks\TaskType\ImageRecommendationTaskTypeHandler;
14use GrowthExperiments\NewcomerTasks\TaskType\LinkRecommendationTaskType;
15use GrowthExperiments\NewcomerTasks\TaskType\LinkRecommendationTaskTypeHandler;
16use GrowthExperiments\NewcomerTasks\TaskType\SectionImageRecommendationTaskType;
17use GrowthExperiments\NewcomerTasks\TaskType\SectionImageRecommendationTaskTypeHandler;
18use MediaWiki\User\UserIdentity;
19
20/**
21 * A TaskSuggester decorator that injects data for task type "quality gates" into a TaskSet.
22 *
23 */
24class QualityGateDecorator implements TaskSuggester {
25
26    /** @var TaskSuggester */
27    private $taskSuggester;
28
29    /** @var ImageRecommendationSubmissionLogFactory */
30    private $imageRecommendationSubmissionLogFactory;
31
32    /** @var LinkRecommendationSubmissionLogFactory */
33    private $linkRecommendationSubmissionLogFactory;
34
35    /** @var ConfigurationLoader */
36    private $configurationLoader;
37
38    /** @var int */
39    private $imageRecommendationCountForUser;
40
41    /** @var int */
42    private $linkRecommendationCountForUser;
43
44    private SectionImageRecommendationSubmissionLogFactory $sectionImageRecommendationSubmissionLogFactory;
45    private ?int $sectionImageRecommendationCountForUser = null;
46
47    /** @var CampaignConfig */
48    private $campaignConfig;
49
50    /**
51     * @param TaskSuggester $taskSuggester
52     * @param ConfigurationLoader $configurationLoader
53     * @param ImageRecommendationSubmissionLogFactory $imageRecommendationSubmissionLogFactory
54     * @param SectionImageRecommendationSubmissionLogFactory $sectionImageRecommendationSubmissionLogFactory
55     * @param LinkRecommendationSubmissionLogFactory $linkRecommendationSubmissionLogFactory
56     * @param CampaignConfig $campaignConfig
57     */
58    public function __construct(
59        TaskSuggester $taskSuggester,
60        ConfigurationLoader $configurationLoader,
61        ImageRecommendationSubmissionLogFactory $imageRecommendationSubmissionLogFactory,
62        SectionImageRecommendationSubmissionLogFactory $sectionImageRecommendationSubmissionLogFactory,
63        LinkRecommendationSubmissionLogFactory $linkRecommendationSubmissionLogFactory,
64        CampaignConfig $campaignConfig
65    ) {
66        $this->taskSuggester = $taskSuggester;
67        $this->imageRecommendationSubmissionLogFactory = $imageRecommendationSubmissionLogFactory;
68        $this->sectionImageRecommendationSubmissionLogFactory = $sectionImageRecommendationSubmissionLogFactory;
69        $this->configurationLoader = $configurationLoader;
70        $this->linkRecommendationSubmissionLogFactory = $linkRecommendationSubmissionLogFactory;
71        $this->campaignConfig = $campaignConfig;
72    }
73
74    /** @inheritDoc */
75    public function suggest(
76        UserIdentity $user,
77        TaskSetFilters $taskSetFilters,
78        ?int $limit = null,
79        ?int $offset = null,
80        array $options = []
81    ) {
82        $tasks = $this->taskSuggester->suggest( $user, $taskSetFilters, $limit, $offset, $options );
83        // TODO: Split out QualityGates methods into separate classes per task type.
84        if ( $tasks instanceof TaskSet ) {
85            $imageRecommendationTaskType =
86                $this->configurationLoader->getTaskTypes()[ImageRecommendationTaskTypeHandler::TASK_TYPE_ID] ?? null;
87            if ( $imageRecommendationTaskType instanceof ImageRecommendationTaskType ) {
88                $tasks->setQualityGateConfigForTaskType( ImageRecommendationTaskTypeHandler::TASK_TYPE_ID, [
89                    'dailyLimit' => $this->isImageRecommendationDailyTaskLimitExceeded(
90                        $user,
91                        $imageRecommendationTaskType
92                    ),
93                    'dailyCount' => $this->getImageRecommendationTasksDoneByUserForCurrentDay( $user ),
94                ] );
95            }
96            $sectionImageRecommendationTaskType =
97                $this->configurationLoader->getTaskTypes()[SectionImageRecommendationTaskTypeHandler::TASK_TYPE_ID]
98                ?? null;
99            if ( $sectionImageRecommendationTaskType instanceof SectionImageRecommendationTaskType ) {
100                $tasks->setQualityGateConfigForTaskType( SectionImageRecommendationTaskTypeHandler::TASK_TYPE_ID, [
101                    'dailyLimit' => $this->isSectionImageRecommendationDailyTaskLimitExceeded(
102                        $user,
103                        $sectionImageRecommendationTaskType
104                    ),
105                    'dailyCount' => $this->getSectionImageRecommendationTasksDoneByUserForCurrentDay( $user ),
106                ] );
107            }
108
109            $linkRecommendationTaskType =
110                $this->configurationLoader->getTaskTypes()[LinkRecommendationTaskTypeHandler::TASK_TYPE_ID] ?? null;
111            if ( $linkRecommendationTaskType instanceof LinkRecommendationTaskType ) {
112                $tasks->setQualityGateConfigForTaskType( LinkRecommendationTaskTypeHandler::TASK_TYPE_ID,
113                    [
114                        'dailyLimit' => $this->isLinkRecommendationDailyTaskLimitExceeded(
115                            $user,
116                            $linkRecommendationTaskType
117                        ),
118                        'dailyCount' => $this->getLinkRecommendationTasksDoneByUserForCurrentDay( $user )
119                    ] );
120            }
121        }
122        return $tasks;
123    }
124
125    /** @inheritDoc */
126    public function filter( UserIdentity $user, TaskSet $taskSet ) {
127        return $this->taskSuggester->filter( $user, $taskSet );
128    }
129
130    /**
131     * Check if daily limit of image recommendation is exceeded for a user.
132     *
133     * TODO: Move this into a image-recommendation specific class.
134     *
135     * @param UserIdentity $user
136     * @param ImageRecommendationTaskType $imageRecommendationTaskType
137     * @return bool|null
138     */
139    private function isImageRecommendationDailyTaskLimitExceeded(
140        UserIdentity $user,
141        ImageRecommendationTaskType $imageRecommendationTaskType
142    ): ?bool {
143        if ( $this->campaignConfig->shouldSkipImageRecommendationDailyTaskLimitForUser( $user ) ) {
144            return false;
145        }
146        return $this->getImageRecommendationTasksDoneByUserForCurrentDay( $user )
147            >= $imageRecommendationTaskType->getMaxTasksPerDay();
148    }
149
150    /**
151     * @param UserIdentity $user
152     * @return int
153     */
154    private function getImageRecommendationTasksDoneByUserForCurrentDay(
155        UserIdentity $user
156    ): int {
157        if ( $this->imageRecommendationCountForUser ) {
158            return $this->imageRecommendationCountForUser;
159        }
160        $imageRecommendationSubmissionLog =
161            $this->imageRecommendationSubmissionLogFactory
162                ->newImageRecommendationSubmissionLog( $user );
163        $this->imageRecommendationCountForUser = $imageRecommendationSubmissionLog->count();
164        return $this->imageRecommendationCountForUser;
165    }
166
167    /**
168     * Check if daily limit of link recommendation is exceeded for a user.
169     *
170     * TODO: Move this into a link-recommendation specific class.
171     *
172     * @param UserIdentity $user
173     * @param LinkRecommendationTaskType $linkRecommendationTaskType
174     * @return bool|null
175     */
176    private function isLinkRecommendationDailyTaskLimitExceeded(
177        UserIdentity $user,
178        LinkRecommendationTaskType $linkRecommendationTaskType
179    ): ?bool {
180        return $this->getLinkRecommendationTasksDoneByUserForCurrentDay( $user )
181            >= $linkRecommendationTaskType->getMaxTasksPerDay();
182    }
183
184    /**
185     * @param UserIdentity $user
186     * @return int
187     */
188    private function getLinkRecommendationTasksDoneByUserForCurrentDay(
189        UserIdentity $user
190    ): int {
191        if ( $this->linkRecommendationCountForUser ) {
192            return $this->linkRecommendationCountForUser;
193        }
194        $linkRecommendationSubmissionLog =
195            $this->linkRecommendationSubmissionLogFactory
196                ->newLinkRecommendationSubmissionLog( $user );
197        $this->linkRecommendationCountForUser = $linkRecommendationSubmissionLog->count();
198        return $this->linkRecommendationCountForUser;
199    }
200
201    /**
202     * Check if daily limit of section image recommendation is exceeded for a user.
203     *
204     * TODO: Move this into a section-image-recommendation specific class.
205     *
206     * @param UserIdentity $user
207     * @param SectionImageRecommendationTaskType $sectionImageRecommendationTaskType
208     * @return bool|null
209     */
210    private function isSectionImageRecommendationDailyTaskLimitExceeded(
211        UserIdentity $user,
212        SectionImageRecommendationTaskType $sectionImageRecommendationTaskType
213    ): ?bool {
214        return $this->getSectionImageRecommendationTasksDoneByUserForCurrentDay( $user )
215            >= $sectionImageRecommendationTaskType->getMaxTasksPerDay();
216    }
217
218    /**
219     * @param UserIdentity $user
220     * @return int
221     */
222    private function getSectionImageRecommendationTasksDoneByUserForCurrentDay(
223        UserIdentity $user
224    ): int {
225        if ( $this->sectionImageRecommendationCountForUser ) {
226            return $this->sectionImageRecommendationCountForUser;
227        }
228        $sectionImageRecommendationSubmissionLog =
229            $this->sectionImageRecommendationSubmissionLogFactory
230                ->newSectionImageRecommendationSubmissionLog( $user );
231        $this->sectionImageRecommendationCountForUser = $sectionImageRecommendationSubmissionLog->count();
232        return $this->sectionImageRecommendationCountForUser;
233    }
234
235}