diff --git a/assets/css/sync.css b/assets/css/sync.css
index f6cc9cd892..3d513296a1 100644
--- a/assets/css/sync.css
+++ b/assets/css/sync.css
@@ -1,334 +1,18 @@
-:root {
- --ep-admin-color-base-white: #fff;
- --ep-admin-color-blue-01: #00a0d2;
- --ep-admin-color-grey-01: #f1f1f1;
- --ep-admin-color-green-01: #46b450;
- --ep-admin-color-red-01: #b52727;
- --ep-admin-color-dark-01: #333;
- --ep-admin-max-width: 1200px;
-
- --ep-admin-box-title: #1d2327;
-
- --ep-admin-delete-sync-button-bg-color: rgba(181, 39, 39, 0.03);
-
- --ep-admin-progress-bar-bg-color: rgba(0, 160, 210, 0.3);
-
- --ep-admin-output-tab-color: #1e1e1e;
-
- --ep-admin-log-bg-color: #1a1e24;
- --ep-admin-log-line-number-color: #999;
- --ep-admin-log-line-number-bg-color: #303030;
-
-}
-
-.elasticpress_page_elasticpress-sync .button:disabled {
- cursor: not-allowed;
-}
-
-.ep-sync-box__progress-wrapper {
- display: none;
-}
-
-.ep-sync-box__output {
- background-color: var(--ep-admin-log-bg-color);
- display: none;
- margin-bottom: 20px;
- max-height: 200px;
- overflow-y: scroll;
- position: relative;
-}
-
-.ep-sync-box__output_active {
- display: block;
-}
-
-.ep-sync-box__output-wrapper {
- color: var(--ep-admin-color-base-white);
- margin-left: 30px;
- min-height: 200px;
-}
-
-.ep-sync-box__output-line {
- position: relative;
-}
-
-.ep-sync-box__output-line-number {
- background-color: var(--ep-admin-log-line-number-bg-color);
- color: var(--ep-admin-log-line-number-color);
- left: -30px;
- min-width: 20px;
- padding: 0 3px 0 5px;
- position: absolute;
- text-align: right;
- white-space: nowrap;
-}
-
-.ep-sync-box__output-line-text {
- font-size: 12px;
- padding-left: 14px;
-}
-
-.ep-sync-box__output-tabs {
- align-items: center;
- display: flex;
-}
-
-.ep-sync-box__output-tabs_hide {
- display: none;
-}
-
-.ep-sync-box__output-tab {
- color: var(--ep-admin-output-tab-color);
- padding: 16px;
-}
-
-.ep-sync-box__output-tab:hover {
- cursor: pointer;
-}
-
-.ep-sync-box__output-tab_active {
- border-bottom: 4px solid var(--ep-admin-color-blue-01);
- padding-bottom: 12px;
-}
-
-.ep-sync-box__output-tab_active:hover {
- cursor: default;
-}
-
-.ep-sync-box__button-text {
- height: 21px;
-}
-
-.elasticpress_page_elasticpress-sync .card {
- max-width: var(--ep-admin-max-width);
-
- & .ep-sync-box__description-actions {
- display: flex;
- flex-direction: column;
-
- @media (min-width: 768px) {
- flex-direction: row;
- justify-content: space-between;
- }
- }
-
- & .ep-sync-box__description {
+@import "sync/button.css";
+@import "sync/controls.css";
+@import "sync/heading.css";
+@import "sync/messages.css";
+@import "sync/panel.css";
+@import "sync/progress.css";
+@import "sync/progress-bar.css";
+@import "sync/status.css";
+@import "sync/warning.css";
- @media (min-width: 768px) {
- width: 69%;
- }
- }
-
- & .ep-sync-box__action {
- align-items: center;
- display: flex;
- flex-direction: column;
- margin-top: 40px;
-
- @media (min-width: 768px) {
- width: 30%;
- }
-
- & .ep-sync-box__button {
- align-items: center;
- display: flex;
- font-size: 24px;
- font-weight: 700;
- height: 68px;
- justify-content: space-between;
- padding: 20px 40px;
- width: 228px;
- }
-
- & .ep-sync-box__icon-button {
- font-size: 28px;
- height: 28px;
- width: 28px;
-
- }
-
- & .ep-sync-box__learn-more-link {
- margin-top: 19px;
- }
-
- }
-
- & .ep-sync-box__description_text {
- font-size: 18px;
- }
-
- & .ep-last-sync {
- margin-bottom: 12px;
- }
-
- & .ep-last-sync__title {
- font-size: 20px;
- font-weight: 700;
- margin-bottom: 0.5em;
- }
-
- & .ep-last-sync__icon-status {
- margin-right: 5px;
- vertical-align: text-bottom;
- }
-
- & .ep-last-sync__date {
- background-color: var(--ep-admin-color-grey-01);
- padding: 6px;
- }
-
- & .ep-sync-box__buttons {
- display: flex;
-
- }
-
- & .ep-sync-box__button-resume,
- & .ep-sync-box__button-pause,
- & .ep-sync-box__button-stop {
- align-items: center;
- display: none;
- flex-direction: column;
- height: 68px;
- justify-content: center;
- width: 100px;
- }
-
- & .ep-sync-box__button-stop {
- background-color: var(--ep-admin-color-blue-01);
- border-color: var(--ep-admin-color-blue-01);
- margin-left: 24px;
- }
-
- & .ep-sync-box__progress {
- align-items: normal;
- display: flex;
- flex-direction: column;
- margin-bottom: 5px;
- margin-top: 19px;
-
- @media (min-width: 768px) {
- align-items: center;
- flex-direction: row;
- }
- }
-
- & .ep-sync-box__sync-in-progress {
- display: flex;
- flex-direction: row;
-
- @media (min-width: 768px) {
- width: 30%;
- }
- }
-
- & .ep-sync-box__progressbar {
- background-color: var(--ep-admin-color-grey-01);
- border-radius: 24px;
- height: 20px;
- margin: 15px 0;
- position: relative;
- width: 100%;
-
- @media (min-width: 768px) {
- margin: 0;
- width: 65%;
- }
- }
-
- & .ep-sync-box__progressbar_animated {
- background-color: var(--ep-admin-progress-bar-bg-color);
- color: var(--ep-admin-color-dark-01);
- display: block;
- height: 100%;
- margin: 0;
- text-align: center;
- transition: width 0.5s ease-in-out;
- width: 0;
- }
-
- & .ep-sync-box__progressbar_complete {
- background-color: var(--ep-admin-color-green-01);
- color: var(--ep-admin-color-base-white);
- }
-
- & .ep-sync-box__sync-in-progress-info {
- display: flex;
- flex-direction: column;
- margin-left: 12px;
- }
-
- & .ep-sync-box__progress-info {
- font-size: 14px;
- font-weight: 500;
- margin-bottom: 5px;
- }
-
- & .ep-sync-box__start-time {
- color: var(--ep-admin-color-blue-01);
- font-size: 14px;
- }
-
- & .ep-sync-box__start-time-date {
- color: var(--ep-admin-color-dark-01);
- }
-}
-
-.elasticpress_page_elasticpress-sync .ep-delete-data-and-sync {
- margin-top: 40px;
-
- & .card {
- margin-top: 17px;
- }
-
- & .ep-delete-data-and-sync__title {
- color: var(--ep-admin-box-title);
- font-size: 18px;
- font-weight: 400;
- }
-
- & .ep-delete-data-and-sync__warning {
- align-items: flex-start;
- display: flex;
-
- @media (min-width: 768px) {
- width: 69%;
- }
- }
-
- & .ep-delete-data-and-sync__warning-icon {
- margin-right: 9px;
- margin-top: 17px;
- }
-
- & .ep-delete-data-and-sync__button {
- background-color: var(--ep-admin-delete-sync-button-bg-color);
- border: 1px solid var(--ep-admin-color-red-01);
- color: var(--ep-admin-color-red-01);
- margin: 5px 0 12px;
- }
-
- & .ep-delete-data-and-sync__button-cancel {
- display: none;
- }
-
- & .ep-sync-box__action {
- flex-direction: column;
- height: auto;
- justify-content: center;
- margin-top: 0;
- width: 100%;
-
- @media (min-width: 768px) {
- flex-direction: row;
- height: 68px;
- justify-content: space-between;
- }
- }
-
- & .ep-sync-box__buttons {
-
- @media (min-width: 768px) {
- margin-right: 5%;
- }
- }
+:root {
+ --ep-sync-color-black: #1a1e24;
+ --ep-sync-color-error: #b52727;
+ --ep-sync-color-light-grey: #f0f0f0;
+ --ep-sync-color-success: #46b450;
+ --ep-sync-color-warning: #ffb359;
+ --ep-sync-color-white: #fff;
}
diff --git a/assets/css/sync/button.css b/assets/css/sync/button.css
new file mode 100644
index 0000000000..784c320788
--- /dev/null
+++ b/assets/css/sync/button.css
@@ -0,0 +1,25 @@
+.ep-sync-button {
+
+ &.components-button.has-icon.has-text {
+ height: 4rem;
+ justify-content: center;
+ width: 100%;
+
+ & svg {
+ height: 2em;
+ margin: 0;
+ width: 2em;
+ }
+ }
+}
+
+.ep-sync-button--sync {
+ font-size: 1.5em;
+ font-weight: 700;
+}
+
+.ep-sync-button--pause,
+.ep-sync-button--resume,
+.ep-sync-button--stop {
+ flex-direction: column;
+}
diff --git a/assets/css/sync/controls.css b/assets/css/sync/controls.css
new file mode 100644
index 0000000000..2a9b63eb1e
--- /dev/null
+++ b/assets/css/sync/controls.css
@@ -0,0 +1,16 @@
+.ep-sync-controls {
+ display: grid;
+ grid-gap: 1em;
+ grid-template-columns: 1fr 1fr;
+ margin: 0 auto;
+ max-width: 16rem;
+}
+
+.ep-sync-controls__sync {
+ grid-column: 1 / -1;
+}
+
+.ep-sync-controls__learn-more {
+ grid-column: 1 / -1;
+ text-align: center;
+}
diff --git a/assets/css/sync/heading.css b/assets/css/sync/heading.css
new file mode 100644
index 0000000000..6a3ceb84a5
--- /dev/null
+++ b/assets/css/sync/heading.css
@@ -0,0 +1,12 @@
+.ep-sync-heading {
+
+ @nest .wrap & {
+ font-weight: 400;
+ margin: 0.5rem 0 0.75rem;
+ padding: 0;
+
+ &h2 {
+ color: inherit;
+ }
+ }
+}
diff --git a/assets/css/sync/messages.css b/assets/css/sync/messages.css
new file mode 100644
index 0000000000..58233f4d4d
--- /dev/null
+++ b/assets/css/sync/messages.css
@@ -0,0 +1,25 @@
+.ep-sync-messages {
+ align-content: start;
+ background: var(--ep-sync-color-black);
+ color: var(--ep-sync-color-white);
+ display: grid;
+ font-family: monospace;
+ grid-auto-flow: column;
+ grid-template-columns: min-content auto;
+ height: 21em;
+ line-height: 2;
+ overflow-y: auto;
+ white-space: pre-wrap;
+}
+
+.ep-sync-messages__message {
+ grid-column: 2;
+}
+
+.ep-sync-messages__line-number {
+ box-sizing: content-box;
+ min-width: 3ch;
+ opacity: 0.5;
+ padding: 0 0.5em;
+ text-align: right;
+}
diff --git a/assets/css/sync/panel.css b/assets/css/sync/panel.css
new file mode 100644
index 0000000000..88340dc2ea
--- /dev/null
+++ b/assets/css/sync/panel.css
@@ -0,0 +1,34 @@
+.ep-sync-panel {
+ margin-bottom: 2rem;
+ max-width: 1200px;
+}
+
+.ep-sync-panel__body {
+ display: grid;
+ grid-column-gap: 2rem;
+ grid-row-gap: 1rem;
+ grid-template-columns: auto 16rem;
+
+ &.is-opened {
+ padding: 2rem 2rem 1rem 2rem;
+ }
+
+ & p,
+ & .components-toggle-control,
+ & .components-tab-panel__tab-content {
+ margin-bottom: 1rem;
+ margin-top: 0;
+ }
+
+ @media (max-width: 960px) {
+ grid-template-columns: 100%;
+ }
+}
+
+.ep-sync-panel__row {
+ grid-column: 1 / -1;
+}
+
+.ep-sync-panel__introduction {
+ font-size: 18px;
+}
diff --git a/assets/css/sync/progress-bar.css b/assets/css/sync/progress-bar.css
new file mode 100644
index 0000000000..c694e6a087
--- /dev/null
+++ b/assets/css/sync/progress-bar.css
@@ -0,0 +1,27 @@
+.ep-sync-progress-bar {
+ background: var(--ep-sync-color-light-grey);
+ display: flex;
+ overflow: hidden;
+ text-align: center;
+}
+
+.ep-sync-progress-bar,
+.ep-sync-progress-bar__progress {
+ border-radius: 0.875em;
+}
+
+.ep-sync-progress-bar__progress {
+ background: var(--wp-admin-theme-color);
+ color: var(--ep-sync-color-white);
+ padding: 0 0.875em;
+ transition: all 500ms ease-in-out;
+ white-space: nowrap;
+
+ @nest .ep-sync-progress-bar--complete & {
+ background: var(--ep-sync-color-success);
+ }
+
+ @nest .ep-sync-progress-bar--paused & {
+ opacity: 0.5;
+ }
+}
diff --git a/assets/css/sync/progress.css b/assets/css/sync/progress.css
new file mode 100644
index 0000000000..e80a2533a0
--- /dev/null
+++ b/assets/css/sync/progress.css
@@ -0,0 +1,52 @@
+@keyframes epSyncRotation {
+
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(359deg);
+ }
+}
+
+.ep-sync-progress {
+ align-items: center;
+ display: grid;
+ grid-row-gap: 1rem;
+ grid-template-columns: min-content minmax(max-content, 1fr) 3fr;
+ margin-bottom: 1rem;
+
+ @media (max-width: 960px) {
+ grid-template-columns: min-content auto;
+ }
+
+ & svg {
+ animation: epSyncRotation 1500ms infinite linear;
+ animation-play-state: paused;
+ height: 36px;
+ margin-right: 12px;
+ width: 36px;
+ }
+}
+
+.ep-sync-progress--syncing {
+
+ & svg {
+ animation-play-state: running;
+ }
+}
+
+.ep-sync-progress__details {
+
+ & strong {
+ display: block;
+ font-size: 14px;
+ }
+}
+
+.ep-sync-progress__progress-bar {
+
+ @media (max-width: 960px) {
+ grid-column: 1 / -1;
+ }
+}
diff --git a/assets/css/sync/status.css b/assets/css/sync/status.css
new file mode 100644
index 0000000000..178a1cc270
--- /dev/null
+++ b/assets/css/sync/status.css
@@ -0,0 +1,23 @@
+.ep-sync-status {
+ align-items: center;
+ display: grid;
+ grid-gap: 0.5rem;
+ grid-template-columns: min-content auto;
+
+ & svg {
+ fill: var(--ep-sync-color-error);
+ }
+}
+
+.ep-sync-status--success {
+
+ & svg {
+ fill: var(--ep-sync-color-success);
+ }
+}
+
+.ep-sync-status__time {
+ background-color: var(--ep-sync-color-light-grey);
+ border-radius: 2px;
+ padding: 0.25em 0.5em;
+}
diff --git a/assets/css/sync/warning.css b/assets/css/sync/warning.css
new file mode 100644
index 0000000000..a96cbbe375
--- /dev/null
+++ b/assets/css/sync/warning.css
@@ -0,0 +1,10 @@
+.ep-sync-warning {
+ display: grid;
+ grid-gap: 0.5rem;
+ grid-template-columns: min-content auto;
+
+ & svg {
+ fill: var(--ep-sync-color-warning);
+ margin-top: -3px;
+ }
+}
diff --git a/assets/js/sync.js b/assets/js/sync.js
deleted file mode 100644
index 62da139d92..0000000000
--- a/assets/js/sync.js
+++ /dev/null
@@ -1,855 +0,0 @@
-import apiFetch from '@wordpress/api-fetch';
-import { dateI18n } from '@wordpress/date';
-
-/* eslint-disable camelcase, no-use-before-define */
-const { epDash, history } = window;
-const { __, sprintf } = wp.i18n;
-
-const { ajax_url: ajaxurl = '', is_epio } = epDash;
-
-// Main elements of sync page
-const syncBox = document.querySelector('.ep-sync-data');
-const deleteAndSyncBox = document.querySelector('.ep-delete-data-and-sync');
-
-// It could be the syncBox or deleteAndSyncBox
-let activeBox;
-
-// Buttons to start a sync or delete data
-const syncButton = syncBox.querySelector('.ep-sync-box__button-sync');
-const deleteAndSyncButton = deleteAndSyncBox.querySelector(
- '.ep-delete-data-and-sync__button-delete',
-);
-
-// Log elements
-const syncBoxFulllogTab = document.querySelector('.ep-sync-data .ep-sync-box__output-tab-fulllog');
-const syncBoxOutputFulllog = document.querySelector('.ep-sync-data .ep-sync-box__output-fulllog');
-const syncBoxErrorTab = document.querySelector('.ep-sync-data .ep-sync-box__output-tab-error');
-const syncBoxOutputError = document.querySelector('.ep-sync-data .ep-sync-box__output-error');
-
-const deleteBoxFulllogTab = document.querySelector(
- '.ep-delete-data-and-sync .ep-sync-box__output-tab-fulllog',
-);
-const deleteBoxErrorTab = document.querySelector(
- '.ep-delete-data-and-sync .ep-sync-box__output-tab-error',
-);
-const deleteBoxOutputFulllog = document.querySelector(
- '.ep-delete-data-and-sync .ep-sync-box__output-fulllog',
-);
-const deleteBoxOutputError = document.querySelector(
- '.ep-delete-data-and-sync .ep-sync-box__output-error',
-);
-
-syncButton.addEventListener('click', function () {
- activeBox = syncBox;
-
- disableButtonsInDeleteBox();
-
- syncButton.style.display = 'none';
- updateDisabledAttribute(syncButton, true);
-
- const learnMoreLink = activeBox.querySelector('.ep-sync-box__learn-more-link');
- learnMoreLink.style.display = 'none';
-
- showPauseStopButtons();
- showProgress();
- addLineToOutput(__('Indexing data…', 'elasticpress'));
-
- const progressInfoElement = activeBox.querySelector('.ep-sync-box__progress-info');
- const progressBar = activeBox.querySelector('.ep-sync-box__progressbar_animated');
- const startDateTime = activeBox.querySelector('.ep-sync-box__start-time-date');
-
- progressInfoElement.innerText = __('Sync in progress', 'elasticpress');
-
- progressBar.style.width = `0`;
- progressBar.innerText = ``;
-
- startDateTime.innerText = '';
-
- startSyncProcess();
-});
-
-deleteAndSyncButton.addEventListener('click', deleteAndSync);
-
-function deleteAndSync() {
- activeBox = deleteAndSyncBox;
-
- disableButtonsInSyncBox();
- updateDisabledAttribute(deleteAndSyncButton, true);
- showPauseStopButtons();
- showProgress();
-
- addLineToOutput(__('Deleting data…', 'elasticpress'));
-
- const progressInfoElement = activeBox.querySelector('.ep-sync-box__progress-info');
- const progressBar = activeBox.querySelector('.ep-sync-box__progressbar_animated');
- const startDateTime = activeBox.querySelector('.ep-sync-box__start-time-date');
-
- progressInfoElement.innerText = __('Deleting in progress', 'elasticpress');
-
- progressBar.style.width = `0`;
- progressBar.innerText = ``;
-
- startDateTime.innerText = '';
-
- startSyncProcess(true);
-}
-
-/**
- * Show Pause and Stop buttons on the active box
- */
-function showPauseStopButtons() {
- if (activeBox) {
- showStopButton();
- showPauseButton();
- }
-}
-
-/**
- * Hide Pause and Stop buttons on the active box
- */
-function hidePauseStopButtons() {
- hideStopButton();
- hidePauseButton();
-}
-
-/**
- * Show Pause button on the active box
- */
-function showPauseButton() {
- if (activeBox) {
- const pauseButton = activeBox.querySelector('.ep-sync-box__button-pause');
-
- updateDisabledAttribute(pauseButton, false);
-
- pauseButton.style.display = 'flex';
- }
-}
-
-/**
- * Hide Pause button on the active box
- */
-function hidePauseButton() {
- if (activeBox) {
- const pauseButton = activeBox.querySelector('.ep-sync-box__button-pause');
-
- pauseButton.style.display = 'none';
- }
-}
-
-/**
- * Show Resume button on the active box
- */
-function showResumeButton() {
- if (activeBox) {
- const resumeButton = activeBox.querySelector('.ep-sync-box__button-resume');
-
- updateDisabledAttribute(resumeButton, false);
-
- resumeButton.style.display = 'flex';
- }
-}
-
-/**
- * Hide Pause button on the active box
- */
-function hideResumeButton() {
- if (activeBox) {
- const resumeButton = activeBox.querySelector('.ep-sync-box__button-resume');
-
- resumeButton.style.display = 'none';
- }
-}
-
-/**
- * Show Stop button on the active box
- */
-function showStopButton() {
- if (activeBox) {
- const stopButton = activeBox.querySelector('.ep-sync-box__button-stop');
-
- updateDisabledAttribute(stopButton, false);
-
- stopButton.style.display = 'flex';
- }
-}
-
-/**
- * Hide Stop button on the active box
- */
-function hideStopButton() {
- if (activeBox) {
- const stopButton = activeBox.querySelector('.ep-sync-box__button-stop');
-
- stopButton.style.display = 'none';
- }
-}
-
-function showProgress() {
- const progressWrapper = activeBox?.querySelector('.ep-sync-box__progress-wrapper');
-
- if (progressWrapper?.style) {
- progressWrapper.style.display = 'block';
- }
-}
-
-let syncStatus = 'sync';
-let syncStack;
-let processed = 0;
-let toProcess = 0;
-let totalProcessed = 0;
-
-updateLastSyncDateTime(epDash?.ep_last_sync_date);
-
-if (epDash.index_meta) {
- if (epDash.index_meta.method === 'cli') {
- syncStatus = 'wpcli';
- processed = epDash?.index_meta?.items_indexed;
- toProcess = epDash?.index_meta?.total_items;
-
- activeBox = epDash.index_meta.put_mapping ? deleteAndSyncBox : syncBox;
-
- const progressInfoElement = activeBox.querySelector('.ep-sync-box__progress-info');
-
- progressInfoElement.innerText = __('WP-CLI sync in progress', 'elasticpress');
-
- updateStartDateTime(epDash?.index_meta?.start_date_time);
-
- updateDisabledAttribute(syncButton, true);
- updateDisabledAttribute(deleteAndSyncButton, true);
-
- showProgress();
-
- updateSyncDash();
- cliSync();
- } else {
- processed = epDash.index_meta.offset;
- toProcess = epDash.index_meta.found_items;
-
- if (epDash.index_meta.sync_stack) {
- syncStack = epDash.index_meta.sync_stack;
- }
-
- if ((!syncStack || !syncStack.length) && toProcess === 0 && !epDash.index_meta.start) {
- // Sync finished
- syncStatus = 'finished';
- } else {
- syncStatus = 'pause';
- }
- activeBox = epDash.index_meta?.put_mapping ? deleteAndSyncBox : syncBox;
-
- disableButtonsInSyncBox();
- disableButtonsInDeleteBox();
-
- if (activeBox === syncBox) {
- syncButton.style.display = 'none';
-
- const learnMoreLink = activeBox.querySelector('.ep-sync-box__learn-more-link');
-
- learnMoreLink.style.display = 'none';
- }
-
- showResumeButton();
- showStopButton();
-
- showProgress();
-
- updateSyncDash();
- }
-} else if (epDash.auto_start_index) {
- deleteAndSync();
-
- history.pushState(
- {},
- document.title,
- document.location.pathname + document.location.search.replace(/&do_sync/, ''),
- );
-}
-
-/**
- * Change the disabled attribute of an element
- *
- * @param {HTMLElement} element Element to be updated
- * @param {boolean} value The value used in disabled attribute
- */
-function updateDisabledAttribute(element, value) {
- element.disabled = value;
-}
-
-/**
- * Update dashboard with syncing information
- */
-function updateSyncDash() {
- const progressBar = activeBox.querySelector('.ep-sync-box__progressbar_animated');
-
- const isSyncing = ['initialsync', 'sync', 'pause', 'wpcli'].includes(syncStatus);
-
- let progressBarWidth;
- if (isSyncing) {
- progressBarWidth =
- toProcess === 0 ? 0 : (parseInt(processed, 10) / parseInt(toProcess, 10)) * 100;
- } else {
- progressBarWidth = 100;
- }
-
- if (
- typeof progressBarWidth === 'number' &&
- !Number.isNaN(progressBarWidth) &&
- Number.isFinite(progressBarWidth)
- ) {
- const width = Math.min(100, progressBarWidth);
- progressBar.style.width = `${width}%`;
- progressBar.innerText = `${Math.trunc(width)}%`;
- }
-
- if (isSyncing) {
- progressBar.classList.remove('ep-sync-box__progressbar_complete');
- } else if (syncStatus === 'error') {
- const progressInfoElement = activeBox.querySelector('.ep-sync-box__progress-info');
-
- progressInfoElement.innerText = __('Sync failed', 'elasticpress');
-
- updateStartDateTime(new Date());
- updateDisabledAttribute(deleteAndSyncButton, false);
- updateDisabledAttribute(syncButton, false);
-
- hidePauseStopButtons();
- hideResumeButton();
-
- syncButton.style.display = 'flex';
-
- const learnMoreLink = activeBox.querySelector('.ep-sync-box__learn-more-link');
-
- if (learnMoreLink?.style) {
- learnMoreLink.style.display = 'block';
- }
- } else if (syncStatus === 'interrupt') {
- const progressInfoElement = activeBox.querySelector('.ep-sync-box__progress-info');
-
- progressInfoElement.innerText = __('Sync interrupted', 'elasticpress');
-
- updateDisabledAttribute(deleteAndSyncButton, false);
- updateDisabledAttribute(syncButton, false);
-
- hidePauseStopButtons();
- hideResumeButton();
-
- syncButton.style.display = 'flex';
-
- const learnMoreLink = activeBox.querySelector('.ep-sync-box__learn-more-link');
-
- if (learnMoreLink?.style) {
- learnMoreLink.style.display = 'block';
- }
- } else {
- const progressInfoElement = activeBox.querySelector('.ep-sync-box__progress-info');
-
- progressInfoElement.innerText = __('Sync completed', 'elasticpress');
-
- progressBar.classList.add('ep-sync-box__progressbar_complete');
-
- updateDisabledAttribute(deleteAndSyncButton, false);
- updateDisabledAttribute(syncButton, false);
-
- hidePauseStopButtons();
- hideResumeButton();
-
- syncButton.style.display = 'flex';
-
- const learnMoreLink = activeBox.querySelector('.ep-sync-box__learn-more-link');
-
- if (learnMoreLink?.style) {
- learnMoreLink.style.display = 'block';
- }
- }
-}
-
-/**
- * Cancel a sync
- */
-function cancelSync() {
- toProcess = 0;
- processed = 0;
- totalProcessed = 0;
-
- apiFetch({
- url: ajaxurl,
- method: 'POST',
- body: new URLSearchParams({
- action: 'ep_cancel_index',
- nonce: epDash.nonce,
- }),
- });
-}
-
-function cliSync() {
- const requestSettings = {
- url: ajaxurl,
- method: 'POST',
- body: new URLSearchParams({
- action: 'ep_cli_index',
- nonce: epDash.nonce,
- }),
- };
-
- apiFetch(requestSettings).then((response) => {
- if (syncStatus === 'interrupt') {
- return;
- }
-
- if (syncStatus === 'wpcli') {
- toProcess = response.data?.index_meta?.total_items;
- processed = response.data?.index_meta?.items_indexed;
-
- if (response.data.index_meta?.current_sync_item?.failed) {
- const message = response.data?.message;
- if (Array.isArray(message)) {
- message.forEach((item) => {
- addErrorToOutput(item);
- addLineToOutput(item);
- });
- } else if (typeof message === 'string') {
- addErrorToOutput(message);
- addLineToOutput(message);
- }
- } else {
- addLineToOutput(response.data.message);
- }
-
- updateSyncDash();
-
- if (response.data?.index_meta?.indexing) {
- cliSync();
- return;
- }
- }
-
- syncStatus = 'finished';
- addLineToOutput('===============================');
- addLineToOutput(__('WP-CLI sync is finished', 'elasticpress'));
- updateSyncDash();
- });
-}
-
-/**
- * Add a line to the active output
- *
- * @param {string} text Message to show on output
- */
-function addLineToOutput(text) {
- if (activeBox && text) {
- const wrapperElement = activeBox.querySelector('.ep-sync-box__output-wrapper');
-
- const lastLineNumberElement = activeBox.querySelector(
- '.ep-sync-box__output-line:last-child .ep-sync-box__output-line-number',
- );
- const lastLineNumber = Number(lastLineNumberElement?.innerText);
-
- const lineNumber = document.createElement('div');
- lineNumber.className = 'ep-sync-box__output-line-number';
- lineNumber.innerText =
- typeof lastLineNumber === 'number' && !Number.isNaN(lastLineNumber)
- ? lastLineNumber + 1
- : 1;
-
- const lineText = document.createElement('div');
- lineText.className = 'ep-sync-box__output-line-text';
- lineText.innerText = text;
-
- const line = document.createElement('div');
- line.className = 'ep-sync-box__output-line';
- line.append(lineNumber);
- line.append(lineText);
-
- wrapperElement.append(line);
-
- const outputElement = activeBox.querySelector('.ep-sync-box__output_active');
- outputElement.scrollTo(0, wrapperElement.scrollHeight);
- }
-}
-
-function addErrorToOutput(text) {
- if (activeBox) {
- const wrapperElement = activeBox.querySelector(
- '.ep-sync-box__output-error .ep-sync-box__output-wrapper',
- );
-
- const lastLineNumberElement = activeBox.querySelector(
- '.ep-sync-box__output-error .ep-sync-box__output-line:last-child .ep-sync-box__output-line-number',
- );
- const lastLineNumber = Number(lastLineNumberElement?.innerText);
-
- const lineNumber = document.createElement('div');
- lineNumber.className = 'ep-sync-box__output-line-number';
- lineNumber.innerText =
- typeof lastLineNumber === 'number' && !Number.isNaN(lastLineNumber)
- ? lastLineNumber + 1
- : 1;
-
- const lineText = document.createElement('div');
- lineText.className = 'ep-sync-box__output-line-text';
- lineText.innerText = text;
-
- const line = document.createElement('div');
- line.className = 'ep-sync-box__output-line';
- line.append(lineNumber);
- line.append(lineText);
-
- wrapperElement.append(line);
-
- const errorTab = activeBox.querySelector('.ep-sync-box__output-tab-error');
-
- errorTab.innerText = sprintf(
- // translators: Number of errors
- __('Errors (%d)', 'elasticpress'),
- lineNumber.innerText,
- );
-
- const outputElement = activeBox.querySelector('.ep-sync-box__output-error');
- outputElement.scrollTo(0, wrapperElement.scrollHeight);
- }
-}
-
-/**
- * Update the start datetime on active box
- *
- * @param {Date | string} dateValue The datetime value
- */
-function updateStartDateTime(dateValue) {
- if (dateValue) {
- const startDateTime = activeBox.querySelector('.ep-sync-box__start-time-date');
-
- if (startDateTime) {
- startDateTime.innerText = dateI18n(
- // translators: index start date format, see https://wordpress.org/support/article/formatting-date-and-time/
- __('D, F d, Y H:i', 'elasticpress'),
- dateValue,
- );
- }
- }
-}
-
-/**
- * Update the last sync datetime
- *
- * @param {Date | string} dateValue Date object or string, parsable by moment.js.
- */
-function updateLastSyncDateTime(dateValue) {
- if (dateValue) {
- const lastSyncDate = document.querySelector('.ep-last-sync__date');
-
- if (lastSyncDate) {
- lastSyncDate.innerText = dateI18n(
- // translators: last sync datetime format, see https://wordpress.org/support/article/formatting-date-and-time/
- __('D, F d, Y H:i', 'elasticpress'),
- dateValue,
- );
- }
- }
-}
-
-/**
- * Check if a destructive index is running
- *
- * @returns {boolean} Wheter or not is a destructive index
- */
-function isDestructiveIndex() {
- return activeBox === deleteAndSyncBox;
-}
-
-/**
- * Interrupt the sync process
- *
- * @param {boolean} value True to interrupt the sync process
- */
-function shouldInterruptSync(value) {
- if (!value) {
- return;
- }
-
- syncStatus = 'interrupt';
-
- let logMessage = __('Sync interrupted by WP-CLI command', 'elasticpress');
- if (isDestructiveIndex()) {
- logMessage = sprintf(
- // translators: ElasticPress.io or Elasticsearch
- __(
- '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.',
- 'elasticpress',
- ),
- is_epio ? 'ElasticPress.io' : 'Elasticsearch',
- );
- }
- stopIndex(__('Sync interrupted', 'elasticpress'), logMessage);
-}
-
-/**
- * Perform an elasticpress sync
- *
- * @param {boolean} putMapping Whetever mapping should be sent or not.
- */
-function sync(putMapping = false) {
- const requestSettings = {
- url: ajaxurl,
- method: 'POST',
- body: new URLSearchParams({
- action: 'ep_index',
- put_mapping: putMapping ? 1 : 0,
- nonce: epDash.nonce,
- }),
- };
-
- apiFetch(requestSettings)
- .then((response) => {
- if (response.data.index_meta?.current_sync_item?.failed) {
- const message = response.data?.message;
- if (Array.isArray(message)) {
- message.forEach((item) => {
- addErrorToOutput(item);
- addLineToOutput(item);
- });
- } else if (typeof message === 'string') {
- addErrorToOutput(message);
- addLineToOutput(message);
- }
- } else {
- addLineToOutput(response.data.message);
- }
- updateStartDateTime(response?.data?.index_meta?.start_date_time);
- shouldInterruptSync(response.data?.index_meta?.should_interrupt_sync);
-
- if (response.data?.method === 'cli') {
- syncStatus = 'wpcli';
- cliSync();
- return;
- }
-
- if (syncStatus !== 'sync') {
- return;
- }
-
- if (!response.data.index_meta) {
- syncStatus = 'finished';
-
- const lastSyncStatusIcon = document.querySelector('.ep-last-sync__icon-status');
- const lastSyncStatus = document.querySelector('.ep-last-sync__status');
-
- lastSyncStatusIcon.src = response.data.totals.failed
- ? lastSyncStatusIcon.src?.replace(/thumbsup/, 'thumbsdown')
- : lastSyncStatusIcon.src?.replace(/thumbsdown/, 'thumbsup');
- lastSyncStatus.innerText = response.data.totals.failed
- ? __('Sync unsuccessful on ', 'elasticpress')
- : __('Sync success on ', 'elasticpress');
-
- updateLastSyncDateTime(response.data?.totals?.end_date_time);
-
- updateSyncDash();
-
- addLineToOutput('===============================');
-
- if (epDash.install_sync) {
- document.location.replace(epDash.install_complete_url);
- }
-
- activeBox = undefined;
-
- processed = 0;
- toProcess = 0;
- totalProcessed = 0;
-
- return;
- }
-
- if (!toProcess) {
- toProcess = response.data?.index_meta?.current_sync_item?.found_items;
-
- if (response.data?.index_meta?.sync_stack) {
- syncStack = response.data.index_meta.sync_stack;
-
- toProcess = syncStack?.reduce((previousValue, currentSync) => {
- return previousValue + currentSync.found_items;
- }, toProcess);
- }
- }
-
- if (response.data.index_meta.offset === 0 && processed > 0) {
- totalProcessed = processed;
- }
-
- processed = totalProcessed + response.data.index_meta.offset;
-
- updateSyncDash();
- sync(putMapping);
- })
- .catch((response) => {
- if (response && response.code === 'invalid_json') {
- syncStatus = 'error';
- updateSyncDash();
- cancelSync();
- addErrorToOutput(response.message);
- }
-
- if (
- response &&
- response.status &&
- parseInt(response.status, 10) >= 400 &&
- parseInt(response.status, 10) < 600
- ) {
- syncStatus = 'error';
- updateSyncDash();
-
- cancelSync();
- }
-
- updateDisabledAttribute(syncButton, false);
- updateDisabledAttribute(deleteAndSyncButton, false);
- });
-}
-
-/**
- * Start sync process
- *
- * @param {boolean} putMapping Determines whether to send the mapping and delete all data before sync.
- */
-function startSyncProcess(putMapping) {
- syncStatus = 'initialsync';
-
- const progressWrapperElement = activeBox.querySelector('.ep-sync-box__progress-wrapper');
- const progressInfoElement = activeBox.querySelector('.ep-sync-box__progress-info');
- const progressBar = activeBox.querySelector('.ep-sync-box__progressbar_animated');
- const startDateTime = activeBox.querySelector('.ep-sync-box__start-time-date');
-
- progressWrapperElement.style.display = 'block';
- progressInfoElement.innerText = __('Sync in progress', 'elasticpress');
-
- progressBar.style.width = `0`;
- progressBar.innerText = ``;
-
- startDateTime.innerText = '';
-
- updateSyncDash();
-
- syncStatus = 'sync';
-
- sync(putMapping);
-}
-
-/**
- * Disable buttons in the Sync box
- */
-function disableButtonsInSyncBox() {
- const buttons = syncBox.querySelectorAll('.ep-sync-data button');
-
- buttons.forEach((button) => updateDisabledAttribute(button, true));
-}
-
-/**
- * Disable buttons in the Delete box
- */
-function disableButtonsInDeleteBox() {
- const buttons = deleteAndSyncBox.querySelectorAll('.ep-delete-data-and-sync button');
-
- buttons.forEach((button) => updateDisabledAttribute(button, true));
-}
-
-document.querySelectorAll('.ep-sync-box__button-pause')?.forEach((button) => {
- button?.addEventListener('click', function () {
- syncStatus = 'pause';
-
- const progressInfoElement = activeBox?.querySelector('.ep-sync-box__progress-info');
-
- if (progressInfoElement?.innerText) {
- progressInfoElement.innerText = __('Sync paused', 'elasticpress');
- }
-
- updateSyncDash();
-
- hidePauseButton();
- showResumeButton();
-
- addLineToOutput(__('Sync paused', 'elasticpress'));
- });
-});
-
-document.querySelectorAll('.ep-sync-box__button-resume')?.forEach((button) => {
- button?.addEventListener('click', function () {
- syncStatus = 'sync';
-
- const progressInfoElement = activeBox.querySelector('.ep-sync-box__progress-info');
-
- progressInfoElement.innerText = __('Sync in progress', 'elasticpress');
-
- updateSyncDash();
-
- hideResumeButton();
- showPauseButton();
-
- sync();
- });
-});
-
-function stopIndex(syncMessage, logMessage) {
- syncStatus = syncStatus === 'wpcli' ? 'interrupt' : 'cancel';
-
- const progressInfoElement = activeBox.querySelector('.ep-sync-box__progress-info');
- const progressBar = activeBox.querySelector('.ep-sync-box__progressbar_animated');
-
- updateSyncDash();
-
- cancelSync();
-
- progressInfoElement.innerText = syncMessage;
-
- progressBar.style.width = `0`;
- progressBar.innerText = ``;
-
- addLineToOutput(logMessage);
-}
-document.querySelectorAll('.ep-sync-box__button-stop')?.forEach((button) => {
- button?.addEventListener('click', () => {
- stopIndex(__('Sync stopped', 'elasticpress'), __('Sync stopped', 'elasticpress'));
- });
-});
-
-document.querySelectorAll('.ep-sync-box__show-hide-log')?.forEach((element) => {
- element.addEventListener('click', function (event) {
- event.preventDefault();
-
- if (element.nextElementSibling?.classList?.toggle('ep-sync-box__output-tabs_hide')) {
- element.innerText = __('Show log', 'elasticpress');
- } else {
- element.innerText = __('Hide log', 'elasticpress');
- }
- });
-});
-
-syncBoxFulllogTab.addEventListener('click', function () {
- syncBoxFulllogTab.classList.add('ep-sync-box__output-tab_active');
- syncBoxOutputFulllog.classList.add('ep-sync-box__output_active');
-
- syncBoxErrorTab.classList.remove('ep-sync-box__output-tab_active');
- syncBoxOutputError.classList.remove('ep-sync-box__output_active');
-});
-
-syncBoxErrorTab.addEventListener('click', function () {
- syncBoxErrorTab.classList.add('ep-sync-box__output-tab_active');
- syncBoxOutputError.classList.add('ep-sync-box__output_active');
-
- syncBoxFulllogTab.classList.remove('ep-sync-box__output-tab_active');
- syncBoxOutputFulllog.classList.remove('ep-sync-box__output_active');
-});
-
-deleteBoxFulllogTab.addEventListener('click', function () {
- deleteBoxFulllogTab.classList.add('ep-sync-box__output-tab_active');
- deleteBoxOutputFulllog.classList.add('ep-sync-box__output_active');
-
- deleteBoxErrorTab.classList.remove('ep-sync-box__output-tab_active');
- deleteBoxOutputError.classList.remove('ep-sync-box__output_active');
-});
-
-deleteBoxErrorTab.addEventListener('click', function () {
- deleteBoxErrorTab.classList.add('ep-sync-box__output-tab_active');
- deleteBoxOutputError.classList.add('ep-sync-box__output_active');
-
- deleteBoxFulllogTab.classList.remove('ep-sync-box__output-tab_active');
- deleteBoxOutputFulllog.classList.remove('ep-sync-box__output_active');
-});
diff --git a/assets/js/sync/components/common/date-time.js b/assets/js/sync/components/common/date-time.js
new file mode 100644
index 0000000000..2c2f7342e0
--- /dev/null
+++ b/assets/js/sync/components/common/date-time.js
@@ -0,0 +1,20 @@
+/**
+ * WordPress dependencies.
+ */
+import { dateI18n } from '@wordpress/date';
+import { WPElement } from '@wordpress/element';
+
+/**
+ * Log component.
+ *
+ * @param {object} props Component props.
+ * @param {string} props.dateTime Date and time.
+ * @returns {WPElement} Component.
+ */
+export default ({ dateTime, ...props }) => {
+ return (
+
+ {dateI18n('D, F d, Y H:i', dateTime)}
+
+ );
+};
diff --git a/assets/js/sync/components/common/message-log.js b/assets/js/sync/components/common/message-log.js
new file mode 100644
index 0000000000..f555197d95
--- /dev/null
+++ b/assets/js/sync/components/common/message-log.js
@@ -0,0 +1,35 @@
+/**
+ * WordPress dependencies.
+ */
+import { WPElement } from '@wordpress/element';
+
+/**
+ * Log component.
+ *
+ * @param {object} props Component props.
+ * @param {object[]} props.messages Log messages.
+ * @returns {WPElement} Component.
+ */
+export default ({ messages }) => {
+ return (
+
+ {messages.map((m, i) => (
+
+ {i + 1}
+
+ ))}
+ {messages.map((m) => (
+
+ {m.message}
+
+ ))}
+
+ );
+};
diff --git a/assets/js/sync/components/common/progress-bar.js b/assets/js/sync/components/common/progress-bar.js
new file mode 100644
index 0000000000..8bd4ce7d2c
--- /dev/null
+++ b/assets/js/sync/components/common/progress-bar.js
@@ -0,0 +1,32 @@
+/**
+ * WordPress dependencies.
+ */
+import { WPElement } from '@wordpress/element';
+
+/**
+ * Progress bar component.
+ *
+ * @param {object} props Component props.
+ * @param {number} props.current Current value.
+ * @param {number} props.total Current total.
+ * @param {boolean} props.isComplete If operation is complete.
+ * @returns {WPElement} Component.
+ */
+export default ({ isComplete, current, total }) => {
+ const now = Math.floor((current / total) * 100);
+
+ return (
+
+ );
+};
diff --git a/assets/js/sync/components/icons/pause.js b/assets/js/sync/components/icons/pause.js
new file mode 100644
index 0000000000..64c45c5334
--- /dev/null
+++ b/assets/js/sync/components/icons/pause.js
@@ -0,0 +1,15 @@
+import { SVG, Path } from '@wordpress/primitives';
+
+export default () => {
+ return (
+
+
+
+ );
+};
diff --git a/assets/js/sync/components/icons/play.js b/assets/js/sync/components/icons/play.js
new file mode 100644
index 0000000000..9052734fd3
--- /dev/null
+++ b/assets/js/sync/components/icons/play.js
@@ -0,0 +1,15 @@
+import { SVG, Path } from '@wordpress/primitives';
+
+export default () => {
+ return (
+
+
+
+ );
+};
diff --git a/assets/js/sync/components/icons/stop.js b/assets/js/sync/components/icons/stop.js
new file mode 100644
index 0000000000..027bd0998b
--- /dev/null
+++ b/assets/js/sync/components/icons/stop.js
@@ -0,0 +1,15 @@
+import { SVG, Path } from '@wordpress/primitives';
+
+export default () => {
+ return (
+
+
+
+ );
+};
diff --git a/assets/js/sync/components/icons/sync.js b/assets/js/sync/components/icons/sync.js
new file mode 100644
index 0000000000..c8e23d3b8a
--- /dev/null
+++ b/assets/js/sync/components/icons/sync.js
@@ -0,0 +1,15 @@
+import { SVG, Path } from '@wordpress/primitives';
+
+export default () => {
+ return (
+
+
+
+ );
+};
diff --git a/assets/js/sync/components/icons/thumbs-down.js b/assets/js/sync/components/icons/thumbs-down.js
new file mode 100644
index 0000000000..7677253ea0
--- /dev/null
+++ b/assets/js/sync/components/icons/thumbs-down.js
@@ -0,0 +1,15 @@
+import { SVG, Path } from '@wordpress/primitives';
+
+export default () => {
+ return (
+
+
+
+ );
+};
diff --git a/assets/js/sync/components/icons/thumbs-up.js b/assets/js/sync/components/icons/thumbs-up.js
new file mode 100644
index 0000000000..3e76177d50
--- /dev/null
+++ b/assets/js/sync/components/icons/thumbs-up.js
@@ -0,0 +1,15 @@
+import { SVG, Path } from '@wordpress/primitives';
+
+export default () => {
+ return (
+
+
+
+ );
+};
diff --git a/assets/js/sync/components/sync-page.js b/assets/js/sync/components/sync-page.js
new file mode 100644
index 0000000000..2d19077dbd
--- /dev/null
+++ b/assets/js/sync/components/sync-page.js
@@ -0,0 +1,182 @@
+/**
+ * WordPress dependencies.
+ */
+import { Button, Icon, Panel, PanelBody } from '@wordpress/components';
+import { WPElement } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { warning } from '@wordpress/icons';
+
+/**
+ * Internal dependencies.
+ */
+import SyncControls from './sync/controls';
+import SyncLog from './sync/log';
+import SyncProgress from './sync/progress';
+import SyncStatus from './sync/status';
+
+/**
+ * Sync page component.
+ *
+ * @param {object} props Component props.
+ * @param {boolean} props.isCli If sync is a CLI sync.
+ * @param {boolean} props.isComplete If sync is complete.
+ * @param {boolean} props.isDeleting If sync is a delete and sync.
+ * @param {boolean} props.isPaused If sync is paused.
+ * @param {boolean} props.isSyncing If sync is running.
+ * @param {number} props.itemsProcessed Number of items processed.
+ * @param {number} props.itemsTotal Number of items to process.
+ * @param {string} props.lastSyncDateTime Date and time of last sync in ISO-8601.
+ * @param {boolean} props.lastSyncFailed If the last sync had failures.
+ * @param {object[]} props.log Sync message log.
+ * @param {Function} props.onDelete Callback for clicking delete and sync.
+ * @param {Function} props.onPause Callback for clicking pause.
+ * @param {Function} props.onResume Callback for clicking resume.
+ * @param {Function} props.onStop Callback for clicking stop.
+ * @param {Function} props.onSync Callback for clicking sync.
+ * @param {string} props.syncStartDateTime Date and time of current sync in ISO 8601.
+ * @returns {WPElement} Sync page component.
+ */
+export default ({
+ isCli,
+ isComplete,
+ isDeleting,
+ isPaused,
+ isSyncing,
+ itemsProcessed,
+ itemsTotal,
+ lastSyncDateTime,
+ lastSyncFailed,
+ log,
+ onDelete,
+ onPause,
+ onResume,
+ onStop,
+ onSync,
+ syncStartDateTime,
+}) => {
+ return (
+ <>
+ {__('Sync Settings', 'elasticpress')}
+
+
+
+
+
+ {__(
+ 'If you are missing data in your search results or have recently added custom content types to your site, you should run a sync to reflect these changes.',
+ 'elasticpress',
+ )}
+
+
+ {lastSyncDateTime ? (
+ <>
+
+ {__('Last Sync', 'elasticpress')}
+
+
+ >
+ ) : null}
+
+
+
+
+
+
+ {!isDeleting && (isSyncing || isComplete) ? (
+
+
+
+ ) : null}
+
+
+ !m.isDeleting)} />
+
+
+
+
+ {__('Delete All Data and Sync', 'elasticpress')}
+
+
+
+
+
+ {__(
+ 'If you are still having issues with your search results, you may need to do a completely fresh sync.',
+ 'elasticpress',
+ )}
+
+
+
+
+ {__('Delete all Data and Start a Fresh Sync', 'elasticpress')}
+
+
+
+
+
+
+
+
+ {isDeleting && (isSyncing || isComplete) ? (
+
+
+
+ ) : null}
+
+
+ m.isDeleting)} />
+
+
+
+
+
+ {__(
+ 'All indexed data on ElasticPress will be deleted without affecting anything on your WordPress website. This may take a few hours depending on the amount of content that needs to be synced and indexed. While this is happenening, searches will use the default WordPress results',
+ 'elasticpress',
+ )}
+
+
+
+
+ >
+ );
+};
diff --git a/assets/js/sync/components/sync/controls.js b/assets/js/sync/components/sync/controls.js
new file mode 100644
index 0000000000..d32817447e
--- /dev/null
+++ b/assets/js/sync/components/sync/controls.js
@@ -0,0 +1,102 @@
+/**
+ * WordPress dependencies.
+ */
+import { Button } from '@wordpress/components';
+import { WPElement } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { update } from '@wordpress/icons';
+
+/**
+ * Internal dependencies.
+ */
+import pause from '../icons/pause';
+import play from '../icons/play';
+import stop from '../icons/stop';
+
+/**
+ * Sync button component.
+ *
+ * @param {object} props Component props.
+ * @param {boolean} props.disabled If controls are disabled.
+ * @param {boolean} props.isPaused If syncing is paused.
+ * @param {boolean} props.isSyncing If syncing is in progress.
+ * @param {Function} props.onPause Pause button click callback.
+ * @param {Function} props.onResume Play button click callback.
+ * @param {Function} props.onStop Stop button click callback.
+ * @param {Function} props.onSync Sync button click callback.
+ * @param {boolean} props.showSync If sync button is shown.
+ * @returns {WPElement} Component.
+ */
+export default ({ disabled, isPaused, isSyncing, onPause, onResume, onStop, onSync, showSync }) => {
+ /**
+ * Render.
+ */
+ return (
+
+ {showSync && !isSyncing ? (
+
+
+ {__('Sync Now', 'elasticpress')}
+
+
+ ) : null}
+
+ {isSyncing ? (
+ <>
+
+ {isPaused ? (
+
+ {__('Resume', 'elasticpress')}
+
+ ) : (
+
+ {__('Pause', 'elasticpress')}
+
+ )}
+
+
+
+
+ {__('Stop', 'elasticpress')}
+
+
+ >
+ ) : null}
+
+ {showSync ? (
+
+
+ {__('Learn more', 'elasticpress')}
+
+
+ ) : null}
+
+ );
+};
diff --git a/assets/js/sync/components/sync/log.js b/assets/js/sync/components/sync/log.js
new file mode 100644
index 0000000000..c9e1c2921a
--- /dev/null
+++ b/assets/js/sync/components/sync/log.js
@@ -0,0 +1,72 @@
+/**
+ * WordPress dependencies.
+ */
+import { TabPanel, ToggleControl } from '@wordpress/components';
+import { useState, WPElement } from '@wordpress/element';
+import { __, sprintf } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies.
+ */
+import MessageLog from '../common/message-log';
+
+/**
+ * Sync logs component.
+ *
+ * @param {object} props Component props.
+ * @param {object[]} props.messages Log messages.
+ * @returns {WPElement} Component.
+ */
+export default ({ messages }) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ /**
+ * Messages with the error status.
+ */
+ const errorMessages = messages.filter((m) => m.status === 'error' || m.status === 'warning');
+
+ /**
+ * Log tabs.
+ */
+ const tabs = [
+ {
+ messages,
+ name: 'full',
+ title: __('Full Log', 'elasticpress'),
+ },
+ {
+ messages: errorMessages,
+ name: 'error',
+ title: sprintf(
+ /* translators: %d: Error message count. */
+ __('Errors (%d)', 'elasticpress'),
+ errorMessages.length,
+ ),
+ },
+ ];
+
+ /**
+ * Handle clicking show log button.
+ *
+ * @param {boolean} checked If toggle is checked.
+ * @returns {void}
+ */
+ const onToggle = (checked) => {
+ setIsOpen(checked);
+ };
+
+ return (
+ <>
+
+ {isOpen ? (
+
+ {({ messages }) => }
+
+ ) : null}
+ >
+ );
+};
diff --git a/assets/js/sync/components/sync/progress.js b/assets/js/sync/components/sync/progress.js
new file mode 100644
index 0000000000..f6e98fe9cf
--- /dev/null
+++ b/assets/js/sync/components/sync/progress.js
@@ -0,0 +1,79 @@
+/**
+ * WordPress dependencies.
+ */
+import { Icon } from '@wordpress/components';
+import { useMemo, WPElement } from '@wordpress/element';
+import { dateI18n } from '@wordpress/date';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies.
+ */
+import DateTime from '../common/date-time';
+import ProgressBar from '../common/progress-bar';
+import sync from '../icons/sync';
+
+/**
+ * Sync button component.
+ *
+ * @param {object} props Component props.
+ * @param {boolean} props.isCli If progress is for a CLI sync.
+ * @param {boolean} props.isComplete If sync is complete.
+ * @param {boolean} props.isPaused If sync is paused.
+ * @param {number} props.itemsProcessed Number of items processed.
+ * @param {number} props.itemsTotal Total number of items.
+ * @param {string} props.dateTime Start date and time.
+ * @returns {WPElement} Component.
+ */
+export default ({ isCli, isComplete, isPaused, itemsProcessed, itemsTotal, dateTime }) => {
+ /**
+ * Sync progress label.
+ */
+ const label = useMemo(
+ /**
+ * Determine appropriate sync status label.
+ *
+ * @returns {string} Sync progress label.
+ */
+ () => {
+ if (isComplete) {
+ return __('Sync complete', 'elasticpress');
+ }
+
+ if (isPaused) {
+ return __('Sync paused', 'elasticpress');
+ }
+
+ if (isCli) {
+ return __('WP CLI sync in progress', 'elasticpress');
+ }
+
+ return __('Sync in progress', 'elasticpress');
+ },
+ [isCli, isComplete, isPaused],
+ );
+
+ return (
+
+
+
+
+ {label}
+ {dateTime ? (
+ <>
+ {__('Started on', 'elasticpress')}{' '}
+
+ >
+ ) : null}
+
+
+
+
+ );
+};
diff --git a/assets/js/sync/components/sync/status.js b/assets/js/sync/components/sync/status.js
new file mode 100644
index 0000000000..4ac77d6839
--- /dev/null
+++ b/assets/js/sync/components/sync/status.js
@@ -0,0 +1,40 @@
+/**
+ * WordPress dependencies.
+ */
+import { Icon } from '@wordpress/components';
+import { dateI18n } from '@wordpress/date';
+import { WPElement } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies.
+ */
+import DateTime from '../common/date-time';
+import thumbsDown from '../icons/thumbs-down';
+import thumbsUp from '../icons/thumbs-up';
+
+/**
+ * Sync button component.
+ *
+ * @param {object} props Component props.
+ * @param {string} props.dateTime Sync date and time.
+ * @param {boolean} props.isSuccess If sync was a success.
+ * @returns {WPElement} Component.
+ */
+export default ({ dateTime, isSuccess }) => {
+ return (
+
+
+
+ {isSuccess
+ ? __('Sync success on', 'elasticpress')
+ : __('Sync unsuccessful on', 'elasticpress')}{' '}
+
+
+
+ );
+};
diff --git a/assets/js/sync/config.js b/assets/js/sync/config.js
new file mode 100644
index 0000000000..b667ebdfd0
--- /dev/null
+++ b/assets/js/sync/config.js
@@ -0,0 +1,14 @@
+/**
+ * Window dependencies.
+ */
+const {
+ auto_start_index: autoIndex,
+ ajax_url: ajaxUrl,
+ index_meta: indexMeta = null,
+ is_epio: isEpio,
+ ep_last_sync_date: lastSyncDateTime = null,
+ ep_last_sync_failed: lastSyncFailed = false,
+ nonce,
+} = window.epDash;
+
+export { autoIndex, ajaxUrl, indexMeta, isEpio, lastSyncDateTime, lastSyncFailed, nonce };
diff --git a/assets/js/sync/hooks.js b/assets/js/sync/hooks.js
new file mode 100644
index 0000000000..84f4651b36
--- /dev/null
+++ b/assets/js/sync/hooks.js
@@ -0,0 +1,131 @@
+/**
+ * WordPress dependencies.
+ */
+import apiFetch from '@wordpress/api-fetch';
+import { useCallback, useRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies.
+ */
+import { ajaxUrl, nonce } from './config';
+
+/**
+ * Indexing hook.
+ *
+ * Provides methods for indexing, getting indexing status, and cancelling
+ * indexing. Methods share an abort controller so that requests can
+ * interrupt eachother to avoid multiple sync requests causing race conditions
+ * or duplicate output, such as by rapidly pausing and unpausing indexing.
+ *
+ * @returns {object} Sync, sync status, and cancel functions.
+ */
+export const useIndex = () => {
+ const abort = useRef(new AbortController());
+ const request = useRef(null);
+
+ const sendRequest = useCallback(
+ /**
+ * Send AJAX request.
+ *
+ * Silently catches abort errors and clears the current request on
+ * completion.
+ *
+ * @param {object} options Request options.
+ * @throws {Error} Any non-abort errors.
+ * @returns {Promise} Current request promise.
+ */
+ (options) => {
+ request.current = apiFetch(options).finally(() => {
+ request.current = null;
+ });
+
+ return request.current;
+ },
+ [],
+ );
+
+ const cancelIndex = useCallback(
+ /**
+ * Send a request to cancel sync.
+ *
+ * @returns {Promise} Fetch request promise.
+ */
+ async () => {
+ abort.current.abort();
+ abort.current = new AbortController();
+
+ const body = new FormData();
+
+ body.append('action', 'ep_cancel_index');
+ body.append('nonce', nonce);
+
+ const options = {
+ url: ajaxUrl,
+ method: 'POST',
+ body,
+ signal: abort.current.signal,
+ };
+
+ return sendRequest(options);
+ },
+ [sendRequest],
+ );
+
+ const index = useCallback(
+ /**
+ * Send a request to sync.
+ *
+ * @param {boolean} putMapping Whether to put mapping.
+ * @returns {Promise} Fetch request promise.
+ */
+ async (putMapping) => {
+ abort.current.abort();
+ abort.current = new AbortController();
+
+ const body = new FormData();
+
+ body.append('action', 'ep_index');
+ body.append('put_mapping', putMapping ? 1 : 0);
+ body.append('nonce', nonce);
+
+ const options = {
+ url: ajaxUrl,
+ method: 'POST',
+ body,
+ signal: abort.current.signal,
+ };
+
+ return sendRequest(options);
+ },
+ [sendRequest],
+ );
+
+ const indexStatus = useCallback(
+ /**
+ * Send a request for CLI sync status.
+ *
+ * @returns {Promise} Fetch request promise.
+ */
+ async () => {
+ abort.current.abort();
+ abort.current = new AbortController();
+
+ const body = new FormData();
+
+ body.append('action', 'ep_index_status');
+ body.append('nonce', nonce);
+
+ const options = {
+ url: ajaxUrl,
+ method: 'POST',
+ body,
+ signal: abort.current.signal,
+ };
+
+ return sendRequest(options);
+ },
+ [sendRequest],
+ );
+
+ return { cancelIndex, index, indexStatus };
+};
diff --git a/assets/js/sync/index.js b/assets/js/sync/index.js
new file mode 100644
index 0000000000..194bb2bb48
--- /dev/null
+++ b/assets/js/sync/index.js
@@ -0,0 +1,469 @@
+/**
+ * External dependencies.
+ */
+import { v4 as uuid } from 'uuid';
+
+/**
+ * WordPress dependencies.
+ */
+import { render, useCallback, useEffect, useRef, useState, WPElement } from '@wordpress/element';
+import { __, sprintf } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies.
+ */
+import { autoIndex, lastSyncDateTime, lastSyncFailed, isEpio, indexMeta } from './config';
+import { useIndex } from './hooks';
+import {
+ clearSyncParam,
+ getItemsProcessedFromIndexMeta,
+ getItemsTotalFromIndexMeta,
+} from './utilities';
+import SyncPage from './components/sync-page';
+
+/**
+ * App component.
+ *
+ * @returns {WPElement} App component.
+ */
+const App = () => {
+ /**
+ * Indexing methods.
+ */
+ const { cancelIndex, index, indexStatus } = useIndex();
+
+ /**
+ * Message log state.
+ */
+ const [log, setLog] = useState([]);
+
+ /**
+ * Sync state.
+ */
+ const [state, setState] = useState({
+ isComplete: false,
+ isDeleting: false,
+ isSyncing: false,
+ itemsProcessed: 0,
+ itemsTotal: 100,
+ lastSyncDateTime,
+ lastSyncFailed,
+ syncStartDateTime: null,
+ });
+
+ /**
+ * Current state reference.
+ */
+ const stateRef = useRef(state);
+
+ /**
+ * Update state, and current state ref.
+ *
+ * @param {object} newState New state properties.
+ * @returns {void}
+ */
+ const updateState = (newState) => {
+ stateRef.current = { ...stateRef.current, ...newState };
+ setState((state) => ({ ...state, ...newState }));
+ };
+
+ const logMessage = useCallback(
+ /**
+ * Log a message.
+ *
+ * @param {Array|string} message Message/s to log.
+ * @param {string} status Message status.
+ * @returns {void}
+ */
+ (message, status) => {
+ const { isDeleting } = stateRef.current;
+
+ const messages = Array.isArray(message) ? message : [message];
+
+ for (const message of messages) {
+ setLog((log) => [...log, { message, status, isDeleting, id: uuid() }]);
+ }
+ },
+ [],
+ );
+
+ const syncCompleted = useCallback(
+ /**
+ * Set sync state to completed, with success based on the number of
+ * failures in the index totals.
+ *
+ * @param {object} indexTotals Index totals.
+ * @returns {void}
+ */
+ (indexTotals) => {
+ updateState({
+ isComplete: true,
+ isPaused: false,
+ isSyncing: false,
+ lastSyncDateTime: indexTotals.end_date_time,
+ lastSyncFailed: indexTotals.failed > 0,
+ });
+ },
+ [],
+ );
+
+ const syncFailed = useCallback(
+ /**
+ * Handle an error in the sync request.
+ *
+ * @param {Error} error Request error.
+ * @returns {void}
+ */
+ (error) => {
+ /**
+ * Any running requests are cancelled when a new request is made.
+ * We can handle this silently.
+ */
+ if (error.name === 'AbortError') {
+ return;
+ }
+
+ /**
+ * Log any messages.
+ */
+ if (error.message) {
+ logMessage(error.message, 'error');
+ }
+
+ logMessage(__('Sync failed', 'elasticpress'), 'error');
+ updateState({ isSyncing: false });
+ },
+ [logMessage],
+ );
+
+ const syncInterrupted = useCallback(
+ /**
+ * Set sync state to interrupted.
+ *
+ * Logs an appropriate message based on the sync method and
+ * Elasticsearch hosting.
+ *
+ * @returns {void}
+ */
+ () => {
+ const { isDeleting } = stateRef.current;
+
+ const message = isDeleting
+ ? sprintf(
+ /* translators: %s: Index type. ElasticPress.io or Elasticsearch. */
+ __(
+ '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.',
+ 'elasticpress',
+ ),
+ isEpio
+ ? __('ElasticPress.io', 'elasticpress')
+ : __('Elasticsearch', 'elasticpress'),
+ )
+ : __('Sync interrupted by WP-CLI command.', 'elasticpress');
+
+ logMessage(message, 'info');
+ updateState({ isSyncing: false });
+ },
+ [logMessage],
+ );
+
+ const syncInProgress = useCallback(
+ /**
+ * Set state for a sync in progress from its index meta.
+ *
+ * @param {object} indexMeta Index meta.
+ * @returns {void}
+ */
+ (indexMeta) => {
+ updateState({
+ isCli: indexMeta.method === 'cli',
+ isComplete: false,
+ isDeleting: indexMeta.put_mapping,
+ isSyncing: true,
+ itemsProcessed: getItemsProcessedFromIndexMeta(indexMeta),
+ itemsTotal: getItemsTotalFromIndexMeta(indexMeta),
+ syncStartDateTime: indexMeta.start_date_time,
+ });
+ },
+ [],
+ );
+
+ const updateSyncState = useCallback(
+ /**
+ * Handle the response to a request to index.
+ *
+ * Updates the application state from the response data and logs any
+ * messages. Returns a Promise that resolves if syncing should
+ * continue.
+ *
+ * @param {object} response AJAX response.
+ * @returns {Promise} Promise that resolves if sync is to continue.
+ */
+ (response) => {
+ const { isPaused, isSyncing } = stateRef.current;
+ const { message, status, totals = [], index_meta: indexMeta } = response.data;
+
+ return new Promise((resolve) => {
+ /**
+ * Don't continue if syncing has been stopped.
+ */
+ if (!isSyncing) {
+ return;
+ }
+
+ /**
+ * Log any messages.
+ */
+ if (message) {
+ logMessage(message, status);
+ }
+
+ /**
+ * If totals are available the index is complete.
+ */
+ if (!Array.isArray(totals)) {
+ syncCompleted(totals);
+ return;
+ }
+
+ /**
+ * Update sync progress from index meta.
+ */
+ syncInProgress(indexMeta);
+
+ /**
+ * Don't continue if the sync was interrupted externally.
+ */
+ if (indexMeta.should_interrupt_sync) {
+ syncInterrupted();
+ return;
+ }
+
+ /**
+ * Don't continue if syncing has been paused.
+ */
+ if (isPaused) {
+ logMessage(__('Sync paused', 'elasticpress'), 'info');
+ return;
+ }
+
+ /**
+ * Syncing should continue.
+ */
+ resolve(indexMeta.method);
+ });
+ },
+ [syncCompleted, syncInProgress, syncInterrupted, logMessage],
+ );
+
+ const doIndexStatus = useCallback(
+ /**
+ * Check the status of a sync.
+ *
+ * Used to get the status of an external sync already in progress, such
+ * as a WP CLI index.
+ *
+ * @returns {void}
+ */
+ () => {
+ indexStatus().then(updateSyncState).then(doIndexStatus).catch(syncFailed);
+ },
+ [indexStatus, syncFailed, updateSyncState],
+ );
+
+ const doIndex = useCallback(
+ /**
+ * Start or continues a sync.
+ *
+ * @param {boolean} isDeleting Whether to delete and sync.
+ * @returns {void}
+ */
+ (isDeleting) => {
+ index(isDeleting)
+ .then(updateSyncState)
+ .then(
+ /**
+ * If an existing sync has been found just check its status,
+ * otherwise continue syncing.
+ *
+ * @param {string} method Sync method.
+ */
+ (method) => {
+ if (method === 'cli') {
+ doIndexStatus();
+ } else {
+ doIndex(isDeleting);
+ }
+ },
+ )
+ .catch(syncFailed);
+ },
+ [doIndexStatus, index, syncFailed, updateSyncState],
+ );
+
+ const pauseSync = useCallback(
+ /**
+ * Stop syncing.
+ *
+ * @returns {void}
+ */
+ () => {
+ updateState({ isComplete: false, isPaused: true, isSyncing: true });
+ },
+ [],
+ );
+
+ const stopSync = useCallback(
+ /**
+ * Stop syncing.
+ *
+ * @returns {void}
+ */
+ () => {
+ updateState({ isComplete: false, isPaused: false, isSyncing: false });
+ cancelIndex();
+ },
+ [cancelIndex],
+ );
+
+ const resumeSync = useCallback(
+ /**
+ * Resume syncing.
+ *
+ * @returns {void}
+ */
+ () => {
+ updateState({ isComplete: false, isPaused: false, isSyncing: true });
+ doIndex(stateRef.current.isDeleting);
+ },
+ [doIndex],
+ );
+
+ const startSync = useCallback(
+ /**
+ * Stop syncing.
+ *
+ * @param {boolean} isDeleting Whether to delete and sync.
+ * @returns {void}
+ */
+ (isDeleting) => {
+ updateState({ isComplete: false, isDeleting, isPaused: false, isSyncing: true });
+ updateState({ itemsProcessed: 0, syncStartDateTime: Date.now() });
+ doIndex(isDeleting);
+ },
+ [doIndex],
+ );
+
+ /**
+ * Handle clicking delete and sync button.
+ *
+ * @returns {void}
+ */
+ const onDelete = async () => {
+ startSync(true);
+ logMessage(__('Starting delete and sync…', 'elasticpress'), 'info');
+ };
+
+ /**
+ * Handle clicking pause button.
+ *
+ * @returns {void}
+ */
+ const onPause = () => {
+ pauseSync();
+ logMessage(__('Pausing sync…', 'elasticpress'), 'info');
+ };
+
+ /**
+ * Handle clicking play button.
+ *
+ * @returns {void}
+ */
+ const onResume = () => {
+ resumeSync();
+ logMessage(__('Resuming sync…', 'elasticpress'), 'info');
+ };
+
+ /**
+ * Handle clicking stop button.
+ *
+ * @returns {void}
+ */
+ const onStop = () => {
+ stopSync();
+ logMessage(__('Sync stopped', 'elasticpress'), 'info');
+ };
+
+ /**
+ * Handle clicking sync button.
+ *
+ * @returns {void}
+ */
+ const onSync = async () => {
+ startSync(false);
+ logMessage(__('Starting sync…', 'elasticpress'), 'info');
+ };
+
+ /**
+ * Initialize.
+ *
+ * @returns {void}
+ */
+ const init = () => {
+ /**
+ * Clear sync parameter from the URL to prevent a refresh triggering a new
+ * sync.
+ */
+ clearSyncParam();
+
+ /**
+ * If a sync is in progress, update state to reflect its progress.
+ */
+ if (indexMeta) {
+ syncInProgress(indexMeta);
+
+ /**
+ * If the sync is a CLI sync, start getting its status.
+ */
+ if (indexMeta.method === 'cli') {
+ doIndexStatus();
+ logMessage(__('WP CLI sync in progress', 'elasticpress'), 'info');
+ } else {
+ pauseSync();
+ logMessage(__('Sync paused', 'elasticpress'), 'info');
+ }
+
+ return;
+ }
+
+ /**
+ * Start an initial index.
+ */
+ if (autoIndex) {
+ startSync(true);
+ logMessage(__('Starting delete and sync…', 'elasticpress'), 'info');
+ }
+ };
+
+ /**
+ * Effects.
+ */
+ useEffect(init, [doIndexStatus, syncInProgress, logMessage, pauseSync, startSync]);
+
+ /**
+ * Render.
+ */
+ return (
+
+ );
+};
+
+render( , document.getElementById('ep-sync'));
diff --git a/assets/js/sync/utilities.js b/assets/js/sync/utilities.js
new file mode 100644
index 0000000000..57be5b6c9e
--- /dev/null
+++ b/assets/js/sync/utilities.js
@@ -0,0 +1,59 @@
+/**
+ * Clear sync parameter from the URL.
+ *
+ * @returns {void}
+ */
+export const clearSyncParam = () => {
+ window.history.replaceState(
+ {},
+ document.title,
+ document.location.pathname + document.location.search.replace(/&do_sync/, ''),
+ );
+};
+
+/**
+ * Get the total number of items from index meta.
+ *
+ * @param {object} indexMeta Index meta.
+ * @returns {number} Number of items.
+ */
+export const getItemsTotalFromIndexMeta = (indexMeta) => {
+ let itemsTotal = 0;
+
+ if (indexMeta.current_sync_item) {
+ itemsTotal += indexMeta.current_sync_item.found_items;
+ }
+
+ itemsTotal = indexMeta.sync_stack.reduce(
+ (itemsTotal, sync) => itemsTotal + sync.found_items,
+ itemsTotal,
+ );
+
+ itemsTotal += indexMeta.totals.failed;
+ itemsTotal += indexMeta.totals.skipped;
+ itemsTotal += indexMeta.totals.synced;
+
+ return itemsTotal;
+};
+
+/**
+ * Get the number of processed items from index meta.
+ *
+ * @param {object} indexMeta Index meta.
+ * @returns {number} Number of processed items.
+ */
+export const getItemsProcessedFromIndexMeta = (indexMeta) => {
+ let itemsProcessed = 0;
+
+ if (indexMeta.current_sync_item) {
+ itemsProcessed += indexMeta.current_sync_item.failed;
+ itemsProcessed += indexMeta.current_sync_item.skipped;
+ itemsProcessed += indexMeta.current_sync_item.synced;
+ }
+
+ itemsProcessed += indexMeta.totals.failed;
+ itemsProcessed += indexMeta.totals.skipped;
+ itemsProcessed += indexMeta.totals.synced;
+
+ return itemsProcessed;
+};
diff --git a/images/pause.svg b/images/pause.svg
deleted file mode 100644
index 7e602ca8dd..0000000000
--- a/images/pause.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/images/resume.svg b/images/resume.svg
deleted file mode 100644
index 4d09d15dcc..0000000000
--- a/images/resume.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/images/stop.svg b/images/stop.svg
deleted file mode 100644
index 99ea466ff3..0000000000
--- a/images/stop.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/images/thumbsdown.svg b/images/thumbsdown.svg
deleted file mode 100644
index cf22ed0ade..0000000000
--- a/images/thumbsdown.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/images/thumbsup.svg b/images/thumbsup.svg
deleted file mode 100644
index 42612aafc1..0000000000
--- a/images/thumbsup.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/includes/classes/Command.php b/includes/classes/Command.php
index 1973bb8ccb..7e2c3fd15b 100644
--- a/includes/classes/Command.php
+++ b/includes/classes/Command.php
@@ -261,7 +261,7 @@ private function put_mapping_helper( $args, $assoc_args ) {
continue;
}
- WP_CLI::line( sprintf( esc_html__( 'Adding %1$s mapping for site %2$d...', 'elasticpress' ), esc_html( strtolower( $indexable->labels['singular'] ) ), (int) $site['blog_id'] ) );
+ WP_CLI::line( sprintf( esc_html__( 'Adding %1$s mapping for site %2$d…', 'elasticpress' ), esc_html( strtolower( $indexable->labels['singular'] ) ), (int) $site['blog_id'] ) );
$indexable->delete_index();
$result = $indexable->put_mapping();
@@ -296,7 +296,7 @@ private function put_mapping_helper( $args, $assoc_args ) {
continue;
}
- WP_CLI::line( sprintf( esc_html__( 'Adding %s mapping...', 'elasticpress' ), esc_html( strtolower( $indexable->labels['singular'] ) ) ) );
+ WP_CLI::line( sprintf( esc_html__( 'Adding %s mapping…', 'elasticpress' ), esc_html( strtolower( $indexable->labels['singular'] ) ) ) );
$indexable->delete_index();
$result = $indexable->put_mapping();
@@ -332,7 +332,7 @@ private function put_mapping_helper( $args, $assoc_args ) {
continue;
}
- WP_CLI::line( sprintf( esc_html__( 'Adding %s mapping...', 'elasticpress' ), esc_html( strtolower( $indexable->labels['singular'] ) ) ) );
+ WP_CLI::line( sprintf( esc_html__( 'Adding %s mapping…', 'elasticpress' ), esc_html( strtolower( $indexable->labels['singular'] ) ) ) );
$indexable->delete_index();
$result = $indexable->put_mapping();
@@ -507,7 +507,7 @@ public function delete_index( $args, $assoc_args ) {
foreach ( $non_global_indexable_objects as $indexable ) {
- WP_CLI::line( sprintf( esc_html__( 'Deleting %1$s index for site %2$d...', 'elasticpress' ), esc_html( strtolower( $indexable->labels['singular'] ) ), (int) $site['blog_id'] ) );
+ WP_CLI::line( sprintf( esc_html__( 'Deleting %1$s index for site %2$d…', 'elasticpress' ), esc_html( strtolower( $indexable->labels['singular'] ) ), (int) $site['blog_id'] ) );
$result = $indexable->delete_index();
@@ -522,7 +522,7 @@ public function delete_index( $args, $assoc_args ) {
}
} else {
foreach ( $non_global_indexable_objects as $indexable ) {
- WP_CLI::line( sprintf( esc_html__( 'Deleting index for %s...', 'elasticpress' ), esc_html( strtolower( $indexable->labels['plural'] ) ) ) );
+ WP_CLI::line( sprintf( esc_html__( 'Deleting index for %s…', 'elasticpress' ), esc_html( strtolower( $indexable->labels['plural'] ) ) ) );
$result = $indexable->delete_index();
@@ -535,7 +535,7 @@ public function delete_index( $args, $assoc_args ) {
}
foreach ( $global_indexable_objects as $indexable ) {
- WP_CLI::line( sprintf( esc_html__( 'Deleting index for %s...', 'elasticpress' ), esc_html( strtolower( $indexable->labels['plural'] ) ) ) );
+ WP_CLI::line( sprintf( esc_html__( 'Deleting index for %s…', 'elasticpress' ), esc_html( strtolower( $indexable->labels['plural'] ) ) ) );
$result = $indexable->delete_index();
@@ -564,7 +564,7 @@ public function recreate_network_alias( $args, $assoc_args ) {
$indexables = Indexables::factory()->get_all( false );
foreach ( $indexables as $indexable ) {
- WP_CLI::line( sprintf( esc_html__( 'Recreating %s network alias...', 'elasticpress' ), esc_html( strtolower( $indexable->labels['singular'] ) ) ) );
+ WP_CLI::line( sprintf( esc_html__( 'Recreating %s network alias…', 'elasticpress' ), esc_html( strtolower( $indexable->labels['singular'] ) ) ) );
$indexable->delete_network_alias();
@@ -1132,7 +1132,7 @@ public function stop_indexing( $args, $assoc_args ) {
if ( empty( \ElasticPress\Utils\get_indexing_status() ) ) {
WP_CLI::warning( esc_html__( 'There is no indexing operation running.', 'elasticpress' ) );
} else {
- WP_CLI::line( esc_html__( 'Stopping indexing...', 'elasticpress' ) );
+ WP_CLI::line( esc_html__( 'Stopping indexing…', 'elasticpress' ) );
if ( isset( $indexing_status['method'] ) && 'cli' === $indexing_status['method'] ) {
set_transient( 'ep_wpcli_sync_interrupted', true, MINUTE_IN_SECONDS );
diff --git a/includes/classes/Feature/Search/Search.php b/includes/classes/Feature/Search/Search.php
index 6f2e454b6b..47ebbe9a26 100644
--- a/includes/classes/Feature/Search/Search.php
+++ b/includes/classes/Feature/Search/Search.php
@@ -59,7 +59,7 @@ public function __construct() {
$this->requires_install_reindex = false;
- $this->default_settings = [
+ $this->default_settings = [
'decaying_enabled' => '1',
'synonyms_editor_mode' => 'simple',
'highlight_enabled' => '0',
diff --git a/includes/classes/IndexHelper.php b/includes/classes/IndexHelper.php
index 5d27626b87..e81913bf8d 100644
--- a/includes/classes/IndexHelper.php
+++ b/includes/classes/IndexHelper.php
@@ -287,7 +287,7 @@ protected function process_sync_item() {
$this->output_success(
sprintf(
/* translators: 1: Indexable name, 2: Site ID */
- esc_html__( 'Indexing %1$s on site %2$d...', 'elasticpress' ),
+ esc_html__( 'Indexing %1$s on site %2$d…', 'elasticpress' ),
esc_html( strtolower( $indexable->labels['plural'] ) ),
$this->index_meta['current_sync_item']['blog_id']
)
@@ -295,9 +295,9 @@ protected function process_sync_item() {
} else {
$message_string = ( $indexable->global ) ?
/* translators: 1: Indexable name */
- esc_html__( 'Indexing %1$s (globally)...', 'elasticpress' ) :
+ esc_html__( 'Indexing %1$s (globally)…', 'elasticpress' ) :
/* translators: 1: Indexable name */
- esc_html__( 'Indexing %1$s...', 'elasticpress' );
+ esc_html__( 'Indexing %1$s…', 'elasticpress' );
$this->output_success(
sprintf(
@@ -410,7 +410,7 @@ protected function index_objects() {
$this->output(
sprintf(
/* translators: 1. Number of objects skipped 2. Indexable type */
- esc_html__( 'Skipping %1$d %2$s...', 'elasticpress' ),
+ esc_html__( 'Skipping %1$d %2$s…', 'elasticpress' ),
$this->index_meta['from'],
esc_html( strtolower( $indexable->labels['plural'] ) )
),
@@ -751,9 +751,9 @@ protected function index_cleanup() {
$current_sync_item = $this->index_meta['current_sync_item'];
- if ( $current_sync_item['failed'] ) {
- $this->index_meta['current_sync_item']['failed'] = 0;
+ $this->index_meta['current_sync_item'] = null;
+ if ( $current_sync_item['failed'] ) {
if ( ! empty( $current_sync_item['blog_id'] ) && defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
$message = sprintf(
/* translators: 1: indexable (plural), 2: Blog ID, 3: number of failed objects */
@@ -774,8 +774,7 @@ protected function index_cleanup() {
$this->output( $message, 'warning' );
}
- $this->index_meta['offset'] = 0;
- $this->index_meta['current_sync_item'] = null;
+ $this->index_meta['offset'] = 0;
if ( ! empty( $current_sync_item['blog_id'] ) && defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
$message = sprintf(
@@ -878,7 +877,7 @@ protected function create_network_alias() {
$this->output_success(
sprintf(
/* translators: 1: Indexable name */
- esc_html__( 'Network alias created for %1$s ...', 'elasticpress' ),
+ esc_html__( 'Network alias created for %1$s', 'elasticpress' ),
esc_html( strtolower( $indexable->labels['plural'] ) )
)
);
@@ -886,7 +885,7 @@ protected function create_network_alias() {
$this->output_error(
sprintf(
/* translators: 1: Indexable name */
- esc_html__( 'Network alias creation failed for %1$s ...', 'elasticpress' ),
+ esc_html__( 'Network alias creation failed for %1$s', 'elasticpress' ),
esc_html( strtolower( $indexable->labels['plural'] ) )
)
);
diff --git a/includes/classes/Indexable/User/User.php b/includes/classes/Indexable/User/User.php
index 40136f9367..ada5fe2d5c 100644
--- a/includes/classes/Indexable/User/User.php
+++ b/includes/classes/Indexable/User/User.php
@@ -134,7 +134,7 @@ public function format_args( $query_vars, $query ) {
// If there are no specific roles named, make sure the user is a member of the site.
if ( empty( $query_vars['role'] ) && empty( $query_vars['role__in'] ) && empty( $query_vars['role__not_in'] ) ) {
- $filter['bool']['must'][] = array(
+ $filter['bool']['must'][] = array(
'exists' => array(
'field' => 'capabilities.' . $blog_id . '.roles',
),
@@ -747,7 +747,7 @@ public function query_db( $args ) {
* WP_User_Query doesn't let us get users across all blogs easily. This is the best
* way to do that.
*/
- // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$objects = $wpdb->get_results( $wpdb->prepare( "SELECT SQL_CALC_FOUND_ROWS ID FROM {$wpdb->users} {$orderby} LIMIT %d, %d", (int) $args['offset'], (int) $args['number'] ) );
return [
diff --git a/includes/classes/Screen/Sync.php b/includes/classes/Screen/Sync.php
index 8a419f9ac6..03515b6f74 100644
--- a/includes/classes/Screen/Sync.php
+++ b/includes/classes/Screen/Sync.php
@@ -27,8 +27,8 @@ class Sync {
* Initialize class
*/
public function setup() {
- add_action( 'wp_ajax_ep_cli_index', [ $this, 'action_wp_ajax_ep_cli_index' ] );
add_action( 'wp_ajax_ep_index', [ $this, 'action_wp_ajax_ep_index' ] );
+ add_action( 'wp_ajax_ep_index_status', [ $this, 'action_wp_ajax_ep_index_status' ] );
add_action( 'wp_ajax_ep_cancel_index', [ $this, 'action_wp_ajax_ep_cancel_index' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
@@ -39,9 +39,9 @@ public function setup() {
*
* @since 3.6.0
*/
- public function action_wp_ajax_ep_cli_index() {
+ public function action_wp_ajax_ep_index_status() {
if ( ! check_ajax_referer( 'ep_dashboard_nonce', 'nonce', false ) || ! EP_DASHBOARD_SYNC ) {
- wp_send_json_error();
+ wp_send_json_error( null, 403 );
exit;
}
@@ -65,6 +65,7 @@ public function action_wp_ajax_ep_cli_index() {
wp_send_json_success(
[
'is_finished' => true,
+ 'totals' => Utils\get_option( 'ep_last_index' ),
]
);
}
@@ -76,14 +77,14 @@ public function action_wp_ajax_ep_cli_index() {
*/
public function action_wp_ajax_ep_index() {
if ( ! check_ajax_referer( 'ep_dashboard_nonce', 'nonce', false ) || ! EP_DASHBOARD_SYNC ) {
- wp_send_json_error();
+ wp_send_json_error( null, 403 );
exit;
}
$index_meta = Utils\get_indexing_status();
if ( isset( $index_meta['method'] ) && 'cli' === $index_meta['method'] ) {
- wp_send_json_success( $index_meta );
+ $this->action_wp_ajax_ep_index_status();
exit;
}
@@ -104,7 +105,7 @@ public function action_wp_ajax_ep_index() {
*/
public function action_wp_ajax_ep_cancel_index() {
if ( ! check_ajax_referer( 'ep_dashboard_nonce', 'nonce', false ) || ! EP_DASHBOARD_SYNC ) {
- wp_send_json_error();
+ wp_send_json_error( null, 403 );
exit;
}
@@ -131,6 +132,7 @@ public function admin_enqueue_scripts() {
if ( 'sync' !== Screen::factory()->get_current_screen() ) {
return;
}
+
wp_enqueue_script(
'ep_sync_scripts',
EP_URL . 'dist/js/sync-script.min.js',
@@ -139,6 +141,9 @@ public function admin_enqueue_scripts() {
true
);
+ wp_enqueue_style( 'wp-components' );
+ wp_enqueue_style( 'wp-edit-post' );
+
wp_enqueue_style(
'ep_sync_style',
EP_URL . 'dist/css/sync-styles.min.css',
@@ -168,7 +173,8 @@ public function admin_enqueue_scripts() {
$ep_last_index = IndexHelper::factory()->get_last_index();
if ( ! empty( $ep_last_index ) ) {
- $data['ep_last_sync_date'] = ! empty( $ep_last_index['end_date_time'] ) ? $ep_last_index['end_date_time'] : false;
+ $data['ep_last_sync_date'] = ! empty( $ep_last_index['end_date_time'] ) ? $ep_last_index['end_date_time'] : false;
+ $data['ep_last_sync_failed'] = ! empty( $ep_last_index['failed'] ) ? true : false;
}
/**
diff --git a/includes/partials/sync-page.php b/includes/partials/sync-page.php
index 0889e2379b..9ae08ab989 100644
--- a/includes/partials/sync-page.php
+++ b/includes/partials/sync-page.php
@@ -6,200 +6,11 @@
* @package elasticpress
*/
-$ep_last_index = \ElasticPress\IndexHelper::factory()->get_last_index();
-$ep_last_sync_has_error = ! empty( $ep_last_index['failed'] );
+if ( ! defined( 'ABSPATH' ) ) {
+ exit; // Exit if accessed directly.
+}
+require_once __DIR__ . '/header.php';
?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/package-lock.json b/package-lock.json
index 179c39e8d5..8d0bc20c87 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -32,6 +32,7 @@
"devDependencies": {
"@wordpress/env": "^4.2.2",
"10up-toolkit": "^3.0.0",
+ "classnames": "^2.3.1",
"cypress": "^9.5.0",
"cypress-file-upload": "^5.0.8",
"eslint-plugin-cypress": "^2.12.1",
@@ -6323,7 +6324,8 @@
},
"node_modules/classnames": {
"version": "2.3.1",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
+ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"node_modules/clean-css": {
"version": "5.2.4",
@@ -24188,7 +24190,9 @@
"dev": true
},
"classnames": {
- "version": "2.3.1"
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
+ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"clean-css": {
"version": "5.2.4",
diff --git a/package.json b/package.json
index 2e5517091b..6382e06c40 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"devDependencies": {
"@wordpress/env": "^4.2.2",
"10up-toolkit": "^3.0.0",
+ "classnames": "^2.3.1",
"cypress": "^9.5.0",
"cypress-file-upload": "^5.0.8",
"eslint-plugin-cypress": "^2.12.1",
@@ -54,7 +55,7 @@
"ordering-script.min": "./assets/js/ordering/index.js",
"related-posts-block-script.min": "./assets/js/blocks/related-posts/block.js",
"settings-script.min": "./assets/js/settings.js",
- "sync-script.min": "./assets/js/sync.js",
+ "sync-script.min": "./assets/js/sync/index.js",
"sites-admin-script.min": "./assets/js/sites-admin.js",
"stats-script.min": "./assets/js/stats.js",
"synonyms-script.min": "./assets/js/synonyms/index.js",
diff --git a/tests/cypress/integration/dashboard-sync.spec.js b/tests/cypress/integration/dashboard-sync.spec.js
index c28d7daba3..e00dbf3c53 100644
--- a/tests/cypress/integration/dashboard-sync.spec.js
+++ b/tests/cypress/integration/dashboard-sync.spec.js
@@ -18,10 +18,10 @@ describe('Dashboard Sync', () => {
}
function resumeAndWait() {
- cy.get('.ep-delete-data-and-sync .resume-sync').click();
- cy.get('.ep-delete-data-and-sync .ep-sync-box__progress-info', {
+ cy.get('.ep-sync-button--resume').click();
+ cy.get('.ep-sync-progress strong', {
timeout: Cypress.config('elasticPressIndexTimeout'),
- }).should('contain.text', 'Sync completed');
+ }).should('contain.text', 'Sync complete');
}
before(() => {
@@ -37,10 +37,10 @@ describe('Dashboard Sync', () => {
it('Can index content and see indexes names in the Health Screen', () => {
cy.visitAdminPage('admin.php?page=elasticpress-sync');
- cy.get('.ep-delete-data-and-sync__button-delete').click();
- cy.get('.ep-delete-data-and-sync .ep-sync-box__progress-info', {
+ cy.get('.ep-sync-button--delete').click();
+ cy.get('.ep-sync-progress strong', {
timeout: Cypress.config('elasticPressIndexTimeout'),
- }).should('contain.text', 'Sync completed');
+ }).should('contain.text', 'Sync complete');
canSeeIndexesNames();
});
@@ -55,10 +55,10 @@ describe('Dashboard Sync', () => {
);
cy.visitAdminPage('admin.php?page=elasticpress-sync');
- cy.get('.ep-delete-data-and-sync__button-delete').click();
- cy.get('.ep-delete-data-and-sync .ep-sync-box__progress-info', {
+ cy.get('.ep-sync-button--delete').click();
+ cy.get('.ep-sync-progress strong', {
timeout: Cypress.config('elasticPressIndexTimeout'),
- }).should('contain.text', 'Sync completed');
+ }).should('contain.text', 'Sync complete');
cy.visitAdminPage('admin.php?page=elasticpress-health');
cy.get('.wrap').should(
@@ -85,10 +85,10 @@ describe('Dashboard Sync', () => {
);
cy.visitAdminPage('network/admin.php?page=elasticpress-sync');
- cy.get('.ep-delete-data-and-sync__button-delete').click();
- cy.get('.ep-delete-data-and-sync .ep-sync-box__progress-info', {
+ cy.get('.ep-sync-button--delete').click();
+ cy.get('.ep-sync-progress strong', {
timeout: Cypress.config('elasticPressIndexTimeout'),
- }).should('contain.text', 'Sync completed');
+ }).should('contain.text', 'Sync complete');
cy.visitAdminPage('network/admin.php?page=elasticpress-health');
cy.get('.wrap').should(
@@ -120,17 +120,15 @@ describe('Dashboard Sync', () => {
cy.visitAdminPage('admin.php?page=elasticpress-sync');
cy.intercept('POST', '/wp-admin/admin-ajax.php*').as('ajaxRequest');
- cy.get('.ep-delete-data-and-sync__button-delete').click();
+ cy.get('.ep-sync-button--delete').click();
cy.wait('@ajaxRequest').its('response.statusCode').should('eq', 200);
- cy.get('.ep-delete-data-and-sync .pause-sync').should('be.visible');
+ cy.get('.ep-sync-button--pause').should('be.visible');
cy.visitAdminPage('index.php');
cy.visitAdminPage('admin.php?page=elasticpress-sync');
- cy.get('.ep-delete-data-and-sync .ep-sync-box__progress-info').should(
- 'contain.text',
- 'Sync in progress',
- );
+ cy.get('.ep-sync-button--resume').should('be.visible');
+ cy.get('.ep-sync-progress strong').should('contain.text', 'Sync paused');
resumeAndWait();
@@ -144,7 +142,7 @@ describe('Dashboard Sync', () => {
cy.visitAdminPage('admin.php?page=elasticpress-sync');
cy.intercept('POST', '/wp-admin/admin-ajax.php*').as('ajaxRequest');
- cy.get('.ep-delete-data-and-sync__button-delete').click();
+ cy.get('.ep-sync-button--delete').click();
cy.wait('@ajaxRequest').its('response.statusCode').should('eq', 200);
cy.visitAdminPage('admin.php?page=elasticpress');
@@ -164,12 +162,11 @@ describe('Dashboard Sync', () => {
cy.visitAdminPage('admin.php?page=elasticpress-sync');
cy.intercept('POST', '/wp-admin/admin-ajax.php*').as('ajaxRequest');
- cy.get('.ep-delete-data-and-sync__button-delete').click();
+ cy.get('.ep-sync-button--delete').click();
cy.wait('@ajaxRequest').its('response.statusCode').should('eq', 200);
- cy.get('.ep-delete-data-and-sync .pause-sync').should('be.visible');
- cy.get('.ep-delete-data-and-sync .pause-sync').click();
- cy.wait('@ajaxRequest').its('response.statusCode').should('eq', 200);
+ cy.get('.ep-sync-button--pause').should('be.visible');
+ cy.get('.ep-sync-button--pause').click();
cy.wpCli('wp elasticpress index', true)
.its('stderr')
diff --git a/tests/cypress/integration/features/protected-content.spec.js b/tests/cypress/integration/features/protected-content.spec.js
index 9f5e88437b..738d202237 100644
--- a/tests/cypress/integration/features/protected-content.spec.js
+++ b/tests/cypress/integration/features/protected-content.spec.js
@@ -10,9 +10,9 @@ describe('Protected Content Feature', () => {
return true;
});
- cy.get('.ep-delete-data-and-sync .ep-sync-box__progress-info', {
+ cy.get('.ep-sync-progress strong', {
timeout: Cypress.config('elasticPressIndexTimeout'),
- }).should('contain.text', 'Sync completed');
+ }).should('contain.text', 'Sync complete');
cy.wpCli('elasticpress list-features').its('stdout').should('contain', 'protected_content');
});
diff --git a/tests/cypress/integration/features/woocommerce.spec.js b/tests/cypress/integration/features/woocommerce.spec.js
index 8cc64b668c..a5b9ed6fd5 100644
--- a/tests/cypress/integration/features/woocommerce.spec.js
+++ b/tests/cypress/integration/features/woocommerce.spec.js
@@ -29,9 +29,9 @@ describe('WooCommerce Feature', () => {
return true;
});
- cy.get('.ep-delete-data-and-sync .ep-sync-box__progress-info', {
+ cy.get('.ep-sync-progress strong', {
timeout: Cypress.config('elasticPressIndexTimeout'),
- }).should('contain.text', 'Sync completed');
+ }).should('contain.text', 'Sync complete');
cy.wpCli('elasticpress list-features').its('stdout').should('contain', 'woocommerce');
});
diff --git a/tests/cypress/integration/indexables/user.spec.js b/tests/cypress/integration/indexables/user.spec.js
index b4e8c45119..14abd65b94 100644
--- a/tests/cypress/integration/indexables/user.spec.js
+++ b/tests/cypress/integration/indexables/user.spec.js
@@ -44,9 +44,9 @@ describe('User Indexable', () => {
return true;
});
- cy.get('.ep-delete-data-and-sync .ep-sync-box__progress-info', {
+ cy.get('.ep-sync-progress strong', {
timeout: Cypress.config('elasticPressIndexTimeout'),
- }).should('contain.text', 'Sync completed');
+ }).should('contain.text', 'Sync complete');
cy.wpCli('elasticpress list-features').its('stdout').should('contain', 'users');
});