Add support for multiple any of for Woo subscriptions segment

[MAILPOET-3956]
This commit is contained in:
Rostislav Wolny 2021-12-09 11:06:51 +01:00 committed by Veljko V
parent c86443a08c
commit 651528c0b6
6 changed files with 96 additions and 30 deletions

View File

@ -1,11 +1,14 @@
import React from 'react';
import React, { useEffect } from 'react';
import MailPoet from 'mailpoet';
import { find } from 'lodash/fp';
import { filter } from 'lodash/fp';
import { useSelect, useDispatch } from '@wordpress/data';
import Select from 'common/form/react_select/react_select';
import ReactSelect from 'common/form/react_select/react_select';
import Select from 'common/form/select/select';
import { Grid } from 'common/grid';
import {
AnyValueTypes,
SegmentTypes,
SelectOption,
WindowSubscriptionProducts,
@ -21,11 +24,12 @@ export const WooCommerceSubscriptionOptions = [
];
export function validateWooCommerceSubscription(
formItems: WooCommerceSubscriptionFormItem
formItem: WooCommerceSubscriptionFormItem
): boolean {
const isIncomplete = !formItem.product_ids || !formItem.product_ids.length || !formItem.operator;
if (
formItems.action === WooCommerceSubscriptionsActionTypes.ACTIVE_SUBSCRIPTIONS
&& !formItems.product_id
formItem.action === WooCommerceSubscriptionsActionTypes.ACTIVE_SUBSCRIPTIONS
&& isIncomplete
) {
return false;
}
@ -42,7 +46,7 @@ export const WooCommerceSubscriptionFields: React.FunctionComponent<Props> = ({
[filterIndex]
);
const { updateSegmentFilter } = useDispatch('mailpoet-dynamic-segments-form');
const { updateSegmentFilter, updateSegmentFilterFromEvent } = useDispatch('mailpoet-dynamic-segments-form');
const subscriptionProducts: WindowSubscriptionProducts = useSelect(
(select) => select('mailpoet-dynamic-segments-form').getSubscriptionProducts(),
@ -53,19 +57,57 @@ export const WooCommerceSubscriptionFields: React.FunctionComponent<Props> = ({
label: product.name,
}));
useEffect(() => {
if (
(segment.action === WooCommerceSubscriptionsActionTypes.ACTIVE_SUBSCRIPTIONS)
&& (segment.operator !== AnyValueTypes.ANY)
) {
updateSegmentFilter({ operator: AnyValueTypes.ANY }, filterIndex);
}
// Temporary BC fix
if (segment.product_id && !segment.product_ids) {
updateSegmentFilter({ product_ids: [segment.product_id] }, filterIndex);
}
}, [updateSegmentFilter, segment, filterIndex]);
return (
<div>
<Select
dimension="small"
isFullWidth
placeholder={MailPoet.I18n.t('selectWooSubscription')}
automationId="segment-woo-subscription-action"
options={productOptions}
value={find(['value', segment.product_id], productOptions)}
onChange={(option: SelectOption): void => {
updateSegmentFilter({ product_id: option.value }, filterIndex);
}}
/>
</div>
<>
<Grid.CenteredRow>
<Select
key="select-operator"
value={segment.operator}
onChange={(e) => updateSegmentFilterFromEvent(
'operator',
filterIndex,
e
)}
automationId="select-operator"
>
<option value={AnyValueTypes.ANY}>{MailPoet.I18n.t('anyOf')}</option>
</Select>
</Grid.CenteredRow>
<Grid.CenteredRow>
<ReactSelect
isMulti
dimension="small"
key="select-segment-category"
isFullWidth
placeholder={MailPoet.I18n.t('selectWooSubscription')}
options={productOptions}
value={filter(
(option) => {
if (!segment.product_ids) return false;
return segment.product_ids.indexOf(option.value) !== -1;
},
productOptions
)}
onChange={(options: SelectOption[]): void => updateSegmentFilter(
{ product_ids: (options || []).map((x: SelectOption) => x.value) },
filterIndex
)}
automationId="select-segment-products"
/>
</Grid.CenteredRow>
</>
);
};

View File

@ -85,6 +85,8 @@ export interface WooCommerceFormItem extends FormItem {
export interface WooCommerceSubscriptionFormItem extends FormItem {
product_id?: string;
product_ids?: string[];
operator?: AnyValueTypes;
}
export interface EmailFormItem extends FormItem {

View File

@ -223,8 +223,10 @@ class FilterDataMapper {
$filterType = DynamicSegmentFilterData::TYPE_WOOCOMMERCE_SUBSCRIPTION;
$action = $data['action'];
if ($data['action'] === WooCommerceSubscription::ACTION_HAS_ACTIVE) {
if (!isset($data['product_id'])) throw new InvalidFilterException('Missing product', InvalidFilterException::MISSING_PRODUCT_ID);
$filterData['product_id'] = $data['product_id'];
if (!isset($data['product_ids']) || !is_array($data['product_ids'])) throw new InvalidFilterException('Missing product', InvalidFilterException::MISSING_PRODUCT_ID);
if (!isset($data['operator'])) throw new InvalidFilterException('Missing operator', InvalidFilterException::MISSING_OPERATOR);
$filterData['operator'] = $data['operator'];
$filterData['product_ids'] = $data['product_ids'];
} else {
throw new InvalidFilterException("Unknown action " . $data['action'], InvalidFilterException::MISSING_ACTION);
}

View File

@ -5,6 +5,7 @@ namespace MailPoet\Segments\DynamicSegments\Filters;
use MailPoet\Entities\DynamicSegmentFilterEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Util\Security;
use MailPoetVendor\Doctrine\DBAL\Connection;
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
use MailPoetVendor\Doctrine\ORM\EntityManager;
@ -23,7 +24,11 @@ class WooCommerceSubscription implements Filter {
public function apply(QueryBuilder $queryBuilder, DynamicSegmentFilterEntity $filter): QueryBuilder {
global $wpdb;
$filterData = $filter->getFilterData();
$productId = (int)$filterData->getParam('product_id');
$productIds = $filterData->getParam('product_ids');
// Temporary BC fix
if (!$productIds) {
$productIds = [(int)$filterData->getParam('product_id')];
}
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
$parameterSuffix = $filter->getId() ?: Security::generateRandomString();
return $queryBuilder->innerJoin(
@ -45,7 +50,7 @@ class WooCommerceSubscription implements Filter {
'items',
$wpdb->prefix . 'woocommerce_order_itemmeta',
'itemmeta',
"itemmeta.order_item_id=items.order_item_id AND itemmeta.meta_key='_product_id' AND itemmeta.meta_value=:product" . $parameterSuffix
)->setParameter('product' . $parameterSuffix, $productId);
"itemmeta.order_item_id=items.order_item_id AND itemmeta.meta_key='_product_id' AND itemmeta.meta_value IN (:products" . $parameterSuffix . ")"
)->setParameter('products' . $parameterSuffix, $productIds, Connection::PARAM_STR_ARRAY);
}
}

View File

@ -51,7 +51,7 @@ class WooCommerceSubscriptionsSegmentCest {
$i->fillField(['name' => 'name'], $segmentTitle);
$i->fillField(['name' => 'description'], 'Desc ' . $segmentTitle);
$i->selectOptionInReactSelect('has an active subscription', $segmentActionSelectElement);
$i->selectOptionInReactSelect('Subscription 1', '[data-automation-id="segment-woo-subscription-action"]');
$i->selectOptionInReactSelect('Subscription 1', '[data-automation-id="select-segment-products"]');
$i->waitForText('Calculating segment size…');
$i->waitForText('This segment has 2 subscribers.');
$i->seeNoJSErrors();

View File

@ -373,7 +373,8 @@ class FilterDataMapperTest extends \MailPoetUnitTest {
$data = ['filters' => [[
'segmentType' => DynamicSegmentFilterData::TYPE_WOOCOMMERCE_SUBSCRIPTION,
'action' => WooCommerceSubscription::ACTION_HAS_ACTIVE,
'product_id' => '10',
'operator' => DynamicSegmentFilterData::OPERATOR_ANY,
'product_ids' => ['10'],
'some_mess' => 'mess',
]]];
$filters = $this->mapper->map($data);
@ -385,7 +386,8 @@ class FilterDataMapperTest extends \MailPoetUnitTest {
expect($filter->getFilterType())->equals(DynamicSegmentFilterData::TYPE_WOOCOMMERCE_SUBSCRIPTION);
expect($filter->getAction())->equals(WooCommerceSubscription::ACTION_HAS_ACTIVE);
expect($filter->getData())->equals([
'product_id' => '10',
'product_ids' => ['10'],
'operator' => DynamicSegmentFilterData::OPERATOR_ANY,
'connect' => DynamicSegmentFilterData::CONNECT_TYPE_AND,
]);
}
@ -396,12 +398,25 @@ class FilterDataMapperTest extends \MailPoetUnitTest {
$this->expectExceptionCode(InvalidFilterException::MISSING_ACTION);
$data = ['filters' => [[
'segmentType' => DynamicSegmentFilterData::TYPE_WOOCOMMERCE_SUBSCRIPTION,
'product_id' => '10',
'operator' => DynamicSegmentFilterData::OPERATOR_ANY,
'product_ids' => ['10'],
]]];
$this->mapper->map($data);
}
public function testItChecksWooCommerceSubscriptionProductId() {
public function testItChecksWooCommerceSubscriptionProductIds() {
$this->expectException(InvalidFilterException::class);
$this->expectExceptionMessage('Missing product');
$this->expectExceptionCode(InvalidFilterException::MISSING_PRODUCT_ID);
$data = ['filters' => [[
'segmentType' => DynamicSegmentFilterData::TYPE_WOOCOMMERCE_SUBSCRIPTION,
'action' => WooCommerceSubscription::ACTION_HAS_ACTIVE,
'operator' => DynamicSegmentFilterData::OPERATOR_ANY,
]]];
$this->mapper->map($data);
}
public function testItChecksWooCommerceSubscriptionMissingOperator() {
$this->expectException(InvalidFilterException::class);
$this->expectExceptionMessage('Missing product');
$this->expectExceptionCode(InvalidFilterException::MISSING_PRODUCT_ID);