PluginProbe
ElasticPress / 4.5.1
ElasticPress v4.5.1
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / assets / js / sync / index.js

index.js in ElasticPress 4.5.1, at assets/js/sync/index.js

530 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * External dependencies.
3 */
4 import { v4 as uuid } from 'uuid';
5
6 /**
7 * WordPress dependencies.
8 */
9 import { render, useCallback, useEffect, useRef, useState, WPElement } from '@wordpress/element';
10 import { __, sprintf } from '@wordpress/i18n';
11
12 /**
13 * Internal dependencies.
14 */
15 import { autoIndex, lastSyncDateTime, lastSyncFailed, isEpio, indexMeta } from './config';
16 import { useIndex } from './hooks';
17 import {
18 clearSyncParam,
19 getItemsProcessedFromIndexMeta,
20 getItemsTotalFromIndexMeta,
21 } from './utilities';
22 import SyncPage from './components/sync-page';
23
24 /**
25 * App component.
26 *
27 * @returns {WPElement} App component.
28 */
29 const App = () => {
30 /**
31 * Indexing methods.
32 */
33 const { cancelIndex, index, indexStatus } = useIndex();
34
35 /**
36 * Message log state.
37 */
38 const [log, setLog] = useState([]);
39
40 /**
41 * Sync state.
42 */
43 const [state, setState] = useState({
44 isComplete: false,
45 isDeleting: false,
46 isFailed: false,
47 isSyncing: false,
48 itemsProcessed: 0,
49 itemsTotal: 100,
50 lastSyncDateTime,
51 lastSyncFailed,
52 syncStartDateTime: null,
53 });
54
55 /**
56 * Current state reference.
57 */
58 const stateRef = useRef(state);
59
60 /**
61 * Update state, and current state ref.
62 *
63 * @param {object} newState New state properties.
64 * @returns {void}
65 */
66 const updateState = (newState) => {
67 stateRef.current = { ...stateRef.current, ...newState };
68 setState((state) => ({ ...state, ...newState }));
69 };
70
71 const logMessage = useCallback(
72 /**
73 * Log a message.
74 *
75 * @param {Array|string} message Message/s to log.
76 * @param {string} status Message status.
77 * @returns {void}
78 */
79 (message, status) => {
80 const { isDeleting } = stateRef.current;
81
82 const messages = Array.isArray(message) ? message : [message];
83
84 for (const message of messages) {
85 setLog((log) => [...log, { message, status, isDeleting, id: uuid() }]);
86 }
87 },
88 [],
89 );
90
91 const stopSync = useCallback(
92 /**
93 * Stop syncing.
94 *
95 * @returns {void}
96 */
97 () => {
98 updateState({ isPaused: false, isSyncing: false });
99 cancelIndex();
100 },
101 [cancelIndex],
102 );
103
104 const syncCompleted = useCallback(
105 /**
106 * Set sync state to completed, with success based on the number of
107 * failures in the index totals.
108 *
109 * @param {object} indexTotals Index totals.
110 * @returns {void}
111 */
112 (indexTotals) => {
113 updateState({
114 isComplete: true,
115 isPaused: false,
116 isSyncing: false,
117 lastSyncDateTime: indexTotals.end_date_time,
118 lastSyncFailed: indexTotals.failed > 0,
119 });
120
121 /**
122 * Hide the "just need to sync" notice, if it's present.
123 */
124 document.querySelector('[data-ep-notice="no_sync"]')?.remove();
125 },
126 [],
127 );
128
129 const syncFailed = useCallback(
130 /**
131 * Handle an error in the sync request.
132 *
133 * @param {object|Error} response Request response.
134 * @returns {void}
135 */
136 (response) => {
137 /**
138 * Any running requests are cancelled when a new request is made.
139 * We can handle this silently.
140 */
141 if (response.name === 'AbortError') {
142 return;
143 }
144
145 /**
146 * Log any error messages.
147 */
148 if (response.message) {
149 logMessage(response.message, 'error');
150 }
151
152 /**
153 * Log a final message and update the sync state.
154 */
155 logMessage(__('Sync failed', 'elasticpress'), 'error');
156
157 updateState({
158 isFailed: true,
159 isSyncing: false,
160 lastSyncDateTime: stateRef.current.syncStartDateTime,
161 lastSyncFailed: true,
162 });
163 },
164 [logMessage],
165 );
166
167 const syncInterrupted = useCallback(
168 /**
169 * Set sync state to interrupted.
170 *
171 * Logs an appropriate message based on the sync method and
172 * Elasticsearch hosting.
173 *
174 * @returns {void}
175 */
176 () => {
177 const { isDeleting } = stateRef.current;
178
179 const message = isDeleting
180 ? sprintf(
181 /* translators: %s: Index type. ElasticPress.io or Elasticsearch. */
182 __(
183 'Your indexing process has been stopped by WP-CLI and your %s index could be missing content. To restart indexing, please click the Start button or use WP-CLI commands to perform the reindex. Please note that search results could be incorrect or incomplete until the reindex finishes.',
184 'elasticpress',
185 ),
186 isEpio
187 ? __('ElasticPress.io', 'elasticpress')
188 : __('Elasticsearch', 'elasticpress'),
189 )
190 : __('Sync interrupted by WP-CLI command.', 'elasticpress');
191
192 logMessage(message, 'info');
193 updateState({ isSyncing: false });
194 },
195 [logMessage],
196 );
197
198 const syncInProgress = useCallback(
199 /**
200 * Set state for a sync in progress from its index meta.
201 *
202 * @param {object} indexMeta Index meta.
203 * @returns {void}
204 */
205 (indexMeta) => {
206 const isInitialSync = stateRef.current.lastSyncDateTime === null;
207
208 /**
209 * We should not appear to be deleting if this is the first sync.
210 */
211 const isDeleting = isInitialSync ? false : indexMeta.put_mapping;
212
213 updateState({
214 isCli: indexMeta.method === 'cli',
215 isDeleting,
216 isSyncing: true,
217 itemsProcessed: getItemsProcessedFromIndexMeta(indexMeta),
218 itemsTotal: getItemsTotalFromIndexMeta(indexMeta),
219 syncStartDateTime: indexMeta.start_date_time,
220 });
221 },
222 [],
223 );
224
225 const updateSyncState = useCallback(
226 /**
227 * Handle the response to a request to index.
228 *
229 * Updates the application state from the response data and logs any
230 * messages. Returns a Promise that resolves if syncing should
231 * continue.
232 *
233 * @param {object} response AJAX response.
234 * @returns {Promise} Promise that resolves if sync is to continue.
235 */
236 (response) => {
237 const { isPaused, isSyncing } = stateRef.current;
238 const { message, status, totals = [], index_meta: indexMeta } = response.data;
239
240 return new Promise((resolve) => {
241 /**
242 * Don't continue if syncing has been stopped.
243 */
244 if (!isSyncing) {
245 return;
246 }
247
248 /**
249 * Stop sync if there is an error.
250 */
251 if (status === 'error') {
252 syncFailed(response.data);
253 return;
254 }
255
256 /**
257 * Log any messages.
258 */
259 if (message) {
260 logMessage(message, status);
261 }
262
263 /**
264 * If totals are available the index is complete.
265 */
266 if (!Array.isArray(totals)) {
267 syncCompleted(totals);
268 return;
269 }
270
271 /**
272 * Update sync progress from index meta.
273 */
274 syncInProgress(indexMeta);
275
276 /**
277 * Don't continue if the sync was interrupted externally.
278 */
279 if (indexMeta.should_interrupt_sync) {
280 syncInterrupted();
281 return;
282 }
283
284 /**
285 * Don't continue if syncing has been paused.
286 */
287 if (isPaused) {
288 logMessage(__('Sync paused', 'elasticpress'), 'info');
289 return;
290 }
291
292 /**
293 * Syncing should continue.
294 */
295 resolve(indexMeta.method);
296 });
297 },
298 [syncCompleted, syncFailed, syncInProgress, syncInterrupted, logMessage],
299 );
300
301 const doIndexStatus = useCallback(
302 /**
303 * Check the status of a sync.
304 *
305 * Used to get the status of an external sync already in progress, such
306 * as a WP CLI index.
307 *
308 * @returns {void}
309 */
310 () => {
311 indexStatus().then(updateSyncState).then(doIndexStatus).catch(syncFailed);
312 },
313 [indexStatus, syncFailed, updateSyncState],
314 );
315
316 const doIndex = useCallback(
317 /**
318 * Start or continue a sync.
319 *
320 * @param {boolean} putMapping Whether to send mapping.
321 * @returns {void}
322 */
323 (putMapping) => {
324 index(putMapping)
325 .then(updateSyncState)
326 .then(
327 /**
328 * If an existing sync has been found just check its status,
329 * otherwise continue syncing.
330 *
331 * @param {string} method Sync method.
332 */
333 (method) => {
334 if (method === 'cli') {
335 doIndexStatus();
336 } else {
337 doIndex(putMapping);
338 }
339 },
340 )
341 .catch(syncFailed);
342 },
343 [doIndexStatus, index, syncFailed, updateSyncState],
344 );
345
346 const pauseSync = useCallback(
347 /**
348 * Stop syncing.
349 *
350 * @returns {void}
351 */
352 () => {
353 updateState({ isPaused: true, isSyncing: true });
354 },
355 [],
356 );
357
358 const resumeSync = useCallback(
359 /**
360 * Resume syncing.
361 *
362 * @returns {void}
363 */
364 () => {
365 const { isDeleting, lastSyncDateTime } = stateRef.current;
366 const isInitialSync = lastSyncDateTime === null;
367
368 /**
369 * Send mapping if we are deleting and syncing or if this is the
370 * first sync.
371 */
372 const putMapping = isInitialSync || isDeleting;
373
374 updateState({ isPaused: false, isSyncing: true });
375 doIndex(putMapping);
376 },
377 [doIndex],
378 );
379
380 const startSync = useCallback(
381 /**
382 * Start syncing.
383 *
384 * @param {boolean} deleteAndSync Whether to delete and sync.
385 * @returns {void}
386 */
387 (deleteAndSync) => {
388 const { lastSyncDateTime } = stateRef.current;
389 const isInitialSync = lastSyncDateTime === null;
390
391 /**
392 * We should not appear to be deleting if this is the first sync.
393 */
394 const isDeleting = isInitialSync ? false : deleteAndSync;
395
396 /**
397 * Send mapping if we are deleting and syncing or if this is the
398 * first sync.
399 */
400 const putMapping = isInitialSync || deleteAndSync;
401
402 updateState({
403 isComplete: false,
404 isFailed: false,
405 isDeleting,
406 isPaused: false,
407 isSyncing: true,
408 });
409
410 updateState({ itemsProcessed: 0, syncStartDateTime: Date.now() });
411 doIndex(putMapping);
412 },
413 [doIndex],
414 );
415
416 /**
417 * Handle clicking delete and sync button.
418 *
419 * @returns {void}
420 */
421 const onDelete = async () => {
422 startSync(true);
423 logMessage(__('Starting delete and sync…', 'elasticpress'), 'info');
424 };
425
426 /**
427 * Handle clicking pause button.
428 *
429 * @returns {void}
430 */
431 const onPause = () => {
432 pauseSync();
433 logMessage(__('Pausing sync…', 'elasticpress'), 'info');
434 };
435
436 /**
437 * Handle clicking play button.
438 *
439 * @returns {void}
440 */
441 const onResume = () => {
442 resumeSync();
443 logMessage(__('Resuming sync…', 'elasticpress'), 'info');
444 };
445
446 /**
447 * Handle clicking stop button.
448 *
449 * @returns {void}
450 */
451 const onStop = () => {
452 stopSync();
453 logMessage(__('Sync stopped', 'elasticpress'), 'info');
454 };
455
456 /**
457 * Handle clicking sync button.
458 *
459 * @returns {void}
460 */
461 const onSync = async () => {
462 startSync(false);
463 logMessage(__('Starting sync…', 'elasticpress'), 'info');
464 };
465
466 /**
467 * Initialize.
468 *
469 * @returns {void}
470 */
471 const init = () => {
472 /**
473 * Clear sync parameter from the URL to prevent a refresh triggering a new
474 * sync.
475 */
476 clearSyncParam();
477
478 /**
479 * If a sync is in progress, update state to reflect its progress.
480 */
481 if (indexMeta) {
482 syncInProgress(indexMeta);
483
484 /**
485 * If the sync is a CLI sync, start getting its status.
486 */
487 if (indexMeta.method === 'cli') {
488 doIndexStatus();
489 logMessage(__('WP CLI sync in progress', 'elasticpress'), 'info');
490 } else {
491 pauseSync();
492 logMessage(__('Sync paused', 'elasticpress'), 'info');
493 }
494
495 return;
496 }
497
498 /**
499 * Start an initial index.
500 */
501 if (autoIndex) {
502 startSync(true);
503 logMessage(__('Starting delete and sync…', 'elasticpress'), 'info');
504 }
505 };
506
507 /**
508 * Effects.
509 */
510 useEffect(init, [doIndexStatus, syncInProgress, logMessage, pauseSync, startSync]);
511
512 /**
513 * Render.
514 */
515 return (
516 <SyncPage
517 isEpio={isEpio}
518 log={log}
519 onDelete={onDelete}
520 onPause={onPause}
521 onResume={onResume}
522 onStop={onStop}
523 onSync={onSync}
524 {...state}
525 />
526 );
527 };
528
529 render(<App />, document.getElementById('ep-sync'));
530