PluginProbe
Parse.ly / 3.24.2
Parse.ly v3.24.2
3.24.2 3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 3.20.2 All 106 releases
wp-parsely / src / content-helper / common / content-helper-error.tsx

content-helper-error.tsx in Parse.ly 3.24.2, at src/content-helper/common/content-helper-error.tsx

245 lines 8.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * WordPress dependencies
3 */
4 import { __ } from '@wordpress/i18n';
5
6 /**
7 * Internal dependencies
8 */
9 import { dispatch } from '@wordpress/data';
10 import {
11 ContentHelperErrorMessage,
12 ContentHelperErrorMessageProps,
13 EmptyCredentialsMessage,
14 } from './content-helper-error-message';
15
16 /**
17 * Enumeration of all the possible errors that might get thrown or processed by
18 * Content Intelligence during error handling. All errors thrown by Content
19 * Intelligence should start with a "ch_" prefix.
20 */
21 export enum ContentHelperErrorCode {
22 AccessToFeatureDisabled = 'ch_access_to_feature_disabled',
23 FetchError = 'fetch_error', // apiFetch() failure, possibly caused by ad blocker.
24 HttpRequestFailed = 'http_request_failed', // Parse.ly API is unreachable.
25 ParselyAborted = 'ch_parsely_aborted', // The request was aborted.
26 ParselyApiForbidden = '403',
27 ParselyApiResponseContainsError = 'ch_response_contains_error',
28 ParselyApiReturnedNoData = 'ch_parsely_api_returned_no_data',
29 ParselyApiReturnedTooManyResults = 'ch_parsely_api_returned_too_many_results',
30 PluginCredentialsNotSetMessageDetected = 'parsely_credentials_not_set_message_detected',
31 PluginSettingsApiSecretNotSet = 'parsely_api_secret_not_set',
32 PluginSettingsSiteIdNotSet = 'parsely_site_id_not_set',
33 PostIsNotPublished = 'ch_post_not_published',
34 UnknownError = 'ch_unknown_error',
35
36 // Suggestions API.
37 ParselySuggestionsApiAuthUnavailable = 'AUTH_UNAVAILABLE', // HTTP Code 503.
38 ParselySuggestionsApiNoAuthentication = 'NO_AUTHENTICATION', // HTTP Code 401.
39 ParselySuggestionsApiNoAuthorization = 'NO_AUTHORIZATION', // HTTP Code 403.
40 ParselySuggestionsApiNoData = 'NO_DATA', // HTTP Code 507.
41 ParselySuggestionsApiNoDataManualLinking = 'NO_DATA_MANUAL_LINKING', // HTTP Code 507.
42 ParselySuggestionsApiOpenAiError = 'OPENAI_ERROR', // HTTP Code 500.
43 ParselySuggestionsApiOpenAiSchema = 'OPENAI_SCHEMA', // HTTP Code 507.
44 ParselySuggestionsApiOpenAiUnavailable = 'OPENAI_UNAVAILABLE', // HTTP Code 500.
45 ParselySuggestionsApiResponseValidationError = 'RESPONSE_VALIDATION_ERROR', // HTTP Code 500.
46 ParselySuggestionsApiSchemaError = 'SCHEMA_ERROR', // HTTP Code 422.
47 ParselySuggestionsInvalidRequest = 'INVALID_REQUEST', // HTTP Code 400.
48 }
49
50 /**
51 * Extends the standard JS Error class for use with Content Intelligence.
52 *
53 * @see https://github.com/microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work
54 */
55 export class ContentHelperError extends Error {
56 public code: ContentHelperErrorCode;
57 protected hint: string | null = null;
58 public retryFetch: boolean;
59
60 constructor(
61 message: string,
62 code: ContentHelperErrorCode,
63 messagePrefix: string = __( 'Error:', 'wp-parsely' )
64 ) {
65 // Avoid double message prefix.
66 if ( message.startsWith( messagePrefix ) ) {
67 messagePrefix = '';
68 }
69
70 // If the error code is not a string, convert it to a string.
71 if ( typeof code !== 'string' ) {
72 code = String( code ) as ContentHelperErrorCode;
73 }
74
75 // Initialization.
76 super( messagePrefix.length > 0 ? `${ messagePrefix } ${ message }` : message );
77 this.name = this.constructor.name;
78 this.code = code;
79
80 // Errors for which we should retry fetch operations. We call them soft
81 // errors as they will not terminate execution.
82 const softErrors: Array<ContentHelperErrorCode> = [
83 // Generic HTTP/Fetch errors.
84 ContentHelperErrorCode.FetchError,
85 ContentHelperErrorCode.HttpRequestFailed,
86
87 // Suggestions API errors that can be caused by network issues or
88 // due to the non-deterministic nature of LLMs.
89 ContentHelperErrorCode.ParselySuggestionsApiAuthUnavailable,
90 ContentHelperErrorCode.ParselySuggestionsApiOpenAiError,
91 ContentHelperErrorCode.ParselySuggestionsApiOpenAiSchema,
92 ContentHelperErrorCode.ParselySuggestionsApiOpenAiUnavailable,
93 ContentHelperErrorCode.ParselySuggestionsApiSchemaError,
94 ];
95
96 this.retryFetch = softErrors.includes( this.code );
97
98 // Set the prototype explicitly.
99 Object.setPrototypeOf( this, ContentHelperError.prototype );
100
101 this.CustomizeErrorMessaging();
102 }
103
104 /**
105 * Customizes error messages and hints for clarity, or to provide tailored
106 * messages when specific errors occur.
107 *
108 * This also allows for the internationalization of errors/hints.
109 *
110 * @since 3.20.8
111 */
112 protected CustomizeErrorMessaging(): void {
113 // Errors that need rephrasing.
114 if ( this.code === ContentHelperErrorCode.AccessToFeatureDisabled ) {
115 this.message = __(
116 'Access to this feature is disabled by the site\'s administration.',
117 'wp-parsely'
118 );
119 } else if ( this.code === ContentHelperErrorCode.ParselySuggestionsApiNoAuthorization ) {
120 this.message = __(
121 'This AI-powered feature is opt-in. To gain access, please submit a request ' +
122 '<a href="https://wpvip.com/content-helper/#content-helper-form" target="_blank" rel="noopener">here</a>.',
123 'wp-parsely'
124 );
125 } else if ( this.code === ContentHelperErrorCode.ParselySuggestionsApiOpenAiError ||
126 this.code === ContentHelperErrorCode.ParselySuggestionsApiOpenAiUnavailable ) {
127 this.message = __(
128 'The Parse.ly API returned an internal server error. Please retry with a different input, or try again later.',
129 'wp-parsely'
130 );
131 } else if ( this.code === ContentHelperErrorCode.HttpRequestFailed &&
132 this.message.includes( 'cURL error 28' ) ) {
133 this.message = __(
134 'The Parse.ly API did not respond in a timely manner. Please try again later.',
135 'wp-parsely'
136 );
137 } else if ( this.code === ContentHelperErrorCode.ParselySuggestionsApiSchemaError ||
138 this.code === ContentHelperErrorCode.ParselySuggestionsInvalidRequest
139 ) {
140 this.message = __(
141 'The Parse.ly API returned a validation error. Please try again with different parameters.',
142 'wp-parsely'
143 );
144 } else if ( this.code === ContentHelperErrorCode.ParselySuggestionsApiNoData ||
145 this.code === ContentHelperErrorCode.ParselySuggestionsApiNoDataManualLinking
146 ) {
147 this.message = __(
148 'The Parse.ly API couldn\'t find any relevant data to fulfill the request.',
149 'wp-parsely'
150 );
151 } else if ( this.code === ContentHelperErrorCode.ParselySuggestionsApiOpenAiSchema ||
152 this.code === ContentHelperErrorCode.ParselySuggestionsApiResponseValidationError
153 ) {
154 this.message = __(
155 'The Parse.ly API returned an incorrect response.',
156 'wp-parsely'
157 );
158 } else if ( this.code === ContentHelperErrorCode.ParselySuggestionsApiAuthUnavailable ) {
159 this.message = __(
160 'The Parse.ly API is currently unavailable. Please try again later.',
161 'wp-parsely'
162 );
163 }
164
165 // Errors that need a hint.
166 if ( this.code === ContentHelperErrorCode.FetchError ) {
167 this.hint = this.Hint( __(
168 'This error can sometimes be caused by ad-blockers or browser tracking protections. Please add this site to any applicable allow lists and try again.',
169 'wp-parsely'
170 ) );
171 }
172 if ( this.code === ContentHelperErrorCode.ParselyApiForbidden ||
173 this.code === ContentHelperErrorCode.ParselySuggestionsApiNoAuthentication ) {
174 this.hint = this.Hint( __(
175 "Please ensure that the Site ID and API Secret given in the plugin's settings are correct.",
176 'wp-parsely'
177 ) );
178 }
179 if ( this.code === ContentHelperErrorCode.HttpRequestFailed ) {
180 this.hint = this.Hint( __(
181 'The Parse.ly API cannot be reached. Please verify that you are online.',
182 'wp-parsely'
183 ) );
184 }
185 }
186
187 /**
188 * Shows a hint in order to provide clarity in regards to the error.
189 *
190 * @param {string} hint The hint to display
191 */
192 protected Hint( hint: string ): string {
193 return `<p className="content-helper-error-message-hint" data-testid="content-helper-error-message-hint"><strong>${ __( 'Hint:', 'wp-parsely' ) }</strong> ${ hint }</p>`;
194 }
195
196 /**
197 * Renders the error's message.
198 *
199 * @param {ContentHelperErrorMessageProps|null} props The props needed for the function.
200 *
201 * @return {import('react').JSX.Element} The resulting JSX Element.
202 */
203 public Message( props: ContentHelperErrorMessageProps|null = null ): React.JSX.Element {
204 // Handle cases where credentials are not set.
205 const CredentialsNotSetErrorCodes = [
206 ContentHelperErrorCode.PluginCredentialsNotSetMessageDetected,
207 ContentHelperErrorCode.PluginSettingsSiteIdNotSet,
208 ContentHelperErrorCode.PluginSettingsApiSecretNotSet,
209 ];
210 if ( CredentialsNotSetErrorCodes.includes( this.code ) ) {
211 return EmptyCredentialsMessage( props );
212 }
213
214 return (
215 <ContentHelperErrorMessage
216 className={ props?.className }
217 testId="error">
218 { `<p>${ this.message }</p>${ this.hint ? this.hint : '' }` }
219 </ContentHelperErrorMessage>
220 );
221 }
222
223 /**
224 * Creates an error Snackbar Notice, unless the error message contains links.
225 *
226 * @since 3.16.0
227 *
228 * @link https://github.com/Parsely/wp-parsely/issues/2424#issuecomment-2196196232
229 */
230 public createErrorSnackbar(): void {
231 if ( /<a.*?>/.test( this.message ) ) {
232 return;
233 }
234
235 // @ts-ignore
236 dispatch( 'core/notices' ).createNotice(
237 'error',
238 this.message,
239 {
240 type: 'snackbar',
241 }
242 );
243 }
244 }
245