Skip to content

Commit

Permalink
feat(mailchimp): handle segmentation via tags (#241)
Browse files Browse the repository at this point in the history
Closes #21
  • Loading branch information
adekbadek authored Jul 7, 2020
1 parent e854521 commit 99f2f1f
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ public function register_routes() {
);
\register_rest_route(
$this->service_provider::BASE_NAMESPACE . $this->service_provider->service,
'(?P<id>[\a-z]+)/interest/(?P<interest_id>[\a-z]+)',
'(?P<id>[\a-z]+)/segments',
[
'methods' => \WP_REST_Server::EDITABLE,
'callback' => [ $this, 'api_interest' ],
'callback' => [ $this, 'api_segments' ],
'permission_callback' => [ $this->service_provider, 'api_authoring_permissions_check' ],
'args' => [
'id' => [
Expand All @@ -119,6 +119,9 @@ public function register_routes() {
'interest_id' => [
'sanitize_callback' => 'esc_attr',
],
'tag_ids' => [
'sanitize_callback' => 'wp_parse_list',
],
],
]
);
Expand Down Expand Up @@ -183,15 +186,16 @@ public function api_list( $request ) {
}

/**
* Set Mailchimp interest (group) for a campaign.
* Set Mailchimp audience segments for a campaign.
*
* @param WP_REST_Request $request API request object.
* @return WP_REST_Response|mixed API response or error.
*/
public function api_interest( $request ) {
$response = $this->service_provider->interest(
public function api_segments( $request ) {
$response = $this->service_provider->audience_segments(
$request['id'],
$request['interest_id']
$request['interest_id'],
$request['tag_ids']
);
return \rest_ensure_response( $response );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,15 @@ public function retrieve( $post_id ) {
}
}

$tags = [];
if ( $list_id ) {
$tags_response = $this->validate(
$mc->get( "lists/$list_id/segments?count=1000" ),
__( 'Error retrieving Mailchimp tags.', 'newspack_newsletters' )
);
$tags = $tags_response['segments'];
}

return [
'lists' => $this->validate(
$mc->get(
Expand All @@ -154,6 +163,7 @@ public function retrieve( $post_id ) {
'campaign' => $campaign,
'campaign_id' => $mc_campaign_id,
'interest_categories' => $interest_categories,
'tags' => $tags,
];
} catch ( Exception $e ) {
return new WP_Error(
Expand Down Expand Up @@ -449,22 +459,25 @@ public function trash( $post_id ) {
}

/**
* Set Mailchimp Interest.
* Set Mailchimp Audience segments for a Campaign.
*
* @param string $post_id Numeric ID of the post.
* @param string $compound_interest_id ID of the interest, including field.
* @param string $post_id Numeric ID of the post.
* @param string $compound_interest_id ID of the interest, including field.
* @param number[] $tag_ids List of tag IDs.
* @return object|WP_Error API API Response or error.
*/
public function interest( $post_id, $compound_interest_id ) {
$exploded = explode( ':', $compound_interest_id );
$field = count( $exploded ) ? $exploded[0] : null;
$interest_id = count( $exploded ) > 1 ? $exploded[1] : null;

if ( 'no_interests' !== $compound_interest_id && ( ! $field || ! $interest_id ) ) {
return new WP_Error(
'newspack_newsletters_invalid_mailchimp_interest',
__( 'Invalid Mailchimp Interest .', 'newspack-newsletters' )
);
public function audience_segments( $post_id, $compound_interest_id, $tag_ids ) {
if ( $compound_interest_id ) {
$exploded = explode( ':', $compound_interest_id );
$field = count( $exploded ) ? $exploded[0] : null;
$interest_id = count( $exploded ) > 1 ? $exploded[1] : null;
$is_unsetting_interest = 'no_interests' === $compound_interest_id;
if ( ! $is_unsetting_interest && ( ! $field || ! $interest_id ) ) {
return new WP_Error(
'newspack_newsletters_invalid_mailchimp_interest',
__( 'Invalid Mailchimp Interest.', 'newspack-newsletters' )
);
}
}

$mc_campaign_id = get_post_meta( $post_id, 'mc_campaign_id', true );
Expand All @@ -489,22 +502,39 @@ public function interest( $post_id, $compound_interest_id ) {
);
}

$segment_opts = ( 'no_interests' === $compound_interest_id ) ?
(object) [] :
[
'match' => 'any',
'conditions' => [
[
'condition_type' => 'Interests',
'field' => $field,
'op' => 'interestcontains',
'value' => [
$interest_id,
],
],
],
$has_interest = $compound_interest_id && ! $is_unsetting_interest;

$segment_opts = (object) [];

if ( $has_interest || $tag_ids ) {
$segment_opts = [
'match' => 'all',
'conditions' => [],
];

if ( $has_interest ) {
$segment_opts['conditions'][] = [
'condition_type' => 'Interests',
'field' => $field,
'op' => 'interestcontains',
'value' => [
$interest_id,
],
];
}

if ( $tag_ids ) {
foreach ( $tag_ids as $tag_id ) {
$segment_opts['conditions'][] = [
'condition_type' => 'StaticSegment',
'field' => 'static_segment',
'op' => 'static_is',
'value' => $tag_id,
];
}
}
}

$payload = [
'recipients' => [
'list_id' => $list_id,
Expand All @@ -514,7 +544,7 @@ public function interest( $post_id, $compound_interest_id ) {

$result = $this->validate(
$mc->patch( "campaigns/$mc_campaign_id", $payload ),
__( 'Error updating Mailchimp groups.', 'newspack_newsletters' )
__( 'Error updating Mailchimp segments.', 'newspack_newsletters' )
);

$data = $this->retrieve( $post_id );
Expand Down
23 changes: 17 additions & 6 deletions src/components/send-button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { get } from 'lodash';
* Internal dependencies
*/
import { getServiceProvider } from '../../service-providers';
import './style.scss';
import { NEWSLETTER_AD_CPT_SLUG, NEWSLETTER_CPT_SLUG } from '../../utils/consts';
import { isAdActive } from '../../ads-admin/utils';

Expand Down Expand Up @@ -58,18 +59,14 @@ export default compose( [
isSaving,
savePost,
status,
validationErrors,
validationErrors = [],
isEditedPostBeingScheduled,
hasPublishAction,
visibility,
newsletterData,
} ) => {
const isButtonEnabled =
( isPublishable || isEditedPostBeingScheduled ) &&
isSaveable &&
validationErrors &&
! validationErrors.length &&
'publish' !== status;
( isPublishable || isEditedPostBeingScheduled ) && isSaveable && 'publish' !== status;
let label;
if ( 'publish' === status ) {
label = isSaving
Expand Down Expand Up @@ -153,8 +150,22 @@ export default compose( [
</Notice>
) : null }
{ renderPreSendInfo( newsletterData ) }
{ validationErrors.length ? (
<Notice status="error" isDismissible={ false }>
{ __(
'The following errors prevent the newsletter from being sent:',
'newspack-newsletters'
) }
<ul>
{ validationErrors.map( ( error, i ) => (
<li key={ i }>{ error }</li>
) ) }
</ul>
</Notice>
) : null }
<Button
isPrimary
disabled={ validationErrors.length > 0 }
onClick={ () => {
triggerCampaignSend();
setModalVisible( false );
Expand Down
9 changes: 9 additions & 0 deletions src/components/send-button/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.newspack-newsletters__modal {
.components-notice {
margin: 0 0 1em;
ul {
list-style-type: disc;
margin-left: 1.3em;
}
}
}
116 changes: 88 additions & 28 deletions src/service-providers/mailchimp/ProviderSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,82 @@
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Fragment, useEffect } from '@wordpress/element';
import { ExternalLink, SelectControl, Spinner, Notice } from '@wordpress/components';
import { Fragment, useEffect, useState } from '@wordpress/element';
import {
ExternalLink,
SelectControl,
Spinner,
Notice,
FormTokenField,
} from '@wordpress/components';

/**
* Internal dependencies
*/
import { getListInterestsSettings } from './utils';
import { getListInterestsSettings, getListTags, getTagNames, getTagIds } from './utils';

const SegmentsSelection = ( {
onUpdate,
inFlight,
chosenInterestId,
availableInterests,
chosenTags,
availableTags,
} ) => {
const [ segmentsData, setSegmentsData ] = useState( {
interest_id: chosenInterestId,
tag_ids: chosenTags,
} );
const updateSegmentsData = data => setSegmentsData( { ...segmentsData, ...data } );

// Update with real data after local (optimistic) update.
useEffect(() => {
setSegmentsData( {
interest_id: chosenInterestId,
tag_ids: chosenTags,
} );
}, [ chosenInterestId, chosenTags.join() ]);

const [ isInitial, setIsInitial ] = useState( true );
useEffect(() => {
if ( ! isInitial ) {
onUpdate( segmentsData );
}
setIsInitial( false );
}, [ segmentsData.interest_id, segmentsData.tag_ids.join() ]);

return (
<Fragment>
{ availableInterests.length ? (
<SelectControl
label={ __( 'Groups', 'newspack-newsletters' ) }
value={ segmentsData.interest_id }
options={ [
{
label: __( '-- Select a group --', 'newspack-newsletters' ),
value: 'no_interests',
},
...availableInterests,
] }
onChange={ interest_id => updateSegmentsData( { interest_id } ) }
disabled={ inFlight }
/>
) : null }
{ availableTags.length ? (
<FormTokenField
className="newspack-newsletters__mailchimp-tags"
label={ __( 'Tags', 'newspack-newsletters' ) }
value={ getTagNames( segmentsData.tag_ids, availableTags ) }
suggestions={ availableTags.map( tag => tag.name ) }
onChange={ updatedTagsNames =>
updateSegmentsData( { tag_ids: getTagIds( updatedTagsNames, availableTags ) } )
}
disabled={ inFlight }
/>
) : null }
</Fragment>
);
};

const ProviderSidebar = ( {
renderSubject,
Expand All @@ -28,10 +97,11 @@ const ProviderSidebar = ( {
method: 'POST',
} );

const setInterest = interestId =>
const updateSegments = updatedData =>
apiFetch( {
path: `/newspack-newsletters/v1/mailchimp/${ postId }/interest/${ interestId }`,
path: `/newspack-newsletters/v1/mailchimp/${ postId }/segments`,
method: 'POST',
data: updatedData,
} );

const setSender = ( { senderName, senderEmail } ) =>
Expand All @@ -53,28 +123,6 @@ const ProviderSidebar = ( {
}
}, [ campaign ]);

const renderInterestCategories = () => {
const interestSettings = getListInterestsSettings( newsletterData );
if ( ! interestSettings ) {
return;
}
return (
<SelectControl
label={ __( 'Groups', 'newspack-newsletters' ) }
value={ interestSettings.interestValue }
options={ [
{
label: __( '-- Select a group --', 'newspack-newsletters' ),
value: 'no_interests',
},
...interestSettings.options,
] }
onChange={ setInterest }
disabled={ inFlight }
/>
);
};

if ( ! campaign ) {
return (
<div className="newspack-newsletters__loading-data">
Expand All @@ -98,6 +146,9 @@ const ProviderSidebar = ( {
const list = list_id && lists.find( ( { id } ) => list_id === id );
const { web_id: listWebId } = list || {};

const interestSettings = getListInterestsSettings( newsletterData );
const chosenTags = getListTags( newsletterData );

return (
<Fragment>
{ renderSubject() }
Expand Down Expand Up @@ -126,7 +177,16 @@ const ProviderSidebar = ( {
</ExternalLink>
</p>
) }
{ renderInterestCategories() }

<SegmentsSelection
chosenInterestId={ interestSettings.interestValue }
availableInterests={ interestSettings.options }
chosenTags={ chosenTags }
availableTags={ newsletterData.tags.filter( tag => tag.member_count > 0 ) }
apiFetch={ apiFetch }
inFlight={ inFlight }
onUpdate={ updateSegments }
/>

{ renderFrom( { handleSenderUpdate: setSender } ) }
</Fragment>
Expand Down
Loading

0 comments on commit 99f2f1f

Please sign in to comment.