Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display grouped error messages with solutions. #3803

Merged
merged 8 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .wp-env.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"wp-content/plugins/filter-instant-results-args-schema.php": "./tests/cypress/wordpress-files/test-plugins/filter-instant-results-args-schema.php",
"wp-content/plugins/filter-autosuggest-navigate-callback.php": "./tests/cypress/wordpress-files/test-plugins/filter-autosuggest-navigate-callback.php",
"wp-content/plugins/show-comments-and-terms.php": "./tests/cypress/wordpress-files/test-plugins/show-comments-and-terms.php",
"wp-content/plugins/sync-error.php": "./tests/cypress/wordpress-files/test-plugins/sync-error.php",
"wp-content/uploads/content-example.xml": "./tests/cypress/wordpress-files/test-docs/content-example.xml"
}
}
Expand Down
48 changes: 48 additions & 0 deletions assets/js/sync-ui/components/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* WordPress dependencies.
*/
import { safeHTML } from '@wordpress/dom';
import { RawHTML, WPElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies.
*/
import { useSync } from '../../sync';

/**
* Log messages component.
*
* @returns {WPElement} Component.
*/
export default () => {
const { errorCounts } = useSync();

return (
<div className="ep-sync-errors">
{errorCounts.length ? (
<table className="ep-sync-errors__table">
<thead>
<tr>
<th>{__('Count', 'elasticpress')}</th>
<th>{__('Error type', 'elasticpress')}</th>
</tr>
</thead>
{errorCounts.map((e) => (
<tr key={e.type}>
<td className="ep-sync-errors__count">{e.count}</td>
<td>
{e.type}
<RawHTML className="ep-sync-errors__solution">
{safeHTML(e.solution)}
</RawHTML>
</td>
</tr>
))}
</table>
) : (
<p>{__('No errors found in the log.', 'elasticpress')}</p>
)}
</div>
);
};
59 changes: 26 additions & 33 deletions assets/js/sync-ui/components/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,36 @@
*/
import { Button, Flex, FlexItem, TabPanel } from '@wordpress/components';
import { useCopyToClipboard } from '@wordpress/compose';
import { dateI18n } from '@wordpress/date';
import { Fragment, useMemo, WPElement } from '@wordpress/element';
import { useMemo, WPElement } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies.
*/
import { useSettingsScreen } from '../../settings-screen';
import { useSync } from '../../sync';
import Errors from './errors';
import Messages from './messages';

/**
* Sync logs component.
*
* @returns {WPElement} Component.
*/
export default () => {
const { clearLog, log } = useSync();
const { clearLog, errorCounts, log } = useSync();
const { createNotice } = useSettingsScreen();

/**
* The number of errors in the log.
*
* @type {number}
*/
const errorCount = useMemo(
() => errorCounts.reduce((errorCount, e) => errorCount + e.count, 0),
[errorCounts],
);

/**
* The log as plain text.
*
Expand All @@ -31,16 +42,6 @@ export default () => {
return log.map((m) => `${m.dateTime} ${m.message}`).join('\n');
}, [log]);

/**
* Error messages from the log.
*
* @type {Array}
*/
const errorLog = useMemo(
() => log.filter((m) => m.status === 'error' || m.status === 'warning'),
[log],
);

/**
* Handle clicking the clear log button.
*
Expand All @@ -66,40 +67,32 @@ export default () => {
*/
const tabs = [
{
messages: log,
name: 'full',
title: __('Full Log', 'elasticpress'),
title: __('Log', 'elasticpress'),
},
{
messages: errorLog,
name: 'error',
title: sprintf(
/* translators: %d: Error message count. */
__('Errors (%d)', 'elasticpress'),
errorLog.length,
errorCount,
),
},
];

return (
<>
<TabPanel className="ep-sync-log" tabs={tabs}>
{({ messages }) => (
<div className="ep-sync-messages">
{messages.map((m) => (
<Fragment key={m.id}>
<div className="ep-sync-messages__line-number" role="presentation">
{dateI18n('Y-m-d H:i:s', m.dateTime)}
</div>
<div
className={`ep-sync-messages__message ep-sync-messages__message--${m.status}`}
>
{m.message}
</div>
</Fragment>
))}
</div>
)}
{({ name }) => {
switch (name) {
case 'full':
return <Messages />;
case 'error':
return <Errors />;
default:
return null;
}
}}
</TabPanel>
<Flex justify="start">
<FlexItem>
Expand Down
36 changes: 36 additions & 0 deletions assets/js/sync-ui/components/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* WordPress dependencies.
*/
import { dateI18n } from '@wordpress/date';
import { Fragment, WPElement } from '@wordpress/element';

/**
* Internal dependencies.
*/
import { useSync } from '../../sync';

/**
* Log messages component.
*
* @returns {WPElement} Component.
*/
export default () => {
const { log } = useSync();

return (
<div className="ep-sync-messages">
{log.map((m) => (
<Fragment key={m.id}>
<div className="ep-sync-messages__line-number" role="presentation">
{dateI18n('Y-m-d H:i:s', m.dateTime)}
</div>
<div
className={`ep-sync-messages__message ep-sync-messages__message--${m.status}`}
>
{m.message}
</div>
</Fragment>
))}
</div>
);
};
33 changes: 33 additions & 0 deletions assets/js/sync-ui/css/errors.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.ep-sync-errors {
background: var(--ep-sync-color-light-grey);
padding: 8px 16px;
}

.ep-sync-errors__table {
width: 100%;

& thead {
color: rgb(117, 117, 117);
}

& th,
& td {
padding: 8px 16px 8px 0;
text-align: left;
vertical-align: top;
}

& th {
font-weight: 500;
}
}

.ep-sync-errors__count {
font-weight: 700;
}

.ep-sync-errors__solution {
color: rgb(117, 117, 117);
display: block;
font-size: 12px;
}
1 change: 1 addition & 0 deletions assets/js/sync-ui/style.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@import "./css/advanced-control.css";
@import "./css/controls.css";
@import "./css/errors.css";
@import "./css/log.css";
@import "./css/messages.css";
@import "./css/panel.css";
Expand Down
48 changes: 46 additions & 2 deletions assets/js/sync/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ export const SyncProvider = ({
*/
const [log, setLog] = useState([]);

/**
* Error types state.
*/
const [errorCounts, setErrorCounts] = useState([]);

/**
* Sync state.
*/
Expand Down Expand Up @@ -98,6 +103,39 @@ export const SyncProvider = ({
setState((state) => ({ ...state, ...newState }));
};

const countErrors = useCallback(
/**
* Add up the counts for each error type.
*
* @param {object} errors Errors returned by the sync request.
*/
(errors) => {
setErrorCounts((errorCounts) => {
const newErrorCounts = [...errorCounts];

Object.keys(errors).forEach((e) => {
if (!errors[e].solution) {
return;
}

const i = newErrorCounts.findIndex((t) => e === t.type);

if (i !== -1) {
newErrorCounts[i].count += errors[e].count;
} else {
newErrorCounts.push({
...errors[e],
type: e,
});
}
});

return newErrorCounts;
});
},
[],
);

const logMessage = useCallback(
/**
* Log a message.
Expand Down Expand Up @@ -135,6 +173,7 @@ export const SyncProvider = ({
*/
() => {
setLog([]);
setErrorCounts([]);
},
[setLog],
);
Expand Down Expand Up @@ -288,7 +327,7 @@ export const SyncProvider = ({
*/
(response) => {
const { isPaused, isSyncing } = stateRef.current;
const { message, status, totals = [], index_meta: indexMeta } = response.data;
const { errors, message, status, totals = [], index_meta: indexMeta } = response.data;

return new Promise((resolve) => {
/**
Expand All @@ -298,6 +337,10 @@ export const SyncProvider = ({
return;
}

if (errors) {
countErrors(errors);
}

/**
* Stop sync if there is an error.
*/
Expand Down Expand Up @@ -348,7 +391,7 @@ export const SyncProvider = ({
resolve(indexMeta.method);
});
},
[syncCompleted, syncFailed, syncInProgress, syncInterrupted, logMessage],
[syncCompleted, syncFailed, syncInProgress, syncInterrupted, countErrors, logMessage],
);

const doCancelIndex = useCallback(
Expand Down Expand Up @@ -544,6 +587,7 @@ export const SyncProvider = ({
// eslint-disable-next-line react/jsx-no-constructed-context-values
const contextValue = {
clearLog,
errorCounts,
isCli,
isComplete,
isDeleting,
Expand Down
7 changes: 7 additions & 0 deletions includes/classes/ElasticsearchErrorInterpreter.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ public function maybe_suggest_solution_for_es( $error ) {
];
}

if ( preg_match( '/Number of (.*) index errors/', $error, $matches ) ) {
return [
'error' => $error,
'solution' => '',
];
}

if ( Utils\is_epio() ) {
return [
'error' => $error,
Expand Down
Loading
Loading