Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
9.09% covered (danger)
9.09%
4 / 44
10.00% covered (danger)
10.00%
1 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
LinksUpdate
9.09% covered (danger)
9.09%
4 / 44
10.00% covered (danger)
10.00%
1 / 10
234.13
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 newPageChangeUpdate
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 newPastRevisionVisibilityChange
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 newPageRefreshUpdate
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 newSaneitizerUpdate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 doJob
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 saneitize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 update
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 queueIncomingLinksJobs
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 isPrioritized
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace CirrusSearch\Job;
4
5use CirrusSearch\CirrusConfigNames;
6use CirrusSearch\Updater;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\Revision\RevisionRecord;
9use MediaWiki\Title\Title;
10
11/**
12 * Performs the appropriate updates to Elasticsearch after a LinksUpdate is
13 * completed.  The page itself is updated first then a second copy of this job
14 * is queued to update linked articles if any links change.  The job can be
15 * 'prioritized' via the 'prioritize' parameter which will switch it to a
16 * different queue then the non-prioritized jobs.  Prioritized jobs will never
17 * be deduplicated with non-prioritized jobs which is good because we can't
18 * control which job is removed during deduplication.  In our case it'd only be
19 * ok to remove the non-prioritized version.
20 *
21 * @license GPL-2.0-or-later
22 */
23class LinksUpdate extends CirrusTitleJob {
24    /**
25     * param key to determine if the job should be "prioritized"
26     */
27    private const PRIORITIZE = 'prioritize';
28
29    public function __construct( Title $title, array $params ) {
30        parent::__construct( $title, $params );
31
32        if ( $this->isPrioritized() ) {
33            $this->command .= 'Prioritized';
34        }
35        // Note that we have to keep the prioritized param or else when the job
36        // is loaded it'll load under a different name/command/type which would
37        // be confusing.
38    }
39
40    /**
41     * Prepare a page update for when this page is directly updated (new revision/delete/restore)
42     *
43     * @param Title $title
44     * @param RevisionRecord|null $revisionRecord
45     * @param array $params
46     * @return self
47     */
48    public static function newPageChangeUpdate( Title $title, ?RevisionRecord $revisionRecord, array $params ): self {
49        $params += [
50            self::PRIORITIZE => true,
51        ] + self::buildRootEventParams( self::PAGE_CHANGE, $revisionRecord );
52
53        return new self( $title, $params );
54    }
55
56    /**
57     * Prepare a cautionary update of a page that had some of its revision's visibility changed.
58     * (Theoretically not required because old revisions should not be part of the index)
59     */
60    public static function newPastRevisionVisibilityChange( Title $title ): self {
61        $params = [
62            self::PRIORITIZE => true,
63        ] + self::buildRootEventParams( self::VISIBILITY_CHANGE );
64
65        return new self( $title, $params );
66    }
67
68    /**
69     * Prepare a page update for when the rendered output of the page might have changed due to a
70     * change not directly related to this page (e.g. template update).
71     */
72    public static function newPageRefreshUpdate( Title $title, array $params ): self {
73        $params += [
74            self::PRIORITIZE => false,
75        ] + self::buildRootEventParams( self::PAGE_REFRESH );
76        return new self( $title, $params );
77    }
78
79    /**
80     * New change emitted from the saneitizer
81     * @param Title $title
82     * @param string|null $cluster optional target cluster, null for all clusters
83     * @return self
84     */
85    public static function newSaneitizerUpdate( Title $title, ?string $cluster ): self {
86        $params = [
87            self::PRIORITIZE => false,
88            self::CLUSTER => $cluster,
89        ] + self::buildRootEventParams( self::SANEITIZER );
90        return new self( $title, $params );
91    }
92
93    /**
94     * @return bool
95     */
96    protected function doJob() {
97        $updater = Updater::build( $this->getSearchConfig(), $this->params['cluster'] ?? null );
98        if ( $this->params[self::UPDATE_KIND] === self::SANEITIZER ) {
99            $this->saneitize( $updater );
100        } else {
101            $this->update( $updater );
102        }
103
104        if ( $this->getSearchConfig()->get( CirrusConfigNames::EnableIncomingLinkCounting ) ) {
105            $this->queueIncomingLinksJobs();
106        }
107
108        return true;
109    }
110
111    /**
112     * Indirection doing technically nothing but help measure the impact of these jobs via flame graphs.
113     * @param Updater $updater
114     * @return void
115     */
116    private function saneitize( Updater $updater ): void {
117        $this->update( $updater );
118    }
119
120    private function update( Updater $updater ): void {
121        $updater->updateFromTitle( $this->title, $this->params[self::UPDATE_KIND], $this->params[self::ROOT_EVENT_TIME] );
122    }
123
124    /**
125     * Queue IncomingLinkCount jobs when pages are newly linked or unlinked
126     */
127    private function queueIncomingLinksJobs() {
128        $titleKeys = array_merge( $this->params[ 'addedLinks' ] ?? [],
129            $this->params[ 'removedLinks' ] ?? [] );
130        $refreshInterval = $this->getSearchConfig()->get( CirrusConfigNames::RefreshInterval );
131        $jobs = [];
132        $jobQueue = MediaWikiServices::getInstance()->getJobQueueGroup();
133        foreach ( $titleKeys as $titleKey ) {
134            $title = Title::newFromDBkey( $titleKey );
135            if ( !$title || !$title->canExist() ) {
136                continue;
137            }
138            // If possible, delay the job execution by a few seconds so Elasticsearch
139            // can refresh to contain what we just sent it.  The delay should be long
140            // enough for Elasticsearch to complete the refresh cycle, which normally
141            // takes wgCirrusSearchRefreshInterval seconds but we double it and add
142            // one just in case.
143            $delay = 2 * $refreshInterval + 1;
144            $jobs[] = new IncomingLinkCount( $title, [
145                'cluster' => $this->params['cluster'],
146            ] + self::buildJobDelayOptions( IncomingLinkCount::class, $delay, $jobQueue ) );
147        }
148        $jobQueue->push( $jobs );
149    }
150
151    /**
152     * @return bool Is this job prioritized?
153     */
154    public function isPrioritized() {
155        return isset( $this->params[self::PRIORITIZE] ) && $this->params[self::PRIORITIZE];
156    }
157}