Skip to content

Commit d2a4959

Browse files
authored
Improve localist event orphan checker to check for deleted instances if the event exists (#512)
1 parent 00d50fb commit d2a4959

File tree

6 files changed

+214
-90
lines changed

6 files changed

+214
-90
lines changed

modules/stanford_events/modules/stanford_events_importer/src/EventSubscriber/EventsImporterSubscriber.php

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ final class EventsImporterSubscriber implements EventSubscriberInterface {
2222
*/
2323
public function __construct(
2424
private readonly QueueFactory $queue,
25-
private readonly EntityTypeManagerInterface $entityTypeManager
25+
private readonly EntityTypeManagerInterface $entityTypeManager,
26+
private readonly Connection $database
2627
) {}
2728

2829
/**
@@ -48,31 +49,44 @@ public function postImport(MigrateImportEvent $event): void {
4849
) {
4950
return;
5051
}
51-
$nodeStorage = $this->entityTypeManager->getStorage('node');
52-
try {
53-
$nids = $nodeStorage->getQuery()
54-
->accessCheck(FALSE)
55-
->condition('su_event_localist_id', 0, '>')
56-
->range(0, 50)
57-
->execute();
58-
}
59-
catch (\Exception $e) {
60-
// If the field doesn't exist, an exception will be thrown. But we can
61-
// just exit the method because that's where the data lives. Likely this
62-
// is because the configuration has been imported yet.
63-
return;
52+
$query = $this->database->select($event->getMigration()
53+
->getIdMap()
54+
->getQualifiedMapTableName(), 'map')
55+
->fields('map', ['destid1', 'sourceid1'])
56+
->condition('source_row_status', MigrateIdMapInterface::STATUS_IGNORED)
57+
->orderBy('last_imported', 'ASC')
58+
->execute();
59+
while ($row = $query->fetchAssoc()) {
60+
$this->queueNode((int) $row['destid1'], (int) $row['sourceid1']);
6461
}
62+
}
63+
64+
/**
65+
* Queue the given node to be check if it needs to be cleared.
66+
*
67+
* @param int $nid
68+
* @param int $instanceId
69+
*
70+
* @return void
71+
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
72+
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
73+
*/
74+
protected function queueNode(int $nid, int $instanceId): void {
75+
$queue = $this->queue->get('localist_event_checker');
76+
$node = $this->entityTypeManager->getStorage('node')->load($nid);
6577

66-
if (!$nids) {
78+
if (
79+
!$node->hasField('su_event_localist_id') ||
80+
!$node->get('su_event_localist_id')->count()
81+
) {
6782
return;
6883
}
6984

70-
foreach ($nodeStorage->loadMultiple($nids) as $node) {
71-
$queue->createItem([
72-
(int) $node->get('su_event_localist_id')?->getString(),
73-
(int) $node->id(),
74-
]);
75-
}
85+
$queue->createItem([
86+
(int) $node->get('su_event_localist_id')?->getString(),
87+
$nid,
88+
$instanceId,
89+
]);
7690
}
7791

7892
}

modules/stanford_events/modules/stanford_events_importer/src/Plugin/QueueWorker/LocalistEventChecker.php

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,25 @@ public static function create(ContainerInterface $container, array $configuratio
5353
* {@inheritdoc}
5454
*/
5555
public function processItem($data): void {
56-
[$sourceId, $destId] = $data;
56+
[$sourceId, $destId, $instanceId] = $data;
5757
// Fetch from the API. Only delete the destination entity if the API
58-
// response indicates the event was not found. Don't do anything if there's
59-
// a timeout or some unexpected error.
58+
// response indicates the event was not found or the given event instance
59+
// does not exist in the list. An event instance is tied to the date of the
60+
// event. If a user deletes a date, but the event still exists, the instance
61+
// will not exist. Don't do anything if there's a timeout or some unexpected
62+
// error.
6063
try {
61-
$this->httpClient->get("https://events.stanford.edu/api/2/events/$sourceId", ['timeout' => 5]);
64+
$response = $this->httpClient->get("https://events.stanford.edu/api/2/events/$sourceId", ['timeout' => 5]);
65+
66+
$response = json_decode($response->getBody()
67+
->getContents(), TRUE, 512, JSON_THROW_ON_ERROR);
68+
69+
foreach ($response['event']['event_instances'] as $instance) {
70+
if ($instance['event_instance']['id'] == $instanceId) {
71+
throw new \Exception('Instance Exists');
72+
}
73+
}
74+
$this->deleteNode($destId);
6275
}
6376
catch (ClientException $e) {
6477
try {
@@ -68,15 +81,28 @@ public function processItem($data): void {
6881
isset($errorResponse['error']) &&
6982
str_contains($errorResponse['error'], 'Couldn\'t find Event with')
7083
) {
71-
$this->entityTypeManager->getStorage('node')
72-
->load($destId)
73-
->delete();
84+
$this->deleteNode($destId);
7485
}
7586
}
7687
catch (\Throwable $e) {
7788
// Do nothing.
7889
}
7990
}
91+
catch (\Exception $e) {
92+
// Do nothing.
93+
}
94+
}
95+
96+
/**
97+
* Delete the given node from the system.
98+
*
99+
* @param int $nid
100+
* Node id.
101+
*/
102+
protected function deleteNode(int $nid) {
103+
$this->entityTypeManager->getStorage('node')
104+
->load($nid)
105+
->delete();
80106
}
81107

82108
}

modules/stanford_events/modules/stanford_events_importer/stanford_events_importer.install

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,10 @@ function stanford_events_importer_update_8004() {
132132
->fields(['source_row_status' => 1])
133133
->execute();
134134
}
135+
136+
/**
137+
* Clear the event migration queue.
138+
*/
139+
function stanford_events_importer_update_8005() {
140+
\Drupal::queue('localist_event_checker')->deleteQueue();
141+
}

modules/stanford_events/modules/stanford_events_importer/stanford_events_importer.services.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ services:
77

88
stanford_events_importer.event_subscriber:
99
class: Drupal\stanford_events_importer\EventSubscriber\EventsImporterSubscriber
10-
arguments: ['@queue', '@entity_type.manager']
10+
arguments: ['@queue', '@entity_type.manager', '@database']
1111
tags:
1212
- { name: event_subscriber }

modules/stanford_events/modules/stanford_events_importer/tests/src/Unit/EventSubscriber/EventsImporterSubscriberTest.php

Lines changed: 76 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
namespace Drupal\Tests\stanford_events_importer\Unit\EventSubscriber;
66

7+
use Drupal\Core\Database\Connection;
78
use Drupal\Core\Database\Query\Select;
89
use Drupal\Core\Database\StatementInterface;
910
use Drupal\Core\Entity\EntityStorageInterface;
1011
use Drupal\Core\Entity\EntityTypeManagerInterface;
11-
use Drupal\Core\Entity\Query\QueryInterface;
1212
use Drupal\Core\Field\FieldItemListInterface;
1313
use Drupal\Core\Queue\QueueFactory;
1414
use Drupal\Core\Queue\QueueInterface;
@@ -38,12 +38,19 @@ class EventsImporterSubscriberTest extends UnitTestCase {
3838
protected $queueFactory;
3939

4040
/**
41-
* The database connection mock.
41+
* The entity type manager mock.
4242
*
4343
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
4444
*/
4545
protected $entityTypeManager;
4646

47+
/**
48+
* The database connection mock.
49+
*
50+
* @var \Drupal\Core\Database\Connection|\PHPUnit\Framework\MockObject\MockObject
51+
*/
52+
protected $database;
53+
4754
/**
4855
* The event subscriber.
4956
*
@@ -59,10 +66,12 @@ protected function setUp(): void {
5966

6067
$this->queueFactory = $this->createMock(QueueFactory::class);
6168
$this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
69+
$this->database = $this->createMock(Connection::class);
6270

6371
$this->subscriber = new EventsImporterSubscriber(
6472
$this->queueFactory,
65-
$this->entityTypeManager
73+
$this->entityTypeManager,
74+
$this->database
6675
);
6776
}
6877

@@ -140,84 +149,102 @@ public function testPostImportNonEmptyQueue(): void {
140149
* Tests postImport processes ignored events.
141150
*/
142151
public function testPostImportProcessesIgnoredEvents(): void {
152+
$idMap = $this->createMock(Sql::class);
153+
$idMap->expects($this->once())
154+
->method('getQualifiedMapTableName')
155+
->willReturn('migrate_map_stanford_localist_importer');
156+
143157
$migration = $this->createMock(MigrationInterface::class);
144158
$migration->expects($this->once())
145159
->method('id')
146160
->willReturn('stanford_localist_importer');
161+
$migration->expects($this->once())
162+
->method('getIdMap')
163+
->willReturn($idMap);
147164

148165
$event = $this->createMock(MigrateImportEvent::class);
149-
$event->expects($this->once())
166+
$event->expects($this->any())
150167
->method('getMigration')
151168
->willReturn($migration);
152169

153170
$queue = $this->createMock(QueueInterface::class);
154171
$queue->expects($this->once())
155172
->method('numberOfItems')
156173
->willReturn(0);
157-
158-
$ignoredItems = [
159-
'12345' => '67890',
160-
'23456' => '78901',
161-
'34567' => '89012',
162-
];
163-
164-
$queue->expects($this->once())
174+
$queue->expects($this->exactly(2))
165175
->method('createItem')
166-
->willReturnCallback(function($item) use (&$ignoredItems) {
167-
$sourceId = (string) $item[0];
168-
$this->assertArrayHasKey($sourceId, $ignoredItems);
169-
$this->assertEquals((int) $ignoredItems[$sourceId], $item[1]);
170-
return TRUE;
171-
});
176+
->willReturn(TRUE);
172177

173-
$this->queueFactory->expects($this->once())
178+
$this->queueFactory->expects($this->any())
174179
->method('get')
175180
->with('localist_event_checker')
176181
->willReturn($queue);
177182

178-
$query = $this->createMock(QueryInterface::class);
179-
180-
$query->expects($this->once())
181-
->method('accessCheck')
182-
->with(FALSE)
183+
$statement = $this->createMock(StatementInterface::class);
184+
$statement->expects($this->exactly(3))
185+
->method('fetchAssoc')
186+
->willReturnOnConsecutiveCalls(
187+
['destid1' => '100', 'sourceid1' => '12345'],
188+
['destid1' => '200', 'sourceid1' => '23456'],
189+
FALSE
190+
);
191+
192+
$dbQuery = $this->createMock(Select::class);
193+
$dbQuery->expects($this->once())
194+
->method('fields')
195+
->with('map', ['destid1', 'sourceid1'])
183196
->willReturnSelf();
184-
185-
$query->expects($this->once())
197+
$dbQuery->expects($this->once())
186198
->method('condition')
187-
->with('su_event_localist_id', 0, '>')
199+
->with('source_row_status', MigrateIdMapInterface::STATUS_IGNORED)
188200
->willReturnSelf();
189-
190-
$query->expects($this->once())
191-
->method('range')
192-
->with(0, 50)
201+
$dbQuery->expects($this->once())
202+
->method('orderBy')
203+
->with('last_imported', 'ASC')
193204
->willReturnSelf();
205+
$dbQuery->expects($this->once())
206+
->method('execute')
207+
->willReturn($statement);
194208

195-
$query->expects($this->once())->method('execute')->willReturn([
196-
1 => 2,
197-
3 => 4,
198-
]);
199-
200-
$storage = $this->createMock(EntityStorageInterface::class);
201-
$storage->expects($this->once())->method('getQuery')
202-
->willReturn($query);
209+
$this->database->expects($this->once())
210+
->method('select')
211+
->with('migrate_map_stanford_localist_importer', 'map')
212+
->willReturn($dbQuery);
203213

204214
$field = $this->createMock(FieldItemListInterface::class);
205-
$field->expects($this->once())->method('getString')->willReturn(12345);
215+
$field->expects($this->exactly(2))
216+
->method('getString')
217+
->willReturnOnConsecutiveCalls('67890', '78901');
218+
$field->expects($this->exactly(2))
219+
->method('count')
220+
->willReturn(1);
221+
222+
$node1 = $this->createMock(NodeInterface::class);
223+
$node1->expects($this->once())
224+
->method('hasField')
225+
->with('su_event_localist_id')
226+
->willReturn(TRUE);
227+
$node1->expects($this->exactly(2))
228+
->method('get')
229+
->with('su_event_localist_id')
230+
->willReturn($field);
206231

207-
$node = $this->createMock(NodeInterface::class);
208-
$node->expects($this->once())
232+
$node2 = $this->createMock(NodeInterface::class);
233+
$node2->expects($this->once())
234+
->method('hasField')
235+
->with('su_event_localist_id')
236+
->willReturn(TRUE);
237+
$node2->expects($this->exactly(2))
209238
->method('get')
210239
->with('su_event_localist_id')
211240
->willReturn($field);
212-
$node->expects($this->once())
213-
->method('id')
214-
->willReturn(67890);
215241

216-
$storage->expects($this->once())
217-
->method('loadMultiple')
218-
->willReturn([67890 => $node]);
242+
$storage = $this->createMock(EntityStorageInterface::class);
243+
$storage->expects($this->exactly(2))
244+
->method('load')
245+
->willReturnOnConsecutiveCalls($node1, $node2);
219246

220-
$this->entityTypeManager->expects($this->once())
247+
$this->entityTypeManager->expects($this->exactly(2))
221248
->method('getStorage')
222249
->with('node')
223250
->willReturn($storage);

0 commit comments

Comments
 (0)