PluginProbe
ElasticPress / 5.3.5
ElasticPress v5.3.5
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
← All changes | assets/js/sync/index.js +275 -124 4.2.1 → 5.3.5 View file →
@@ -5,33 +5,61 @@
5 5
6 6 /**
7 7 * WordPress dependencies.
8 8 */
9 -import { render, useCallback, useEffect, useRef, useState, WPElement } from '@wordpress/element';
9 +import { dateI18n } from '@wordpress/date';
10 +import {
11 + createContext,
12 + useCallback,
13 + useContext,
14 + useEffect,
15 + useRef,
16 + useState,
17 + WPElement,
18 +} from '@wordpress/element';
10 19 import { __, sprintf } from '@wordpress/i18n';
11 20
12 21 /**
13 22 * Internal dependencies.
14 23 */
15 -import { autoIndex, lastSyncDateTime, lastSyncFailed, isEpio, indexMeta } from './config';
16 -import { useIndex } from './hooks';
24 +import { useIndex } from './src/hooks';
17 25 import {
18 26 clearSyncParam,
19 27 getItemsProcessedFromIndexMeta,
20 28 getItemsTotalFromIndexMeta,
21 -} from './utilities';
22 -import SyncPage from './components/sync-page';
29 +} from './src/utilities';
23 30
24 31 /**
32 + * Sync context.
33 + */
34 +const Context = createContext();
35 +
36 +/**
25 37 * App component.
26 38 *
39 + * @param {object} props Component props.
40 + * @param {string} props.apiUrl API endpoint URL.
41 + * @param {Function} props.children Component children
42 + * @param {Array} props.defaultSyncHistory Sync history.
43 + * @param {Array} props.defaultSyncTrigger Sync trigger.
44 + * @param {object|null} props.indexMeta Details of a sync in progress.
45 + * @param {boolean} props.isEpio Whether ElasticPress.io is in use.
46 + * @param {string} props.nonce WordPress nonce.
27 47 * @returns {WPElement} App component.
28 48 */
29 -const App = () => {
49 +export const SyncProvider = ({
50 + apiUrl,
51 + children,
52 + defaultSyncHistory,
53 + defaultSyncTrigger,
54 + indexMeta,
55 + isEpio,
56 + nonce,
57 +}) => {
30 58 /**
31 59 * Indexing methods.
32 60 */
33 - const { cancelIndex, index, indexStatus } = useIndex();
61 + const { cancelIndex, index, indexStatus } = useIndex(apiUrl, nonce);
34 62
35 63 /**
36 64 * Message log state.
37 65 */
@@ -37,19 +65,27 @@
37 65 */
38 66 const [log, setLog] = useState([]);
39 67
40 68 /**
69 + * Error types state.
70 + */
71 + const [errorCounts, setErrorCounts] = useState([]);
72 +
73 + /**
41 74 * Sync state.
42 75 */
43 76 const [state, setState] = useState({
77 + isCli: false,
44 78 isComplete: false,
45 79 isDeleting: false,
80 + isFailed: false,
81 + isPaused: false,
46 82 isSyncing: false,
47 83 itemsProcessed: 0,
48 84 itemsTotal: 100,
49 - lastSyncDateTime,
50 - lastSyncFailed,
51 85 syncStartDateTime: null,
86 + syncHistory: defaultSyncHistory,
87 + syncTrigger: defaultSyncTrigger,
52 88 });
53 89
54 90 /**
55 91 * Current state reference.
@@ -66,8 +102,41 @@
66 102 stateRef.current = { ...stateRef.current, ...newState };
67 103 setState((state) => ({ ...state, ...newState }));
68 104 };
69 105
106 + const countErrors = useCallback(
107 + /**
108 + * Add up the counts for each error type.
109 + *
110 + * @param {object} errors Errors returned by the sync request.
111 + */
112 + (errors) => {
113 + setErrorCounts((errorCounts) => {
114 + const newErrorCounts = [...errorCounts];
115 +
116 + Object.keys(errors).forEach((e) => {
117 + if (!errors[e].solution) {
118 + return;
119 + }
120 +
121 + const i = newErrorCounts.findIndex((t) => e === t.type);
122 +
123 + if (i !== -1) {
124 + newErrorCounts[i].count += errors[e].count;
125 + } else {
126 + newErrorCounts.push({
127 + ...errors[e],
128 + type: e,
129 + });
130 + }
131 + });
132 +
133 + return newErrorCounts;
134 + });
135 + },
136 + [],
137 + );
138 +
70 139 const logMessage = useCallback(
71 140 /**
72 141 * Log a message.
73 142 *
@@ -80,14 +149,36 @@
80 149
81 150 const messages = Array.isArray(message) ? message : [message];
82 151
83 152 for (const message of messages) {
84 - setLog((log) => [...log, { message, status, isDeleting, id: uuid() }]);
153 + setLog((log) => [
154 + ...log,
155 + {
156 + message,
157 + status,
158 + dateTime: dateI18n('Y-m-d H:i:s', new Date()),
159 + isDeleting,
160 + id: uuid(),
161 + },
162 + ]);
85 163 }
86 164 },
87 165 [],
88 166 );
89 167
168 + const clearLog = useCallback(
169 + /**
170 + * Clear the log.
171 + *
172 + * @returns {void}
173 + */
174 + () => {
175 + setLog([]);
176 + setErrorCounts([]);
177 + },
178 + [setLog],
179 + );
180 +
90 181 const syncCompleted = useCallback(
91 182 /**
92 183 * Set sync state to completed, with success based on the number of
93 184 * failures in the index totals.
@@ -99,11 +190,15 @@
99 190 updateState({
100 191 isComplete: true,
101 192 isPaused: false,
102 193 isSyncing: false,
103 - lastSyncDateTime: indexTotals.end_date_time,
104 - lastSyncFailed: indexTotals.failed > 0,
194 + syncHistory: [indexTotals, ...stateRef.current.syncHistory],
105 195 });
196 +
197 + /**
198 + * Hide the "just need to sync" notice, if it's present.
199 + */
200 + document.querySelector('[data-ep-notice="no_sync"]')?.remove();
106 201 },
107 202 [],
108 203 );
109 204
@@ -110,29 +205,44 @@
110 205 const syncFailed = useCallback(
111 206 /**
112 207 * Handle an error in the sync request.
113 208 *
114 - * @param {Error} error Request error.
209 + * @param {object|Error} response Request response.
115 210 * @returns {void}
116 211 */
117 - (error) => {
212 + (response) => {
118 213 /**
119 214 * Any running requests are cancelled when a new request is made.
120 215 * We can handle this silently.
121 216 */
122 - if (error.name === 'AbortError') {
217 + if (response.name === 'AbortError') {
123 218 return;
124 219 }
125 220
126 221 /**
127 - * Log any messages.
222 + * Log any error messages.
128 223 */
129 - if (error.message) {
130 - logMessage(error.message, 'error');
224 + if (response.message) {
225 + logMessage(response.message, 'error');
131 226 }
132 227
228 + /**
229 + * If the error has totals, add to the sync history.
230 + */
231 + const syncHistory = response.totals
232 + ? [response.totals, ...stateRef.current.syncHistory]
233 + : stateRef.current.syncHistory;
234 +
235 + /**
236 + * Log a final message and update the sync state.
237 + */
133 238 logMessage(__('Sync failed', 'elasticpress'), 'error');
134 - updateState({ isSyncing: false });
239 +
240 + updateState({
241 + isFailed: true,
242 + isSyncing: false,
243 + syncHistory,
244 + });
135 245 },
136 246 [logMessage],
137 247 );
138 248
@@ -157,15 +267,15 @@
157 267 ),
158 268 isEpio
159 269 ? __('ElasticPress.io', 'elasticpress')
160 270 : __('Elasticsearch', 'elasticpress'),
161 - )
271 + )
162 272 : __('Sync interrupted by WP-CLI command.', 'elasticpress');
163 273
164 274 logMessage(message, 'info');
165 275 updateState({ isSyncing: false });
166 276 },
167 - [logMessage],
277 + [isEpio, logMessage],
168 278 );
169 279
170 280 const syncInProgress = useCallback(
171 281 /**
@@ -176,19 +286,35 @@
176 286 */
177 287 (indexMeta) => {
178 288 updateState({
179 289 isCli: indexMeta.method === 'cli',
180 - isComplete: false,
181 - isDeleting: indexMeta.put_mapping,
182 290 isSyncing: true,
183 291 itemsProcessed: getItemsProcessedFromIndexMeta(indexMeta),
184 292 itemsTotal: getItemsTotalFromIndexMeta(indexMeta),
185 293 syncStartDateTime: indexMeta.start_date_time,
294 + syncTrigger: indexMeta.trigger || null,
186 295 });
187 296 },
188 297 [],
189 298 );
190 299
300 + const syncStopped = useCallback(
301 + /**
302 + * Set state for a stopped sync.
303 + *
304 + * @param {object} response Cancel request response.
305 + * @returns {void}
306 + */
307 + (response) => {
308 + const syncHistory = response.data
309 + ? [response.data, ...stateRef.current.syncHistory]
310 + : stateRef.current.syncHistory;
311 +
312 + updateState({ syncHistory });
313 + },
314 + [],
315 + );
316 +
191 317 const updateSyncState = useCallback(
192 318 /**
193 319 * Handle the response to a request to index.
194 320 *
@@ -195,14 +321,14 @@
195 321 * Updates the application state from the response data and logs any
196 322 * messages. Returns a Promise that resolves if syncing should
197 323 * continue.
198 324 *
199 - * @param {object} response AJAX response.
325 + * @param {object} response API response.
200 326 * @returns {Promise} Promise that resolves if sync is to continue.
201 327 */
202 328 (response) => {
203 329 const { isPaused, isSyncing } = stateRef.current;
204 - const { message, status, totals = [], index_meta: indexMeta } = response.data;
330 + const { errors, message, status, totals = [], index_meta: indexMeta } = response.data;
205 331
206 332 return new Promise((resolve) => {
207 333 /**
208 334 * Don't continue if syncing has been stopped.
@@ -210,9 +336,21 @@
210 336 if (!isSyncing) {
211 337 return;
212 338 }
213 339
340 + if (errors) {
341 + countErrors(errors);
342 + }
343 +
214 344 /**
345 + * Stop sync if there is an error.
346 + */
347 + if (status === 'error') {
348 + syncFailed(response.data);
349 + return;
350 + }
351 +
352 + /**
215 353 * Log any messages.
216 354 */
217 355 if (message) {
218 356 logMessage(message, status);
@@ -252,11 +390,29 @@
252 390 */
253 391 resolve(indexMeta.method);
254 392 });
255 393 },
256 - [syncCompleted, syncInProgress, syncInterrupted, logMessage],
394 + [syncCompleted, syncFailed, syncInProgress, syncInterrupted, countErrors, logMessage],
257 395 );
258 396
397 + const doCancelIndex = useCallback(
398 + /**
399 + * Cancel a sync.
400 + *
401 + * @returns {void}
402 + */
403 + () => {
404 + cancelIndex()
405 + .then(syncStopped)
406 + .catch((error) => {
407 + if (error?.name !== 'AbortError') {
408 + throw error;
409 + }
410 + });
411 + },
412 + [cancelIndex, syncStopped],
413 + );
414 +
259 415 const doIndexStatus = useCallback(
260 416 /**
261 417 * Check the status of a sync.
262 418 *
@@ -272,15 +428,15 @@
272 428 );
273 429
274 430 const doIndex = useCallback(
275 431 /**
276 - * Start or continues a sync.
432 + * Start or continue a sync.
277 433 *
278 - * @param {boolean} isDeleting Whether to delete and sync.
434 + * @param {object} args Sync args.
279 435 * @returns {void}
280 436 */
281 - (isDeleting) => {
282 - index(isDeleting)
437 + (args) => {
438 + index(args)
283 439 .then(updateSyncState)
284 440 .then(
285 441 /**
286 442 * If an existing sync has been found just check its status,
@@ -291,9 +447,9 @@
291 447 (method) => {
292 448 if (method === 'cli') {
293 449 doIndexStatus();
294 450 } else {
295 - doIndex(isDeleting);
451 + doIndex(args);
296 452 }
297 453 },
298 454 )
299 455 .catch(syncFailed);
@@ -307,105 +463,76 @@
307 463 *
308 464 * @returns {void}
309 465 */
310 466 () => {
311 - updateState({ isComplete: false, isPaused: true, isSyncing: true });
467 + updateState({ isPaused: true, isSyncing: true });
312 468 },
313 469 [],
314 470 );
315 471
316 - const stopSync = useCallback(
472 + const resumeSync = useCallback(
317 473 /**
318 - * Stop syncing.
474 + * Resume syncing.
319 475 *
476 + * @param {object} args Sync args.
320 477 * @returns {void}
321 478 */
322 - () => {
323 - updateState({ isComplete: false, isPaused: false, isSyncing: false });
324 - cancelIndex();
479 + (args) => {
480 + updateState({ isPaused: false, isSyncing: true });
481 + doIndex(args);
325 482 },
326 - [cancelIndex],
483 + [doIndex],
327 484 );
328 485
329 - const resumeSync = useCallback(
486 + const startSync = useCallback(
330 487 /**
331 - * Resume syncing.
488 + * Start syncing.
332 489 *
490 + * @param {object} args Sync args.
333 491 * @returns {void}
334 492 */
335 - () => {
336 - updateState({ isComplete: false, isPaused: false, isSyncing: true });
337 - doIndex(stateRef.current.isDeleting);
493 + (args) => {
494 + const { syncHistory } = stateRef.current;
495 + const isInitialSync = !syncHistory.length;
496 +
497 + /**
498 + * We should not appear to be deleting if this is the first sync.
499 + */
500 + const isDeleting = !!(isInitialSync || args.put_mapping);
501 +
502 + updateState({
503 + isComplete: false,
504 + isFailed: false,
505 + isDeleting,
506 + isPaused: false,
507 + isSyncing: true,
508 + });
509 +
510 + updateState({
511 + itemsProcessed: 0,
512 + syncStartDateTime: Date.now(),
513 + syncTrigger: args.trigger || null,
514 + });
515 +
516 + doIndex(args);
338 517 },
339 518 [doIndex],
340 519 );
341 520
342 - const startSync = useCallback(
521 + const stopSync = useCallback(
343 522 /**
344 523 * Stop syncing.
345 524 *
346 - * @param {boolean} isDeleting Whether to delete and sync.
347 525 * @returns {void}
348 526 */
349 - (isDeleting) => {
350 - updateState({ isComplete: false, isDeleting, isPaused: false, isSyncing: true });
351 - updateState({ itemsProcessed: 0, syncStartDateTime: Date.now() });
352 - doIndex(isDeleting);
527 + () => {
528 + updateState({ isPaused: false, isSyncing: false });
529 + doCancelIndex();
353 530 },
354 - [doIndex],
531 + [doCancelIndex],
355 532 );
356 533
357 534 /**
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 535 * Initialize.
409 536 *
410 537 * @returns {void}
411 538 */
@@ -431,39 +558,63 @@
431 558 } else {
432 559 pauseSync();
433 560 logMessage(__('Sync paused', 'elasticpress'), 'info');
434 561 }
435 -
436 - return;
437 562 }
438 -
439 - /**
440 - * Start an initial index.
441 - */
442 - if (autoIndex) {
443 - startSync(true);
444 - logMessage(__('Starting delete and sync…', 'elasticpress'), 'info');
445 - }
446 563 };
447 564
448 565 /**
449 566 * Effects.
450 567 */
451 - useEffect(init, [doIndexStatus, syncInProgress, logMessage, pauseSync, startSync]);
568 + useEffect(init, [doIndexStatus, syncInProgress, indexMeta, logMessage, pauseSync, startSync]);
452 569
453 570 /**
454 - * Render.
571 + * Provide state to context.
455 572 */
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 - );
573 + const {
574 + isCli,
575 + isComplete,
576 + isDeleting,
577 + isFailed,
578 + isPaused,
579 + isSyncing,
580 + itemsProcessed,
581 + itemsTotal,
582 + syncHistory,
583 + syncStartDateTime,
584 + syncTrigger,
585 + } = stateRef.current;
586 +
587 + // eslint-disable-next-line react/jsx-no-constructed-context-values
588 + const contextValue = {
589 + clearLog,
590 + errorCounts,
591 + isCli,
592 + isComplete,
593 + isDeleting,
594 + isFailed,
595 + isPaused,
596 + isSyncing,
597 + itemsProcessed,
598 + itemsTotal,
599 + syncHistory,
600 + log,
601 + logMessage,
602 + pauseSync,
603 + resumeSync,
604 + startSync,
605 + stopSync,
606 + syncStartDateTime,
607 + syncTrigger,
608 + };
609 +
610 + return <Context.Provider value={contextValue}>{children}</Context.Provider>;
467 611 };
468 612
469 -render(<App />, document.getElementById('ep-sync'));
613 +/**
614 + * Use the API Search context.
615 + *
616 + * @returns {object} API Search Context.
617 + */
618 +export const useSync = () => {
619 + return useContext(Context);
620 +};