Integrate JS remote logging package in WooCommerce Admin (#50134)

* Update remote logger to check dev env and whether logging is enabled

* Add changelog

* Integrate JS remote logging package in WooCommerce Admin

* Add changelog

* Update remote logger package

* Update test

* Log error in error boundary

* Update remote logger

* Fix webpack config

* Update log stack format

* Update handleError

* Add init debug
This commit is contained in:
Chi-Hsuan Huang 2024-08-02 10:25:39 +08:00 committed by GitHub
parent a3f4d722b9
commit ed81aa8201
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 280 additions and 47 deletions

View File

@ -28,7 +28,7 @@ The WooCommerce Remote Logging package offers the following features:
2. Log messages or errors: 2. Log messages or errors:
```js ```js
import { log } from '@woocommerce/remote-logging'; import { log, captureException } from '@woocommerce/remote-logging';
// Log an informational message // Log an informational message
log('info', 'User completed checkout', { extra: { orderId: '12345' } }); log('info', 'User completed checkout', { extra: { orderId: '12345' } });
@ -40,7 +40,7 @@ The WooCommerce Remote Logging package offers the following features:
try { try {
// Some operation that might throw // Some operation that might throw
} catch (error) { } catch (error) {
log('error', 'Failed to process order', { extra: { error } }); captureException(error, { extra: { orderId: '12345' } });
} }
``` ```
@ -76,5 +76,6 @@ addFilter(
- `init(config: RemoteLoggerConfig): void`: Initializes the remote logger with the given configuration. - `init(config: RemoteLoggerConfig): void`: Initializes the remote logger with the given configuration.
- `log(severity: LogSeverity, message: string, extraData?: object): Promise<void>`: Logs a message with the specified severity and optional extra data. - `log(severity: LogSeverity, message: string, extraData?: object): Promise<void>`: Logs a message with the specified severity and optional extra data.
- `captureException(error: Error, extraData?: object): void`: Captures an error and sends it to the remote API.
For more detailed information about types and interfaces, refer to the source code and inline documentation. For more detailed information about types and interfaces, refer to the source code and inline documentation.

View File

@ -0,0 +1,4 @@
Significance: patch
Type: update
Update remote logger to check dev env and whether logging is enabled

View File

@ -1,6 +1,7 @@
export { export {
init, init,
log, log,
captureException,
REMOTE_LOGGING_SHOULD_SEND_ERROR_FILTER, REMOTE_LOGGING_SHOULD_SEND_ERROR_FILTER,
REMOTE_LOGGING_ERROR_DATA_FILTER, REMOTE_LOGGING_ERROR_DATA_FILTER,
REMOTE_LOGGING_LOG_ENDPOINT_FILTER, REMOTE_LOGGING_LOG_ENDPOINT_FILTER,

View File

@ -9,7 +9,7 @@ import { applyFilters } from '@wordpress/hooks';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { mergeLogData } from './utils'; import { mergeLogData, isDevelopmentEnvironment } from './utils';
import { LogData, ErrorData, RemoteLoggerConfig } from './types'; import { LogData, ErrorData, RemoteLoggerConfig } from './types';
const debug = debugFactory( 'wc:remote-logging' ); const debug = debugFactory( 'wc:remote-logging' );
@ -80,9 +80,38 @@ export class RemoteLogger {
...extraData, ...extraData,
} ); } );
debug( 'Logging:', logData );
await this.sendLog( logData ); await this.sendLog( logData );
} }
/**
* Logs an error to Logstash.
*
* @param error - The error to log.
* @param extraData - Optional additional data to include in the log.
*
* @return {Promise<void>} - A promise that resolves when the error is logged.
*/
public async error( error: Error, extraData?: Partial< LogData > ) {
if ( this.isRateLimited() ) {
return;
}
const errorData: ErrorData = {
...mergeLogData( DEFAULT_LOG_DATA, {
message: error.message,
severity: 'error',
...extraData,
} ),
trace: this.getFormattedStackFrame(
TraceKit.computeStackTrace( error )
),
};
debug( 'Logging error:', errorData );
await this.sendError( errorData );
}
/** /**
* Initializes error event listeners for catching unhandled errors and unhandled rejections. * Initializes error event listeners for catching unhandled errors and unhandled rejections.
*/ */
@ -115,6 +144,11 @@ export class RemoteLogger {
* @param logData - The log data to be sent. * @param logData - The log data to be sent.
*/ */
private async sendLog( logData: LogData ): Promise< void > { private async sendLog( logData: LogData ): Promise< void > {
if ( isDevelopmentEnvironment ) {
debug( 'Skipping send log in development environment' );
return;
}
const body = new window.FormData(); const body = new window.FormData();
body.append( 'params', JSON.stringify( logData ) ); body.append( 'params', JSON.stringify( logData ) );
@ -142,24 +176,18 @@ export class RemoteLogger {
} }
/** /**
* Handles an error and prepares it for sending to the remote API. * Handles an uncaught error and sends it to the remote API.
* *
* @param error - The error to handle. * @param error - The error to handle.
*/ */
private async handleError( error: Error ) { private async handleError( error: Error ) {
const currentTime = Date.now(); const trace = TraceKit.computeStackTrace( error );
if ( ! this.shouldHandleError( error, trace.stack ) ) {
if ( debug( 'Irrelevant error. Skipping handling.', error );
currentTime - this.lastErrorSentTime <
this.config.errorRateLimitMs
) {
debug( 'Rate limit reached. Skipping send error', error );
return; return;
} }
const trace = TraceKit.computeStackTrace( error ); if ( this.isRateLimited() ) {
if ( ! this.shouldSendError( error, trace.stack ) ) {
debug( 'Skipping error:', error );
return; return;
} }
@ -196,12 +224,15 @@ export class RemoteLogger {
* @param error - The error data to be sent. * @param error - The error data to be sent.
*/ */
private async sendError( error: ErrorData ) { private async sendError( error: ErrorData ) {
if ( isDevelopmentEnvironment ) {
debug( 'Skipping send error in development environment' );
return;
}
const body = new window.FormData(); const body = new window.FormData();
body.append( 'error', JSON.stringify( error ) ); body.append( 'error', JSON.stringify( error ) );
try { try {
debug( 'Sending error to API:', error );
/** /**
* Filters the JS error endpoint URL. * Filters the JS error endpoint URL.
* *
@ -212,6 +243,8 @@ export class RemoteLogger {
'https://public-api.wordpress.com/rest/v1.1/js-error' 'https://public-api.wordpress.com/rest/v1.1/js-error'
) as string; ) as string;
debug( 'Sending error to API:', error );
await window.fetch( endpoint, { await window.fetch( endpoint, {
method: 'POST', method: 'POST',
body, body,
@ -279,13 +312,13 @@ export class RemoteLogger {
} }
/** /**
* Determines whether an error should be sent to the remote API. * Determines whether an error should be handled.
* *
* @param error - The error to check. * @param error - The error to check.
* @param stackFrames - The stack frames of the error. * @param stackFrames - The stack frames of the error.
* @return Whether the error should be sent. * @return Whether the error should be handled.
*/ */
private shouldSendError( private shouldHandleError(
error: Error, error: Error,
stackFrames: TraceKit.StackFrame[] stackFrames: TraceKit.StackFrame[]
) { ) {
@ -310,10 +343,41 @@ export class RemoteLogger {
stackFrames stackFrames
) as boolean; ) as boolean;
} }
private isRateLimited(): boolean {
const currentTime = Date.now();
if (
currentTime - this.lastErrorSentTime <
this.config.errorRateLimitMs
) {
debug( 'Rate limit reached. Skipping send error' );
return true;
}
return false;
}
} }
let logger: RemoteLogger | null = null; let logger: RemoteLogger | null = null;
/**
* Checks if remote logging is enabled and if the logger is initialized.
*
* @return {boolean} - Returns true if remote logging is enabled and the logger is initialized, otherwise false.
*/
function canLog(): boolean {
if ( ! getSetting( 'isRemoteLoggingEnabled', false ) ) {
debug( 'Remote logging is disabled.' );
return false;
}
if ( ! logger ) {
warnLog( 'RemoteLogger is not initialized. Call init() first.' );
return false;
}
return true;
}
/** /**
* Initializes the remote logging and error handlers. * Initializes the remote logging and error handlers.
* This function should be called once at the start of the application. * This function should be called once at the start of the application.
@ -321,9 +385,9 @@ let logger: RemoteLogger | null = null;
* @param config - Configuration object for the RemoteLogger. * @param config - Configuration object for the RemoteLogger.
* *
*/ */
export function init( config: RemoteLoggerConfig ): void { export function init( config: RemoteLoggerConfig ) {
if ( ! window.wcTracks || ! window.wcTracks.isEnabled ) { if ( ! getSetting( 'isRemoteLoggingEnabled', false ) ) {
debug( 'Tracks is not enabled.' ); debug( 'Remote logging is disabled.' );
return; return;
} }
@ -335,38 +399,56 @@ export function init( config: RemoteLoggerConfig ): void {
try { try {
logger = new RemoteLogger( config ); logger = new RemoteLogger( config );
logger.initializeErrorHandlers(); logger.initializeErrorHandlers();
debug( 'RemoteLogger initialized.' );
} catch ( error ) { } catch ( error ) {
errorLog( 'Failed to initialize RemoteLogger:', error ); errorLog( 'Failed to initialize RemoteLogger:', error );
} }
} }
/** /**
* Logs a message or error, respecting rate limiting. * Logs a message or error.
* *
* This function is inefficient because the data goes over the REST API, so use sparingly. * This function is inefficient because the data goes over the REST API, so use sparingly.
* *
* @param severity - The severity of the log. * @param severity - The severity of the log.
* @param message - The message to log. * @param message - The message to log.
* @param extraData - Optional additional data to include in the log. * @param extraData - Optional additional data to include in the log.
*
*/ */
export async function log( export async function log(
severity: Exclude< LogData[ 'severity' ], undefined >, severity: Exclude< LogData[ 'severity' ], undefined >,
message: string, message: string,
extraData?: Partial< Exclude< LogData, 'message' | 'severity' > > extraData?: Partial< Exclude< LogData, 'message' | 'severity' > >
) { ): Promise< void > {
if ( ! window.wcTracks || ! window.wcTracks.isEnabled ) { if ( ! canLog() ) {
debug( 'Tracks is not enabled.' );
return;
}
if ( ! logger ) {
warnLog( 'RemoteLogger is not initialized. Call init() first.' );
return; return;
} }
try { try {
await logger.log( severity, message, extraData ); await logger?.log( severity, message, extraData );
} catch ( error ) { } catch ( error ) {
errorLog( 'Failed to send log:', error ); errorLog( 'Failed to send log:', error );
} }
} }
/**
* Captures an error and sends it to the remote API. Respects the error rate limit.
*
* @param error - The error to capture.
* @param extraData - Optional additional data to include in the log.
*/
export async function captureException(
error: Error,
extraData?: Partial< LogData >
) {
if ( ! canLog() ) {
return;
}
try {
await logger?.error( error, extraData );
} catch ( _error ) {
errorLog( 'Failed to send log:', _error );
}
}

View File

@ -3,10 +3,12 @@
*/ */
import '@wordpress/jest-console'; import '@wordpress/jest-console';
import { addFilter, removeFilter } from '@wordpress/hooks'; import { addFilter, removeFilter } from '@wordpress/hooks';
import { getSetting } from '@woocommerce/settings';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { init, log } from '../'; import { init, log, captureException } from '../';
import { import {
RemoteLogger, RemoteLogger,
REMOTE_LOGGING_SHOULD_SEND_ERROR_FILTER, REMOTE_LOGGING_SHOULD_SEND_ERROR_FILTER,
@ -16,6 +18,12 @@ import {
} from '../remote-logger'; } from '../remote-logger';
import { fetchMock } from './__mocks__/fetch'; import { fetchMock } from './__mocks__/fetch';
jest.mock( '@woocommerce/settings', () => ( {
getSetting: jest.fn().mockReturnValue( {
isRemoteLoggingEnabled: true,
} ),
} ) );
jest.mock( 'tracekit', () => ( { jest.mock( 'tracekit', () => ( {
computeStackTrace: jest.fn().mockReturnValue( { computeStackTrace: jest.fn().mockReturnValue( {
name: 'Error', name: 'Error',
@ -93,6 +101,51 @@ describe( 'RemoteLogger', () => {
} ); } );
} ); } );
describe( 'error', () => {
it( 'should send an error to the API with default data', async () => {
const error = new Error( 'Test error' );
await logger.error( error );
expect( fetchMock ).toHaveBeenCalledWith(
'https://public-api.wordpress.com/rest/v1.1/js-error',
expect.objectContaining( {
method: 'POST',
body: expect.any( FormData ),
} )
);
const formData = fetchMock.mock.calls[0][1].body;
const payload = JSON.parse(formData.get('error'));
expect( payload['message'] ).toBe( 'Test error' );
expect( payload['severity'] ).toBe( 'error' );
expect( payload['trace'] ).toContain( '#1 at testFunction (http://example.com/woocommerce/assets/js/admin/app.min.js:1:1)' );
} );
it( 'should send an error to the API with extra data', async () => {
const error = new Error( 'Test error' );
const extraData = {
severity: 'warning' as const,
tags: ['custom-tag'],
};
await logger.error( error, extraData );
expect( fetchMock ).toHaveBeenCalledWith(
'https://public-api.wordpress.com/rest/v1.1/js-error',
expect.objectContaining( {
method: 'POST',
body: expect.any( FormData ),
} )
);
const formData = fetchMock.mock.calls[0][1].body;
const payload = JSON.parse(formData.get('error'));
expect( payload['message'] ).toBe( 'Test error' );
expect( payload['severity'] ).toBe( 'warning' );
expect( payload['tags'] ).toEqual( ["woocommerce", "js", "custom-tag"]);
expect( payload['trace'] ).toContain( '#1 at testFunction (http://example.com/woocommerce/assets/js/admin/app.min.js:1:1)' );
} );
} );
describe( 'handleError', () => { describe( 'handleError', () => {
it( 'should send an error to the API', async () => { it( 'should send an error to the API', async () => {
const error = new Error( 'Test error' ); const error = new Error( 'Test error' );
@ -164,7 +217,7 @@ describe( 'RemoteLogger', () => {
} ); } );
} ); } );
describe( 'shouldSendError', () => { describe( 'shouldHandleError', () => {
it( 'should return true for WooCommerce errors', () => { it( 'should return true for WooCommerce errors', () => {
const error = new Error( 'Test error' ); const error = new Error( 'Test error' );
const stackFrames = [ const stackFrames = [
@ -176,7 +229,7 @@ describe( 'RemoteLogger', () => {
column: 1, column: 1,
}, },
]; ];
const result = ( logger as any ).shouldSendError( const result = ( logger as any ).shouldHandleError(
error, error,
stackFrames stackFrames
); );
@ -194,7 +247,7 @@ describe( 'RemoteLogger', () => {
column: 1, column: 1,
}, },
]; ];
const result = ( logger as any ).shouldSendError( const result = ( logger as any ).shouldHandleError(
error, error,
stackFrames stackFrames
); );
@ -203,7 +256,7 @@ describe( 'RemoteLogger', () => {
it( 'should return false for WooCommerce errors with no stack frames', () => { it( 'should return false for WooCommerce errors with no stack frames', () => {
const error = new Error( 'Test error' ); const error = new Error( 'Test error' );
const result = ( logger as any ).shouldSendError( error, [] ); const result = ( logger as any ).shouldHandleError( error, [] );
expect( result ).toBe( false ); expect( result ).toBe( false );
} ); } );
@ -214,7 +267,7 @@ describe( 'RemoteLogger', () => {
() => true () => true
); );
const error = new Error( 'Test error' ); const error = new Error( 'Test error' );
const result = ( logger as any ).shouldSendError( error, [] ); const result = ( logger as any ).shouldHandleError( error, [] );
expect( result ).toBe( true ); expect( result ).toBe( true );
} ); } );
} ); } );
@ -255,18 +308,37 @@ describe( 'RemoteLogger', () => {
describe( 'init', () => { describe( 'init', () => {
beforeEach( () => { beforeEach( () => {
jest.clearAllMocks(); jest.clearAllMocks();
window.wcTracks = { isEnabled: true };
( getSetting as jest.Mock ).mockImplementation(
( key, defaultValue ) => {
if ( key === 'isRemoteLoggingEnabled' ) {
return true;
}
return defaultValue;
}
);
} ); } );
it( 'should not initialize the logger if Tracks is not enabled', () => { it( 'should not initialize or log when remote logging is disabled', () => {
window.wcTracks = { isEnabled: false }; // Mock the getSetting function to return false for isRemoteLoggingEnabled
( getSetting as jest.Mock ).mockImplementation(
( key, defaultValue ) => {
if ( key === 'isRemoteLoggingEnabled' ) {
return false;
}
return defaultValue;
}
);
init( { errorRateLimitMs: 1000 } ); init( { errorRateLimitMs: 1000 } );
expect( () => log( 'info', 'Test message' ) ).not.toThrow(); log( 'info', 'Test message' );
expect( fetchMock ).not.toHaveBeenCalled();
} ); } );
it( 'should initialize the logger if Tracks is enabled', () => { it( 'should initialize and log without throwing when remote logging is enabled', () => {
init( { errorRateLimitMs: 1000 } ); init( { errorRateLimitMs: 1000 } );
expect( () => log( 'info', 'Test message' ) ).not.toThrow(); expect( () => log( 'info', 'Test message' ) ).not.toThrow();
expect( fetchMock ).toHaveBeenCalled();
} ); } );
it( 'should not initialize the logger twice', () => { it( 'should not initialize the logger twice', () => {
@ -280,9 +352,33 @@ describe( 'init', () => {
} ); } );
describe( 'log', () => { describe( 'log', () => {
it( 'should not log if Tracks is not enabled', () => { it( 'should not log if remote logging is disabled', () => {
window.wcTracks = { isEnabled: false }; ( getSetting as jest.Mock ).mockImplementation(
( key, defaultValue ) => {
if ( key === 'isRemoteLoggingEnabled' ) {
return false;
}
return defaultValue;
}
);
log( 'info', 'Test message' ); log( 'info', 'Test message' );
expect( fetchMock ).not.toHaveBeenCalled(); expect( fetchMock ).not.toHaveBeenCalled();
} ); } );
} ); } );
describe( 'captureException', () => {
it( 'should not log error if remote logging is disabled', () => {
( getSetting as jest.Mock ).mockImplementation(
( key, defaultValue ) => {
if ( key === 'isRemoteLoggingEnabled' ) {
return false;
}
return defaultValue;
}
);
captureException( new Error( 'Test error' ) );
expect( fetchMock ).not.toHaveBeenCalled();
} );
} );

View File

@ -39,3 +39,5 @@ export function mergeLogData( target: LogData, source: Partial< LogData > ) {
} }
return result; return result;
} }
export const isDevelopmentEnvironment = process.env.NODE_ENV === 'development';

View File

@ -43,6 +43,7 @@ import CurrencyFactory from '@woocommerce/currency';
* Internal dependencies * Internal dependencies
*/ */
import { findComponentMeta } from '~/utils/xstate/find-component'; import { findComponentMeta } from '~/utils/xstate/find-component';
import { initRemoteLogging } from '~/lib/init-remote-logging';
import { IntroOptIn } from './pages/IntroOptIn'; import { IntroOptIn } from './pages/IntroOptIn';
import { import {
UserProfile, UserProfile,
@ -330,6 +331,7 @@ const updateTrackingOption = fromPromise(
) { ) {
window.wcTracks.enable( () => { window.wcTracks.enable( () => {
initializeExPlat(); initializeExPlat();
initRemoteLogging();
resolve(); // resolve the promise only after explat is enabled by the callback resolve(); // resolve the promise only after explat is enabled by the callback
} ); } );
} else { } else {

View File

@ -4,6 +4,7 @@
import { Component, ReactNode, ErrorInfo } from 'react'; import { Component, ReactNode, ErrorInfo } from 'react';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components'; import { Button } from '@wordpress/components';
import { captureException } from '@woocommerce/remote-logging';
/** /**
* Internal dependencies * Internal dependencies
*/ */
@ -34,9 +35,22 @@ export class ErrorBoundary extends Component<
return { hasError: true, error }; return { hasError: true, error };
} }
componentDidCatch( _error: Error, errorInfo: ErrorInfo ) { componentDidCatch( error: Error, errorInfo: ErrorInfo ) {
this.setState( { errorInfo } ); this.setState( { errorInfo } );
// TODO: Log error to error tracking service
// Limit the component stack to 10 calls so we don't send too much data.
const componentStack = errorInfo.componentStack
.trim()
.split( '\n' )
.slice( 0, 10 )
.map( ( line ) => line.trim() );
captureException( error, {
severity: 'critical',
extra: {
componentStack,
},
} );
} }
handleRefresh = () => { handleRefresh = () => {

View File

@ -8,6 +8,12 @@ import {
withCurrentUserHydration, withCurrentUserHydration,
withSettingsHydration, withSettingsHydration,
} from '@woocommerce/data'; } from '@woocommerce/data';
/**
* Internal dependencies
*/
import { initRemoteLogging } from './lib/init-remote-logging';
// Initialize remote logging early to log any errors that occur during initialization.
initRemoteLogging();
/** /**
* Internal dependencies * Internal dependencies

View File

@ -0,0 +1,10 @@
/**
* External dependencies
*/
import { init } from '@woocommerce/remote-logging';
export const initRemoteLogging = () => {
init( {
errorRateLimitMs: 60000, // 1 minute
} );
};

View File

@ -163,6 +163,7 @@
"@woocommerce/number": "workspace:*", "@woocommerce/number": "workspace:*",
"@woocommerce/onboarding": "workspace:*", "@woocommerce/onboarding": "workspace:*",
"@woocommerce/product-editor": "workspace:*", "@woocommerce/product-editor": "workspace:*",
"@woocommerce/remote-logging": "workspace:*",
"@woocommerce/tracks": "workspace:*", "@woocommerce/tracks": "workspace:*",
"@wordpress/babel-preset-default": "^6.17.0", "@wordpress/babel-preset-default": "^6.17.0",
"@wordpress/block-editor": "^9.8.0", "@wordpress/block-editor": "^9.8.0",
@ -305,6 +306,9 @@
"node_modules/@woocommerce/tracks/build", "node_modules/@woocommerce/tracks/build",
"node_modules/@woocommerce/tracks/build-module", "node_modules/@woocommerce/tracks/build-module",
"node_modules/@woocommerce/tracks/build-types", "node_modules/@woocommerce/tracks/build-types",
"node_modules/@woocommerce/remote-logging/build",
"node_modules/@woocommerce/remote-logging/build-module",
"node_modules/@woocommerce/remote-logging/build-types",
"node_modules/@woocommerce/product-editor/build", "node_modules/@woocommerce/product-editor/build",
"node_modules/@woocommerce/product-editor/build-module", "node_modules/@woocommerce/product-editor/build-module",
"node_modules/@woocommerce/product-editor/build-style", "node_modules/@woocommerce/product-editor/build-style",

View File

@ -42,6 +42,7 @@ const wcAdminPackages = [
'onboarding', 'onboarding',
'block-templates', 'block-templates',
'product-editor', 'product-editor',
'remote-logging',
]; ];
// wpAdminScripts are loaded on wp-admin pages outside the context of WooCommerce Admin // wpAdminScripts are loaded on wp-admin pages outside the context of WooCommerce Admin
// See ./client/wp-admin-scripts/README.md for more details // See ./client/wp-admin-scripts/README.md for more details

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Integrate JS remote logging package in WooCommerce Admin

View File

@ -3,6 +3,7 @@ namespace Automattic\WooCommerce\Blocks\Assets;
use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\Hydration; use Automattic\WooCommerce\Blocks\Domain\Services\Hydration;
use Automattic\WooCommerce\Internal\Logging\RemoteLogger;
use Exception; use Exception;
use InvalidArgumentException; use InvalidArgumentException;
@ -89,6 +90,7 @@ class AssetDataRegistry {
'dateFormat' => wc_date_format(), 'dateFormat' => wc_date_format(),
'homeUrl' => esc_url( home_url( '/' ) ), 'homeUrl' => esc_url( home_url( '/' ) ),
'locale' => $this->get_locale_data(), 'locale' => $this->get_locale_data(),
'isRemoteLoggingEnabled' => wc_get_container()->get( RemoteLogger::class )->is_remote_logging_allowed(),
'dashboardUrl' => wc_get_account_endpoint_url( 'dashboard' ), 'dashboardUrl' => wc_get_account_endpoint_url( 'dashboard' ),
'orderStatuses' => $this->get_order_statuses(), 'orderStatuses' => $this->get_order_statuses(),
'placeholderImgSrc' => wc_placeholder_img_src(), 'placeholderImgSrc' => wc_placeholder_img_src(),

View File

@ -279,6 +279,7 @@ class WCAdminAssets {
'wc-navigation', 'wc-navigation',
'wc-block-templates', 'wc-block-templates',
'wc-product-editor', 'wc-product-editor',
'wc-remote-logging',
); );
$scripts_map = array( $scripts_map = array(

View File

@ -3600,6 +3600,9 @@ importers:
'@woocommerce/product-editor': '@woocommerce/product-editor':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/js/product-editor version: link:../../packages/js/product-editor
'@woocommerce/remote-logging':
specifier: workspace:*
version: link:../../packages/js/remote-logging
'@woocommerce/tracks': '@woocommerce/tracks':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/js/tracks version: link:../../packages/js/tracks