From b15840e6afb35bbaa944bac15e84ddebb67a3129 Mon Sep 17 00:00:00 2001 From: Jacob Peattie Date: Fri, 29 Sep 2023 03:37:27 +1000 Subject: [PATCH 1/6] Update sync provider to use sync history. --- assets/js/sync/index.js | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/assets/js/sync/index.js b/assets/js/sync/index.js index ef01eaec6a..ae1ab72907 100644 --- a/assets/js/sync/index.js +++ b/assets/js/sync/index.js @@ -39,8 +39,7 @@ const Context = createContext(); * @param {object} props Component props. * @param {string} props.apiUrl API endpoint URL. * @param {Function} props.children Component children - * @param {string} props.defaultLastSyncDateTime Last sync date and time. - * @param {boolean} props.defaultLastSyncFailed Whether the last sync failed. + * @param {Array} props.defaultSyncHistory Sync history. * @param {object|null} props.indexMeta Details of a sync in progress. * @param {boolean} props.isEpio Whether ElasticPress.io is in use. * @param {string} props.nonce WordPress nonce. @@ -49,8 +48,7 @@ const Context = createContext(); export const SyncProvider = ({ apiUrl, children, - defaultLastSyncDateTime, - defaultLastSyncFailed, + defaultSyncHistory, indexMeta, isEpio, nonce, @@ -77,9 +75,8 @@ export const SyncProvider = ({ isSyncing: false, itemsProcessed: 0, itemsTotal: 100, - lastSyncDateTime: defaultLastSyncDateTime, - lastSyncFailed: defaultLastSyncFailed, syncStartDateTime: null, + syncHistory: defaultSyncHistory, }); /** @@ -165,8 +162,7 @@ export const SyncProvider = ({ isComplete: true, isPaused: false, isSyncing: false, - lastSyncDateTime: indexTotals.end_date_time, - lastSyncFailed: indexTotals.failed > 0, + syncHistory: [indexTotals, ...stateRef.current.syncHistory], }); /** @@ -208,8 +204,6 @@ export const SyncProvider = ({ updateState({ isFailed: true, isSyncing: false, - lastSyncDateTime: stateRef.current.syncStartDateTime, - lastSyncFailed: true, }); }, [logMessage], @@ -420,8 +414,8 @@ export const SyncProvider = ({ * @returns {void} */ (args) => { - const { lastSyncDateTime } = stateRef.current; - const isInitialSync = lastSyncDateTime === null; + const { syncHistory } = stateRef.current; + const isInitialSync = !syncHistory.length; /** * We should not appear to be deleting if this is the first sync. @@ -490,8 +484,7 @@ export const SyncProvider = ({ isSyncing, itemsProcessed, itemsTotal, - lastSyncDateTime, - lastSyncFailed, + syncHistory, syncStartDateTime, } = stateRef.current; @@ -506,8 +499,7 @@ export const SyncProvider = ({ isSyncing, itemsProcessed, itemsTotal, - lastSyncDateTime, - lastSyncFailed, + syncHistory, log, logMessage, pauseSync, From 3a1b32209ed1007b0f2d4d967abf4b6559167542 Mon Sep 17 00:00:00 2001 From: Jacob Peattie Date: Fri, 29 Sep 2023 03:50:53 +1000 Subject: [PATCH 2/6] Use sync history instead of last sync time. --- assets/js/sync-ui/apps/sync.js | 8 ++++---- assets/js/sync-ui/components/controls.js | 4 ++-- assets/js/sync-ui/config.js | 25 +++--------------------- assets/js/sync-ui/index.js | 8 +++----- includes/classes/Screen/Sync.php | 21 ++++++++++---------- 5 files changed, 22 insertions(+), 44 deletions(-) diff --git a/assets/js/sync-ui/apps/sync.js b/assets/js/sync-ui/apps/sync.js index 7eeb66f239..f316ba7afd 100644 --- a/assets/js/sync-ui/apps/sync.js +++ b/assets/js/sync-ui/apps/sync.js @@ -26,7 +26,7 @@ import { useSyncSettings } from '../provider'; */ export default () => { const { createNotice } = useSettingsScreen(); - const { isComplete, isEpio, isSyncing, lastSyncDateTime, logMessage, startSync } = useSync(); + const { isComplete, isEpio, isSyncing, logMessage, startSync, syncHistory } = useSync(); const { autoIndex } = useSyncSettings(); /** @@ -56,7 +56,7 @@ export default () => { return ( <>

- {lastSyncDateTime + {syncHistory.length ? __( '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', @@ -76,12 +76,12 @@ export default () => { {isSyncing || isComplete ? : null} - {lastSyncDateTime ? : null} + {syncHistory.length ? : null} - {lastSyncDateTime ? ( + {syncHistory.length ? ( { const { isPaused, isSyncing, - lastSyncDateTime, logMessage, pauseSync, resumeSync, startSync, stopSync, + syncHistory, } = useSync(); const { args } = useSyncSettings(); @@ -68,7 +68,7 @@ export default () => { const onSync = async () => { const { put_mapping } = args; - const putMapping = lastSyncDateTime ? put_mapping : true; + const putMapping = syncHistory.length ? put_mapping : true; const syncArgs = { ...args, put_mapping: putMapping }; startSync(syncArgs); diff --git a/assets/js/sync-ui/config.js b/assets/js/sync-ui/config.js index c5f1073593..e48e52aa7e 100644 --- a/assets/js/sync-ui/config.js +++ b/assets/js/sync-ui/config.js @@ -1,26 +1,7 @@ /** * Window dependencies. */ -const { - autoIndex, - apiUrl, - indexMeta, - indexables, - isEpio, - lastSyncDateTime, - lastSyncFailed, - nonce, - postTypes, -} = window.epDash; +const { autoIndex, apiUrl, indexMeta, indexables, isEpio, nonce, postTypes, syncHistory } = + window.epDash; -export { - autoIndex, - apiUrl, - indexables, - indexMeta, - isEpio, - lastSyncDateTime, - lastSyncFailed, - nonce, - postTypes, -}; +export { autoIndex, apiUrl, indexables, indexMeta, isEpio, nonce, postTypes, syncHistory }; diff --git a/assets/js/sync-ui/index.js b/assets/js/sync-ui/index.js index a88a2051c2..9389cb8b52 100644 --- a/assets/js/sync-ui/index.js +++ b/assets/js/sync-ui/index.js @@ -13,12 +13,11 @@ import { apiUrl, autoIndex, indexables, - lastSyncDateTime, - lastSyncFailed, indexMeta, isEpio, - postTypes, nonce, + postTypes, + syncHistory, } from './config'; import { SyncSettingsProvider } from './provider'; import Sync from './apps/sync'; @@ -36,8 +35,7 @@ import './style.css'; const App = () => ( get_indices_comparison(); $indices_missing = count( $indices_comparison['missing_indices'] ) > 0; - $last_sync = ! $indices_missing ? IndexHelper::factory()->get_last_sync() : []; - $post_types = Indexables::factory()->get( 'post' )->get_indexable_post_types(); $post_types = array_values( $post_types ); + $sync_history = ! $indices_missing ? IndexHelper::factory()->get_sync_history() : []; + $data = [ - 'apiUrl' => rest_url( 'elasticpress/v1/sync' ), - 'autoIndex' => isset( $_GET['do_sync'] ) && ( ! defined( 'EP_DASHBOARD_SYNC' ) || EP_DASHBOARD_SYNC ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended - 'indexMeta' => Utils\get_indexing_status(), - 'lastSyncDateTime' => ! empty( $last_sync['end_date_time'] ) ? $last_sync['end_date_time'] : null, - 'lastSyncFailed' => ! empty( $last_sync['failed'] ) || ! empty( $last_sync['errors'] ) ? true : false, - 'indexables' => array_map( fn( $indexable) => [ $indexable->slug, $indexable->labels['plural'] ], $indexables ), - 'isEpio' => Utils\is_epio(), - 'nonce' => wp_create_nonce( 'wp_rest' ), - 'postTypes' => array_map( fn( $post_type ) => [ $post_type, get_post_type_object( $post_type )->labels->name ], $post_types ), + 'apiUrl' => rest_url( 'elasticpress/v1/sync' ), + 'autoIndex' => isset( $_GET['do_sync'] ) && ( ! defined( 'EP_DASHBOARD_SYNC' ) || EP_DASHBOARD_SYNC ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended + 'indexMeta' => Utils\get_indexing_status(), + 'indexables' => array_map( fn( $indexable) => [ $indexable->slug, $indexable->labels['plural'] ], $indexables ), + 'isEpio' => Utils\is_epio(), + 'nonce' => wp_create_nonce( 'wp_rest' ), + 'postTypes' => array_map( fn( $post_type ) => [ $post_type, get_post_type_object( $post_type )->labels->name ], $post_types ), + 'syncHistory' => $sync_history, ]; wp_localize_script( 'ep_sync_scripts', 'epDash', $data ); From 34518fbecf996611f7fb22788671e6de10b562c2 Mon Sep 17 00:00:00 2001 From: Jacob Peattie Date: Fri, 29 Sep 2023 03:51:40 +1000 Subject: [PATCH 3/6] Display sync history. --- assets/js/sync-ui/apps/sync.js | 22 +++-- assets/js/sync-ui/components/icons/error.js | 15 ++++ assets/js/sync-ui/components/icons/success.js | 16 ++++ assets/js/sync-ui/components/previous-sync.js | 82 +++++++++++++++++++ assets/js/sync-ui/components/sync-history.js | 36 ++++++++ assets/js/sync-ui/css/previous-sync.css | 34 ++++++++ assets/js/sync-ui/css/sync-history.css | 11 +++ assets/js/sync-ui/style.css | 6 +- 8 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 assets/js/sync-ui/components/icons/error.js create mode 100644 assets/js/sync-ui/components/icons/success.js create mode 100644 assets/js/sync-ui/components/previous-sync.js create mode 100644 assets/js/sync-ui/components/sync-history.js create mode 100644 assets/js/sync-ui/css/previous-sync.css create mode 100644 assets/js/sync-ui/css/sync-history.css diff --git a/assets/js/sync-ui/apps/sync.js b/assets/js/sync-ui/apps/sync.js index f316ba7afd..06fb401b17 100644 --- a/assets/js/sync-ui/apps/sync.js +++ b/assets/js/sync-ui/apps/sync.js @@ -17,6 +17,7 @@ import Log from '../components/log'; import Objects from '../components/objects'; import Progress from '../components/progress'; import PutMapping from '../components/put-mapping'; +import SyncHistory from '../components/sync-history'; import { useSyncSettings } from '../provider'; /** @@ -82,14 +83,19 @@ export default () => { {syncHistory.length ? ( - - - - + <> + + + + + + + + ) : null} diff --git a/assets/js/sync-ui/components/icons/error.js b/assets/js/sync-ui/components/icons/error.js new file mode 100644 index 0000000000..2d16608d88 --- /dev/null +++ b/assets/js/sync-ui/components/icons/error.js @@ -0,0 +1,15 @@ +import { SVG, Path } from '@wordpress/primitives'; + +export default () => { + return ( + + + + ); +}; diff --git a/assets/js/sync-ui/components/icons/success.js b/assets/js/sync-ui/components/icons/success.js new file mode 100644 index 0000000000..eeb679dc49 --- /dev/null +++ b/assets/js/sync-ui/components/icons/success.js @@ -0,0 +1,16 @@ +import { SVG, Path } from '@wordpress/primitives'; + +export default () => { + return ( + + + + + ); +}; diff --git a/assets/js/sync-ui/components/previous-sync.js b/assets/js/sync-ui/components/previous-sync.js new file mode 100644 index 0000000000..49f6faf409 --- /dev/null +++ b/assets/js/sync-ui/components/previous-sync.js @@ -0,0 +1,82 @@ +/** + * External dependencies. + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies. + */ +import { Icon } from '@wordpress/components'; +import { useMemo, WPElement } from '@wordpress/element'; +import { dateI18n } from '@wordpress/date'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies. + */ +import error from './icons/error'; +import success from './icons/success'; + +/** + * Delete checkbox component. + * + * @param {object} props Component props. + * @param {number} props.failures Number of failed items. + * @param {string} props.method Sync method. + * @param {string} props.stateDatetime Sync end date and time. + * @param {string} props.trigger Sync trigger. + * @returns {WPElement} Sync page component. + */ +export default ({ failures, method, stateDatetime, trigger }) => { + /** + * Whether the sync failed. + */ + const isFailed = useMemo(() => !!failures, [failures]); + + /** + * When the sync was started. + */ + const when = useMemo(() => dateI18n('l F j, Y g:ia', stateDatetime), [stateDatetime]); + + /** + * How the sync completed. + */ + const how = useMemo( + () => + isFailed + ? sprintf(__('Completed with %d errors.', 'elasticpress'), failures) + : __('Completed successfully.', 'elasticpress'), + [isFailed, failures], + ); + + /** + * Why the sync was started. + */ + const why = useMemo(() => { + if (method === 'cli') { + return __('Manual sync from WP CLI', 'elasticpress'); + } + + switch (trigger) { + case 'manual': + default: { + return __('Manual sync from Sync Settings', 'elasticpress'); + } + } + }, [method, trigger]); + + return ( +

+ +
+ {when} — {why} +
+
{how}
+
+ ); +}; diff --git a/assets/js/sync-ui/components/sync-history.js b/assets/js/sync-ui/components/sync-history.js new file mode 100644 index 0000000000..475fd3b75b --- /dev/null +++ b/assets/js/sync-ui/components/sync-history.js @@ -0,0 +1,36 @@ +/** + * WordPress dependencies. + */ +import { WPElement } from '@wordpress/element'; + +/** + * Internal dependencies. + */ +import { useSync } from '../../sync'; +import PreviousSync from './previous-sync'; + +/** + * Delete checkbox component. + * + * @returns {WPElement} Sync page component. + */ +export default () => { + const { syncHistory } = useSync(); + + return ( +
    + {syncHistory.map((s) => { + return ( +
  1. + +
  2. + ); + })} +
+ ); +}; diff --git a/assets/js/sync-ui/css/previous-sync.css b/assets/js/sync-ui/css/previous-sync.css new file mode 100644 index 0000000000..e60c7e3566 --- /dev/null +++ b/assets/js/sync-ui/css/previous-sync.css @@ -0,0 +1,34 @@ +.ep-previous-sync { + align-items: center; + display: grid; + grid-column-gap: 8px; + grid-template-areas: + "icon title" + ". help"; + grid-template-columns: max-content auto; + justify-content: start; + + & svg { + display: block; + grid-area: icon; + } + + &.is-error svg { + fill: var(--ep-sync-color-success); + } + + &.is-success svg { + fill: var(--ep-sync-color-success); + } +} + +.ep-previous-sync__title { + grid-area: title; +} + +.ep-previous-sync__help { + color: rgb(117, 117, 117); + font-size: 12px; + font-style: normal; + grid-area: help; +} diff --git a/assets/js/sync-ui/css/sync-history.css b/assets/js/sync-ui/css/sync-history.css new file mode 100644 index 0000000000..7457f1209f --- /dev/null +++ b/assets/js/sync-ui/css/sync-history.css @@ -0,0 +1,11 @@ +.ep-sync-history { + display: flex; + flex-direction: column; + gap: 16px; + list-style: none; + margin-left: 0; + + & li { + margin: 0; + } +} diff --git a/assets/js/sync-ui/style.css b/assets/js/sync-ui/style.css index 2b1c29e09a..c5cd85c635 100644 --- a/assets/js/sync-ui/style.css +++ b/assets/js/sync-ui/style.css @@ -3,14 +3,16 @@ @import "./css/log.css"; @import "./css/messages.css"; @import "./css/panel.css"; +@import "./css/previous-sync.css"; @import "./css/progress-bar.css"; @import "./css/progress.css"; +@import "./css/sync-history.css"; :root { --ep-sync-color-black: #1a1e24; - --ep-sync-color-error: #b52727; + --ep-sync-color-error: #cc1818; --ep-sync-color-light-grey: #f0f0f0; - --ep-sync-color-success: #46b450; + --ep-sync-color-success: #4ab866; --ep-sync-color-warning: #ffb359; --ep-sync-color-white: #fff; } From ba774826f7d92891942889809bb0d2189cce395b Mon Sep 17 00:00:00 2001 From: Jacob Peattie Date: Fri, 29 Sep 2023 06:31:07 +1000 Subject: [PATCH 4/6] Fix failures appearing as succeeded. --- assets/js/sync-ui/components/sync-history.js | 2 +- assets/js/sync-ui/css/previous-sync.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/js/sync-ui/components/sync-history.js b/assets/js/sync-ui/components/sync-history.js index 475fd3b75b..b474ba4ac1 100644 --- a/assets/js/sync-ui/components/sync-history.js +++ b/assets/js/sync-ui/components/sync-history.js @@ -23,7 +23,7 @@ export default () => { return (
  • Date: Tue, 3 Oct 2023 10:29:55 +1100 Subject: [PATCH 5/6] Support aborted syncs in sync history. --- assets/js/sync/index.js | 63 +++++++++++++++++++++++++++------- includes/classes/REST/Sync.php | 12 ++++++- 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/assets/js/sync/index.js b/assets/js/sync/index.js index ae1ab72907..c6861061ae 100644 --- a/assets/js/sync/index.js +++ b/assets/js/sync/index.js @@ -136,19 +136,6 @@ export const SyncProvider = ({ [setLog], ); - const stopSync = useCallback( - /** - * Stop syncing. - * - * @returns {void} - */ - () => { - updateState({ isPaused: false, isSyncing: false }); - cancelIndex(); - }, - [cancelIndex], - ); - const syncCompleted = useCallback( /** * Set sync state to completed, with success based on the number of @@ -196,6 +183,13 @@ export const SyncProvider = ({ logMessage(response.message, 'error'); } + /** + * If the error has totals, add to the sync history. + */ + const syncHistory = response.totals + ? [response.totals, ...stateRef.current.syncHistory] + : stateRef.current.syncHistory; + /** * Log a final message and update the sync state. */ @@ -204,6 +198,7 @@ export const SyncProvider = ({ updateState({ isFailed: true, isSyncing: false, + syncHistory, }); }, [logMessage], @@ -259,6 +254,23 @@ export const SyncProvider = ({ [], ); + const syncStopped = useCallback( + /** + * Set state for a stopped sync. + * + * @param {object} response Cancel request response. + * @returns {void} + */ + (response) => { + const syncHistory = response.data + ? [response.data, ...stateRef.current.syncHistory] + : stateRef.current.syncHistory; + + updateState({ syncHistory }); + }, + [], + ); + const updateSyncState = useCallback( /** * Handle the response to a request to index. @@ -335,6 +347,18 @@ export const SyncProvider = ({ [syncCompleted, syncFailed, syncInProgress, syncInterrupted, logMessage], ); + const doCancelIndex = useCallback( + /** + * Cancel a sync. + * + * @returns {void} + */ + () => { + cancelIndex().then(syncStopped); + }, + [cancelIndex, syncStopped], + ); + const doIndexStatus = useCallback( /** * Check the status of a sync. @@ -436,6 +460,19 @@ export const SyncProvider = ({ [doIndex], ); + const stopSync = useCallback( + /** + * Stop syncing. + * + * @returns {void} + */ + () => { + updateState({ isPaused: false, isSyncing: false }); + doCancelIndex(); + }, + [doCancelIndex], + ); + /** * Initialize. * diff --git a/includes/classes/REST/Sync.php b/includes/classes/REST/Sync.php index 8b9b398995..2b4c02c5af 100644 --- a/includes/classes/REST/Sync.php +++ b/includes/classes/REST/Sync.php @@ -208,7 +208,17 @@ public function cancel_sync( \WP_REST_Request $request ) { exit; } - IndexHelper::factory()->clear_index_meta(); + $index_meta = IndexHelper::factory()->get_index_meta(); + + if ( $index_meta ) { + IndexHelper::factory()->clear_index_meta(); + + wp_send_json_success( + IndexHelper::factory()->get_last_sync() + ); + + return; + } wp_send_json_success(); } From 20f80d8131760ec46e615991d1da16cf7326da4e Mon Sep 17 00:00:00 2001 From: Jacob Peattie Date: Tue, 3 Oct 2023 10:31:06 +1100 Subject: [PATCH 6/6] Use final_status to display sync status. --- assets/js/sync-ui/components/previous-sync.js | 65 +++++++++++++------ assets/js/sync-ui/components/sync-history.js | 1 + assets/js/sync-ui/css/previous-sync.css | 8 +-- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/assets/js/sync-ui/components/previous-sync.js b/assets/js/sync-ui/components/previous-sync.js index 49f6faf409..94c4031451 100644 --- a/assets/js/sync-ui/components/previous-sync.js +++ b/assets/js/sync-ui/components/previous-sync.js @@ -9,7 +9,7 @@ import classnames from 'classnames'; import { Icon } from '@wordpress/components'; import { useMemo, WPElement } from '@wordpress/element'; import { dateI18n } from '@wordpress/date'; -import { __, sprintf } from '@wordpress/i18n'; +import { __, _n, sprintf } from '@wordpress/i18n'; /** * Internal dependencies. @@ -24,15 +24,11 @@ import success from './icons/success'; * @param {number} props.failures Number of failed items. * @param {string} props.method Sync method. * @param {string} props.stateDatetime Sync end date and time. + * @param {string} props.status Sync status. * @param {string} props.trigger Sync trigger. * @returns {WPElement} Sync page component. */ -export default ({ failures, method, stateDatetime, trigger }) => { - /** - * Whether the sync failed. - */ - const isFailed = useMemo(() => !!failures, [failures]); - +export default ({ failures, method, stateDatetime, status, trigger }) => { /** * When the sync was started. */ @@ -41,38 +37,69 @@ export default ({ failures, method, stateDatetime, trigger }) => { /** * How the sync completed. */ - const how = useMemo( - () => - isFailed - ? sprintf(__('Completed with %d errors.', 'elasticpress'), failures) - : __('Completed successfully.', 'elasticpress'), - [isFailed, failures], - ); + const how = useMemo(() => { + switch (status) { + case 'failed': + return __('Failed.', 'elasticpress'); + case 'with_errors': + return failures + ? sprintf( + _n( + 'Completed with %d error.', + 'Completed with %d errors.', + failures, + 'elasticpress', + ), + failures, + ) + : __('Completed with errors.', 'elasticpress'); + case 'aborted': + return __('Stopped.', 'elasticpress'); + case 'success': + return __('Completed successfully.', 'elasticpress'); + default: + return __('Completed.', 'elasticpress'); + } + }, [failures, status]); /** * Why the sync was started. */ const why = useMemo(() => { if (method === 'cli') { - return __('Manual sync from WP CLI', 'elasticpress'); + return __('Manual sync from WP CLI.', 'elasticpress'); } switch (trigger) { case 'manual': default: { - return __('Manual sync from Sync Settings', 'elasticpress'); + return __('Manual sync from Sync Settings.', 'elasticpress'); } } }, [method, trigger]); + /** + * Whether the sync has errors. + */ + const isError = useMemo(() => { + return status === 'failed' || status === 'with_errors' || status === 'aborted'; + }, [status]); + + /** + * Whether the sync was a success. + */ + const isSuccess = useMemo(() => { + return status === 'success'; + }, [status]); + return (
    - +
    {when} — {why}
    diff --git a/assets/js/sync-ui/components/sync-history.js b/assets/js/sync-ui/components/sync-history.js index b474ba4ac1..8c4288e690 100644 --- a/assets/js/sync-ui/components/sync-history.js +++ b/assets/js/sync-ui/components/sync-history.js @@ -26,6 +26,7 @@ export default () => { failures={s.failed} method={s.method} stateDatetime={s.start_date_time} + status={s.final_status} trigger={s.trigger} />
  • diff --git a/assets/js/sync-ui/css/previous-sync.css b/assets/js/sync-ui/css/previous-sync.css index d82b612252..e02f7301ff 100644 --- a/assets/js/sync-ui/css/previous-sync.css +++ b/assets/js/sync-ui/css/previous-sync.css @@ -13,13 +13,13 @@ grid-area: icon; } - &.is-error svg { - fill: var(--ep-sync-color-error); - } - &.is-success svg { fill: var(--ep-sync-color-success); } + + &.is-error svg { + fill: var(--ep-sync-color-error); + } } .ep-previous-sync__title {