Skip to content
Open
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 changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

= 1.25.6 - 202x-xx-xx =
* Fix - Refreshes shipping methods after registering or removing carrier accounts.
* Add - Remember state of "Mark this order as complete and notify the customer" and "Notify the customer with shipment details" checkboxes.

= 1.25.5 - 2021-01-11 =
* Fix - Redux DevTools usage update.
Expand Down
1 change: 1 addition & 0 deletions classes/class-wc-connect-options.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public static function get_option_names( $type = 'compact' ) {
'payment_methods',
'account_settings',
'paper_size',
'post_print_notification_settings',
'packages',
'predefined_packages',
'shipping_methods_migrated',
Expand Down
61 changes: 61 additions & 0 deletions classes/class-wc-connect-service-settings-store.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,67 @@ public function set_preferred_paper_size( $size ) {
return WC_Connect_Options::update_option( 'paper_size', $size );
}

/**
* Gets post-label-printing notification settings.
*
* @return array Array of boolean values, indexed by setting names.
*/
public function get_post_print_notification_settings() {
$defaults = array(
'mark_order_complete_and_notify_customer' => false,
'notify_customer_with_shipment_details' => false,
);

$settings = WC_Connect_Options::get_option( 'post_print_notification_settings', $defaults );

return array_merge( $defaults, $settings );
}

/**
* Updates post-label-printing notification settings.
*
* @param string $name Name of the setting to enable/disable.
* @param bool $enabled Whether the setting should be enabled.
*
* @return true|WP_Error WP_Error if an error occurred, `true` otherwise.
*/
public function set_post_print_notification_setting( $name, $enabled ) {
$allowed_names = array(
Copy link
Contributor

@harriswong harriswong Jan 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't trust the user input and I like how you add the check here instead of the controller's level. 💯

'mark_order_complete_and_notify_customer',
'notify_customer_with_shipment_details',
);

if ( ! in_array( $name, $allowed_names, true ) ) {
return new WP_Error(
'invalid_notification_setting_name',
__( 'Invalid notification setting name supplied.', 'woocommerce-services' )
);
}

$old_settings = WC_Connect_Options::get_option( 'post_print_notification_settings' );
$settings = $old_settings;
$settings[ $name ] = (bool) $enabled;

/*
* WC_Connect_Options::update_option() returns `false` if the new value is the same as the old one.
* As this is not an issue in this case, we return `true` here and leave the option unchanged.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice comment!

*/
if ( $settings === $old_settings ) {
return true;
}

$result = WC_Connect_Options::update_option( 'post_print_notification_settings', $settings );

if ( ! $result ) {
return new WP_Error(
'save_failed',
__( 'Updating the option failed.', 'woocommerce-services' )
);
}

return $result;
}

/**
* Attempts to recover faulty json string fields that might contain strings with unescaped quotes
*
Expand Down
4 changes: 4 additions & 0 deletions classes/class-wc-connect-shipping-label.php
Original file line number Diff line number Diff line change
Expand Up @@ -391,10 +391,14 @@ public function get_label_payload( $post_order_or_id ) {
return false;
}

$notification_settings = $this->settings_store->get_post_print_notification_settings();

$order_id = WC_Connect_Compatibility::instance()->get_order_id( $order );
$payload = array(
'orderId' => $order_id,
'paperSize' => $this->settings_store->get_preferred_paper_size(),
'fulfillOrder' => $notification_settings['mark_order_complete_and_notify_customer'],
'emailDetails' => $notification_settings['notify_customer_with_shipment_details'],
'formData' => $this->get_form_data( $order ),
'labelsData' => $this->settings_store->get_label_order_meta_data( $order_id ),
'storeOptions' => $this->settings_store->get_store_options(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we use classes/class-wc-rest-connect-shipping-label-preview-controller.php (connect/label/preview) when we save the paper_size preference. I am a little surprised that I can't find an existing end point to update settings. So this new end point makes sense.

Were you also not able to find any existing end point to update settings?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

I was considering adding the notification-settings-saving logic to an existing endpoint but:

  1. Saving when shipping rates are requested or purchased is too early because the checkbox might be toggled afterward.
  2. Saving when the label PDF is requested (this happens automatically after a request to purchase a label is made) was another option I considered but:
    1. the notifications are not yet sent at this point so again, this is too early;
    2. this is a GET request. The paper size setting is saved upon this request, but this makes more sense than saving notification settings here - the paper size has to be provided anyway so a label can be printed in the selected size.
  3. Saving when the label PDF is displayed (either after "Print" is clicked or the PDF is opened automatically) sometimes triggers another request. This will be either a request to mark an order as completed, to send the email with details, but can also be no request at all. In such cases, a separate call would have to be made anyway.

Based on the above, I decided that placing the logic in a separate REST endpoint accessed after the "Print" button is clicked would be the cleanest solution.

Copy link
Contributor

@harriswong harriswong Jan 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The choice of the new end point makes a lot of sense now. Thank you!


if ( ! defined( 'ABSPATH' ) ) {
exit();
}

if ( class_exists( 'WC_REST_Connect_Post_Print_Notification_Settings_Controller' ) ) {
return;
}

class WC_REST_Connect_Post_Print_Notification_Settings_Controller extends WC_REST_Connect_Base_Controller {
protected $rest_base = 'connect/post-print-notification-settings';

public function post( $request ) {
$result = $this->settings_store->set_post_print_notification_setting( $request['name'], $request['enabled'] );

if ( is_wp_error( $result ) ) {
$error = new WP_Error(
'save_failed',
sprintf(
__( 'Unable to update notification setting. %s', 'woocommerce-services' ),
$result->get_error_message()
),
array( 'status' => 400 )
);

$this->logger->log( $error, __CLASS__ );

return $error;
}

return new WP_REST_Response( array( 'success' => true ), 200 );
Copy link
Contributor

@harriswong harriswong Jan 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! There are a few different ways in our existing code to return a response/error. I am glad you picked WP_REST_Response :D 👍 💯

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @format */
export const accountSettings = 'connect/account/settings';
export const postPrintNotificationSettings = 'connect/post-print-notification-settings';
export const packages = 'connect/packages';
export const orderLabels = ( orderId ) => `connect/label/${ orderId }`;
export const getLabelRates = ( orderId ) => `connect/label/${ orderId }/rates`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,10 @@ export default data => {
loaded: false,
isFetching: false,
error: false,
fulfillOrder: false,
emailDetails: false,
};
}

const { formData, labelsData, paperSize, storeOptions, canChangeCountries } = data;
const { formData, labelsData, paperSize, fulfillOrder, emailDetails, storeOptions, canChangeCountries } = data;
//old WCS required a phone number and detected normalization status based on the existence of the phone field
//newer versions send the normalized flag
const originNormalized = Boolean( formData.origin_normalized || formData.origin.phone );
Expand Down Expand Up @@ -74,8 +72,8 @@ export default data => {
loaded: true,
isFetching: false,
error: false,
fulfillOrder: false,
emailDetails: false,
fulfillOrder,
emailDetails,
refreshedLabelStatus: false,
labels: labelsData || [],
paperSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import {
} from './selectors';
import { createNote } from 'woocommerce/state/sites/orders/notes/actions';
import { saveOrder } from 'woocommerce/state/sites/orders/actions';
import { getOrder } from 'woocommerce/state/sites/orders/selectors';
import { isOrderFinished } from 'woocommerce/lib/order-status';
import { getAllPackageDefinitions } from 'woocommerce/woocommerce-services/state/packages/selectors';
import { getEmailReceipts } from 'woocommerce/woocommerce-services/state/label-settings/selectors';
import getAddressValues from 'woocommerce/woocommerce-services/lib/utils/get-address-values';
Expand Down Expand Up @@ -729,6 +731,26 @@ export const setFulfillOrderOption = ( orderId, siteId, value ) => {
};
};

export const updatePostPrintNotificationSettings = ( state, orderId, siteId ) => {
const { fulfillOrder, emailDetails } = getShippingLabel( state, orderId, siteId );
const order = getOrder( state, orderId );
let data = {};

if ( isOrderFinished( order.status ) ) {
data = {
name: 'notify_customer_with_shipment_details',
enabled: emailDetails,
};
} else {
data = {
name: 'mark_order_complete_and_notify_customer',
enabled: fulfillOrder,
};
}

api.post( siteId, api.url.postPrintNotificationSettings, data );
};

const purchaseLabelResponse = ( orderId, siteId, response, error ) => {
return {
type: WOOCOMMERCE_SERVICES_SHIPPING_LABEL_PURCHASE_RESPONSE,
Expand Down Expand Up @@ -777,7 +799,7 @@ const labelStatusTask = ( orderId, siteId, labelId, retryCount ) => {
} );
};

const handlePrintFinished = ( orderId, siteId, dispatch, getState, hasError, labels ) => {
export const handlePrintFinished = ( orderId, siteId, dispatch, getState, hasError, labels ) => {
dispatch( exitPrintingFlow( orderId, siteId, true ) );
dispatch( clearAvailableRates( orderId, siteId ) );

Expand Down Expand Up @@ -824,6 +846,8 @@ const handlePrintFinished = ( orderId, siteId, dispatch, getState, hasError, lab
} )
);
}

updatePostPrintNotificationSettings( getState(), orderId, siteId );
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ import {
hasStates,
} from 'woocommerce/state/sites/data/locations/selectors';
import { isWcsInternationalLabelsEnabled } from 'woocommerce/state/selectors/plugins';
import { getOrder } from 'woocommerce/state/sites/orders/selectors';
import { isOrderFinished } from '../../../lib/order-status';

export const getShippingLabel = ( state, orderId, siteId = getSelectedSiteId( state ) ) => {
return get(
Expand Down Expand Up @@ -92,12 +94,14 @@ export const getLabelById = ( state, orderId, siteId, labelId ) => {

export const shouldFulfillOrder = ( state, orderId, siteId = getSelectedSiteId( state ) ) => {
const shippingLabel = getShippingLabel( state, orderId, siteId );
return shippingLabel && shippingLabel.fulfillOrder;
const order = getOrder( state, orderId );
return shippingLabel && shippingLabel.fulfillOrder && order && ! isOrderFinished( order.status );
};

export const shouldEmailDetails = ( state, orderId, siteId = getSelectedSiteId( state ) ) => {
const shippingLabel = getShippingLabel( state, orderId, siteId );
return shippingLabel && shippingLabel.emailDetails;
const order = getOrder( state, orderId );
return shippingLabel && shippingLabel.emailDetails && order && isOrderFinished( order.status );
};

export const getSelectedPaymentMethod = ( state, orderId, siteId = getSelectedSiteId( state ) ) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import nock from 'nock';
*/
import {
openPrintingFlow,
handlePrintFinished,
convertToApiPackage,
submitAddressForNormalization,
confirmAddressSuggestion,
Expand Down Expand Up @@ -269,6 +270,85 @@ describe( 'Shipping label Actions', () => {
nock.cleanAll();
} );

const createGetStateFnForPostPrintNotificationSettings = ( orderStatus, notificationSettings ) => () => ( {
ui: {
selectedSiteId: siteId,
},
extensions: {
woocommerce: {
sites: {
[ siteId ]: {
orders: {
items: {
[ orderId ]: {
status: orderStatus,
},
}
}
},
},
woocommerceServices: {
[ siteId ]: {
shippingLabel: {
[ orderId ]: notificationSettings,
},
},
},
},
},
} );

const mockPostPrintNotificationSettingsUpdateRequest = expectedBody => {
return nock( 'https://public-api.wordpress.com:443' )
.post(
`/rest/v1.1/jetpack-blogs/${ siteId }/rest-api/`,
body => {
const expectedPath = '/wc/v1/connect/post-print-notification-settings&_via_calypso&_method=post';

return expectedPath === body.path && JSON.stringify( expectedBody ) === body.body;
}
)
.reply( 200, { success: true } );
};

const testPostPrintNotificationSettingsUpdate = ( orderStatus, state, expectedBody ) => {
const getState = createGetStateFnForPostPrintNotificationSettings( orderStatus, state );
const request = mockPostPrintNotificationSettingsUpdateRequest( expectedBody );
handlePrintFinished( orderId, siteId, sinon.spy(), getState, false, [] );
request.done();
};

const testCases = [
{
orderStatus: 'completed',
state: { name: 'emailDetails', value: true },
expectedBody: { name: 'notify_customer_with_shipment_details', enabled: true },
},
{
orderStatus: 'completed',
state: { name: 'emailDetails', value: false },
expectedBody: { name: 'notify_customer_with_shipment_details', enabled: false },
},
{
orderStatus: 'pending',
state: { name: 'fulfillOrder', value: true },
expectedBody: { name: 'mark_order_complete_and_notify_customer', enabled: true },
},
{
orderStatus: 'pending',
state: { name: 'fulfillOrder', value: false },
expectedBody: { name: 'mark_order_complete_and_notify_customer', enabled: false },
},
];

describe.each( testCases )( '#handlePrintFinished', testCase => {
const { expectedBody, orderStatus, state } = testCase;

it( `sets "${expectedBody.name}" to ${expectedBody.value}`, () => {
testPostPrintNotificationSettingsUpdate( orderStatus, { [ state.name ]: state.value }, expectedBody );
} );
} );

describe( '#convertToApiPackage', () => {
it( 'totals value correctly (by quantity)', () => {
const pckg = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { expect } from 'chai';
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16'
import { CheckboxControl, RadioControl } from '@wordpress/components';
import { moment } from 'i18n-calypso';

/**
* Internal dependencies
Expand Down Expand Up @@ -94,7 +95,9 @@ describe( 'ShippingRate', () => {
} );

it( 'renders the delivery date', () => {
expect( shippingRateWrapper ).to.contain( <div className="rates-step__shipping-rate-delivery-date">January 1</div> ); // eslint-disable-line
const expectedDate = moment( shippingRateWrapper.props().rateObject.delivery_date ).format( 'LL' ).split( ',' )[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the timezone issue? :D

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! The issue was that the time provided in createShippingRateWrapper() (which is 2020-01-01T23:59:00.000Z) can, in some timezones, not match the test's expectation of January 1. :)


expect( shippingRateWrapper ).to.contain( <div className="rates-step__shipping-rate-delivery-date">{ expectedDate }</div> ); // eslint-disable-line
} );

} );
Expand Down
Loading