Skip to content

Commit 20307f5

Browse files
Issue #2930720 by drunken monkey, borisson_: Added a UI for rebuilding the tracking table for an index.
1 parent 6798554 commit 20307f5

9 files changed

+183
-17
lines changed

Diff for: CHANGELOG.txt

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
Search API 1.x, dev (xxxx-xx-xx):
22
---------------------------------
3+
- #2930720 by drunken monkey, borisson_: Added a UI for rebuilding the tracking
4+
table for an index.
35
- #2912246 by drunken monkey: Fixed inconsistent array indices in query
46
languages.
57
- #2939405 by bserem, borisson_, drunken monkey, yoroy: Improved UI text on

Diff for: search_api.routing.yml

+11
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,14 @@ entity.search_api_index.clear:
229229
parameters:
230230
search_api_index:
231231
with_config_overrides: TRUE
232+
233+
entity.search_api_index.rebuild_tracker:
234+
path: '/admin/config/search/search-api/index/{search_api_index}/rebuild-tracker'
235+
defaults:
236+
_entity_form: 'search_api_index.rebuild_tracker'
237+
requirements:
238+
_entity_access: 'search_api_index.rebuild_tracker'
239+
options:
240+
parameters:
241+
search_api_index:
242+
with_config_overrides: TRUE

Diff for: src/Entity/Index.php

+37-15
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* "disable" = "Drupal\search_api\Form\IndexDisableConfirmForm",
5050
* "reindex" = "Drupal\search_api\Form\IndexReindexConfirmForm",
5151
* "clear" = "Drupal\search_api\Form\IndexClearConfirmForm",
52+
* "rebuild_tracker" = "Drupal\search_api\Form\IndexRebuildTrackerConfirmForm",
5253
* },
5354
* },
5455
* admin_permission = "administer search_api",
@@ -1109,24 +1110,45 @@ public function reindex() {
11091110
* {@inheritdoc}
11101111
*/
11111112
public function clear() {
1112-
if ($this->status()) {
1113-
// Only invoke the hook if we actually did something.
1114-
$invoke_hook = FALSE;
1115-
if (!$this->isReindexing()) {
1116-
$invoke_hook = TRUE;
1117-
$this->setHasReindexed();
1118-
$this->getTrackerInstance()->trackAllItemsUpdated();
1119-
}
1120-
if (!$this->isReadOnly()) {
1121-
$invoke_hook = TRUE;
1122-
$this->getServerInstance()->deleteAllIndexItems($this);
1123-
}
1124-
if ($invoke_hook) {
1125-
\Drupal::moduleHandler()->invokeAll('search_api_index_reindex', [$this, !$this->isReadOnly()]);
1126-
}
1113+
if (!$this->status()) {
1114+
return;
1115+
}
1116+
1117+
// Only invoke the hook if we actually did something.
1118+
$invoke_hook = FALSE;
1119+
if (!$this->isReindexing()) {
1120+
$invoke_hook = TRUE;
1121+
$this->setHasReindexed();
1122+
$this->getTrackerInstance()->trackAllItemsUpdated();
1123+
}
1124+
if (!$this->isReadOnly()) {
1125+
$invoke_hook = TRUE;
1126+
$this->getServerInstance()->deleteAllIndexItems($this);
1127+
}
1128+
if ($invoke_hook) {
1129+
\Drupal::moduleHandler()
1130+
->invokeAll('search_api_index_reindex', [$this, !$this->isReadOnly()]);
11271131
}
11281132
}
11291133

1134+
/**
1135+
* {@inheritdoc}
1136+
*/
1137+
public function rebuildTracker() {
1138+
if (!$this->status()) {
1139+
return;
1140+
}
1141+
1142+
$index_task_manager = \Drupal::getContainer()
1143+
->get('search_api.index_task_manager');
1144+
$index_task_manager->stopTracking($this);
1145+
$index_task_manager->startTracking($this);
1146+
$this->setHasReindexed();
1147+
\Drupal::moduleHandler()
1148+
->invokeAll('search_api_index_reindex', [$this, FALSE]);
1149+
$index_task_manager->addItemsBatch($this);
1150+
}
1151+
11301152
/**
11311153
* {@inheritdoc}
11321154
*/

Diff for: src/Form/IndexClearConfirmForm.php

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
4343

4444
try {
4545
$index->clear();
46+
drupal_set_message($this->t('All items were successfully deleted from search index %name.', ['%name' => $index->label()]));
4647
}
4748
catch (SearchApiException $e) {
4849
drupal_set_message($this->t('Failed to clear the search index %name.', ['%name' => $index->label()]), 'error');

Diff for: src/Form/IndexRebuildTrackerConfirmForm.php

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace Drupal\search_api\Form;
4+
5+
use Drupal\Core\Entity\EntityConfirmFormBase;
6+
use Drupal\Core\Form\FormStateInterface;
7+
use Drupal\Core\Url;
8+
9+
/**
10+
* Defines a confirm form for clearing an index.
11+
*/
12+
class IndexRebuildTrackerConfirmForm extends EntityConfirmFormBase {
13+
14+
/**
15+
* {@inheritdoc}
16+
*/
17+
public function getQuestion() {
18+
return $this->t('Are you sure you want to rebuild the tracking data for the search index %name?', ['%name' => $this->entity->label()]);
19+
}
20+
21+
/**
22+
* {@inheritdoc}
23+
*/
24+
public function getDescription() {
25+
return $this->t("<p>The complete information about existing and indexed items for this index will be deleted and will have to be rebuilt.</p><p>This should usually not be necessary, but can help if some existing items aren't contained in the index's tracking data for whatever reason (in other words, when the total number of items to be indexed is less than it should be).</p><p>This action cannot be undone.</p>");
26+
}
27+
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function getCancelUrl() {
32+
return new Url('entity.search_api_index.canonical', ['search_api_index' => $this->entity->id()]);
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function submitForm(array &$form, FormStateInterface $form_state) {
39+
/** @var \Drupal\search_api\IndexInterface $index */
40+
$index = $this->getEntity();
41+
$index->rebuildTracker();
42+
drupal_set_message($this->t('The tracking information for search index %name will be rebuilt.', ['%name' => $index->label()]));
43+
$form_state->setRedirect('entity.search_api_index.canonical', ['search_api_index' => $index->id()]);
44+
}
45+
46+
}

Diff for: src/Form/IndexStatusForm.php

+13-2
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ public function buildForm(array $form, FormStateInterface $form_state, IndexInte
6262

6363
$form['#index'] = $index;
6464

65-
$form['#attached']['library'][] = 'search_api/drupal.search_api.admin_css';
65+
if ($index->status() && $index->hasValidTracker()) {
66+
$form['#attached']['library'][] = 'search_api/drupal.search_api.admin_css';
6667

67-
if ($index->hasValidTracker()) {
6868
if (!$this->getIndexTaskManager()->isTrackingComplete($index)) {
6969
$form['tracking'] = [
7070
'#type' => 'details',
@@ -163,7 +163,14 @@ public function buildForm(array $form, FormStateInterface $form_state, IndexInte
163163
'#name' => 'clear',
164164
'#button_type' => 'danger',
165165
];
166+
$form['actions']['rebuild_tracker'] = [
167+
'#type' => 'submit',
168+
'#value' => $this->t('Rebuild tracking information'),
169+
'#name' => 'rebuild_tracker',
170+
'#button_type' => 'danger',
171+
];
166172
}
173+
167174
return $form;
168175
}
169176

@@ -226,6 +233,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
226233
$form_state->setRedirect('entity.search_api_index.clear', ['search_api_index' => $index->id()]);
227234
break;
228235

236+
case 'rebuild_tracker':
237+
$form_state->setRedirect('entity.search_api_index.rebuild_tracker', ['search_api_index' => $index->id()]);
238+
break;
239+
229240
case 'track_now':
230241
$this->getIndexTaskManager()->addItemsBatch($index);
231242
break;

Diff for: src/IndexInterface.php

+8
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,14 @@ public function reindex();
695695
*/
696696
public function clear();
697697

698+
/**
699+
* Starts a rebuild of the index's tracking information.
700+
*
701+
* @see \Drupal\search_api\Task\IndexTaskManagerInterface::stopTracking()
702+
* @see \Drupal\search_api\Task\IndexTaskManagerInterface::startTracking()
703+
*/
704+
public function rebuildTracker();
705+
698706
/**
699707
* Determines whether reindexing has been triggered in this page request.
700708
*

Diff for: src/UnsavedIndexConfiguration.php

+7
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,13 @@ public function clear() {
595595
$this->entity->clear();
596596
}
597597

598+
/**
599+
* {@inheritdoc}
600+
*/
601+
public function rebuildTracker() {
602+
$this->entity->rebuildTracker();
603+
}
604+
598605
/**
599606
* {@inheritdoc}
600607
*/

Diff for: tests/src/Functional/IntegrationTest.php

+58
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ public function testFramework() {
118118
$this->changeIndexDatasource();
119119
$this->changeIndexServer();
120120
$this->checkIndexing();
121+
$this->checkIndexActions();
121122

122123
$this->deleteServer();
123124
}
@@ -183,6 +184,7 @@ public function testIntegerIndex() {
183184
$this->changeIndexDatasource();
184185
$this->changeIndexServer();
185186
$this->checkIndexing();
187+
$this->checkIndexActions();
186188
}
187189

188190
/**
@@ -689,6 +691,17 @@ protected function countRemainingItems() {
689691
return $this->getIndex()->getTrackerInstance()->getRemainingItemsCount();
690692
}
691693

694+
/**
695+
* Counts the number of items indexed on the server for the test index.
696+
*
697+
* @return int
698+
* The number of items indexed on the server for the test index.
699+
*/
700+
protected function countItemsOnServer() {
701+
$key = 'search_api_test.backend.indexed.' . $this->indexId;
702+
return count(\Drupal::state()->get($key, []));
703+
}
704+
692705
/**
693706
* Enables all processors.
694707
*/
@@ -1444,6 +1457,51 @@ protected function checkIndexing() {
14441457
$this->assertSession()->pageTextNotContains('An error occurred');
14451458
}
14461459

1460+
/**
1461+
* Tests the various actions on the index status form.
1462+
*/
1463+
protected function checkIndexActions() {
1464+
$assert_session = $this->assertSession();
1465+
$index = $this->getIndex();
1466+
$tracker = $index->getTrackerInstance();
1467+
$label = $index->label();
1468+
$this->indexItems();
1469+
1470+
// Manipulate the tracking information to make it slightly off (so
1471+
// rebuilding the tracker will be necessary).
1472+
$deleted = \Drupal::database()->delete('search_api_item')
1473+
->condition('index_id', $index->id())
1474+
->condition('item_id', Utility::createCombinedId('entity:node', '2:en'))
1475+
->execute();
1476+
$this->assertEquals(1, $deleted);
1477+
$manipulated_items_count = \Drupal::entityQuery('node')->count()->execute() - 1;
1478+
1479+
$this->assertEquals($manipulated_items_count, $tracker->getIndexedItemsCount());
1480+
$this->assertEquals($manipulated_items_count, $tracker->getTotalItemsCount());
1481+
$this->assertEquals($manipulated_items_count + 1, $this->countItemsOnServer());
1482+
1483+
$this->drupalPostForm($this->getIndexPath('reindex'), [], 'Confirm');
1484+
$assert_session->pageTextContains("The search index $label was successfully reindexed.");
1485+
$this->assertEquals(0, $tracker->getIndexedItemsCount());
1486+
$this->assertEquals($manipulated_items_count, $tracker->getTotalItemsCount());
1487+
$this->assertEquals($manipulated_items_count + 1, $this->countItemsOnServer());
1488+
$this->indexItems();
1489+
1490+
$this->drupalPostForm($this->getIndexPath('clear'), [], 'Confirm');
1491+
$assert_session->pageTextContains("All items were successfully deleted from search index $label.");
1492+
$this->assertEquals(0, $tracker->getIndexedItemsCount());
1493+
$this->assertEquals($manipulated_items_count, $tracker->getTotalItemsCount());
1494+
$this->assertEquals(0, $this->countItemsOnServer());
1495+
$this->indexItems();
1496+
1497+
$this->drupalPostForm($this->getIndexPath('rebuild-tracker'), [], 'Confirm');
1498+
$assert_session->pageTextContains("The tracking information for search index $label will be rebuilt.");
1499+
$this->assertEquals(0, $tracker->getIndexedItemsCount());
1500+
$this->assertEquals($manipulated_items_count + 1, $tracker->getTotalItemsCount());
1501+
$this->assertEquals($manipulated_items_count, $this->countItemsOnServer());
1502+
$this->indexItems();
1503+
}
1504+
14471505
/**
14481506
* Tests deleting a search server via the UI.
14491507
*/

0 commit comments

Comments
 (0)