Add control for selecting segments

[MAILPOET-3502]
This commit is contained in:
Pavel Dohnal
2021-11-23 13:13:47 +01:00
committed by Veljko V
parent 4870b2a319
commit dcc8eccb42
7 changed files with 92 additions and 5 deletions

View File

@@ -1,11 +1,17 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { compose, get, filter } from 'lodash/fp';
import { useDispatch, useSelect } from '@wordpress/data'; import { useDispatch, useSelect } from '@wordpress/data';
import MailPoet from 'mailpoet'; import MailPoet from 'mailpoet';
import { Grid } from 'common/grid';
import Select from 'common/form/select/select'; import Select from 'common/form/select/select';
import ReactSelect from 'common/form/react_select/react_select';
import { AnyValueTypes, WordpressRoleFormItem } from '../types'; import {
AnyValueTypes,
SelectOption,
StaticSegment,
WordpressRoleFormItem,
} from '../types';
export function validateSubscribedToList(formItems: WordpressRoleFormItem): boolean { export function validateSubscribedToList(formItems: WordpressRoleFormItem): boolean {
return ( return (
@@ -24,6 +30,10 @@ export const SubscribedToList: React.FunctionComponent<Props> = ({ filterIndex }
(select) => select('mailpoet-dynamic-segments-form').getSegmentFilter(filterIndex), (select) => select('mailpoet-dynamic-segments-form').getSegmentFilter(filterIndex),
[filterIndex] [filterIndex]
); );
const staticSegmentsList: StaticSegment[] = useSelect(
(select) => select('mailpoet-dynamic-segments-form').getStaticSegmentsList(),
[]
);
const { updateSegmentFilter, updateSegmentFilterFromEvent } = useDispatch('mailpoet-dynamic-segments-form'); const { updateSegmentFilter, updateSegmentFilterFromEvent } = useDispatch('mailpoet-dynamic-segments-form');
@@ -38,9 +48,13 @@ export const SubscribedToList: React.FunctionComponent<Props> = ({ filterIndex }
updateSegmentFilter({ operator: AnyValueTypes.ANY }, filterIndex); updateSegmentFilter({ operator: AnyValueTypes.ANY }, filterIndex);
} }
}, [updateSegmentFilter, segment, filterIndex]); }, [updateSegmentFilter, segment, filterIndex]);
const options = staticSegmentsList.map((currentValue) => ({
value: currentValue.id.toString(),
label: currentValue.name,
}));
return ( return (
<Grid.CenteredRow> <>
<Select <Select
key="select" key="select"
value={segment.operator} value={segment.operator}
@@ -52,6 +66,29 @@ export const SubscribedToList: React.FunctionComponent<Props> = ({ filterIndex }
<option value={AnyValueTypes.ALL}>{MailPoet.I18n.t('allOf')}</option> <option value={AnyValueTypes.ALL}>{MailPoet.I18n.t('allOf')}</option>
<option value={AnyValueTypes.NONE}>{MailPoet.I18n.t('noneOf')}</option> <option value={AnyValueTypes.NONE}>{MailPoet.I18n.t('noneOf')}</option>
</Select> </Select>
</Grid.CenteredRow> <ReactSelect
dimension="small"
isFullWidth
isMulti
placeholder={MailPoet.I18n.t('searchLists')}
options={options}
value={
filter(
(option) => {
if (!segment.segments) return undefined;
const segmentId = Number(option.value);
return segment.segments.indexOf(segmentId) !== -1;
},
options
)
}
onChange={(options: SelectOption[]): void => {
updateSegmentFilter(
{ segments: options.map(compose([Number, get('value')])) },
filterIndex
);
}}
/>
</>
); );
}; };

View File

@@ -5,6 +5,7 @@ import {
GroupFilterValue, GroupFilterValue,
Segment, Segment,
StateType, StateType,
StaticSegment,
SubscriberCount, SubscriberCount,
WindowCustomFields, WindowCustomFields,
WindowEditableRoles, WindowEditableRoles,
@@ -45,6 +46,9 @@ export const getCustomFieldsList = (state: StateType): WindowCustomFields => (
export const getSegment = (state: StateType): Segment => ( export const getSegment = (state: StateType): Segment => (
state.segment state.segment
); );
export const getStaticSegmentsList = (state: StateType): StaticSegment[] => (
state.staticSegmentsList
);
export const getSubscriberCount = (state: StateType): SubscriberCount => ( export const getSubscriberCount = (state: StateType): SubscriberCount => (
state.subscriberCount state.subscriberCount
); );

View File

@@ -23,6 +23,7 @@ const STORE = 'mailpoet-dynamic-segments-form';
export const createStore = (): void => { export const createStore = (): void => {
const defaultState: StateType = { const defaultState: StateType = {
products: window.mailpoet_products, products: window.mailpoet_products,
staticSegmentsList: window.mailpoet_static_segments_list,
subscriptionProducts: window.mailpoet_subscription_products, subscriptionProducts: window.mailpoet_subscription_products,
productCategories: window.mailpoet_product_categories, productCategories: window.mailpoet_product_categories,
newslettersList: window.mailpoet_newsletters_list, newslettersList: window.mailpoet_newsletters_list,

View File

@@ -68,6 +68,7 @@ export interface WordpressRoleFormItem extends FormItem {
custom_field_id?: string; custom_field_id?: string;
custom_field_type?: string; custom_field_type?: string;
date_type?: string; date_type?: string;
segments?: number[];
} }
export interface WooCommerceFormItem extends FormItem { export interface WooCommerceFormItem extends FormItem {
@@ -156,6 +157,13 @@ export type WindowCustomFields = {
updated_at: string; updated_at: string;
}[]; }[];
export type StaticSegment = {
id: number;
name: string;
type: string;
description: string;
};
export interface SegmentFormDataWindow extends Window { export interface SegmentFormDataWindow extends Window {
wordpress_editable_roles_list: WindowEditableRoles; wordpress_editable_roles_list: WindowEditableRoles;
mailpoet_products: WindowProducts; mailpoet_products: WindowProducts;
@@ -166,6 +174,7 @@ export interface SegmentFormDataWindow extends Window {
mailpoet_custom_fields: WindowCustomFields; mailpoet_custom_fields: WindowCustomFields;
mailpoet_can_use_woocommerce_subscriptions: boolean; mailpoet_can_use_woocommerce_subscriptions: boolean;
mailpoet_woocommerce_currency_symbol: string; mailpoet_woocommerce_currency_symbol: string;
mailpoet_static_segments_list: StaticSegment[];
} }
export interface StateType { export interface StateType {
@@ -182,6 +191,7 @@ export interface StateType {
subscriberCount: SubscriberCount, subscriberCount: SubscriberCount,
errors: string[], errors: string[],
allAvailableFilters: GroupFilterValue[], allAvailableFilters: GroupFilterValue[],
staticSegmentsList: StaticSegment[],
} }
export enum Actions { export enum Actions {

View File

@@ -8,9 +8,11 @@ use MailPoet\Cache\TransientCache;
use MailPoet\Config\ServicesChecker; use MailPoet\Config\ServicesChecker;
use MailPoet\CustomFields\CustomFieldsRepository; use MailPoet\CustomFields\CustomFieldsRepository;
use MailPoet\Entities\DynamicSegmentFilterData; use MailPoet\Entities\DynamicSegmentFilterData;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Listing\PageLimit; use MailPoet\Listing\PageLimit;
use MailPoet\Models\Newsletter; use MailPoet\Models\Newsletter;
use MailPoet\Segments\SegmentDependencyValidator; use MailPoet\Segments\SegmentDependencyValidator;
use MailPoet\Segments\SegmentsRepository;
use MailPoet\Services\Bridge; use MailPoet\Services\Bridge;
use MailPoet\Settings\SettingsController; use MailPoet\Settings\SettingsController;
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature; use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
@@ -18,6 +20,7 @@ use MailPoet\WooCommerce\Helper as WooCommerceHelper;
use MailPoet\WP\AutocompletePostListLoader as WPPostListLoader; use MailPoet\WP\AutocompletePostListLoader as WPPostListLoader;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\Carbon\Carbon; use MailPoetVendor\Carbon\Carbon;
use MailPoetVendor\Doctrine\Common\Collections\Criteria;
class Segments { class Segments {
/** @var PageRenderer */ /** @var PageRenderer */
@@ -56,6 +59,9 @@ class Segments {
/** @var TransientCache */ /** @var TransientCache */
private $transientCache; private $transientCache;
/** @var SegmentsRepository */
private $segmentsRepository;
public function __construct( public function __construct(
PageRenderer $pageRenderer, PageRenderer $pageRenderer,
PageLimit $listingPageLimit, PageLimit $listingPageLimit,
@@ -68,6 +74,7 @@ class Segments {
CustomFieldsRepository $customFieldsRepository, CustomFieldsRepository $customFieldsRepository,
CustomFieldsResponseBuilder $customFieldsResponseBuilder, CustomFieldsResponseBuilder $customFieldsResponseBuilder,
SegmentDependencyValidator $segmentDependencyValidator, SegmentDependencyValidator $segmentDependencyValidator,
SegmentsRepository $segmentsRepository,
TransientCache $transientCache TransientCache $transientCache
) { ) {
$this->pageRenderer = $pageRenderer; $this->pageRenderer = $pageRenderer;
@@ -82,6 +89,7 @@ class Segments {
$this->customFieldsRepository = $customFieldsRepository; $this->customFieldsRepository = $customFieldsRepository;
$this->customFieldsResponseBuilder = $customFieldsResponseBuilder; $this->customFieldsResponseBuilder = $customFieldsResponseBuilder;
$this->transientCache = $transientCache; $this->transientCache = $transientCache;
$this->segmentsRepository = $segmentsRepository;
} }
public function render() { public function render() {
@@ -112,6 +120,22 @@ class Segments {
->where('type', Newsletter::TYPE_STANDARD) ->where('type', Newsletter::TYPE_STANDARD)
->orderByExpr('ISNULL(sent_at) DESC, sent_at DESC')->findArray(); ->orderByExpr('ISNULL(sent_at) DESC, sent_at DESC')->findArray();
$data['static_segments_list'] = [];
$criteria = new Criteria();
$criteria->where(Criteria::expr()->isNull('deletedAt'));
$criteria->andWhere(Criteria::expr()->neq('type', SegmentEntity::TYPE_DYNAMIC));
$criteria->orderBy(['name' => 'ASC']);
$segments = $this->segmentsRepository->matching($criteria);
foreach ($segments as $segment) {
$data['static_segments_list'][] = [
'id' => $segment->getId(),
'name' => $segment->getName(),
'type' => $segment->getType(),
'description' => $segment->getDescription(),
];
}
$data['product_categories'] = $this->wpPostListLoader->getWooCommerceCategories(); $data['product_categories'] = $this->wpPostListLoader->getWooCommerceCategories();
$data['products'] = $this->wpPostListLoader->getProducts(); $data['products'] = $this->wpPostListLoader->getProducts();

View File

@@ -2,6 +2,8 @@
namespace MailPoet\Doctrine; namespace MailPoet\Doctrine;
use MailPoetVendor\Doctrine\Common\Collections\Collection;
use MailPoetVendor\Doctrine\Common\Collections\Criteria;
use MailPoetVendor\Doctrine\ORM\EntityManager; use MailPoetVendor\Doctrine\ORM\EntityManager;
use MailPoetVendor\Doctrine\ORM\EntityRepository as DoctrineEntityRepository; use MailPoetVendor\Doctrine\ORM\EntityRepository as DoctrineEntityRepository;
use MailPoetVendor\Doctrine\ORM\Mapping\ClassMetadata; use MailPoetVendor\Doctrine\ORM\Mapping\ClassMetadata;
@@ -41,6 +43,14 @@ abstract class Repository {
return $this->doctrineRepository->findBy($criteria, $orderBy, $limit, $offset); return $this->doctrineRepository->findBy($criteria, $orderBy, $limit, $offset);
} }
/**
* @param Criteria $criteria
* @return Collection<int, T>
*/
public function matching(Criteria $criteria) {
return $this->doctrineRepository->matching($criteria);
}
public function countBy(array $criteria): int { public function countBy(array $criteria): int {
return $this->doctrineRepository->count($criteria); return $this->doctrineRepository->count($criteria);
} }

View File

@@ -22,6 +22,7 @@
var mailpoet_mss_key_invalid = <%= mss_key_invalid ? 'true' : 'false' %>; var mailpoet_mss_key_invalid = <%= mss_key_invalid ? 'true' : 'false' %>;
var mailpoet_subscribers_count = <%= subscriber_count %>; var mailpoet_subscribers_count = <%= subscriber_count %>;
var mailpoet_custom_fields = <%= json_encode(custom_fields) %>; var mailpoet_custom_fields = <%= json_encode(custom_fields) %>;
var mailpoet_static_segments_list = <%= json_encode(static_segments_list) %>;
var mailpoet_has_premium_support = <%= has_premium_support ? 'true' : 'false' %>; var mailpoet_has_premium_support = <%= has_premium_support ? 'true' : 'false' %>;
var wordpress_editable_roles_list = <%= json_encode(wordpress_editable_roles_list) %>; var wordpress_editable_roles_list = <%= json_encode(wordpress_editable_roles_list) %>;
var mailpoet_newsletters_list = <%= json_encode(newsletters_list) %>; var mailpoet_newsletters_list = <%= json_encode(newsletters_list) %>;
@@ -166,7 +167,6 @@
'inTheLast': _x('in the last', 'Meaning: "Subscriber subscribed in the last 3 days"'), 'inTheLast': _x('in the last', 'Meaning: "Subscriber subscribed in the last 3 days"'),
'notInTheLast': _x('not in the last', 'Meaning: "Subscriber subscribed not in the last 3 days"'), 'notInTheLast': _x('not in the last', 'Meaning: "Subscriber subscribed not in the last 3 days"'),
'emailActionNotOpened': _x('not opened', 'Dynamic segment creation: when newsletter was not opened'), 'emailActionNotOpened': _x('not opened', 'Dynamic segment creation: when newsletter was not opened'),
'emailActionClicked': _x('clicked', 'Dynamic segment creation: when a newsletter link was clicked'), 'emailActionClicked': _x('clicked', 'Dynamic segment creation: when a newsletter link was clicked'),
'emailActionClickedAnyEmail': _x('clicked any email', 'Dynamic segment creation: when a newsletter link in any email was clicked'), 'emailActionClickedAnyEmail': _x('clicked any email', 'Dynamic segment creation: when a newsletter link in any email was clicked'),
@@ -185,6 +185,7 @@
'segmentsActiveSubscription': __('has an active subscription'), 'segmentsActiveSubscription': __('has an active subscription'),
'woocommerceSubscriptions': _x('WooCommerce Subscriptions', 'Dynamic segment creation: User selects this to use any WooCommerce Subscriptions filters'), 'woocommerceSubscriptions': _x('WooCommerce Subscriptions', 'Dynamic segment creation: User selects this to use any WooCommerce Subscriptions filters'),
'selectWooSubscription': __('Search subscriptions'), 'selectWooSubscription': __('Search subscriptions'),
'searchLists': __('Search lists'),
'anyOf': __('any of'), 'anyOf': __('any of'),
'allOf': __('all of'), 'allOf': __('all of'),