Compare commits
455 Commits
Author | SHA1 | Date | |
---|---|---|---|
03b1a6e9ce | |||
69440ec2a6 | |||
0bbdf4b47f | |||
f11aca925f | |||
e4029a607a | |||
eb53973799 | |||
aab8281af0 | |||
3df00548ab | |||
ada346f4ef | |||
b3691a4625 | |||
e02b631172 | |||
2200411455 | |||
b0ab2f404f | |||
4195428643 | |||
1692c9bef5 | |||
cb25cf2944 | |||
794c5ce2d5 | |||
974d2d5a59 | |||
2ebcad1468 | |||
671c7eed7d | |||
72de76fe9b | |||
53cc357632 | |||
47d3472fef | |||
05c5b46089 | |||
f85bd8622f | |||
503cf61c0f | |||
d9eca55189 | |||
e850eaa90c | |||
478eab61e4 | |||
ea53728799 | |||
914d0d37c0 | |||
c1aae2c192 | |||
03cdcb30f8 | |||
a462e72b30 | |||
83db3db90d | |||
9495a40900 | |||
dbc2c9c240 | |||
d6bd1a5527 | |||
c5cd863f15 | |||
8ef1bfaf24 | |||
abac8d7147 | |||
367bcaf233 | |||
fdbb9428d9 | |||
bd047a0108 | |||
05202d572f | |||
a698e416ad | |||
1efc417f82 | |||
4dc31bbdb0 | |||
12dc727fda | |||
6602103d0f | |||
f3e8fb8ae2 | |||
a205d4d7f4 | |||
1783ca6e42 | |||
6555833fb6 | |||
ce3b9b2b7b | |||
1a653c5676 | |||
85a8d8aedb | |||
5c453fcd54 | |||
ec9adf8a6c | |||
b5064ca46f | |||
84b97ead76 | |||
4e578238f7 | |||
bb6d6137aa | |||
74c7728e3a | |||
e13113e38b | |||
68db0dbefe | |||
e015302a94 | |||
20fcf23f8f | |||
11beebf74d | |||
5a53406d33 | |||
ee83e4d748 | |||
970db8f0d8 | |||
71828f9f6e | |||
0525dd0f56 | |||
e3ba525195 | |||
1eed55cbce | |||
fd1331e602 | |||
02b82c04f3 | |||
ad5ef6ebbc | |||
3c5bc5b384 | |||
f229df0383 | |||
9092f892d0 | |||
9c3fb0856f | |||
2a8c665e12 | |||
9b1942ce48 | |||
932b4532f0 | |||
6533a1444d | |||
05a109dcf4 | |||
5b5b7856c6 | |||
4ceb508218 | |||
3afc7dc140 | |||
1c1058667b | |||
129aed2ae9 | |||
f8c7adf5a6 | |||
6346d39507 | |||
1b76f3b862 | |||
40c140a631 | |||
7fd11d4fb5 | |||
7fa694314b | |||
080e385b02 | |||
7c0b625793 | |||
f0b858b1f8 | |||
b218663e7e | |||
d8ea29423d | |||
ff038a1c56 | |||
588e8fca6b | |||
8d9f23d5f8 | |||
ea4637c740 | |||
c0e56a4f92 | |||
53a4722e91 | |||
d175870adf | |||
a64229f688 | |||
cd69e1e7b9 | |||
321db0b004 | |||
aaf01475b0 | |||
1cd38a396e | |||
1f1c9dd077 | |||
0da6d84309 | |||
3a05598166 | |||
5035d64298 | |||
56f6d20244 | |||
650d730c3f | |||
decaa0f636 | |||
c2b9e33b79 | |||
34347f241d | |||
5802f2132d | |||
8e502e5f50 | |||
0187f0cede | |||
8457aa7e2b | |||
67156b7c6f | |||
f9250b66a9 | |||
1fcdccaa33 | |||
5b93b88f46 | |||
cbd41cd1be | |||
95a8943b7e | |||
dd363370f6 | |||
1027a6a676 | |||
0fd93b0ff9 | |||
bfade87b62 | |||
26d9030544 | |||
5688a0daea | |||
92996ac781 | |||
1f471782ab | |||
320459eaab | |||
e65b2cce02 | |||
520ee981d3 | |||
f100ef72da | |||
146e1c871f | |||
669367c9c6 | |||
77d62c431a | |||
4a5fc507ca | |||
06f59dd320 | |||
4367f44449 | |||
856e0f69d3 | |||
c4bd2acd84 | |||
210c240aab | |||
d19f5dc732 | |||
6541c20466 | |||
3952484848 | |||
7d918604c3 | |||
ffaf5b88b6 | |||
eca4a9e923 | |||
1cee4e67e8 | |||
b1c35b60d8 | |||
1e5cfb4eaf | |||
5e5956420f | |||
78446d174a | |||
917d64a3e3 | |||
a3d51b2b08 | |||
3ffbd6e236 | |||
9753331d52 | |||
b266edb8e7 | |||
f1899055b8 | |||
c3f0a36665 | |||
88efb1f3d1 | |||
146f5881f7 | |||
3279fb1154 | |||
5f5efbe876 | |||
12afcfb656 | |||
0aac9e9d60 | |||
4e3b1527cd | |||
b27dcdd40a | |||
7b584dd482 | |||
5e71d94b62 | |||
8bb4338627 | |||
4bcd4a2cf2 | |||
ea7971cb3b | |||
9036b210a0 | |||
f337ac388b | |||
b0ae21ec60 | |||
7d11eeeddd | |||
4577ca5476 | |||
846794b60d | |||
5757af95aa | |||
b8c3b38652 | |||
3d21e4f35e | |||
73ce89d051 | |||
49d9dd666b | |||
53dda33c02 | |||
9b9f61bf13 | |||
651e2d3c56 | |||
0bd627d3b1 | |||
99d0eede80 | |||
d09b4ca409 | |||
caa76983e4 | |||
7b6bbb0bc3 | |||
cdf7c81a94 | |||
86812c5259 | |||
657302b720 | |||
172b5215d2 | |||
52a9d9f76c | |||
8eb7a48d3e | |||
a8bceffc9c | |||
2c728c793a | |||
8d974d8147 | |||
8fbc5c270a | |||
92dc5921df | |||
3c3ce37720 | |||
f369e399ed | |||
01aa1e1e52 | |||
2bbc1b7063 | |||
04b8a0ed73 | |||
761f7c6537 | |||
723dc2c9d3 | |||
d66fbb1c20 | |||
c2107b8d59 | |||
926620e8f8 | |||
82aeb89854 | |||
c67c58709b | |||
75b5958a53 | |||
bba5101669 | |||
b9c5dddbaf | |||
0566b3f5fa | |||
ac4adac1ab | |||
33a9097719 | |||
08c74cdf8b | |||
8c5222d850 | |||
08c76a46af | |||
3d24cb1deb | |||
a151f93be1 | |||
bce6a06c15 | |||
10a6e387f3 | |||
b6a37f274c | |||
2cdbc68643 | |||
7db40b27b5 | |||
b1760ff676 | |||
293eef2c78 | |||
299c6b779e | |||
842f435976 | |||
11faf925cc | |||
1295aa21cc | |||
84cd137c76 | |||
37067dff67 | |||
c7f850e1ba | |||
b25f2cd5f4 | |||
af95080b67 | |||
6b8c35d5fe | |||
a35e0dced4 | |||
c6259eb185 | |||
670017b342 | |||
ce7b210da9 | |||
1a2d8b8d40 | |||
fe2df00a8c | |||
20577d974f | |||
7369465a3a | |||
5769fa45f5 | |||
193b4cbf98 | |||
461df1a560 | |||
d5f78680eb | |||
96dde7107b | |||
3e756bfadd | |||
82c755200e | |||
735d33d05b | |||
90c8e5b2c9 | |||
cd1f06b116 | |||
173f347431 | |||
353d3389cd | |||
fe364978a1 | |||
cd512bbb01 | |||
f5193df721 | |||
25b5d24867 | |||
1d99ff0943 | |||
99d6d96b05 | |||
6ddfe2c605 | |||
d2ec7bba42 | |||
2557171953 | |||
0d7e45ea03 | |||
e677169a51 | |||
e268eac378 | |||
375f2b13c5 | |||
34406a2f72 | |||
cc658bcc3d | |||
973aefe7ae | |||
34e91251e8 | |||
882402645d | |||
d9d4dccc09 | |||
9d2fcccbf5 | |||
c4fdd881e6 | |||
83f7eee8ae | |||
dc1f63d8bb | |||
3e2425abd8 | |||
65e78c7990 | |||
2bdc5d3683 | |||
1de276378f | |||
cc76fe77f0 | |||
c8636aac6f | |||
a09f41143e | |||
8b29694446 | |||
5e0e89408c | |||
86076547c3 | |||
b1bbabe14d | |||
f5c678ec2d | |||
223625bd9b | |||
fbd7cf8cc7 | |||
26c7e4d1cc | |||
4832771185 | |||
7fb8d64628 | |||
36fed3bbf8 | |||
8ab738b315 | |||
b84b9606e9 | |||
aa68b7e148 | |||
ca44339512 | |||
91f5376dfc | |||
d51ef70db6 | |||
c164268c20 | |||
4c7c94c75d | |||
ccf141b279 | |||
e42808f22d | |||
75a4b2c538 | |||
f1d64c0572 | |||
25c57bf9fe | |||
079c90dbba | |||
0fc342ee4c | |||
7570c9fa0e | |||
7642c4da4f | |||
dce13e5b8e | |||
8b922681b6 | |||
bd33cdbc24 | |||
bc04ad2528 | |||
c54a5213b3 | |||
07cf0cd79b | |||
e64dc1669f | |||
48181994d8 | |||
31c2915075 | |||
67f05ecfe2 | |||
c121c60e8c | |||
110859508e | |||
efd3377ecd | |||
a7c1d8431d | |||
127f3e6363 | |||
6bec12c762 | |||
d1df2d4f42 | |||
5598f0af19 | |||
8dd80ce0f2 | |||
e45510e064 | |||
bfafff132a | |||
d6748fe979 | |||
2169522818 | |||
06cfa3fc60 | |||
f9e507eb15 | |||
90332a23b3 | |||
a82a810269 | |||
5a378b3c5f | |||
49a2df3dbb | |||
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:
|
||||
name: Download additional WP Plugins for tests
|
||||
command: |
|
||||
./do download:woo-commerce-zip 6.8.2
|
||||
./do download:woo-commerce-subscriptions-zip 4.5.1
|
||||
./do download:woo-commerce-memberships-zip 1.23.0
|
||||
./do download:woo-commerce-blocks-zip 8.4.0
|
||||
./do download:woo-commerce-zip 7.1.0
|
||||
./do download:woo-commerce-subscriptions-zip 4.6.0
|
||||
./do download:woo-commerce-memberships-zip 1.23.1
|
||||
./do download:woo-commerce-blocks-zip 8.8.2
|
||||
- run:
|
||||
name: Dump tests ENV variables for acceptance tests
|
||||
command: |
|
||||
@ -311,7 +311,8 @@ jobs:
|
||||
parallelism: 20
|
||||
working_directory: /home/circleci/mailpoet/mailpoet
|
||||
machine:
|
||||
image: ubuntu-2204:2022.07.1
|
||||
image: ubuntu-2204:2022.10.2
|
||||
docker_layer_caching: false
|
||||
parameters:
|
||||
multisite:
|
||||
type: integer
|
||||
@ -322,7 +323,7 @@ jobs:
|
||||
mysql_command:
|
||||
type: string
|
||||
default: ''
|
||||
mysql_image_version:
|
||||
mysql_image:
|
||||
type: string
|
||||
default: ''
|
||||
codeception_image_version:
|
||||
@ -351,7 +352,7 @@ jobs:
|
||||
default: 0
|
||||
environment:
|
||||
MYSQL_COMMAND: << parameters.mysql_command >>
|
||||
MYSQL_IMAGE_VERSION: << parameters.mysql_image_version >>
|
||||
MYSQL_IMAGE: << parameters.mysql_image >>
|
||||
CODECEPTION_IMAGE_VERSION: << parameters.codeception_image_version >>
|
||||
WORDPRESS_IMAGE_VERSION: << parameters.wordpress_image_version >>
|
||||
steps:
|
||||
@ -411,6 +412,12 @@ jobs:
|
||||
circleci tests glob "tests/acceptance/**/*Cest.php" | circleci tests split --split-by=timings > tests/acceptance/_groups/circleci_split_group
|
||||
fi
|
||||
cat tests/acceptance/_groups/circleci_split_group
|
||||
- run:
|
||||
name: Create docker containers for test
|
||||
# We experienced some failures when creating containers so we do it explicitly with one retry
|
||||
command: |
|
||||
cd tests/docker
|
||||
docker-compose create || docker-compose create
|
||||
- run:
|
||||
name: Run acceptance tests
|
||||
command: |
|
||||
@ -476,9 +483,12 @@ jobs:
|
||||
integration_tests:
|
||||
working_directory: /home/circleci/mailpoet/mailpoet
|
||||
machine:
|
||||
image: ubuntu-2204:2022.07.1
|
||||
image: ubuntu-2204:2022.10.2
|
||||
docker_layer_caching: false
|
||||
environment:
|
||||
CODECEPTION_IMAGE_VERSION: << parameters.codeception_image_version >>
|
||||
MYSQL_COMMAND: << parameters.mysql_command >>
|
||||
MYSQL_IMAGE: << parameters.mysql_image >>
|
||||
parameters:
|
||||
codeception_image_version:
|
||||
type: string
|
||||
@ -501,7 +511,10 @@ jobs:
|
||||
multisite:
|
||||
type: integer
|
||||
default: 0
|
||||
woo_core_version:
|
||||
mysql_command:
|
||||
type: string
|
||||
default: ''
|
||||
mysql_image:
|
||||
type: string
|
||||
default: ''
|
||||
steps:
|
||||
@ -511,14 +524,6 @@ jobs:
|
||||
name: 'Pull test docker images'
|
||||
# Pull docker images with 3 retries
|
||||
command: i='0';while ! docker-compose -f tests/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
|
||||
- when:
|
||||
condition: << parameters.woo_core_version >>
|
||||
steps:
|
||||
- run:
|
||||
name: Download WooCommerce Core
|
||||
command: |
|
||||
cd tests/docker
|
||||
docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-zip << parameters.woo_core_version >>" --no-deps codeception_integration
|
||||
- run:
|
||||
name: 'PHP Integration tests'
|
||||
command: |
|
||||
@ -629,7 +634,7 @@ workflows:
|
||||
- build
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_tests
|
||||
name: acceptance_tests_base_and_woo_cot_off
|
||||
requires:
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
@ -641,7 +646,6 @@ workflows:
|
||||
group: woo
|
||||
enable_cot: 1
|
||||
enable_cot_sync: 1
|
||||
woo_core_version: 7.1.0-rc.2 # Temporarily force COT beta version
|
||||
requires:
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
@ -653,17 +657,6 @@ workflows:
|
||||
group: woo
|
||||
enable_cot: 1
|
||||
enable_cot_sync: 0
|
||||
woo_core_version: 7.1.0-rc.2 # Temporarily force COT beta version
|
||||
requires:
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_tests_woo_cot_off
|
||||
group: woo
|
||||
woo_core_version: 7.1.0-rc.2 # Temporarily force COT beta version
|
||||
requires:
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
@ -673,21 +666,11 @@ workflows:
|
||||
<<: *slack-fail-post-step
|
||||
requires:
|
||||
- build
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
group: woo
|
||||
name: integration_test_woocommerce
|
||||
requires:
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
group: woo
|
||||
enable_cot: 1
|
||||
enable_cot_sync: 1
|
||||
woo_core_version: 7.1.0-rc.2 # Temporarily force COT beta version
|
||||
name: integration_test_woo_cot_sync
|
||||
requires:
|
||||
- unit_tests
|
||||
@ -699,7 +682,6 @@ workflows:
|
||||
group: woo
|
||||
enable_cot: 1
|
||||
enable_cot_sync: 0
|
||||
woo_core_version: 7.1.0-rc.2 # Temporarily force COT beta version
|
||||
name: integration_test_woo_cot_no_sync
|
||||
requires:
|
||||
- unit_tests
|
||||
@ -709,7 +691,6 @@ workflows:
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
group: woo
|
||||
woo_core_version: 7.1.0-rc.2 # Temporarily force COT beta version
|
||||
name: integration_test_woo_cot_off
|
||||
requires:
|
||||
- unit_tests
|
||||
@ -745,10 +726,14 @@ workflows:
|
||||
<<: *slack-fail-post-step
|
||||
requires:
|
||||
- build
|
||||
- acceptance_tests
|
||||
- acceptance_tests_base_and_woo_cot_off
|
||||
- js_tests
|
||||
- integration_test_woocommerce
|
||||
- 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_no_sync
|
||||
|
||||
nightly:
|
||||
triggers:
|
||||
@ -773,14 +758,14 @@ workflows:
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_oldest
|
||||
woo_core_version: 6.2.2
|
||||
woo_core_version: 6.8.0
|
||||
woo_subscriptions_version: 4.3.0
|
||||
woo_memberships_version: 1.21.0
|
||||
woo_blocks_version: 5.3.2
|
||||
mysql_command: --max_allowed_packet=100M
|
||||
mysql_image_version: 5.7.36
|
||||
codeception_image_version: 7.4-cli_20210126.1
|
||||
wordpress_image_version: wp-5.6_php7.2_20220406.1
|
||||
woo_blocks_version: 6.8.0
|
||||
mysql_command: --max_allowed_packet=100M --default-storage-engine=MYISAM
|
||||
mysql_image: mysql:5.5
|
||||
codeception_image_version: 7.4-cli_20220605.0
|
||||
wordpress_image_version: wp-5.8_php7.3_20221104.1
|
||||
requires:
|
||||
- build
|
||||
- unit_tests:
|
||||
@ -803,6 +788,8 @@ workflows:
|
||||
<<: *slack-fail-post-step
|
||||
name: integration_oldest
|
||||
codeception_image_version: 7.2-cli_20220605.0
|
||||
mysql_command: --max_allowed_packet=100M --default-storage-engine=MYISAM
|
||||
mysql_image: mysql:5.5
|
||||
requires:
|
||||
- build
|
||||
- build_premium:
|
||||
|
15
README.md
15
README.md
@ -126,23 +126,24 @@ You can access this help in your command line running `./do` without parameters.
|
||||
|
||||
[Read the article.](https://mailpoet.atlassian.net/wiki/spaces/MAILPOET/pages/629374977/Adding+new+templates+to+the+plugin)
|
||||
|
||||
## 🚥 Testing with PHP 7.4 or PHP 8.0
|
||||
## 🚥 Testing with different PHP versions
|
||||
|
||||
To switch the environment to PHP 7.4/8.0:
|
||||
To switch the environment to a different PHP version:
|
||||
|
||||
1. Configure the `wordpress` service in `docker-compose.override.yml` to build from the php74 Dockerfile:
|
||||
1. Check https://github.com/mailpoet/mailpoet/tree/trunk/dev for a list of available PHP versions. Each directory starting with `php` corresponds to a available version.
|
||||
2. Configure the `wordpress` service in `docker-compose.override.yml` to build from the desired PHP version Dockerfile (replace {PHP_VERSION} with the name of the directory that corresponds to the version that you want to use):
|
||||
|
||||
```yaml
|
||||
wordpress:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: dev/php74/Dockerfile # OR dev/php80/Dockerfile
|
||||
dockerfile: dev/{PHP_VERSION}/Dockerfile
|
||||
```
|
||||
|
||||
2. Run `docker-compose build wordpress`.
|
||||
3. Start the stack with `./do start`.
|
||||
3. Run `docker-compose build wordpress`.
|
||||
4. Start the stack with `./do start`.
|
||||
|
||||
To switch back to PHP 8.1 remove what was added in 1) and, run `docker-compose build wordpress` for application container and `docker-compose build test_wordpress` for tests container,
|
||||
To switch back to the default PHP version remove what was added in 2) and, run `docker-compose build wordpress` for application container and `docker-compose build test_wordpress` for tests container,
|
||||
and start the stack using `./do start`.
|
||||
|
||||
## ✅ TODO
|
||||
|
46
dev/php82/Dockerfile
Normal file
46
dev/php82/Dockerfile
Normal file
@ -0,0 +1,46 @@
|
||||
FROM php:8.2.0RC6-apache
|
||||
|
||||
ARG UID=1000
|
||||
ARG GID=1000
|
||||
|
||||
# additinal extensions
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y git zlib1g-dev libzip-dev zip wget gnupg msmtp libpng-dev gettext subversion \
|
||||
&& \
|
||||
# Install NodeJS, enable Corepack
|
||||
curl -sL https://deb.nodesource.com/setup_17.x | bash - && \
|
||||
apt-get install -y nodejs build-essential && \
|
||||
corepack enable && \
|
||||
\
|
||||
# Install WP-CLI
|
||||
curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \
|
||||
chmod +x /usr/local/bin/wp && \
|
||||
\
|
||||
# Clean up
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY dev/php.ini /usr/local/etc/php/conf.d/php_user.ini
|
||||
|
||||
# msmtp config
|
||||
RUN printf "account default\nhost smtp\nport 1025" > /etc/msmtprc
|
||||
|
||||
# xdebug build an config
|
||||
ENV XDEBUGINI_PATH=/usr/local/etc/php/conf.d/xdebug.ini
|
||||
RUN git clone -b "3.2.0RC2" --depth 1 https://github.com/xdebug/xdebug.git /usr/src/php/ext/xdebug \
|
||||
&& docker-php-ext-configure xdebug --enable-xdebug-dev \
|
||||
&& docker-php-ext-install xdebug \
|
||||
&& mkdir /tmp/debug
|
||||
COPY dev/xdebug.ini /tmp/xdebug.ini
|
||||
RUN cat /tmp/xdebug.ini >> $XDEBUGINI_PATH
|
||||
|
||||
# php extensions
|
||||
RUN docker-php-ext-install pdo_mysql
|
||||
RUN docker-php-ext-install mysqli
|
||||
|
||||
# allow .htaccess files (between <Directory /var/www/> and </Directory>, which is WordPress installation)
|
||||
RUN sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
|
||||
|
||||
# ensure existing content in /var/www/html respects UID and GID, give Node permissions for Corepack
|
||||
RUN chown -R ${UID}:${GID} /var/www/html && \
|
||||
mkdir -p /.node && chown -R ${UID}:${GID} /.node
|
@ -15,6 +15,7 @@ services:
|
||||
volumes:
|
||||
- my-datavolume:/var/lib/mysql
|
||||
- ./dev/database/create_test_db.sh:/docker-entrypoint-initdb.d/10-create_test_db.sh
|
||||
command: --sql_mode=STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,ANSI,ONLY_FULL_GROUP_BY
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: somewordpress
|
||||
MYSQL_DATABASE: wordpress
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
// phpcs:disable PSR1.Classes.ClassDeclaration
|
||||
// phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
|
||||
@ -425,6 +425,9 @@ class RoboFile extends \Robo\Tasks {
|
||||
$collection->addCode(function() {
|
||||
return $this->qaCodeSniffer([]);
|
||||
});
|
||||
$collection->addCode(function() {
|
||||
return $this->qaMinimalPluginStandard([]);
|
||||
});
|
||||
return $collection->run();
|
||||
}
|
||||
|
||||
@ -536,6 +539,55 @@ class RoboFile extends \Robo\Tasks {
|
||||
|
||||
$stringFilesToCheck = !empty($filesToCheck) ? implode(' ', $filesToCheck) : '.';
|
||||
|
||||
return $this->taskExec($task)
|
||||
->arg('--ignore=' . implode(',', $ignorePatterns))
|
||||
->rawArg($stringFilesToCheck)
|
||||
->run();
|
||||
}
|
||||
|
||||
public function qaMinimalPluginStandard(array $filesToCheck, $opts = ['severity' => 'all']) {
|
||||
$severityFlag = $opts['severity'] === 'all' ? '-w' : '-n';
|
||||
|
||||
$task = implode(' ', [
|
||||
'php -d memory_limit=-1',
|
||||
'./tasks/code_sniffer/vendor/bin/phpcs',
|
||||
'--extensions=php',
|
||||
$severityFlag,
|
||||
'--standard=tasks/code_sniffer/vendor/wporg/plugin-directory/MinimalPluginStandard',
|
||||
'-s',
|
||||
]);
|
||||
|
||||
$ignorePaths = [
|
||||
'.mp_svn',
|
||||
'assets',
|
||||
'doc',
|
||||
'generated',
|
||||
'lib/Config/PopulatorData/Templates',
|
||||
'lib-3rd-party',
|
||||
'node_modules',
|
||||
'plugin_repository',
|
||||
'prefixer/build',
|
||||
'prefixer/vendor',
|
||||
'tasks/code_sniffer/vendor',
|
||||
'tasks/phpstan/vendor',
|
||||
'tasks/makepot',
|
||||
'tools/vendor',
|
||||
'temp',
|
||||
'tests/_data',
|
||||
'tests/_output',
|
||||
'tests/_support/_generated',
|
||||
'vendor',
|
||||
'vendor-prefixed',
|
||||
'views',
|
||||
];
|
||||
|
||||
// the "--ignore" arg takes a list of regexes, we need to anchor and escape them
|
||||
$ignorePatterns = array_map(function (string $path): string {
|
||||
return '^' . preg_quote(__DIR__ . DIRECTORY_SEPARATOR . $path);
|
||||
}, $ignorePaths);
|
||||
|
||||
$stringFilesToCheck = !empty($filesToCheck) ? implode(' ', $filesToCheck) : '.';
|
||||
|
||||
return $this
|
||||
->taskExec($task)
|
||||
->arg('--ignore=' . implode(',', $ignorePatterns))
|
||||
@ -799,6 +851,9 @@ class RoboFile extends \Robo\Tasks {
|
||||
->addCode(function () use ($version) {
|
||||
$this->releaseCreatePullRequest($version);
|
||||
})
|
||||
->addCode(function () use ($version) {
|
||||
$this->releaseRerunCircleWorkflow(\MailPoetTasks\Release\CircleCiController::PROJECT_PREMIUM);
|
||||
})
|
||||
->addCode(function () use ($version) {
|
||||
$this->translationsPrepareLanguagePacks($version);
|
||||
})
|
||||
@ -1116,6 +1171,18 @@ class RoboFile extends \Robo\Tasks {
|
||||
$this->say("Release '$version[name]' info was published on Slack.");
|
||||
}
|
||||
|
||||
public function releaseRerunCircleWorkflow(string $project = null) {
|
||||
$circleciController = $this->createCircleCiController();
|
||||
$result = $circleciController->rerunLatestWorkflow($project);
|
||||
// Sometimes can be useful to know which Circle project workflow was restarted
|
||||
$project = $project ? " for the project '{$project}'" : '';
|
||||
if (!$result) {
|
||||
$this->yell("Circle Workflow{$project} was not restarted", 40, 'red');
|
||||
} else {
|
||||
$this->say("Circle Workflow{$project} was started from the beginning");
|
||||
}
|
||||
}
|
||||
|
||||
public function downloadWooCommerceBlocksZip($tag = null) {
|
||||
$this->createWpOrgDownloader('woo-gutenberg-products-block')
|
||||
->downloadPluginZip('woo-gutenberg-products-block.zip', __DIR__ . '/tests/plugins/', $tag);
|
||||
|
BIN
mailpoet/assets/audio/0.mp3
Normal file
BIN
mailpoet/assets/audio/0.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/1.mp3
Normal file
BIN
mailpoet/assets/audio/1.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/2.mp3
Normal file
BIN
mailpoet/assets/audio/2.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/3.mp3
Normal file
BIN
mailpoet/assets/audio/3.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/4.mp3
Normal file
BIN
mailpoet/assets/audio/4.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/5.mp3
Normal file
BIN
mailpoet/assets/audio/5.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/6.mp3
Normal file
BIN
mailpoet/assets/audio/6.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/7.mp3
Normal file
BIN
mailpoet/assets/audio/7.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/8.mp3
Normal file
BIN
mailpoet/assets/audio/8.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/9.mp3
Normal file
BIN
mailpoet/assets/audio/9.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/a.mp3
Normal file
BIN
mailpoet/assets/audio/a.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/b.mp3
Normal file
BIN
mailpoet/assets/audio/b.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/c.mp3
Normal file
BIN
mailpoet/assets/audio/c.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/d.mp3
Normal file
BIN
mailpoet/assets/audio/d.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/e.mp3
Normal file
BIN
mailpoet/assets/audio/e.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/f.mp3
Normal file
BIN
mailpoet/assets/audio/f.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/g.mp3
Normal file
BIN
mailpoet/assets/audio/g.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/h.mp3
Normal file
BIN
mailpoet/assets/audio/h.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/i.mp3
Normal file
BIN
mailpoet/assets/audio/i.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/j.mp3
Normal file
BIN
mailpoet/assets/audio/j.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/k.mp3
Normal file
BIN
mailpoet/assets/audio/k.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/l.mp3
Normal file
BIN
mailpoet/assets/audio/l.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/m.mp3
Normal file
BIN
mailpoet/assets/audio/m.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/n.mp3
Normal file
BIN
mailpoet/assets/audio/n.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/o.mp3
Normal file
BIN
mailpoet/assets/audio/o.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/p.mp3
Normal file
BIN
mailpoet/assets/audio/p.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/q.mp3
Normal file
BIN
mailpoet/assets/audio/q.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/r.mp3
Normal file
BIN
mailpoet/assets/audio/r.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/s.mp3
Normal file
BIN
mailpoet/assets/audio/s.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/t.mp3
Normal file
BIN
mailpoet/assets/audio/t.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/u.mp3
Normal file
BIN
mailpoet/assets/audio/u.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/v.mp3
Normal file
BIN
mailpoet/assets/audio/v.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/w.mp3
Normal file
BIN
mailpoet/assets/audio/w.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/x.mp3
Normal file
BIN
mailpoet/assets/audio/x.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/y.mp3
Normal file
BIN
mailpoet/assets/audio/y.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/z.mp3
Normal file
BIN
mailpoet/assets/audio/z.mp3
Normal file
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
.mailpoet-automation-workflow-add-trigger {
|
||||
.mailpoet-automation-add-trigger {
|
||||
align-items: center;
|
||||
border: 1px dashed #c3c4c7;
|
||||
border-radius: 4px;
|
||||
|
@ -1,14 +1,14 @@
|
||||
.mailpoet-automation-editor-workflow {
|
||||
.mailpoet-automation-editor-automation {
|
||||
background: #fbfbfb;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.mailpoet-automation-editor-workflow-wrapper {
|
||||
.mailpoet-automation-editor-automation-wrapper {
|
||||
display: grid;
|
||||
padding: 50px 20px;
|
||||
}
|
||||
|
||||
.mailpoet-automation-editor-workflow-end {
|
||||
.mailpoet-automation-editor-automation-end {
|
||||
background: #8c8f94;
|
||||
border-radius: 999999px;
|
||||
fill: white;
|
@ -1,4 +1,4 @@
|
||||
.mailpoet-automation-editor-empty-workflow {
|
||||
.mailpoet-automation-editor-empty-automation {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
height: 100%;
|
@ -36,7 +36,8 @@
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
textarea,
|
||||
input[type='text'].components-form-token-field__input {
|
||||
background: right top/26px no-repeat url('../../img/icons/alert.svg');
|
||||
padding-right: 26px;
|
||||
}
|
||||
|
@ -51,4 +51,8 @@
|
||||
.mailpoet_form_field_block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mailpoet_form_field_input_nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
@ -122,3 +122,21 @@ body .components-modal__screen-overlay {
|
||||
justify-content: flex-end;
|
||||
margin-top: $grid-gap-half;
|
||||
}
|
||||
|
||||
.mailpoet-locked-badge {
|
||||
align-items: center;
|
||||
background: #fcf9e8;
|
||||
border: .5px solid #f5e6ab;
|
||||
border-radius: 4px;
|
||||
color: #bd8600;
|
||||
display: flex;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
gap: 4px;
|
||||
height: 20px;
|
||||
letter-spacing: .2px;
|
||||
line-height: 16px;
|
||||
padding: 2px 8px 2px 4px;
|
||||
text-transform: uppercase;
|
||||
width: 82px;
|
||||
}
|
||||
|
11
mailpoet/assets/css/src/components-plugin/_landingpage.scss
Normal file
11
mailpoet/assets/css/src/components-plugin/_landingpage.scss
Normal file
@ -0,0 +1,11 @@
|
||||
#mailpoet_landingpage_container {
|
||||
.mailpoet-content-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.landing-footer {
|
||||
box-shadow: 0 -1px 0 0 $color-tertiary-light;
|
||||
padding-bottom: 1px;
|
||||
padding-top: 25px;
|
||||
}
|
||||
}
|
@ -119,6 +119,10 @@
|
||||
color: $color-stats-average;
|
||||
}
|
||||
|
||||
.mailpoet-statistics-value-number-critical {
|
||||
color: $color-stats-critical;
|
||||
}
|
||||
|
||||
.mailpoet-statistics-value-number-excellent {
|
||||
color: $color-stats-excellent;
|
||||
}
|
||||
|
@ -22,6 +22,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
#mailpoet-wizard-container {
|
||||
.mailpoet-top-bar {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-wizard-logo {
|
||||
margin-bottom: 100px;
|
||||
text-align: center;
|
||||
@ -35,6 +41,7 @@
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
|
||||
@include respond-to(medium-screen) {
|
||||
flex-direction: column;
|
||||
@ -42,7 +49,7 @@
|
||||
}
|
||||
|
||||
.mailpoet-wizard-step-illustration {
|
||||
margin-right: $grid-gap;
|
||||
margin-right: $grid-gap-xl;
|
||||
max-width: $grid-column;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
@ -63,12 +70,16 @@
|
||||
}
|
||||
|
||||
.mailpoet-wizard-step-content {
|
||||
max-width: $grid-column-small + $grid-gap + $grid-column;
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
|
||||
@include respond-to(medium-screen) {
|
||||
max-width: $grid-column;
|
||||
}
|
||||
|
||||
.mailpoet-button {
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-wizard-label {
|
||||
@ -111,12 +122,16 @@
|
||||
|
||||
.mailpoet-wizard-woocommerce-option {
|
||||
align-items: center;
|
||||
box-shadow: 0 -1px 0 0 $color-tertiary-light;
|
||||
box-shadow: 0 1px 0 0 $color-tertiary-light;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 25px;
|
||||
padding-top: 1px;
|
||||
|
||||
&:last-child {
|
||||
box-shadow: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-wizard-note {
|
||||
|
12
mailpoet/assets/css/src/components-public/captcha.scss
Normal file
12
mailpoet/assets/css/src/components-public/captcha.scss
Normal file
@ -0,0 +1,12 @@
|
||||
.mailpoet_captcha_form {
|
||||
.mailpoet_icon_button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
}
|
@ -112,3 +112,11 @@
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.authorize-sender-email-and-domain-modal {
|
||||
z-index: 30; // overlay other modals
|
||||
}
|
||||
|
||||
.authorize-sender-email-and-domain-modal-overlay {
|
||||
z-index: $modal-screen-overlay-z-index + 4; // overlay other modals
|
||||
}
|
||||
|
@ -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 {
|
||||
border-color: $color-stats-good;
|
||||
color: $color-stats-good;
|
||||
|
@ -29,7 +29,6 @@ $beamer-dot-size: 8px;
|
||||
}
|
||||
|
||||
.mailpoet-top-bar-logo {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
z-index: 1;
|
||||
@ -39,6 +38,10 @@ $beamer-dot-size: 8px;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
a.mailpoet-top-bar-logo {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-top-bar-logo-desktop {
|
||||
|
@ -11,17 +11,17 @@
|
||||
|
||||
@import './components-automation-editor/add-step-button';
|
||||
@import './components-automation-editor/add-trigger';
|
||||
@import './components-automation-editor/automation';
|
||||
@import './components-automation-editor/block-icon';
|
||||
@import './components-automation-editor/chip';
|
||||
@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/panel';
|
||||
@import './components-automation-editor/separator';
|
||||
@import './components-automation-editor/status';
|
||||
@import './components-automation-editor/step';
|
||||
@import './components-automation-editor/step-card';
|
||||
@import './components-automation-editor/workflow';
|
||||
@import './components-automation-editor/notices';
|
||||
@import './components-automation-editor/deactivate-modal';
|
||||
|
||||
|
@ -17,17 +17,30 @@ ul.mailpoet-automation-templates {
|
||||
|
||||
.mailpoet-automation-template-list-item {
|
||||
button.components-button {
|
||||
align-content: baseline;
|
||||
align-items: flex-start;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdcde;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-rows: 40px auto auto;
|
||||
height: 100%;
|
||||
padding: 24px 24px 26px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
|
||||
&:disabled,
|
||||
&[aria-disabled='true'] {
|
||||
color: #787c82;
|
||||
cursor: not-allowed;
|
||||
opacity: 1;
|
||||
|
||||
h2 {
|
||||
color: #787c82;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #fff;
|
||||
border: 1px solid #dcdcde;
|
||||
@ -49,7 +62,6 @@ ul.mailpoet-automation-templates {
|
||||
h2 {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #2271b1;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 21px;
|
||||
@ -78,4 +90,27 @@ ul.mailpoet-automation-templates {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -85,3 +85,4 @@
|
||||
@import 'components-plugin/set-from-address-modal';
|
||||
@import 'components-plugin/stats';
|
||||
@import 'components-plugin/import-export';
|
||||
@import 'components-plugin/landingpage';
|
||||
|
@ -19,3 +19,4 @@
|
||||
@import 'components-public/public';
|
||||
@import 'components-public/animation';
|
||||
@import 'components-public/form_colors';
|
||||
@import 'components-public/captcha';
|
||||
|
@ -69,6 +69,7 @@ $color-badge-video-guide: #46b450;
|
||||
$color-stats-average: #f559c3;
|
||||
$color-stats-good: #ff9f00;
|
||||
$color-stats-excellent: #7ed321;
|
||||
$color-stats-critical: #f00;
|
||||
$color-stats-unknown: $color-primary-inactive;
|
||||
|
||||
// Automation editor
|
||||
|
1
mailpoet/assets/img/icons/controls-volumeon.svg
Normal file
1
mailpoet/assets/img/icons/controls-volumeon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><rect x="0" fill="none" width="20" height="20"/><g><path d="M2 7h4l5-4v14l-5-4H2V7zm12.69-2.46C14.82 4.59 18 5.92 18 10s-3.18 5.41-3.31 5.46c-.06.03-.13.04-.19.04-.2 0-.39-.12-.46-.31-.11-.26.02-.55.27-.65.11-.05 2.69-1.15 2.69-4.54 0-3.41-2.66-4.53-2.69-4.54-.25-.1-.38-.39-.27-.65.1-.25.39-.38.65-.27zM16 10c0 2.57-2.23 3.43-2.32 3.47-.06.02-.12.03-.18.03-.2 0-.39-.12-.47-.32-.1-.26.04-.55.29-.65.07-.02 1.68-.67 1.68-2.53s-1.61-2.51-1.68-2.53c-.25-.1-.38-.39-.29-.65.1-.25.39-.39.65-.29.09.04 2.32.9 2.32 3.47z"/></g></svg>
|
After Width: | Height: | Size: 587 B |
1
mailpoet/assets/img/icons/image-rotate.svg
Normal file
1
mailpoet/assets/img/icons/image-rotate.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><rect x="0" fill="none" width="20" height="20"/><g><path d="M10.25 1.02c5.1 0 8.75 4.04 8.75 9s-3.65 9-8.75 9c-3.2 0-6.02-1.59-7.68-3.99l2.59-1.52c1.1 1.5 2.86 2.51 4.84 2.51 3.3 0 6-2.79 6-6s-2.7-6-6-6c-1.97 0-3.72 1-4.82 2.49L7 8.02l-6 2v-7L2.89 4.6c1.69-2.17 4.36-3.58 7.36-3.58z"/></g></svg>
|
After Width: | Height: | Size: 355 B |
@ -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 { api } from '../config';
|
||||
|
||||
export * from './hooks';
|
||||
|
||||
const apiUrl = `${api.root}/mailpoet/v1/automation/`;
|
||||
const apiUrl = `${api.root}/mailpoet/v1/`;
|
||||
|
||||
export type ApiError = {
|
||||
code?: string;
|
||||
|
@ -1,24 +1,33 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { TopBarWithBeamer } from 'common/top_bar/top_bar';
|
||||
import { Popover, SlotFillProvider } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { initializeApi, useMutation } from './api';
|
||||
import { initializeApi } from './api';
|
||||
import { registerTranslations } from './i18n';
|
||||
import { createStore, storeName } from './listing/store';
|
||||
import { AutomationListing, AutomationListingHeader } from './listing';
|
||||
import { registerApiErrorHandler } from './listing/api-error-handler';
|
||||
import { Notices } from './listing/components/notices';
|
||||
import { WorkflowListingNotices } from './listing/workflow-listing-notices';
|
||||
import { BuildYourOwnSection, HeroSection, TemplatesSection } from './sections';
|
||||
import {
|
||||
CreateEmptyWorkflowButton,
|
||||
CreateWorkflowFromTemplateButton,
|
||||
} from './testing';
|
||||
import { MailPoet } from '../mailpoet';
|
||||
|
||||
const trackOpenEvent = () => {
|
||||
MailPoet.trackEvent('Automations > Listing viewed');
|
||||
};
|
||||
|
||||
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 =
|
||||
count > 0 ? (
|
||||
<>
|
||||
@ -49,7 +58,7 @@ function Content(): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
function Workflows(): JSX.Element {
|
||||
function Automations(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<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 {
|
||||
return (
|
||||
<SlotFillProvider>
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
<Workflows />
|
||||
<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>
|
||||
<Automations />
|
||||
<Popover.Slot />
|
||||
</BrowserRouter>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
|
@ -4,9 +4,9 @@ declare global {
|
||||
root: string;
|
||||
nonce: string;
|
||||
};
|
||||
mailpoet_workflow_count: number;
|
||||
mailpoet_automation_count: number;
|
||||
}
|
||||
}
|
||||
|
||||
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 code = errorObject.code;
|
||||
|
||||
if (code === 'mailpoet_automation_workflow_not_valid') {
|
||||
if (code === 'mailpoet_automation_not_valid') {
|
||||
dispatch(storeName).setErrors({ steps: errorObject.data.errors });
|
||||
return undefined;
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ import { storeName } from '../../store';
|
||||
|
||||
export function TrashButton(): JSX.Element {
|
||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||
const { workflow } = useSelect(
|
||||
const { automation } = useSelect(
|
||||
(select) => ({
|
||||
workflow: select(storeName).getWorkflowData(),
|
||||
automation: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -33,7 +33,7 @@ export function TrashButton(): JSX.Element {
|
||||
>
|
||||
{sprintf(
|
||||
__('You are about to delete the automation "%s".', 'mailpoet'),
|
||||
workflow.name,
|
||||
automation.name,
|
||||
)}
|
||||
<br />
|
||||
{__(' This will stop it for all subscribers immediately.', 'mailpoet')}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
import { __unstableCompositeItem as CompositeItem } from '@wordpress/components';
|
||||
import { Icon, plus } from '@wordpress/icons';
|
||||
import { WorkflowCompositeContext } from './context';
|
||||
import { AutomationCompositeContext } from './context';
|
||||
|
||||
type Props = {
|
||||
onClick?: (element: HTMLButtonElement) => void;
|
||||
@ -9,7 +9,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export function AddStepButton({ onClick, previousStepId }: Props): JSX.Element {
|
||||
const compositeState = useContext(WorkflowCompositeContext);
|
||||
const compositeState = useContext(AutomationCompositeContext);
|
||||
return (
|
||||
<CompositeItem
|
||||
state={compositeState}
|
@ -3,7 +3,7 @@ import { __unstableCompositeItem as CompositeItem } from '@wordpress/components'
|
||||
import { Icon, plus } from '@wordpress/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { WorkflowCompositeContext } from './context';
|
||||
import { AutomationCompositeContext } from './context';
|
||||
import { Step } from './types';
|
||||
import { storeName } from '../../store';
|
||||
|
||||
@ -12,14 +12,14 @@ type Props = {
|
||||
};
|
||||
|
||||
export function AddTrigger({ step }: Props): JSX.Element {
|
||||
const compositeState = useContext(WorkflowCompositeContext);
|
||||
const compositeState = useContext(AutomationCompositeContext);
|
||||
const { setInserterPopover } = useDispatch(storeName);
|
||||
|
||||
return (
|
||||
<CompositeItem
|
||||
state={compositeState}
|
||||
role="treeitem"
|
||||
className="mailpoet-automation-workflow-add-trigger"
|
||||
className="mailpoet-automation-add-trigger"
|
||||
data-previous-step-id={step.id}
|
||||
focusable
|
||||
onClick={(event) => {
|
@ -1,5 +1,5 @@
|
||||
import { __unstableUseCompositeState as useCompositeState } from '@wordpress/components';
|
||||
import { createContext } from '@wordpress/element';
|
||||
|
||||
export const WorkflowCompositeContext =
|
||||
export const AutomationCompositeContext =
|
||||
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 { Icon, check } from '@wordpress/icons';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { WorkflowCompositeContext } from './context';
|
||||
import { EmptyWorkflow } from './empty-workflow';
|
||||
import { AutomationCompositeContext } from './context';
|
||||
import { EmptyAutomation } from './empty-automation';
|
||||
import { Separator } from './separator';
|
||||
import { Step } from './step';
|
||||
import { Step as StepData } from './types';
|
||||
@ -21,10 +21,10 @@ import {
|
||||
RenderStepType,
|
||||
} from '../../../types/filters';
|
||||
|
||||
export function Workflow(): JSX.Element {
|
||||
const { workflowData, selectedStep } = useSelect(
|
||||
export function Automation(): JSX.Element {
|
||||
const { automationData, selectedStep } = useSelect(
|
||||
(select) => ({
|
||||
workflowData: select(storeName).getWorkflowData(),
|
||||
automationData: select(storeName).getAutomationData(),
|
||||
selectedStep: select(storeName).getSelectedStep(),
|
||||
}),
|
||||
[],
|
||||
@ -36,9 +36,9 @@ export function Workflow(): JSX.Element {
|
||||
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 stepArray = [stepMap.root];
|
||||
|
||||
@ -56,7 +56,7 @@ export function Workflow(): JSX.Element {
|
||||
const renderStep = useMemo(
|
||||
(): RenderStepType =>
|
||||
Hooks.applyFilters(
|
||||
'mailpoet.automation.workflow.render_step',
|
||||
'mailpoet.automation.render_step',
|
||||
(stepData: StepData) =>
|
||||
stepData.type === 'root' ? (
|
||||
<AddTrigger step={stepData} />
|
||||
@ -73,7 +73,7 @@ export function Workflow(): JSX.Element {
|
||||
const renderSeparator = useMemo(
|
||||
(): RenderStepSeparatorType =>
|
||||
Hooks.applyFilters(
|
||||
'mailpoet.automation.workflow.render_step_separator',
|
||||
'mailpoet.automation.render_step_separator',
|
||||
(previousStepData: StepData) => (
|
||||
<Separator previousStepId={previousStepData.id} />
|
||||
),
|
||||
@ -81,20 +81,20 @@ export function Workflow(): JSX.Element {
|
||||
[],
|
||||
);
|
||||
|
||||
if (!workflowData) {
|
||||
return <EmptyWorkflow />;
|
||||
if (!automationData) {
|
||||
return <EmptyAutomation />;
|
||||
}
|
||||
|
||||
return (
|
||||
<WorkflowCompositeContext.Provider value={compositeState}>
|
||||
<AutomationCompositeContext.Provider value={compositeState}>
|
||||
<Composite
|
||||
state={compositeState}
|
||||
role="tree"
|
||||
aria-label={__('Automation', 'mailpoet')}
|
||||
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 />
|
||||
{stepMap.root.next_steps.length === 0 ? (
|
||||
<>
|
||||
@ -119,13 +119,13 @@ export function Workflow(): JSX.Element {
|
||||
</Fragment>
|
||||
))}
|
||||
<Icon
|
||||
className="mailpoet-automation-editor-workflow-end"
|
||||
className="mailpoet-automation-editor-automation-end"
|
||||
icon={check}
|
||||
/>
|
||||
<div />
|
||||
</div>
|
||||
<InserterPopover />
|
||||
</Composite>
|
||||
</WorkflowCompositeContext.Provider>
|
||||
</AutomationCompositeContext.Provider>
|
||||
);
|
||||
}
|
@ -4,9 +4,9 @@ import { storeName } from '../../store';
|
||||
import { Statistics as BaseStatistics } from '../../../components/statistics';
|
||||
|
||||
export function Statistics(): JSX.Element {
|
||||
const { workflow } = useSelect(
|
||||
const { automation } = useSelect(
|
||||
(select) => ({
|
||||
workflow: select(storeName).getWorkflowData(),
|
||||
automation: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -19,19 +19,19 @@ export function Statistics(): JSX.Element {
|
||||
key: 'entered',
|
||||
// translators: Total number of subscribers who entered an automation
|
||||
label: _x('Total Entered', 'automation stats', 'mailpoet'),
|
||||
value: workflow.stats.totals.entered,
|
||||
value: automation.stats.totals.entered,
|
||||
},
|
||||
{
|
||||
key: 'processing',
|
||||
// translators: Total number of subscribers who are being processed in an automation
|
||||
label: _x('Total Processing', 'automation stats', 'mailpoet'),
|
||||
value: workflow.stats.totals.in_progress,
|
||||
value: automation.stats.totals.in_progress,
|
||||
},
|
||||
{
|
||||
key: 'exited',
|
||||
// translators: Total number of subscribers who exited an automation, no matter the result
|
||||
label: _x('Total Exited', 'automation stats', 'mailpoet'),
|
||||
value: workflow.stats.totals.exited,
|
||||
value: automation.stats.totals.exited,
|
||||
},
|
||||
]}
|
||||
/>
|
@ -1,12 +1,10 @@
|
||||
import { useState, Fragment } from 'react';
|
||||
import { DropdownMenu } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { moreVertical, trash } from '@wordpress/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { PremiumModal } from 'common/premium_modal';
|
||||
import { Step as StepData } from './types';
|
||||
import { storeName } from '../../store';
|
||||
import { StepMoreControlsType } from '../../../types/filters';
|
||||
|
||||
type Props = {
|
||||
@ -14,16 +12,10 @@ type Props = {
|
||||
};
|
||||
|
||||
export function StepMoreMenu({ step }: Props): JSX.Element {
|
||||
const { stepType } = useSelect(
|
||||
(select) => ({
|
||||
stepType: select(storeName).getStepType(step.key),
|
||||
}),
|
||||
[step],
|
||||
);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const moreControls: StepMoreControlsType = Hooks.applyFilters(
|
||||
'mailpoet.automation.workflow.step.more-controls',
|
||||
'mailpoet.automation.step.more-controls',
|
||||
{
|
||||
delete: {
|
||||
key: 'delete',
|
||||
@ -53,7 +45,6 @@ export function StepMoreMenu({ step }: Props): JSX.Element {
|
||||
},
|
||||
},
|
||||
step,
|
||||
stepType,
|
||||
);
|
||||
|
||||
const slots = Object.values(moreControls).filter(
|
@ -4,7 +4,7 @@ import { __unstableCompositeItem as CompositeItem } from '@wordpress/components'
|
||||
import { useDispatch, useRegistry, useSelect } from '@wordpress/data';
|
||||
import { blockMeta } from '@wordpress/icons';
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { WorkflowCompositeContext } from './context';
|
||||
import { AutomationCompositeContext } from './context';
|
||||
import { StepMoreMenu } from './step-more-menu';
|
||||
import { Step as StepData } from './types';
|
||||
import { Chip } from '../chip';
|
||||
@ -48,7 +48,7 @@ export function Step({ step, isSelected }: Props): JSX.Element {
|
||||
[step],
|
||||
);
|
||||
const { openSidebar, selectStep } = useDispatch(storeName);
|
||||
const compositeState = useContext(WorkflowCompositeContext);
|
||||
const compositeState = useContext(AutomationCompositeContext);
|
||||
const { batch } = useRegistry();
|
||||
|
||||
const compositeItemId = `step-${step.id}`;
|
@ -1,4 +1,4 @@
|
||||
import { WorkflowStatus } from '../../../listing/workflow';
|
||||
import { AutomationStatus } from '../../../listing/automation';
|
||||
|
||||
export type NextStep = {
|
||||
id: string;
|
||||
@ -12,10 +12,10 @@ export type Step = {
|
||||
next_steps: NextStep[];
|
||||
};
|
||||
|
||||
export type Workflow = {
|
||||
export type Automation = {
|
||||
id: number;
|
||||
name: string;
|
||||
status: WorkflowStatus;
|
||||
status: AutomationStatus;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
activated_at: string;
|
@ -10,7 +10,7 @@ import { useRef } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { chevronDown } from '@wordpress/icons';
|
||||
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
|
||||
|
||||
@ -21,11 +21,11 @@ const Dropdown: ComponentType<
|
||||
}
|
||||
> = WpDropdown;
|
||||
|
||||
export function DocumentActions({ children }): JSX.Element {
|
||||
const { workflowName, workflowStatus, showIconLabels } = useSelect(
|
||||
function DocumentActions({ children }): JSX.Element {
|
||||
const { automationName, automationStatus, showIconLabels } = useSelect(
|
||||
(select) => ({
|
||||
workflowName: select(storeName).getWorkflowData().name,
|
||||
workflowStatus: select(storeName).getWorkflowData().status,
|
||||
automationName: select(storeName).getAutomationData().name,
|
||||
automationStatus: select(storeName).getAutomationData().status,
|
||||
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
||||
}),
|
||||
[],
|
||||
@ -36,9 +36,9 @@ export function DocumentActions({ children }): JSX.Element {
|
||||
const titleRef = useRef();
|
||||
|
||||
let chipClass = 'mailpoet-automation-editor-chip-gray';
|
||||
if (workflowStatus === WorkflowStatus.ACTIVE) {
|
||||
if (automationStatus === AutomationStatus.ACTIVE) {
|
||||
chipClass = 'mailpoet-automation-editor-chip-success';
|
||||
} else if (workflowStatus === WorkflowStatus.DEACTIVATING) {
|
||||
} else if (automationStatus === AutomationStatus.DEACTIVATING) {
|
||||
chipClass = 'mailpoet-automation-editor-chip-danger';
|
||||
}
|
||||
|
||||
@ -66,18 +66,18 @@ export function DocumentActions({ children }): JSX.Element {
|
||||
<VisuallyHidden as="span">
|
||||
{__('Editing automation:', 'mailpoet')}
|
||||
</VisuallyHidden>
|
||||
{workflowName}
|
||||
{automationName}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
size="body"
|
||||
className={`edit-site-document-actions__secondary-item ${chipClass}`}
|
||||
>
|
||||
{workflowStatus === WorkflowStatus.ACTIVE &&
|
||||
{automationStatus === AutomationStatus.ACTIVE &&
|
||||
__('Active', 'mailpoet')}
|
||||
{workflowStatus === WorkflowStatus.DEACTIVATING &&
|
||||
{automationStatus === AutomationStatus.DEACTIVATING &&
|
||||
__('Deactivating', 'mailpoet')}
|
||||
{workflowStatus === WorkflowStatus.DRAFT &&
|
||||
{automationStatus === AutomationStatus.DRAFT &&
|
||||
__('Draft', 'mailpoet')}
|
||||
</Text>
|
||||
</a>
|
||||
@ -101,3 +101,6 @@ export function DocumentActions({ children }): JSX.Element {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
DocumentActions.displayName = 'DocumentActions';
|
||||
export { DocumentActions };
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { createContext } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { ErrorBoundary } from 'common';
|
||||
import { Chip } from '../chip';
|
||||
import { ColoredIcon } from '../icons';
|
||||
import {
|
||||
@ -35,17 +36,17 @@ type StepErrorProps = {
|
||||
function StepError({ stepId }: StepErrorProps): JSX.Element {
|
||||
const compositeState = useContext(ErrorsCompositeContext);
|
||||
|
||||
const { steps, workflowData } = useSelect(
|
||||
const { steps, automationData } = useSelect(
|
||||
(select) => ({
|
||||
steps: select(storeName).getSteps(),
|
||||
workflowData: select(storeName).getWorkflowData(),
|
||||
automationData: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const { openSidebar, selectStep } = useDispatch(storeName);
|
||||
|
||||
const stepData = workflowData.steps[stepId];
|
||||
const stepData = automationData.steps[stepId];
|
||||
const step = steps.find(({ key }) => key === stepData.key);
|
||||
|
||||
return (
|
||||
@ -70,6 +71,8 @@ function StepError({ stepId }: StepErrorProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
StepError.displayName = 'StepError';
|
||||
|
||||
export function Errors(): JSX.Element | null {
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
|
||||
@ -78,10 +81,10 @@ export function Errors(): JSX.Element | null {
|
||||
shift: true,
|
||||
});
|
||||
|
||||
const { errors, workflowData } = useSelect(
|
||||
const { errors, automationData } = useSelect(
|
||||
(select) => ({
|
||||
errors: select(storeName).getErrors(),
|
||||
workflowData: select(storeName).getWorkflowData(),
|
||||
automationData: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -93,18 +96,18 @@ export function Errors(): JSX.Element | null {
|
||||
}
|
||||
|
||||
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) {
|
||||
const id = ids.shift();
|
||||
if (!visited.has(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),
|
||||
);
|
||||
}
|
||||
}
|
||||
return [...visited.values()].filter((error) => !!error);
|
||||
}, [errors, workflowData]);
|
||||
}, [errors, automationData]);
|
||||
|
||||
// automatically open the popover when errors appear
|
||||
const hasErrors = stepErrors.length > 0;
|
||||
@ -160,9 +163,11 @@ export function Errors(): JSX.Element | null {
|
||||
__('The following steps are not fully set:', 'mailpoet')
|
||||
}
|
||||
</div>
|
||||
{stepErrors.map((error) => (
|
||||
<StepError key={error.step_id} stepId={error.step_id} />
|
||||
))}
|
||||
<ErrorBoundary>
|
||||
{stepErrors.map((error) => (
|
||||
<StepError key={error.step_id} stepId={error.step_id} />
|
||||
))}
|
||||
</ErrorBoundary>
|
||||
</Composite>
|
||||
</ErrorsCompositeContext.Provider>
|
||||
</Popover>
|
||||
|
@ -8,12 +8,13 @@ import {
|
||||
import { dispatch, useDispatch, useSelect } from '@wordpress/data';
|
||||
import { PinnedItems } from '@wordpress/interface';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { ErrorBoundary } from 'common';
|
||||
import { DocumentActions } from './document_actions';
|
||||
import { Errors } from './errors';
|
||||
import { InserterToggle } from './inserter_toggle';
|
||||
import { MoreMenu } from './more_menu';
|
||||
import { storeName } from '../../store';
|
||||
import { WorkflowStatus } from '../../../listing/workflow';
|
||||
import { AutomationStatus } from '../../../listing/automation';
|
||||
import {
|
||||
DeactivateImmediatelyModal,
|
||||
DeactivateModal,
|
||||
@ -23,22 +24,23 @@ import {
|
||||
// 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
|
||||
|
||||
function ActivateButton({ onClick, label }): JSX.Element {
|
||||
function ActivateButton({ label }): JSX.Element {
|
||||
const { errors, isDeactivating } = useSelect(
|
||||
(select) => ({
|
||||
errors: select(storeName).getErrors(),
|
||||
isDeactivating:
|
||||
select(storeName).getWorkflowData().status ===
|
||||
WorkflowStatus.DEACTIVATING,
|
||||
select(storeName).getAutomationData().status ===
|
||||
AutomationStatus.DEACTIVATING,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const { openActivationPanel } = useDispatch(storeName);
|
||||
|
||||
const button = (
|
||||
<Button
|
||||
variant="primary"
|
||||
className="editor-post-publish-button"
|
||||
onClick={onClick}
|
||||
onClick={openActivationPanel}
|
||||
disabled={isDeactivating || !!errors}
|
||||
>
|
||||
{label}
|
||||
@ -53,7 +55,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.
|
||||
delay={0}
|
||||
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',
|
||||
)}
|
||||
>
|
||||
@ -68,14 +70,14 @@ function ActivateButton({ onClick, label }): JSX.Element {
|
||||
function UpdateButton(): JSX.Element {
|
||||
const { save } = useDispatch(storeName);
|
||||
|
||||
const { workflow } = useSelect(
|
||||
const { automation } = useSelect(
|
||||
(select) => ({
|
||||
workflow: select(storeName).getWorkflowData(),
|
||||
automation: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
if (workflow.stats.totals.in_progress === 0) {
|
||||
if (automation.stats.totals.in_progress === 0) {
|
||||
return (
|
||||
<Button
|
||||
variant="primary"
|
||||
@ -93,7 +95,7 @@ function UpdateButton(): JSX.Element {
|
||||
// The following error seems to be a mismatch. It claims the 'delay' prop does not exist, but it does.
|
||||
delay={0}
|
||||
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',
|
||||
)}
|
||||
>
|
||||
@ -125,7 +127,7 @@ function DeactivateButton(): JSX.Element {
|
||||
const { hasUsersInProgress } = useSelect(
|
||||
(select) => ({
|
||||
hasUsersInProgress:
|
||||
select(storeName).getWorkflowData().stats.totals.in_progress > 0,
|
||||
select(storeName).getAutomationData().stats.totals.in_progress > 0,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -165,7 +167,7 @@ function DeactivateNowButton(): JSX.Element {
|
||||
const { hasUsersInProgress } = useSelect(
|
||||
(select) => ({
|
||||
hasUsersInProgress:
|
||||
select(storeName).getWorkflowData().stats.totals.in_progress > 0,
|
||||
select(storeName).getAutomationData().stats.totals.in_progress > 0,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -201,18 +203,14 @@ function DeactivateNowButton(): JSX.Element {
|
||||
|
||||
type Props = {
|
||||
showInserterToggle: boolean;
|
||||
toggleActivatePanel: () => void;
|
||||
};
|
||||
|
||||
export function Header({
|
||||
showInserterToggle,
|
||||
toggleActivatePanel,
|
||||
}: Props): JSX.Element {
|
||||
const { setWorkflowName } = useDispatch(storeName);
|
||||
const { workflowName, workflowStatus } = useSelect(
|
||||
export function Header({ showInserterToggle }: Props): JSX.Element {
|
||||
const { setAutomationName } = useDispatch(storeName);
|
||||
const { automationName, automationStatus } = useSelect(
|
||||
(select) => ({
|
||||
workflowName: select(storeName).getWorkflowData().name,
|
||||
workflowStatus: select(storeName).getWorkflowData().status,
|
||||
automationName: select(storeName).getAutomationData().name,
|
||||
automationStatus: select(storeName).getAutomationData().status,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -230,50 +228,48 @@ export function Header({
|
||||
</div>
|
||||
|
||||
<div className="edit-site-header_center">
|
||||
<DocumentActions>
|
||||
{() => (
|
||||
<div className="mailpoet-automation-editor-dropdown-name-edit">
|
||||
<div className="mailpoet-automation-editor-dropdown-name-edit-title">
|
||||
{__('Automation name', 'mailpoet')}
|
||||
<ErrorBoundary>
|
||||
<DocumentActions>
|
||||
{() => (
|
||||
<div className="mailpoet-automation-editor-dropdown-name-edit">
|
||||
<div className="mailpoet-automation-editor-dropdown-name-edit-title">
|
||||
{__('Automation name', 'mailpoet')}
|
||||
</div>
|
||||
<TextControl
|
||||
value={automationName}
|
||||
onChange={(newName) => setAutomationName(newName)}
|
||||
help={__(
|
||||
`Give the automation a name that indicates its purpose. E.g. "Abandoned cart recovery"`,
|
||||
'mailpoet',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<TextControl
|
||||
value={workflowName}
|
||||
onChange={(newName) => setWorkflowName(newName)}
|
||||
help={__(
|
||||
`Give the automation a name that indicates its purpose. E.g. "Abandoned cart recovery"`,
|
||||
'mailpoet',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</DocumentActions>
|
||||
)}
|
||||
</DocumentActions>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
<div className="edit-site-header_end">
|
||||
<div className="edit-site-header__actions">
|
||||
<Errors />
|
||||
{workflowStatus === WorkflowStatus.DRAFT && (
|
||||
<ErrorBoundary>
|
||||
<Errors />
|
||||
</ErrorBoundary>
|
||||
{automationStatus === AutomationStatus.DRAFT && (
|
||||
<>
|
||||
<SaveDraftButton />
|
||||
<ActivateButton
|
||||
onClick={toggleActivatePanel}
|
||||
label={__('Activate', 'mailpoet')}
|
||||
/>
|
||||
<ActivateButton label={__('Activate', 'mailpoet')} />
|
||||
</>
|
||||
)}
|
||||
{workflowStatus === WorkflowStatus.ACTIVE && (
|
||||
{automationStatus === AutomationStatus.ACTIVE && (
|
||||
<>
|
||||
<DeactivateButton />
|
||||
<UpdateButton />
|
||||
</>
|
||||
)}
|
||||
{workflowStatus === WorkflowStatus.DEACTIVATING && (
|
||||
{automationStatus === AutomationStatus.DEACTIVATING && (
|
||||
<>
|
||||
<DeactivateNowButton />
|
||||
<ActivateButton
|
||||
onClick={toggleActivatePanel}
|
||||
label={__('Update & Activate', 'mailpoet')}
|
||||
/>
|
||||
<ActivateButton label={__('Update & Activate', 'mailpoet')} />
|
||||
</>
|
||||
)}
|
||||
<PinnedItems.Slot scope={storeName} />
|
||||
|
@ -0,0 +1,2 @@
|
||||
export * from './panel';
|
||||
export * from './form-token-field';
|
@ -22,7 +22,7 @@ export function InserterPopover(): JSX.Element | null {
|
||||
|
||||
const onInsert = useCallback((item: Item) => {
|
||||
const addStepCallback: AddStepCallbackType = Hooks.applyFilters(
|
||||
'mailpoet.automation.workflow.add_step_callback',
|
||||
'mailpoet.automation.add_step_callback',
|
||||
() => {
|
||||
setShowModal(true);
|
||||
},
|
||||
@ -45,7 +45,7 @@ export function InserterPopover(): JSX.Element | null {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Inserter onInsert={onInsert} />
|
||||
<Inserter onInsert={onInsert} showInserterHelpPanel={false} />
|
||||
</Popover>
|
||||
|
||||
{showModal && (
|
||||
|
@ -20,134 +20,139 @@ const filterItems = (value: string, item: Item[]): Item[] =>
|
||||
|
||||
type Props = {
|
||||
onInsert?: (item: Item) => void;
|
||||
showInserterHelpPanel?: boolean;
|
||||
};
|
||||
|
||||
export const Inserter = forwardRef(({ onInsert }: Props, ref): JSX.Element => {
|
||||
const [filterValue, setFilterValue] = useState('');
|
||||
const [hoveredItem, setHoveredItem] = useState(null);
|
||||
export const Inserter = forwardRef(
|
||||
({ onInsert, showInserterHelpPanel = true }: Props, ref): JSX.Element => {
|
||||
const [filterValue, setFilterValue] = useState('');
|
||||
const [hoveredItem, setHoveredItem] = useState(null);
|
||||
|
||||
const { steps, type } = useSelect(
|
||||
(select) => ({
|
||||
steps: select(storeName).getSteps(),
|
||||
type: select(storeName).getInserterPopover().type,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const { steps, type } = useSelect(
|
||||
(select) => ({
|
||||
steps: select(storeName).getSteps(),
|
||||
type: select(storeName).getInserterPopover().type,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const groups: Group[] = useMemo(
|
||||
() =>
|
||||
type === 'triggers'
|
||||
? [
|
||||
{
|
||||
type: 'triggers',
|
||||
title: undefined,
|
||||
// translators: Label for a list of automation steps of type trigger
|
||||
label: _x('Triggers', 'automation steps', 'mailpoet'),
|
||||
items: steps.filter(({ group }) => group === 'triggers'),
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
type: 'actions',
|
||||
// translators: Label for a list of automation steps of type action
|
||||
title: _x('Actions', 'automation steps', 'mailpoet'),
|
||||
// translators: Label for a list of automation steps of type action
|
||||
label: _x('Actions', 'automation steps', 'mailpoet'),
|
||||
items: steps.filter(({ group }) => group === 'actions'),
|
||||
},
|
||||
{
|
||||
type: 'logical',
|
||||
// translators: Label for a list of logical automation steps (if/else, etc.)
|
||||
title: _x('Logical', 'automation steps', 'mailpoet'),
|
||||
// translators: Label for a list of logical automation steps (if/else, etc.)
|
||||
label: _x('Logical', 'automation steps', 'mailpoet'),
|
||||
items: steps.filter(({ group }) => group === 'logical'),
|
||||
},
|
||||
],
|
||||
[steps, type],
|
||||
);
|
||||
const groups: Group[] = useMemo(
|
||||
() =>
|
||||
type === 'triggers'
|
||||
? [
|
||||
{
|
||||
type: 'triggers',
|
||||
title: undefined,
|
||||
// translators: Label for a list of automation steps of type trigger
|
||||
label: _x('Triggers', 'automation steps', 'mailpoet'),
|
||||
items: steps.filter(({ group }) => group === 'triggers'),
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
type: 'actions',
|
||||
// translators: Label for a list of automation steps of type action
|
||||
title: _x('Actions', 'automation steps', 'mailpoet'),
|
||||
// translators: Label for a list of automation steps of type action
|
||||
label: _x('Actions', 'automation steps', 'mailpoet'),
|
||||
items: steps.filter(({ group }) => group === 'actions'),
|
||||
},
|
||||
{
|
||||
type: 'logical',
|
||||
// translators: Label for a list of logical automation steps (if/else, etc.)
|
||||
title: _x('Logical', 'automation steps', 'mailpoet'),
|
||||
// translators: Label for a list of logical automation steps (if/else, etc.)
|
||||
label: _x('Logical', 'automation steps', 'mailpoet'),
|
||||
items: steps.filter(({ group }) => group === 'logical'),
|
||||
},
|
||||
],
|
||||
[steps, type],
|
||||
);
|
||||
|
||||
const onHover = useCallback(
|
||||
(item) => {
|
||||
setHoveredItem(item);
|
||||
},
|
||||
[setHoveredItem],
|
||||
);
|
||||
const onHover = useCallback(
|
||||
(item) => {
|
||||
setHoveredItem(item);
|
||||
},
|
||||
[setHoveredItem],
|
||||
);
|
||||
|
||||
const searchRef = useRef<HTMLInputElement>();
|
||||
useImperativeHandle(ref, () => ({
|
||||
focusSearch: () => {
|
||||
searchRef.current?.focus();
|
||||
},
|
||||
}));
|
||||
const searchRef = useRef<HTMLInputElement>();
|
||||
useImperativeHandle(ref, () => ({
|
||||
focusSearch: () => {
|
||||
searchRef.current?.focus();
|
||||
},
|
||||
}));
|
||||
|
||||
const filteredGroups = useMemo(
|
||||
() =>
|
||||
groups.map((group) => ({
|
||||
...group,
|
||||
items: filterItems(filterValue, group.items),
|
||||
})),
|
||||
[filterValue, groups],
|
||||
);
|
||||
const filteredGroups = useMemo(
|
||||
() =>
|
||||
groups.map((group) => ({
|
||||
...group,
|
||||
items: filterItems(filterValue, group.items),
|
||||
})),
|
||||
[filterValue, groups],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="block-editor-inserter__menu">
|
||||
<div className="block-editor-inserter__main-area">
|
||||
<div className="block-editor-inserter__content">
|
||||
<SearchControl
|
||||
className="block-editor-inserter__search"
|
||||
onChange={(value: string) => {
|
||||
if (hoveredItem) setHoveredItem(null);
|
||||
setFilterValue(value);
|
||||
}}
|
||||
value={filterValue}
|
||||
label={__('Search for automation steps', 'mailpoet')}
|
||||
placeholder={__('Search', 'mailpoet')}
|
||||
ref={searchRef}
|
||||
/>
|
||||
return (
|
||||
<div className="block-editor-inserter__menu">
|
||||
<div className="block-editor-inserter__main-area">
|
||||
<div className="block-editor-inserter__content">
|
||||
<SearchControl
|
||||
className="block-editor-inserter__search"
|
||||
onChange={(value: string) => {
|
||||
if (hoveredItem) setHoveredItem(null);
|
||||
setFilterValue(value);
|
||||
}}
|
||||
value={filterValue}
|
||||
label={__('Search for automation steps', 'mailpoet')}
|
||||
placeholder={__('Search', 'mailpoet')}
|
||||
ref={searchRef}
|
||||
/>
|
||||
|
||||
<div className="block-editor-inserter__block-list">
|
||||
<InserterListbox>
|
||||
{filteredGroups.map(
|
||||
(group) =>
|
||||
group.items.length > 0 && (
|
||||
<Fragment key={group.type}>
|
||||
{group.title && (
|
||||
<div className="block-editor-inserter__panel-header">
|
||||
<h2 className="block-editor-inserter__panel-title">
|
||||
<div>{group.title}</div>
|
||||
</h2>
|
||||
<div className="block-editor-inserter__block-list">
|
||||
<InserterListbox>
|
||||
{filteredGroups.map(
|
||||
(group) =>
|
||||
group.items.length > 0 && (
|
||||
<Fragment key={group.type}>
|
||||
{group.title && (
|
||||
<div className="block-editor-inserter__panel-header">
|
||||
<h2 className="block-editor-inserter__panel-title">
|
||||
<div>{group.title}</div>
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
<div className="block-editor-inserter__panel-content">
|
||||
<StepList
|
||||
items={group.items}
|
||||
onHover={onHover}
|
||||
onSelect={(item: Item) => onInsert(item)}
|
||||
label={group.label}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="block-editor-inserter__panel-content">
|
||||
<StepList
|
||||
items={group.items}
|
||||
onHover={onHover}
|
||||
onSelect={(item: Item) => onInsert(item)}
|
||||
label={group.label}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
),
|
||||
)}
|
||||
</Fragment>
|
||||
),
|
||||
)}
|
||||
|
||||
{filteredGroups.reduce(
|
||||
(sum, { items }) => sum + items.length,
|
||||
0,
|
||||
) === 0 && (
|
||||
<div className="block-editor-inserter__no-results">
|
||||
<Icon
|
||||
className="block-editor-inserter__no-results-icon"
|
||||
icon={blockDefault}
|
||||
/>
|
||||
<p>{__('No results found.', 'mailpoet')}</p>
|
||||
</div>
|
||||
)}
|
||||
</InserterListbox>
|
||||
{filteredGroups.reduce(
|
||||
(sum, { items }) => sum + items.length,
|
||||
0,
|
||||
) === 0 && (
|
||||
<div className="block-editor-inserter__no-results">
|
||||
<Icon
|
||||
className="block-editor-inserter__no-results-icon"
|
||||
icon={blockDefault}
|
||||
/>
|
||||
<p>{__('No results found.', 'mailpoet')}</p>
|
||||
</div>
|
||||
)}
|
||||
</InserterListbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showInserterHelpPanel && hoveredItem && (
|
||||
<StepInfoPanel item={hoveredItem} />
|
||||
)}
|
||||
</div>
|
||||
{hoveredItem && <StepInfoPanel item={hoveredItem} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
store as keyboardShortcutsStore,
|
||||
} from '@wordpress/keyboard-shortcuts';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store';
|
||||
import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
|
||||
|
||||
// See:
|
||||
// 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 {
|
||||
const sidebarToOpen = selectedStep()
|
||||
? stepSidebarKey
|
||||
: workflowSidebarKey;
|
||||
: automationSidebarKey;
|
||||
openSidebar(sidebarToOpen);
|
||||
}
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ import { Button, Modal } from '@wordpress/components';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { storeName } from '../../store';
|
||||
import { WorkflowStatus } from '../../../listing/workflow';
|
||||
import { AutomationStatus } from '../../../listing/automation';
|
||||
|
||||
type DeactivateImmediatelyModalProps = {
|
||||
onClose: () => void;
|
||||
@ -15,7 +15,7 @@ export function DeactivateImmediatelyModal({
|
||||
return (
|
||||
<Modal
|
||||
className="mailpoet-automatoin-deactivate-modal"
|
||||
title={__('Stop automatoin for all subscribers?', 'mailpoet')}
|
||||
title={__('Stop automation for all subscribers?', 'mailpoet')}
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<p>
|
||||
@ -49,20 +49,20 @@ type DeactivateModalProps = {
|
||||
export function DeactivateModal({
|
||||
onClose,
|
||||
}: DeactivateModalProps): JSX.Element {
|
||||
const { workflowName } = useSelect(
|
||||
const { automationName } = useSelect(
|
||||
(select) => ({
|
||||
workflowName: select(storeName).getWorkflowData().name,
|
||||
automationName: select(storeName).getAutomationData().name,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const [selected, setSelected] = useState<
|
||||
WorkflowStatus.DRAFT | WorkflowStatus.DEACTIVATING
|
||||
>(WorkflowStatus.DEACTIVATING);
|
||||
AutomationStatus.DRAFT | AutomationStatus.DEACTIVATING
|
||||
>(AutomationStatus.DEACTIVATING);
|
||||
const [isBusy, setIsBusy] = useState<boolean>(false);
|
||||
// translators: %s is the name of the automation.
|
||||
const title = sprintf(
|
||||
__('Deactivate the "%s" automation?', 'mailpoet'),
|
||||
workflowName,
|
||||
automationName,
|
||||
);
|
||||
|
||||
return (
|
||||
@ -79,7 +79,7 @@ export function DeactivateModal({
|
||||
<li>
|
||||
<label
|
||||
className={
|
||||
selected === WorkflowStatus.DEACTIVATING
|
||||
selected === AutomationStatus.DEACTIVATING
|
||||
? 'mailpoet-automation-option active'
|
||||
: 'mailpoet-automation-option'
|
||||
}
|
||||
@ -89,8 +89,8 @@ export function DeactivateModal({
|
||||
type="radio"
|
||||
disabled={isBusy}
|
||||
name="deactivation-method"
|
||||
checked={selected === WorkflowStatus.DEACTIVATING}
|
||||
onChange={() => setSelected(WorkflowStatus.DEACTIVATING)}
|
||||
checked={selected === AutomationStatus.DEACTIVATING}
|
||||
onChange={() => setSelected(AutomationStatus.DEACTIVATING)}
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
@ -107,7 +107,7 @@ export function DeactivateModal({
|
||||
<li>
|
||||
<label
|
||||
className={
|
||||
selected === WorkflowStatus.DRAFT
|
||||
selected === AutomationStatus.DRAFT
|
||||
? 'mailpoet-automation-option active'
|
||||
: 'mailpoet-automation-option'
|
||||
}
|
||||
@ -117,8 +117,8 @@ export function DeactivateModal({
|
||||
type="radio"
|
||||
disabled={isBusy}
|
||||
name="deactivation-method"
|
||||
checked={selected === WorkflowStatus.DRAFT}
|
||||
onChange={() => setSelected(WorkflowStatus.DRAFT)}
|
||||
checked={selected === AutomationStatus.DRAFT}
|
||||
onChange={() => setSelected(AutomationStatus.DRAFT)}
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
@ -140,7 +140,7 @@ export function DeactivateModal({
|
||||
onClick={() => {
|
||||
setIsBusy(true);
|
||||
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 { __, sprintf } from '@wordpress/i18n';
|
||||
import { storeName } from '../../store';
|
||||
import { WorkflowStatus } from '../../../listing/workflow';
|
||||
import { AutomationStatus } from '../../../listing/automation';
|
||||
import { MailPoet } from '../../../../mailpoet';
|
||||
|
||||
function PreStep({ onClose }): JSX.Element {
|
||||
@ -58,9 +58,9 @@ function PreStep({ onClose }): JSX.Element {
|
||||
}
|
||||
|
||||
function PostStep({ onClose }): JSX.Element {
|
||||
const { workflow } = useSelect(
|
||||
const { automation } = useSelect(
|
||||
(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__section">
|
||||
{sprintf(__('"%s" is now live.', 'mailpoet'), workflow.name)}
|
||||
{sprintf(__('"%s" is now live.', 'mailpoet'), automation.name)}
|
||||
</div>
|
||||
<p>
|
||||
<strong>{__('What’s next?', 'mailpoet')}</strong>
|
||||
@ -100,29 +100,31 @@ function PostStep({ onClose }): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export function ActivatePanel({ onClose }): JSX.Element {
|
||||
const { workflow, errors } = useSelect(
|
||||
export function ActivatePanel(): JSX.Element {
|
||||
const { automation, errors } = useSelect(
|
||||
(select) => ({
|
||||
errors: select(storeName).getErrors(),
|
||||
workflow: select(storeName).getWorkflowData(),
|
||||
automation: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const { closeActivationPanel } = useDispatch(storeName);
|
||||
|
||||
useEffect(() => {
|
||||
if (errors) {
|
||||
onClose();
|
||||
closeActivationPanel();
|
||||
}
|
||||
}, [errors, onClose]);
|
||||
}, [errors, closeActivationPanel]);
|
||||
|
||||
if (errors) {
|
||||
return null;
|
||||
}
|
||||
const isActive = workflow.status === WorkflowStatus.ACTIVE;
|
||||
const isActive = automation.status === AutomationStatus.ACTIVE;
|
||||
return (
|
||||
<div className="mailpoet-automation-activate-panel">
|
||||
{isActive && <PostStep onClose={onClose} />}
|
||||
{!isActive && <PreStep onClose={onClose} />}
|
||||
{isActive && <PostStep onClose={closeActivationPanel} />}
|
||||
{!isActive && <PreStep onClose={closeActivationPanel} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -4,10 +4,10 @@ import { __ } from '@wordpress/i18n';
|
||||
import { storeName } from '../../../store';
|
||||
import { TrashButton } from '../../actions/trash-button';
|
||||
|
||||
export function WorkflowSidebar(): JSX.Element {
|
||||
const { workflowData } = useSelect(
|
||||
export function AutomationSidebar(): JSX.Element {
|
||||
const { automationData } = useSelect(
|
||||
(select) => ({
|
||||
workflowData: select(storeName).getWorkflowData(),
|
||||
automationData: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -22,30 +22,30 @@ export function WorkflowSidebar(): JSX.Element {
|
||||
<PanelBody title={__('Automation details', 'mailpoet')} initialOpen>
|
||||
<PanelRow>
|
||||
<strong>Date added</strong>{' '}
|
||||
{new Date(Date.parse(workflowData.created_at)).toLocaleDateString(
|
||||
{new Date(Date.parse(automationData.created_at)).toLocaleDateString(
|
||||
undefined,
|
||||
dateOptions,
|
||||
)}
|
||||
</PanelRow>
|
||||
<PanelRow>
|
||||
<strong>Activated</strong>{' '}
|
||||
{workflowData.status === 'active' &&
|
||||
new Date(Date.parse(workflowData.updated_at)).toLocaleDateString(
|
||||
{automationData.status === 'active' &&
|
||||
new Date(Date.parse(automationData.updated_at)).toLocaleDateString(
|
||||
undefined,
|
||||
dateOptions,
|
||||
)}
|
||||
{workflowData.status !== 'active' &&
|
||||
workflowData.activated_at &&
|
||||
new Date(Date.parse(workflowData.activated_at)).toLocaleDateString(
|
||||
{automationData.status !== 'active' &&
|
||||
automationData.activated_at &&
|
||||
new Date(Date.parse(automationData.activated_at)).toLocaleDateString(
|
||||
undefined,
|
||||
dateOptions,
|
||||
)}
|
||||
{workflowData.status !== 'active' && !workflowData.activated_at && (
|
||||
{automationData.status !== 'active' && !automationData.activated_at && (
|
||||
<span className="mailpoet-deactive">Not activated yet.</span>
|
||||
)}
|
||||
</PanelRow>
|
||||
<PanelRow>
|
||||
<strong>Author</strong> {workflowData.author.name}
|
||||
<strong>Author</strong> {automationData.author.name}
|
||||
</PanelRow>
|
||||
<PanelRow>
|
||||
<TrashButton />
|
@ -1,7 +1,7 @@
|
||||
import { Button } from '@wordpress/components';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store';
|
||||
import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
|
||||
|
||||
// See:
|
||||
// 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 {
|
||||
const { openSidebar } = useDispatch(storeName);
|
||||
const openWorkflowSettings = () => openSidebar(workflowSidebarKey);
|
||||
const openAutomationSettings = () => openSidebar(automationSidebarKey);
|
||||
const openStepSettings = () => openSidebar(stepSidebarKey);
|
||||
|
||||
const [workflowAriaLabel, workflowActiveClass] =
|
||||
sidebarKey === workflowSidebarKey
|
||||
const [automationAriaLabel, automationActiveClass] =
|
||||
sidebarKey === automationSidebarKey
|
||||
? [__('Automation (selected)', 'mailpoet'), 'is-active']
|
||||
: [__('Automation', 'mailpoet'), ''];
|
||||
|
||||
@ -30,9 +30,9 @@ export function Header({ sidebarKey }: Props): JSX.Element {
|
||||
<ul>
|
||||
<li>
|
||||
<Button
|
||||
onClick={openWorkflowSettings}
|
||||
className={`edit-site-sidebar__panel-tab ${workflowActiveClass}`}
|
||||
aria-label={workflowAriaLabel}
|
||||
onClick={openAutomationSettings}
|
||||
className={`edit-site-sidebar__panel-tab ${automationActiveClass}`}
|
||||
aria-label={automationAriaLabel}
|
||||
data-label={__('Automation', 'mailpoet')}
|
||||
>
|
||||
{__('Automation', 'mailpoet')}
|
||||
|
@ -10,8 +10,8 @@ import {
|
||||
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
|
||||
import { Header } from './header';
|
||||
import { StepSidebar } from './step';
|
||||
import { WorkflowSidebar } from './workflow';
|
||||
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store';
|
||||
import { AutomationSidebar } from './automation';
|
||||
import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
|
||||
|
||||
// See:
|
||||
// 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>;
|
||||
|
||||
export function Sidebar(props: Props): JSX.Element {
|
||||
const { keyboardShortcut, sidebarKey, showIconLabels, workflowName } =
|
||||
const { keyboardShortcut, sidebarKey, showIconLabels, automationName } =
|
||||
useSelect(
|
||||
(select) => ({
|
||||
keyboardShortcut: select(
|
||||
@ -36,9 +36,9 @@ export function Sidebar(props: Props): JSX.Element {
|
||||
),
|
||||
sidebarKey:
|
||||
select(interfaceStore).getActiveComplementaryArea(storeName) ??
|
||||
workflowSidebarKey,
|
||||
automationSidebarKey,
|
||||
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}
|
||||
className="edit-site-sidebar mailpoet-automation-sidebar"
|
||||
panelClassName="edit-site-sidebar"
|
||||
smallScreenTitle={workflowName || __('(no title)', 'mailpoet')}
|
||||
smallScreenTitle={automationName || __('(no title)', 'mailpoet')}
|
||||
scope={storeName}
|
||||
toggleShortcut={keyboardShortcut}
|
||||
isActiveByDefault={sidebarActiveByDefault}
|
||||
showIconLabels={showIconLabels}
|
||||
{...props}
|
||||
>
|
||||
{sidebarKey === workflowSidebarKey && <WorkflowSidebar />}
|
||||
{sidebarKey === automationSidebarKey && <AutomationSidebar />}
|
||||
{sidebarKey === stepSidebarKey && <StepSidebar />}
|
||||
</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 { EditorNotices } from './components/notices';
|
||||
import { Sidebar } from './components/sidebar';
|
||||
import { Workflow } from './components/workflow';
|
||||
import { Automation } from './components/automation';
|
||||
import { createStore, storeName } from './store';
|
||||
import { initializeApi } from '../api';
|
||||
import { initialize as initializeCoreIntegration } from '../integrations/core';
|
||||
import { initialize as initializeMailPoetIntegration } from '../integrations/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 { ActivatePanel } from './components/panel/activate-panel';
|
||||
import { registerTranslations } from '../i18n';
|
||||
import { WorkflowStatus } from '../listing/workflow';
|
||||
import { AutomationStatus } from '../listing/automation';
|
||||
|
||||
// See:
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Show temporary message that active workflows cant be updated
|
||||
* Show temporary message that active automations cant be updated
|
||||
*
|
||||
* see MAILPOET-4744
|
||||
*/
|
||||
function updatingActiveWorkflowNotPossible() {
|
||||
const workflow = globalSelect(storeName).getWorkflowData();
|
||||
function updatingActiveAutomationNotPossible() {
|
||||
const automation = globalSelect(storeName).getAutomationData();
|
||||
if (
|
||||
![WorkflowStatus.ACTIVE, WorkflowStatus.DEACTIVATING].includes(
|
||||
workflow.status,
|
||||
![AutomationStatus.ACTIVE, AutomationStatus.DEACTIVATING].includes(
|
||||
automation.status,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (workflow.stats.totals.in_progress === 0) {
|
||||
if (automation.stats.totals.in_progress === 0) {
|
||||
return;
|
||||
}
|
||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||
void createNotice(
|
||||
'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',
|
||||
),
|
||||
{
|
||||
@ -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 {
|
||||
const {
|
||||
isFullscreenActive,
|
||||
isInserterOpened,
|
||||
isActivationPanelOpened,
|
||||
isSidebarOpened,
|
||||
showIconLabels,
|
||||
workflow,
|
||||
automation,
|
||||
} = useSelect(
|
||||
(select) => ({
|
||||
isFullscreenActive: select(storeName).isFeatureActive('fullscreenMode'),
|
||||
isInserterOpened: select(storeName).isInserterSidebarOpened(),
|
||||
isSidebarOpened: select(storeName).isSidebarOpened(),
|
||||
isActivationPanelOpened: select(storeName).isActivationPanelOpened(),
|
||||
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
||||
workflow: select(storeName).getWorkflowData(),
|
||||
automation: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const [showActivatePanel, setShowActivatePanel] = useState(false);
|
||||
const [isBooting, setIsBooting] = useState(true);
|
||||
|
||||
useConfirmUnsaved();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBooting) {
|
||||
return;
|
||||
}
|
||||
updatingActiveWorkflowNotPossible();
|
||||
updatingActiveAutomationNotPossible();
|
||||
setIsBooting(false);
|
||||
}, [isBooting]);
|
||||
const className = classnames('interface-interface-skeleton', {
|
||||
@ -104,17 +126,13 @@ function Editor(): JSX.Element {
|
||||
'show-icon-labels': showIconLabels,
|
||||
});
|
||||
|
||||
if (workflow.status === 'trash') {
|
||||
if (automation.status === 'trash') {
|
||||
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
||||
[LISTING_NOTICE_PARAMETERS.workflowHadBeenDeleted]: workflow.id,
|
||||
[LISTING_NOTICE_PARAMETERS.automationHadBeenDeleted]: automation.id,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
const toggleActivatePanel = () => {
|
||||
setShowActivatePanel(!showActivatePanel);
|
||||
};
|
||||
|
||||
return (
|
||||
<ShortcutProvider>
|
||||
<SlotFillProvider>
|
||||
@ -135,16 +153,11 @@ function Editor(): JSX.Element {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
header={
|
||||
<Header
|
||||
showInserterToggle={showInserterSidebar}
|
||||
toggleActivatePanel={toggleActivatePanel}
|
||||
/>
|
||||
}
|
||||
header={<Header showInserterToggle={showInserterSidebar} />}
|
||||
content={
|
||||
<>
|
||||
<EditorNotices />
|
||||
<Workflow />
|
||||
<Automation />
|
||||
</>
|
||||
}
|
||||
sidebar={<ComplementaryArea.Slot scope={storeName} />}
|
||||
@ -152,7 +165,7 @@ function Editor(): JSX.Element {
|
||||
showInserterSidebar && isInserterOpened ? <InserterSidebar /> : null
|
||||
}
|
||||
/>
|
||||
{showActivatePanel && <ActivatePanel onClose={toggleActivatePanel} />}
|
||||
{isActivationPanelOpened && <ActivatePanel />}
|
||||
<Popover.Slot />
|
||||
</SlotFillProvider>
|
||||
</ShortcutProvider>
|
||||
|
@ -7,14 +7,42 @@ import { store as preferencesStore } from '@wordpress/preferences';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import { storeName } from './constants';
|
||||
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 { WorkflowStatus } from '../../listing/workflow';
|
||||
import { AutomationStatus } from '../../listing/automation';
|
||||
|
||||
export const openSidebar =
|
||||
(key) =>
|
||||
({ registry }) =>
|
||||
const trackErrors = (errors) => {
|
||||
if (!errors?.steps) {
|
||||
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);
|
||||
};
|
||||
|
||||
export const closeSidebar =
|
||||
() =>
|
||||
@ -46,23 +74,23 @@ export function selectStep(value) {
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function setWorkflowName(name) {
|
||||
const workflow = select(storeName).getWorkflowData();
|
||||
export function setAutomationName(name) {
|
||||
const automation = select(storeName).getAutomationData();
|
||||
return {
|
||||
type: 'UPDATE_WORKFLOW',
|
||||
workflow: {
|
||||
...workflow,
|
||||
type: 'UPDATE_AUTOMATION',
|
||||
automation: {
|
||||
...automation,
|
||||
name,
|
||||
},
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function* save() {
|
||||
const workflow = select(storeName).getWorkflowData();
|
||||
const automation = select(storeName).getAutomationData();
|
||||
const data = yield apiFetch({
|
||||
path: `/workflows/${workflow.id}`,
|
||||
path: `/automations/${automation.id}`,
|
||||
method: 'PUT',
|
||||
data: { ...workflow },
|
||||
data: { ...automation },
|
||||
});
|
||||
|
||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||
@ -78,23 +106,23 @@ export function* save() {
|
||||
|
||||
return {
|
||||
type: 'SAVE',
|
||||
workflow: data?.data ?? workflow,
|
||||
automation: data?.data ?? automation,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function* activate() {
|
||||
const workflow = select(storeName).getWorkflowData();
|
||||
const automation = select(storeName).getAutomationData();
|
||||
const data = yield apiFetch({
|
||||
path: `/workflows/${workflow.id}`,
|
||||
path: `/automations/${automation.id}`,
|
||||
method: 'PUT',
|
||||
data: {
|
||||
...workflow,
|
||||
status: WorkflowStatus.ACTIVE,
|
||||
...automation,
|
||||
status: AutomationStatus.ACTIVE,
|
||||
},
|
||||
});
|
||||
|
||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||
if (data?.data.status === WorkflowStatus.ACTIVE) {
|
||||
if (data?.data.status === AutomationStatus.ACTIVE) {
|
||||
void createNotice(
|
||||
'success',
|
||||
__('Well done! Automation is now activated!', 'mailpoet'),
|
||||
@ -102,29 +130,33 @@ export function* activate() {
|
||||
type: 'snackbar',
|
||||
},
|
||||
);
|
||||
MailPoet.trackEvent('Automations > Automation activated');
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'ACTIVATE',
|
||||
workflow: data?.data ?? workflow,
|
||||
automation: data?.data ?? automation,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function* deactivate(deactivateWorkflowRuns = true) {
|
||||
const workflow = select(storeName).getWorkflowData();
|
||||
export function* deactivate(deactivateAutomationRuns = true) {
|
||||
const automation = select(storeName).getAutomationData();
|
||||
const data = yield apiFetch({
|
||||
path: `/workflows/${workflow.id}`,
|
||||
path: `/automations/${automation.id}`,
|
||||
method: 'PUT',
|
||||
data: {
|
||||
...workflow,
|
||||
status: deactivateWorkflowRuns
|
||||
? WorkflowStatus.DRAFT
|
||||
: WorkflowStatus.DEACTIVATING,
|
||||
...automation,
|
||||
status: deactivateAutomationRuns
|
||||
? AutomationStatus.DRAFT
|
||||
: AutomationStatus.DEACTIVATING,
|
||||
},
|
||||
});
|
||||
|
||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||
if (deactivateWorkflowRuns && data?.data.status === WorkflowStatus.DRAFT) {
|
||||
if (
|
||||
deactivateAutomationRuns &&
|
||||
data?.data.status === AutomationStatus.DRAFT
|
||||
) {
|
||||
void createNotice(
|
||||
'success',
|
||||
__('Automation is now deactivated!', 'mailpoet'),
|
||||
@ -132,10 +164,14 @@ export function* deactivate(deactivateWorkflowRuns = true) {
|
||||
type: 'snackbar',
|
||||
},
|
||||
);
|
||||
|
||||
MailPoet.trackEvent('Automations > Automation deactivated', {
|
||||
type: 'immediate',
|
||||
});
|
||||
}
|
||||
if (
|
||||
!deactivateWorkflowRuns &&
|
||||
data?.data.status === WorkflowStatus.DEACTIVATING
|
||||
!deactivateAutomationRuns &&
|
||||
data?.data.status === AutomationStatus.DEACTIVATING
|
||||
) {
|
||||
void createNotice(
|
||||
'success',
|
||||
@ -147,36 +183,39 @@ export function* deactivate(deactivateWorkflowRuns = true) {
|
||||
type: 'snackbar',
|
||||
},
|
||||
);
|
||||
MailPoet.trackEvent('Automations > Automation deactivated', {
|
||||
type: 'continuous',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'DEACTIVATE',
|
||||
workflow: data?.data ?? workflow,
|
||||
automation: data?.data ?? automation,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function* trash(onTrashed: () => void = undefined) {
|
||||
const workflow = select(storeName).getWorkflowData();
|
||||
const automation = select(storeName).getAutomationData();
|
||||
const data = yield apiFetch({
|
||||
path: `/workflows/${workflow.id}`,
|
||||
path: `/automations/${automation.id}`,
|
||||
method: 'PUT',
|
||||
data: {
|
||||
...workflow,
|
||||
status: WorkflowStatus.TRASH,
|
||||
...automation,
|
||||
status: AutomationStatus.TRASH,
|
||||
},
|
||||
});
|
||||
|
||||
onTrashed?.();
|
||||
|
||||
if (data?.status === WorkflowStatus.TRASH) {
|
||||
if (data?.status === AutomationStatus.TRASH) {
|
||||
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
||||
[LISTING_NOTICE_PARAMETERS.workflowDeleted]: workflow.id,
|
||||
[LISTING_NOTICE_PARAMETERS.automationDeleted]: automation.id,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'TRASH',
|
||||
workflow: data?.data ?? workflow,
|
||||
automation: data?.data ?? automation,
|
||||
} as const;
|
||||
}
|
||||
|
||||
@ -197,6 +236,7 @@ export function updateStepArgs(stepId, name, value) {
|
||||
}
|
||||
|
||||
export function setErrors(errors) {
|
||||
trackErrors(errors);
|
||||
return {
|
||||
type: 'SET_ERRORS',
|
||||
errors,
|
||||
|
@ -1,4 +1,4 @@
|
||||
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';
|
||||
|
@ -5,12 +5,15 @@ declare let window: AutomationEditorWindow;
|
||||
export const getInitialState = (): State => ({
|
||||
context: { ...window.mailpoet_automation_context },
|
||||
stepTypes: {},
|
||||
workflowData: { ...window.mailpoet_automation_workflow },
|
||||
workflowSaved: true,
|
||||
automationData: { ...window.mailpoet_automation },
|
||||
automationSaved: true,
|
||||
selectedStep: undefined,
|
||||
inserterSidebar: {
|
||||
isOpened: false,
|
||||
},
|
||||
activationPanel: {
|
||||
isOpened: false,
|
||||
},
|
||||
inserterPopover: undefined,
|
||||
errors: undefined,
|
||||
});
|
||||
|
@ -3,6 +3,14 @@ import { State } from './types';
|
||||
|
||||
export function reducer(state: State, action: Action): State {
|
||||
switch (action.type) {
|
||||
case 'SET_ACTIVATION_PANEL_VISIBILITY':
|
||||
return {
|
||||
...state,
|
||||
activationPanel: {
|
||||
...state.activationPanel,
|
||||
isOpened: action.value,
|
||||
},
|
||||
};
|
||||
case 'TOGGLE_INSERTER_SIDEBAR':
|
||||
return {
|
||||
...state,
|
||||
@ -21,35 +29,35 @@ export function reducer(state: State, action: Action): State {
|
||||
...state,
|
||||
selectedStep: action.value,
|
||||
};
|
||||
case 'UPDATE_WORKFLOW':
|
||||
case 'UPDATE_AUTOMATION':
|
||||
return {
|
||||
...state,
|
||||
workflowData: action.workflow,
|
||||
workflowSaved: false,
|
||||
automationData: action.automation,
|
||||
automationSaved: false,
|
||||
};
|
||||
case 'SAVE':
|
||||
return {
|
||||
...state,
|
||||
workflowData: action.workflow,
|
||||
workflowSaved: true,
|
||||
automationData: action.automation,
|
||||
automationSaved: true,
|
||||
};
|
||||
case 'ACTIVATE':
|
||||
return {
|
||||
...state,
|
||||
workflowData: action.workflow,
|
||||
workflowSaved: true,
|
||||
automationData: action.automation,
|
||||
automationSaved: true,
|
||||
};
|
||||
case 'DEACTIVATE':
|
||||
return {
|
||||
...state,
|
||||
workflowData: action.workflow,
|
||||
workflowSaved: true,
|
||||
automationData: action.automation,
|
||||
automationSaved: true,
|
||||
};
|
||||
case 'TRASH':
|
||||
return {
|
||||
...state,
|
||||
workflowData: action.workflow,
|
||||
workflowSaved: true,
|
||||
automationData: action.automation,
|
||||
automationSaved: true,
|
||||
};
|
||||
case 'REGISTER_STEP_TYPE':
|
||||
return {
|
||||
@ -60,7 +68,7 @@ export function reducer(state: State, action: Action): State {
|
||||
},
|
||||
};
|
||||
case 'UPDATE_STEP_ARGS': {
|
||||
const prevArgs = state.workflowData.steps[action.stepId].args ?? {};
|
||||
const prevArgs = state.automationData.steps[action.stepId].args ?? {};
|
||||
|
||||
const value =
|
||||
typeof action.value === 'function'
|
||||
@ -74,7 +82,7 @@ export function reducer(state: State, action: Action): State {
|
||||
)
|
||||
: { ...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(
|
||||
({ step_id }) => step_id !== action.stepId,
|
||||
@ -82,14 +90,14 @@ export function reducer(state: State, action: Action): State {
|
||||
|
||||
return {
|
||||
...state,
|
||||
workflowData: {
|
||||
...state.workflowData,
|
||||
automationData: {
|
||||
...state.automationData,
|
||||
steps: {
|
||||
...state.workflowData.steps,
|
||||
...state.automationData.steps,
|
||||
[action.stepId]: step,
|
||||
},
|
||||
},
|
||||
workflowSaved: false,
|
||||
automationSaved: false,
|
||||
selectedStep: step,
|
||||
errors:
|
||||
stepErrors.length > 0
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { storeName } from './constants';
|
||||
import { StepType } from './types';
|
||||
|
||||
export const registerStepType = (stepType: StepType): void => {
|
||||
dispatch(storeName).registerStepType(stepType);
|
||||
dispatch(storeName).registerStepType(
|
||||
Hooks.applyFilters('mailpoet.automation.register_step_type', stepType),
|
||||
);
|
||||
};
|
||||
|
@ -4,7 +4,7 @@ import { store as preferencesStore } from '@wordpress/preferences';
|
||||
import { storeName } from './constants';
|
||||
import { Context, Errors, Feature, State, StepErrors, StepType } from './types';
|
||||
import { Item } from '../components/inserter/item';
|
||||
import { Step, Workflow } from '../components/workflow/types';
|
||||
import { Step, Automation } from '../components/automation/types';
|
||||
|
||||
export const isFeatureActive = createRegistrySelector(
|
||||
(select) =>
|
||||
@ -21,6 +21,10 @@ export function isInserterSidebarOpened(state: State): boolean {
|
||||
return state.inserterSidebar.isOpened;
|
||||
}
|
||||
|
||||
export function isActivationPanelOpened(state: State): boolean {
|
||||
return state.activationPanel.isOpened;
|
||||
}
|
||||
|
||||
export function getContext(state: State): Context {
|
||||
return state.context;
|
||||
}
|
||||
@ -54,18 +58,22 @@ export function getInserterPopover(
|
||||
return state.inserterPopover;
|
||||
}
|
||||
|
||||
export function getWorkflowData(state: State): Workflow {
|
||||
return state.workflowData;
|
||||
export function getAutomationData(state: State): Automation {
|
||||
return state.automationData;
|
||||
}
|
||||
|
||||
export function getWorkflowSaved(state: State): boolean {
|
||||
return state.workflowSaved;
|
||||
export function getAutomationSaved(state: State): boolean {
|
||||
return state.automationSaved;
|
||||
}
|
||||
|
||||
export function getSelectedStep(state: State): Step | undefined {
|
||||
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 {
|
||||
return state.stepTypes[key] ?? undefined;
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { Step, Workflow } from '../components/workflow/types';
|
||||
import { Step, Automation } from '../components/automation/types';
|
||||
|
||||
export interface AutomationEditorWindow extends Window {
|
||||
mailpoet_automation_context: Context;
|
||||
mailpoet_automation_workflow: Workflow;
|
||||
mailpoet_automation: Automation;
|
||||
}
|
||||
|
||||
export type Context = {
|
||||
@ -48,12 +48,15 @@ export type Errors = {
|
||||
export type State = {
|
||||
context: Context;
|
||||
stepTypes: Record<string, StepType>;
|
||||
workflowData: Workflow;
|
||||
workflowSaved: boolean;
|
||||
automationData: Automation;
|
||||
automationSaved: boolean;
|
||||
selectedStep: Step | undefined;
|
||||
inserterSidebar: {
|
||||
isOpened: boolean;
|
||||
};
|
||||
activationPanel: {
|
||||
isOpened: boolean;
|
||||
};
|
||||
inserterPopover?: {
|
||||
anchor: HTMLElement;
|
||||
type: 'steps' | 'triggers';
|
||||
|
@ -19,7 +19,7 @@ export const step: StepType = {
|
||||
foreground: '#7F54B3',
|
||||
background: '#f7edf7',
|
||||
description: __(
|
||||
'Wait some time before proceeding with the steps below',
|
||||
'Wait some time before proceeding with the steps below.',
|
||||
'mailpoet',
|
||||
),
|
||||
subtitle: (data): string => {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user