PluginProbe
ElasticPress / 4.2.2
ElasticPress v4.2.2
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.2.2, at assets/js/sync/index.js

470 lines 9.8 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 isSyncing: false,
47 itemsProcessed: 0,
48 itemsTotal: 100,
49 lastSyncDateTime,
50 lastSyncFailed,
51 syncStartDateTime: null,
52 });
53
54 /**
55 * Current state reference.
56 */
57 const stateRef = useRef(state);
58
59 /**
60 * Update state, and current state ref.
61 *
62 * @param {object} newState New state properties.
63 * @returns {void}
64 */
65 const updateState = (newState) => {
66 stateRef.current = { ...stateRef.current, ...newState };
67 setState((state) => ({ ...state, ...newState }));
68 };
69
70 const logMessage = useCallback(
71 /**
72 * Log a message.
73 *
74 * @param {Array|string} message Message/s to log.
75 * @param {string} status Message status.
76 * @returns {void}
77 */
78 (message, status) => {
79 const { isDeleting } = stateRef.current;
80
81 const messages = Array.isArray(message) ? message : [message];
82
83 for (const message of messages) {
84 setLog((log) => [...log, { message, status, isDeleting, id: uuid() }]);
85 }
86 },
87 [],
88 );
89
90 const syncCompleted = useCallback(
91 /**
92 * Set sync state to completed, with success based on the number of
93 * failures in the index totals.
94 *
95 * @param {object} indexTotals Index totals.
96 * @returns {void}
97 */
98 (indexTotals) => {
99 updateState({
100 isComplete: true,
101 isPaused: false,
102 isSyncing: false,
103 lastSyncDateTime: indexTotals.end_date_time,
104 lastSyncFailed: indexTotals.failed > 0,
105 });
106 },
107 [],
108 );
109
110 const syncFailed = useCallback(
111 /**
112 * Handle an error in the sync request.
113 *
114 * @param {Error} error Request error.
115 * @returns {void}
116 */
117 (error) => {
118 /**
119 * Any running requests are cancelled when a new request is made.
120 * We can handle this silently.
121 */
122 if (error.name === 'AbortError') {
123 return;
124 }
125
126 /**
127 * Log any messages.
128 */
129 if (error.message) {
130 logMessage(error.message, 'error');
131 }
132
133 logMessage(__('Sync failed', 'elasticpress'), 'error');
134 updateState({ isSyncing: false });
135 },
136 [logMessage],
137 );
138
139 const syncInterrupted = useCallback(
140 /**
141 * Set sync state to interrupted.
142 *
143 * Logs an appropriate message based on the sync method and
144 * Elasticsearch hosting.
145 *
146 * @returns {void}
147 */
148 () => {
149 const { isDeleting } = stateRef.current;
150
151 const message = isDeleting
152 ? sprintf(
153 /* translators: %s: Index type. ElasticPress.io or Elasticsearch. */
154 __(
155 '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.',
156 'elasticpress',
157 ),
158 isEpio
159 ? __('ElasticPress.io', 'elasticpress')
160 : __('Elasticsearch', 'elasticpress'),
161 )
162 : __('Sync interrupted by WP-CLI command.', 'elasticpress');
163
164 logMessage(message, 'info');
165 updateState({ isSyncing: false });
166 },
167 [logMessage],
168 );
169
170 const syncInProgress = useCallback(
171 /**
172 * Set state for a sync in progress from its index meta.
173 *
174 * @param {object} indexMeta Index meta.
175 * @returns {void}
176 */
177 (indexMeta) => {
178 updateState({
179 isCli: indexMeta.method === 'cli',
180 isComplete: false,
181 isDeleting: indexMeta.put_mapping,
182 isSyncing: true,
183 itemsProcessed: getItemsProcessedFromIndexMeta(indexMeta),
184 itemsTotal: getItemsTotalFromIndexMeta(indexMeta),
185 syncStartDateTime: indexMeta.start_date_time,
186 });
187 },
188 [],
189 );
190
191 const updateSyncState = useCallback(
192 /**
193 * Handle the response to a request to index.
194 *
195 * Updates the application state from the response data and logs any
196 * messages. Returns a Promise that resolves if syncing should
197 * continue.
198 *
199 * @param {object} response AJAX response.
200 * @returns {Promise} Promise that resolves if sync is to continue.
201 */
202 (response) => {
203 const { isPaused, isSyncing } = stateRef.current;
204 const { message, status, totals = [], index_meta: indexMeta } = response.data;
205
206 return new Promise((resolve) => {
207 /**
208 * Don't continue if syncing has been stopped.
209 */
210 if (!isSyncing) {
211 return;
212 }
213
214 /**
215 * Log any messages.
216 */
217 if (message) {
218 logMessage(message, status);
219 }
220
221 /**
222 * If totals are available the index is complete.
223 */
224 if (!Array.isArray(totals)) {
225 syncCompleted(totals);
226 return;
227 }
228
229 /**
230 * Update sync progress from index meta.
231 */
232 syncInProgress(indexMeta);
233
234 /**
235 * Don't continue if the sync was interrupted externally.
236 */
237 if (indexMeta.should_interrupt_sync) {
238 syncInterrupted();
239 return;
240 }
241
242 /**
243 * Don't continue if syncing has been paused.
244 */
245 if (isPaused) {
246 logMessage(__('Sync paused', 'elasticpress'), 'info');
247 return;
248 }
249
250 /**
251 * Syncing should continue.
252 */
253 resolve(indexMeta.method);
254 });
255 },
256 [syncCompleted, syncInProgress, syncInterrupted, logMessage],
257 );
258
259 const doIndexStatus = useCallback(
260 /**
261 * Check the status of a sync.
262 *
263 * Used to get the status of an external sync already in progress, such
264 * as a WP CLI index.
265 *
266 * @returns {void}
267 */
268 () => {
269 indexStatus().then(updateSyncState).then(doIndexStatus).catch(syncFailed);
270 },
271 [indexStatus, syncFailed, updateSyncState],
272 );
273
274 const doIndex = useCallback(
275 /**
276 * Start or continues a sync.
277 *
278 * @param {boolean} isDeleting Whether to delete and sync.
279 * @returns {void}
280 */
281 (isDeleting) => {
282 index(isDeleting)
283 .then(updateSyncState)
284 .then(
285 /**
286 * If an existing sync has been found just check its status,
287 * otherwise continue syncing.
288 *
289 * @param {string} method Sync method.
290 */
291 (method) => {
292 if (method === 'cli') {
293 doIndexStatus();
294 } else {
295 doIndex(isDeleting);
296 }
297 },
298 )
299 .catch(syncFailed);
300 },
301 [doIndexStatus, index, syncFailed, updateSyncState],
302 );
303
304 const pauseSync = useCallback(
305 /**
306 * Stop syncing.
307 *
308 * @returns {void}
309 */
310 () => {
311 updateState({ isComplete: false, isPaused: true, isSyncing: true });
312 },
313 [],
314 );
315
316 const stopSync = useCallback(
317 /**
318 * Stop syncing.
319 *
320 * @returns {void}
321 */
322 () => {
323 updateState({ isComplete: false, isPaused: false, isSyncing: false });
324 cancelIndex();
325 },
326 [cancelIndex],
327 );
328
329 const resumeSync = useCallback(
330 /**
331 * Resume syncing.
332 *
333 * @returns {void}
334 */
335 () => {
336 updateState({ isComplete: false, isPaused: false, isSyncing: true });
337 doIndex(stateRef.current.isDeleting);
338 },
339 [doIndex],
340 );
341
342 const startSync = useCallback(
343 /**
344 * Stop syncing.
345 *
346 * @param {boolean} isDeleting Whether to delete and sync.
347 * @returns {void}
348 */
349 (isDeleting) => {
350 updateState({ isComplete: false, isDeleting, isPaused: false, isSyncing: true });
351 updateState({ itemsProcessed: 0, syncStartDateTime: Date.now() });
352 doIndex(isDeleting);
353 },
354 [doIndex],
355 );
356
357 /**
358 * Handle clicking delete and sync button.
359 *
360 * @returns {void}
361 */
362 const onDelete = async () => {
363 startSync(true);
364 logMessage(__('Starting delete and sync…', 'elasticpress'), 'info');
365 };
366
367 /**
368 * Handle clicking pause button.
369 *
370 * @returns {void}
371 */
372 const onPause = () => {
373 pauseSync();
374 logMessage(__('Pausing sync…', 'elasticpress'), 'info');
375 };
376
377 /**
378 * Handle clicking play button.
379 *
380 * @returns {void}
381 */
382 const onResume = () => {
383 resumeSync();
384 logMessage(__('Resuming sync…', 'elasticpress'), 'info');
385 };
386
387 /**
388 * Handle clicking stop button.
389 *
390 * @returns {void}
391 */
392 const onStop = () => {
393 stopSync();
394 logMessage(__('Sync stopped', 'elasticpress'), 'info');
395 };
396
397 /**
398 * Handle clicking sync button.
399 *
400 * @returns {void}
401 */
402 const onSync = async () => {
403 startSync(false);
404 logMessage(__('Starting sync…', 'elasticpress'), 'info');
405 };
406
407 /**
408 * Initialize.
409 *
410 * @returns {void}
411 */
412 const init = () => {
413 /**
414 * Clear sync parameter from the URL to prevent a refresh triggering a new
415 * sync.
416 */
417 clearSyncParam();
418
419 /**
420 * If a sync is in progress, update state to reflect its progress.
421 */
422 if (indexMeta) {
423 syncInProgress(indexMeta);
424
425 /**
426 * If the sync is a CLI sync, start getting its status.
427 */
428 if (indexMeta.method === 'cli') {
429 doIndexStatus();
430 logMessage(__('WP CLI sync in progress', 'elasticpress'), 'info');
431 } else {
432 pauseSync();
433 logMessage(__('Sync paused', 'elasticpress'), 'info');
434 }
435
436 return;
437 }
438
439 /**
440 * Start an initial index.
441 */
442 if (autoIndex) {
443 startSync(true);
444 logMessage(__('Starting delete and sync…', 'elasticpress'), 'info');
445 }
446 };
447
448 /**
449 * Effects.
450 */
451 useEffect(init, [doIndexStatus, syncInProgress, logMessage, pauseSync, startSync]);
452
453 /**
454 * Render.
455 */
456 return (
457 <SyncPage
458 log={log}
459 onDelete={onDelete}
460 onPause={onPause}
461 onResume={onResume}
462 onStop={onStop}
463 onSync={onSync}
464 {...state}
465 />
466 );
467 };
468
469 render(<App />, document.getElementById('ep-sync'));
470