Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.12% covered (success)
94.12%
32 / 34
80.00% covered (warning)
80.00%
8 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
Task
94.12% covered (success)
94.12%
32 / 34
80.00% covered (warning)
80.00%
8 / 10
13.03
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
 getToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTaskType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTitle
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTopics
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTopicScores
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setTopics
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 toJsonArray
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 newFromJsonArray
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
3.01
1<?php
2
3namespace GrowthExperiments\NewcomerTasks\Task;
4
5use GrowthExperiments\NewcomerTasks\TaskType\TaskType;
6use GrowthExperiments\NewcomerTasks\Topic\Topic;
7use GrowthExperiments\Util;
8use MediaWiki\Json\JsonUnserializable;
9use MediaWiki\Json\JsonUnserializableTrait;
10use MediaWiki\Json\JsonUnserializer;
11use MediaWiki\Linker\LinkTarget;
12use MediaWiki\Title\TitleValue;
13
14/**
15 * A single task recommendation.
16 * A Task specifies a page and the type of the task to perform on it.
17 */
18class Task implements JsonUnserializable {
19
20    use JsonUnserializableTrait;
21
22    /** @var TaskType */
23    private $taskType;
24
25    /** @var LinkTarget The page to edit. */
26    private $title;
27
28    /** @var Topic[] */
29    private $topics = [];
30
31    /** @var float[] Match scores associated to the topics in $topics, keyed by topic ID. */
32    private $topicScores = [];
33
34    /** @var string unique task identifier for analytics purposes */
35    private $token;
36
37    /**
38     * @param TaskType $taskType
39     * @param LinkTarget $title The page this task is about.
40     * @param string|null $token
41     */
42    public function __construct( TaskType $taskType, LinkTarget $title, ?string $token = null ) {
43        $this->taskType = $taskType;
44        $this->title = $title;
45        $this->token = $token ?? Util::generateRandomToken();
46    }
47
48    /**
49     * @return string
50     */
51    public function getToken(): string {
52        return $this->token;
53    }
54
55    /**
56     * @param string $token
57     */
58    public function setToken( string $token ): void {
59        $this->token = $token;
60    }
61
62    /**
63     * @return TaskType
64     */
65    public function getTaskType(): TaskType {
66        return $this->taskType;
67    }
68
69    /**
70     * @return LinkTarget
71     */
72    public function getTitle(): LinkTarget {
73        return $this->title;
74    }
75
76    /**
77     * Topics of the underlying article. Depending on the how topics are implemented, this
78     * might be never set, even if the TaskSuggester otherwise supports topic search.
79     * @return Topic[]
80     */
81    public function getTopics(): array {
82        return $this->topics;
83    }
84
85    /**
86     * Get topic matching scores for each topic this task is in.
87     * @return float[] Topic ID => score
88     */
89    public function getTopicScores(): array {
90        // Make sure the set of returned items always matches getTopics().
91        $topicScores = [];
92        foreach ( $this->getTopics() as $topic ) {
93            $topicScores[$topic->getId()] = $this->topicScores[$topic->getId()] ?? 0;
94        }
95        return $topicScores;
96    }
97
98    /**
99     * @param Topic[] $topics
100     * @param float[] $topicScores Match scores associated to the topics in $topics,
101     *   keyed by topic ID. Keys are a subset of those in $topics.
102     */
103    public function setTopics( array $topics, array $topicScores = [] ): void {
104        $this->topics = $topics;
105        $this->topicScores = $topicScores;
106    }
107
108    /** @inheritDoc */
109    protected function toJsonArray(): array {
110        # T312589 explicitly calling jsonSerialize() will be unnecessary
111        # in the future.
112        return [
113            'taskType' => $this->getTaskType()->jsonSerialize(),
114            'title' => [ $this->getTitle()->getNamespace(), $this->getTitle()->getDBkey() ],
115            'topics' => array_map( static function ( Topic $topic ) {
116                return $topic->jsonSerialize();
117            }, $this->getTopics() ),
118            'topicScores' => $this->getTopicScores(),
119            'token' => $this->getToken()
120        ];
121    }
122
123    /** @inheritDoc */
124    public static function newFromJsonArray( JsonUnserializer $unserializer, array $json ) {
125        # T312589: In the future JsonCodec will take care of unserializing
126        # the values in the $json array itself.
127        $taskType = $json['taskType'] instanceof TaskType ?
128            $json['taskType'] :
129            $unserializer->unserialize( $json['taskType'], TaskType::class );
130        $title = new TitleValue( $json['title'][0], $json['title'][1] );
131        $topics = array_map( static function ( $topic ) use ( $unserializer ) {
132            return $topic instanceof Topic ? $topic :
133                $unserializer->unserialize( $topic, Topic::class );
134        }, $json['topics'] );
135
136        $task = new static( $taskType, $title, $json['token'] ?? null );
137        $task->setTopics( $topics, $json['topicScores'] );
138        return $task;
139    }
140
141}