Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
ConfigDump
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 11
600
0.00% covered (danger)
0.00%
0 / 1
 execute
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
56
 addGlobals
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 ensureAssociative
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 addConcreteNamespaceMap
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 addReplicaGroup
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addProfiles
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 addUserTesting
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 addExpectedIndices
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 isInternal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace CirrusSearch\Api;
4
5use CirrusSearch\Maintenance\ExpectedIndicesBuilder;
6use CirrusSearch\Profile\SearchProfileService;
7use CirrusSearch\SearchConfig;
8use CirrusSearch\UserTestingEngine;
9use MediaWiki\Api\ApiBase;
10use MediaWiki\Api\ApiResult;
11use MediaWiki\MediaWikiServices;
12use Wikimedia\ParamValidator\ParamValidator;
13
14/**
15 * Dumps CirrusSearch configuration for easy viewing.
16 *
17 * @license GPL-2.0-or-later
18 */
19class ConfigDump extends ApiBase {
20    use ApiTrait;
21
22    /** @var string[] */
23    public static $PUBLICLY_SHAREABLE_CONFIG_VARS = [
24        'CirrusSearchDisableUpdate',
25        'CirrusSearchConnectionAttempts',
26        'CirrusSearchSlowSearch',
27        'CirrusSearchUseExperimentalHighlighter',
28        'CirrusSearchOptimizeIndexForExperimentalHighlighter',
29        'CirrusSearchNamespaceMappings',
30        'CirrusSearchExtraIndexes',
31        'CirrusSearchExtraIndexClusters',
32        'CirrusSearchFetchConfigFromApi',
33        'CirrusSearchUpdateShardTimeout',
34        'CirrusSearchClientSideUpdateTimeout',
35        'CirrusSearchSearchShardTimeout',
36        'CirrusSearchClientSizeSearchTimeout',
37        'CirrusSearchMaintenanceTimeout',
38        'CirrusSearchPrefixSearchStartsWithAnyWord',
39        'CirrusSearchPhraseSlop',
40        'CirrusSearchPhraseRescoreBoost',
41        'CirrusSearchPhraseRescoreWindowSize',
42        'CirrusSearchFunctionRescoreWindowSize',
43        'CirrusSearchMoreAccurateScoringMode',
44        'CirrusSearchPhraseSuggestUseText',
45        'CirrusSearchPhraseSuggestUseOpeningText',
46        'CirrusSearchIndexedRedirects',
47        'CirrusSearchLinkedArticlesToUpdate',
48        'CirrusSearchUnlikedArticlesToUpdate',
49        'CirrusSearchWeights',
50        'CirrusSearchBoostOpening',
51        'CirrusSearchNearMatchWeight',
52        'CirrusSearchStemmedWeight',
53        'CirrusSearchNamespaceWeights',
54        'CirrusSearchDefaultNamespaceWeight',
55        'CirrusSearchTalkNamespaceWeight',
56        'CirrusSearchLanguageWeight',
57        'CirrusSearchPreferRecentDefaultDecayPortion',
58        'CirrusSearchPreferRecentUnspecifiedDecayPortion',
59        'CirrusSearchPreferRecentDefaultHalfLife',
60        'CirrusSearchMoreLikeThisConfig',
61        'CirrusSearchInterwikiSources',
62        'CirrusSearchRefreshInterval',
63        'CirrusSearchFragmentSize',
64        'CirrusSearchIndexAllocation',
65        'CirrusSearchFullTextQueryBuilderProfile',
66        'CirrusSearchRescoreProfile',
67        'CirrusSearchPrefixSearchRescoreProfile',
68        'CirrusSearchSimilarityProfile',
69        'CirrusSearchCrossProjectProfiles',
70        'CirrusSearchCrossProjectOrder',
71        'CirrusSearchCrossProjectSearchBlockList',
72        'CirrusSearchExtraIndexBoostTemplates',
73        'CirrusSearchEnableCrossProjectSearch',
74        'CirrusSearchEnableAltLanguage',
75        'CirrusSearchEnableArchive',
76        'CirrusSearchUseIcuFolding',
77        'CirrusSearchUseIcuTokenizer',
78        'CirrusSearchPhraseSuggestProfiles',
79        'CirrusSearchCrossProjectBlockScorerProfiles',
80        'CirrusSearchSimilarityProfiles',
81        'CirrusSearchRescoreFunctionChains',
82        'CirrusSearchCompletionProfiles',
83        'CirrusSearchCompletionSettings',
84        'CirrusSearchCompletionSuggesterUseDefaultSort',
85        // All the config below was added when moving this data
86        // from CirrusSearch config to a static array in this class
87        'CirrusSearchDevelOptions',
88        'CirrusSearchPrefixIds',
89        'CirrusSearchMoreLikeThisFields',
90        'CirrusSearchMoreLikeThisTTL',
91        'CirrusSearchFiletypeAliases',
92        'CirrusSearchDefaultCluster',
93        'CirrusSearchClientSideConnectTimeout',
94        'CirrusSearchReplicaGroup',
95        'CirrusSearchExtraBackendLatency',
96        'CirrusSearchAllowLeadingWildcard',
97        'CirrusSearchClientSideSearchTimeout',
98        'CirrusSearchStripQuestionMarks',
99        'CirrusSearchFullTextQueryBuilderProfiles',
100        'CirrusSearchEnableRegex',
101        'CirrusSearchWikimediaExtraPlugin',
102        'CirrusSearchRegexMaxDeterminizedStates',
103        'CirrusSearchMaxIncategoryOptions',
104        'CirrusSearchEnablePhraseSuggest',
105        'CirrusSearchClusterOverrides',
106        'CirrusSearchRescoreProfiles',
107        'CirrusSearchRescoreFunctionScoreChains',
108        'CirrusSearchNumCrossProjectSearchResults',
109        'CirrusSearchLanguageToWikiMap',
110        'CirrusSearchWikiToNameMap',
111        'CirrusSearchIncLinksAloneW',
112        'CirrusSearchIncLinksAloneK',
113        'CirrusSearchIncLinksAloneA',
114        'CirrusSearchNewCrossProjectPage',
115        'CirrusSearchQueryStringMaxDeterminizedStates',
116        'CirrusSearchElasticQuirks',
117        'CirrusSearchPhraseSuggestMaxErrors',
118        'CirrusSearchPhraseSuggestReverseField',
119        'CirrusSearchBoostTemplates',
120        'CirrusSearchIgnoreOnWikiBoostTemplates',
121        'CirrusSearchIndexBaseName',
122        'CirrusSearchInterleaveConfig',
123        'CirrusSearchMaxPhraseTokens',
124        'LanguageCode',
125        'ContentNamespaces',
126        'NamespacesToBeSearchedDefault',
127        'CirrusSearchCategoryDepth',
128        'CirrusSearchCategoryMax',
129        'CirrusSearchCategoryEndpoint',
130        'CirrusSearchFallbackProfile',
131        'CirrusSearchFallbackProfiles',
132    ];
133
134    public function execute() {
135        $result = $this->getResult();
136        $props = array_flip( $this->extractRequestParams()[ 'prop' ] );
137        if ( isset( $props['globals'] ) ) {
138            $this->addGlobals( $result );
139        }
140        if ( isset( $props['namespacemap'] ) ) {
141            $this->addConcreteNamespaceMap( $result );
142        }
143        if ( isset( $props['profiles'] ) ) {
144            $this->addProfiles( $result );
145        }
146        if ( isset( $props['replicagroup'] ) ) {
147            $this->addReplicaGroup( $result );
148        }
149        if ( isset( $props['usertesting'] ) ) {
150            $this->addUserTesting( $result );
151        }
152        if ( isset( $props['expectedindices'] ) ) {
153            $this->addExpectedIndices( $result );
154        }
155    }
156
157    protected function addGlobals( ApiResult $result ): void {
158        $config = $this->getConfig();
159        foreach ( self::$PUBLICLY_SHAREABLE_CONFIG_VARS as $key ) {
160            if ( $config->has( $key ) ) {
161                $result->addValue( null, $key, $config->get( $key ) );
162            }
163        }
164    }
165
166    /**
167     * When encoding to json when an array is constructed starting
168     * from zero and adding only sequential keys it will be emit
169     * as a list, instead of a map. Re-order the array so it doesn't
170     * start at zero, unless it's a single element list.
171     *
172     * This does not solve the single element list problem, but in
173     * practice the use case always has multiple values.
174     *
175     * @param array $items
176     * @return array associative array version of source if 2+ elements exist.
177     */
178    private function ensureAssociative( array $items ): array {
179        if ( isset( $items[0] ) ) {
180            $value = $items[0];
181            unset( $items[0] );
182            $items[0] = $value;
183        }
184        return $items;
185    }
186
187    /**
188     * Include a complete mapping from namespace id to index containing pages.
189     *
190     * Intended for external services/users that need to interact
191     * with elasticsearch or cirrussearch dumps directly.
192     *
193     * @param ApiResult $result Impl to write results to
194     */
195    private function addConcreteNamespaceMap( ApiResult $result ) {
196        $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
197        $conn = $this->getCirrusConnection();
198        $indexBaseName = $conn->getConfig()->get( SearchConfig::INDEX_BASE_NAME );
199        $items = [];
200        foreach ( $nsInfo->getValidNamespaces() as $ns ) {
201            $indexSuffix = $conn->getIndexSuffixForNamespace( $ns );
202            $indexName = $conn->getIndexName( $indexBaseName, $indexSuffix );
203            $items[$ns] = $indexName;
204        }
205        foreach ( self::ensureAssociative( $items ) as $ns => $indexName ) {
206            $result->addValue( 'CirrusSearchConcreteNamespaceMap', $ns, $indexName );
207        }
208    }
209
210    private function addReplicaGroup( ApiResult $result ) {
211        $result->addValue( null, 'CirrusSearchConcreteReplicaGroup',
212            $this->getCirrusConnection()->getConfig()->getClusterAssignment()->getCrossClusterName() );
213    }
214
215    /**
216     * Profile names and types
217     * @var string[]
218     */
219    private static $PROFILES = [
220        'CirrusSearchPhraseSuggestProfiles' => SearchProfileService::PHRASE_SUGGESTER,
221        'CirrusSearchCrossProjectBlockScorerProfiles' => SearchProfileService::CROSS_PROJECT_BLOCK_SCORER,
222        'CirrusSearchSimilarityProfiles' => SearchProfileService::SIMILARITY,
223        'CirrusSearchRescoreFunctionChains' => SearchProfileService::RESCORE_FUNCTION_CHAINS,
224        'CirrusSearchCompletionProfiles' => SearchProfileService::COMPLETION,
225        'CirrusSearchFullTextQueryBuilderProfiles' => SearchProfileService::FT_QUERY_BUILDER,
226        'CirrusSearchRescoreProfiles' => SearchProfileService::RESCORE,
227    ];
228
229    /**
230     * Add data from profiles
231     */
232    private function addProfiles( ApiResult $result ) {
233        $config = new SearchConfig();
234        $profileService = $config->getProfileService();
235        foreach ( self::$PROFILES as $var => $profileType ) {
236            $data = $profileService->listExposedProfiles( $profileType );
237            $result->addValue( null, $var, $data, ApiResult::OVERRIDE );
238        }
239    }
240
241    /**
242     * @param ApiResult $result
243     * @return void
244     * @throws \CirrusSearch\NoActiveTestException
245     */
246    protected function addUserTesting( ApiResult $result ): void {
247        // UserTesting only automatically assigns test buckets during web requests.
248        // This api call is different from a typical search request though, this is
249        // used from non-search pages to find out what bucket to provide to a new
250        // autocomplete session.
251        $engine = UserTestingEngine::fromConfig( $this->getConfig() );
252        $status = $engine->decideTestByAutoenroll();
253        $result->addValue( null, 'CirrusSearchActiveUserTest',
254            $status->isActive() ? $status->getTrigger() : '' );
255    }
256
257    /**
258     * @param ApiResult $result
259     * @return void
260     */
261    protected function addExpectedIndices( ApiResult $result ): void {
262        $builder = new ExpectedIndicesBuilder( $this->getSearchConfig() );
263        $result->addValue( null, 'CirrusSearchExpectedIndices',
264            $builder->build( false, null ) );
265    }
266
267    /** @inheritDoc */
268    public function getAllowedParams() {
269        return [
270            'prop' => [
271                ParamValidator::PARAM_DEFAULT => 'globals|namespacemap|profiles|replicagroup',
272                ParamValidator::PARAM_TYPE => [
273                    'globals',
274                    'namespacemap',
275                    'profiles',
276                    'replicagroup',
277                    'usertesting',
278                    'expectedindices'
279                ],
280                ParamValidator::PARAM_ISMULTI => true,
281            ],
282        ];
283    }
284
285    /**
286     * Mark as internal. This isn't meant to be used by normal api users
287     * @return bool
288     */
289    public function isInternal() {
290        return true;
291    }
292
293    /**
294     * @see ApiBase::getExamplesMessages
295     * @return array
296     */
297    protected function getExamplesMessages() {
298        return [
299            'action=cirrus-config-dump' =>
300                'apihelp-cirrus-config-dump-example'
301        ];
302    }
303
304}