diff --git a/.env.dist b/.env.dist index 1f2ec0812..fd9c8e30a 100644 --- a/.env.dist +++ b/.env.dist @@ -12,3 +12,6 @@ DEFAULT_SERVER= PRIMARY_COLOR= SMARTLOOK_PROJECT_KEY= DEBUG_REDUX_LOGGER_LEVEL=log +DATADOG_ENABLED=0 +DATADOG_CLIENT_TOKEN= +DATADOG_APPLICATION_ID= diff --git a/.github/workflows/fastlane_android.yml b/.github/workflows/fastlane_android.yml index 336c143d5..541472939 100644 --- a/.github/workflows/fastlane_android.yml +++ b/.github/workflows/fastlane_android.yml @@ -89,6 +89,8 @@ jobs: - name: Create .env file env: SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + DATADOG_CLIENT_TOKEN: ${{ secrets.DATADOG_CLIENT_TOKEN }} + DATADOG_APPLICATION_ID: ${{ secrets.DATADOG_APPLICATION_ID }} GOOGLE_MAPS_BROWSER_KEY: ${{ secrets.GOOGLE_MAPS_BROWSER_KEY }} GOOGLE_MAPS_ANDROID_KEY: ${{ secrets.GOOGLE_MAPS_ANDROID_KEY }} TRANSISTORSOFT_LICENSE_KEY: ${{ secrets.TRANSISTORSOFT_LICENSE_KEY }} @@ -104,6 +106,9 @@ jobs: echo COUNTLY_APP_KEY= >> .env echo COUNTLY_SALT= >> .env echo SENTRY_DSN="$SENTRY_DSN" >> .env + echo DATADOG_ENABLED="1" >> .env + echo DATADOG_CLIENT_TOKEN="$DATADOG_CLIENT_TOKEN" >> .env + echo DATADOG_APPLICATION_ID="$DATADOG_APPLICATION_ID" >> .env echo GOOGLE_MAPS_BROWSER_KEY="$GOOGLE_MAPS_BROWSER_KEY" >> .env echo GOOGLE_MAPS_ANDROID_KEY="$GOOGLE_MAPS_ANDROID_KEY" >> .env echo TRANSISTORSOFT_LICENSE_KEY="$TRANSISTORSOFT_LICENSE_KEY" >> .env diff --git a/.github/workflows/fastlane_ios.yml b/.github/workflows/fastlane_ios.yml index 6a664049c..5e6c46c26 100644 --- a/.github/workflows/fastlane_ios.yml +++ b/.github/workflows/fastlane_ios.yml @@ -83,6 +83,8 @@ jobs: - name: Create .env file env: SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + DATADOG_CLIENT_TOKEN: ${{ secrets.DATADOG_CLIENT_TOKEN }} + DATADOG_APPLICATION_ID: ${{ secrets.DATADOG_APPLICATION_ID }} GOOGLE_MAPS_BROWSER_KEY: ${{ secrets.GOOGLE_MAPS_BROWSER_KEY }} GOOGLE_MAPS_ANDROID_KEY: ${{ secrets.GOOGLE_MAPS_ANDROID_KEY }} TRANSISTORSOFT_LICENSE_KEY: ${{ secrets.TRANSISTORSOFT_LICENSE_KEY }} @@ -97,6 +99,9 @@ jobs: echo COUNTLY_APP_KEY= >> .env echo COUNTLY_SALT= >> .env echo SENTRY_DSN="$SENTRY_DSN" >> .env + echo DATADOG_ENABLED="1" >> .env + echo DATADOG_CLIENT_TOKEN="$DATADOG_CLIENT_TOKEN" >> .env + echo DATADOG_APPLICATION_ID="$DATADOG_APPLICATION_ID" >> .env echo GOOGLE_MAPS_BROWSER_KEY="$GOOGLE_MAPS_BROWSER_KEY" >> .env echo GOOGLE_MAPS_ANDROID_KEY="$GOOGLE_MAPS_ANDROID_KEY" >> .env echo TRANSISTORSOFT_LICENSE_KEY="$TRANSISTORSOFT_LICENSE_KEY" >> .env diff --git a/android/app/build.gradle b/android/app/build.gradle index 0b2c0ea7d..8f4dfa60f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,9 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled true - versionCode 213 - versionName "2.22.0" + // No need to increment the versionCode, it will be automatically incremented by the fastlane + versionCode 215 + versionName "2.24.0" manifestPlaceholders = [ tipsiStripeRedirectScheme: "coopcycle", diff --git a/ios/CoopCycle/Info.plist b/ios/CoopCycle/Info.plist index 4bc1e5f94..e0cdbba5b 100644 --- a/ios/CoopCycle/Info.plist +++ b/ios/CoopCycle/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.22.0 + 2.24.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6936035c1..8f92e728e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -14,6 +14,29 @@ PODS: - React - CountlyReactNative/NotificationService (22.06.1): - React + - DatadogCore (2.14.2): + - DatadogInternal (= 2.14.2) + - DatadogCrashReporting (2.14.2): + - DatadogInternal (= 2.14.2) + - PLCrashReporter (~> 1.11.2) + - DatadogInternal (2.14.2) + - DatadogLogs (2.14.2): + - DatadogInternal (= 2.14.2) + - DatadogRUM (2.14.2): + - DatadogInternal (= 2.14.2) + - DatadogSDKReactNative (2.4.3): + - DatadogCore (~> 2.14.1) + - DatadogCrashReporting (~> 2.14.1) + - DatadogLogs (~> 2.14.1) + - DatadogRUM (~> 2.14.1) + - DatadogTrace (~> 2.14.1) + - DatadogWebViewTracking (~> 2.14.1) + - React-Core + - DatadogTrace (2.14.2): + - DatadogInternal (= 2.14.2) + - OpenTelemetrySwiftApi (= 1.6.0) + - DatadogWebViewTracking (2.14.2): + - DatadogInternal (= 2.14.2) - DoubleConversion (1.1.6) - EXConstants (15.4.5): - ExpoModulesCore @@ -191,6 +214,8 @@ PODS: - nanopb/encode (= 2.30909.1) - nanopb/decode (2.30909.1) - nanopb/encode (2.30909.1) + - OpenTelemetrySwiftApi (1.6.0) + - PLCrashReporter (1.11.2) - PromisesObjC (2.4.0) - PromisesSwift (2.4.0): - PromisesObjC (= 2.4.0) @@ -1401,6 +1426,7 @@ PODS: DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - CountlyReactNative (from `../node_modules/countly-sdk-react-native-bridge`) + - "DatadogSDKReactNative (from `../node_modules/@datadog/mobile-react-native`)" - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - EXConstants (from `../node_modules/expo-constants/ios`) - EXFont (from `../node_modules/expo-font/ios`) @@ -1501,6 +1527,13 @@ SPEC REPOS: trunk: - AppAuth - CocoaLumberjack + - DatadogCore + - DatadogCrashReporting + - DatadogInternal + - DatadogLogs + - DatadogRUM + - DatadogTrace + - DatadogWebViewTracking - FBAEMKit - FBSDKCoreKit - FBSDKCoreKit_Basics @@ -1525,6 +1558,8 @@ SPEC REPOS: - IQKeyboardManagerSwift - libevent - nanopb + - OpenTelemetrySwiftApi + - PLCrashReporter - PromisesObjC - PromisesSwift - Sentry @@ -1545,6 +1580,8 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" CountlyReactNative: :path: "../node_modules/countly-sdk-react-native-bridge" + DatadogSDKReactNative: + :path: "../node_modules/@datadog/mobile-react-native" DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" EXConstants: @@ -1734,6 +1771,14 @@ SPEC CHECKSUMS: boost: d3f49c53809116a5d38da093a8aa78bf551aed09 CocoaLumberjack: 543c79c114dadc3b1aba95641d8738b06b05b646 CountlyReactNative: f544f34347bc635e2951e2797d4750e66f886a09 + DatadogCore: a3429f62b7da0e715e179833b3daa0203ba2a5ad + DatadogCrashReporting: 3b5e496c1f61971a93b9a1bff3312297be03f031 + DatadogInternal: 11798eea970f195ad720168f9a51712e71affe33 + DatadogLogs: 4192cc1979467ce0c2521d73ba92b6197e0c7f06 + DatadogRUM: f732009eba7a848d6e2df9ef12944e30f0e94ba2 + DatadogSDKReactNative: f418f95923dc78ad7b4dcd1710804f1672d8abfa + DatadogTrace: 5687191622d91152f10616e206500fe5acd8a342 + DatadogWebViewTracking: cb818ee5943de63940b4f536320361c17af0f7b1 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 EXConstants: 988aa430ca0f76b43cd46b66e7fae3287f9cc2fc EXFont: f20669cb266ef48b004f1eb1f2b20db96cd1df9f @@ -1771,6 +1816,8 @@ SPEC CHECKSUMS: IQKeyboardManagerSwift: 12d89768845bb77b55cc092ecc2b1f9370f06b76 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 + OpenTelemetrySwiftApi: 657da8071c2908caecce11548e006f779924ff9c + PLCrashReporter: 499c53b0104f95c302d94fd723ebb03c56d9bac8 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 diff --git a/package.json b/package.json index 254d2074c..dd959e653 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ }, "dependencies": { "@babel/helper-get-function-arity": "^7.16.7", + "@datadog/mobile-react-native": "^2.4.3", + "@datadog/mobile-react-navigation": "^2.4.3", "@heasy/react-native-sunmi-printer": "git+https://github.com/Surile/react-native-sunmi-printer.git#666f6f76ce737faf7a500b88a08ad10da68c5967", "@invertase/react-native-apple-authentication": "^2.2.2", "@mapbox/polyline": "^1.1.1", diff --git a/src/App.js b/src/App.js index 7ba03c0d2..29e46a097 100644 --- a/src/App.js +++ b/src/App.js @@ -61,6 +61,7 @@ import { AccountResetPasswordNewPasswordScreen, } from './navigation/navigators/AccountNavigator'; import { nativeBaseTheme } from './styles/theme'; +import { DatadogWrapper, navigationContainerOnReady } from './Datadog'; LogBox.ignoreLogs([ 'Warning: isMounted(...) is deprecated in plain JavaScript React classes.', @@ -86,6 +87,7 @@ function getCurrentRouteName() { function onReady() { routeNameRef.current = getCurrentRouteName(); + navigationContainerOnReady(navigationRef); } /** @@ -143,39 +145,41 @@ const App = () => { }, []); return ( - - - - - } - persistor={persistor}> - - - - - - - - { - DropdownHolder.setDropdown(ref); - }} - /> - - - - - - - - + + + + + + } + persistor={persistor}> + + + + + + + + { + DropdownHolder.setDropdown(ref); + }} + /> + + + + + + + + + ); }; diff --git a/src/Datadog.js b/src/Datadog.js new file mode 100644 index 000000000..9463f28d9 --- /dev/null +++ b/src/Datadog.js @@ -0,0 +1,159 @@ +import React from 'react'; +import { + BatchSize, + DatadogProvider, + DatadogProviderConfiguration, + DdLogs, + DdSdkReactNative, + SdkVerbosity, + UploadFrequency, +} from '@datadog/mobile-react-native'; +import Config from 'react-native-config'; +import { DdRumReactNavigationTracking } from '@datadog/mobile-react-navigation'; + +const enabled = Config.DATADOG_ENABLED === '1'; + +const clientToken = Config.DATADOG_CLIENT_TOKEN; +if (enabled && !clientToken) { + throw new Error('DATADOG_CLIENT_TOKEN is required'); +} + +const applicationId = Config.DATADOG_APPLICATION_ID; +if (enabled && !applicationId) { + throw new Error('DATADOG_APPLICATION_ID is required'); +} + +const datadogConfig = new DatadogProviderConfiguration( + clientToken, + __DEV__ ? 'dev' : 'prod', + applicationId, + true, // track User interactions (e.g.: Tap on buttons. You can use 'accessibilityLabel' element property to give tap action the name, otherwise element type will be reported) + true, // track XHR Resources + true, // track Errors +); +// Optional: Select your Datadog website (one of "US1", "EU1", "US3", "US5", "AP1" or "GOV") +datadogConfig.site = 'US1'; +// Optional: Enable JavaScript long task collection +datadogConfig.longTaskThresholdMs = 100; +// Optional: enable or disable native crash reports +datadogConfig.nativeCrashReportEnabled = true; +// Optional: Sample RUM sessions (% of session are sent to Datadog. Default is 100%). +datadogConfig.sessionSamplingRate = __DEV__ ? 100 : 20; +// Optional: Sample tracing integrations for network calls between your app and your backend (% of calls to your instrumented backend are linked from the RUM view to the APM view. Default is 20%) +// You need to specify the hosts of your backends to enable tracing with these backends +datadogConfig.resourceTracingSamplingRate = __DEV__ ? 100 : 20; +datadogConfig.telemetrySampleRate = 0; +datadogConfig.firstPartyHosts = ['coopcycle.org']; // matches 'example.com' and subdomains like 'api.example.com' +// Optional: let the SDK print internal logs above or equal to the provided level. Default is undefined (meaning no logs) +datadogConfig.verbosity = __DEV__ ? SdkVerbosity.DEBUG : SdkVerbosity.WARN; + +if (__DEV__) { + // Optional: Send data more frequently + datadogConfig.uploadFrequency = UploadFrequency.FREQUENT; + // Optional: Send smaller batches of data + datadogConfig.batchSize = BatchSize.SMALL; +} + +export function DatadogWrapper({ children }) { + if (!enabled) { + return <>{children}; + } + + return ( + {children} + ); +} + +const viewNamePredicate = function customViewNamePredicate(route, trackedName) { + // return custom view name or null to use the previous RUM view + return trackedName; +}; + +export function navigationContainerOnReady(navigationRef) { + if (!enabled) { + return; + } + + DdRumReactNavigationTracking.startTrackingViews( + navigationRef.current, + viewNamePredicate, + ); +} + +export const DatadogSdk = { + setAttributes(attributes) { + if (!enabled) { + console.debug('(disabled) DatadogSdk.setAttributes', attributes); + return; + } + + DdSdkReactNative.setAttributes(attributes); + }, + + setUser(user) { + if (!enabled) { + console.debug('(disabled) DatadogSdk.setUser', user); + return; + } + + DdSdkReactNative.setUser(user); + }, +}; + +export const DatadogLogger = { + /** + * Send a log with debug level. + * @param message: The message to send. + * @param context: The additional context to send. + */ + debug(message, context) { + if (!enabled) { + console.debug(message, context); + return; + } + + DdLogs.debug(message, context); + }, + + /** + * Send a log with info level. + * @param message: The message to send. + * @param context: The additional context to send. + */ + info(message, context) { + if (!enabled) { + console.info(message, context); + return; + } + + DdLogs.info(message, context); + }, + + /** + * Send a log with warn level. + * @param message: The message to send. + * @param context: The additional context to send. + */ + warn(message, context) { + if (!enabled) { + console.warn(message, context); + return; + } + + DdLogs.warn(message, context); + }, + + /** + * Send a log with error level. + * @param message: The message to send. + * @param context: The additional context to send. + */ + error(message, context) { + if (!enabled) { + console.error(message, context); + return; + } + + DdLogs.error(message, context); + }, +}; diff --git a/src/navigation/restaurant/Order.js b/src/navigation/restaurant/Order.js index 7fea23185..fb7e07a4b 100644 --- a/src/navigation/restaurant/Order.js +++ b/src/navigation/restaurant/Order.js @@ -20,6 +20,7 @@ import { selectIsPrinterConnected, selectPrinter, } from '../../redux/Restaurant/selectors'; +import { DatadogLogger } from '../../Datadog'; const OrderNotes = ({ order }) => { if (order.notes) { @@ -57,7 +58,14 @@ class OrderScreen extends Component { screen: 'RestaurantPrinter', }) } - printOrder={() => this.props.printOrder(this.props.order)} + printOrder={() => { + const order = this.props.order; + DatadogLogger.info('printing ticket', { + trigger: 'manual', + orderId: order.id, + }); + this.props.printOrder(order); + }} /> @@ -87,18 +95,25 @@ class OrderScreen extends Component { } /> )} - {canEdit && (order.state === 'accepted' || order.state === 'started' || order.state === 'ready') && ( - - this.props.navigation.navigate('RestaurantOrderCancel', { order }) - } - onPressDelay={() => - this.props.navigation.navigate('RestaurantOrderDelay', { order }) - } - onPressFulfill={() => this.fulfillOrder(order)} - /> - )} + {canEdit && + (order.state === 'accepted' || + order.state === 'started' || + order.state === 'ready') && ( + + this.props.navigation.navigate('RestaurantOrderCancel', { + order, + }) + } + onPressDelay={() => + this.props.navigation.navigate('RestaurantOrderDelay', { + order, + }) + } + onPressFulfill={() => this.fulfillOrder(order)} + /> + )} ); } diff --git a/src/navigation/restaurant/components/OrdersToPrintQueue.js b/src/navigation/restaurant/components/OrdersToPrintQueue.js index b5cebaa3e..f6fb8de15 100644 --- a/src/navigation/restaurant/components/OrdersToPrintQueue.js +++ b/src/navigation/restaurant/components/OrdersToPrintQueue.js @@ -11,6 +11,7 @@ import { } from '../../../redux/Restaurant/selectors'; import { printOrderById } from '../../../redux/Restaurant/actions'; import { useNavigation } from '@react-navigation/native'; +import { DatadogLogger } from '../../../Datadog'; function usePrinter() { const connected = useSelector(selectIsPrinterConnected); @@ -29,12 +30,20 @@ function usePrinter() { return; } + const orderId = orderIdsToPrint[0]; + if (!connected) { - console.warn('Printer is not connected'); + DatadogLogger.info('printer is not connected', { + trigger: 'auto', + orderId, + }); return; } - const orderId = orderIdsToPrint[0]; + DatadogLogger.info('printing ticket', { + trigger: 'auto', + orderId, + }); dispatch(printOrderById(orderId)); }, [printingOrderId, orderIdsToPrint, connected, dispatch]); diff --git a/src/redux/App/actions.js b/src/redux/App/actions.js index 8330b83d3..144f886da 100644 --- a/src/redux/App/actions.js +++ b/src/redux/App/actions.js @@ -25,6 +25,7 @@ import { selectIsAuthenticated, selectResumeCheckoutAfterActivation, } from './selectors'; +import { DatadogSdk } from '../../Datadog' /* * Action Types @@ -221,6 +222,9 @@ export const stopSound = createAction('STOP_SOUND'); function setBaseURL(baseURL) { return (dispatch, getState) => { dispatch(_setBaseURL(baseURL)); + DatadogSdk.setAttributes({ + 'instance_url': baseURL, + }) tracker.setUserProperty(userProperty.server, baseURL); }; } @@ -241,7 +245,7 @@ function authenticationSuccess(user) { await dispatch(loadAddresses()); await dispatch(assignAllCarts()); - setRolesProperty(user); + updateUserProperties(user); tracker.logEvent( analyticsEvent.user.login._category, analyticsEvent.user.login.success, @@ -265,28 +269,27 @@ function logoutSuccess() { return (dispatch, getState) => { dispatch(_logoutSuccess()); dispatch(updateCarts({})); - setRolesProperty(null); + updateUserProperties(null); }; } -function setRolesProperty(user) { +function updateUserProperties(user) { let roles; - if (user !== null && user.roles !== null) { - roles = user.roles.slice(); - roles.sort(); - } else { - roles = []; - } - - if (user) { - if (roles.length > 0) { - tracker.setUserProperty(userProperty.roles, roles.toString()); + if (user !== null && user.username !== null) { + if (user.roles !== null) { + roles = user.roles.slice(); + roles.sort(); } else { - tracker.setUserProperty(userProperty.roles, 'ROLE_USER'); + roles = ['ROLE_USER']; } } else { - tracker.setUserProperty(userProperty.roles, 'ROLE_AD_HOC_CUSTOMER'); + roles = ['ROLE_AD_HOC_CUSTOMER']; } + + DatadogSdk.setUser({ + roles: roles.toString(), + }) + tracker.setUserProperty(userProperty.roles, roles.toString()); } function navigateToHome(dispatch, getState) { @@ -409,7 +412,7 @@ export function bootstrap(baseURL, user, loader = true) { dispatch(setUser(user)); dispatch(setBaseURL(baseURL)); - setRolesProperty(user); + updateUserProperties(user); const httpClient = getState().app.httpClient; diff --git a/src/redux/Restaurant/actions.js b/src/redux/Restaurant/actions.js index 25c9b7d03..cb6200a2f 100644 --- a/src/redux/Restaurant/actions.js +++ b/src/redux/Restaurant/actions.js @@ -20,7 +20,12 @@ import { } from '../App/actions'; import { selectHttpClient } from '../App/selectors'; -import { selectOrderById } from './selectors'; +import { + selectIsSunmiPrinter, + selectOrderById, + selectPrinter, +} from './selectors'; +import { DatadogLogger } from '../../Datadog'; /* * Action Types @@ -244,6 +249,38 @@ export const printPending = createAction('PRINT_PENDING'); export const printFulfilled = createAction('PRINT_FULFILLED'); export const printRejected = createAction('PRINT_REJECTED'); +export function handlePrintFulfilled(order) { + return function (dispatch) { + DatadogLogger.info(`handlePrintFulfilled | success`, { + orderId: order.id, + }); + + dispatch(printFulfilled(order)); + }; +} + +export function handlePrintRejected( + order, + internalErrorMessage = '', + messageForUser = '', +) { + return function (dispatch) { + DatadogLogger.warn( + `handlePrintRejected | failed to print ticket | ${internalErrorMessage}`, + { + orderId: order.id, + }, + ); + + dispatch(printRejected(order)); + DropdownHolder.getDropdown().alertWithType( + 'error', + i18n.t('RESTAURANT_PRINTER_CONNECT_ERROR_TITLE'), + messageForUser, + ); + }; +} + export const setPrintNumberOfCopies = createAction( 'SET_PRINT_NUMBER_OF_COPIES', ); @@ -643,7 +680,7 @@ export function deleteOpeningHoursSpecification(openingHoursSpecification) { }; } -function bluetoothErrorToString(e) { +function errorToString(e) { if (typeof e === 'string') { return e; } @@ -660,7 +697,9 @@ export function printOrderById(orderId) { const order = selectOrderById(getState(), orderId); if (!order) { - console.warn('Order not found', orderId); + DatadogLogger.warn('printOrderById | Order not found', { + orderId, + }); return; } @@ -672,7 +711,8 @@ export function printOrder(order) { return async (dispatch, getState) => { dispatch(printPending(order)); - const { printer, isSunmiPrinter } = getState().restaurant; + const printer = selectPrinter(getState()); + const isSunmiPrinter = selectIsSunmiPrinter(getState()); try { if (isSunmiPrinter) { @@ -680,16 +720,26 @@ export function printOrder(order) { await SunmiPrinterLibrary.sendRAWData( Buffer.from(encodeForPrinter(order, true)).toString('base64'), ); - dispatch(printFulfilled(order)); + dispatch(handlePrintFulfilled(order)); return; } } catch (e) { - console.warn('printOrder with SunmiPrinter failed', e); + DatadogLogger.warn( + `printOrder with SunmiPrinter failed: ${errorToString(e)}`, + { + orderId: order.id, + }, + ); } if (!printer) { - console.warn('No printer selected'); - dispatch(printRejected(order)); + dispatch( + handlePrintRejected( + order, + 'No printer selected', + 'No printer selected', + ), + ); return; } @@ -705,11 +755,11 @@ export function printOrder(order) { await BleManager.connect(printer.id); } catch (e) { dispatch(printerDisconnected()); - dispatch(printRejected(order)); - DropdownHolder.getDropdown().alertWithType( - 'error', - i18n.t('RESTAURANT_PRINTER_CONNECT_ERROR_TITLE'), - bluetoothErrorToString(e), + dispatch( + handlePrintRejected( + order, + `Printer disconnected: ${errorToString(e)}`, + ), ); return; } @@ -740,6 +790,13 @@ export function printOrder(order) { ); if (writableCharacteristics.length > 0) { + DatadogLogger.info( + `printOrder | ${writableCharacteristics.length} writableCharacteristics found`, + { + orderId: order.id, + }, + ); + const encoded = encodeForPrinter(order); writableCharacteristics.sort((a, b) => { @@ -779,21 +836,18 @@ export function printOrder(order) { writableCharacteristic.characteristic, Array.from(encoded), ); - dispatch(printFulfilled(order)); + dispatch(handlePrintFulfilled(order)); } catch (e) { - console.warn('printOrder | Write failed', e); - dispatch(printRejected(order)); + dispatch( + handlePrintRejected(order, `Write failed: ${errorToString(e)}`), + ); } } + } else { + dispatch(handlePrintRejected(order, `No writable characteristics`)); } } catch (e) { - console.warn('printOrder | Error', e); - dispatch(printRejected(order)); - DropdownHolder.getDropdown().alertWithType( - 'error', - i18n.t('RESTAURANT_PRINTER_CONNECT_ERROR_TITLE'), - bluetoothErrorToString(e), - ); + dispatch(handlePrintRejected(order, `Misc error: ${errorToString(e)}`)); } }; } @@ -818,7 +872,7 @@ export function connectPrinter(device, cb) { DropdownHolder.getDropdown().alertWithType( 'error', i18n.t('RESTAURANT_PRINTER_CONNECT_ERROR_TITLE'), - bluetoothErrorToString(e), + errorToString(e), ); }); }; diff --git a/src/redux/Restaurant/selectors.js b/src/redux/Restaurant/selectors.js index 00205ea3f..890fbec49 100644 --- a/src/redux/Restaurant/selectors.js +++ b/src/redux/Restaurant/selectors.js @@ -162,7 +162,7 @@ export const selectFulfilledOrders = createSelector( export const selectPrinter = state => state.restaurant.printer; -const selectIsSunmiPrinter = state => state.restaurant.isSunmiPrinter; +export const selectIsSunmiPrinter = state => state.restaurant.isSunmiPrinter; export const selectIsPrinterConnected = createSelector( selectPrinter, diff --git a/yarn.lock b/yarn.lock index 815d116f1..cf47c8675 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1917,6 +1917,16 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@datadog/mobile-react-native@^2.4.3": + version "2.4.3" + resolved "https://registry.yarnpkg.com/@datadog/mobile-react-native/-/mobile-react-native-2.4.3.tgz#4e0e1d046b8e98d790a91de289264c5b9039d305" + integrity sha512-po+R7JKiFkTS65vWKwfIWAn+aQuWdxeU61C5wc1YYtXVVBpNDCPs3qbDXxQmxJMDxMcjyDnYC0F0tO2EXS62yQ== + +"@datadog/mobile-react-navigation@^2.4.3": + version "2.4.3" + resolved "https://registry.yarnpkg.com/@datadog/mobile-react-navigation/-/mobile-react-navigation-2.4.3.tgz#0008d5ae70ad892c2e587382baaa51d05e0eefc0" + integrity sha512-DcT8GQ2Ne1F5ryR7oM7jaKYn8pJ8r5r1BwbbqnKVkvxNAA2W4HDXn54u2C8ShuYhKs3STIjOo1zCDNxvSRsrgg== + "@egjs/hammerjs@^2.0.17": version "2.0.17" resolved "https://registry.yarnpkg.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124"