Compare commits
92 Commits
Author | SHA1 | Date | |
---|---|---|---|
f0b779d724 | |||
dee5ff38f5 | |||
70aefb421b | |||
764e1a1bf0 | |||
b140011f92 | |||
b48c9e437e | |||
f34efb4d6f | |||
324c03e8b9 | |||
e718101425 | |||
080ce50fea | |||
1b9eb223b0 | |||
ea2fa794ac | |||
c46c61a923 | |||
1136431551 | |||
957be23212 | |||
91a88b3e91 | |||
7c5d239267 | |||
c748b80447 | |||
725e0ecb00 | |||
eb71dd8a68 | |||
ce1687cf97 | |||
2e328b6d7f | |||
8489e63d34 | |||
f8c17730fc | |||
93e43eee7a | |||
9063dc3079 | |||
9d55d3f134 | |||
d199c3768a | |||
0e0c2447d9 | |||
99dfb3d24b | |||
c4366e009b | |||
1d31202607 | |||
78d25ffb69 | |||
b710682c66 | |||
52c6b94315 | |||
fd6b49e598 | |||
df2982454e | |||
8a19fd906f | |||
99198e5c2d | |||
9204f37560 | |||
765aa6efab | |||
50b613365f | |||
4fe8d10d6c | |||
162dab790d | |||
6e8c9731d8 | |||
235552f91d | |||
4ee08c296b | |||
bda979ec4f | |||
78f10f064e | |||
eca4a68fec | |||
9daa5d58c7 | |||
6cc232fbad | |||
6a07bd44ff | |||
63cd326191 | |||
40b15b0eb1 | |||
3825e9cb11 | |||
7db2942140 | |||
3a5d28f24a | |||
d02c63844d | |||
c3045dba07 | |||
c239566e1e | |||
dfdc8cfd09 | |||
4977b7ffa2 | |||
8e69299a6a | |||
903bcbb92e | |||
8d6492ac8c | |||
c39ae1fe1f | |||
e3539b06a2 | |||
d34a265ac2 | |||
9a72e361b1 | |||
a520e4bd93 | |||
9ab6ebbe0d | |||
6a3cfd05e7 | |||
f825e535e3 | |||
a2c5420d7f | |||
c6d3573652 | |||
e41bcd0d02 | |||
f805907954 | |||
d970efc0da | |||
c0ce5944dc | |||
467f354eb1 | |||
3f016b45f9 | |||
679f74e498 | |||
08b314e0b4 | |||
cc5959805b | |||
407f3d1609 | |||
503df3584c | |||
fa9510f0c1 | |||
cc92df4e7f | |||
d76c5d32f2 | |||
c6198cba4c | |||
e29dd4286e |
@ -179,10 +179,10 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Download additional WP Plugins for tests
|
name: Download additional WP Plugins for tests
|
||||||
command: |
|
command: |
|
||||||
./do download:woo-commerce-zip 6.8.2
|
./do download:woo-commerce-zip 7.0.1
|
||||||
./do download:woo-commerce-subscriptions-zip 4.5.1
|
./do download:woo-commerce-subscriptions-zip 4.6.0
|
||||||
./do download:woo-commerce-memberships-zip 1.23.0
|
./do download:woo-commerce-memberships-zip 1.23.1
|
||||||
./do download:woo-commerce-blocks-zip 8.4.0
|
./do download:woo-commerce-blocks-zip 8.8.2
|
||||||
- run:
|
- run:
|
||||||
name: Dump tests ENV variables for acceptance tests
|
name: Dump tests ENV variables for acceptance tests
|
||||||
command: |
|
command: |
|
||||||
@ -749,6 +749,12 @@ workflows:
|
|||||||
- js_tests
|
- js_tests
|
||||||
- integration_test_woocommerce
|
- integration_test_woocommerce
|
||||||
- integration_test_base
|
- integration_test_base
|
||||||
|
- integration_test_woo_cot_no_sync
|
||||||
|
- integration_test_woo_cot_off
|
||||||
|
- integration_test_woo_cot_sync
|
||||||
|
- acceptance_tests_woo_cot_sync
|
||||||
|
- acceptance_tests_woo_cot_off
|
||||||
|
- acceptance_tests_woo_cot_no_sync
|
||||||
|
|
||||||
nightly:
|
nightly:
|
||||||
triggers:
|
triggers:
|
||||||
@ -773,14 +779,14 @@ workflows:
|
|||||||
- acceptance_tests:
|
- acceptance_tests:
|
||||||
<<: *slack-fail-post-step
|
<<: *slack-fail-post-step
|
||||||
name: acceptance_oldest
|
name: acceptance_oldest
|
||||||
woo_core_version: 6.2.2
|
woo_core_version: 6.8.0
|
||||||
woo_subscriptions_version: 4.3.0
|
woo_subscriptions_version: 4.3.0
|
||||||
woo_memberships_version: 1.21.0
|
woo_memberships_version: 1.21.0
|
||||||
woo_blocks_version: 5.3.2
|
woo_blocks_version: 6.8.0
|
||||||
mysql_command: --max_allowed_packet=100M
|
mysql_command: --max_allowed_packet=100M
|
||||||
mysql_image_version: 5.7.36
|
mysql_image_version: 5.7.36
|
||||||
codeception_image_version: 7.4-cli_20210126.1
|
codeception_image_version: 7.4-cli_20220605.0
|
||||||
wordpress_image_version: wp-5.6_php7.2_20220406.1
|
wordpress_image_version: wp-5.8_php7.3_20221104.1
|
||||||
requires:
|
requires:
|
||||||
- build
|
- build
|
||||||
- unit_tests:
|
- unit_tests:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.mailpoet-automation-workflow-add-trigger {
|
.mailpoet-automation-add-trigger {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px dashed #c3c4c7;
|
border: 1px dashed #c3c4c7;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
.mailpoet-automation-editor-workflow {
|
.mailpoet-automation-editor-automation {
|
||||||
background: #fbfbfb;
|
background: #fbfbfb;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mailpoet-automation-editor-workflow-wrapper {
|
.mailpoet-automation-editor-automation-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
padding: 50px 20px;
|
padding: 50px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mailpoet-automation-editor-workflow-end {
|
.mailpoet-automation-editor-automation-end {
|
||||||
background: #8c8f94;
|
background: #8c8f94;
|
||||||
border-radius: 999999px;
|
border-radius: 999999px;
|
||||||
fill: white;
|
fill: white;
|
@ -1,4 +1,4 @@
|
|||||||
.mailpoet-automation-editor-empty-workflow {
|
.mailpoet-automation-editor-empty-automation {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: grid;
|
display: grid;
|
||||||
height: 100%;
|
height: 100%;
|
@ -119,6 +119,10 @@
|
|||||||
color: $color-stats-average;
|
color: $color-stats-average;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mailpoet-statistics-value-number-critical {
|
||||||
|
color: $color-stats-critical;
|
||||||
|
}
|
||||||
|
|
||||||
.mailpoet-statistics-value-number-excellent {
|
.mailpoet-statistics-value-number-excellent {
|
||||||
color: $color-stats-excellent;
|
color: $color-stats-excellent;
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mailpoet-tag-critical {
|
||||||
|
border-color: $color-stats-critical;
|
||||||
|
color: $color-stats-critical;
|
||||||
|
|
||||||
|
&.mailpoet-tag-inverted {
|
||||||
|
background: $color-stats-critical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mailpoet-tag-good {
|
.mailpoet-tag-good {
|
||||||
border-color: $color-stats-good;
|
border-color: $color-stats-good;
|
||||||
color: $color-stats-good;
|
color: $color-stats-good;
|
||||||
|
@ -11,17 +11,17 @@
|
|||||||
|
|
||||||
@import './components-automation-editor/add-step-button';
|
@import './components-automation-editor/add-step-button';
|
||||||
@import './components-automation-editor/add-trigger';
|
@import './components-automation-editor/add-trigger';
|
||||||
|
@import './components-automation-editor/automation';
|
||||||
@import './components-automation-editor/block-icon';
|
@import './components-automation-editor/block-icon';
|
||||||
@import './components-automation-editor/chip';
|
@import './components-automation-editor/chip';
|
||||||
@import './components-automation-editor/dropdown';
|
@import './components-automation-editor/dropdown';
|
||||||
@import './components-automation-editor/empty-workflow';
|
@import './components-automation-editor/empty-automation';
|
||||||
@import './components-automation-editor/errors';
|
@import './components-automation-editor/errors';
|
||||||
@import './components-automation-editor/panel';
|
@import './components-automation-editor/panel';
|
||||||
@import './components-automation-editor/separator';
|
@import './components-automation-editor/separator';
|
||||||
@import './components-automation-editor/status';
|
@import './components-automation-editor/status';
|
||||||
@import './components-automation-editor/step';
|
@import './components-automation-editor/step';
|
||||||
@import './components-automation-editor/step-card';
|
@import './components-automation-editor/step-card';
|
||||||
@import './components-automation-editor/workflow';
|
|
||||||
@import './components-automation-editor/notices';
|
@import './components-automation-editor/notices';
|
||||||
@import './components-automation-editor/deactivate-modal';
|
@import './components-automation-editor/deactivate-modal';
|
||||||
|
|
||||||
|
@ -17,17 +17,30 @@ ul.mailpoet-automation-templates {
|
|||||||
|
|
||||||
.mailpoet-automation-template-list-item {
|
.mailpoet-automation-template-list-item {
|
||||||
button.components-button {
|
button.components-button {
|
||||||
|
align-content: baseline;
|
||||||
|
align-items: flex-start;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #dcdcde;
|
border: 1px solid #dcdcde;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-rows: 40px auto auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 24px 24px 26px;
|
padding: 24px 24px 26px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
&:disabled,
|
||||||
|
&[aria-disabled='true'] {
|
||||||
|
color: #787c82;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #787c82;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #dcdcde;
|
border: 1px solid #dcdcde;
|
||||||
@ -49,7 +62,6 @@ ul.mailpoet-automation-templates {
|
|||||||
h2 {
|
h2 {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
color: #2271b1;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
@ -78,4 +90,27 @@ ul.mailpoet-automation-templates {
|
|||||||
fill: #dcdcde;
|
fill: #dcdcde;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
text-align: right;
|
||||||
|
transform: translateX(24px);
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding: 3px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-template-list-item-coming-soon {
|
||||||
|
.badge span {
|
||||||
|
background: #ffe9cc;
|
||||||
|
color: #1d2327;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-template-list-item-premium {
|
||||||
|
.badge span {
|
||||||
|
background: #ff5301;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,7 @@ $color-badge-video-guide: #46b450;
|
|||||||
$color-stats-average: #f559c3;
|
$color-stats-average: #f559c3;
|
||||||
$color-stats-good: #ff9f00;
|
$color-stats-good: #ff9f00;
|
||||||
$color-stats-excellent: #7ed321;
|
$color-stats-excellent: #7ed321;
|
||||||
|
$color-stats-critical: #f00;
|
||||||
$color-stats-unknown: $color-primary-inactive;
|
$color-stats-unknown: $color-primary-inactive;
|
||||||
|
|
||||||
// Automation editor
|
// Automation editor
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
import { api } from '../config';
|
|
||||||
|
|
||||||
const API_URL = `${api.root}/mailpoet/v1/automation`;
|
|
||||||
|
|
||||||
export const request = (
|
|
||||||
path: string,
|
|
||||||
init?: RequestInit,
|
|
||||||
): ReturnType<typeof fetch> => fetch(`${API_URL}/${path}`, init);
|
|
||||||
|
|
||||||
type Error<T> = {
|
|
||||||
response?: Response;
|
|
||||||
data?: T;
|
|
||||||
};
|
|
||||||
|
|
||||||
type State<T> = {
|
|
||||||
data?: T;
|
|
||||||
loading: boolean;
|
|
||||||
error?: Error<T>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Result<T> = [(init?: RequestInit) => Promise<void>, State<T>];
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
type Data = Record<string, any>;
|
|
||||||
|
|
||||||
export const useMutation = <T extends Data>(
|
|
||||||
path: string,
|
|
||||||
config?: RequestInit,
|
|
||||||
): Result<T> => {
|
|
||||||
const [state, setState] = useState<State<T>>({
|
|
||||||
data: undefined,
|
|
||||||
loading: false,
|
|
||||||
error: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mutation = useCallback(
|
|
||||||
async (init?: RequestInit) => {
|
|
||||||
setState((prevState) => ({ ...prevState, loading: true }));
|
|
||||||
const response = await request(path, {
|
|
||||||
...config,
|
|
||||||
...init,
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
...(init?.headers ?? {}),
|
|
||||||
'x-wp-nonce': api.nonce,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await response.json();
|
|
||||||
const error = response.ok ? null : { ...response, data };
|
|
||||||
setState((prevState) => ({ ...prevState, data, error }));
|
|
||||||
} catch (_) {
|
|
||||||
const error = { response };
|
|
||||||
setState((prevState) => ({ ...prevState, error }));
|
|
||||||
} finally {
|
|
||||||
setState((prevState) => ({ ...prevState, loading: false }));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[config, path],
|
|
||||||
);
|
|
||||||
|
|
||||||
return [mutation, state];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useQuery = <T extends Data>(
|
|
||||||
path: string,
|
|
||||||
init?: RequestInit,
|
|
||||||
): State<T> => {
|
|
||||||
const [mutation, result] = useMutation<T>(path, init);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
void mutation();
|
|
||||||
},
|
|
||||||
[] /* eslint-disable-line react-hooks/exhaustive-deps -- request only on initial load */,
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
@ -1,9 +1,7 @@
|
|||||||
import apiFetch from '@wordpress/api-fetch';
|
import apiFetch from '@wordpress/api-fetch';
|
||||||
import { api } from '../config';
|
import { api } from '../config';
|
||||||
|
|
||||||
export * from './hooks';
|
const apiUrl = `${api.root}/mailpoet/v1/`;
|
||||||
|
|
||||||
const apiUrl = `${api.root}/mailpoet/v1/automation/`;
|
|
||||||
|
|
||||||
export type ApiError = {
|
export type ApiError = {
|
||||||
code?: string;
|
code?: string;
|
||||||
|
@ -1,24 +1,33 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { TopBarWithBeamer } from 'common/top_bar/top_bar';
|
import { TopBarWithBeamer } from 'common/top_bar/top_bar';
|
||||||
import { Popover, SlotFillProvider } from '@wordpress/components';
|
import { Popover, SlotFillProvider } from '@wordpress/components';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
import { initializeApi, useMutation } from './api';
|
import { initializeApi } from './api';
|
||||||
import { registerTranslations } from './i18n';
|
import { registerTranslations } from './i18n';
|
||||||
import { createStore, storeName } from './listing/store';
|
import { createStore, storeName } from './listing/store';
|
||||||
import { AutomationListing, AutomationListingHeader } from './listing';
|
import { AutomationListing, AutomationListingHeader } from './listing';
|
||||||
import { registerApiErrorHandler } from './listing/api-error-handler';
|
import { registerApiErrorHandler } from './listing/api-error-handler';
|
||||||
import { Notices } from './listing/components/notices';
|
import { Notices } from './listing/components/notices';
|
||||||
import { WorkflowListingNotices } from './listing/workflow-listing-notices';
|
|
||||||
import { BuildYourOwnSection, HeroSection, TemplatesSection } from './sections';
|
import { BuildYourOwnSection, HeroSection, TemplatesSection } from './sections';
|
||||||
import {
|
import { MailPoet } from '../mailpoet';
|
||||||
CreateEmptyWorkflowButton,
|
|
||||||
CreateWorkflowFromTemplateButton,
|
const trackOpenEvent = () => {
|
||||||
} from './testing';
|
MailPoet.trackEvent('Automations > Listing viewed');
|
||||||
|
};
|
||||||
|
|
||||||
function Content(): JSX.Element {
|
function Content(): JSX.Element {
|
||||||
const count = useSelect((select) => select(storeName).getWorkflowCount());
|
const [isBooting, setIsBooting] = useState(true);
|
||||||
|
const count = useSelect((select) => select(storeName).getAutomationCount());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isBooting || count === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trackOpenEvent();
|
||||||
|
setIsBooting(false);
|
||||||
|
}, [isBooting, count]);
|
||||||
const content =
|
const content =
|
||||||
count > 0 ? (
|
count > 0 ? (
|
||||||
<>
|
<>
|
||||||
@ -49,7 +58,7 @@ function Content(): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Workflows(): JSX.Element {
|
function Automations(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopBarWithBeamer />
|
<TopBarWithBeamer />
|
||||||
@ -59,79 +68,12 @@ function Workflows(): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RecreateSchemaButton(): JSX.Element {
|
|
||||||
const [createSchema, { loading, error }] = useMutation('system/database', {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<WorkflowListingNotices />
|
|
||||||
<button
|
|
||||||
className="button button-link-delete"
|
|
||||||
type="button"
|
|
||||||
onClick={() => createSchema()}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
Recreate DB schema (data will be lost)
|
|
||||||
</button>
|
|
||||||
{error && (
|
|
||||||
<div>{error?.data?.message ?? 'An unknown error occurred'}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function DeleteSchemaButton(): JSX.Element {
|
|
||||||
const [deleteSchema, { loading, error }] = useMutation('system/database', {
|
|
||||||
method: 'DELETE',
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
className="button button-link-delete"
|
|
||||||
type="button"
|
|
||||||
onClick={async () => {
|
|
||||||
await deleteSchema();
|
|
||||||
window.location.href =
|
|
||||||
'/wp-admin/admin.php?page=mailpoet-experimental';
|
|
||||||
}}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
Delete DB schema & deactivate feature
|
|
||||||
</button>
|
|
||||||
{error && (
|
|
||||||
<div>{error?.data?.message ?? 'An unknown error occurred'}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function App(): JSX.Element {
|
function App(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<SlotFillProvider>
|
<SlotFillProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<div>
|
<Automations />
|
||||||
<Workflows />
|
<Popover.Slot />
|
||||||
<div style={{ marginTop: 30, display: 'grid', gridGap: 8 }}>
|
|
||||||
<CreateEmptyWorkflowButton />
|
|
||||||
<CreateWorkflowFromTemplateButton slug="simple-welcome-email">
|
|
||||||
Create testing workflow from template (welcome email)
|
|
||||||
</CreateWorkflowFromTemplateButton>
|
|
||||||
<CreateWorkflowFromTemplateButton slug="welcome-email-sequence">
|
|
||||||
Create testing workflow from template (welcome sequence, only
|
|
||||||
premium)
|
|
||||||
</CreateWorkflowFromTemplateButton>
|
|
||||||
<CreateWorkflowFromTemplateButton slug="advanced-welcome-email-sequence">
|
|
||||||
Create testing workflow from template (advanced welcome sequence,
|
|
||||||
only premium)
|
|
||||||
</CreateWorkflowFromTemplateButton>
|
|
||||||
<RecreateSchemaButton />
|
|
||||||
<DeleteSchemaButton />
|
|
||||||
</div>
|
|
||||||
<Popover.Slot />
|
|
||||||
</div>
|
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</SlotFillProvider>
|
</SlotFillProvider>
|
||||||
);
|
);
|
||||||
|
@ -4,9 +4,9 @@ declare global {
|
|||||||
root: string;
|
root: string;
|
||||||
nonce: string;
|
nonce: string;
|
||||||
};
|
};
|
||||||
mailpoet_workflow_count: number;
|
mailpoet_automation_count: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const api = window.mailpoet_automation_api;
|
export const api = window.mailpoet_automation_api;
|
||||||
export const workflowCount = window.mailpoet_workflow_count;
|
export const automationCount = window.mailpoet_automation_count;
|
||||||
|
@ -19,7 +19,7 @@ export const registerApiErrorHandler = (): void =>
|
|||||||
const status = errorObject.data?.status;
|
const status = errorObject.data?.status;
|
||||||
const code = errorObject.code;
|
const code = errorObject.code;
|
||||||
|
|
||||||
if (code === 'mailpoet_automation_workflow_not_valid') {
|
if (code === 'mailpoet_automation_not_valid') {
|
||||||
dispatch(storeName).setErrors({ steps: errorObject.data.errors });
|
dispatch(storeName).setErrors({ steps: errorObject.data.errors });
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,9 @@ import { storeName } from '../../store';
|
|||||||
|
|
||||||
export function TrashButton(): JSX.Element {
|
export function TrashButton(): JSX.Element {
|
||||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||||
const { workflow } = useSelect(
|
const { automation } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
workflow: select(storeName).getWorkflowData(),
|
automation: select(storeName).getAutomationData(),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -33,7 +33,7 @@ export function TrashButton(): JSX.Element {
|
|||||||
>
|
>
|
||||||
{sprintf(
|
{sprintf(
|
||||||
__('You are about to delete the automation "%s".', 'mailpoet'),
|
__('You are about to delete the automation "%s".', 'mailpoet'),
|
||||||
workflow.name,
|
automation.name,
|
||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
{__(' This will stop it for all subscribers immediately.', 'mailpoet')}
|
{__(' This will stop it for all subscribers immediately.', 'mailpoet')}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { __unstableCompositeItem as CompositeItem } from '@wordpress/components';
|
import { __unstableCompositeItem as CompositeItem } from '@wordpress/components';
|
||||||
import { Icon, plus } from '@wordpress/icons';
|
import { Icon, plus } from '@wordpress/icons';
|
||||||
import { WorkflowCompositeContext } from './context';
|
import { AutomationCompositeContext } from './context';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClick?: (element: HTMLButtonElement) => void;
|
onClick?: (element: HTMLButtonElement) => void;
|
||||||
@ -9,7 +9,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function AddStepButton({ onClick, previousStepId }: Props): JSX.Element {
|
export function AddStepButton({ onClick, previousStepId }: Props): JSX.Element {
|
||||||
const compositeState = useContext(WorkflowCompositeContext);
|
const compositeState = useContext(AutomationCompositeContext);
|
||||||
return (
|
return (
|
||||||
<CompositeItem
|
<CompositeItem
|
||||||
state={compositeState}
|
state={compositeState}
|
@ -3,7 +3,7 @@ import { __unstableCompositeItem as CompositeItem } from '@wordpress/components'
|
|||||||
import { Icon, plus } from '@wordpress/icons';
|
import { Icon, plus } from '@wordpress/icons';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { useDispatch } from '@wordpress/data';
|
import { useDispatch } from '@wordpress/data';
|
||||||
import { WorkflowCompositeContext } from './context';
|
import { AutomationCompositeContext } from './context';
|
||||||
import { Step } from './types';
|
import { Step } from './types';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
|
|
||||||
@ -12,14 +12,14 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function AddTrigger({ step }: Props): JSX.Element {
|
export function AddTrigger({ step }: Props): JSX.Element {
|
||||||
const compositeState = useContext(WorkflowCompositeContext);
|
const compositeState = useContext(AutomationCompositeContext);
|
||||||
const { setInserterPopover } = useDispatch(storeName);
|
const { setInserterPopover } = useDispatch(storeName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CompositeItem
|
<CompositeItem
|
||||||
state={compositeState}
|
state={compositeState}
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
className="mailpoet-automation-workflow-add-trigger"
|
className="mailpoet-automation-add-trigger"
|
||||||
data-previous-step-id={step.id}
|
data-previous-step-id={step.id}
|
||||||
focusable
|
focusable
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
@ -1,5 +1,5 @@
|
|||||||
import { __unstableUseCompositeState as useCompositeState } from '@wordpress/components';
|
import { __unstableUseCompositeState as useCompositeState } from '@wordpress/components';
|
||||||
import { createContext } from '@wordpress/element';
|
import { createContext } from '@wordpress/element';
|
||||||
|
|
||||||
export const WorkflowCompositeContext =
|
export const AutomationCompositeContext =
|
||||||
createContext<ReturnType<typeof useCompositeState>>(undefined);
|
createContext<ReturnType<typeof useCompositeState>>(undefined);
|
@ -0,0 +1,9 @@
|
|||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
export function EmptyAutomation(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div className="mailpoet-automation-editor-empty-automation">
|
||||||
|
{__('No automation data.', 'mailpoet')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -7,8 +7,8 @@ import { useSelect } from '@wordpress/data';
|
|||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Icon, check } from '@wordpress/icons';
|
import { Icon, check } from '@wordpress/icons';
|
||||||
import { Hooks } from 'wp-js-hooks';
|
import { Hooks } from 'wp-js-hooks';
|
||||||
import { WorkflowCompositeContext } from './context';
|
import { AutomationCompositeContext } from './context';
|
||||||
import { EmptyWorkflow } from './empty-workflow';
|
import { EmptyAutomation } from './empty-automation';
|
||||||
import { Separator } from './separator';
|
import { Separator } from './separator';
|
||||||
import { Step } from './step';
|
import { Step } from './step';
|
||||||
import { Step as StepData } from './types';
|
import { Step as StepData } from './types';
|
||||||
@ -21,10 +21,10 @@ import {
|
|||||||
RenderStepType,
|
RenderStepType,
|
||||||
} from '../../../types/filters';
|
} from '../../../types/filters';
|
||||||
|
|
||||||
export function Workflow(): JSX.Element {
|
export function Automation(): JSX.Element {
|
||||||
const { workflowData, selectedStep } = useSelect(
|
const { automationData, selectedStep } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
workflowData: select(storeName).getWorkflowData(),
|
automationData: select(storeName).getAutomationData(),
|
||||||
selectedStep: select(storeName).getSelectedStep(),
|
selectedStep: select(storeName).getSelectedStep(),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
@ -36,9 +36,9 @@ export function Workflow(): JSX.Element {
|
|||||||
shift: true,
|
shift: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const stepMap = workflowData?.steps ?? undefined;
|
const stepMap = automationData?.steps ?? undefined;
|
||||||
|
|
||||||
// serialize steps (for now, we support only one trigger and linear workflows)
|
// serialize steps (for now, we support only one trigger and linear automations)
|
||||||
const steps = useMemo(() => {
|
const steps = useMemo(() => {
|
||||||
const stepArray = [stepMap.root];
|
const stepArray = [stepMap.root];
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ export function Workflow(): JSX.Element {
|
|||||||
const renderStep = useMemo(
|
const renderStep = useMemo(
|
||||||
(): RenderStepType =>
|
(): RenderStepType =>
|
||||||
Hooks.applyFilters(
|
Hooks.applyFilters(
|
||||||
'mailpoet.automation.workflow.render_step',
|
'mailpoet.automation.render_step',
|
||||||
(stepData: StepData) =>
|
(stepData: StepData) =>
|
||||||
stepData.type === 'root' ? (
|
stepData.type === 'root' ? (
|
||||||
<AddTrigger step={stepData} />
|
<AddTrigger step={stepData} />
|
||||||
@ -73,7 +73,7 @@ export function Workflow(): JSX.Element {
|
|||||||
const renderSeparator = useMemo(
|
const renderSeparator = useMemo(
|
||||||
(): RenderStepSeparatorType =>
|
(): RenderStepSeparatorType =>
|
||||||
Hooks.applyFilters(
|
Hooks.applyFilters(
|
||||||
'mailpoet.automation.workflow.render_step_separator',
|
'mailpoet.automation.render_step_separator',
|
||||||
(previousStepData: StepData) => (
|
(previousStepData: StepData) => (
|
||||||
<Separator previousStepId={previousStepData.id} />
|
<Separator previousStepId={previousStepData.id} />
|
||||||
),
|
),
|
||||||
@ -81,20 +81,20 @@ export function Workflow(): JSX.Element {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!workflowData) {
|
if (!automationData) {
|
||||||
return <EmptyWorkflow />;
|
return <EmptyAutomation />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowCompositeContext.Provider value={compositeState}>
|
<AutomationCompositeContext.Provider value={compositeState}>
|
||||||
<Composite
|
<Composite
|
||||||
state={compositeState}
|
state={compositeState}
|
||||||
role="tree"
|
role="tree"
|
||||||
aria-label={__('Automation', 'mailpoet')}
|
aria-label={__('Automation', 'mailpoet')}
|
||||||
aria-orientation="vertical"
|
aria-orientation="vertical"
|
||||||
className="mailpoet-automation-editor-workflow"
|
className="mailpoet-automation-editor-automation"
|
||||||
>
|
>
|
||||||
<div className="mailpoet-automation-editor-workflow-wrapper">
|
<div className="mailpoet-automation-editor-automation-wrapper">
|
||||||
<Statistics />
|
<Statistics />
|
||||||
{stepMap.root.next_steps.length === 0 ? (
|
{stepMap.root.next_steps.length === 0 ? (
|
||||||
<>
|
<>
|
||||||
@ -119,13 +119,13 @@ export function Workflow(): JSX.Element {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
<Icon
|
<Icon
|
||||||
className="mailpoet-automation-editor-workflow-end"
|
className="mailpoet-automation-editor-automation-end"
|
||||||
icon={check}
|
icon={check}
|
||||||
/>
|
/>
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
<InserterPopover />
|
<InserterPopover />
|
||||||
</Composite>
|
</Composite>
|
||||||
</WorkflowCompositeContext.Provider>
|
</AutomationCompositeContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -4,9 +4,9 @@ import { storeName } from '../../store';
|
|||||||
import { Statistics as BaseStatistics } from '../../../components/statistics';
|
import { Statistics as BaseStatistics } from '../../../components/statistics';
|
||||||
|
|
||||||
export function Statistics(): JSX.Element {
|
export function Statistics(): JSX.Element {
|
||||||
const { workflow } = useSelect(
|
const { automation } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
workflow: select(storeName).getWorkflowData(),
|
automation: select(storeName).getAutomationData(),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -19,19 +19,19 @@ export function Statistics(): JSX.Element {
|
|||||||
key: 'entered',
|
key: 'entered',
|
||||||
// translators: Total number of subscribers who entered an automation
|
// translators: Total number of subscribers who entered an automation
|
||||||
label: _x('Total Entered', 'automation stats', 'mailpoet'),
|
label: _x('Total Entered', 'automation stats', 'mailpoet'),
|
||||||
value: workflow.stats.totals.entered,
|
value: automation.stats.totals.entered,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'processing',
|
key: 'processing',
|
||||||
// translators: Total number of subscribers who are being processed in an automation
|
// translators: Total number of subscribers who are being processed in an automation
|
||||||
label: _x('Total Processing', 'automation stats', 'mailpoet'),
|
label: _x('Total Processing', 'automation stats', 'mailpoet'),
|
||||||
value: workflow.stats.totals.in_progress,
|
value: automation.stats.totals.in_progress,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'exited',
|
key: 'exited',
|
||||||
// translators: Total number of subscribers who exited an automation, no matter the result
|
// translators: Total number of subscribers who exited an automation, no matter the result
|
||||||
label: _x('Total Exited', 'automation stats', 'mailpoet'),
|
label: _x('Total Exited', 'automation stats', 'mailpoet'),
|
||||||
value: workflow.stats.totals.exited,
|
value: automation.stats.totals.exited,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
@ -23,7 +23,7 @@ export function StepMoreMenu({ step }: Props): JSX.Element {
|
|||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
|
||||||
const moreControls: StepMoreControlsType = Hooks.applyFilters(
|
const moreControls: StepMoreControlsType = Hooks.applyFilters(
|
||||||
'mailpoet.automation.workflow.step.more-controls',
|
'mailpoet.automation.step.more-controls',
|
||||||
{
|
{
|
||||||
delete: {
|
delete: {
|
||||||
key: 'delete',
|
key: 'delete',
|
@ -4,7 +4,7 @@ import { __unstableCompositeItem as CompositeItem } from '@wordpress/components'
|
|||||||
import { useDispatch, useRegistry, useSelect } from '@wordpress/data';
|
import { useDispatch, useRegistry, useSelect } from '@wordpress/data';
|
||||||
import { blockMeta } from '@wordpress/icons';
|
import { blockMeta } from '@wordpress/icons';
|
||||||
import { __, _x } from '@wordpress/i18n';
|
import { __, _x } from '@wordpress/i18n';
|
||||||
import { WorkflowCompositeContext } from './context';
|
import { AutomationCompositeContext } from './context';
|
||||||
import { StepMoreMenu } from './step-more-menu';
|
import { StepMoreMenu } from './step-more-menu';
|
||||||
import { Step as StepData } from './types';
|
import { Step as StepData } from './types';
|
||||||
import { Chip } from '../chip';
|
import { Chip } from '../chip';
|
||||||
@ -48,7 +48,7 @@ export function Step({ step, isSelected }: Props): JSX.Element {
|
|||||||
[step],
|
[step],
|
||||||
);
|
);
|
||||||
const { openSidebar, selectStep } = useDispatch(storeName);
|
const { openSidebar, selectStep } = useDispatch(storeName);
|
||||||
const compositeState = useContext(WorkflowCompositeContext);
|
const compositeState = useContext(AutomationCompositeContext);
|
||||||
const { batch } = useRegistry();
|
const { batch } = useRegistry();
|
||||||
|
|
||||||
const compositeItemId = `step-${step.id}`;
|
const compositeItemId = `step-${step.id}`;
|
@ -1,4 +1,4 @@
|
|||||||
import { WorkflowStatus } from '../../../listing/workflow';
|
import { AutomationStatus } from '../../../listing/automation';
|
||||||
|
|
||||||
export type NextStep = {
|
export type NextStep = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -12,10 +12,10 @@ export type Step = {
|
|||||||
next_steps: NextStep[];
|
next_steps: NextStep[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Workflow = {
|
export type Automation = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
status: WorkflowStatus;
|
status: AutomationStatus;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
activated_at: string;
|
activated_at: string;
|
@ -10,7 +10,7 @@ import { useRef } from '@wordpress/element';
|
|||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { chevronDown } from '@wordpress/icons';
|
import { chevronDown } from '@wordpress/icons';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
import { WorkflowStatus } from '../../../listing/workflow';
|
import { AutomationStatus } from '../../../listing/automation';
|
||||||
|
|
||||||
// See: https://github.com/WordPress/gutenberg/blob/eff0cab2b3181c004dbd15398e570ecec28a3726/packages/edit-site/src/components/header/document-actions/index.js
|
// See: https://github.com/WordPress/gutenberg/blob/eff0cab2b3181c004dbd15398e570ecec28a3726/packages/edit-site/src/components/header/document-actions/index.js
|
||||||
|
|
||||||
@ -22,10 +22,10 @@ const Dropdown: ComponentType<
|
|||||||
> = WpDropdown;
|
> = WpDropdown;
|
||||||
|
|
||||||
export function DocumentActions({ children }): JSX.Element {
|
export function DocumentActions({ children }): JSX.Element {
|
||||||
const { workflowName, workflowStatus, showIconLabels } = useSelect(
|
const { automationName, automationStatus, showIconLabels } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
workflowName: select(storeName).getWorkflowData().name,
|
automationName: select(storeName).getAutomationData().name,
|
||||||
workflowStatus: select(storeName).getWorkflowData().status,
|
automationStatus: select(storeName).getAutomationData().status,
|
||||||
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
@ -36,9 +36,9 @@ export function DocumentActions({ children }): JSX.Element {
|
|||||||
const titleRef = useRef();
|
const titleRef = useRef();
|
||||||
|
|
||||||
let chipClass = 'mailpoet-automation-editor-chip-gray';
|
let chipClass = 'mailpoet-automation-editor-chip-gray';
|
||||||
if (workflowStatus === WorkflowStatus.ACTIVE) {
|
if (automationStatus === AutomationStatus.ACTIVE) {
|
||||||
chipClass = 'mailpoet-automation-editor-chip-success';
|
chipClass = 'mailpoet-automation-editor-chip-success';
|
||||||
} else if (workflowStatus === WorkflowStatus.DEACTIVATING) {
|
} else if (automationStatus === AutomationStatus.DEACTIVATING) {
|
||||||
chipClass = 'mailpoet-automation-editor-chip-danger';
|
chipClass = 'mailpoet-automation-editor-chip-danger';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,18 +66,18 @@ export function DocumentActions({ children }): JSX.Element {
|
|||||||
<VisuallyHidden as="span">
|
<VisuallyHidden as="span">
|
||||||
{__('Editing automation:', 'mailpoet')}
|
{__('Editing automation:', 'mailpoet')}
|
||||||
</VisuallyHidden>
|
</VisuallyHidden>
|
||||||
{workflowName}
|
{automationName}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
size="body"
|
size="body"
|
||||||
className={`edit-site-document-actions__secondary-item ${chipClass}`}
|
className={`edit-site-document-actions__secondary-item ${chipClass}`}
|
||||||
>
|
>
|
||||||
{workflowStatus === WorkflowStatus.ACTIVE &&
|
{automationStatus === AutomationStatus.ACTIVE &&
|
||||||
__('Active', 'mailpoet')}
|
__('Active', 'mailpoet')}
|
||||||
{workflowStatus === WorkflowStatus.DEACTIVATING &&
|
{automationStatus === AutomationStatus.DEACTIVATING &&
|
||||||
__('Deactivating', 'mailpoet')}
|
__('Deactivating', 'mailpoet')}
|
||||||
{workflowStatus === WorkflowStatus.DRAFT &&
|
{automationStatus === AutomationStatus.DRAFT &&
|
||||||
__('Draft', 'mailpoet')}
|
__('Draft', 'mailpoet')}
|
||||||
</Text>
|
</Text>
|
||||||
</a>
|
</a>
|
||||||
|
@ -35,17 +35,17 @@ type StepErrorProps = {
|
|||||||
function StepError({ stepId }: StepErrorProps): JSX.Element {
|
function StepError({ stepId }: StepErrorProps): JSX.Element {
|
||||||
const compositeState = useContext(ErrorsCompositeContext);
|
const compositeState = useContext(ErrorsCompositeContext);
|
||||||
|
|
||||||
const { steps, workflowData } = useSelect(
|
const { steps, automationData } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
steps: select(storeName).getSteps(),
|
steps: select(storeName).getSteps(),
|
||||||
workflowData: select(storeName).getWorkflowData(),
|
automationData: select(storeName).getAutomationData(),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { openSidebar, selectStep } = useDispatch(storeName);
|
const { openSidebar, selectStep } = useDispatch(storeName);
|
||||||
|
|
||||||
const stepData = workflowData.steps[stepId];
|
const stepData = automationData.steps[stepId];
|
||||||
const step = steps.find(({ key }) => key === stepData.key);
|
const step = steps.find(({ key }) => key === stepData.key);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -78,10 +78,10 @@ export function Errors(): JSX.Element | null {
|
|||||||
shift: true,
|
shift: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { errors, workflowData } = useSelect(
|
const { errors, automationData } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
errors: select(storeName).getErrors(),
|
errors: select(storeName).getErrors(),
|
||||||
workflowData: select(storeName).getWorkflowData(),
|
automationData: select(storeName).getAutomationData(),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -93,18 +93,18 @@ export function Errors(): JSX.Element | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const visited = new Map<string, StepErrorType | undefined>();
|
const visited = new Map<string, StepErrorType | undefined>();
|
||||||
const ids = workflowData.steps.root.next_steps.map(({ id }) => id);
|
const ids = automationData.steps.root.next_steps.map(({ id }) => id);
|
||||||
while (ids.length > 0) {
|
while (ids.length > 0) {
|
||||||
const id = ids.shift();
|
const id = ids.shift();
|
||||||
if (!visited.has(id)) {
|
if (!visited.has(id)) {
|
||||||
visited.set(id, errors.steps[id]);
|
visited.set(id, errors.steps[id]);
|
||||||
workflowData.steps[id]?.next_steps?.forEach((step) =>
|
automationData.steps[id]?.next_steps?.forEach((step) =>
|
||||||
ids.push(step.id),
|
ids.push(step.id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [...visited.values()].filter((error) => !!error);
|
return [...visited.values()].filter((error) => !!error);
|
||||||
}, [errors, workflowData]);
|
}, [errors, automationData]);
|
||||||
|
|
||||||
// automatically open the popover when errors appear
|
// automatically open the popover when errors appear
|
||||||
const hasErrors = stepErrors.length > 0;
|
const hasErrors = stepErrors.length > 0;
|
||||||
|
@ -13,7 +13,7 @@ import { Errors } from './errors';
|
|||||||
import { InserterToggle } from './inserter_toggle';
|
import { InserterToggle } from './inserter_toggle';
|
||||||
import { MoreMenu } from './more_menu';
|
import { MoreMenu } from './more_menu';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
import { WorkflowStatus } from '../../../listing/workflow';
|
import { AutomationStatus } from '../../../listing/automation';
|
||||||
import {
|
import {
|
||||||
DeactivateImmediatelyModal,
|
DeactivateImmediatelyModal,
|
||||||
DeactivateModal,
|
DeactivateModal,
|
||||||
@ -23,22 +23,23 @@ import {
|
|||||||
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/header/index.js
|
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/header/index.js
|
||||||
// https://github.com/WordPress/gutenberg/blob/0ee78b1bbe9c6f3e6df99f3b967132fa12bef77d/packages/edit-site/src/components/header/index.js
|
// https://github.com/WordPress/gutenberg/blob/0ee78b1bbe9c6f3e6df99f3b967132fa12bef77d/packages/edit-site/src/components/header/index.js
|
||||||
|
|
||||||
function ActivateButton({ onClick, label }): JSX.Element {
|
function ActivateButton({ label }): JSX.Element {
|
||||||
const { errors, isDeactivating } = useSelect(
|
const { errors, isDeactivating } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
errors: select(storeName).getErrors(),
|
errors: select(storeName).getErrors(),
|
||||||
isDeactivating:
|
isDeactivating:
|
||||||
select(storeName).getWorkflowData().status ===
|
select(storeName).getAutomationData().status ===
|
||||||
WorkflowStatus.DEACTIVATING,
|
AutomationStatus.DEACTIVATING,
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
const { openActivationPanel } = useDispatch(storeName);
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className="editor-post-publish-button"
|
className="editor-post-publish-button"
|
||||||
onClick={onClick}
|
onClick={openActivationPanel}
|
||||||
disabled={isDeactivating || !!errors}
|
disabled={isDeactivating || !!errors}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
@ -53,7 +54,7 @@ function ActivateButton({ onClick, label }): JSX.Element {
|
|||||||
// The following error seems to be a mismatch. It claims the 'delay' prop does not exist, but it does.
|
// The following error seems to be a mismatch. It claims the 'delay' prop does not exist, but it does.
|
||||||
delay={0}
|
delay={0}
|
||||||
text={__(
|
text={__(
|
||||||
'Editing an active workflow is temporarily unavailable. We are working on introducing this functionality.',
|
'Editing an active automation is temporarily unavailable. We are working on introducing this functionality.',
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -68,14 +69,14 @@ function ActivateButton({ onClick, label }): JSX.Element {
|
|||||||
function UpdateButton(): JSX.Element {
|
function UpdateButton(): JSX.Element {
|
||||||
const { save } = useDispatch(storeName);
|
const { save } = useDispatch(storeName);
|
||||||
|
|
||||||
const { workflow } = useSelect(
|
const { automation } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
workflow: select(storeName).getWorkflowData(),
|
automation: select(storeName).getAutomationData(),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (workflow.stats.totals.in_progress === 0) {
|
if (automation.stats.totals.in_progress === 0) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@ -93,7 +94,7 @@ function UpdateButton(): JSX.Element {
|
|||||||
// The following error seems to be a mismatch. It claims the 'delay' prop does not exist, but it does.
|
// The following error seems to be a mismatch. It claims the 'delay' prop does not exist, but it does.
|
||||||
delay={0}
|
delay={0}
|
||||||
text={__(
|
text={__(
|
||||||
'Editing an active workflow is temporarily unavailable. We are working on introducing this functionality.',
|
'Editing an active automation is temporarily unavailable. We are working on introducing this functionality.',
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -125,7 +126,7 @@ function DeactivateButton(): JSX.Element {
|
|||||||
const { hasUsersInProgress } = useSelect(
|
const { hasUsersInProgress } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
hasUsersInProgress:
|
hasUsersInProgress:
|
||||||
select(storeName).getWorkflowData().stats.totals.in_progress > 0,
|
select(storeName).getAutomationData().stats.totals.in_progress > 0,
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -165,7 +166,7 @@ function DeactivateNowButton(): JSX.Element {
|
|||||||
const { hasUsersInProgress } = useSelect(
|
const { hasUsersInProgress } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
hasUsersInProgress:
|
hasUsersInProgress:
|
||||||
select(storeName).getWorkflowData().stats.totals.in_progress > 0,
|
select(storeName).getAutomationData().stats.totals.in_progress > 0,
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -201,18 +202,14 @@ function DeactivateNowButton(): JSX.Element {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
showInserterToggle: boolean;
|
showInserterToggle: boolean;
|
||||||
toggleActivatePanel: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Header({
|
export function Header({ showInserterToggle }: Props): JSX.Element {
|
||||||
showInserterToggle,
|
const { setAutomationName } = useDispatch(storeName);
|
||||||
toggleActivatePanel,
|
const { automationName, automationStatus } = useSelect(
|
||||||
}: Props): JSX.Element {
|
|
||||||
const { setWorkflowName } = useDispatch(storeName);
|
|
||||||
const { workflowName, workflowStatus } = useSelect(
|
|
||||||
(select) => ({
|
(select) => ({
|
||||||
workflowName: select(storeName).getWorkflowData().name,
|
automationName: select(storeName).getAutomationData().name,
|
||||||
workflowStatus: select(storeName).getWorkflowData().status,
|
automationStatus: select(storeName).getAutomationData().status,
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -237,8 +234,8 @@ export function Header({
|
|||||||
{__('Automation name', 'mailpoet')}
|
{__('Automation name', 'mailpoet')}
|
||||||
</div>
|
</div>
|
||||||
<TextControl
|
<TextControl
|
||||||
value={workflowName}
|
value={automationName}
|
||||||
onChange={(newName) => setWorkflowName(newName)}
|
onChange={(newName) => setAutomationName(newName)}
|
||||||
help={__(
|
help={__(
|
||||||
`Give the automation a name that indicates its purpose. E.g. "Abandoned cart recovery"`,
|
`Give the automation a name that indicates its purpose. E.g. "Abandoned cart recovery"`,
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
@ -252,28 +249,22 @@ export function Header({
|
|||||||
<div className="edit-site-header_end">
|
<div className="edit-site-header_end">
|
||||||
<div className="edit-site-header__actions">
|
<div className="edit-site-header__actions">
|
||||||
<Errors />
|
<Errors />
|
||||||
{workflowStatus === WorkflowStatus.DRAFT && (
|
{automationStatus === AutomationStatus.DRAFT && (
|
||||||
<>
|
<>
|
||||||
<SaveDraftButton />
|
<SaveDraftButton />
|
||||||
<ActivateButton
|
<ActivateButton label={__('Activate', 'mailpoet')} />
|
||||||
onClick={toggleActivatePanel}
|
|
||||||
label={__('Activate', 'mailpoet')}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{workflowStatus === WorkflowStatus.ACTIVE && (
|
{automationStatus === AutomationStatus.ACTIVE && (
|
||||||
<>
|
<>
|
||||||
<DeactivateButton />
|
<DeactivateButton />
|
||||||
<UpdateButton />
|
<UpdateButton />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{workflowStatus === WorkflowStatus.DEACTIVATING && (
|
{automationStatus === AutomationStatus.DEACTIVATING && (
|
||||||
<>
|
<>
|
||||||
<DeactivateNowButton />
|
<DeactivateNowButton />
|
||||||
<ActivateButton
|
<ActivateButton label={__('Update & Activate', 'mailpoet')} />
|
||||||
onClick={toggleActivatePanel}
|
|
||||||
label={__('Update & Activate', 'mailpoet')}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<PinnedItems.Slot scope={storeName} />
|
<PinnedItems.Slot scope={storeName} />
|
||||||
|
@ -22,7 +22,7 @@ export function InserterPopover(): JSX.Element | null {
|
|||||||
|
|
||||||
const onInsert = useCallback((item: Item) => {
|
const onInsert = useCallback((item: Item) => {
|
||||||
const addStepCallback: AddStepCallbackType = Hooks.applyFilters(
|
const addStepCallback: AddStepCallbackType = Hooks.applyFilters(
|
||||||
'mailpoet.automation.workflow.add_step_callback',
|
'mailpoet.automation.add_step_callback',
|
||||||
() => {
|
() => {
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
},
|
},
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
store as keyboardShortcutsStore,
|
store as keyboardShortcutsStore,
|
||||||
} from '@wordpress/keyboard-shortcuts';
|
} from '@wordpress/keyboard-shortcuts';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store';
|
import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
|
||||||
|
|
||||||
// See:
|
// See:
|
||||||
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/keyboard-shortcuts/index.js
|
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/keyboard-shortcuts/index.js
|
||||||
@ -55,7 +55,7 @@ export function KeyboardShortcuts(): null {
|
|||||||
} else {
|
} else {
|
||||||
const sidebarToOpen = selectedStep()
|
const sidebarToOpen = selectedStep()
|
||||||
? stepSidebarKey
|
? stepSidebarKey
|
||||||
: workflowSidebarKey;
|
: automationSidebarKey;
|
||||||
openSidebar(sidebarToOpen);
|
openSidebar(sidebarToOpen);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@ import { Button, Modal } from '@wordpress/components';
|
|||||||
import { __, sprintf } from '@wordpress/i18n';
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
import { dispatch, useSelect } from '@wordpress/data';
|
import { dispatch, useSelect } from '@wordpress/data';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
import { WorkflowStatus } from '../../../listing/workflow';
|
import { AutomationStatus } from '../../../listing/automation';
|
||||||
|
|
||||||
type DeactivateImmediatelyModalProps = {
|
type DeactivateImmediatelyModalProps = {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@ -49,20 +49,20 @@ type DeactivateModalProps = {
|
|||||||
export function DeactivateModal({
|
export function DeactivateModal({
|
||||||
onClose,
|
onClose,
|
||||||
}: DeactivateModalProps): JSX.Element {
|
}: DeactivateModalProps): JSX.Element {
|
||||||
const { workflowName } = useSelect(
|
const { automationName } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
workflowName: select(storeName).getWorkflowData().name,
|
automationName: select(storeName).getAutomationData().name,
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
const [selected, setSelected] = useState<
|
const [selected, setSelected] = useState<
|
||||||
WorkflowStatus.DRAFT | WorkflowStatus.DEACTIVATING
|
AutomationStatus.DRAFT | AutomationStatus.DEACTIVATING
|
||||||
>(WorkflowStatus.DEACTIVATING);
|
>(AutomationStatus.DEACTIVATING);
|
||||||
const [isBusy, setIsBusy] = useState<boolean>(false);
|
const [isBusy, setIsBusy] = useState<boolean>(false);
|
||||||
// translators: %s is the name of the automation.
|
// translators: %s is the name of the automation.
|
||||||
const title = sprintf(
|
const title = sprintf(
|
||||||
__('Deactivate the "%s" automation?', 'mailpoet'),
|
__('Deactivate the "%s" automation?', 'mailpoet'),
|
||||||
workflowName,
|
automationName,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -79,7 +79,7 @@ export function DeactivateModal({
|
|||||||
<li>
|
<li>
|
||||||
<label
|
<label
|
||||||
className={
|
className={
|
||||||
selected === WorkflowStatus.DEACTIVATING
|
selected === AutomationStatus.DEACTIVATING
|
||||||
? 'mailpoet-automation-option active'
|
? 'mailpoet-automation-option active'
|
||||||
: 'mailpoet-automation-option'
|
: 'mailpoet-automation-option'
|
||||||
}
|
}
|
||||||
@ -89,8 +89,8 @@ export function DeactivateModal({
|
|||||||
type="radio"
|
type="radio"
|
||||||
disabled={isBusy}
|
disabled={isBusy}
|
||||||
name="deactivation-method"
|
name="deactivation-method"
|
||||||
checked={selected === WorkflowStatus.DEACTIVATING}
|
checked={selected === AutomationStatus.DEACTIVATING}
|
||||||
onChange={() => setSelected(WorkflowStatus.DEACTIVATING)}
|
onChange={() => setSelected(AutomationStatus.DEACTIVATING)}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
@ -107,7 +107,7 @@ export function DeactivateModal({
|
|||||||
<li>
|
<li>
|
||||||
<label
|
<label
|
||||||
className={
|
className={
|
||||||
selected === WorkflowStatus.DRAFT
|
selected === AutomationStatus.DRAFT
|
||||||
? 'mailpoet-automation-option active'
|
? 'mailpoet-automation-option active'
|
||||||
: 'mailpoet-automation-option'
|
: 'mailpoet-automation-option'
|
||||||
}
|
}
|
||||||
@ -117,8 +117,8 @@ export function DeactivateModal({
|
|||||||
type="radio"
|
type="radio"
|
||||||
disabled={isBusy}
|
disabled={isBusy}
|
||||||
name="deactivation-method"
|
name="deactivation-method"
|
||||||
checked={selected === WorkflowStatus.DRAFT}
|
checked={selected === AutomationStatus.DRAFT}
|
||||||
onChange={() => setSelected(WorkflowStatus.DRAFT)}
|
onChange={() => setSelected(AutomationStatus.DRAFT)}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
@ -140,7 +140,7 @@ export function DeactivateModal({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsBusy(true);
|
setIsBusy(true);
|
||||||
dispatch(storeName).deactivate(
|
dispatch(storeName).deactivate(
|
||||||
selected !== WorkflowStatus.DEACTIVATING,
|
selected !== AutomationStatus.DEACTIVATING,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -4,7 +4,7 @@ import { Button, Spinner } from '@wordpress/components';
|
|||||||
import { closeSmall } from '@wordpress/icons';
|
import { closeSmall } from '@wordpress/icons';
|
||||||
import { __, sprintf } from '@wordpress/i18n';
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
import { WorkflowStatus } from '../../../listing/workflow';
|
import { AutomationStatus } from '../../../listing/automation';
|
||||||
import { MailPoet } from '../../../../mailpoet';
|
import { MailPoet } from '../../../../mailpoet';
|
||||||
|
|
||||||
function PreStep({ onClose }): JSX.Element {
|
function PreStep({ onClose }): JSX.Element {
|
||||||
@ -58,9 +58,9 @@ function PreStep({ onClose }): JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PostStep({ onClose }): JSX.Element {
|
function PostStep({ onClose }): JSX.Element {
|
||||||
const { workflow } = useSelect(
|
const { automation } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
workflow: select(storeName).getWorkflowData(),
|
automation: select(storeName).getAutomationData(),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -81,7 +81,7 @@ function PostStep({ onClose }): JSX.Element {
|
|||||||
|
|
||||||
<div className="mailpoet-automation-activate-panel__body">
|
<div className="mailpoet-automation-activate-panel__body">
|
||||||
<div className="mailpoet-automation-activate-panel__section">
|
<div className="mailpoet-automation-activate-panel__section">
|
||||||
{sprintf(__('"%s" is now live.', 'mailpoet'), workflow.name)}
|
{sprintf(__('"%s" is now live.', 'mailpoet'), automation.name)}
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
<strong>{__('What’s next?', 'mailpoet')}</strong>
|
<strong>{__('What’s next?', 'mailpoet')}</strong>
|
||||||
@ -100,29 +100,31 @@ function PostStep({ onClose }): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ActivatePanel({ onClose }): JSX.Element {
|
export function ActivatePanel(): JSX.Element {
|
||||||
const { workflow, errors } = useSelect(
|
const { automation, errors } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
errors: select(storeName).getErrors(),
|
errors: select(storeName).getErrors(),
|
||||||
workflow: select(storeName).getWorkflowData(),
|
automation: select(storeName).getAutomationData(),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { closeActivationPanel } = useDispatch(storeName);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (errors) {
|
if (errors) {
|
||||||
onClose();
|
closeActivationPanel();
|
||||||
}
|
}
|
||||||
}, [errors, onClose]);
|
}, [errors, closeActivationPanel]);
|
||||||
|
|
||||||
if (errors) {
|
if (errors) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const isActive = workflow.status === WorkflowStatus.ACTIVE;
|
const isActive = automation.status === AutomationStatus.ACTIVE;
|
||||||
return (
|
return (
|
||||||
<div className="mailpoet-automation-activate-panel">
|
<div className="mailpoet-automation-activate-panel">
|
||||||
{isActive && <PostStep onClose={onClose} />}
|
{isActive && <PostStep onClose={closeActivationPanel} />}
|
||||||
{!isActive && <PreStep onClose={onClose} />}
|
{!isActive && <PreStep onClose={closeActivationPanel} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@ import { __ } from '@wordpress/i18n';
|
|||||||
import { storeName } from '../../../store';
|
import { storeName } from '../../../store';
|
||||||
import { TrashButton } from '../../actions/trash-button';
|
import { TrashButton } from '../../actions/trash-button';
|
||||||
|
|
||||||
export function WorkflowSidebar(): JSX.Element {
|
export function AutomationSidebar(): JSX.Element {
|
||||||
const { workflowData } = useSelect(
|
const { automationData } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
workflowData: select(storeName).getWorkflowData(),
|
automationData: select(storeName).getAutomationData(),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -22,30 +22,30 @@ export function WorkflowSidebar(): JSX.Element {
|
|||||||
<PanelBody title={__('Automation details', 'mailpoet')} initialOpen>
|
<PanelBody title={__('Automation details', 'mailpoet')} initialOpen>
|
||||||
<PanelRow>
|
<PanelRow>
|
||||||
<strong>Date added</strong>{' '}
|
<strong>Date added</strong>{' '}
|
||||||
{new Date(Date.parse(workflowData.created_at)).toLocaleDateString(
|
{new Date(Date.parse(automationData.created_at)).toLocaleDateString(
|
||||||
undefined,
|
undefined,
|
||||||
dateOptions,
|
dateOptions,
|
||||||
)}
|
)}
|
||||||
</PanelRow>
|
</PanelRow>
|
||||||
<PanelRow>
|
<PanelRow>
|
||||||
<strong>Activated</strong>{' '}
|
<strong>Activated</strong>{' '}
|
||||||
{workflowData.status === 'active' &&
|
{automationData.status === 'active' &&
|
||||||
new Date(Date.parse(workflowData.updated_at)).toLocaleDateString(
|
new Date(Date.parse(automationData.updated_at)).toLocaleDateString(
|
||||||
undefined,
|
undefined,
|
||||||
dateOptions,
|
dateOptions,
|
||||||
)}
|
)}
|
||||||
{workflowData.status !== 'active' &&
|
{automationData.status !== 'active' &&
|
||||||
workflowData.activated_at &&
|
automationData.activated_at &&
|
||||||
new Date(Date.parse(workflowData.activated_at)).toLocaleDateString(
|
new Date(Date.parse(automationData.activated_at)).toLocaleDateString(
|
||||||
undefined,
|
undefined,
|
||||||
dateOptions,
|
dateOptions,
|
||||||
)}
|
)}
|
||||||
{workflowData.status !== 'active' && !workflowData.activated_at && (
|
{automationData.status !== 'active' && !automationData.activated_at && (
|
||||||
<span className="mailpoet-deactive">Not activated yet.</span>
|
<span className="mailpoet-deactive">Not activated yet.</span>
|
||||||
)}
|
)}
|
||||||
</PanelRow>
|
</PanelRow>
|
||||||
<PanelRow>
|
<PanelRow>
|
||||||
<strong>Author</strong> {workflowData.author.name}
|
<strong>Author</strong> {automationData.author.name}
|
||||||
</PanelRow>
|
</PanelRow>
|
||||||
<PanelRow>
|
<PanelRow>
|
||||||
<TrashButton />
|
<TrashButton />
|
@ -1,7 +1,7 @@
|
|||||||
import { Button } from '@wordpress/components';
|
import { Button } from '@wordpress/components';
|
||||||
import { useDispatch } from '@wordpress/data';
|
import { useDispatch } from '@wordpress/data';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store';
|
import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
|
||||||
|
|
||||||
// See:
|
// See:
|
||||||
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/sidebar/settings-header/index.js
|
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/sidebar/settings-header/index.js
|
||||||
@ -13,11 +13,11 @@ type Props = {
|
|||||||
|
|
||||||
export function Header({ sidebarKey }: Props): JSX.Element {
|
export function Header({ sidebarKey }: Props): JSX.Element {
|
||||||
const { openSidebar } = useDispatch(storeName);
|
const { openSidebar } = useDispatch(storeName);
|
||||||
const openWorkflowSettings = () => openSidebar(workflowSidebarKey);
|
const openAutomationSettings = () => openSidebar(automationSidebarKey);
|
||||||
const openStepSettings = () => openSidebar(stepSidebarKey);
|
const openStepSettings = () => openSidebar(stepSidebarKey);
|
||||||
|
|
||||||
const [workflowAriaLabel, workflowActiveClass] =
|
const [automationAriaLabel, automationActiveClass] =
|
||||||
sidebarKey === workflowSidebarKey
|
sidebarKey === automationSidebarKey
|
||||||
? [__('Automation (selected)', 'mailpoet'), 'is-active']
|
? [__('Automation (selected)', 'mailpoet'), 'is-active']
|
||||||
: [__('Automation', 'mailpoet'), ''];
|
: [__('Automation', 'mailpoet'), ''];
|
||||||
|
|
||||||
@ -30,9 +30,9 @@ export function Header({ sidebarKey }: Props): JSX.Element {
|
|||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<Button
|
<Button
|
||||||
onClick={openWorkflowSettings}
|
onClick={openAutomationSettings}
|
||||||
className={`edit-site-sidebar__panel-tab ${workflowActiveClass}`}
|
className={`edit-site-sidebar__panel-tab ${automationActiveClass}`}
|
||||||
aria-label={workflowAriaLabel}
|
aria-label={automationAriaLabel}
|
||||||
data-label={__('Automation', 'mailpoet')}
|
data-label={__('Automation', 'mailpoet')}
|
||||||
>
|
>
|
||||||
{__('Automation', 'mailpoet')}
|
{__('Automation', 'mailpoet')}
|
||||||
|
@ -10,8 +10,8 @@ import {
|
|||||||
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
|
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
|
||||||
import { Header } from './header';
|
import { Header } from './header';
|
||||||
import { StepSidebar } from './step';
|
import { StepSidebar } from './step';
|
||||||
import { WorkflowSidebar } from './workflow';
|
import { AutomationSidebar } from './automation';
|
||||||
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store';
|
import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
|
||||||
|
|
||||||
// See:
|
// See:
|
||||||
// https://github.com/WordPress/gutenberg/blob/5caeae34b3fb303761e3b9432311b26f4e5ea3a6/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js
|
// https://github.com/WordPress/gutenberg/blob/5caeae34b3fb303761e3b9432311b26f4e5ea3a6/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js
|
||||||
@ -26,7 +26,7 @@ const sidebarActiveByDefault = Platform.select({
|
|||||||
type Props = ComponentProps<typeof ComplementaryArea>;
|
type Props = ComponentProps<typeof ComplementaryArea>;
|
||||||
|
|
||||||
export function Sidebar(props: Props): JSX.Element {
|
export function Sidebar(props: Props): JSX.Element {
|
||||||
const { keyboardShortcut, sidebarKey, showIconLabels, workflowName } =
|
const { keyboardShortcut, sidebarKey, showIconLabels, automationName } =
|
||||||
useSelect(
|
useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
keyboardShortcut: select(
|
keyboardShortcut: select(
|
||||||
@ -36,9 +36,9 @@ export function Sidebar(props: Props): JSX.Element {
|
|||||||
),
|
),
|
||||||
sidebarKey:
|
sidebarKey:
|
||||||
select(interfaceStore).getActiveComplementaryArea(storeName) ??
|
select(interfaceStore).getActiveComplementaryArea(storeName) ??
|
||||||
workflowSidebarKey,
|
automationSidebarKey,
|
||||||
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
||||||
workflowName: select(storeName).getWorkflowData().name,
|
automationName: select(storeName).getAutomationData().name,
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -53,14 +53,14 @@ export function Sidebar(props: Props): JSX.Element {
|
|||||||
icon={cog}
|
icon={cog}
|
||||||
className="edit-site-sidebar mailpoet-automation-sidebar"
|
className="edit-site-sidebar mailpoet-automation-sidebar"
|
||||||
panelClassName="edit-site-sidebar"
|
panelClassName="edit-site-sidebar"
|
||||||
smallScreenTitle={workflowName || __('(no title)', 'mailpoet')}
|
smallScreenTitle={automationName || __('(no title)', 'mailpoet')}
|
||||||
scope={storeName}
|
scope={storeName}
|
||||||
toggleShortcut={keyboardShortcut}
|
toggleShortcut={keyboardShortcut}
|
||||||
isActiveByDefault={sidebarActiveByDefault}
|
isActiveByDefault={sidebarActiveByDefault}
|
||||||
showIconLabels={showIconLabels}
|
showIconLabels={showIconLabels}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{sidebarKey === workflowSidebarKey && <WorkflowSidebar />}
|
{sidebarKey === automationSidebarKey && <AutomationSidebar />}
|
||||||
{sidebarKey === stepSidebarKey && <StepSidebar />}
|
{sidebarKey === stepSidebarKey && <StepSidebar />}
|
||||||
</ComplementaryArea>
|
</ComplementaryArea>
|
||||||
);
|
);
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import { __ } from '@wordpress/i18n';
|
|
||||||
|
|
||||||
export function EmptyWorkflow(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className="mailpoet-automation-editor-empty-workflow">
|
|
||||||
{__('No automation data.', 'mailpoet')}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -23,17 +23,17 @@ import { InserterSidebar } from './components/inserter-sidebar';
|
|||||||
import { KeyboardShortcuts } from './components/keyboard-shortcuts';
|
import { KeyboardShortcuts } from './components/keyboard-shortcuts';
|
||||||
import { EditorNotices } from './components/notices';
|
import { EditorNotices } from './components/notices';
|
||||||
import { Sidebar } from './components/sidebar';
|
import { Sidebar } from './components/sidebar';
|
||||||
import { Workflow } from './components/workflow';
|
import { Automation } from './components/automation';
|
||||||
import { createStore, storeName } from './store';
|
import { createStore, storeName } from './store';
|
||||||
import { initializeApi } from '../api';
|
import { initializeApi } from '../api';
|
||||||
import { initialize as initializeCoreIntegration } from '../integrations/core';
|
import { initialize as initializeCoreIntegration } from '../integrations/core';
|
||||||
import { initialize as initializeMailPoetIntegration } from '../integrations/mailpoet';
|
import { initialize as initializeMailPoetIntegration } from '../integrations/mailpoet';
|
||||||
import { MailPoet } from '../../mailpoet';
|
import { MailPoet } from '../../mailpoet';
|
||||||
import { LISTING_NOTICE_PARAMETERS } from '../listing/workflow-listing-notices';
|
import { LISTING_NOTICE_PARAMETERS } from '../listing/automation-listing-notices';
|
||||||
import { registerApiErrorHandler } from './api-error-handler';
|
import { registerApiErrorHandler } from './api-error-handler';
|
||||||
import { ActivatePanel } from './components/panel/activate-panel';
|
import { ActivatePanel } from './components/panel/activate-panel';
|
||||||
import { registerTranslations } from '../i18n';
|
import { registerTranslations } from '../i18n';
|
||||||
import { WorkflowStatus } from '../listing/workflow';
|
import { AutomationStatus } from '../listing/automation';
|
||||||
|
|
||||||
// See:
|
// See:
|
||||||
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/layout/index.js
|
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/layout/index.js
|
||||||
@ -43,27 +43,27 @@ import { WorkflowStatus } from '../listing/workflow';
|
|||||||
const showInserterSidebar = false;
|
const showInserterSidebar = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show temporary message that active workflows cant be updated
|
* Show temporary message that active automations cant be updated
|
||||||
*
|
*
|
||||||
* see MAILPOET-4744
|
* see MAILPOET-4744
|
||||||
*/
|
*/
|
||||||
function updatingActiveWorkflowNotPossible() {
|
function updatingActiveAutomationNotPossible() {
|
||||||
const workflow = globalSelect(storeName).getWorkflowData();
|
const automation = globalSelect(storeName).getAutomationData();
|
||||||
if (
|
if (
|
||||||
![WorkflowStatus.ACTIVE, WorkflowStatus.DEACTIVATING].includes(
|
![AutomationStatus.ACTIVE, AutomationStatus.DEACTIVATING].includes(
|
||||||
workflow.status,
|
automation.status,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (workflow.stats.totals.in_progress === 0) {
|
if (automation.stats.totals.in_progress === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||||
void createNotice(
|
void createNotice(
|
||||||
'success',
|
'success',
|
||||||
__(
|
__(
|
||||||
'Editing an active workflow is temporarily unavailable. We are working on introducing this functionality.',
|
'Editing an active automation is temporarily unavailable. We are working on introducing this functionality.',
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
@ -72,31 +72,53 @@ function updatingActiveWorkflowNotPossible() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUnload(event) {
|
||||||
|
if (!globalSelect(storeName).getAutomationSaved()) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
event.returnValue = __(
|
||||||
|
'There are unsaved changes that will be lost. Do you want to continue?',
|
||||||
|
'mailpoet',
|
||||||
|
);
|
||||||
|
return event.returnValue;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function useConfirmUnsaved() {
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('beforeunload', onUnload);
|
||||||
|
return () => window.removeEventListener('beforeunload', onUnload);
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
function Editor(): JSX.Element {
|
function Editor(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
isFullscreenActive,
|
isFullscreenActive,
|
||||||
isInserterOpened,
|
isInserterOpened,
|
||||||
|
isActivationPanelOpened,
|
||||||
isSidebarOpened,
|
isSidebarOpened,
|
||||||
showIconLabels,
|
showIconLabels,
|
||||||
workflow,
|
automation,
|
||||||
} = useSelect(
|
} = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
isFullscreenActive: select(storeName).isFeatureActive('fullscreenMode'),
|
isFullscreenActive: select(storeName).isFeatureActive('fullscreenMode'),
|
||||||
isInserterOpened: select(storeName).isInserterSidebarOpened(),
|
isInserterOpened: select(storeName).isInserterSidebarOpened(),
|
||||||
isSidebarOpened: select(storeName).isSidebarOpened(),
|
isSidebarOpened: select(storeName).isSidebarOpened(),
|
||||||
|
isActivationPanelOpened: select(storeName).isActivationPanelOpened(),
|
||||||
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
||||||
workflow: select(storeName).getWorkflowData(),
|
automation: select(storeName).getAutomationData(),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
const [showActivatePanel, setShowActivatePanel] = useState(false);
|
|
||||||
const [isBooting, setIsBooting] = useState(true);
|
const [isBooting, setIsBooting] = useState(true);
|
||||||
|
|
||||||
|
useConfirmUnsaved();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isBooting) {
|
if (!isBooting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updatingActiveWorkflowNotPossible();
|
updatingActiveAutomationNotPossible();
|
||||||
setIsBooting(false);
|
setIsBooting(false);
|
||||||
}, [isBooting]);
|
}, [isBooting]);
|
||||||
const className = classnames('interface-interface-skeleton', {
|
const className = classnames('interface-interface-skeleton', {
|
||||||
@ -104,17 +126,13 @@ function Editor(): JSX.Element {
|
|||||||
'show-icon-labels': showIconLabels,
|
'show-icon-labels': showIconLabels,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (workflow.status === 'trash') {
|
if (automation.status === 'trash') {
|
||||||
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
||||||
[LISTING_NOTICE_PARAMETERS.workflowHadBeenDeleted]: workflow.id,
|
[LISTING_NOTICE_PARAMETERS.automationHadBeenDeleted]: automation.id,
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleActivatePanel = () => {
|
|
||||||
setShowActivatePanel(!showActivatePanel);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ShortcutProvider>
|
<ShortcutProvider>
|
||||||
<SlotFillProvider>
|
<SlotFillProvider>
|
||||||
@ -135,16 +153,11 @@ function Editor(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
header={
|
header={<Header showInserterToggle={showInserterSidebar} />}
|
||||||
<Header
|
|
||||||
showInserterToggle={showInserterSidebar}
|
|
||||||
toggleActivatePanel={toggleActivatePanel}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
<EditorNotices />
|
<EditorNotices />
|
||||||
<Workflow />
|
<Automation />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
sidebar={<ComplementaryArea.Slot scope={storeName} />}
|
sidebar={<ComplementaryArea.Slot scope={storeName} />}
|
||||||
@ -152,7 +165,7 @@ function Editor(): JSX.Element {
|
|||||||
showInserterSidebar && isInserterOpened ? <InserterSidebar /> : null
|
showInserterSidebar && isInserterOpened ? <InserterSidebar /> : null
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{showActivatePanel && <ActivatePanel onClose={toggleActivatePanel} />}
|
{isActivationPanelOpened && <ActivatePanel />}
|
||||||
<Popover.Slot />
|
<Popover.Slot />
|
||||||
</SlotFillProvider>
|
</SlotFillProvider>
|
||||||
</ShortcutProvider>
|
</ShortcutProvider>
|
||||||
|
@ -7,14 +7,42 @@ import { store as preferencesStore } from '@wordpress/preferences';
|
|||||||
import { addQueryArgs } from '@wordpress/url';
|
import { addQueryArgs } from '@wordpress/url';
|
||||||
import { storeName } from './constants';
|
import { storeName } from './constants';
|
||||||
import { Feature, State } from './types';
|
import { Feature, State } from './types';
|
||||||
import { LISTING_NOTICE_PARAMETERS } from '../../listing/workflow-listing-notices';
|
import { LISTING_NOTICE_PARAMETERS } from '../../listing/automation-listing-notices';
|
||||||
import { MailPoet } from '../../../mailpoet';
|
import { MailPoet } from '../../../mailpoet';
|
||||||
import { WorkflowStatus } from '../../listing/workflow';
|
import { AutomationStatus } from '../../listing/automation';
|
||||||
|
|
||||||
export const openSidebar =
|
const trackErrors = (errors) => {
|
||||||
(key) =>
|
if (!errors?.steps) {
|
||||||
({ registry }) =>
|
return;
|
||||||
|
}
|
||||||
|
const payload = Object.keys(errors.steps as object).map((stepId) => {
|
||||||
|
const error = errors.steps[stepId];
|
||||||
|
const stepKey = select(storeName).getStepById(stepId)?.key;
|
||||||
|
const fields = Object.keys(error.fields as object)
|
||||||
|
.map((field) => `${stepKey}/${field}`)
|
||||||
|
.reduce((prev, next) => prev.concat(next));
|
||||||
|
return fields;
|
||||||
|
});
|
||||||
|
|
||||||
|
MailPoet.trackEvent('Automations > Automation validation error', {
|
||||||
|
errors: payload,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const openActivationPanel = () => ({
|
||||||
|
type: 'SET_ACTIVATION_PANEL_VISIBILITY',
|
||||||
|
value: true,
|
||||||
|
});
|
||||||
|
export const closeActivationPanel = () => ({
|
||||||
|
type: 'SET_ACTIVATION_PANEL_VISIBILITY',
|
||||||
|
value: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const openSidebar = (key) => {
|
||||||
|
dispatch(storeName).closeActivationPanel();
|
||||||
|
return ({ registry }) =>
|
||||||
registry.dispatch(interfaceStore).enableComplementaryArea(storeName, key);
|
registry.dispatch(interfaceStore).enableComplementaryArea(storeName, key);
|
||||||
|
};
|
||||||
|
|
||||||
export const closeSidebar =
|
export const closeSidebar =
|
||||||
() =>
|
() =>
|
||||||
@ -46,23 +74,23 @@ export function selectStep(value) {
|
|||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setWorkflowName(name) {
|
export function setAutomationName(name) {
|
||||||
const workflow = select(storeName).getWorkflowData();
|
const automation = select(storeName).getAutomationData();
|
||||||
return {
|
return {
|
||||||
type: 'UPDATE_WORKFLOW',
|
type: 'UPDATE_AUTOMATION',
|
||||||
workflow: {
|
automation: {
|
||||||
...workflow,
|
...automation,
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* save() {
|
export function* save() {
|
||||||
const workflow = select(storeName).getWorkflowData();
|
const automation = select(storeName).getAutomationData();
|
||||||
const data = yield apiFetch({
|
const data = yield apiFetch({
|
||||||
path: `/workflows/${workflow.id}`,
|
path: `/automations/${automation.id}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: { ...workflow },
|
data: { ...automation },
|
||||||
});
|
});
|
||||||
|
|
||||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||||
@ -78,23 +106,23 @@ export function* save() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'SAVE',
|
type: 'SAVE',
|
||||||
workflow: data?.data ?? workflow,
|
automation: data?.data ?? automation,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* activate() {
|
export function* activate() {
|
||||||
const workflow = select(storeName).getWorkflowData();
|
const automation = select(storeName).getAutomationData();
|
||||||
const data = yield apiFetch({
|
const data = yield apiFetch({
|
||||||
path: `/workflows/${workflow.id}`,
|
path: `/automations/${automation.id}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: {
|
data: {
|
||||||
...workflow,
|
...automation,
|
||||||
status: WorkflowStatus.ACTIVE,
|
status: AutomationStatus.ACTIVE,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||||
if (data?.data.status === WorkflowStatus.ACTIVE) {
|
if (data?.data.status === AutomationStatus.ACTIVE) {
|
||||||
void createNotice(
|
void createNotice(
|
||||||
'success',
|
'success',
|
||||||
__('Well done! Automation is now activated!', 'mailpoet'),
|
__('Well done! Automation is now activated!', 'mailpoet'),
|
||||||
@ -102,29 +130,33 @@ export function* activate() {
|
|||||||
type: 'snackbar',
|
type: 'snackbar',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
MailPoet.trackEvent('Automations > Automation activated');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'ACTIVATE',
|
type: 'ACTIVATE',
|
||||||
workflow: data?.data ?? workflow,
|
automation: data?.data ?? automation,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* deactivate(deactivateWorkflowRuns = true) {
|
export function* deactivate(deactivateAutomationRuns = true) {
|
||||||
const workflow = select(storeName).getWorkflowData();
|
const automation = select(storeName).getAutomationData();
|
||||||
const data = yield apiFetch({
|
const data = yield apiFetch({
|
||||||
path: `/workflows/${workflow.id}`,
|
path: `/automations/${automation.id}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: {
|
data: {
|
||||||
...workflow,
|
...automation,
|
||||||
status: deactivateWorkflowRuns
|
status: deactivateAutomationRuns
|
||||||
? WorkflowStatus.DRAFT
|
? AutomationStatus.DRAFT
|
||||||
: WorkflowStatus.DEACTIVATING,
|
: AutomationStatus.DEACTIVATING,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||||
if (deactivateWorkflowRuns && data?.data.status === WorkflowStatus.DRAFT) {
|
if (
|
||||||
|
deactivateAutomationRuns &&
|
||||||
|
data?.data.status === AutomationStatus.DRAFT
|
||||||
|
) {
|
||||||
void createNotice(
|
void createNotice(
|
||||||
'success',
|
'success',
|
||||||
__('Automation is now deactivated!', 'mailpoet'),
|
__('Automation is now deactivated!', 'mailpoet'),
|
||||||
@ -132,10 +164,14 @@ export function* deactivate(deactivateWorkflowRuns = true) {
|
|||||||
type: 'snackbar',
|
type: 'snackbar',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
MailPoet.trackEvent('Automations > Automation deactivated', {
|
||||||
|
type: 'immediate',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!deactivateWorkflowRuns &&
|
!deactivateAutomationRuns &&
|
||||||
data?.data.status === WorkflowStatus.DEACTIVATING
|
data?.data.status === AutomationStatus.DEACTIVATING
|
||||||
) {
|
) {
|
||||||
void createNotice(
|
void createNotice(
|
||||||
'success',
|
'success',
|
||||||
@ -147,36 +183,39 @@ export function* deactivate(deactivateWorkflowRuns = true) {
|
|||||||
type: 'snackbar',
|
type: 'snackbar',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
MailPoet.trackEvent('Automations > Automation deactivated', {
|
||||||
|
type: 'continuous',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'DEACTIVATE',
|
type: 'DEACTIVATE',
|
||||||
workflow: data?.data ?? workflow,
|
automation: data?.data ?? automation,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* trash(onTrashed: () => void = undefined) {
|
export function* trash(onTrashed: () => void = undefined) {
|
||||||
const workflow = select(storeName).getWorkflowData();
|
const automation = select(storeName).getAutomationData();
|
||||||
const data = yield apiFetch({
|
const data = yield apiFetch({
|
||||||
path: `/workflows/${workflow.id}`,
|
path: `/automations/${automation.id}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: {
|
data: {
|
||||||
...workflow,
|
...automation,
|
||||||
status: WorkflowStatus.TRASH,
|
status: AutomationStatus.TRASH,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
onTrashed?.();
|
onTrashed?.();
|
||||||
|
|
||||||
if (data?.status === WorkflowStatus.TRASH) {
|
if (data?.status === AutomationStatus.TRASH) {
|
||||||
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
||||||
[LISTING_NOTICE_PARAMETERS.workflowDeleted]: workflow.id,
|
[LISTING_NOTICE_PARAMETERS.automationDeleted]: automation.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'TRASH',
|
type: 'TRASH',
|
||||||
workflow: data?.data ?? workflow,
|
automation: data?.data ?? automation,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +236,7 @@ export function updateStepArgs(stepId, name, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setErrors(errors) {
|
export function setErrors(errors) {
|
||||||
|
trackErrors(errors);
|
||||||
return {
|
return {
|
||||||
type: 'SET_ERRORS',
|
type: 'SET_ERRORS',
|
||||||
errors,
|
errors,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export const storeName = 'mailpoet/automation-editor';
|
export const storeName = 'mailpoet/automation-editor';
|
||||||
|
|
||||||
export const workflowSidebarKey = 'mailpoet/automation-editor/workflow';
|
export const automationSidebarKey = 'mailpoet/automation-editor/automation';
|
||||||
export const stepSidebarKey = 'mailpoet/automation-editor/step';
|
export const stepSidebarKey = 'mailpoet/automation-editor/step';
|
||||||
|
@ -5,12 +5,15 @@ declare let window: AutomationEditorWindow;
|
|||||||
export const getInitialState = (): State => ({
|
export const getInitialState = (): State => ({
|
||||||
context: { ...window.mailpoet_automation_context },
|
context: { ...window.mailpoet_automation_context },
|
||||||
stepTypes: {},
|
stepTypes: {},
|
||||||
workflowData: { ...window.mailpoet_automation_workflow },
|
automationData: { ...window.mailpoet_automation },
|
||||||
workflowSaved: true,
|
automationSaved: true,
|
||||||
selectedStep: undefined,
|
selectedStep: undefined,
|
||||||
inserterSidebar: {
|
inserterSidebar: {
|
||||||
isOpened: false,
|
isOpened: false,
|
||||||
},
|
},
|
||||||
|
activationPanel: {
|
||||||
|
isOpened: false,
|
||||||
|
},
|
||||||
inserterPopover: undefined,
|
inserterPopover: undefined,
|
||||||
errors: undefined,
|
errors: undefined,
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,14 @@ import { State } from './types';
|
|||||||
|
|
||||||
export function reducer(state: State, action: Action): State {
|
export function reducer(state: State, action: Action): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case 'SET_ACTIVATION_PANEL_VISIBILITY':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activationPanel: {
|
||||||
|
...state.activationPanel,
|
||||||
|
isOpened: action.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
case 'TOGGLE_INSERTER_SIDEBAR':
|
case 'TOGGLE_INSERTER_SIDEBAR':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -21,35 +29,35 @@ export function reducer(state: State, action: Action): State {
|
|||||||
...state,
|
...state,
|
||||||
selectedStep: action.value,
|
selectedStep: action.value,
|
||||||
};
|
};
|
||||||
case 'UPDATE_WORKFLOW':
|
case 'UPDATE_AUTOMATION':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
workflowData: action.workflow,
|
automationData: action.automation,
|
||||||
workflowSaved: false,
|
automationSaved: false,
|
||||||
};
|
};
|
||||||
case 'SAVE':
|
case 'SAVE':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
workflowData: action.workflow,
|
automationData: action.automation,
|
||||||
workflowSaved: true,
|
automationSaved: true,
|
||||||
};
|
};
|
||||||
case 'ACTIVATE':
|
case 'ACTIVATE':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
workflowData: action.workflow,
|
automationData: action.automation,
|
||||||
workflowSaved: true,
|
automationSaved: true,
|
||||||
};
|
};
|
||||||
case 'DEACTIVATE':
|
case 'DEACTIVATE':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
workflowData: action.workflow,
|
automationData: action.automation,
|
||||||
workflowSaved: true,
|
automationSaved: true,
|
||||||
};
|
};
|
||||||
case 'TRASH':
|
case 'TRASH':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
workflowData: action.workflow,
|
automationData: action.automation,
|
||||||
workflowSaved: true,
|
automationSaved: true,
|
||||||
};
|
};
|
||||||
case 'REGISTER_STEP_TYPE':
|
case 'REGISTER_STEP_TYPE':
|
||||||
return {
|
return {
|
||||||
@ -60,7 +68,7 @@ export function reducer(state: State, action: Action): State {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
case 'UPDATE_STEP_ARGS': {
|
case 'UPDATE_STEP_ARGS': {
|
||||||
const prevArgs = state.workflowData.steps[action.stepId].args ?? {};
|
const prevArgs = state.automationData.steps[action.stepId].args ?? {};
|
||||||
|
|
||||||
const value =
|
const value =
|
||||||
typeof action.value === 'function'
|
typeof action.value === 'function'
|
||||||
@ -74,7 +82,7 @@ export function reducer(state: State, action: Action): State {
|
|||||||
)
|
)
|
||||||
: { ...prevArgs, [action.name]: value };
|
: { ...prevArgs, [action.name]: value };
|
||||||
|
|
||||||
const step = { ...state.workflowData.steps[action.stepId], args };
|
const step = { ...state.automationData.steps[action.stepId], args };
|
||||||
|
|
||||||
const stepErrors = Object.values(state.errors?.steps ?? {}).filter(
|
const stepErrors = Object.values(state.errors?.steps ?? {}).filter(
|
||||||
({ step_id }) => step_id !== action.stepId,
|
({ step_id }) => step_id !== action.stepId,
|
||||||
@ -82,14 +90,14 @@ export function reducer(state: State, action: Action): State {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
workflowData: {
|
automationData: {
|
||||||
...state.workflowData,
|
...state.automationData,
|
||||||
steps: {
|
steps: {
|
||||||
...state.workflowData.steps,
|
...state.automationData.steps,
|
||||||
[action.stepId]: step,
|
[action.stepId]: step,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
workflowSaved: false,
|
automationSaved: false,
|
||||||
selectedStep: step,
|
selectedStep: step,
|
||||||
errors:
|
errors:
|
||||||
stepErrors.length > 0
|
stepErrors.length > 0
|
||||||
|
@ -4,7 +4,7 @@ import { store as preferencesStore } from '@wordpress/preferences';
|
|||||||
import { storeName } from './constants';
|
import { storeName } from './constants';
|
||||||
import { Context, Errors, Feature, State, StepErrors, StepType } from './types';
|
import { Context, Errors, Feature, State, StepErrors, StepType } from './types';
|
||||||
import { Item } from '../components/inserter/item';
|
import { Item } from '../components/inserter/item';
|
||||||
import { Step, Workflow } from '../components/workflow/types';
|
import { Step, Automation } from '../components/automation/types';
|
||||||
|
|
||||||
export const isFeatureActive = createRegistrySelector(
|
export const isFeatureActive = createRegistrySelector(
|
||||||
(select) =>
|
(select) =>
|
||||||
@ -21,6 +21,10 @@ export function isInserterSidebarOpened(state: State): boolean {
|
|||||||
return state.inserterSidebar.isOpened;
|
return state.inserterSidebar.isOpened;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isActivationPanelOpened(state: State): boolean {
|
||||||
|
return state.activationPanel.isOpened;
|
||||||
|
}
|
||||||
|
|
||||||
export function getContext(state: State): Context {
|
export function getContext(state: State): Context {
|
||||||
return state.context;
|
return state.context;
|
||||||
}
|
}
|
||||||
@ -54,18 +58,22 @@ export function getInserterPopover(
|
|||||||
return state.inserterPopover;
|
return state.inserterPopover;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWorkflowData(state: State): Workflow {
|
export function getAutomationData(state: State): Automation {
|
||||||
return state.workflowData;
|
return state.automationData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWorkflowSaved(state: State): boolean {
|
export function getAutomationSaved(state: State): boolean {
|
||||||
return state.workflowSaved;
|
return state.automationSaved;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSelectedStep(state: State): Step | undefined {
|
export function getSelectedStep(state: State): Step | undefined {
|
||||||
return state.selectedStep;
|
return state.selectedStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getStepById(state: State, id: string): Step | undefined {
|
||||||
|
return state.automationData.steps[id] ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function getStepType(state: State, key: string): StepType | undefined {
|
export function getStepType(state: State, key: string): StepType | undefined {
|
||||||
return state.stepTypes[key] ?? undefined;
|
return state.stepTypes[key] ?? undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { ComponentType } from 'react';
|
import { ComponentType } from 'react';
|
||||||
import { Step, Workflow } from '../components/workflow/types';
|
import { Step, Automation } from '../components/automation/types';
|
||||||
|
|
||||||
export interface AutomationEditorWindow extends Window {
|
export interface AutomationEditorWindow extends Window {
|
||||||
mailpoet_automation_context: Context;
|
mailpoet_automation_context: Context;
|
||||||
mailpoet_automation_workflow: Workflow;
|
mailpoet_automation: Automation;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Context = {
|
export type Context = {
|
||||||
@ -48,12 +48,15 @@ export type Errors = {
|
|||||||
export type State = {
|
export type State = {
|
||||||
context: Context;
|
context: Context;
|
||||||
stepTypes: Record<string, StepType>;
|
stepTypes: Record<string, StepType>;
|
||||||
workflowData: Workflow;
|
automationData: Automation;
|
||||||
workflowSaved: boolean;
|
automationSaved: boolean;
|
||||||
selectedStep: Step | undefined;
|
selectedStep: Step | undefined;
|
||||||
inserterSidebar: {
|
inserterSidebar: {
|
||||||
isOpened: boolean;
|
isOpened: boolean;
|
||||||
};
|
};
|
||||||
|
activationPanel: {
|
||||||
|
isOpened: boolean;
|
||||||
|
};
|
||||||
inserterPopover?: {
|
inserterPopover?: {
|
||||||
anchor: HTMLElement;
|
anchor: HTMLElement;
|
||||||
type: 'steps' | 'triggers';
|
type: 'steps' | 'triggers';
|
||||||
|
@ -3,7 +3,7 @@ import { chartBar } from '@wordpress/icons';
|
|||||||
import { Hooks } from 'wp-js-hooks';
|
import { Hooks } from 'wp-js-hooks';
|
||||||
import { MoreControlType, StepMoreControlsType } from '../../../types/filters';
|
import { MoreControlType, StepMoreControlsType } from '../../../types/filters';
|
||||||
import { StepType } from '../../../editor/store';
|
import { StepType } from '../../../editor/store';
|
||||||
import { Step } from '../../../editor/components/workflow/types';
|
import { Step } from '../../../editor/components/automation/types';
|
||||||
|
|
||||||
const emailStatisticsControl = (step: Step): MoreControlType => {
|
const emailStatisticsControl = (step: Step): MoreControlType => {
|
||||||
const hasEmail = step.args?.email_id > 0;
|
const hasEmail = step.args?.email_id > 0;
|
||||||
@ -28,7 +28,7 @@ const emailStatisticsControl = (step: Step): MoreControlType => {
|
|||||||
|
|
||||||
export function registerStepControls() {
|
export function registerStepControls() {
|
||||||
Hooks.addFilter(
|
Hooks.addFilter(
|
||||||
'mailpoet.automation.workflow.step.more-controls',
|
'mailpoet.automation.step.more-controls',
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
(
|
(
|
||||||
controls: StepMoreControlsType,
|
controls: StepMoreControlsType,
|
||||||
|
@ -31,11 +31,11 @@ export function EditNewsletter(): JSX.Element {
|
|||||||
useState(false);
|
useState(false);
|
||||||
const [fetchingPreviewLink, setFetchingPreviewLink] = useState(false);
|
const [fetchingPreviewLink, setFetchingPreviewLink] = useState(false);
|
||||||
|
|
||||||
const { selectedStep, workflowId, workflowSaved, errors } = useSelect(
|
const { selectedStep, automationId, automationSaved, errors } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
selectedStep: select(storeName).getSelectedStep(),
|
selectedStep: select(storeName).getSelectedStep(),
|
||||||
workflowId: select(storeName).getWorkflowData().id,
|
automationId: select(storeName).getAutomationData().id,
|
||||||
workflowSaved: select(storeName).getWorkflowSaved(),
|
automationSaved: select(storeName).getAutomationSaved(),
|
||||||
errors: select(storeName).getStepError(
|
errors: select(storeName).getStepError(
|
||||||
select(storeName).getSelectedStep().id,
|
select(storeName).getSelectedStep().id,
|
||||||
),
|
),
|
||||||
@ -44,7 +44,7 @@ export function EditNewsletter(): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const emailId = selectedStep?.args?.email_id as number | undefined;
|
const emailId = selectedStep?.args?.email_id as number | undefined;
|
||||||
const workflowStepId = selectedStep.id;
|
const automationStepId = selectedStep.id;
|
||||||
const errorFields = errors?.fields ?? {};
|
const errorFields = errors?.fields ?? {};
|
||||||
const emailIdError = errorFields?.email_id ?? '';
|
const emailIdError = errorFields?.email_id ?? '';
|
||||||
|
|
||||||
@ -58,28 +58,28 @@ export function EditNewsletter(): JSX.Element {
|
|||||||
type: 'automation',
|
type: 'automation',
|
||||||
subject: '',
|
subject: '',
|
||||||
options: {
|
options: {
|
||||||
workflowId,
|
automationId,
|
||||||
workflowStepId,
|
automationStepId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(storeName).updateStepArgs(
|
dispatch(storeName).updateStepArgs(
|
||||||
workflowStepId,
|
automationStepId,
|
||||||
'email_id',
|
'email_id',
|
||||||
parseInt(response.data.id as string, 10),
|
parseInt(response.data.id as string, 10),
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch(storeName).save();
|
dispatch(storeName).save();
|
||||||
}, [workflowId, workflowStepId]);
|
}, [automationId, automationStepId]);
|
||||||
|
|
||||||
// This component is rendered only when no email ID is set. Once we have the ID
|
// This component is rendered only when no email ID is set. Once we have the ID
|
||||||
// and the workflow is saved, we can safely redirect to the email design flow.
|
// and the automation is saved, we can safely redirect to the email design flow.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (redirectToTemplateSelection && emailId && workflowSaved) {
|
if (redirectToTemplateSelection && emailId && automationSaved) {
|
||||||
window.location.href = `admin.php?page=mailpoet-newsletters#/template/${emailId}`;
|
window.location.href = `admin.php?page=mailpoet-newsletters#/template/${emailId}`;
|
||||||
}
|
}
|
||||||
}, [emailId, workflowSaved, redirectToTemplateSelection]);
|
}, [emailId, automationSaved, redirectToTemplateSelection]);
|
||||||
|
|
||||||
if (!emailId || redirectToTemplateSelection) {
|
if (!emailId || redirectToTemplateSelection) {
|
||||||
return (
|
return (
|
||||||
|
@ -8,7 +8,6 @@ export function ShortcodeHelpText(): JSX.Element {
|
|||||||
href="https://kb.mailpoet.com/article/215-personalize-newsletter-with-shortcodes"
|
href="https://kb.mailpoet.com/article/215-personalize-newsletter-with-shortcodes"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
data-beacon-article="59d662ef042863379ddc6faa"
|
|
||||||
>
|
>
|
||||||
{__('MailPoet shortcodes', 'mailpoet')}
|
{__('MailPoet shortcodes', 'mailpoet')}
|
||||||
</a>
|
</a>
|
||||||
|
@ -3,7 +3,7 @@ import { Hooks } from 'wp-js-hooks';
|
|||||||
import { Icon } from './icon';
|
import { Icon } from './icon';
|
||||||
import { Edit } from './edit';
|
import { Edit } from './edit';
|
||||||
import { State, StepType } from '../../../../editor/store/types';
|
import { State, StepType } from '../../../../editor/store/types';
|
||||||
import { Step } from '../../../../editor/components/workflow/types';
|
import { Step } from '../../../../editor/components/automation/types';
|
||||||
|
|
||||||
export const step: StepType = {
|
export const step: StepType = {
|
||||||
key: 'mailpoet:send-email',
|
key: 'mailpoet:send-email',
|
||||||
@ -20,6 +20,6 @@ export const step: StepType = {
|
|||||||
Hooks.applyFilters(
|
Hooks.applyFilters(
|
||||||
'mailpoet.automation.send_email.create_step',
|
'mailpoet.automation.send_email.create_step',
|
||||||
stepData,
|
stepData,
|
||||||
state.workflowData.id,
|
state.automationData.id,
|
||||||
),
|
),
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -2,10 +2,13 @@
|
|||||||
* The types in this file document the expected return types of specific
|
* The types in this file document the expected return types of specific
|
||||||
* filters.
|
* filters.
|
||||||
*/
|
*/
|
||||||
import { Step } from '../../../editor/components/workflow/types';
|
import { Step } from '../../../editor/components/automation/types';
|
||||||
|
|
||||||
// mailpoet.automation.send_email.create_step
|
// mailpoet.automation.send_email.create_step
|
||||||
export type SendEmailCreateStepType = (step: Step, workflowId: number) => Step;
|
export type SendEmailCreateStepType = (
|
||||||
|
step: Step,
|
||||||
|
automationId: number,
|
||||||
|
) => Step;
|
||||||
|
|
||||||
// mailpoet.automation.send_email.google_analytics_panel
|
// mailpoet.automation.send_email.google_analytics_panel
|
||||||
export type GoogleAnalyticsPanelBodyType = JSX.Element;
|
export type GoogleAnalyticsPanelBodyType = JSX.Element;
|
||||||
|
@ -3,27 +3,30 @@ import { __ } from '@wordpress/i18n';
|
|||||||
import { Notice } from '../../notices/notice';
|
import { Notice } from '../../notices/notice';
|
||||||
|
|
||||||
export const LISTING_NOTICE_PARAMETERS = {
|
export const LISTING_NOTICE_PARAMETERS = {
|
||||||
workflowHadBeenDeleted: 'mailpoet-had-been-deleted',
|
automationHadBeenDeleted: 'mailpoet-had-been-deleted',
|
||||||
workflowDeleted: 'mailpoet-workflow-deleted',
|
automationDeleted: 'mailpoet-automation-deleted',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function WorkflowListingNotices(): JSX.Element {
|
export function AutomationListingNotices(): JSX.Element {
|
||||||
const workflowHadBeenDeleted = parseInt(
|
const automationHadBeenDeleted = parseInt(
|
||||||
getQueryArg(
|
getQueryArg(
|
||||||
window.location.href,
|
window.location.href,
|
||||||
LISTING_NOTICE_PARAMETERS.workflowHadBeenDeleted,
|
LISTING_NOTICE_PARAMETERS.automationHadBeenDeleted,
|
||||||
) as string,
|
) as string,
|
||||||
10,
|
10,
|
||||||
);
|
);
|
||||||
const workflowDeleted = parseInt(
|
const automationDeleted = parseInt(
|
||||||
getQueryArg(
|
getQueryArg(
|
||||||
window.location.href,
|
window.location.href,
|
||||||
LISTING_NOTICE_PARAMETERS.workflowDeleted,
|
LISTING_NOTICE_PARAMETERS.automationDeleted,
|
||||||
) as string,
|
) as string,
|
||||||
10,
|
10,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Number.isNaN(workflowHadBeenDeleted) && Number.isNaN(workflowDeleted)) {
|
if (
|
||||||
|
Number.isNaN(automationHadBeenDeleted) &&
|
||||||
|
Number.isNaN(automationDeleted)
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +35,7 @@ export function WorkflowListingNotices(): JSX.Element {
|
|||||||
...Object.values(LISTING_NOTICE_PARAMETERS),
|
...Object.values(LISTING_NOTICE_PARAMETERS),
|
||||||
);
|
);
|
||||||
window.history.pushState('', '', urlWithoutNotices);
|
window.history.pushState('', '', urlWithoutNotices);
|
||||||
if (workflowHadBeenDeleted) {
|
if (automationHadBeenDeleted) {
|
||||||
return (
|
return (
|
||||||
<Notice type="error" closable timeout={false}>
|
<Notice type="error" closable timeout={false}>
|
||||||
<p>
|
<p>
|
||||||
@ -44,7 +47,7 @@ export function WorkflowListingNotices(): JSX.Element {
|
|||||||
</Notice>
|
</Notice>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (workflowDeleted) {
|
if (automationDeleted) {
|
||||||
return (
|
return (
|
||||||
<Notice type="success" closable timeout={false}>
|
<Notice type="success" closable timeout={false}>
|
||||||
<p>{__('1 automation moved to the Trash.', 'mailpoet')}</p>
|
<p>{__('1 automation moved to the Trash.', 'mailpoet')}</p>
|
@ -1,14 +1,14 @@
|
|||||||
export enum WorkflowStatus {
|
export enum AutomationStatus {
|
||||||
ACTIVE = 'active',
|
ACTIVE = 'active',
|
||||||
DRAFT = 'draft',
|
DRAFT = 'draft',
|
||||||
TRASH = 'trash',
|
TRASH = 'trash',
|
||||||
DEACTIVATING = 'deactivating',
|
DEACTIVATING = 'deactivating',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Workflow = {
|
export type Automation = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
status: WorkflowStatus;
|
status: AutomationStatus;
|
||||||
stats: {
|
stats: {
|
||||||
totals: {
|
totals: {
|
||||||
entered: number;
|
entered: number;
|
@ -1,19 +1,19 @@
|
|||||||
import { Button } from '@wordpress/components';
|
import { Button } from '@wordpress/components';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { addQueryArgs } from '@wordpress/url';
|
import { addQueryArgs } from '@wordpress/url';
|
||||||
import { Workflow } from '../../workflow';
|
import { Automation } from '../../automation';
|
||||||
import { MailPoet } from '../../../../mailpoet';
|
import { MailPoet } from '../../../../mailpoet';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workflow: Workflow;
|
automation: Automation;
|
||||||
label?: string;
|
label?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditWorkflow({ workflow, label }: Props): JSX.Element {
|
export function EditAutomation({ automation, label }: Props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
href={addQueryArgs(MailPoet.urls.automationEditor, { id: workflow.id })}
|
href={addQueryArgs(MailPoet.urls.automationEditor, { id: automation.id })}
|
||||||
>
|
>
|
||||||
{label ?? __('Edit', 'mailpoet')}
|
{label ?? __('Edit', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
@ -1,2 +1,2 @@
|
|||||||
export * from './edit-workflow';
|
export * from './edit-automation';
|
||||||
export * from './undo-trash';
|
export * from './undo-trash';
|
||||||
|
@ -2,23 +2,23 @@ import { Button } from '@wordpress/components';
|
|||||||
import { useDispatch } from '@wordpress/data';
|
import { useDispatch } from '@wordpress/data';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { storeName } from '../../store/constants';
|
import { storeName } from '../../store/constants';
|
||||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
import { Automation, AutomationStatus } from '../../automation';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workflow: Workflow;
|
automation: Automation;
|
||||||
previousStatus: WorkflowStatus;
|
previousStatus: AutomationStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function UndoTrashButton({
|
export function UndoTrashButton({
|
||||||
workflow,
|
automation,
|
||||||
previousStatus,
|
previousStatus,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const { restoreWorkflow } = useDispatch(storeName);
|
const { restoreAutomation } = useDispatch(storeName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
onClick={() => restoreWorkflow(workflow, previousStatus)}
|
onClick={() => restoreAutomation(automation, previousStatus)}
|
||||||
>
|
>
|
||||||
{__('Undo', 'mailpoet')}
|
{__('Undo', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -2,32 +2,26 @@ import { Fragment } from 'react';
|
|||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { DropdownMenu } from '@wordpress/components';
|
import { DropdownMenu } from '@wordpress/components';
|
||||||
import { moreVertical } from '@wordpress/icons';
|
import { moreVertical } from '@wordpress/icons';
|
||||||
import {
|
import { useDeleteButton, useRestoreButton, useTrashButton } from '../menu';
|
||||||
useDeleteButton,
|
import { Automation } from '../../automation';
|
||||||
useDuplicateButton,
|
import { EditAutomation } from '../actions';
|
||||||
useRestoreButton,
|
|
||||||
useTrashButton,
|
|
||||||
} from '../menu';
|
|
||||||
import { Workflow } from '../../workflow';
|
|
||||||
import { EditWorkflow } from '../actions';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workflow: Workflow;
|
automation: Automation;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Actions({ workflow }: Props): JSX.Element {
|
export function Actions({ automation }: Props): JSX.Element {
|
||||||
// Menu items are using custom hooks because the "DropdownMenu" component uses the "controls"
|
// Menu items are using custom hooks because the "DropdownMenu" component uses the "controls"
|
||||||
// attribute rather than child components, but we need to render modal confirmation dialogs.
|
// attribute rather than child components, but we need to render modal confirmation dialogs.
|
||||||
const duplicate = useDuplicateButton(workflow);
|
const trash = useTrashButton(automation);
|
||||||
const trash = useTrashButton(workflow);
|
const restore = useRestoreButton(automation);
|
||||||
const restore = useRestoreButton(workflow);
|
const del = useDeleteButton(automation);
|
||||||
const del = useDeleteButton(workflow);
|
|
||||||
|
|
||||||
const menuItems = [duplicate, trash, restore, del].filter((item) => item);
|
const menuItems = [trash, restore, del].filter((item) => item);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mailpoet-automation-listing-cell-actions">
|
<div className="mailpoet-automation-listing-cell-actions">
|
||||||
<EditWorkflow workflow={workflow} />
|
<EditAutomation automation={automation} />
|
||||||
{menuItems.map(({ control, slot }) => (
|
{menuItems.map(({ control, slot }) => (
|
||||||
<Fragment key={control.title}>{slot}</Fragment>
|
<Fragment key={control.title}>{slot}</Fragment>
|
||||||
))}
|
))}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { EditWorkflow } from '../actions';
|
import { EditAutomation } from '../actions';
|
||||||
import { Workflow } from '../../workflow';
|
import { Automation } from '../../automation';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workflow: Workflow;
|
automation: Automation;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Name({ workflow }: Props): JSX.Element {
|
export function Name({ automation }: Props): JSX.Element {
|
||||||
return <EditWorkflow workflow={workflow} label={workflow.name} />;
|
return <EditAutomation automation={automation} label={automation.name} />;
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
import { Automation, AutomationStatus } from '../../automation';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workflow: Workflow;
|
automation: Automation;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Status({ workflow }: Props): JSX.Element {
|
export function Status({ automation }: Props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="mailpoet-automation-listing-cell-status">
|
<div className="mailpoet-automation-listing-cell-status">
|
||||||
{workflow.status === WorkflowStatus.ACTIVE
|
{automation.status === AutomationStatus.ACTIVE
|
||||||
? __('Active', 'mailpoet')
|
? __('Active', 'mailpoet')
|
||||||
: __('Not active', 'mailpoet')}
|
: __('Draft', 'mailpoet')}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { _x } from '@wordpress/i18n';
|
import { _x } from '@wordpress/i18n';
|
||||||
import { Workflow } from '../../workflow';
|
import { Automation } from '../../automation';
|
||||||
import { Statistics } from '../../../components/statistics';
|
import { Statistics } from '../../../components/statistics';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workflow: Workflow;
|
automation: Automation;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Subscribers({ workflow }: Props): JSX.Element {
|
export function Subscribers({ automation }: Props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Statistics
|
<Statistics
|
||||||
labelPosition="after"
|
labelPosition="after"
|
||||||
@ -15,19 +15,19 @@ export function Subscribers({ workflow }: Props): JSX.Element {
|
|||||||
key: 'entered',
|
key: 'entered',
|
||||||
// translators: Total number of subscribers who entered an automation
|
// translators: Total number of subscribers who entered an automation
|
||||||
label: _x('Entered', 'automation stats', 'mailpoet'),
|
label: _x('Entered', 'automation stats', 'mailpoet'),
|
||||||
value: workflow.stats.totals.entered,
|
value: automation.stats.totals.entered,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'processing',
|
key: 'processing',
|
||||||
// translators: Total number of subscribers who are being processed in an automation
|
// translators: Total number of subscribers who are being processed in an automation
|
||||||
label: _x('Processing', 'automation stats', 'mailpoet'),
|
label: _x('Processing', 'automation stats', 'mailpoet'),
|
||||||
value: workflow.stats.totals.in_progress,
|
value: automation.stats.totals.in_progress,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'exited',
|
key: 'exited',
|
||||||
// translators: Total number of subscribers who exited an automation, no matter the result
|
// translators: Total number of subscribers who exited an automation, no matter the result
|
||||||
label: _x('Exited', 'automation stats', 'mailpoet'),
|
label: _x('Exited', 'automation stats', 'mailpoet'),
|
||||||
value: workflow.stats.totals.exited,
|
value: automation.stats.totals.exited,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
@ -4,13 +4,13 @@ import { useDispatch } from '@wordpress/data';
|
|||||||
import { __, sprintf } from '@wordpress/i18n';
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
import { Item } from './item';
|
import { Item } from './item';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
import { Automation, AutomationStatus } from '../../automation';
|
||||||
|
|
||||||
export const useDeleteButton = (workflow: Workflow): Item | undefined => {
|
export const useDeleteButton = (automation: Automation): Item | undefined => {
|
||||||
const [showDialog, setShowDialog] = useState(false);
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
const { deleteWorkflow } = useDispatch(storeName);
|
const { deleteAutomation } = useDispatch(storeName);
|
||||||
|
|
||||||
if (workflow.status !== WorkflowStatus.TRASH) {
|
if (automation.status !== AutomationStatus.TRASH) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ export const useDeleteButton = (workflow: Workflow): Item | undefined => {
|
|||||||
title={__('Permanently delete automation', 'mailpoet')}
|
title={__('Permanently delete automation', 'mailpoet')}
|
||||||
confirmButtonText={__('Yes, permanently delete', 'mailpoet')}
|
confirmButtonText={__('Yes, permanently delete', 'mailpoet')}
|
||||||
__experimentalHideHeader={false}
|
__experimentalHideHeader={false}
|
||||||
onConfirm={() => deleteWorkflow(workflow)}
|
onConfirm={() => deleteAutomation(automation)}
|
||||||
onCancel={() => setShowDialog(false)}
|
onCancel={() => setShowDialog(false)}
|
||||||
>
|
>
|
||||||
{sprintf(
|
{sprintf(
|
||||||
@ -36,7 +36,7 @@ export const useDeleteButton = (workflow: Workflow): Item | undefined => {
|
|||||||
'Are you sure you want to permanently delete "%s" and all associated data? This cannot be undone!',
|
'Are you sure you want to permanently delete "%s" and all associated data? This cannot be undone!',
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
),
|
),
|
||||||
workflow.name,
|
automation.name,
|
||||||
)}
|
)}
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
),
|
),
|
||||||
|
@ -2,12 +2,14 @@ import { useDispatch } from '@wordpress/data';
|
|||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Item } from './item';
|
import { Item } from './item';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
import { Automation, AutomationStatus } from '../../automation';
|
||||||
|
|
||||||
export const useDuplicateButton = (workflow: Workflow): Item | undefined => {
|
export const useDuplicateButton = (
|
||||||
const { duplicateWorkflow } = useDispatch(storeName);
|
automation: Automation,
|
||||||
|
): Item | undefined => {
|
||||||
|
const { duplicateAutomation } = useDispatch(storeName);
|
||||||
|
|
||||||
if (workflow.status === WorkflowStatus.TRASH) {
|
if (automation.status === AutomationStatus.TRASH) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,7 +18,7 @@ export const useDuplicateButton = (workflow: Workflow): Item | undefined => {
|
|||||||
control: {
|
control: {
|
||||||
title: __('Duplicate', 'mailpoet'),
|
title: __('Duplicate', 'mailpoet'),
|
||||||
icon: null,
|
icon: null,
|
||||||
onClick: () => duplicateWorkflow(workflow),
|
onClick: () => duplicateAutomation(automation),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -2,12 +2,12 @@ import { useDispatch } from '@wordpress/data';
|
|||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Item } from './item';
|
import { Item } from './item';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
import { Automation, AutomationStatus } from '../../automation';
|
||||||
|
|
||||||
export const useRestoreButton = (workflow: Workflow): Item | undefined => {
|
export const useRestoreButton = (automation: Automation): Item | undefined => {
|
||||||
const { restoreWorkflow } = useDispatch(storeName);
|
const { restoreAutomation } = useDispatch(storeName);
|
||||||
|
|
||||||
if (workflow.status !== WorkflowStatus.TRASH) {
|
if (automation.status !== AutomationStatus.TRASH) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ export const useRestoreButton = (workflow: Workflow): Item | undefined => {
|
|||||||
control: {
|
control: {
|
||||||
title: __('Restore', 'mailpoet'),
|
title: __('Restore', 'mailpoet'),
|
||||||
icon: null,
|
icon: null,
|
||||||
onClick: () => restoreWorkflow(workflow, WorkflowStatus.DRAFT),
|
onClick: () => restoreAutomation(automation, AutomationStatus.DRAFT),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -4,13 +4,13 @@ import { useDispatch } from '@wordpress/data';
|
|||||||
import { __, _x, sprintf } from '@wordpress/i18n';
|
import { __, _x, sprintf } from '@wordpress/i18n';
|
||||||
import { Item } from './item';
|
import { Item } from './item';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
import { Automation, AutomationStatus } from '../../automation';
|
||||||
|
|
||||||
export const useTrashButton = (workflow: Workflow): Item | undefined => {
|
export const useTrashButton = (automation: Automation): Item | undefined => {
|
||||||
const [showDialog, setShowDialog] = useState(false);
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
const { trashWorkflow } = useDispatch(storeName);
|
const { trashAutomation } = useDispatch(storeName);
|
||||||
|
|
||||||
if (workflow.status === WorkflowStatus.TRASH) {
|
if (automation.status === AutomationStatus.TRASH) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ export const useTrashButton = (workflow: Workflow): Item | undefined => {
|
|||||||
title={__('Trash automation', 'mailpoet')}
|
title={__('Trash automation', 'mailpoet')}
|
||||||
confirmButtonText={__('Yes, move to trash', 'mailpoet')}
|
confirmButtonText={__('Yes, move to trash', 'mailpoet')}
|
||||||
__experimentalHideHeader={false}
|
__experimentalHideHeader={false}
|
||||||
onConfirm={() => trashWorkflow(workflow)}
|
onConfirm={() => trashAutomation(automation)}
|
||||||
onCancel={() => setShowDialog(false)}
|
onCancel={() => setShowDialog(false)}
|
||||||
>
|
>
|
||||||
{sprintf(
|
{sprintf(
|
||||||
@ -36,7 +36,7 @@ export const useTrashButton = (workflow: Workflow): Item | undefined => {
|
|||||||
'Are you sure you want to move the automation "%s" to the Trash?',
|
'Are you sure you want to move the automation "%s" to the Trash?',
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
),
|
),
|
||||||
workflow.name,
|
automation.name,
|
||||||
)}
|
)}
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
),
|
),
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
import { Workflow } from './workflow';
|
import { Automation } from './automation';
|
||||||
import { Actions, Name, Status, Subscribers } from './components/cells';
|
import { Actions, Name, Status, Subscribers } from './components/cells';
|
||||||
|
|
||||||
export function getRow(workflow: Workflow): object[] {
|
export function getRow(automation: Automation): object[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: workflow.id,
|
id: automation.id,
|
||||||
value: workflow.name,
|
value: automation.name,
|
||||||
display: <Name workflow={workflow} />,
|
display: <Name automation={automation} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: workflow.id,
|
id: automation.id,
|
||||||
value: null,
|
value: null,
|
||||||
display: <Subscribers workflow={workflow} />,
|
display: <Subscribers automation={automation} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: workflow.id,
|
id: automation.id,
|
||||||
value: workflow.status,
|
value: automation.status,
|
||||||
display: <Status workflow={workflow} />,
|
display: <Status automation={automation} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: workflow.id,
|
id: automation.id,
|
||||||
value: null,
|
value: null,
|
||||||
display: <Actions workflow={workflow} />,
|
display: <Actions automation={automation} />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { useHistory, useLocation } from 'react-router-dom';
|
|||||||
import { plusIcon } from 'common/button/icon/plus';
|
import { plusIcon } from 'common/button/icon/plus';
|
||||||
import { getRow } from './get-row';
|
import { getRow } from './get-row';
|
||||||
import { storeName } from './store';
|
import { storeName } from './store';
|
||||||
import { Workflow, WorkflowStatus } from './workflow';
|
import { Automation, AutomationStatus } from './automation';
|
||||||
import { MailPoet } from '../../mailpoet';
|
import { MailPoet } from '../../mailpoet';
|
||||||
|
|
||||||
const tabConfig = [
|
const tabConfig = [
|
||||||
@ -17,17 +17,17 @@ const tabConfig = [
|
|||||||
className: 'mailpoet-tab-all',
|
className: 'mailpoet-tab-all',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: WorkflowStatus.ACTIVE,
|
name: AutomationStatus.ACTIVE,
|
||||||
title: __('Active', 'mailpoet'),
|
title: __('Active', 'mailpoet'),
|
||||||
className: 'mailpoet-tab-active',
|
className: 'mailpoet-tab-active',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: WorkflowStatus.DRAFT,
|
name: AutomationStatus.DRAFT,
|
||||||
title: _x('Draft', 'noun', 'mailpoet'),
|
title: _x('Draft', 'noun', 'mailpoet'),
|
||||||
className: 'mailpoet-tab-draft',
|
className: 'mailpoet-tab-draft',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: WorkflowStatus.TRASH,
|
name: AutomationStatus.TRASH,
|
||||||
title: _x('Trash', 'noun', 'mailpoet'),
|
title: _x('Trash', 'noun', 'mailpoet'),
|
||||||
className: 'mailpoet-tab-trash',
|
className: 'mailpoet-tab-trash',
|
||||||
},
|
},
|
||||||
@ -68,14 +68,14 @@ export function AutomationListing(): JSX.Element {
|
|||||||
[location],
|
[location],
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflows = useSelect((select) => select(storeName).getWorkflows());
|
const automations = useSelect((select) => select(storeName).getAutomations());
|
||||||
const { loadWorkflows } = useDispatch(storeName);
|
const { loadAutomations } = useDispatch(storeName);
|
||||||
|
|
||||||
const status = pageSearch.get('status');
|
const status = pageSearch.get('status');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadWorkflows();
|
loadAutomations();
|
||||||
}, [loadWorkflows]);
|
}, [loadAutomations]);
|
||||||
|
|
||||||
// focus tab button on status change (needed due to the force re-mount below)
|
// focus tab button on status change (needed due to the force re-mount below)
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@ -103,24 +103,24 @@ export function AutomationListing(): JSX.Element {
|
|||||||
[pageSearch, history],
|
[pageSearch, history],
|
||||||
);
|
);
|
||||||
|
|
||||||
const groupedWorkflows = useMemo<Record<string, Workflow[]>>(() => {
|
const groupedAutomations = useMemo<Record<string, Automation[]>>(() => {
|
||||||
const grouped = { all: [] };
|
const grouped = { all: [] };
|
||||||
(workflows ?? []).forEach((workflow) => {
|
(automations ?? []).forEach((automation) => {
|
||||||
if (!grouped[workflow.status]) {
|
if (!grouped[automation.status]) {
|
||||||
grouped[workflow.status] = [];
|
grouped[automation.status] = [];
|
||||||
}
|
}
|
||||||
grouped[workflow.status].push(workflow);
|
grouped[automation.status].push(automation);
|
||||||
if (workflow.status !== WorkflowStatus.TRASH) {
|
if (automation.status !== AutomationStatus.TRASH) {
|
||||||
grouped.all.push(workflow);
|
grouped.all.push(automation);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return grouped;
|
return grouped;
|
||||||
}, [workflows]);
|
}, [automations]);
|
||||||
|
|
||||||
const tabs = useMemo(
|
const tabs = useMemo(
|
||||||
() =>
|
() =>
|
||||||
tabConfig.map((tab) => {
|
tabConfig.map((tab) => {
|
||||||
const count = (groupedWorkflows[tab.name] ?? []).length;
|
const count = (groupedAutomations[tab.name] ?? []).length;
|
||||||
return {
|
return {
|
||||||
name: tab.name,
|
name: tab.name,
|
||||||
title: (
|
title: (
|
||||||
@ -132,38 +132,39 @@ export function AutomationListing(): JSX.Element {
|
|||||||
className: tab.className,
|
className: tab.className,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
[groupedWorkflows],
|
[groupedAutomations],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderTabs = useCallback(
|
const renderTabs = useCallback(
|
||||||
(tab) => {
|
(tab) => {
|
||||||
const filteredWorkflows: Workflow[] = groupedWorkflows[tab.name] ?? [];
|
const filteredAutomations: Automation[] =
|
||||||
|
groupedAutomations[tab.name] ?? [];
|
||||||
const rowsPerPage = parseInt(pageSearch.get('per_page') ?? '25', 10);
|
const rowsPerPage = parseInt(pageSearch.get('per_page') ?? '25', 10);
|
||||||
const currentPage = parseInt(pageSearch.get('paged') ?? '1', 10);
|
const currentPage = parseInt(pageSearch.get('paged') ?? '1', 10);
|
||||||
const start = (currentPage - 1) * rowsPerPage;
|
const start = (currentPage - 1) * rowsPerPage;
|
||||||
const rows = filteredWorkflows
|
const rows = filteredAutomations
|
||||||
.map((workflow) => getRow(workflow))
|
.map((automation) => getRow(automation))
|
||||||
.slice(start, start + rowsPerPage);
|
.slice(start, start + rowsPerPage);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableCard
|
<TableCard
|
||||||
className="mailpoet-automation-listing"
|
className="mailpoet-automation-listing"
|
||||||
title=""
|
title=""
|
||||||
isLoading={!workflows}
|
isLoading={!automations}
|
||||||
headers={tableHeaders}
|
headers={tableHeaders}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
rowKey={(_, i) => filteredWorkflows[i].id}
|
rowKey={(_, i) => filteredAutomations[i].id}
|
||||||
rowsPerPage={rowsPerPage}
|
rowsPerPage={rowsPerPage}
|
||||||
onQueryChange={(key) => (value) => {
|
onQueryChange={(key) => (value) => {
|
||||||
updateUrlSearchString({ [key]: value });
|
updateUrlSearchString({ [key]: value });
|
||||||
}}
|
}}
|
||||||
totalRows={filteredWorkflows.length}
|
totalRows={filteredAutomations.length}
|
||||||
query={Object.fromEntries(pageSearch)}
|
query={Object.fromEntries(pageSearch)}
|
||||||
showMenu={false}
|
showMenu={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[workflows, groupedWorkflows, pageSearch, updateUrlSearchString],
|
[automations, groupedAutomations, pageSearch, updateUrlSearchString],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -2,8 +2,8 @@ import { dispatch, StoreDescriptor } from '@wordpress/data';
|
|||||||
import { apiFetch } from '@wordpress/data-controls';
|
import { apiFetch } from '@wordpress/data-controls';
|
||||||
import { __, sprintf } from '@wordpress/i18n';
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
import { store as noticesStore } from '@wordpress/notices';
|
import { store as noticesStore } from '@wordpress/notices';
|
||||||
import { Workflow, WorkflowStatus } from '../workflow';
|
import { Automation, AutomationStatus } from '../automation';
|
||||||
import { EditWorkflow, UndoTrashButton } from '../components/actions';
|
import { EditAutomation, UndoTrashButton } from '../components/actions';
|
||||||
|
|
||||||
const createSuccessNotice = (content: string, options?: unknown) =>
|
const createSuccessNotice = (content: string, options?: unknown) =>
|
||||||
dispatch(noticesStore as StoreDescriptor).createSuccessNotice(
|
dispatch(noticesStore as StoreDescriptor).createSuccessNotice(
|
||||||
@ -14,78 +14,84 @@ const createSuccessNotice = (content: string, options?: unknown) =>
|
|||||||
const removeNotice = (id: string) =>
|
const removeNotice = (id: string) =>
|
||||||
dispatch(noticesStore as StoreDescriptor).removeNotice(id);
|
dispatch(noticesStore as StoreDescriptor).removeNotice(id);
|
||||||
|
|
||||||
export function* loadWorkflows() {
|
export function* loadAutomations() {
|
||||||
const data = yield apiFetch({
|
const data = yield apiFetch({
|
||||||
path: `/workflows`,
|
path: `/automations`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'SET_WORKFLOWS',
|
type: 'SET_AUTOMATIONS',
|
||||||
workflows: data.data,
|
automations: data.data,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* duplicateWorkflow(workflow: Workflow) {
|
export function* duplicateAutomation(automation: Automation) {
|
||||||
const data = yield apiFetch({
|
const data = yield apiFetch({
|
||||||
path: `/workflows/${workflow.id}/duplicate`,
|
path: `/automations/${automation.id}/duplicate`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
|
|
||||||
void createSuccessNotice(
|
void createSuccessNotice(
|
||||||
// translators: %s is the automation name
|
// translators: %s is the automation name
|
||||||
sprintf(__('Automation "%s" was duplicated.', 'mailpoet'), workflow.name),
|
sprintf(__('Automation "%s" was duplicated.', 'mailpoet'), automation.name),
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'ADD_WORKFLOW',
|
type: 'ADD_AUTOMATION',
|
||||||
workflow: data.data,
|
automation: data.data,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* trashWorkflow(workflow: Workflow) {
|
export function* trashAutomation(automation: Automation) {
|
||||||
const data = yield apiFetch({
|
const data = yield apiFetch({
|
||||||
path: `/workflows/${workflow.id}`,
|
path: `/automations/${automation.id}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: {
|
data: {
|
||||||
status: WorkflowStatus.TRASH,
|
status: AutomationStatus.TRASH,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const message = __('1 automation moved to the Trash.', 'mailpoet');
|
const message = __('1 automation moved to the Trash.', 'mailpoet');
|
||||||
void createSuccessNotice(message, {
|
void createSuccessNotice(message, {
|
||||||
id: `workflow-trashed-${workflow.id}`,
|
id: `automation-trashed-${automation.id}`,
|
||||||
__unstableHTML: (
|
__unstableHTML: (
|
||||||
<p>
|
<p>
|
||||||
{message}{' '}
|
{message}{' '}
|
||||||
<UndoTrashButton workflow={workflow} previousStatus={workflow.status} />
|
<UndoTrashButton
|
||||||
|
automation={automation}
|
||||||
|
previousStatus={automation.status}
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'UPDATE_WORKFLOW',
|
type: 'UPDATE_AUTOMATION',
|
||||||
workflow: data.data,
|
automation: data.data,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* restoreWorkflow(workflow: Workflow, status: WorkflowStatus) {
|
export function* restoreAutomation(
|
||||||
|
automation: Automation,
|
||||||
|
status: AutomationStatus,
|
||||||
|
) {
|
||||||
const data = yield apiFetch({
|
const data = yield apiFetch({
|
||||||
path: `/workflows/${workflow.id}`,
|
path: `/automations/${automation.id}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: {
|
data: {
|
||||||
status,
|
status,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
void removeNotice(`workflow-trashed-${workflow.id}`);
|
void removeNotice(`automation-trashed-${automation.id}`);
|
||||||
|
|
||||||
const message = __('1 automation restored from the Trash.', 'mailpoet');
|
const message = __('1 automation restored from the Trash.', 'mailpoet');
|
||||||
void createSuccessNotice(message, {
|
void createSuccessNotice(message, {
|
||||||
__unstableHTML: (
|
__unstableHTML: (
|
||||||
<p>
|
<p>
|
||||||
{message}{' '}
|
{message}{' '}
|
||||||
<EditWorkflow
|
<EditAutomation
|
||||||
workflow={workflow}
|
automation={automation}
|
||||||
label={__('Edit automation', 'mailpoet')}
|
label={__('Edit automation', 'mailpoet')}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
@ -93,14 +99,14 @@ export function* restoreWorkflow(workflow: Workflow, status: WorkflowStatus) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'UPDATE_WORKFLOW',
|
type: 'UPDATE_AUTOMATION',
|
||||||
workflow: data.data,
|
automation: data.data,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* deleteWorkflow(workflow: Workflow) {
|
export function* deleteAutomation(automation: Automation) {
|
||||||
yield apiFetch({
|
yield apiFetch({
|
||||||
path: `/workflows/${workflow.id}`,
|
path: `/automations/${automation.id}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -109,7 +115,7 @@ export function* deleteWorkflow(workflow: Workflow) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'DELETE_WORKFLOW',
|
type: 'DELETE_AUTOMATION',
|
||||||
workflow,
|
automation,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { State } from './types';
|
import { State } from './types';
|
||||||
|
|
||||||
export const getInitialState = (): State => ({
|
export const getInitialState = (): State => ({
|
||||||
workflows: undefined,
|
automations: undefined,
|
||||||
});
|
});
|
||||||
|
@ -1,31 +1,33 @@
|
|||||||
import { Action } from '@wordpress/data';
|
import { Action } from '@wordpress/data';
|
||||||
import { State } from './types';
|
import { State } from './types';
|
||||||
import { Workflow } from '../workflow';
|
import { Automation } from '../automation';
|
||||||
|
|
||||||
export function reducer(state: State, action: Action): State {
|
export function reducer(state: State, action: Action): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'SET_WORKFLOWS':
|
case 'SET_AUTOMATIONS':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
workflows: action.workflows,
|
automations: action.automations,
|
||||||
};
|
};
|
||||||
case 'ADD_WORKFLOW':
|
case 'ADD_AUTOMATION':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
workflows: [action.workflow, ...state.workflows],
|
automations: [action.automation, ...state.automations],
|
||||||
};
|
};
|
||||||
case 'UPDATE_WORKFLOW':
|
case 'UPDATE_AUTOMATION':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
workflows: state.workflows.map((workflow: Workflow) =>
|
automations: state.automations.map((automation: Automation) =>
|
||||||
workflow.id === action.workflow.id ? action.workflow : workflow,
|
automation.id === action.automation.id
|
||||||
|
? action.automation
|
||||||
|
: automation,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
case 'DELETE_WORKFLOW':
|
case 'DELETE_AUTOMATION':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
workflows: state.workflows.filter(
|
automations: state.automations.filter(
|
||||||
(workflow: Workflow) => workflow.id !== action.workflow.id,
|
(automation: Automation) => automation.id !== action.automation.id,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { State } from './types';
|
import { State } from './types';
|
||||||
import { Workflow } from '../workflow';
|
import { Automation } from '../automation';
|
||||||
import { workflowCount } from '../../config';
|
import { automationCount } from '../../config';
|
||||||
|
|
||||||
export function getWorkflows(state: State): Workflow[] {
|
export function getAutomations(state: State): Automation[] {
|
||||||
return state.workflows;
|
return state.automations;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWorkflowCount(state: State): number {
|
export function getAutomationCount(state: State): number {
|
||||||
return state.workflows ? state.workflows.length : workflowCount;
|
return state.automations ? state.automations.length : automationCount;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Workflow } from '../workflow';
|
import { Automation } from '../automation';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
workflows?: Workflow[];
|
automations?: Automation[];
|
||||||
};
|
};
|
||||||
|
@ -15,13 +15,13 @@ export function BuildYourOwnSection(): JSX.Element {
|
|||||||
image: `${MailPoet.cdnUrl}automation/sections/start-with-a-trigger.png`,
|
image: `${MailPoet.cdnUrl}automation/sections/start-with-a-trigger.png`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'customize-your-workflow',
|
slug: 'customize-your-automation',
|
||||||
title: __('Customize your automation', 'mailpoet'),
|
title: __('Customize your automation', 'mailpoet'),
|
||||||
text: __(
|
text: __(
|
||||||
'Choose steps and create a custom journey to best suit your needs.',
|
'Choose steps and create a custom journey to best suit your needs.',
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
),
|
),
|
||||||
image: `${MailPoet.cdnUrl}automation/sections/customize-your-workflow.png`,
|
image: `${MailPoet.cdnUrl}automation/sections/customize-your-automation.png`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'design-your-email',
|
slug: 'design-your-email',
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Button } from '@wordpress/components';
|
import { Button } from '@wordpress/components';
|
||||||
import { MailPoet } from '../../mailpoet';
|
import { MailPoet } from '../../mailpoet';
|
||||||
import { workflowTemplates } from '../templates/config';
|
import { automationTemplates } from '../templates/config';
|
||||||
import { TemplateListItem } from '../templates/components/template-list-item';
|
import { TemplateListItem } from '../templates/components/template-list-item';
|
||||||
|
|
||||||
export function TemplatesSection(): JSX.Element {
|
export function TemplatesSection(): JSX.Element {
|
||||||
const templates = workflowTemplates.slice(0, 3);
|
const templates = automationTemplates.slice(0, 3);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mailpoet-automation-section">
|
<section className="mailpoet-automation-section">
|
||||||
|
@ -1,30 +1,59 @@
|
|||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import apiFetch from '@wordpress/api-fetch';
|
||||||
import { Button } from '@wordpress/components';
|
import { Button } from '@wordpress/components';
|
||||||
import { addQueryArgs } from '@wordpress/url';
|
import { addQueryArgs } from '@wordpress/url';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { WorkflowTemplate } from '../config';
|
import { AutomationTemplate } from '../config';
|
||||||
import { useMutation } from '../../api';
|
|
||||||
import { MailPoet } from '../../../mailpoet';
|
import { MailPoet } from '../../../mailpoet';
|
||||||
import { Notice } from '../../../notices/notice';
|
import { Notice } from '../../../notices/notice';
|
||||||
|
import {
|
||||||
|
PremiumModal,
|
||||||
|
premiumValidAndActive,
|
||||||
|
} from '../../../common/premium_modal';
|
||||||
|
|
||||||
type TemplateListItemProps = {
|
type TemplateListItemProps = {
|
||||||
template: WorkflowTemplate;
|
template: AutomationTemplate;
|
||||||
heading?: 'h2' | 'h3';
|
heading?: 'h2' | 'h3';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useCreateFromTemplate = () => {
|
||||||
|
const [state, setState] = useState({
|
||||||
|
data: undefined,
|
||||||
|
loading: false,
|
||||||
|
error: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const create = useCallback(async (slug: string) => {
|
||||||
|
setState((prevState) => ({ ...prevState, loading: true }));
|
||||||
|
try {
|
||||||
|
const data = await apiFetch({
|
||||||
|
path: `/automations/create-from-template`,
|
||||||
|
method: 'POST',
|
||||||
|
data: { slug },
|
||||||
|
});
|
||||||
|
setState((prevState) => ({ ...prevState, data }));
|
||||||
|
} catch (error) {
|
||||||
|
setState((prevState) => ({ ...prevState, error }));
|
||||||
|
} finally {
|
||||||
|
setState((prevState) => ({ ...prevState, loading: false }));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return [create, state] as const;
|
||||||
|
};
|
||||||
|
|
||||||
export function TemplateListItem({
|
export function TemplateListItem({
|
||||||
template,
|
template,
|
||||||
heading,
|
heading,
|
||||||
}: TemplateListItemProps): JSX.Element {
|
}: TemplateListItemProps): JSX.Element {
|
||||||
const [createWorkflowFromTemplate, { loading, error, data }] = useMutation(
|
const [showPremium, setShowPremium] = useState(false);
|
||||||
'workflows/create-from-template',
|
const [createAutomationFromTemplate, { loading, error, data }] =
|
||||||
{
|
useCreateFromTemplate();
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
slug: template.slug,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!error && data) {
|
if (!error && data) {
|
||||||
|
MailPoet.trackEvent('Automations > Template selected', {
|
||||||
|
'Automation slug': template.slug,
|
||||||
|
});
|
||||||
window.location.href = addQueryArgs(MailPoet.urls.automationEditor, {
|
window.location.href = addQueryArgs(MailPoet.urls.automationEditor, {
|
||||||
id: data.data.id,
|
id: data.data.id,
|
||||||
});
|
});
|
||||||
@ -45,19 +74,50 @@ export function TemplateListItem({
|
|||||||
|
|
||||||
const headingTag = heading ?? 'h2';
|
const headingTag = heading ?? 'h2';
|
||||||
return (
|
return (
|
||||||
<li className="mailpoet-automation-template-list-item">
|
<li
|
||||||
|
className={`mailpoet-automation-template-list-item mailpoet-automation-template-list-item-${template.type}`}
|
||||||
|
>
|
||||||
{notice}
|
{notice}
|
||||||
<Button
|
<Button
|
||||||
isBusy={loading}
|
isBusy={loading}
|
||||||
|
disabled={template.type === 'coming-soon'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void createWorkflowFromTemplate();
|
if (template.type === 'premium' && !premiumValidAndActive) {
|
||||||
|
setShowPremium(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void createAutomationFromTemplate(template.slug);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div className="badge">
|
||||||
|
{template.type === 'coming-soon' && (
|
||||||
|
<span>{__('Coming soon', 'mailpoet')}</span>
|
||||||
|
)}
|
||||||
|
{template.type === 'premium' && (
|
||||||
|
<span>{__('Premium', 'mailpoet')}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{headingTag === 'h3' && <h3>{template.name} →</h3>}
|
{headingTag === 'h3' && <h3>{template.name} →</h3>}
|
||||||
{headingTag === 'h2' && <h2>{template.name} →</h2>}
|
{headingTag === 'h2' && <h2>{template.name} →</h2>}
|
||||||
|
|
||||||
<p>{template.description}</p>
|
<p>{template.description}</p>
|
||||||
</Button>
|
</Button>
|
||||||
|
{showPremium && (
|
||||||
|
<PremiumModal
|
||||||
|
onRequestClose={() => {
|
||||||
|
setShowPremium(false);
|
||||||
|
}}
|
||||||
|
tracking={{
|
||||||
|
utm_medium: 'upsell_modal',
|
||||||
|
utm_campaign: 'automation_premium_template',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{__(
|
||||||
|
'All templates and fully configurable automations are available in the premium version.',
|
||||||
|
'mailpoet',
|
||||||
|
)}
|
||||||
|
</PremiumModal>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
export type WorkflowTemplate = {
|
export type AutomationTemplate = {
|
||||||
slug: string;
|
slug: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
type: 'default' | 'free-only' | 'premium' | 'coming-soon';
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
mailpoet_automation_templates: WorkflowTemplate[];
|
mailpoet_automation_templates: AutomationTemplate[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const workflowTemplates = window.mailpoet_automation_templates;
|
export const automationTemplates = window.mailpoet_automation_templates;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Flex } from '@wordpress/components';
|
import { Flex } from '@wordpress/components';
|
||||||
import { workflowTemplates } from './config';
|
import { automationTemplates } from './config';
|
||||||
import { TemplateListItem } from './components/template-list-item';
|
import { TemplateListItem } from './components/template-list-item';
|
||||||
import { initializeApi } from '../api';
|
import { initializeApi } from '../api';
|
||||||
import { registerTranslations } from '../i18n';
|
import { registerTranslations } from '../i18n';
|
||||||
@ -23,7 +23,7 @@ function Templates(): JSX.Element {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<ul className="mailpoet-automation-templates">
|
<ul className="mailpoet-automation-templates">
|
||||||
{workflowTemplates.map((template) => (
|
{automationTemplates.map((template) => (
|
||||||
<TemplateListItem key={template.slug} template={template} />
|
<TemplateListItem key={template.slug} template={template} />
|
||||||
))}
|
))}
|
||||||
<FromScratchListItem />
|
<FromScratchListItem />
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
import { ReactNode } from 'react';
|
|
||||||
import { useMutation } from './api';
|
|
||||||
import { Step, Workflow } from './editor/components/workflow/types';
|
|
||||||
|
|
||||||
export const createRootStep = (): Step => ({
|
|
||||||
id: 'root',
|
|
||||||
type: 'root',
|
|
||||||
key: 'core:root',
|
|
||||||
args: {},
|
|
||||||
next_steps: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const createWorkflow = (): Partial<Workflow> => ({
|
|
||||||
name: 'Empty workflow',
|
|
||||||
steps: {
|
|
||||||
root: createRootStep(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function CreateEmptyWorkflowButton(): JSX.Element {
|
|
||||||
const [createSchema, { loading, error }] = useMutation('workflows', {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
className="button"
|
|
||||||
type="button"
|
|
||||||
onClick={async () => {
|
|
||||||
await createSchema({
|
|
||||||
body: JSON.stringify(createWorkflow()),
|
|
||||||
});
|
|
||||||
window.location.reload();
|
|
||||||
}}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
Create empty workflow (premium required)
|
|
||||||
</button>
|
|
||||||
{error && (
|
|
||||||
<div>{error?.data?.message ?? 'An unknown error occurred'}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type TemplateButtonProps = {
|
|
||||||
slug: string;
|
|
||||||
children?: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function CreateWorkflowFromTemplateButton({
|
|
||||||
slug,
|
|
||||||
children,
|
|
||||||
}: TemplateButtonProps): JSX.Element {
|
|
||||||
const [createWorkflowFromTemplate, { loading, error }] = useMutation(
|
|
||||||
'workflows/create-from-template',
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
slug,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
className="button button-primary"
|
|
||||||
type="button"
|
|
||||||
onClick={async () => {
|
|
||||||
await createWorkflowFromTemplate();
|
|
||||||
window.location.reload();
|
|
||||||
}}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
{error && (
|
|
||||||
<div>{error?.data?.message ?? 'An unknown error occurred'}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ import { Dispatch } from 'react';
|
|||||||
import { DropdownMenu } from '@wordpress/components';
|
import { DropdownMenu } from '@wordpress/components';
|
||||||
import { StoreConfig } from '@wordpress/data';
|
import { StoreConfig } from '@wordpress/data';
|
||||||
import { Item } from '../editor/components/inserter/item';
|
import { Item } from '../editor/components/inserter/item';
|
||||||
import { Step } from '../editor/components/workflow/types';
|
import { Step } from '../editor/components/automation/types';
|
||||||
import { State } from '../editor/store/types';
|
import { State } from '../editor/store/types';
|
||||||
|
|
||||||
export type MoreControlType = {
|
export type MoreControlType = {
|
||||||
@ -20,17 +20,17 @@ export type MoreControlType = {
|
|||||||
* APPLICATION HOOKS
|
* APPLICATION HOOKS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// mailpoet.automation.workflow.step.more-controls
|
// mailpoet.automation.step.more-controls
|
||||||
// mailpoet.automation.hero.actions
|
// mailpoet.automation.hero.actions
|
||||||
export type StepMoreControlsType = Record<string, MoreControlType>;
|
export type StepMoreControlsType = Record<string, MoreControlType>;
|
||||||
|
|
||||||
// mailpoet.automation.workflow.add_step_callback
|
// mailpoet.automation.add_step_callback
|
||||||
export type AddStepCallbackType = (item?: Item) => void;
|
export type AddStepCallbackType = (item?: Item) => void;
|
||||||
|
|
||||||
// mailpoet.automation.workflow.render_step
|
// mailpoet.automation.render_step
|
||||||
export type RenderStepType = (step: Step) => JSX.Element;
|
export type RenderStepType = (step: Step) => JSX.Element;
|
||||||
|
|
||||||
// mailpoet.automation.workflow.render_step_separator
|
// mailpoet.automation.render_step_separator
|
||||||
export type RenderStepSeparatorType = (step: Step) => JSX.Element;
|
export type RenderStepSeparatorType = (step: Step) => JSX.Element;
|
||||||
|
|
||||||
// mailpoet.automation.editor.create_store
|
// mailpoet.automation.editor.create_store
|
||||||
|
@ -6,14 +6,14 @@ MailPoet.I18n.add('excellentBadgeName', 'Excellent');
|
|||||||
MailPoet.I18n.add('excellentBadgeTooltip', 'Congrats!');
|
MailPoet.I18n.add('excellentBadgeTooltip', 'Congrats!');
|
||||||
MailPoet.I18n.add('goodBadgeName', 'Good');
|
MailPoet.I18n.add('goodBadgeName', 'Good');
|
||||||
MailPoet.I18n.add('goodBadgeTooltip', 'Good stuff.');
|
MailPoet.I18n.add('goodBadgeTooltip', 'Good stuff.');
|
||||||
MailPoet.I18n.add('averageBadgeName', 'Average');
|
MailPoet.I18n.add('criticalBadgeName', 'Critical');
|
||||||
MailPoet.I18n.add('averageBadgeTooltip', 'Something to improve.');
|
MailPoet.I18n.add('criticalBadgeTooltip', 'Something to improve.');
|
||||||
MailPoet.I18n.add('openedStatTooltipExcellent', 'above 30%');
|
MailPoet.I18n.add('openedStatTooltipExcellent', 'above 30%');
|
||||||
MailPoet.I18n.add('openedStatTooltipGood', 'between 10 and 30%');
|
MailPoet.I18n.add('openedStatTooltipGood', 'between 10 and 30%');
|
||||||
MailPoet.I18n.add('openedStatTooltipAverage', 'under 10%');
|
MailPoet.I18n.add('openedStatTooltipCritical', 'under 10%');
|
||||||
MailPoet.I18n.add('clickedStatTooltipExcellent', 'above 3%');
|
MailPoet.I18n.add('clickedStatTooltipExcellent', 'above 3%');
|
||||||
MailPoet.I18n.add('clickedStatTooltipGood', 'between 1 and 3%');
|
MailPoet.I18n.add('clickedStatTooltipGood', 'between 1 and 3%');
|
||||||
MailPoet.I18n.add('clickedStatTooltipAverage', 'under 1%');
|
MailPoet.I18n.add('clickedStatTooltipCritical', 'under 1%');
|
||||||
MailPoet.I18n.add(
|
MailPoet.I18n.add(
|
||||||
'revenueStatsTooltipShort',
|
'revenueStatsTooltipShort',
|
||||||
'Revenues by customer who clicked on this email in the last 2 weeks.',
|
'Revenues by customer who clicked on this email in the last 2 weeks.',
|
||||||
|
@ -42,6 +42,12 @@ export function NewsletterStats({
|
|||||||
rate={clicked}
|
rate={clicked}
|
||||||
tooltipId={`clicked-${newsletterId || '0'}`}
|
tooltipId={`clicked-${newsletterId || '0'}`}
|
||||||
/>
|
/>
|
||||||
|
<br />
|
||||||
|
<StatsBadge
|
||||||
|
stat="opened"
|
||||||
|
rate={opened}
|
||||||
|
tooltipId={`opened-${newsletterId || '0'}`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,7 @@ type BadgeProps = {
|
|||||||
tooltip?: string | ReactNode;
|
tooltip?: string | ReactNode;
|
||||||
tooltipId?: string;
|
tooltipId?: string;
|
||||||
tooltipPlace?: Place;
|
tooltipPlace?: Place;
|
||||||
type?: 'average' | 'good' | 'excellent' | 'unknown';
|
type?: 'average' | 'good' | 'excellent' | 'critical' | 'unknown';
|
||||||
isInverted?: boolean;
|
isInverted?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13,21 +13,39 @@ type StatsBadgeProps = {
|
|||||||
const stats = {
|
const stats = {
|
||||||
opened: {
|
opened: {
|
||||||
badgeRanges: [30, 10, 0],
|
badgeRanges: [30, 10, 0],
|
||||||
badgeTypes: ['excellent', 'good', 'average'],
|
badgeTypes: ['excellent', 'good', 'critical'],
|
||||||
tooltipText: [
|
tooltipText: {
|
||||||
MailPoet.I18n.t('openedStatTooltipExcellent'),
|
excellent: MailPoet.I18n.t('openedStatTooltipExcellent'),
|
||||||
MailPoet.I18n.t('openedStatTooltipGood'),
|
good: MailPoet.I18n.t('openedStatTooltipGood'),
|
||||||
MailPoet.I18n.t('openedStatTooltipAverage'),
|
critical: MailPoet.I18n.t('openedStatTooltipCritical'),
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
clicked: {
|
clicked: {
|
||||||
badgeRanges: [3, 1, 0],
|
badgeRanges: [3, 1, 0],
|
||||||
badgeTypes: ['excellent', 'good', 'average'],
|
badgeTypes: ['excellent', 'good', 'critical'],
|
||||||
tooltipText: [
|
tooltipText: {
|
||||||
MailPoet.I18n.t('clickedStatTooltipExcellent'),
|
excellent: MailPoet.I18n.t('clickedStatTooltipExcellent'),
|
||||||
MailPoet.I18n.t('clickedStatTooltipGood'),
|
good: MailPoet.I18n.t('clickedStatTooltipGood'),
|
||||||
MailPoet.I18n.t('clickedStatTooltipAverage'),
|
critical: MailPoet.I18n.t('clickedStatTooltipCritical'),
|
||||||
],
|
},
|
||||||
|
},
|
||||||
|
bounced: {
|
||||||
|
badgeRanges: [1.5, 0.5, 0],
|
||||||
|
badgeTypes: ['critical', 'good', 'excellent'],
|
||||||
|
tooltipText: {
|
||||||
|
excellent: MailPoet.I18n.t('bouncedStatTooltipExcellent'),
|
||||||
|
good: MailPoet.I18n.t('bouncedStatTooltipGood'),
|
||||||
|
critical: MailPoet.I18n.t('bouncedStatTooltipCritical'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
unsubscribed: {
|
||||||
|
badgeRanges: [0.7, 0.3, 0],
|
||||||
|
badgeTypes: ['critical', 'good', 'excellent'],
|
||||||
|
tooltipText: {
|
||||||
|
excellent: MailPoet.I18n.t('unsubscribeStatTooltipExcellent'),
|
||||||
|
good: MailPoet.I18n.t('unsubscribeStatTooltipGood'),
|
||||||
|
critical: MailPoet.I18n.t('unsubscribeStatTooltipCritical'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -60,9 +78,9 @@ function StatsBadge(props: StatsBadgeProps) {
|
|||||||
name: MailPoet.I18n.t('goodBadgeName'),
|
name: MailPoet.I18n.t('goodBadgeName'),
|
||||||
tooltipTitle: MailPoet.I18n.t('goodBadgeTooltip'),
|
tooltipTitle: MailPoet.I18n.t('goodBadgeTooltip'),
|
||||||
},
|
},
|
||||||
average: {
|
critical: {
|
||||||
name: MailPoet.I18n.t('averageBadgeName'),
|
name: MailPoet.I18n.t('criticalBadgeName'),
|
||||||
tooltipTitle: MailPoet.I18n.t('averageBadgeTooltip'),
|
tooltipTitle: MailPoet.I18n.t('criticalBadgeTooltip'),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -86,15 +104,15 @@ function StatsBadge(props: StatsBadgeProps) {
|
|||||||
<div className="mailpoet-listing-stats-tooltip-content">
|
<div className="mailpoet-listing-stats-tooltip-content">
|
||||||
<Badge type="excellent" name={badges.excellent.name} />
|
<Badge type="excellent" name={badges.excellent.name} />
|
||||||
{' : '}
|
{' : '}
|
||||||
{stat.tooltipText[0]}
|
{stat.tooltipText.excellent}
|
||||||
<br />
|
<br />
|
||||||
<Badge type="good" name={badges.good.name} />
|
<Badge type="good" name={badges.good.name} />
|
||||||
{' : '}
|
{' : '}
|
||||||
{stat.tooltipText[1]}
|
{stat.tooltipText.good}
|
||||||
<br />
|
<br />
|
||||||
<Badge type="average" name={badges.average.name} />
|
<Badge type="critical" name={badges.critical.name} />
|
||||||
{' : '}
|
{' : '}
|
||||||
{stat.tooltipText[2]}
|
{stat.tooltipText.critical}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -22,7 +22,8 @@ import {
|
|||||||
UtmParams,
|
UtmParams,
|
||||||
} from './upgrade_info';
|
} from './upgrade_info';
|
||||||
|
|
||||||
const premiumValidAndActive = premiumFeaturesEnabled && MailPoet.premiumActive;
|
export const premiumValidAndActive =
|
||||||
|
premiumFeaturesEnabled && MailPoet.premiumActive;
|
||||||
|
|
||||||
type Props = Omit<ComponentProps<typeof Modal>, 'title' | 'onRequestClose'> & {
|
type Props = Omit<ComponentProps<typeof Modal>, 'title' | 'onRequestClose'> & {
|
||||||
// Fix type from "@types/wordpress__components" where it is defined as a union of event
|
// Fix type from "@types/wordpress__components" where it is defined as a union of event
|
||||||
|
@ -61,6 +61,20 @@ export function Tags() {
|
|||||||
Excellent
|
Excellent
|
||||||
</Tag>
|
</Tag>
|
||||||
<div className="mailpoet-gap" />
|
<div className="mailpoet-gap" />
|
||||||
|
<Tag dimension="large" variant="critical">
|
||||||
|
Critical
|
||||||
|
</Tag>
|
||||||
|
|
||||||
|
<Tag dimension="large" variant="critical" isInverted>
|
||||||
|
Critical
|
||||||
|
</Tag>
|
||||||
|
<br />
|
||||||
|
<Tag variant="critical">Critical</Tag>
|
||||||
|
|
||||||
|
<Tag variant="critical" isInverted>
|
||||||
|
Critical
|
||||||
|
</Tag>
|
||||||
|
<div className="mailpoet-gap" />
|
||||||
<Tag dimension="large" variant="wordpress">
|
<Tag dimension="large" variant="wordpress">
|
||||||
WordPress
|
WordPress
|
||||||
</Tag>
|
</Tag>
|
||||||
|
@ -1,9 +1,18 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
export type TagVariant =
|
||||||
|
| 'average'
|
||||||
|
| 'good'
|
||||||
|
| 'excellent'
|
||||||
|
| 'critical'
|
||||||
|
| 'list'
|
||||||
|
| 'unknown'
|
||||||
|
| 'wordpress';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
variant?: 'average' | 'good' | 'excellent' | 'list' | 'unknown' | 'wordpress';
|
variant?: TagVariant;
|
||||||
dimension?: 'large';
|
dimension?: 'large';
|
||||||
isInverted?: boolean;
|
isInverted?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Tag } from './tag';
|
import { Tag, TagVariant } from './tag';
|
||||||
import { Tooltip } from '../tooltip/tooltip';
|
import { Tooltip } from '../tooltip/tooltip';
|
||||||
import { MailPoet } from '../../mailpoet';
|
import { MailPoet } from '../../mailpoet';
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ type Props = {
|
|||||||
segments?: Segment[];
|
segments?: Segment[];
|
||||||
subscriberTags?: SubscriberTag[];
|
subscriberTags?: SubscriberTag[];
|
||||||
strings?: string[];
|
strings?: string[];
|
||||||
variant?: 'average' | 'good' | 'excellent' | 'list' | 'unknown' | 'wordpress';
|
variant?: TagVariant;
|
||||||
isInverted?: boolean;
|
isInverted?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ Module.SaveView = Marionette.View.extend({
|
|||||||
'click .mailpoet_save_activate_wc_customizer_button':
|
'click .mailpoet_save_activate_wc_customizer_button':
|
||||||
'activateWooCommerceCustomizer',
|
'activateWooCommerceCustomizer',
|
||||||
/* Automation email */
|
/* Automation email */
|
||||||
'click .mailpoet_save_go_to_workflow': 'saveAndGoToWorkflow',
|
'click .mailpoet_save_go_to_automation': 'saveAndGoToAutomation',
|
||||||
'click .mailpoet_show_preview': 'showPreview',
|
'click .mailpoet_show_preview': 'showPreview',
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -316,13 +316,13 @@ Module.SaveView = Marionette.View.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveAndGoToWorkflow: function () {
|
saveAndGoToAutomation: function () {
|
||||||
this.hideSaveOptions();
|
this.hideSaveOptions();
|
||||||
Module._cancelAutosave();
|
Module._cancelAutosave();
|
||||||
Module.save().done(function () {
|
Module.save().done(function () {
|
||||||
const newsletter = App.getNewsletter();
|
const newsletter = App.getNewsletter();
|
||||||
const workflowId = newsletter.get('options').get('workflowId');
|
const automationId = newsletter.get('options').get('automationId');
|
||||||
const goToUrl = `admin.php?page=mailpoet-automation-editor&id=${workflowId}`;
|
const goToUrl = `admin.php?page=mailpoet-automation-editor&id=${automationId}`;
|
||||||
window.location.href = goToUrl;
|
window.location.href = goToUrl;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -18,8 +18,8 @@ const renderHeading = (newsletterType, newsletterOptions) => {
|
|||||||
window.location = `admin.php?page=mailpoet-newsletters`;
|
window.location = `admin.php?page=mailpoet-newsletters`;
|
||||||
};
|
};
|
||||||
if (newsletterType === 'automation') {
|
if (newsletterType === 'automation') {
|
||||||
const workflowId = newsletterOptions.workflowId;
|
const automationId = newsletterOptions.automationId;
|
||||||
const goToUrl = `admin.php?page=mailpoet-automation-editor&id=${workflowId}`;
|
const goToUrl = `admin.php?page=mailpoet-automation-editor&id=${automationId}`;
|
||||||
onLogoClick = () => {
|
onLogoClick = () => {
|
||||||
window.location = goToUrl;
|
window.location = goToUrl;
|
||||||
};
|
};
|
||||||
@ -27,7 +27,7 @@ const renderHeading = (newsletterType, newsletterOptions) => {
|
|||||||
const onClickPreview = () =>
|
const onClickPreview = () =>
|
||||||
document.querySelector('.mailpoet_show_preview').click();
|
document.querySelector('.mailpoet_show_preview').click();
|
||||||
const onClickSave = () =>
|
const onClickSave = () =>
|
||||||
document.querySelector('.mailpoet_save_go_to_workflow').click();
|
document.querySelector('.mailpoet_save_go_to_automation').click();
|
||||||
buttons = (
|
buttons = (
|
||||||
<>
|
<>
|
||||||
<input
|
<input
|
||||||
|
@ -17,6 +17,9 @@ type Props = {
|
|||||||
|
|
||||||
const minNewslettersSent = 20;
|
const minNewslettersSent = 20;
|
||||||
const minNewslettersOpened = 5;
|
const minNewslettersOpened = 5;
|
||||||
|
const minUnsubscribedStat = 5;
|
||||||
|
const minBouncedStat = 5;
|
||||||
|
const minNewslettersSentForBouncedAndUnsubscribed = 100;
|
||||||
|
|
||||||
// When percentage value is lower then 0.1 we want to display value with two decimal places
|
// When percentage value is lower then 0.1 we want to display value with two decimal places
|
||||||
const formatWithOptimalPrecision = (value: number) => {
|
const formatWithOptimalPrecision = (value: number) => {
|
||||||
@ -24,6 +27,16 @@ const formatWithOptimalPrecision = (value: number) => {
|
|||||||
return MailPoet.Num.toLocaleFixed(value, precision);
|
return MailPoet.Num.toLocaleFixed(value, precision);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FormatForStats
|
||||||
|
* always round-up to one decimal place
|
||||||
|
* in stats.tsx, we are comparing against 0, 0.3, 0.5, 0.7, 1.5, etc
|
||||||
|
*/
|
||||||
|
const formatForStats = (value: number): number => {
|
||||||
|
const numValue = +value;
|
||||||
|
return +numValue.toFixed(1);
|
||||||
|
};
|
||||||
|
|
||||||
export function NewsletterGeneralStats({
|
export function NewsletterGeneralStats({
|
||||||
newsletter,
|
newsletter,
|
||||||
isWoocommerceActive,
|
isWoocommerceActive,
|
||||||
@ -61,14 +74,35 @@ export function NewsletterGeneralStats({
|
|||||||
totalSent >= minNewslettersSent &&
|
totalSent >= minNewslettersSent &&
|
||||||
newsletter.statistics.opened >= minNewslettersOpened;
|
newsletter.statistics.opened >= minNewslettersOpened;
|
||||||
|
|
||||||
|
const displayUnsubscribedBadge =
|
||||||
|
newsletter.statistics.unsubscribed >= minUnsubscribedStat &&
|
||||||
|
totalSent >= minNewslettersSentForBouncedAndUnsubscribed;
|
||||||
|
const displayBouncedBadge =
|
||||||
|
newsletter.statistics.bounced >= minBouncedStat &&
|
||||||
|
totalSent >= minNewslettersSentForBouncedAndUnsubscribed;
|
||||||
|
|
||||||
|
const badgeTypeOpened = getBadgeType('opened', percentageOpened) || '';
|
||||||
const opened = (
|
const opened = (
|
||||||
<div className="mailpoet-statistics-value-small">
|
<>
|
||||||
<span className="mailpoet-statistics-value-number">
|
<div className="mailpoet-statistics-value-small">
|
||||||
{percentageOpenedDisplay}
|
<span
|
||||||
{'% '}
|
className={`mailpoet-statistics-value-number mailpoet-statistics-value-number-${badgeTypeOpened}`}
|
||||||
</span>
|
>
|
||||||
{MailPoet.I18n.t('percentageOpened')}
|
{percentageOpenedDisplay}
|
||||||
</div>
|
{'% '}
|
||||||
|
</span>
|
||||||
|
{MailPoet.I18n.t('percentageOpened')}
|
||||||
|
</div>
|
||||||
|
{displayBadges && (
|
||||||
|
<StatsBadge
|
||||||
|
isInverted={false}
|
||||||
|
stat="opened"
|
||||||
|
rate={percentageOpened}
|
||||||
|
tooltipId={`opened-${newsletter.id || '0'}`}
|
||||||
|
tooltipPlace="right"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const machineOpened = (
|
const machineOpened = (
|
||||||
@ -100,24 +134,60 @@ export function NewsletterGeneralStats({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const formattedPercentageUnsubscribed = formatForStats(
|
||||||
|
percentageUnsubscribed,
|
||||||
|
);
|
||||||
|
const badgeTypeUnsubscribed = displayUnsubscribedBadge
|
||||||
|
? getBadgeType('unsubscribed', formattedPercentageUnsubscribed)
|
||||||
|
: '';
|
||||||
const unsubscribed = (
|
const unsubscribed = (
|
||||||
<div className="mailpoet-statistics-value-small">
|
<>
|
||||||
<span className="mailpoet-statistics-value-number">
|
<div className="mailpoet-statistics-value-small">
|
||||||
{percentageUnsubscribedDisplay}
|
<span
|
||||||
{'% '}
|
className={`mailpoet-statistics-value-number mailpoet-statistics-value-number-${badgeTypeUnsubscribed}`}
|
||||||
</span>
|
>
|
||||||
{MailPoet.I18n.t('percentageUnsubscribed')}
|
{percentageUnsubscribedDisplay}
|
||||||
</div>
|
{'% '}
|
||||||
|
</span>
|
||||||
|
{MailPoet.I18n.t('percentageUnsubscribed')}
|
||||||
|
</div>
|
||||||
|
{displayUnsubscribedBadge && (
|
||||||
|
<StatsBadge
|
||||||
|
isInverted={false}
|
||||||
|
stat="unsubscribed"
|
||||||
|
rate={formattedPercentageUnsubscribed}
|
||||||
|
tooltipId={`unsubscribed-${newsletter.id || '0'}`}
|
||||||
|
tooltipPlace="right"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const formattedPercentageBounced = formatForStats(percentageBounced);
|
||||||
|
const badgeTypeBounced = displayBouncedBadge
|
||||||
|
? getBadgeType('bounced', formattedPercentageBounced)
|
||||||
|
: '';
|
||||||
const bounced = (
|
const bounced = (
|
||||||
<div className="mailpoet-statistics-value-small">
|
<>
|
||||||
<span className="mailpoet-statistics-value-number">
|
<div className="mailpoet-statistics-value-small">
|
||||||
{percentageBouncedDisplay}
|
<span
|
||||||
{'% '}
|
className={`mailpoet-statistics-value-number mailpoet-statistics-value-number-${badgeTypeBounced}`}
|
||||||
</span>
|
>
|
||||||
{MailPoet.I18n.t('percentageBounced')}
|
{percentageBouncedDisplay}
|
||||||
</div>
|
{'% '}
|
||||||
|
</span>
|
||||||
|
{MailPoet.I18n.t('percentageBounced')}
|
||||||
|
</div>
|
||||||
|
{displayBouncedBadge && (
|
||||||
|
<StatsBadge
|
||||||
|
isInverted={false}
|
||||||
|
stat="bounced"
|
||||||
|
rate={formattedPercentageBounced}
|
||||||
|
tooltipId={`bounced-${newsletter.id || '0'}`}
|
||||||
|
tooltipPlace="right"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const badgeTypeClicked = getBadgeType('clicked', percentageClicked);
|
const badgeTypeClicked = getBadgeType('clicked', percentageClicked);
|
||||||
|
@ -99,6 +99,10 @@ function CampaignStatsPageComponent({ match, history, location }: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!newsletter) {
|
||||||
|
return <h3> {MailPoet.I18n.t('emailDoesNotExist')} </h3>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HideScreenOptions />
|
<HideScreenOptions />
|
||||||
|
@ -203,11 +203,11 @@ class NewsletterSendComponent extends Component {
|
|||||||
: null;
|
: null;
|
||||||
const item = response.data;
|
const item = response.data;
|
||||||
// Automation type emails should redirect
|
// Automation type emails should redirect
|
||||||
// to an associated workflow from the send page
|
// to an associated automation from the send page
|
||||||
if (item.type === 'automation') {
|
if (item.type === 'automation') {
|
||||||
const workflowId = item.options?.workflowId;
|
const automationId = item.options?.automationId;
|
||||||
const goToUrl = workflowId
|
const goToUrl = automationId
|
||||||
? `admin.php?page=mailpoet-automation-editor&id=${workflowId}`
|
? `admin.php?page=mailpoet-automation-editor&id=${automationId}`
|
||||||
: '/new';
|
: '/new';
|
||||||
return this.setState(
|
return this.setState(
|
||||||
{
|
{
|
||||||
|
@ -317,9 +317,9 @@ class NewsletterTemplates extends Component {
|
|||||||
let buttons = null;
|
let buttons = null;
|
||||||
let onClick;
|
let onClick;
|
||||||
if (this.state.emailType === 'automation') {
|
if (this.state.emailType === 'automation') {
|
||||||
const workflowId = this.state.emailOptions?.workflowId;
|
const automationId = this.state.emailOptions?.automationId;
|
||||||
const goToUrl = workflowId
|
const goToUrl = automationId
|
||||||
? `admin.php?page=mailpoet-automation-editor&id=${workflowId}`
|
? `admin.php?page=mailpoet-automation-editor&id=${automationId}`
|
||||||
: 'admin.php?page=mailpoet-automation';
|
: 'admin.php?page=mailpoet-automation';
|
||||||
onClick = () => {
|
onClick = () => {
|
||||||
window.location = goToUrl;
|
window.location = goToUrl;
|
||||||
|
@ -5,7 +5,6 @@ namespace MailPoet\API\MP\v1;
|
|||||||
use MailPoet\API\JSON\ResponseBuilders\SubscribersResponseBuilder;
|
use MailPoet\API\JSON\ResponseBuilders\SubscribersResponseBuilder;
|
||||||
use MailPoet\Entities\SegmentEntity;
|
use MailPoet\Entities\SegmentEntity;
|
||||||
use MailPoet\Entities\SubscriberEntity;
|
use MailPoet\Entities\SubscriberEntity;
|
||||||
use MailPoet\Features\FeaturesController;
|
|
||||||
use MailPoet\Listing\ListingDefinition;
|
use MailPoet\Listing\ListingDefinition;
|
||||||
use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
|
use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
|
||||||
use MailPoet\Segments\SegmentsRepository;
|
use MailPoet\Segments\SegmentsRepository;
|
||||||
@ -54,9 +53,6 @@ class Subscribers {
|
|||||||
/** @var SubscriberSaveController */
|
/** @var SubscriberSaveController */
|
||||||
private $subscriberSaveController;
|
private $subscriberSaveController;
|
||||||
|
|
||||||
/** @var FeaturesController */
|
|
||||||
private $featuresController;
|
|
||||||
|
|
||||||
/** @var RequiredCustomFieldValidator */
|
/** @var RequiredCustomFieldValidator */
|
||||||
private $requiredCustomFieldsValidator;
|
private $requiredCustomFieldsValidator;
|
||||||
|
|
||||||
@ -76,7 +72,6 @@ class Subscribers {
|
|||||||
SubscriberSaveController $subscriberSaveController,
|
SubscriberSaveController $subscriberSaveController,
|
||||||
SubscribersResponseBuilder $subscribersResponseBuilder,
|
SubscribersResponseBuilder $subscribersResponseBuilder,
|
||||||
WelcomeScheduler $welcomeScheduler,
|
WelcomeScheduler $welcomeScheduler,
|
||||||
FeaturesController $featuresController,
|
|
||||||
RequiredCustomFieldValidator $requiredCustomFieldsValidator,
|
RequiredCustomFieldValidator $requiredCustomFieldsValidator,
|
||||||
SubscriberListingRepository $subscriberListingRepository,
|
SubscriberListingRepository $subscriberListingRepository,
|
||||||
WPFunctions $wp
|
WPFunctions $wp
|
||||||
@ -90,7 +85,6 @@ class Subscribers {
|
|||||||
$this->subscriberSaveController = $subscriberSaveController;
|
$this->subscriberSaveController = $subscriberSaveController;
|
||||||
$this->subscribersResponseBuilder = $subscribersResponseBuilder;
|
$this->subscribersResponseBuilder = $subscribersResponseBuilder;
|
||||||
$this->welcomeScheduler = $welcomeScheduler;
|
$this->welcomeScheduler = $welcomeScheduler;
|
||||||
$this->featuresController = $featuresController;
|
|
||||||
$this->requiredCustomFieldsValidator = $requiredCustomFieldsValidator;
|
$this->requiredCustomFieldsValidator = $requiredCustomFieldsValidator;
|
||||||
$this->wp = $wp;
|
$this->wp = $wp;
|
||||||
$this->subscriberListingRepository = $subscriberListingRepository;
|
$this->subscriberListingRepository = $subscriberListingRepository;
|
||||||
@ -202,10 +196,7 @@ class Subscribers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when global status changes to subscribed, fire subscribed hook for all subscribed segments
|
// when global status changes to subscribed, fire subscribed hook for all subscribed segments
|
||||||
if (
|
if ($subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||||
$this->featuresController->isSupported(FeaturesController::AUTOMATION)
|
|
||||||
&& $subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED
|
|
||||||
) {
|
|
||||||
$subscriberSegments = $subscriber->getSubscriberSegments();
|
$subscriberSegments = $subscriber->getSubscriberSegments();
|
||||||
foreach ($subscriberSegments as $subscriberSegment) {
|
foreach ($subscriberSegments as $subscriberSegment) {
|
||||||
if ($subscriberSegment->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) {
|
if ($subscriberSegment->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||||
|
@ -3,10 +3,9 @@
|
|||||||
namespace MailPoet\AdminPages\Pages;
|
namespace MailPoet\AdminPages\Pages;
|
||||||
|
|
||||||
use MailPoet\AdminPages\PageRenderer;
|
use MailPoet\AdminPages\PageRenderer;
|
||||||
use MailPoet\Automation\Engine\Data\WorkflowTemplate;
|
use MailPoet\Automation\Engine\Data\AutomationTemplate;
|
||||||
use MailPoet\Automation\Engine\Migrations\Migrator;
|
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||||
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
|
use MailPoet\Automation\Engine\Storage\AutomationTemplateStorage;
|
||||||
use MailPoet\Automation\Engine\Storage\WorkflowTemplateStorage;
|
|
||||||
use MailPoet\Form\AssetsController;
|
use MailPoet\Form\AssetsController;
|
||||||
use MailPoet\WP\Functions as WPFunctions;
|
use MailPoet\WP\Functions as WPFunctions;
|
||||||
|
|
||||||
@ -14,51 +13,42 @@ class Automation {
|
|||||||
/** @var AssetsController */
|
/** @var AssetsController */
|
||||||
private $assetsController;
|
private $assetsController;
|
||||||
|
|
||||||
/** @var Migrator */
|
|
||||||
private $migrator;
|
|
||||||
|
|
||||||
/** @var PageRenderer */
|
/** @var PageRenderer */
|
||||||
private $pageRenderer;
|
private $pageRenderer;
|
||||||
|
|
||||||
/** @var WPFunctions */
|
/** @var WPFunctions */
|
||||||
private $wp;
|
private $wp;
|
||||||
|
|
||||||
/** @var WorkflowStorage */
|
/** @var AutomationStorage */
|
||||||
private $workflowStorage;
|
private $automationStorage;
|
||||||
|
|
||||||
/** @var WorkflowTemplateStorage */
|
/** @var AutomationTemplateStorage */
|
||||||
private $templateStorage;
|
private $templateStorage;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
AssetsController $assetsController,
|
AssetsController $assetsController,
|
||||||
Migrator $migrator,
|
|
||||||
PageRenderer $pageRenderer,
|
PageRenderer $pageRenderer,
|
||||||
WPFunctions $wp,
|
WPFunctions $wp,
|
||||||
WorkflowStorage $workflowStorage,
|
AutomationStorage $automationStorage,
|
||||||
WorkflowTemplateStorage $templateStorage
|
AutomationTemplateStorage $templateStorage
|
||||||
) {
|
) {
|
||||||
$this->assetsController = $assetsController;
|
$this->assetsController = $assetsController;
|
||||||
$this->migrator = $migrator;
|
|
||||||
$this->pageRenderer = $pageRenderer;
|
$this->pageRenderer = $pageRenderer;
|
||||||
$this->wp = $wp;
|
$this->wp = $wp;
|
||||||
$this->workflowStorage = $workflowStorage;
|
$this->automationStorage = $automationStorage;
|
||||||
$this->templateStorage = $templateStorage;
|
$this->templateStorage = $templateStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render() {
|
public function render() {
|
||||||
$this->assetsController->setupAutomationListingDependencies();
|
$this->assetsController->setupAutomationListingDependencies();
|
||||||
|
|
||||||
if (!$this->migrator->hasSchema()) {
|
|
||||||
$this->migrator->createSchema();
|
|
||||||
}
|
|
||||||
$this->pageRenderer->displayPage('automation.html', [
|
$this->pageRenderer->displayPage('automation.html', [
|
||||||
'api' => [
|
'api' => [
|
||||||
'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'),
|
'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'),
|
||||||
'nonce' => $this->wp->wpCreateNonce('wp_rest'),
|
'nonce' => $this->wp->wpCreateNonce('wp_rest'),
|
||||||
],
|
],
|
||||||
'workflowCount' => $this->workflowStorage->getWorkflowCount(),
|
'automationCount' => $this->automationStorage->getAutomationCount(),
|
||||||
'templates' => array_map(
|
'templates' => array_map(
|
||||||
function(WorkflowTemplate $template): array {
|
function(AutomationTemplate $template): array {
|
||||||
return $template->toArray();
|
return $template->toArray();
|
||||||
},
|
},
|
||||||
$this->templateStorage->getTemplates()
|
$this->templateStorage->getTemplates()
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
namespace MailPoet\AdminPages\Pages;
|
namespace MailPoet\AdminPages\Pages;
|
||||||
|
|
||||||
use MailPoet\AdminPages\PageRenderer;
|
use MailPoet\AdminPages\PageRenderer;
|
||||||
use MailPoet\Automation\Engine\Data\Workflow;
|
use MailPoet\Automation\Engine\Data\Automation;
|
||||||
use MailPoet\Automation\Engine\Hooks;
|
use MailPoet\Automation\Engine\Hooks;
|
||||||
use MailPoet\Automation\Engine\Mappers\WorkflowMapper;
|
use MailPoet\Automation\Engine\Mappers\AutomationMapper;
|
||||||
use MailPoet\Automation\Engine\Registry;
|
use MailPoet\Automation\Engine\Registry;
|
||||||
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
|
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||||
use MailPoet\Form\AssetsController;
|
use MailPoet\Form\AssetsController;
|
||||||
use MailPoet\Segments\SegmentsRepository;
|
use MailPoet\Segments\SegmentsRepository;
|
||||||
use MailPoet\WP\Functions as WPFunctions;
|
use MailPoet\WP\Functions as WPFunctions;
|
||||||
@ -17,11 +17,11 @@ class AutomationEditor {
|
|||||||
/** @var AssetsController */
|
/** @var AssetsController */
|
||||||
private $assetsController;
|
private $assetsController;
|
||||||
|
|
||||||
/** @var WorkflowMapper */
|
/** @var AutomationMapper */
|
||||||
private $workflowMapper;
|
private $automationMapper;
|
||||||
|
|
||||||
/** @var WorkflowStorage */
|
/** @var AutomationStorage */
|
||||||
private $workflowStorage;
|
private $automationStorage;
|
||||||
|
|
||||||
/** @var PageRenderer */
|
/** @var PageRenderer */
|
||||||
private $pageRenderer;
|
private $pageRenderer;
|
||||||
@ -37,16 +37,16 @@ class AutomationEditor {
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
AssetsController $assetsController,
|
AssetsController $assetsController,
|
||||||
WorkflowMapper $workflowMapper,
|
AutomationMapper $automationMapper,
|
||||||
WorkflowStorage $workflowStorage,
|
AutomationStorage $automationStorage,
|
||||||
PageRenderer $pageRenderer,
|
PageRenderer $pageRenderer,
|
||||||
Registry $registry,
|
Registry $registry,
|
||||||
SegmentsRepository $segmentsRepository,
|
SegmentsRepository $segmentsRepository,
|
||||||
WPFunctions $wp
|
WPFunctions $wp
|
||||||
) {
|
) {
|
||||||
$this->assetsController = $assetsController;
|
$this->assetsController = $assetsController;
|
||||||
$this->workflowMapper = $workflowMapper;
|
$this->automationMapper = $automationMapper;
|
||||||
$this->workflowStorage = $workflowStorage;
|
$this->automationStorage = $automationStorage;
|
||||||
$this->pageRenderer = $pageRenderer;
|
$this->pageRenderer = $pageRenderer;
|
||||||
$this->registry = $registry;
|
$this->registry = $registry;
|
||||||
$this->segmentsRepository = $segmentsRepository;
|
$this->segmentsRepository = $segmentsRepository;
|
||||||
@ -60,8 +60,8 @@ class AutomationEditor {
|
|||||||
|
|
||||||
$this->wp->doAction(Hooks::EDITOR_BEFORE_LOAD, (int)$id);
|
$this->wp->doAction(Hooks::EDITOR_BEFORE_LOAD, (int)$id);
|
||||||
|
|
||||||
$workflow = $id ? $this->workflowStorage->getWorkflow($id) : null;
|
$automation = $id ? $this->automationStorage->getAutomation($id) : null;
|
||||||
if (!$workflow) {
|
if (!$automation) {
|
||||||
$notice = new WPNotice(
|
$notice = new WPNotice(
|
||||||
WPNotice::TYPE_ERROR,
|
WPNotice::TYPE_ERROR,
|
||||||
__('Automation not found.', 'mailpoet')
|
__('Automation not found.', 'mailpoet')
|
||||||
@ -71,7 +71,7 @@ class AutomationEditor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($workflow->getStatus() === Workflow::STATUS_TRASH) {
|
if ($automation->getStatus() === Automation::STATUS_TRASH) {
|
||||||
$this->wp->wpSafeRedirect($this->wp->adminUrl('admin.php?page=mailpoet-automation&status=trash'));
|
$this->wp->wpSafeRedirect($this->wp->adminUrl('admin.php?page=mailpoet-automation&status=trash'));
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ class AutomationEditor {
|
|||||||
$roles = new \WP_Roles();
|
$roles = new \WP_Roles();
|
||||||
$this->pageRenderer->displayPage('automation/editor.html', [
|
$this->pageRenderer->displayPage('automation/editor.html', [
|
||||||
'context' => $this->buildContext(),
|
'context' => $this->buildContext(),
|
||||||
'workflow' => $this->workflowMapper->buildWorkflow($workflow),
|
'automation' => $this->automationMapper->buildAutomation($automation),
|
||||||
'sub_menu' => 'mailpoet-automation',
|
'sub_menu' => 'mailpoet-automation',
|
||||||
'api' => [
|
'api' => [
|
||||||
'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'),
|
'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'),
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
namespace MailPoet\AdminPages\Pages;
|
namespace MailPoet\AdminPages\Pages;
|
||||||
|
|
||||||
use MailPoet\AdminPages\PageRenderer;
|
use MailPoet\AdminPages\PageRenderer;
|
||||||
use MailPoet\Automation\Engine\Data\WorkflowTemplate;
|
use MailPoet\Automation\Engine\Data\AutomationTemplate;
|
||||||
use MailPoet\Automation\Engine\Storage\WorkflowTemplateStorage;
|
use MailPoet\Automation\Engine\Storage\AutomationTemplateStorage;
|
||||||
use MailPoet\Form\AssetsController;
|
use MailPoet\Form\AssetsController;
|
||||||
use MailPoet\WP\Functions as WPFunctions;
|
use MailPoet\WP\Functions as WPFunctions;
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ class AutomationTemplates {
|
|||||||
/** @var PageRenderer */
|
/** @var PageRenderer */
|
||||||
private $pageRenderer;
|
private $pageRenderer;
|
||||||
|
|
||||||
/** @var WorkflowTemplateStorage */
|
/** @var AutomationTemplateStorage */
|
||||||
private $templateStorage;
|
private $templateStorage;
|
||||||
|
|
||||||
/** @var WPFunctions */
|
/** @var WPFunctions */
|
||||||
@ -24,7 +24,7 @@ class AutomationTemplates {
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
AssetsController $assetsController,
|
AssetsController $assetsController,
|
||||||
PageRenderer $pageRenderer,
|
PageRenderer $pageRenderer,
|
||||||
WorkflowTemplateStorage $templateStorage,
|
AutomationTemplateStorage $templateStorage,
|
||||||
WPFunctions $wp
|
WPFunctions $wp
|
||||||
) {
|
) {
|
||||||
$this->assetsController = $assetsController;
|
$this->assetsController = $assetsController;
|
||||||
@ -45,7 +45,7 @@ class AutomationTemplates {
|
|||||||
'nonce' => $this->wp->wpCreateNonce('wp_rest'),
|
'nonce' => $this->wp->wpCreateNonce('wp_rest'),
|
||||||
],
|
],
|
||||||
'templates' => array_map(
|
'templates' => array_map(
|
||||||
function(WorkflowTemplate $template): array {
|
function(AutomationTemplate $template): array {
|
||||||
return $template->toArray();
|
return $template->toArray();
|
||||||
},
|
},
|
||||||
$this->templateStorage->getTemplates()
|
$this->templateStorage->getTemplates()
|
||||||
|
@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
namespace MailPoet\Analytics;
|
namespace MailPoet\Analytics;
|
||||||
|
|
||||||
|
use MailPoet\Automation\Engine\Data\Automation;
|
||||||
use MailPoet\Automation\Engine\Data\Step;
|
use MailPoet\Automation\Engine\Data\Step;
|
||||||
use MailPoet\Automation\Engine\Data\Workflow;
|
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||||
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
|
|
||||||
use MailPoet\Config\ServicesChecker;
|
use MailPoet\Config\ServicesChecker;
|
||||||
use MailPoet\Cron\CronTrigger;
|
use MailPoet\Cron\CronTrigger;
|
||||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||||
use MailPoet\Features\FeaturesController;
|
|
||||||
use MailPoet\Listing\ListingDefinition;
|
use MailPoet\Listing\ListingDefinition;
|
||||||
use MailPoet\Newsletter\NewslettersRepository;
|
use MailPoet\Newsletter\NewslettersRepository;
|
||||||
use MailPoet\Segments\DynamicSegments\DynamicSegmentFilterRepository;
|
use MailPoet\Segments\DynamicSegments\DynamicSegmentFilterRepository;
|
||||||
@ -74,11 +73,8 @@ class Reporter {
|
|||||||
/** @var SubscriberListingRepository */
|
/** @var SubscriberListingRepository */
|
||||||
private $subscriberListingRepository;
|
private $subscriberListingRepository;
|
||||||
|
|
||||||
/** @var FeaturesController */
|
/** @var AutomationStorage */
|
||||||
private $featuresController;
|
private $automationStorage;
|
||||||
|
|
||||||
/** @var WorkflowStorage */
|
|
||||||
private $workflowStorage;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
NewslettersRepository $newslettersRepository,
|
NewslettersRepository $newslettersRepository,
|
||||||
@ -92,8 +88,7 @@ class Reporter {
|
|||||||
SubscribersFeature $subscribersFeature,
|
SubscribersFeature $subscribersFeature,
|
||||||
TrackingConfig $trackingConfig,
|
TrackingConfig $trackingConfig,
|
||||||
SubscriberListingRepository $subscriberListingRepository,
|
SubscriberListingRepository $subscriberListingRepository,
|
||||||
FeaturesController $featuresController,
|
AutomationStorage $automationStorage
|
||||||
WorkflowStorage $workflowStorage
|
|
||||||
) {
|
) {
|
||||||
$this->newslettersRepository = $newslettersRepository;
|
$this->newslettersRepository = $newslettersRepository;
|
||||||
$this->segmentsRepository = $segmentsRepository;
|
$this->segmentsRepository = $segmentsRepository;
|
||||||
@ -106,8 +101,7 @@ class Reporter {
|
|||||||
$this->subscribersFeature = $subscribersFeature;
|
$this->subscribersFeature = $subscribersFeature;
|
||||||
$this->trackingConfig = $trackingConfig;
|
$this->trackingConfig = $trackingConfig;
|
||||||
$this->subscriberListingRepository = $subscriberListingRepository;
|
$this->subscriberListingRepository = $subscriberListingRepository;
|
||||||
$this->featuresController = $featuresController;
|
$this->automationStorage = $automationStorage;
|
||||||
$this->workflowStorage = $workflowStorage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getData() {
|
public function getData() {
|
||||||
@ -231,43 +225,39 @@ class Reporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function automationProperties(): array {
|
private function automationProperties(): array {
|
||||||
if (!$this->featuresController->isSupported(FeaturesController::AUTOMATION)) {
|
$automations = $this->automationStorage->getAutomations();
|
||||||
return [];
|
$activeAutomations = array_filter(
|
||||||
}
|
$automations,
|
||||||
|
function(Automation $automation): bool {
|
||||||
$workflows = $this->workflowStorage->getWorkflows();
|
return $automation->getStatus() === Automation::STATUS_ACTIVE;
|
||||||
$activeWorkflows = array_filter(
|
|
||||||
$workflows,
|
|
||||||
function(Workflow $workflow): bool {
|
|
||||||
return $workflow->getStatus() === Workflow::STATUS_ACTIVE;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
$activeWorkflowCount = count($activeWorkflows);
|
$activeAutomationCount = count($activeAutomations);
|
||||||
$draftWorkflows = array_filter(
|
$draftAutomations = array_filter(
|
||||||
$workflows,
|
$automations,
|
||||||
function(Workflow $workflow): bool {
|
function(Automation $automation): bool {
|
||||||
return $workflow->getStatus() === Workflow::STATUS_DRAFT;
|
return $automation->getStatus() === Automation::STATUS_DRAFT;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
$workflowsWithWordPressUserSubscribesTrigger = array_filter(
|
$automationsWithWordPressUserSubscribesTrigger = array_filter(
|
||||||
$activeWorkflows,
|
$activeAutomations,
|
||||||
function(Workflow $workflow): bool {
|
function(Automation $automation): bool {
|
||||||
return $workflow->getTrigger('mailpoet:wp-user-registered') !== null;
|
return $automation->getTrigger('mailpoet:wp-user-registered') !== null;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
$workflowsWithSomeoneSubscribesTrigger = array_filter(
|
$automationsWithSomeoneSubscribesTrigger = array_filter(
|
||||||
$activeWorkflows,
|
$activeAutomations,
|
||||||
function(Workflow $workflow): bool {
|
function(Automation $automation): bool {
|
||||||
return $workflow->getTrigger('mailpoet:someone-subscribes') !== null;
|
return $automation->getTrigger('mailpoet:someone-subscribes') !== null;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
$totalSteps = 0;
|
$totalSteps = 0;
|
||||||
$minSteps = null;
|
$minSteps = null;
|
||||||
$maxSteps = 0;
|
$maxSteps = 0;
|
||||||
foreach ($activeWorkflows as $workflow) {
|
foreach ($activeAutomations as $automation) {
|
||||||
$steps = array_filter(
|
$steps = array_filter(
|
||||||
$workflow->getSteps(),
|
$automation->getSteps(),
|
||||||
function(Step $step): bool {
|
function(Step $step): bool {
|
||||||
return $step->getType() === Step::TYPE_ACTION;
|
return $step->getType() === Step::TYPE_ACTION;
|
||||||
}
|
}
|
||||||
@ -277,16 +267,16 @@ class Reporter {
|
|||||||
$maxSteps = max($maxSteps, $stepCount);
|
$maxSteps = max($maxSteps, $stepCount);
|
||||||
$totalSteps += $stepCount;
|
$totalSteps += $stepCount;
|
||||||
}
|
}
|
||||||
$averageSteps = $activeWorkflowCount > 0 ? $totalSteps / $activeWorkflowCount : 0;
|
$averageSteps = $activeAutomationCount > 0 ? $totalSteps / $activeAutomationCount : 0;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'Automation > Number of active workflows' => $activeWorkflowCount,
|
'Automation > Number of active automations' => $activeAutomationCount,
|
||||||
'Automation > Number of draft workflows' => count($draftWorkflows),
|
'Automation > Number of draft automations' => count($draftAutomations),
|
||||||
'Automation > Number of "WordPress user registers" active workflows' => count($workflowsWithWordPressUserSubscribesTrigger),
|
'Automation > Number of "WordPress user registers" active automations' => count($automationsWithWordPressUserSubscribesTrigger),
|
||||||
'Automation > Number of "Someone subscribes" active workflows ' => count($workflowsWithSomeoneSubscribesTrigger),
|
'Automation > Number of "Someone subscribes" active automations ' => count($automationsWithSomeoneSubscribesTrigger),
|
||||||
'Automation > Number of steps in shortest active workflow' => $minSteps,
|
'Automation > Number of steps in shortest active automation' => $minSteps,
|
||||||
'Automation > Number of steps in longest active workflow' => $maxSteps,
|
'Automation > Number of steps in longest active automation' => $maxSteps,
|
||||||
'Automation > Average number of steps in active workflows' => $averageSteps,
|
'Automation > Average number of steps in active automations' => $averageSteps,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,9 +6,7 @@ use MailPoet\API\REST\API as MailPoetApi;
|
|||||||
use MailPoet\Automation\Engine\Hooks;
|
use MailPoet\Automation\Engine\Hooks;
|
||||||
use MailPoet\Automation\Engine\WordPress;
|
use MailPoet\Automation\Engine\WordPress;
|
||||||
|
|
||||||
class API {
|
class API extends MailPoetApi {
|
||||||
private const PREFIX = 'automation/';
|
|
||||||
|
|
||||||
/** @var MailPoetApi */
|
/** @var MailPoetApi */
|
||||||
private $api;
|
private $api;
|
||||||
|
|
||||||
@ -30,22 +28,22 @@ class API {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function registerGetRoute(string $route, string $endpoint): void {
|
public function registerGetRoute(string $route, string $endpoint): void {
|
||||||
$this->api->registerGetRoute(self::PREFIX . $route, $endpoint);
|
$this->api->registerGetRoute($route, $endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerPostRoute(string $route, string $endpoint): void {
|
public function registerPostRoute(string $route, string $endpoint): void {
|
||||||
$this->api->registerPostRoute(self::PREFIX . $route, $endpoint);
|
$this->api->registerPostRoute($route, $endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerPutRoute(string $route, string $endpoint): void {
|
public function registerPutRoute(string $route, string $endpoint): void {
|
||||||
$this->api->registerPutRoute(self::PREFIX . $route, $endpoint);
|
$this->api->registerPutRoute($route, $endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerPatchRoute(string $route, string $endpoint): void {
|
public function registerPatchRoute(string $route, string $endpoint): void {
|
||||||
$this->api->registerPatchRoute(self::PREFIX . $route, $endpoint);
|
$this->api->registerPatchRoute($route, $endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerDeleteRoute(string $route, string $endpoint): void {
|
public function registerDeleteRoute(string $route, string $endpoint): void {
|
||||||
$this->api->registerDeleteRoute(self::PREFIX . $route, $endpoint);
|
$this->api->registerDeleteRoute($route, $endpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
<?php declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace MailPoet\Automation\Engine\Builder;
|
||||||
|
|
||||||
|
use MailPoet\Automation\Engine\Data\Automation;
|
||||||
|
use MailPoet\Automation\Engine\Exceptions;
|
||||||
|
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
|
||||||
|
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||||
|
use MailPoet\Automation\Engine\Storage\AutomationTemplateStorage;
|
||||||
|
use MailPoet\Automation\Engine\Validation\AutomationValidator;
|
||||||
|
|
||||||
|
class CreateAutomationFromTemplateController {
|
||||||
|
/** @var AutomationStorage */
|
||||||
|
private $storage;
|
||||||
|
|
||||||
|
/** @var AutomationTemplateStorage */
|
||||||
|
private $templateStorage;
|
||||||
|
|
||||||
|
/** @var AutomationValidator */
|
||||||
|
private $automationValidator;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
AutomationStorage $storage,
|
||||||
|
AutomationTemplateStorage $templateStorage,
|
||||||
|
AutomationValidator $automationValidator
|
||||||
|
) {
|
||||||
|
$this->storage = $storage;
|
||||||
|
$this->templateStorage = $templateStorage;
|
||||||
|
$this->automationValidator = $automationValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createAutomation(string $slug): Automation {
|
||||||
|
$template = $this->templateStorage->getTemplateBySlug($slug);
|
||||||
|
if (!$template) {
|
||||||
|
throw Exceptions::automationTemplateNotFound($slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
$automation = $template->getAutomation();
|
||||||
|
$this->automationValidator->validate($automation);
|
||||||
|
$automationId = $this->storage->createAutomation($automation);
|
||||||
|
$savedAutomation = $this->storage->getAutomation($automationId);
|
||||||
|
if (!$savedAutomation) {
|
||||||
|
throw new InvalidStateException('Automation not found.');
|
||||||
|
}
|
||||||
|
return $savedAutomation;
|
||||||
|
}
|
||||||
|
}
|
@ -1,47 +0,0 @@
|
|||||||
<?php declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace MailPoet\Automation\Engine\Builder;
|
|
||||||
|
|
||||||
use MailPoet\Automation\Engine\Data\Workflow;
|
|
||||||
use MailPoet\Automation\Engine\Exceptions;
|
|
||||||
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
|
|
||||||
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
|
|
||||||
use MailPoet\Automation\Engine\Storage\WorkflowTemplateStorage;
|
|
||||||
use MailPoet\Automation\Engine\Validation\WorkflowValidator;
|
|
||||||
|
|
||||||
class CreateWorkflowFromTemplateController {
|
|
||||||
/** @var WorkflowStorage */
|
|
||||||
private $storage;
|
|
||||||
|
|
||||||
/** @var WorkflowTemplateStorage */
|
|
||||||
private $templateStorage;
|
|
||||||
|
|
||||||
/** @var WorkflowValidator */
|
|
||||||
private $workflowValidator;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
WorkflowStorage $storage,
|
|
||||||
WorkflowTemplateStorage $templateStorage,
|
|
||||||
WorkflowValidator $workflowValidator
|
|
||||||
) {
|
|
||||||
$this->storage = $storage;
|
|
||||||
$this->templateStorage = $templateStorage;
|
|
||||||
$this->workflowValidator = $workflowValidator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createWorkflow(string $slug): Workflow {
|
|
||||||
$template = $this->templateStorage->getTemplateBySlug($slug);
|
|
||||||
if (!$template) {
|
|
||||||
throw Exceptions::workflowTemplateNotFound($slug);
|
|
||||||
}
|
|
||||||
|
|
||||||
$workflow = $template->getWorkflow();
|
|
||||||
$this->workflowValidator->validate($workflow);
|
|
||||||
$workflowId = $this->storage->createWorkflow($workflow);
|
|
||||||
$savedWorkflow = $this->storage->getWorkflow($workflowId);
|
|
||||||
if (!$savedWorkflow) {
|
|
||||||
throw new InvalidStateException('Automation not found.');
|
|
||||||
}
|
|
||||||
return $savedWorkflow;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,32 @@
|
|||||||
|
<?php declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace MailPoet\Automation\Engine\Builder;
|
||||||
|
|
||||||
|
use MailPoet\Automation\Engine\Data\Automation;
|
||||||
|
use MailPoet\Automation\Engine\Exceptions;
|
||||||
|
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||||
|
|
||||||
|
class DeleteAutomationController {
|
||||||
|
/** @var AutomationStorage */
|
||||||
|
private $automationStorage;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
AutomationStorage $automationStorage
|
||||||
|
) {
|
||||||
|
$this->automationStorage = $automationStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteAutomation(int $id): Automation {
|
||||||
|
$automation = $this->automationStorage->getAutomation($id);
|
||||||
|
if (!$automation) {
|
||||||
|
throw Exceptions::automationNotFound($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($automation->getStatus() !== Automation::STATUS_TRASH) {
|
||||||
|
throw Exceptions::automationNotTrashed($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->automationStorage->deleteAutomation($automation);
|
||||||
|
return $automation;
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +0,0 @@
|
|||||||
<?php declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace MailPoet\Automation\Engine\Builder;
|
|
||||||
|
|
||||||
use MailPoet\Automation\Engine\Data\Workflow;
|
|
||||||
use MailPoet\Automation\Engine\Exceptions;
|
|
||||||
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
|
|
||||||
|
|
||||||
class DeleteWorkflowController {
|
|
||||||
/** @var WorkflowStorage */
|
|
||||||
private $workflowStorage;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
WorkflowStorage $workflowStorage
|
|
||||||
) {
|
|
||||||
$this->workflowStorage = $workflowStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteWorkflow(int $id): Workflow {
|
|
||||||
$workflow = $this->workflowStorage->getWorkflow($id);
|
|
||||||
if (!$workflow) {
|
|
||||||
throw Exceptions::workflowNotFound($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($workflow->getStatus() !== Workflow::STATUS_TRASH) {
|
|
||||||
throw Exceptions::workflowNotTrashed($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->workflowStorage->deleteWorkflow($workflow);
|
|
||||||
return $workflow;
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,55 +2,55 @@
|
|||||||
|
|
||||||
namespace MailPoet\Automation\Engine\Builder;
|
namespace MailPoet\Automation\Engine\Builder;
|
||||||
|
|
||||||
|
use MailPoet\Automation\Engine\Data\Automation;
|
||||||
use MailPoet\Automation\Engine\Data\NextStep;
|
use MailPoet\Automation\Engine\Data\NextStep;
|
||||||
use MailPoet\Automation\Engine\Data\Step;
|
use MailPoet\Automation\Engine\Data\Step;
|
||||||
use MailPoet\Automation\Engine\Data\Workflow;
|
|
||||||
use MailPoet\Automation\Engine\Exceptions;
|
use MailPoet\Automation\Engine\Exceptions;
|
||||||
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
|
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
|
||||||
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
|
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||||
use MailPoet\Automation\Engine\WordPress;
|
use MailPoet\Automation\Engine\WordPress;
|
||||||
use MailPoet\Util\Security;
|
use MailPoet\Util\Security;
|
||||||
|
|
||||||
class DuplicateWorkflowController {
|
class DuplicateAutomationController {
|
||||||
/** @var WordPress */
|
/** @var WordPress */
|
||||||
private $wordPress;
|
private $wordPress;
|
||||||
|
|
||||||
/** @var WorkflowStorage */
|
/** @var AutomationStorage */
|
||||||
private $workflowStorage;
|
private $automationStorage;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
WordPress $wordPress,
|
WordPress $wordPress,
|
||||||
WorkflowStorage $workflowStorage
|
AutomationStorage $automationStorage
|
||||||
) {
|
) {
|
||||||
$this->wordPress = $wordPress;
|
$this->wordPress = $wordPress;
|
||||||
$this->workflowStorage = $workflowStorage;
|
$this->automationStorage = $automationStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function duplicateWorkflow(int $id): Workflow {
|
public function duplicateAutomation(int $id): Automation {
|
||||||
$workflow = $this->workflowStorage->getWorkflow($id);
|
$automation = $this->automationStorage->getAutomation($id);
|
||||||
if (!$workflow) {
|
if (!$automation) {
|
||||||
throw Exceptions::workflowNotFound($id);
|
throw Exceptions::automationNotFound($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
$duplicate = new Workflow(
|
$duplicate = new Automation(
|
||||||
$this->getName($workflow->getName()),
|
$this->getName($automation->getName()),
|
||||||
$this->getSteps($workflow->getSteps()),
|
$this->getSteps($automation->getSteps()),
|
||||||
$this->wordPress->wpGetCurrentUser()
|
$this->wordPress->wpGetCurrentUser()
|
||||||
);
|
);
|
||||||
$duplicate->setStatus(Workflow::STATUS_DRAFT);
|
$duplicate->setStatus(Automation::STATUS_DRAFT);
|
||||||
|
|
||||||
$workflowId = $this->workflowStorage->createWorkflow($duplicate);
|
$automationId = $this->automationStorage->createAutomation($duplicate);
|
||||||
$savedWorkflow = $this->workflowStorage->getWorkflow($workflowId);
|
$savedAutomation = $this->automationStorage->getAutomation($automationId);
|
||||||
if (!$savedWorkflow) {
|
if (!$savedAutomation) {
|
||||||
throw new InvalidStateException('Workflow not found.');
|
throw new InvalidStateException('Automation not found.');
|
||||||
}
|
}
|
||||||
return $savedWorkflow;
|
return $savedAutomation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getName(string $name): string {
|
private function getName(string $name): string {
|
||||||
// translators: %s is the original automation name.
|
// translators: %s is the original automation name.
|
||||||
$newName = sprintf(__('Copy of %s', 'mailpoet'), $name);
|
$newName = sprintf(__('Copy of %s', 'mailpoet'), $name);
|
||||||
$maxLength = $this->workflowStorage->getNameColumnLength();
|
$maxLength = $this->automationStorage->getNameColumnLength();
|
||||||
if (strlen($newName) > $maxLength) {
|
if (strlen($newName) > $maxLength) {
|
||||||
$append = '…';
|
$append = '…';
|
||||||
return substr($newName, 0, $maxLength - strlen($append)) . $append;
|
return substr($newName, 0, $maxLength - strlen($append)) . $append;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user