From 20e03094895ff4175b319cbc97f26711b9a4f939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Tonon?= Date: Thu, 5 Oct 2023 14:43:25 +0200 Subject: [PATCH 01/39] Process Bundle Ui v2 initial commit --- README.md | 49 +--- composer.json | 71 +---- config/routes.yaml | 4 - config/services.yaml | 10 +- public/logo.jpg | Bin 0 -> 14990 bytes src/Admin/Field/EnumField.php | 21 ++ src/Admin/Field/LogLevelField.php | 21 ++ src/CleverAgeProcessUiBundle.php | 14 +- src/Command/PurgeProcessExecution.php | 88 ------ src/Command/UserCreateCommand.php | 58 ++-- .../Admin/LogRecordCrudController.php | 73 +++++ .../Admin/Process/ExecuteAction.php | 33 +++ src/Controller/Admin/Process/ListAction.php | 26 ++ .../Admin/Process/UploadAndExecuteAction.php | 75 ++++++ .../Admin/ProcessDashboardController.php | 57 ++++ .../Admin/ProcessExecutionCrudController.php | 131 +++++++++ .../Admin/Security/LoginController.php | 24 ++ .../Admin/Security/LogoutController.php | 21 ++ src/Controller/Admin/UserCrudController.php | 91 +++++++ src/Controller/Crud/ProcessCrudController.php | 141 ---------- .../Crud/ProcessExecutionCrudController.php | 146 ---------- src/Controller/Crud/UserCrudController.php | 128 --------- src/Controller/DashboardController.php | 44 --- src/Controller/SecurityController.php | 46 ---- .../CleverAgeProcessUiExtension.php | 60 ++++- ...ompilerPass.php => RegisterLogHandler.php} | 11 +- src/DependencyInjection/Configuration.php | 34 ++- src/Entity/.gitignore | 0 src/Entity/Enum/ProcessExecutionStatus.php | 12 + src/Entity/LogRecord.php | 59 ++++ src/Entity/Process.php | 113 -------- src/Entity/ProcessExecution.php | 253 +++--------------- src/Entity/ProcessExecutionLogRecord.php | 88 ------ src/Entity/User.php | 44 +-- src/Event/IncrementReportInfoEvent.php | 28 -- src/Event/SetReportInfoEvent.php | 35 --- .../Crud/ProcessCrudListener.php | 38 --- .../ProcessEventSubscriber.php | 171 +++--------- src/Form/Type/ProcessUploadFileType.php | 22 ++ .../ProcessConfigurationValueResolver.php | 24 ++ src/Manager/ProcessExecutionManager.php | 62 +++++ src/Manager/ProcessUiConfigurationManager.php | 97 ------- src/Message/LogIndexerHandler.php | 62 ----- src/Message/LogIndexerMessage.php | 46 ---- src/Message/ProcessExecuteHandler.php | 21 ++ src/Message/ProcessExecuteMessage.php | 12 + src/Message/ProcessRunHandler.php | 36 --- src/Message/ProcessRunMessage.php | 35 --- src/Migrations/Version20210903142035.php | 107 -------- src/Migrations/Version20211028081845.php | 36 --- src/Migrations/Version20231006111525.php | 87 ++++++ .../Handler/DoctrineProcessHandler.php | 77 ++++++ src/Monolog/Handler/ProcessHandler.php | 54 ++++ src/Monolog/Handler/ProcessLogHandler.php | 66 ----- src/Repository/.gitignore | 0 src/Repository/ProcessExecutionRepository.php | 70 ++--- src/Repository/ProcessRepository.php | 66 ----- src/Repository/UserRepository.php | 37 --- src/Security/LoginFormAuthAuthenticator.php | 62 ----- src/Twig/Extension/LogLevelExtension.php | 20 ++ src/Twig/Extension/MD5Extension.php | 19 ++ .../Extension/ProcessExecutionExtension.php | 30 +++ src/Twig/Runtime/LogLevelExtensionRuntime.php | 33 +++ src/Twig/Runtime/MD5ExtensionRuntime.php | 15 ++ .../ProcessExecutionExtensionRuntime.php | 37 +++ templates/admin/field/array.html.twig | 16 ++ templates/admin/field/enum.html.twig | 2 + templates/admin/field/log_level.html.twig | 1 + .../admin/field/process_source.html.twig | 1 + .../admin/field/process_target.html.twig | 1 + templates/admin/field/report.html.twig | 8 + templates/admin/login.html.twig | 1 + templates/admin/process/list.html.twig | 104 +++++++ .../process/upload_and_execute.html.twig | 23 ++ 74 files changed, 1561 insertions(+), 2147 deletions(-) delete mode 100755 config/routes.yaml create mode 100644 public/logo.jpg create mode 100644 src/Admin/Field/EnumField.php create mode 100644 src/Admin/Field/LogLevelField.php delete mode 100644 src/Command/PurgeProcessExecution.php create mode 100644 src/Controller/Admin/LogRecordCrudController.php create mode 100644 src/Controller/Admin/Process/ExecuteAction.php create mode 100644 src/Controller/Admin/Process/ListAction.php create mode 100644 src/Controller/Admin/Process/UploadAndExecuteAction.php create mode 100644 src/Controller/Admin/ProcessDashboardController.php create mode 100644 src/Controller/Admin/ProcessExecutionCrudController.php create mode 100644 src/Controller/Admin/Security/LoginController.php create mode 100644 src/Controller/Admin/Security/LogoutController.php create mode 100644 src/Controller/Admin/UserCrudController.php delete mode 100644 src/Controller/Crud/ProcessCrudController.php delete mode 100644 src/Controller/Crud/ProcessExecutionCrudController.php delete mode 100644 src/Controller/Crud/UserCrudController.php delete mode 100644 src/Controller/DashboardController.php delete mode 100644 src/Controller/SecurityController.php rename src/DependencyInjection/Compiler/{RegisterLogHandlerCompilerPass.php => RegisterLogHandler.php} (66%) delete mode 100644 src/Entity/.gitignore create mode 100644 src/Entity/Enum/ProcessExecutionStatus.php create mode 100644 src/Entity/LogRecord.php delete mode 100644 src/Entity/Process.php delete mode 100644 src/Entity/ProcessExecutionLogRecord.php delete mode 100644 src/Event/IncrementReportInfoEvent.php delete mode 100644 src/Event/SetReportInfoEvent.php delete mode 100644 src/EventSubscriber/Crud/ProcessCrudListener.php create mode 100644 src/Form/Type/ProcessUploadFileType.php create mode 100644 src/Http/ValueResolver/ProcessConfigurationValueResolver.php create mode 100644 src/Manager/ProcessExecutionManager.php delete mode 100644 src/Manager/ProcessUiConfigurationManager.php delete mode 100644 src/Message/LogIndexerHandler.php delete mode 100644 src/Message/LogIndexerMessage.php create mode 100644 src/Message/ProcessExecuteHandler.php create mode 100644 src/Message/ProcessExecuteMessage.php delete mode 100644 src/Message/ProcessRunHandler.php delete mode 100644 src/Message/ProcessRunMessage.php delete mode 100644 src/Migrations/Version20210903142035.php delete mode 100644 src/Migrations/Version20211028081845.php create mode 100644 src/Migrations/Version20231006111525.php create mode 100644 src/Monolog/Handler/DoctrineProcessHandler.php create mode 100644 src/Monolog/Handler/ProcessHandler.php delete mode 100644 src/Monolog/Handler/ProcessLogHandler.php delete mode 100644 src/Repository/.gitignore delete mode 100644 src/Repository/ProcessRepository.php delete mode 100644 src/Repository/UserRepository.php delete mode 100644 src/Security/LoginFormAuthAuthenticator.php create mode 100644 src/Twig/Extension/LogLevelExtension.php create mode 100644 src/Twig/Extension/MD5Extension.php create mode 100644 src/Twig/Extension/ProcessExecutionExtension.php create mode 100644 src/Twig/Runtime/LogLevelExtensionRuntime.php create mode 100644 src/Twig/Runtime/MD5ExtensionRuntime.php create mode 100644 src/Twig/Runtime/ProcessExecutionExtensionRuntime.php create mode 100644 templates/admin/field/array.html.twig create mode 100644 templates/admin/field/enum.html.twig create mode 100644 templates/admin/field/log_level.html.twig create mode 100644 templates/admin/field/process_source.html.twig create mode 100644 templates/admin/field/process_target.html.twig create mode 100644 templates/admin/field/report.html.twig create mode 100644 templates/admin/login.html.twig create mode 100644 templates/admin/process/list.html.twig create mode 100644 templates/admin/process/upload_and_execute.html.twig diff --git a/README.md b/README.md index 475ceab..7b2ff6a 100644 --- a/README.md +++ b/README.md @@ -7,54 +7,11 @@ A simple UX for cleverage/processbundle using EasyAdmin * Import routes ```yaml #config/routes.yaml -process-ui: - resource: '@CleverAgeProcessUiBundle/Resources/config/routes.yaml' +processui: + resource: '@CleverAgeProcessUiBundle/src/Controller' + type: attribute ``` * Run doctrine migration * Create an user using cleverage:process-ui:user-create console. Now you can access Process UI via http://your-domain.com/process - -**Indexing logs** - -You can index logs line into database to perform search on ****Process > History**** page. -See configuration section. - -When indexation is enabled you can perform it async. - -```yaml -#config/messenger.yaml -framework: - messenger: - transports: - log_index: 'doctrine://default' - - routing: - CleverAge\ProcessUiBundle\Message\LogIndexerMessage: log_index -``` - -Then you have to consume messages by running (use a supervisor to keep consumer alive) -``` -bin/console messenger:consume log_index --memory-limit=64M -``` - -See official symfony/messenger component documentations for more informations https://symfony.com/doc/current/messenger.html - -**Integrate CrudController** - -Of course you can integrate ProcessUI CRUD into your own easy admin Dashboard -```php - public function configureMenuItems(): iterable - { - /* ... your configuration */ - yield MenuItem::linkToCrud('History', null, ProcessExecution::class); - } -``` - -**Configuration** -```yaml -clever_age_process_ui: - index_logs: - enabled: false - level: ERROR #Minimum log level to index. Allowed values are DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY -``` diff --git a/composer.json b/composer.json index 152db2c..157e049 100644 --- a/composer.json +++ b/composer.json @@ -1,16 +1,9 @@ { - "name": "cleverage/process-ui-bundle", + "name": "cleverage/process-bundle-ui", "type": "symfony-bundle", "license": "MIT", "description": "UI for cleverage/process-bundle", - "minimum-stability": "dev", - "prefer-stable": true, "authors": [ - { - "name": "Baudouin Douliery", - "email": "bdouliery@clever-age.com", - "role": "Developer" - }, { "name": "Grégory Tonon", "email": "gtonon@clever-age.com", @@ -23,50 +16,19 @@ } ], "require": { - "php": ">=8.0", + "php": ">=8.2", "ext-ctype": "*", "ext-iconv": "*", - "cleverage/process-bundle": "^3.2", - "easycorp/easyadmin-bundle": "^4.0", - "ddtraceweb/monolog-parser": "^1.3", - "league/flysystem": "^2.2", - "composer/package-versions-deprecated": "^1.11", - "doctrine/doctrine-bundle": "^2.4", - "doctrine/doctrine-migrations-bundle": "^3.1", - "doctrine/orm": "^2.9", - "sensio/framework-extra-bundle": "^6.1", - "symfony/console": "^5.4", - "symfony/messenger": "^5.4", - "symfony/doctrine-messenger": "^5.4", - "symfony/filesystem": "^5.4", - "symfony/flex": "^1.3.1", - "symfony/form": "^5.4", - "symfony/framework-bundle": "^5.4", - "symfony/mime": "^5.4", - "symfony/proxy-manager-bridge": "^5.4", - "symfony/runtime": "^5.4", - "symfony/security-bundle": "^5.4", - "symfony/stopwatch": "^5.4", - "symfony/twig-bundle": "^5.4", - "symfony/validator": "^5.4", - "symfony/webpack-encore-bundle": "^1.11", - "symfony/yaml": "^5.4", - "twig/extra-bundle": "^2.12|^3.0", - "twig/intl-extra": "^3.3" - }, - "require-dev": { - "doctrine/doctrine-fixtures-bundle": "^3.4", - "friendsofphp/php-cs-fixer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1", - "phpstan/phpstan-doctrine": "^1.2", - "phpstan/phpstan-symfony": "^1.1", - "rector/rector": "^0.12.13", - "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.6", - "symfony/dotenv": "^5.4", - "symfony/maker-bundle": "^1.31", - "symfony/web-profiler-bundle": "^5.4" + "ext-pcntl": "*", + "cleverage/process-bundle": "dev-v4-dev", + "symfony/flex": "^2", + "symfony/orm-pack": "^v2.4", + "symfony/dotenv": "^6.3", + "symfony/uid": "^6.3", + "symfony/string": "^6.3", + "symfony/messenger": "^6.3", + "symfony/doctrine-messenger": "^6.3", + "easycorp/easyadmin-bundle": "^4.7" }, "config": { "optimize-autoloader": true, @@ -102,14 +64,5 @@ "post-update-cmd": [ "@auto-scripts" ] - }, - "conflict": { - "symfony/symfony": "*" - }, - "extra": { - "symfony": { - "allow-contrib": false, - "require": "5.4.*" - } } } diff --git a/config/routes.yaml b/config/routes.yaml deleted file mode 100755 index 8cfca5b..0000000 --- a/config/routes.yaml +++ /dev/null @@ -1,4 +0,0 @@ -_process-ui: - prefix: '/process' - resource: ../src/Controller/ - type: annotation \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index 3b371fb..039380d 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,15 +1,9 @@ parameters: - uploads_directory: '%kernel.project_dir%/public/uploads' - cleverage_processes: '%kernel.project_dir%/config/packages' - process_logs_dir: '%kernel.logs_dir%/process' - + upload_directory: '%kernel.project_dir%/var/storage/uploads' services: _defaults: autowire: true autoconfigure: true - bind: - $processLogDir: '%process_logs_dir%' - $indexLogs: '%clever_age_process_ui.index_logs.enabled%' CleverAge\ProcessUiBundle\: resource: '../src' @@ -17,4 +11,4 @@ services: - '../src/DependencyInjection/' - '../src/Entity/' - '../src/Tests/' - - '../src/Migrations' \ No newline at end of file + - '../src/Migrations' diff --git a/public/logo.jpg b/public/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d110ec94b9010885e8969a2f187e8ee2f572e06a GIT binary patch literal 14990 zcmdsdcUV(N7k3obE-um(1YA&16j%&B6nCXq2#6sdT?x{Qw9o>$Yk`0v%OcW*C}@B{ zU@?RMp{xi*x*>#;5D;Q$G4#;O7q{=b?)yI9f8X;>9=N|dbMKkooH=*OIkVlr{T}e; z71PV6fE_ykfE|J#V0&~&|K*DpU9MfXG`(zY@@v6cUzlI;&MyG~KmU-R>t^4cxM631 zV$Uc*2p|U72M`9x+=2z)0Ygl!0fH2KD=Zim66E^{|CyG0KS>7wXeyq!{N1vX0R|WD z{$y|=%=)w0`S?~(mS54s93igbxB9rZr0dg9{A;xpc=F+i>^!Kl~N z7OZ6rca(IK@qHL-6N8FB_oLR+qWnj%m~|Pgl)l>^!sb@Dx3`&qi-3K*_w3!Xd*9x@ zd-w0(cR*P3u<)Tn!pFtLMI_~A6%@XbJ#pfcvZl%@rSqpxoKQ7VJFl&)udjbnBEPmbrnx2>i%D+?VkZ6dv`E|CWUs$19pn+5E9w3-3T}? zP~y&=LcdtaZ+h6V=ZoDtckLAtl!Ct$n5WRrox6AM*}Zf3zFk6sZrmxf>kEc&^2iG$B~m77r$@lmN=yaaSIN~oim2Q@?O0e6^N4( z7`)JqPgeg&`bFGsLD^}MPeguAztIttiR@B8^3@mLh~AR7I{&ShLg2-12H>Eec&CVv z2*3zXav@ar=Raiq{{waaL|bE>FJ&-Eo(tJ1q)c%%&`43<_m&%Lm{91BtGhO1KVl(I zUJ5a|y7|$Y9gC2?mi;yRR{nP@xdlj+sG8Rt_VC)FCZo-ivm5GbIoG`HXZw}k$DGDy zkDIhEL29b|!X4s$w*jITdt5J{L{vqZ;uWUBJ8MN(jnqGQ{AuR;rg&jdfNql2 zSgnTYqwp|rg3sGBo9K!40nu8cktF9$X6n-@ouxveJ6(cD8!!=Tgvh?V9u#}ENz5XR zYANDwqUp9Ny&E}A4#vAwXS3Od2o@7z=EN@0_0|>~2oC{QuJH3m2^DBNkuUR~ma-2y z2<3H(H!urd*jHJH4dIHi))nC^z{*UE><3$g-zB2A0jb`ZhS#QlMzx0SjgQwWd!M|o zTkFzJEv@@^lZw?>BPEivL>KWu;ib*GADc80BgwnG3OpA58mh9rPchFSTxkAOAJ05EAxNbu_9!zT1b>;Qb$|7W{;hEqBu`lJXC$uIpqQaB0~AZ@i5`kQo6K^Pz&Yph z=5S|PiC~O#yX2?%;(K)0y_;-zQhx^k}B_+aBVHLX=R1m03Vd7 z4V9bEGpn{uY%J7F_x?s*y>6~Er+-#|-G=dO-(P$9Kkyy~4``v1f#hZ4RxiGP%juu; zWJC{fQ=M1LzkO1Hm567`eb_l$@E>pem)nROLoB5MaSS(S(ErpYChmxm_k4+Ik6GF% z#yZMf1o)!SuY_h2+9zgZOn2<0iX0z9NE|*8Vp}-nbajmwd!ace2JJTkm30YY*8kWs zqPmy5;@=KMdvtSnL_2K-?z+)o!qC8I`a?+UWO)8k!|d__&kZ9VKO^D9FeRQ-5!bP( zvv8vd=y1{lNF3FONBy|-!tZp2ZVKJpAu64HV`l6JslFq#7RW{e?^l7Yz19nlvOo~$ zt-7h&GUY_%nr3gvrIMv{7SA7;oAY@P{sh`drjECXzR(`b5;eEk**V z6DgWlZO|QluQY~k^lexAFpZG}wOoJ<811|M^zs|2(nD@dPuJzH7cy8=y3Ep6L^RmP z^GUGX!=uK*sP5`#zjOP?)hqy`*j7B+QFq{7sd+G$HM}`RLf_ZKKT#8V-ytJ+lU6~8 zHDyjMHPhw_Ti&*?y(zp3<>?CBZ`fmc{XhRh+`nXep+SdQfz^S)R(XYp=%32Y1Lt|J zt_&Q1ooJ$AWgGxJ+jTcmzv-PHv$IG3QTFwS#wDu98p5M*dM=Ye-G_I#?-L4fa#|r4 z1b!iI6~MEKtD{@;yErqm4*e@jfyEHlrfL=YTrF&ZynH5%ei+;GP!y<=t6}WB^TUo6;Ssq#MbX(g(%R$3t!>V@+5WO%gM70&8v0S%RLh=_`3` z*}5qvv#s6(zhtW)ORY03)z*}e%~a>h5uFN91+_-0kZBBph~IjdU9`MkKl*&&jH^o( zI@z6Ui*Khni8s^C>->)0{g(lCPoHoj$Nfe#RtXR1XqXOF4Fm_Oo^+cX3ysf@E^cp9 z->6}8_S3#oS2~NF(+vP;Pt&KE1oo@p^n}7GdU<0rZ8m)Pt28?wIoi8XNC<{;m+60z zQ0Uqz@jNBb)-Nm7YC1dHqs)%w>L@Ce4LJ2rm3%G-Wbd4vR^;K(@VX4Tkw_?qScW}n zUpB*2EMB+t5d3c48o*7POywq=$*+s8izyg#e(>Hjll{IF&D4!!_BMWl=a`yyZ;2pVu!|1n z@V=SI3Rfi}F0gO6>ztujD4bAWhg_6g`j`_;bXH%R8INQ-zr1Q2tv>MG|DyFpWx{R_~A3OcAcDAU6r@&{&}MGK#qT?6;q=>HwL(>y}4DY zJAG^AqUC1Rn|`=5ssw8Jq69RGtJ=`Lcz!j<*|Oz{yKM9RmFr(whoB$m)O-V7$)9p# zCMFwJa*Q6Y+rOet0xh3rOAOuCm~mE#YTpK&a?EGbq12A)JFiwp(X`GF5XuQ5{N2xR z`bQSWn-x|<^=66N22~|^Vbh1aGqS795KBS)%Gj8$ne`8qkqYmJzwsXaa!O(w5bYR% z^el$82|7g=YXKu>*X))ln&5DoEq;3e!rZCw&(moH9mrp7bUC7x6z(V8W&!C>G zMr5!1i9FkJ;!~7xs((nCn z+=CR-UdEU%i+esvA}o|NN%&DbqJvrHW{4Ji9yXaKoA$zys$kVZ24{cEvH~OHhl*(R z{qy6%`k>^c>GnC7BkqA(Mb7@_9U7*K14L22A9X~w&T)Jj@bFlz>n&9N%>&PVYt3IT zyCDxlh8fjETIZUV)>u)=8_AuqmcHs&mGV^7-i-|RltB2U?GD8H02`&J>mDD*IGjf} z#_Zoy#=`?dq=RO!DAadeUyUPtx#9tH=xC@<3|k4q(@mW5BPL@o$2V=Ys7NicdsS`$ zzp?b??QZ9^#po-&&;2p14-}hvXy6*9)fq!^pDXW<8FXxqfzCfn7h*;H{8xgX$3naZ z$SBbHPDKOJpv|e#gfsEuz>}!_JZG_Oz>Dgx7jN^UK}TH4L*kv=0Gs%Qao`l%;a#~E zSffd-(L`fDKFR8!*?!NX8=&Xx$zFJ70cyHWWQmI*8Vv_)iV$*WRem>mPm2_J100bLvGU`C)j)&ps`86*cknl4`ar8@I&-ri4x~HSvV5y z7mF8_`Lwv$h)hO(8eV)a#T+in96 z8R7Eh_!za_$w`JiSrxTrFPu+zagC>-Fsf0QY&?lnVMuB!y**{ON&u#1rz8WJlO=SQ zPA99_qqae4-xs1UWJ}ofcQl2*iYu8~afp_*?jVS;OxAJ@m=y-vFSbsF7RUQvA>{zQ9 z@|&TT7_o9<_oS_3wR56tX#P07W8#yk%(K|Agj{&~e9nw30lj9gre;440a1d!A1O)U z*D87uvjnTQ8>k90$PsG*U0NTFP7HgfLopk@8n}2asl=2?M+c|AUEvu90uPQ64m9OD z>`m|p|9IsW?XM1zO%kCjvXh2Gt|AVHgD1Fx+}-3+^0Ln<|VWpEaB7mW?j?y zBRR;x<)NXGBC%=1sUXnQtuNV{*_d0(fmb}*l)(%>DfyusWsY_)fKrbqLs#xL-%ost~7k$_< z>dBa)Nlli9b6HU0lM*xggo&a^Z}ic0UW6ub*6g^)EOCWk;G3;gxVEGiLqS=uSax;6 zZSn`z91J8yt8Z*d+rH@yS&!(KEUa}MccYv^TT?>Fi=DGAxNn*Z(|19E;(j94DU(-| z>b;$+@+Q-?Uz2S5rfK@6?a}>n`Gl^)){r6!#Mt>=2$|ZOo*7hXWsp27sbo2-mu zqcmO`RoJ|WSYC=tyZQOP-H|fFlAk8v?3;4kGqt&nC59aJ;nk=cj5C>BY^JL>&Rt%x zUd5bx{kr;~XQnT>AB}Zp6cJVs0;)Qzi1as?F%=sKNYqwFO_W4g8Lz+0*vgxwZX!mx zt$ti!0wS+mbgM7{&hnbicAW%V%q#Zl4RzAmlHuiMJ)SR`rLzp1lrYFJ%l7)jWMhHW zo$T}4=9#zo+}V#_*c~su!q+|Kg z*ZLxPrpSgB9Vmp@PwK1P3=PI9wGsNejQwO)qbxYJuxmevU;0$XUY1%Cn0sT%-?`RF zu#?C7l;Ftn3Qybb_r|p;Pp;9c@M4 zuyCTc(iAhl!AN(84+m{THKkSN+hk7SaCgqNH)~dL+UB}SVno^~(YJ_aO9t$j3Xgq- zz^DJ(J_WbC{o%kwV7RSSVlRoD804zfpTThqr#!G4T+~V|8sXZ97H=HrVU>lwic(Z+ zfK@x+*amP2vvQJdcPd`i@Jl9_u?7a|<3@DL1;i4w@7PfNom#(=p(Jh)=T-HjQBs~M z!6p?uh!jli>#dWR_YlDZwj3Um7LXLaE^11}L$NNTS z8C`yhh3%wU^zL%oqX&85^6Oj-L)f^de}PL)ZCb>_?$2XAjE!RRAvKquySyWeH3e!8ltvJO z6jB4%-mu=!Zv#%ofGkNFEw42(%4Iv4{RT=W{%Ff*L4TxCH`{iU2a>9e>@BRC^f%s; ziXm0I%NyV$ycZ?&1Ck4;LLp0MuuY=l2HthnMP5CMWxPAI)+6y(vnziQDTIQk>*s}n zY1(2P8jv@YX}WzP!Kz-fJS=j>d|kq(zHoqA{;1Y;gOW!fY4YkBITK5rR9-~fwT}8i z50{SC($bjC;Gn9xjE(wJlTPc`=ebJ@GdV#*@C$#ZyklzPXW?LV!8i&3#p?fF`sq%{ zsG$a!Tr$k<>m@ue-kP6g)!`7(*Pf>4$y(7636A~OtIIx-l~2{Lf5hfL@oTE|ch zq%_Q~?l2kkXP|oi=gsstP3*-S4t|#{GCB9)wGK&(QHqpGbMq=Uw znEQ*0@45RDECW?4$lQ64m??Jg0q-Ghypq zHMArH;j1+Up`p$Pk8IX@)45Pat$)@NE~*GyFrj-TG!4MYO#-97alhFH*V zP)e#}aq{q$k1gdTX|J{tUmvq>X4^Zv8r=2@nS6I{z^=@aB33jS*#mwmJg^w(Up-MJ9Tg9G1!@W5$@9SvL zAeB@~uXQpqZNx04DP+6QWC*hXdXisERy#H^%Qw}O<_58PWgHodR$IA#v0!sO=n5Yl z*x2R%dOqiJEMMt1;WLsuYXRIP*eRfZ36k~yG!J-%2Xae^C)q!(lFSGXtC8o=$ zp%{%{nMJUC);3*h99=q*4&|4XMjq2~0>JbG2Sw&*XY)Rx+TL%;~#5otIN(Hx` zMd1XUsnjt*85@=?To(4N@g5$GOcmPM-lroZs_avX9em`F(nY0r4ioC6B#QF9rA%7# zw56&QVxSZ~<@uat;=Qy)jmR_%LYp!e86q^?)}wdq-(2>)N2n@w^)m0FUCSY?qHRE* zbxgoTGxyd#&+FHH0=5D2hxh61; z9%V=Mm41kYd0TNsO|C@kOto73yV2QFz#xOHGQ2wYY`Vmgu>{_j$uPZvG@ICmsPed9*WGaD3v@#h90fV z&`pb$P&Hukx1>WlToh?9Lt+a#P;qx#(^E3!=<22Uq%QByGS^OnN|GyG0#CWE0TFAA z!dURgHOF;hEPDBuXt_`ID65*Aa`!b*z#>Q3raa#uO^s(wc?>t}_{r=<#Bz&o3i)bD zIUWWDK>YwG{!|j&J{A#!z_wD zav`7GPYYGu*Lx=n$4YM_$wfBK7>)2&@jbrWrp`8W&w8A}N)PGEZca7N7Aon9OPJ(n z(51~I8Y`ntA;!E`B6>p4&-o7LH&n~R=VyH~Hg@EFFsH+4u6-M+GqRJzV~gq=FIBdz z@>MF~NvYRZ1U;QjQkSGwQ~zw0Idk*Y!kGku2t)+i(ZnHG@xXtv?Y20{$^kY9~G12JndWn+9Di zvHIJ9cly)8zn!l?h96TKB-My&N6Eo(1TbvFcO&QmQNsban9dE z=yKJv7da?ni6)mz=Q)I6*|n#W8mn(y_?&|YSf_#SWpRByDOXH_%(v z>=P@vSaPW8928h%E*Q8S2MOFB63Vh4@K0L=d>L{|X6B(kT=feKI3wA=slF+w0INio zUg|0=oXY;{X8+J5Y02|N8_?)0hWeZeeaRN1=3`71FX;^bXyyjy+|y#j6}&!>JB_XCAt|t?rscIZTRL$uDg|WL&7&DIa*edH?6M>a#_u!X7ooHV=*tc1%dXm4b|LLu z_raQGUAGhHdX{gVcJA$&L=(Le0?bl1O(3&KaFp@lLeBc#$(#R4;Gg$g1=wNa8U&O$r1hz_y|g4mp&w_Z>C+9YqOd1@lqeU#lM*D6KR zx)R#G*4RerC89y}~IY<5X3GbU_b$i%b4G>}QytcFnCbC2MR`|OS6DbB>iT=yiyeOt`d!&& zXcuAg^&IzY3)!T|8s2Bk+rJHn*V*E9)@I9Q`={Y_a7ihY|LN4!vSv%aclW z5R2&KVcG2$x)5}R1YGh=3WahkeapeH7w3v-P^sGm&7^@zABIo*s#z1(r9MoNXIsiD z;V}f?OXq^2zswA54~0#cj&AE}X|1hqT54&1+e+4c72sF}kMhRh$8OE-iFTFO6!sci z0%Qwx?Daa3-sbb{tH6ODv$p|C#pn7YRUl5lT20eAwNnPVKd&ZZ8uTno z*W1}vCJ;7MosM~Q^&IMSC!pooCyBx(FB47SyG>8Etfldw{-tM-=nh6;PLKjACFtBB zC>`~wYk}?@lUNie zjEM38*}q*JAYm(qr>JtSnA0H!gthf=K6Vp}y-N+<{Xjqc7ZvT4#C)5H?$)uNmVpXJ zTVzu4GQ`@LQWrVIImUX`IAM8xP5X%3O{h)J*uYTwTuD;kZ06&dNP7Y=;#(^jNCIUzuJSJh}Vo z-Ct`R3F1gh;K->Eu$bur5M*qY>8j{)&Ahq7vC0IeToqK1n*fW%6+JdSE33wq&V1bW zAfFCB8rWLRe$3his9|F7BUaiHxb&i;!}|;KcV~=S2QAjstJxMsOvm?Zsrfu;nA-}A zs7bO+o5LxTo#4A3zxii#|B22YH!7s(antP|fV!wN5GN~{#hFfnvYMzH3+wZS@q9IV zyOP9;!YUIhCvjt_xS3nya#eW=l3z)$ydsENBkS)OeqTA9L+upIv8@(I) ze5^D7p3fHkZ7COWJhvmbUd`Gvtm;{{JoA?Nvt4%uBL7GVu`}zu3pty4PYL#B2fP{0 zX&Hq#*=yzqC(;l+D`i-0ew^gHRfBOpG~!!#riU%n0}YTG)=%7Y8XXLewO%%xSaeRs zj(UYl-1&)TQXHkh_rw|k1wUo`HG3knwQNRW<}yppBsk2T>bn^e`hg9R^J%VzQJM|v zl#!klRP|?H{2nX&gVZm;nC}rirZwK;HESehQRMwK(BF(!kUvESZg`?Q4V4K50&$U2 zA11{o_xfb2I^+<=A+4^rI$GGx-tC%APXd{X^`?jK^VcJ%vn3V1@)ZaDBGLOcoUeBp z`u7l3*dAq9P1tt;xfcYLna=)@y;`KIH=5B^<(%~(7@?=k;DvM!J%N#1mDWGrj=V5x z*OZG_5+3X0a$;~E*|Z@M@^nj79|IXSU={devPB>tBq6ec)5 zv6<4?GwM|8i&#kn)_c4dp0eyT8gdZ~-d0XFTr5W%tVvb3hRT$;2gkjvRB+CZuMCz3 zg)h#@Ch?6Pa`f|unLOB!2E3Q2e%gyvozG+{Js2#e#Xv3^#JQUW-6TGBK*YT=i*$c3`a@vb&bgt_XDR+QTFjVu@TY>bOjlmg;+U6F?>ZBh{%DMy0>d@U>`%{?0 z&QH$}nsEx_vK)({3g$5s;N0Cm+wdpSI4>oPk2UGRkKGu8XBXvFS0jw-#cB7_JV3&o z76w+(qVgHb<e)fPl`4cs zvyGd@q~XQakv9d7>EWE=tOHH`<1*N*DU^MRhnw0&g3{vjSJ2$-otU z#Dd0I`(a2vj^AN3WD*Hy+iA)5PJtwq+kOysCr~+>0yZ zWd!b_HhO)Sj{37X|AG`AH?C;MBn{48N9mnP~H*e+DBrj`<=Yj5InXY3;e^QZe{J0aW!|DMj-v2>be_KO`SeyQwm zHdkxLp@hyjQ^E?Q<1+KYaY_}6;!0ABw${*K?YKhD67EgLHekh)W4OhsL*8$k6qSxR zznN9pS7KN1PY(0zq*EQiK+9LKFkuQ-C++@X%t?~iINJ=1E{hqaS6PzkTf?BoFH$Ya z@&#V8fbA0Nvl7=IsxO1|~mzY)NG}~$yRm3*H zWcf(JC#E~vbDU^nfVPPmAl@f6mcACst=I<4DC$+PI|~^G%wlKRiSOg5Sbf0;N(eSP z2<;cVYDTmx|JIeDOukA35`7DrXmkD_hA?E~08)v^D}_FgN~iEdk=)_Jv{I-kbOObe zMQ#elFWPbqS-)2}^~L0xg$8VLmb+_WfnNW_!mOK}I_6S?t%*iNQb ze0)4ASN`~@mU)nvz?-$B*;O&jT1IIxTH;rWPZEr(R&pK!i+@uGV{-0P(#}v6z6|sSxHIKX%R$??Ea&gU|8nSZloL^b`yU(vqu!YzHoT4l2spZ|+ zz$wl0#wmDJk6A7sQfE?M7ztWqk=G--ay~Miy?1h1vLX5>riKQ-a#yh(G4C2E z8ZgqVl|g5X-Q3VM2vxI1r*&e=@3KSoq;=Q=1l9f+uT$LnDmgQBe|jcXxRa zV5y(>3w*#kHtoegsrPiFZIu6%Hb<6~8$DFZ4Tp!GV^$}Hek9Wjf8;=nXfo7~sJwn| zT^8zlb=X|^>Z=ImP2qYUqkx1q``vZCP}wtJoOH`ObN64t?pWjLWPTU$N~B{BQ^;g+t7w6i9zB4UlE)PX%VN0cA?{x<9HIfm*Pzg#P4 zDu?YY_i=zNlq$)lZ^@yoDI;@9GpcQ=4FyOf5;Y2|Rriw@EOAii7Y8C$N)%i3E()Sa z$juLe@Jm${Fs&bf-D^N8hKb5KR;)hMn893q^>O_T!*G&JwGL^PH0fogk^MBERDh_349Q-$-#oS$H{nIh<%U&|MCg>3l9;Y}`qV6@jc}GWEoC!&y>b@> z)L^IacgcU;9e2my#TBAYS#_i3z@^~lUfO54JdN9KRz|Sqc#ND}P~@da>31DGIpbQ< zAj6j%IHix_O+Yqx8GdGKc!v1WyR%oYULHvQL*QS*+UN&cWQvdcAkPUmTDgGyuqwhx zv<*~0gX|;n@1>Z!%RI_*c|$g~wS3$>LxxNYgC-q~79;byqkR*r%f<`nP6JelqEdZ; zcAH23!^=esN`P`BaazA8)?=2pf@F-DO?!>AnX|(~1=6I;t3zp!=GVxziqqUaSy9_n z<0yt@;^4}LvkqxlO*PtR#VAB_O6FZdrnOsT2?I>KDIx4j5P_C54+CZA@M z=8@*h@s4ibla>}WEM}i*oAmw^S}XSO-tzGG^1KpO_xg{G3(nO}l7EpDj0+ z&Zt_RAh;*3=7d__H$$w&%e0Zah$hfJsD>372o-Eujg7-vvnEAfWD;DbQ=hXk)hjXs zZbRyp6=ta;avj4gwvM45dt*qkq}V~kMm0lpLuQLO>=c{dMZ^Zr366<=I+#$V|Az3B z&&z*PjdYsnv7E#kZyPf!^5r|{xS1ZYk6Sx=9Flj+2%_})+-^)%j8$Z&M{5aW!BMK& ze8kHx1GEj;-vKpV8&z5eclj!DYa)F;($PWSMFp)vYRod$FFJL$4CBFJxCD>pbdKEK zhZ;Ot)>-zAxNNV%k2~D{jmm%K3n@JJ$xOPJeJm0VhWkfPg{=_<@%ACIj`j0s3-#Cc? zyKY`ZK}a4xha2^+Ud-~)mkVw~OoFAU-WYsPy)tYZ^li>)X9^MQQHsuQoLqslMy3Y9 zKSWL?z2%$L+2GxwS;ps_&rgb)k4#jd&>CY?B5hPFTRO?8%V5X}XXe~TR6Wu06mKS9 zl-j&`MIlv--H<-*x}m=;IFitN1`F{^mJUj4{C?tZwElXtiwXn>LV=%->W&z+IiLu+ zCBitqex9ldAAw6RnzvpUQ0YU?kIn4z)LI+%3D|tx9^1-P@H)*R!aqUG!Fgy17XDWM(mXNake-a6E_qL`SP2wUb1%YDM& z)a({sj0)d)?`G+DOuujmIxrtGCmj*>IMWy}ksjI2n)0WLQ>~$3s~WJt$E@2}Lwvzy zRG?fOEPBqXvq?oJ!E}sXl+xf(NKCq>xhEE7wnt7io>wy1fgA6tz}yEu4L;B`YU@eW z=uEBjIWK=+)$Nw;uq3{nQkTuN!IXH}=L~<-6L?-R3eUn8qIHHt-A{R@yO|xuM23BP zU0x~VuAsH@9vO8%pPH|YWMwLH7!xF8INiy?Sd#@oS9*XGa>wwlyujsiF-u{yLpfHw zbj{O)T7J~t9FApiNSpVW%#iH!-WYPQVnMv%@F1{jh^kqlJu2g^oTp?}Ws*tuv)g0= zWkpH4OYNn<97L=Moz{LAB1=3L(aWv3Tt6UJQyRN3{^QceVjc8LlvB5hJ@iB1Of1s|{pJu>z4A@NUY`M*da z!_^nQ{NY{8tw3SocOpkKEBrEHuQCJZ^yL78Zeq8sIGZP0*fa&J=4C3eD|p6ckCcLb znp<-jl@25hI_Z53@2D=sJa9nBtUGDAQ|2^$y|)3mixiG>`BH1$ml0PONtyZn2&0;n zP2e_Qb`3%USLM2gC|3`Oq}D{OB|OwUv2iD&m6?-?b%l?tAg43hhqw0FESs+`Y^W$m zB)LTLpusE33rxd>hQnt9R-IJlPR)!kUzd$&*kxc)*Bi#>mc_=5n>t)K1*gkL68sQe z@lVQC#P0>IbKE&&wwj#VF7=L^+|&-pOxhc!;jG2;J!A1G@)5v}Xa8MG!MKlfZ&qzx X^VZ~@FaIr!`rk$TKVKTVJ@9`3De)^) literal 0 HcmV?d00001 diff --git a/src/Admin/Field/EnumField.php b/src/Admin/Field/EnumField.php new file mode 100644 index 0000000..6f39f7b --- /dev/null +++ b/src/Admin/Field/EnumField.php @@ -0,0 +1,21 @@ +setProperty($propertyName) + ->setLabel($label) + ->setTemplatePath('@CleverAgeProcessUi/admin/field/enum.html.twig'); + } +} diff --git a/src/Admin/Field/LogLevelField.php b/src/Admin/Field/LogLevelField.php new file mode 100644 index 0000000..df0c2bf --- /dev/null +++ b/src/Admin/Field/LogLevelField.php @@ -0,0 +1,21 @@ +setProperty($propertyName) + ->setLabel($label) + ->setTemplatePath('@CleverAgeProcessUi/admin/field/log_level.html.twig'); + } +} diff --git a/src/CleverAgeProcessUiBundle.php b/src/CleverAgeProcessUiBundle.php index b89f243..90d6971 100644 --- a/src/CleverAgeProcessUiBundle.php +++ b/src/CleverAgeProcessUiBundle.php @@ -4,26 +4,16 @@ namespace CleverAge\ProcessUiBundle; -use CleverAge\ProcessUiBundle\DependencyInjection\Compiler\RegisterLogHandlerCompilerPass; +use CleverAge\ProcessUiBundle\DependencyInjection\Compiler\RegisterLogHandler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; class CleverAgeProcessUiBundle extends Bundle { - public const ICON_NEW = 'fa fa-plus'; - public const ICON_EDIT = 'far fa-edit'; - public const ICON_DELETE = 'fa fa-trash-o'; - public const LABEL_NEW = false; - public const LABEL_EDIT = false; - public const LABEL_DELETE = false; - public const CLASS_NEW = ''; - public const CLASS_EDIT = 'text-warning'; - public const CLASS_DELETE = ''; - public function build(ContainerBuilder $container): void { parent::build($container); - $container->addCompilerPass(new RegisterLogHandlerCompilerPass()); + $container->addCompilerPass(new RegisterLogHandler()); } public function getPath(): string diff --git a/src/Command/PurgeProcessExecution.php b/src/Command/PurgeProcessExecution.php deleted file mode 100644 index ba4e480..0000000 --- a/src/Command/PurgeProcessExecution.php +++ /dev/null @@ -1,88 +0,0 @@ -managerRegistry = $managerRegistry; - } - - /** - * @required - */ - public function setProcessLogDir(string $processLogDir): void - { - $this->processLogDir = $processLogDir; - } - - protected function configure(): void - { - $this->setName('cleverage:process-ui:purge'); - $this->setDescription('Purge process_execution table.'); - $this->setDefinition( - new InputDefinition([ - new InputOption( - 'days', - 'd', - InputOption::VALUE_OPTIONAL, - 'Days to keep. Default 180', - 180 - ), - new InputOption( - 'remove-files', - 'rf', - InputOption::VALUE_NEGATABLE, - 'Remove log files ? (default false)', - false - ), - ]) - ); - } - - public function execute(InputInterface $input, OutputInterface $output): int - { - $days = $input->getOption('days'); - $removeFiles = $input->getOption('remove-files'); - $date = new \DateTime(); - $date->modify("-$days day"); - if ($removeFiles) { - $finder = new Finder(); - $fs = new Filesystem(); - $finder->in($this->processLogDir)->date('before '.$date->format(DateTimeInterface::ATOM)); - $count = $finder->count(); - $fs->remove($finder); - $output->writeln("$count log files are deleted on filesystem."); - } - /** @var ProcessExecutionRepository $repository */ - $repository = $this->managerRegistry->getRepository(ProcessExecution::class); - $repository->deleteBefore($date); - - $output->writeln(<<Process Execution before {$date->format(DateTimeInterface::ATOM)} are deleted into database. - EOT); - - return Command::SUCCESS; - } -} diff --git a/src/Command/UserCreateCommand.php b/src/Command/UserCreateCommand.php index adeb90f..9cd756b 100644 --- a/src/Command/UserCreateCommand.php +++ b/src/Command/UserCreateCommand.php @@ -6,47 +6,44 @@ use CleverAge\ProcessUiBundle\Entity\User; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; +use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Validator\ValidatorInterface; -/** - * Class UserCreateCommand. - */ -final class UserCreateCommand extends Command +#[AsCommand( + name: 'cleverage:process-ui:user-create', + description: 'Command to create a new admin into database for process ui.' +)] +class UserCreateCommand extends Command { - private ValidatorInterface $validator; - private UserPasswordHasherInterface $passwordEncoder; - private EntityManagerInterface $em; - public function __construct( - ValidatorInterface $validator, - UserPasswordHasherInterface $passwordEncoder, - EntityManagerInterface $em + private readonly ValidatorInterface $validator, + private readonly UserPasswordHasherInterface $passwordEncoder, + private readonly EntityManagerInterface $em ) { - $this->validator = $validator; - $this->passwordEncoder = $passwordEncoder; - $this->em = $em; parent::__construct(); } - protected function configure(): void - { - $this->setName('cleverage:process-ui:user-create'); - $this->setDescription('Command to create a new admin into database for process ui.'); - } - protected function execute(InputInterface $input, OutputInterface $output): int { $style = new SymfonyStyle($input, $output); $username = $this->ask('Please enter the email.', $style, [new Email()]); - $password = $this->ask('Please enter the user password.', $style, [new NotBlank(), new Length(min: 8)]); + + $password = $this->askPassword( + (new Question('Please enter the user password.'))->setHidden(true)->setHiddenFallback(false), + $input, + $output + ); $user = new User(); $user->setEmail($username); @@ -62,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } /** - * @param array $constraints + * @param Constraint[] $constraints */ private function ask(string $question, SymfonyStyle $style, array $constraints = []): mixed { @@ -77,4 +74,21 @@ private function ask(string $question, SymfonyStyle $style, array $constraints = return $value; } + + private function askPassword(Question $question, InputInterface $input, OutputInterface $output): mixed + { + $constraints = [new NotBlank(), new Length(min: 8)]; + /** @var QuestionHelper $helper */ + $helper = $this->getHelper('question'); + $password = $helper->ask($input, $output, $question); + $violations = $this->validator->validate($password, $constraints); + while ($violations->count() > 0) { + $violationsMessage = $violations->get(0)->getMessage(); + $output->writeln("$violationsMessage"); + $password = $helper->ask($input, $output, $question); + $violations = $this->validator->validate($password, $constraints); + } + + return $password; + } } diff --git a/src/Controller/Admin/LogRecordCrudController.php b/src/Controller/Admin/LogRecordCrudController.php new file mode 100644 index 0000000..5cc7f1a --- /dev/null +++ b/src/Controller/Admin/LogRecordCrudController.php @@ -0,0 +1,73 @@ +setFormat('Y/M/dd H:mm:ss'), + ArrayField::new('context') + ->setTemplatePath('@CleverAgeProcessUi/admin/field/array.html.twig') + ->onlyOnDetail(), + BooleanField::new('contextIsEmpty', 'Has context info ?') + ->onlyOnIndex() + ->renderAsSwitch(false), + ]; + } + + public function configureCrud(Crud $crud): Crud + { + return $crud->showEntityActionsInlined()->setPaginatorPageSize(250); + } + + public function configureActions(Actions $actions): Actions + { + return Actions::new() + ->add(Crud::PAGE_INDEX, Action::new('detail', false, 'fas fa-eye') + ->setHtmlAttributes( + [ + 'data-bs-toggle' => 'tooltip', + 'data-bs-placement' => 'top', + 'title' => 'Show details', + ] + ) + ->linkToCrudAction('detail')) + ->add(Crud::PAGE_DETAIL, 'index'); + } + + public function configureFilters(Filters $filters): Filters + { + return $filters->add('processExecution') + ->add( + ChoiceFilter::new('level')->setChoices(array_combine(Level::NAMES, Level::VALUES)) + ) + ->add('createdAt'); + } +} diff --git a/src/Controller/Admin/Process/ExecuteAction.php b/src/Controller/Admin/Process/ExecuteAction.php new file mode 100644 index 0000000..8da0685 --- /dev/null +++ b/src/Controller/Admin/Process/ExecuteAction.php @@ -0,0 +1,33 @@ +get('process'); + if (null === $process) { + $this->createNotFoundException('Process is missing'); + } + $bus->dispatch(new ProcessExecuteMessage($process, null)); + $this->addFlash( + 'success', + 'Process has been added to queue. It will start as soon as possible' + ); + + return $this->redirectToRoute('process', ['routeName' => 'process_list']); + } +} diff --git a/src/Controller/Admin/Process/ListAction.php b/src/Controller/Admin/Process/ListAction.php new file mode 100644 index 0000000..3c1cf4c --- /dev/null +++ b/src/Controller/Admin/Process/ListAction.php @@ -0,0 +1,26 @@ +render( + '@CleverAgeProcessUi/admin/process/list.html.twig', + [ + 'processes' => $registry->getProcessConfigurations(), + ] + ); + } +} diff --git a/src/Controller/Admin/Process/UploadAndExecuteAction.php b/src/Controller/Admin/Process/UploadAndExecuteAction.php new file mode 100644 index 0000000..ea540eb --- /dev/null +++ b/src/Controller/Admin/Process/UploadAndExecuteAction.php @@ -0,0 +1,75 @@ + '\w+'], + methods: ['POST', 'GET'] +)] +#[IsGranted('ROLE_USER')] +class UploadAndExecuteAction extends AbstractController +{ + public function __invoke( + RequestStack $requestStack, + MessageBusInterface $messageBus, + #[Autowire(param: 'upload_directory')] string $uploadDirectory, + #[ValueResolver('process')] ProcessConfiguration $processConfiguration + ): Response { + if (null === $processConfiguration->getEntryPoint()) { + throw new \RuntimeException('You must set an entry_point.'); + } + if (false === ($processConfiguration->getOptions()['ui']['upload_and_run'] ?? false)) { + throw new \RuntimeException('ui.upload_and_run options not set to true in process options.'); + } + $form = $this->createForm( + ProcessUploadFileType::class, + null, + ['process_code' => $requestStack->getMainRequest()?->get('process')] + ); + $form->handleRequest($requestStack->getMainRequest()); + if ($form->isSubmitted() && $form->isValid()) { + /** @var UploadedFile $file */ + $file = $form->getData(); + $savedFilepath = sprintf('%s/%s.%s', $uploadDirectory, Uuid::v4(), $file->getClientOriginalExtension()); + (new Filesystem())->dumpFile($savedFilepath, $file->getContent()); + $messageBus->dispatch( + new ProcessExecuteMessage( + $form->getConfig()->getOption('process_code'), + $savedFilepath + ) + ); + $this->addFlash( + 'success', + 'Process has been added to queue. It will start as soon as possible' + ); + + return $this->redirectToRoute('process', ['routeName' => 'process_list']); + } + + return $this->render( + '@CleverAgeProcessUi/admin/process/upload_and_execute.html.twig', + [ + 'form' => $form->createView(), + ] + ); + } +} diff --git a/src/Controller/Admin/ProcessDashboardController.php b/src/Controller/Admin/ProcessDashboardController.php new file mode 100644 index 0000000..a0a323c --- /dev/null +++ b/src/Controller/Admin/ProcessDashboardController.php @@ -0,0 +1,57 @@ +container->get(AdminUrlGenerator::class); + + return $this->redirect($adminUrlGenerator->setController(ProcessExecutionCrudController::class)->generateUrl()); + } + + public function configureDashboard(): Dashboard + { + return Dashboard::new() + ->setTitle(''); + } + + public function configureMenuItems(): iterable + { + yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home'); + yield MenuItem::subMenu('Process', 'fas fa-gear')->setSubItems( + [ + MenuItem::linkToRoute('Process list', 'fas fa-list', 'process_list'), + MenuItem::linkToCrud('Executions', 'fas fa-rocket', ProcessExecution::class), + MenuItem::linkToCrud('Logs', 'fas fa-pen', LogRecord::class), + ] + ); + if ($this->isGranted('ROLE_ADMIN')) { + yield MenuItem::subMenu('Users', 'fas fa-user')->setSubItems( + [ + MenuItem::linkToCrud('User List', 'fas fa-user', User::class), + ] + ); + } + } +} diff --git a/src/Controller/Admin/ProcessExecutionCrudController.php b/src/Controller/Admin/ProcessExecutionCrudController.php new file mode 100644 index 0000000..246cf0d --- /dev/null +++ b/src/Controller/Admin/ProcessExecutionCrudController.php @@ -0,0 +1,131 @@ +setFormat('Y/M/dd H:mm:ss'), + DateTimeField::new('endDate')->setFormat('Y/M/dd H:mm:ss'), + TextField::new('source')->setTemplatePath('@CleverAgeProcessUi/admin/field/process_source.html.twig'), + TextField::new('target')->setTemplatePath('@CleverAgeProcessUi/admin/field/process_target.html.twig'), + TextField::new('duration')->formatValue(function($value, ProcessExecution $entity) { + return $entity->duration(); // returned format can be changed here + }), + ArrayField::new('report')->setTemplatePath('@CleverAgeProcessUi/admin/field/report.html.twig'), + ]; + } + + public function configureCrud(Crud $crud): Crud + { + $crud->showEntityActionsInlined(); + $crud->setDefaultSort(['startDate' => 'DESC']); + + return $crud; + } + + public function configureActions(Actions $actions): Actions + { + return Actions::new() + ->add( + Crud::PAGE_INDEX, + Action::new('showLogs', false, 'fas fa-eye') + ->setHtmlAttributes( + [ + 'data-bs-toggle' => 'tooltip', + 'data-bs-placement' => 'top', + 'title' => 'Show logs stored in database', + ] + ) + ->linkToCrudAction('showLogs') + )->add( + Crud::PAGE_INDEX, + Action::new('downloadLogfile', false, 'fas fa-download') + ->setHtmlAttributes( + [ + 'data-bs-toggle' => 'tooltip', + 'data-bs-placement' => 'top', + 'title' => 'Download log file', + ] + ) + ->linkToCrudAction('downloadLogFile') + ); + } + + public function showLogs(AdminContext $adminContext): RedirectResponse + { + /** @var AdminUrlGenerator $adminUrlGenerator */ + $adminUrlGenerator = $this->container->get(AdminUrlGenerator::class); + $url = $adminUrlGenerator + ->setController(LogRecordCrudController::class) + ->setAction('index') + ->setEntityId(null) + ->set( + 'filters', + [ + 'processExecution' => [ + 'comparison' => '=', + 'value' => $this->getContext()->getEntity()->getInstance()->getId(), + ], + ] + ) + ->generateUrl(); + + return $this->redirect($url); + } + + public function downloadLogFile( + AdminContext $context, + #[Autowire(param: 'kernel.logs_dir')] string $directory, + ): Response { + /** @var ProcessExecution $processExecution */ + $processExecution = $context->getEntity()->getInstance(); + $filepath = $directory.DIRECTORY_SEPARATOR.$processExecution->code.DIRECTORY_SEPARATOR + .$processExecution->logFilename; + $basename = basename($filepath); + $content = file_get_contents($filepath); + if (false === $content) { + throw new NotFoundHttpException('Log file not found.'); + } + $response = new Response($content); + $response->headers->set('Content-Type', 'text/plain; charset=utf-8'); + $response->headers->set('Content-Disposition', "attachment; filename=\"$basename\""); + + return $response; + } + + public function configureFilters(Filters $filters): Filters + { + return $filters->add('code')->add('startDate'); + } +} diff --git a/src/Controller/Admin/Security/LoginController.php b/src/Controller/Admin/Security/LoginController.php new file mode 100644 index 0000000..f39026c --- /dev/null +++ b/src/Controller/Admin/Security/LoginController.php @@ -0,0 +1,24 @@ +render( + '@CleverAgeProcessUi/admin/login.html.twig', + [ + 'page_title' => 'Login', + 'target_path' => '/process', + ] + ); + } +} diff --git a/src/Controller/Admin/Security/LogoutController.php b/src/Controller/Admin/Security/LogoutController.php new file mode 100644 index 0000000..e272d38 --- /dev/null +++ b/src/Controller/Admin/Security/LogoutController.php @@ -0,0 +1,21 @@ +logout(); + + return $this->redirectToRoute('process_login'); + } +} diff --git a/src/Controller/Admin/UserCrudController.php b/src/Controller/Admin/UserCrudController.php new file mode 100644 index 0000000..8cc3cd4 --- /dev/null +++ b/src/Controller/Admin/UserCrudController.php @@ -0,0 +1,91 @@ + $roles */ + public function __construct(private array $roles) + { + } + + public function configureCrud(Crud $crud): Crud + { + $crud->showEntityActionsInlined(); + $crud->setEntityPermission('ROLE_ADMIN'); + + return $crud; + } + + public static function getEntityFqcn(): string + { + return User::class; + } + + public function configureFields(string $pageName): iterable + { + yield FormField::addTab('Credentials')->setIcon('fa fa-key'); + yield EmailField::new('email'); + yield TextField::new('password', 'New password') + ->onlyOnForms() + ->setFormType(RepeatedType::class) + ->setFormTypeOptions( + [ + 'type' => PasswordType::class, + 'first_options' => [ + 'label' => 'New password', + 'hash_property_path' => 'password', + 'always_empty' => false, + ], + 'second_options' => ['label' => 'Repeat password'], + 'mapped' => false, + ] + ); + + yield FormField::addTab('Informations')->setIcon('fa fa-user'); + yield TextField::new('firstname'); + yield TextField::new('lastname'); + + yield FormField::addTab('Roles')->setIcon('fa fa-theater-masks'); + yield ChoiceField::new('roles', false) + ->setChoices($this->roles) + ->setFormTypeOptions(['multiple' => true, 'expanded' => true]); + } + + public function configureActions(Actions $actions): Actions + { + return $actions + ->update(Crud::PAGE_INDEX, Action::NEW, function (Action $action) { + return $action->setIcon('fa fa-plus') + ->setLabel(false) + ->addCssClass(''); + })->update(Crud::PAGE_INDEX, Action::EDIT, function (Action $action) { + return $action->setIcon('fa fa-edit') + ->setLabel(false) + ->addCssClass('text-warning'); + })->update(Crud::PAGE_INDEX, Action::DELETE, function (Action $action) { + return $action->setIcon('fa fa-trash-o') + ->setLabel(false) + ->addCssClass(''); + })->update(Crud::PAGE_INDEX, Action::BATCH_DELETE, function (Action $action) { + return $action->setLabel('Delete') + ->addCssClass(''); + }); + } +} diff --git a/src/Controller/Crud/ProcessCrudController.php b/src/Controller/Crud/ProcessCrudController.php deleted file mode 100644 index ee17c4b..0000000 --- a/src/Controller/Crud/ProcessCrudController.php +++ /dev/null @@ -1,141 +0,0 @@ -processUiConfigurationManager = $processUiConfigurationManager; - } - - public static function getEntityFqcn(): string - { - return Process::class; - } - - public function configureCrud(Crud $crud): Crud - { - $crud->showEntityActionsInlined(); - $crud->setDefaultSort(['lastExecutionDate' => SortOrder::DESC]); - $crud->setEntityPermission('ROLE_ADMIN'); - $crud->setSearchFields(['processCode', 'source', 'target']); - - return $crud; - } - - /** - * @return array - */ - public function configureFields(string $pageName): array - { - return [ - Field::new('processCode', 'Process'), - 'source', - 'target', - 'lastExecutionDate', - IntegerField::new('lastExecutionStatus')->formatValue(static function (?int $value) { - return match ($value) { - ProcessExecution::STATUS_FAIL => '', - ProcessExecution::STATUS_START => '', - ProcessExecution::STATUS_SUCCESS => '', - default => '', - }; - }), - ]; - } - - public function configureActions(Actions $actions): Actions - { - $actions->remove(Crud::PAGE_INDEX, Action::EDIT); - $actions->remove(Crud::PAGE_INDEX, Action::DELETE); - $actions->remove(Crud::PAGE_INDEX, Action::NEW); - $runProcess = Action::new('run', '', 'fa fa-rocket') - ->linkToCrudAction('runProcessAction'); - $runProcess->setHtmlAttributes(['data-toggle' => 'tooltip', 'title' => 'Run process in background']); - $runProcess->displayIf(fn (Process $process) => $this->processUiConfigurationManager->canRun($process)); - $viewHistoryAction = Action::new('viewHistory', '', 'fa fa-history') - ->linkToCrudAction('viewHistoryAction'); - $viewHistoryAction->setHtmlAttributes(['data-toggle' => 'tooltip', 'title' => 'View executions history']); - $actions->add(Crud::PAGE_INDEX, $viewHistoryAction); - $actions->add(Crud::PAGE_INDEX, $runProcess); - - return $actions; - } - - public function runProcessAction(AdminContext $context): Response - { - try { - /** @var Process $process */ - $process = $context->getEntity()->getInstance(); - if (false === $this->processUiConfigurationManager->canRun($process)) { - $this->addFlash( - 'warning', - 'Process is not run-able via Ui.' - ); - } else { - $message = new ProcessRunMessage($process->getProcessCode()); - $this->dispatchMessage($message); - $this->addFlash( - 'success', - 'Process has been added to queue. It will start as soon as possible' - ); - } - } catch (Exception $e) { - $this->addFlash('warning', 'Cannot run process.'); - } - - /** @var AdminUrlGenerator $routeBuilder */ - $routeBuilder = $this->get(AdminUrlGenerator::class); - - return $this->redirect( - $routeBuilder->setController(__CLASS__)->setAction(Action::INDEX)->generateUrl() - ); - } - - public function viewHistoryAction(AdminContext $adminContext): RedirectResponse - { - /** @var AdminUrlGenerator $routeBuilder */ - $routeBuilder = $this->get(AdminUrlGenerator::class); - /** @var Process $process */ - $process = $adminContext->getEntity()->getInstance(); - - return $this->redirect( - $routeBuilder - ->setController(ProcessExecutionCrudController::class) - ->setEntityId(null) - ->setAction(Action::INDEX) - ->setAll([ - 'filters' => [ - 'processCode' => ['comparison' => ComparisonType::EQ, 'value' => $process->getProcessCode()], - ], - ]) - ->generateUrl() - ); - } -} diff --git a/src/Controller/Crud/ProcessExecutionCrudController.php b/src/Controller/Crud/ProcessExecutionCrudController.php deleted file mode 100644 index c5e8a4c..0000000 --- a/src/Controller/Crud/ProcessExecutionCrudController.php +++ /dev/null @@ -1,146 +0,0 @@ -indexLogs = $indexLogs; - } - - /** - * @required - */ - public function setProcessLogDir(string $processLogDir): void - { - $this->processLogDir = $processLogDir; - } - - /** - * @required true - */ - public function setProcessUiConfigurationManager(ProcessUiConfigurationManager $processUiConfigurationManager): void - { - $this->processUiConfigurationManager = $processUiConfigurationManager; - } - - public static function getEntityFqcn(): string - { - return ProcessExecution::class; - } - - public function configureCrud(Crud $crud): Crud - { - $crud->showEntityActionsInlined(); - $crud->setDefaultSort(['startDate' => SortOrder::DESC]); - $crud->setEntityPermission('ROLE_ADMIN'); - $crud->setSearchFields(true === $this->indexLogs ? ['processCode', 'source', 'target', 'logRecords.message'] : ['processCode', 'source', 'target']); - - return $crud; - } - - /** - * @return array - */ - public function configureFields(string $pageName): array - { - return [ - Field::new('processCode', 'Process'), - 'source', - 'target', - 'startDate', - 'endDate', - IntegerField::new('status')->formatValue(static function (?int $value) { - return match ($value) { - ProcessExecution::STATUS_FAIL => '', - ProcessExecution::STATUS_START => '', - ProcessExecution::STATUS_SUCCESS => '', - default => '', - }; - }), - ]; - } - - public function configureFilters(Filters $filters): Filters - { - $processCodeChoices = $this->processUiConfigurationManager->getProcessChoices(); - if (\count($processCodeChoices) > 0) { - $filters->add(ChoiceFilter::new('processCode', 'Process')->setChoices($processCodeChoices)); - } - - $sourceChoices = $this->processUiConfigurationManager->getSourceChoices(); - if (\count($sourceChoices) > 0) { - $filters->add(ChoiceFilter::new('source')->setChoices($sourceChoices)); - } - - $targetChoices = $this->processUiConfigurationManager->getTargetChoices(); - if (\count($targetChoices) > 0) { - $filters->add(ChoiceFilter::new('target')->setChoices($targetChoices)); - } - $filters->add(ChoiceFilter::new('status')->setChoices([ - 'failed' => ProcessExecution::STATUS_FAIL, - 'success' => ProcessExecution::STATUS_SUCCESS, - 'started' => ProcessExecution::STATUS_START, - ])); - $filters->add('startDate'); - $filters->add('endDate'); - - return $filters; - } - - public function configureActions(Actions $actions): Actions - { - $actions->remove(Crud::PAGE_INDEX, Action::EDIT); - $actions->remove(Crud::PAGE_INDEX, Action::DELETE); - $actions->remove(Crud::PAGE_INDEX, Action::NEW); - - $downloadLogAction = Action::new('downloadLog', '', 'fa fa-file-download') - ->linkToCrudAction('downloadLog'); - $downloadLogAction->setHtmlAttributes(['data-toggle' => 'tooltip', 'title' => 'Download log file']); - $actions->add(Crud::PAGE_INDEX, $downloadLogAction); - - return $actions; - } - - public function downloadLog(AdminContext $context): Response - { - /** @var ProcessExecution $processExecution */ - $processExecution = $context->getEntity()->getInstance(); - $filepath = $this->processLogDir.\DIRECTORY_SEPARATOR.$processExecution->getLog(); - $basename = basename($filepath); - $content = file_get_contents($filepath); - if (false === $content) { - throw new NotFoundHttpException('Log file not found.'); - } - $response = new Response($content); - $response->headers->set('Content-Type', 'text/plain; charset=utf-8'); - $response->headers->set('Content-Disposition', "attachment; filename=\"$basename\""); - - return $response; - } -} diff --git a/src/Controller/Crud/UserCrudController.php b/src/Controller/Crud/UserCrudController.php deleted file mode 100644 index d432029..0000000 --- a/src/Controller/Crud/UserCrudController.php +++ /dev/null @@ -1,128 +0,0 @@ -passwordHasher = $passwordHasher; - } - - public function configureCrud(Crud $crud): Crud - { - $crud->showEntityActionsInlined(); - $crud->setEntityPermission('ROLE_ADMIN'); - - return $crud; - } - - public static function getEntityFqcn(): string - { - return User::class; - } - - public function configureFields(string $pageName): iterable - { - yield FormField::addPanel('Credentials')->setIcon('fa fa-key'); - yield EmailField::new('email'); - yield TextField::new('password', 'New password') - ->onlyOnForms() - ->setFormType(RepeatedType::class) - ->setFormTypeOptions([ - 'type' => PasswordType::class, - 'first_options' => ['label' => 'New password'], - 'second_options' => ['label' => 'Repeat password'], - ]); - - yield FormField::addPanel('Informations')->setIcon('fa fa-user'); - yield TextField::new('firstname'); - yield TextField::new('lastname'); - - yield FormField::addPanel('Roles')->setIcon('fa fa-theater-masks'); - yield ChoiceField::new('roles', false) - ->setChoices(['ROLE_ADMIN' => 'ROLE_ADMIN', 'ROLE_USER' => 'ROLE_USER']) - ->setFormTypeOptions(['multiple' => true, 'expanded' => true]); - } - - public function configureActions(Actions $actions): Actions - { - return $actions - ->update(Crud::PAGE_INDEX, Action::NEW, function (Action $action) { - return $action->setIcon(CleverAgeProcessUiBundle::ICON_NEW) - ->setLabel(CleverAgeProcessUiBundle::LABEL_NEW) - ->addCssClass(CleverAgeProcessUiBundle::CLASS_NEW); - })->update(Crud::PAGE_INDEX, Action::EDIT, function (Action $action) { - return $action->setIcon(CleverAgeProcessUiBundle::ICON_EDIT) - ->setLabel(CleverAgeProcessUiBundle::LABEL_EDIT) - ->addCssClass(CleverAgeProcessUiBundle::CLASS_EDIT); - })->update(Crud::PAGE_INDEX, Action::DELETE, function (Action $action) { - return $action->setIcon(CleverAgeProcessUiBundle::ICON_DELETE) - ->setLabel(CleverAgeProcessUiBundle::LABEL_DELETE) - ->addCssClass(CleverAgeProcessUiBundle::CLASS_DELETE); - })->update(Crud::PAGE_INDEX, Action::BATCH_DELETE, function (Action $action) { - return $action->setLabel('Delete') - ->addCssClass(CleverAgeProcessUiBundle::CLASS_DELETE); - }); - } - - public function createEditFormBuilder( - EntityDto $entityDto, - KeyValueStore $formOptions, - AdminContext $context - ): FormBuilderInterface { - $formBuilder = parent::createEditFormBuilder($entityDto, $formOptions, $context); - - $this->addEncodePasswordEventListener($formBuilder); - - return $formBuilder; - } - - public function createNewFormBuilder( - EntityDto $entityDto, - KeyValueStore $formOptions, - AdminContext $context - ): FormBuilderInterface { - $formBuilder = parent::createNewFormBuilder($entityDto, $formOptions, $context); - - $this->addEncodePasswordEventListener($formBuilder); - - return $formBuilder; - } - - protected function addEncodePasswordEventListener(FormBuilderInterface $formBuilder): void - { - $formBuilder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event): void { - /** @var User $user */ - $user = $event->getData(); - $password = $user->getPassword(); - if ($password) { - $user->setPassword($this->passwordHasher->hashPassword($user, $password)); - } - }); - } -} diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php deleted file mode 100644 index dc90303..0000000 --- a/src/Controller/DashboardController.php +++ /dev/null @@ -1,44 +0,0 @@ -container->get(AdminUrlGenerator::class); - - return $this->redirect($routeBuilder->setController(ProcessCrudController::class)->generateUrl()); - } - - public function configureDashboard(): Dashboard - { - return Dashboard::new()->setTitle('CleverAge Process UI'); - } - - public function configureMenuItems(): iterable - { - yield MenuItem::section('Process', 'fas fa-tasks')->setPermission('ROLE_ADMIN'); - yield MenuItem::linkToCrud('List', 'fa fa-list', Process::class)->setPermission('ROLE_ADMIN'); - yield MenuItem::linkToCrud('History', 'fa fa-history', ProcessExecution::class)->setPermission('ROLE_ADMIN'); - - yield MenuItem::section(); - yield MenuItem::section('Settings', 'fas fa-tools')->setPermission('ROLE_ADMIN'); - yield MenuItem::linkToCrud('Users', 'fa fa-users', User::class)->setPermission('ROLE_ADMIN'); - } -} diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php deleted file mode 100644 index 2c7c54f..0000000 --- a/src/Controller/SecurityController.php +++ /dev/null @@ -1,46 +0,0 @@ -getUser()) { - return $this->redirectToRoute('/'); - } - - // get the login error if there is one - $error = $authenticationUtils->getLastAuthenticationError(); - // last username entered by the user - $lastUsername = $authenticationUtils->getLastUsername(); - - return $this->render('@EasyAdmin/page/login.html.twig', [ - 'last_username' => $lastUsername, - 'error' => $error, - 'page_title' => 'Login', - 'username_parameter' => 'email', - 'username_label' => 'Email', - 'password_parameter' => 'password', - 'csrf_token_intention' => 'authenticate', - ]); - } - - /** - * @Route("/logout", name="app_logout") - */ - public function logout(): void - { - throw new \LogicException( - 'This method can be blank - it will be intercepted by the logout key on your firewall.' - ); - } -} diff --git a/src/DependencyInjection/CleverAgeProcessUiExtension.php b/src/DependencyInjection/CleverAgeProcessUiExtension.php index 504ae37..addd734 100644 --- a/src/DependencyInjection/CleverAgeProcessUiExtension.php +++ b/src/DependencyInjection/CleverAgeProcessUiExtension.php @@ -4,15 +4,19 @@ namespace CleverAge\ProcessUiBundle\DependencyInjection; -use CleverAge\ProcessUiBundle\Message\LogIndexerMessage; -use CleverAge\ProcessUiBundle\Message\ProcessRunMessage; +use CleverAge\ProcessUiBundle\Controller\Admin\ProcessDashboardController; +use CleverAge\ProcessUiBundle\Controller\Admin\UserCrudController; +use CleverAge\ProcessUiBundle\Entity\User; +use CleverAge\ProcessUiBundle\Message\ProcessExecuteMessage; +use CleverAge\ProcessUiBundle\Monolog\Handler\DoctrineProcessHandler; +use CleverAge\ProcessUiBundle\Monolog\Handler\ProcessHandler; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -class CleverAgeProcessUiExtension extends Extension implements PrependExtensionInterface +final class CleverAgeProcessUiExtension extends Extension implements PrependExtensionInterface { public function load(array $configs, ContainerBuilder $container): void { @@ -21,7 +25,15 @@ public function load(array $configs, ContainerBuilder $container): void $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $container->setParameter('clever_age_process_ui.index_logs.enabled', $config['index_logs']['enabled']); + $container->getDefinition(UserCrudController::class) + ->setArgument('$roles', array_combine($config['security']['roles'], $config['security']['roles'])); + $container->getDefinition(DoctrineProcessHandler::class) + ->addMethodCall('setEnabled', [$config['logs']['store_in_database']]) + ->addMethodCall('setLevel', [$config['logs']['database_level']]); + $container->getDefinition(ProcessHandler::class) + ->addMethodCall('setReportIncrementLevel', [$config['logs']['report_increment_level']]); + $container->getDefinition(ProcessDashboardController::class) + ->setArgument('$logoPath', $config['design']['logo_path']); } /** @@ -32,29 +44,49 @@ public function prepend(ContainerBuilder $container): void $container->loadFromExtension( 'doctrine_migrations', [ - 'migrations_paths' => ['CleverAgeProcessUi' => \dirname(__DIR__).'/Migrations'], + 'migrations_paths' => ['CleverAge\ProcessUiBundle\Migrations' => \dirname(__DIR__).'/Migrations'], ] ); $container->loadFromExtension( 'framework', [ - 'assets' => ['json_manifest_path' => null], 'messenger' => [ 'transport' => [ [ - 'name' => 'run_process', - 'dsn' => 'doctrine://default', - 'retry_strategy' => ['max_retries' => 0], - ], - [ - 'name' => 'index_logs', + 'name' => 'execute_process', 'dsn' => 'doctrine://default', 'retry_strategy' => ['max_retries' => 0], ], ], 'routing' => [ - ProcessRunMessage::class => 'run_process', - LogIndexerMessage::class => 'index_logs', + ProcessExecuteMessage::class => 'execute_process', + ], + ], + ] + ); + $container->loadFromExtension( + 'security', + [ + 'providers' => [ + 'process_user_provider' => [ + 'entity' => [ + 'class' => User::class, + 'property' => 'email', + ], + ], + ], + 'firewalls' => [ + 'main' => [ + 'provider' => 'process_user_provider', + 'form_login' => [ + 'login_path' => 'process_login', + 'check_path' => 'process_login', + ], + 'logout' => [ + 'path' => 'process_logout', + 'target' => 'process_login', + 'clear_site_data' => '*', + ], ], ], ] diff --git a/src/DependencyInjection/Compiler/RegisterLogHandlerCompilerPass.php b/src/DependencyInjection/Compiler/RegisterLogHandler.php similarity index 66% rename from src/DependencyInjection/Compiler/RegisterLogHandlerCompilerPass.php rename to src/DependencyInjection/Compiler/RegisterLogHandler.php index 3b28378..a4c258d 100644 --- a/src/DependencyInjection/Compiler/RegisterLogHandlerCompilerPass.php +++ b/src/DependencyInjection/Compiler/RegisterLogHandler.php @@ -4,15 +4,19 @@ namespace CleverAge\ProcessUiBundle\DependencyInjection\Compiler; -use CleverAge\ProcessUiBundle\Monolog\Handler\ProcessLogHandler; +use CleverAge\ProcessUiBundle\Monolog\Handler\DoctrineProcessHandler; +use CleverAge\ProcessUiBundle\Monolog\Handler\ProcessHandler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; -class RegisterLogHandlerCompilerPass implements CompilerPassInterface +class RegisterLogHandler implements CompilerPassInterface { public function process(ContainerBuilder $container): void { + if (!$container->hasDefinition('monolog.logger')) { + return; + } $loggers = [ 'monolog.logger.cleverage_process', 'monolog.logger.cleverage_process_task', @@ -22,7 +26,8 @@ public function process(ContainerBuilder $container): void if ($container->has($logger)) { $container ->getDefinition($logger) - ->addMethodCall('pushHandler', [new Reference(ProcessLogHandler::class)]); + ->addMethodCall('pushHandler', [new Reference(ProcessHandler::class)]) + ->addMethodCall('pushHandler', [new Reference(DoctrineProcessHandler::class)]); } } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 0e5c270..57473bb 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -4,6 +4,8 @@ namespace CleverAge\ProcessUiBundle\DependencyInjection; +use Monolog\Level; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -11,16 +13,32 @@ class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder(): TreeBuilder { - $treeBuilder = new TreeBuilder('clever_age_process_ui'); - $treeBuilder->getRootNode() + $tb = new TreeBuilder('clever_age_process_ui'); + /** @var ArrayNodeDefinition $rootNode */ + $rootNode = $tb->getRootNode(); + $rootNode ->children() - ->arrayNode('index_logs') - ->ignoreExtraKeys() - ->addDefaultsIfNotSet() + ->arrayNode('security') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('roles')->defaultValue(['ROLE_ADMIN'])->end(); // Roles displayed inside user edit form + $rootNode + ->children() + ->arrayNode('logs') + ->addDefaultsIfNotSet() ->children() - ->booleanNode('enabled')->defaultFalse()->end() - ?->end(); + ->booleanNode('store_in_database')->defaultValue(true)->end() // enable/disable store log in database (log_record table) + ->scalarNode('database_level')->defaultValue(Level::Debug->name)->end() // min log level to store log record in database + ->scalarNode('report_increment_level')->defaultValue(Level::Warning->name)->end() // min log level to increment process execution report + ->end(); + $rootNode + ->children() + ->arrayNode('design') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('logo_path')->defaultValue('bundles/cleverageprocessui/logo.jpg')->end() + ->end(); - return $treeBuilder; + return $tb; } } diff --git a/src/Entity/.gitignore b/src/Entity/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/src/Entity/Enum/ProcessExecutionStatus.php b/src/Entity/Enum/ProcessExecutionStatus.php new file mode 100644 index 0000000..1950783 --- /dev/null +++ b/src/Entity/Enum/ProcessExecutionStatus.php @@ -0,0 +1,12 @@ + */ + public readonly array $context; + + #[ORM\Column(type: 'datetime_immutable')] + public readonly \DateTimeImmutable $createdAt; + + public function getId(): ?int + { + return $this->id; + } + + public function __construct(\Monolog\LogRecord $record, ProcessExecution $processExecution) + { + $this->channel = (string) (new UnicodeString($record->channel))->truncate(64); + $this->level = $record->level->value; + $this->message = (string) (new UnicodeString($record->message))->truncate(512); + $this->context = $record->context; + $this->processExecution = $processExecution; + $this->createdAt = \DateTimeImmutable::createFromMutable(new \DateTime()); + } + + public function contextIsEmpty(): bool + { + return !empty($this->context); + } +} diff --git a/src/Entity/Process.php b/src/Entity/Process.php deleted file mode 100644 index 7d0ca7b..0000000 --- a/src/Entity/Process.php +++ /dev/null @@ -1,113 +0,0 @@ - - * @ORM\OneToMany(targetEntity="CleverAge\ProcessUiBundle\Entity\ProcessExecution", mappedBy="process") - */ - private $executions; - - /** - * @ORM\Column(name="last_execution_status", type="integer", nullable=true) - */ - private ?int $lastExecutionStatus; - - public function __construct( - string $processCode, - ?string $source = null, - ?string $target = null, - ?DateTime $lastExecutionDate = null, - ?int $lastExecutionStatus = null - ) { - $this->processCode = $processCode; - $this->source = $source; - $this->target = $target; - $this->lastExecutionDate = $lastExecutionDate; - $this->lastExecutionStatus = $lastExecutionStatus; - } - - public function getId(): ?int - { - return $this->id; - } - - public function getProcessCode(): string - { - return $this->processCode; - } - - public function getSource(): ?string - { - return $this->source; - } - - public function getTarget(): ?string - { - return $this->target; - } - - public function getLastExecutionDate(): ?DateTimeInterface - { - return $this->lastExecutionDate; - } - - public function getLastExecutionStatus(): ?int - { - return $this->lastExecutionStatus; - } - - public function setLastExecutionDate(DateTimeInterface $lastExecutionDate): self - { - $this->lastExecutionDate = $lastExecutionDate; - - return $this; - } - - public function setLastExecutionStatus(int $lastExecutionStatus): self - { - $this->lastExecutionStatus = $lastExecutionStatus; - - return $this; - } -} diff --git a/src/Entity/ProcessExecution.php b/src/Entity/ProcessExecution.php index 47ff1ac..b701f34 100644 --- a/src/Entity/ProcessExecution.php +++ b/src/Entity/ProcessExecution.php @@ -4,253 +4,86 @@ namespace CleverAge\ProcessUiBundle\Entity; -use CleverAge\ProcessUiBundle\Repository\ProcessExecutionRepository; -use DateTime; -use DateTimeInterface; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; +use CleverAge\ProcessUiBundle\Entity\Enum\ProcessExecutionStatus; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\String\UnicodeString; -/** - * @ORM\Entity(repositoryClass=ProcessExecutionRepository::class) - */ +#[ORM\Entity] +#[ORM\Index(columns: ['code'], name: 'idx_process_execution_code')] +#[ORM\Index(columns: ['start_date'], name: 'idx_process_execution_start_date')] class ProcessExecution { - public const STATUS_START = 0; - public const STATUS_SUCCESS = 1; - public const STATUS_FAIL = -1; + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; - /** - * @ORM\Id - * @ORM\Column(type="integer") - * @ORM\GeneratedValue() - */ - private ?int $id; + #[ORM\Column(type: 'string', length: 255)] + public readonly string $code; - /** - * @ORM\Column(name="process_code", type="string", length=255, nullable=true) - */ - private ?string $processCode; + #[ORM\Column(type: 'string', length: 255)] + public readonly string $logFilename; - /** - * @ORM\Column(type="string", length=255, nullable=true) - */ - private ?string $source; + #[ORM\Column(type: 'datetime_immutable')] + public readonly \DateTimeImmutable $startDate; - /** - * @ORM\Column(type="string", length=255, nullable=true) - */ - private ?string $target; + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + public ?\DateTimeImmutable $endDate = null; - /** - * @ORM\Column(type="datetime") - */ - private DateTimeInterface $startDate; + #[ORM\Column(type: 'string', enumType: ProcessExecutionStatus::class)] + public ProcessExecutionStatus $status; - /** - * @ORM\Column(type="datetime", nullable=true) - */ - private ?DateTimeInterface $endDate; - - /** - * @ORM\Column(type="integer") - */ - private int $status; - - /** - * @ORM\Column(type="string", length=255, nullable=true) - */ - private ?string $response; - - /** - * @ORM\Column(type="string", length=255, nullable=true) - */ - private ?string $data; - - /** - * @ORM\Column(type="json", nullable=true) - */ - private ?array $report; - - /** - * @ORM\Column(type="string", length=255, nullable=true) - */ - private ?string $log; - - /** - * @ORM\ManyToOne(targetEntity="CleverAge\ProcessUiBundle\Entity\Process", inversedBy="executions") - * @ORM\JoinColumn(name="process_id", referencedColumnName="id", onDelete="SET NULL") - */ - private Process $process; - - /** - * @ORM\OneToMany(targetEntity="ProcessExecutionLogRecord", mappedBy="processExecution", cascade={"persist"}) - * - * @var Collection - */ - private Collection $logRecords; - - public function __construct(Process $process) - { - $this->process = $process; - $this->status = self::STATUS_START; - $this->startDate = new DateTime(); - $this->logRecords = new ArrayCollection(); - } + #[ORM\Column(type: 'json')] + private array $report = []; public function getId(): ?int { return $this->id; } - public function getProcessCode(): ?string - { - return $this->processCode; - } - - public function setProcessCode(?string $processCode): self - { - $this->processCode = $processCode; - - return $this; - } - - public function getSource(): ?string - { - return $this->source; - } - - public function setSource(?string $source): self - { - $this->source = $source; - - return $this; - } - - public function getTarget(): ?string - { - return $this->target; - } - - public function setTarget(?string $target): self - { - $this->target = $target; - - return $this; - } - - public function getStartDate(): DateTimeInterface - { - return $this->startDate; - } - - public function setStartDate(DateTimeInterface $startDate): self - { - $this->startDate = $startDate; - - return $this; - } - - public function getEndDate(): ?DateTimeInterface - { - return $this->endDate; - } - - public function setEndDate(DateTimeInterface $endDate): self - { - $this->endDate = $endDate; - - return $this; - } - - public function getStatus(): ?int + public function __construct(string $code, string $logFilename) { - return $this->status; + $this->code = (string) (new UnicodeString($code))->truncate(255); + $this->logFilename = $logFilename; + $this->startDate = \DateTimeImmutable::createFromMutable(new \DateTime()); + $this->status = ProcessExecutionStatus::Started; } - public function setStatus(int $status): self + public function setStatus(ProcessExecutionStatus $status): void { $this->status = $status; - - return $this; - } - - public function getResponse(): ?string - { - return $this->response; - } - - public function setResponse(?string $response): self - { - $this->response = $response; - - return $this; - } - - public function getData(): ?string - { - return $this->data; - } - - public function setData(?string $data): self - { - $this->data = $data; - - return $this; - } - - public function getLog(): ?string - { - return $this->log; } - public function setLog(string $log): self + public function end(): void { - $this->log = $log; - - return $this; + $this->endDate = \DateTimeImmutable::createFromMutable(new \DateTime()); } - public function addLogRecord(ProcessExecutionLogRecord $processExecutionLogRecord): void + public function __toString(): string { - $processExecutionLogRecord->setProcessExecution($this); - $this->logRecords->add($processExecutionLogRecord); + return sprintf('%s (%s)', $this->id, $this->code); } - /** - * @return Collection - */ - public function getLogRecords(): Collection + public function addReport(string $key, mixed $value): void { - return $this->logRecords; + $this->report[$key] = $value; } - /** - * @param Collection $logRecords - */ - public function setLogRecords(Collection $logRecords): self + public function getReport(string $key = null, mixed $default = null): mixed { - foreach ($logRecords as $logRecord) { - $this->addLogRecord($logRecord); + if (null === $key) { + return $this->report; } - return $this; + return $this->report[$key] ?? $default; } - public function getProcess(): Process + public function duration(string $format = '%H hour(s) %I min(s) %S s'): ?string { - return $this->process; - } - - public function getReport(): array - { - return $this->report ?? []; - } - - public function setReport(?array $report): self - { - $this->report = $report; - - return $this; + if (null === $this->endDate) { + return null; + } + $diff = $this->endDate->diff($this->startDate); + return $diff->format($format); } } diff --git a/src/Entity/ProcessExecutionLogRecord.php b/src/Entity/ProcessExecutionLogRecord.php deleted file mode 100644 index c70807b..0000000 --- a/src/Entity/ProcessExecutionLogRecord.php +++ /dev/null @@ -1,88 +0,0 @@ -logLevel = $logLevel; - $this->message = $message; - } - - public function getId(): ?int - { - return $this->id; - } - - public function getLogLevel(): int - { - return $this->logLevel; - } - - public function setLogLevel(int $logLevel): self - { - $this->logLevel = $logLevel; - - return $this; - } - - public function getMessage(): string - { - return $this->message; - } - - public function setMessage(string $message): self - { - $this->message = $message; - - return $this; - } - - public function setProcessExecution(ProcessExecution $processExecution): self - { - $this->processExecution = $processExecution; - - return $this; - } - - public function getProcessExecution(): ?ProcessExecution - { - return $this->processExecution; - } -} diff --git a/src/Entity/User.php b/src/Entity/User.php index 20dbed5..f89463e 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -4,49 +4,33 @@ namespace CleverAge\ProcessUiBundle\Entity; -use CleverAge\ProcessUiBundle\Repository\UserRepository; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; -/** - * @ORM\Entity(repositoryClass=UserRepository::class) - * @ORM\Table(name="user") - */ +#[ORM\Entity] +#[ORM\Table(name: 'process_user')] +#[ORM\Index(columns: ['email'], name: 'idx_process_user_email')] class User implements UserInterface, PasswordAuthenticatedUserInterface { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] private ?int $id; - /** - * @ORM\Column(type="string", length=255, unique=true) - */ + #[ORM\Column(type: 'string', length: 255, unique: true)] private ?string $email; - /** - * @ORM\Column(type="string", length=255, nullable=true) - */ + #[ORM\Column(type: 'string', length: 255, nullable: true)] private ?string $firstname; - /** - * @ORM\Column(type="string", length=255, nullable=true) - */ + #[ORM\Column(type: 'string', length: 255, nullable: true)] private ?string $lastname; - /** - * @ORM\Column(type="json") - * - * @var array - */ + #[ORM\Column(type: 'json')] private array $roles = []; - /** - * @ORM\Column(type="string") - */ + #[ORM\Column(type: 'string', length: 255, nullable: true)] private ?string $password; public function getId(): ?int @@ -110,11 +94,7 @@ public function getUsername(): string */ public function getRoles(): array { - $roles = $this->roles; - // guarantee every user at least has ROLE_USER - $roles[] = 'ROLE_USER'; - - return array_unique($roles); + return array_unique(['ROLE_USER'] + $this->roles); } /** diff --git a/src/Event/IncrementReportInfoEvent.php b/src/Event/IncrementReportInfoEvent.php deleted file mode 100644 index 4d10e7e..0000000 --- a/src/Event/IncrementReportInfoEvent.php +++ /dev/null @@ -1,28 +0,0 @@ -key = $key; - $this->processCode = $processCode; - } - - public function getKey(): string - { - return $this->key; - } - - public function getProcessCode(): string - { - return $this->processCode; - } -} diff --git a/src/Event/SetReportInfoEvent.php b/src/Event/SetReportInfoEvent.php deleted file mode 100644 index 20d14af..0000000 --- a/src/Event/SetReportInfoEvent.php +++ /dev/null @@ -1,35 +0,0 @@ -key = $key; - $this->value = $value; - $this->processCode = $processCode; - } - - public function getKey(): string - { - return $this->key; - } - - public function getValue(): mixed - { - return $this->value; - } - - public function getProcessCode(): string - { - return $this->processCode; - } -} diff --git a/src/EventSubscriber/Crud/ProcessCrudListener.php b/src/EventSubscriber/Crud/ProcessCrudListener.php deleted file mode 100644 index f8a7543..0000000 --- a/src/EventSubscriber/Crud/ProcessCrudListener.php +++ /dev/null @@ -1,38 +0,0 @@ -entityManager = $entityManager; - } - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return [BeforeCrudActionEvent::class => 'syncProcessIntoDatabase']; - } - - public function syncProcessIntoDatabase(BeforeCrudActionEvent $event): void - { - if (Process::class === $event->getAdminContext()?->getEntity()->getFqcn()) { - /** @var ProcessRepository $repository */ - $repository = $this->entityManager->getRepository(Process::class); - $repository->sync(); - } - } -} diff --git a/src/EventSubscriber/ProcessEventSubscriber.php b/src/EventSubscriber/ProcessEventSubscriber.php index e9ed92f..9ededc7 100644 --- a/src/EventSubscriber/ProcessEventSubscriber.php +++ b/src/EventSubscriber/ProcessEventSubscriber.php @@ -5,161 +5,68 @@ namespace CleverAge\ProcessUiBundle\EventSubscriber; use CleverAge\ProcessBundle\Event\ProcessEvent; -use CleverAge\ProcessUiBundle\Entity\Process; +use CleverAge\ProcessUiBundle\Entity\Enum\ProcessExecutionStatus; use CleverAge\ProcessUiBundle\Entity\ProcessExecution; -use CleverAge\ProcessUiBundle\Event\IncrementReportInfoEvent; -use CleverAge\ProcessUiBundle\Event\SetReportInfoEvent; -use CleverAge\ProcessUiBundle\Manager\ProcessUiConfigurationManager; -use CleverAge\ProcessUiBundle\Message\LogIndexerMessage; -use CleverAge\ProcessUiBundle\Monolog\Handler\ProcessLogHandler; -use CleverAge\ProcessUiBundle\Repository\ProcessRepository; -use DateTime; -use Doctrine\ORM\EntityManagerInterface; -use RuntimeException; -use SplFileObject; +use CleverAge\ProcessUiBundle\Manager\ProcessExecutionManager; +use CleverAge\ProcessUiBundle\Monolog\Handler\DoctrineProcessHandler; +use CleverAge\ProcessUiBundle\Monolog\Handler\ProcessHandler; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Uid\Uuid; -class ProcessEventSubscriber implements EventSubscriberInterface +final readonly class ProcessEventSubscriber implements EventSubscriberInterface { - private array $processExecution = []; - private EntityManagerInterface $entityManager; - private ProcessLogHandler $processLogHandler; - private MessageBusInterface $messageBus; - private ProcessUiConfigurationManager $processUiConfigurationManager; - private string $processLogDir; - private bool $indexLogs; - public function __construct( - EntityManagerInterface $entityManager, - ProcessLogHandler $processLogHandler, - MessageBusInterface $messageBus, - ProcessUiConfigurationManager $processUiConfigurationManager, - string $processLogDir, - bool $indexLogs + private ProcessHandler $processHandler, + private DoctrineProcessHandler $doctrineProcessHandler, + private ProcessExecutionManager $processExecutionManager ) { - $this->entityManager = $entityManager; - $this->processLogHandler = $processLogHandler; - $this->messageBus = $messageBus; - $this->processUiConfigurationManager = $processUiConfigurationManager; - $this->processLogDir = $processLogDir; - $this->indexLogs = $indexLogs; - } - - public static function getSubscribedEvents(): array - { - return [ - ProcessEvent::EVENT_PROCESS_STARTED => [ - ['syncProcessIntoDatabase', 1000], - ['onProcessStarted', 0], - ], - ProcessEvent::EVENT_PROCESS_ENDED => [ - ['onProcessEnded'], - ], - ProcessEvent::EVENT_PROCESS_FAILED => [ - ['onProcessFailed'], - ], - IncrementReportInfoEvent::NAME => [ - ['updateProcessExecutionReport'], - ], - SetReportInfoEvent::NAME => [ - ['updateProcessExecutionReport'], - ], - ]; } - public function onProcessStarted(ProcessEvent $event): void + public function onProcessStart(ProcessEvent $event): void { - $process = $this->entityManager->getRepository(Process::class) - ->findOneBy(['processCode' => $event->getProcessCode()]); - if (null === $process) { - throw new RuntimeException('Unable to found process into database.'); + if (false === $this->processHandler->hasFilename()) { + $this->processHandler->setFilename(sprintf('%s/%s.log', $event->getProcessCode(), Uuid::v4())); } - $processExecution = new ProcessExecution($process); - $processExecution->setProcessCode($event->getProcessCode()); - $processExecution->setSource($this->processUiConfigurationManager->getSource($event->getProcessCode())); - $processExecution->setTarget($this->processUiConfigurationManager->getTarget($event->getProcessCode())); - $logFilename = sprintf( - 'process_%s_%s.log', - $event->getProcessCode(), - sha1(uniqid((string) mt_rand(), true)) - ); - $this->processLogHandler->setLogFilename($logFilename, $event->getProcessCode()); - $this->processLogHandler->setCurrentProcessCode($event->getProcessCode()); - $processExecution->setLog($logFilename); - $this->entityManager->persist($processExecution); - $this->entityManager->flush(); - $this->processExecution[$event->getProcessCode()] = $processExecution; - } - - public function onProcessEnded(ProcessEvent $processEvent): void - { - if ($processExecution = ($this->processExecution[$processEvent->getProcessCode()] ?? null)) { - $this->processExecution = array_filter($this->processExecution); - array_pop($this->processExecution); - $this->processLogHandler->setCurrentProcessCode((string) array_key_last($this->processExecution)); - $processExecution->setEndDate(new DateTime()); - $processExecution->setStatus(ProcessExecution::STATUS_SUCCESS); - $processExecution->getProcess()->setLastExecutionDate($processExecution->getStartDate()); - $processExecution->getProcess()->setLastExecutionStatus( - ProcessExecution::STATUS_SUCCESS + if (null === $this->processExecutionManager->getCurrentProcessExecution()) { + $processExecution = new ProcessExecution( + $event->getProcessCode(), + basename($this->processHandler->getFilename()) ); - $this->entityManager->persist($processExecution); - $this->entityManager->flush(); - $this->dispatchLogIndexerMessage($processExecution); - $this->processExecution[$processEvent->getProcessCode()] = null; + $this->processExecutionManager->setCurrentProcessExecution($processExecution)->save(); } } - public function onProcessFailed(ProcessEvent $processEvent): void + public function success(ProcessEvent $event): void { - if ($processExecution = ($this->processExecution[$processEvent->getProcessCode()] ?? null)) { - $processExecution->setEndDate(new DateTime()); - $processExecution->setStatus(ProcessExecution::STATUS_FAIL); - $processExecution->getProcess()->setLastExecutionDate($processExecution->getStartDate()); - $processExecution->getProcess()->setLastExecutionStatus(ProcessExecution::STATUS_FAIL); - $this->entityManager->persist($processExecution); - $this->entityManager->flush(); - $this->dispatchLogIndexerMessage($processExecution); - $this->processExecution[$processEvent->getProcessCode()] = null; - } + $this->processExecutionManager->getCurrentProcessExecution()?->setStatus(ProcessExecutionStatus::Finish); + $this->processExecutionManager->getCurrentProcessExecution()?->end(); + $this->processExecutionManager->save()->unsetProcessExecution($event->getProcessCode()); } - public function syncProcessIntoDatabase(): void + public function fail(ProcessEvent $event): void { - /** @var ProcessRepository $repository */ - $repository = $this->entityManager->getRepository(Process::class); - $repository->sync(); + $this->processExecutionManager->getCurrentProcessExecution()?->setStatus(ProcessExecutionStatus::Failed); + $this->processExecutionManager->getCurrentProcessExecution()?->end(); + $this->processExecutionManager->save()->unsetProcessExecution($event->getProcessCode()); } - protected function dispatchLogIndexerMessage(ProcessExecution $processExecution): void + public function flushDoctrineLogs(ProcessEvent $event): void { - if ($this->indexLogs && null !== $processExecutionId = $processExecution->getId()) { - $filePath = $this->processLogDir.\DIRECTORY_SEPARATOR.$processExecution->getLog(); - $file = new SplFileObject($filePath); - $file->seek(\PHP_INT_MAX); - $chunkSize = LogIndexerMessage::DEFAULT_OFFSET; - $chunk = (int) ($file->key() / $chunkSize) + 1; - for ($i = 0; $i < $chunk; ++$i) { - $this->messageBus->dispatch( - new LogIndexerMessage( - $processExecutionId, - $this->processLogDir.\DIRECTORY_SEPARATOR.$processExecution->getLog(), - $i * $chunkSize - ) - ); - } - } + $this->doctrineProcessHandler->flush(); } - public function updateProcessExecutionReport(IncrementReportInfoEvent|SetReportInfoEvent $event): void + public static function getSubscribedEvents(): array { - if ($processExecution = ($this->processExecution[$event->getProcessCode()] ?? false)) { - $report = $processExecution->getReport(); - $event instanceof IncrementReportInfoEvent - ? $report[$event->getKey()] = ($report[$event->getKey()] ?? 0) + 1 - : $report[$event->getKey()] = $event->getValue(); - $processExecution->setReport($report); - } + return [ + ProcessEvent::EVENT_PROCESS_STARTED => 'onProcessStart', + ProcessEvent::EVENT_PROCESS_ENDED => [ + ['flushDoctrineLogs', 100], + ['success', 100], + ], + ProcessEvent::EVENT_PROCESS_FAILED => [ + ['flushDoctrineLogs', 100], + ['fail', 100], + ], + ]; } } diff --git a/src/Form/Type/ProcessUploadFileType.php b/src/Form/Type/ProcessUploadFileType.php new file mode 100644 index 0000000..cf476c7 --- /dev/null +++ b/src/Form/Type/ProcessUploadFileType.php @@ -0,0 +1,22 @@ +setRequired('process_code'); + } + + public function getParent(): string + { + return FileType::class; + } +} diff --git a/src/Http/ValueResolver/ProcessConfigurationValueResolver.php b/src/Http/ValueResolver/ProcessConfigurationValueResolver.php new file mode 100644 index 0000000..a8c2a89 --- /dev/null +++ b/src/Http/ValueResolver/ProcessConfigurationValueResolver.php @@ -0,0 +1,24 @@ +registry->getProcessConfiguration($request->get('process'))]; + } +} diff --git a/src/Manager/ProcessExecutionManager.php b/src/Manager/ProcessExecutionManager.php new file mode 100644 index 0000000..cc4ab7c --- /dev/null +++ b/src/Manager/ProcessExecutionManager.php @@ -0,0 +1,62 @@ +currentProcessExecution) { + $this->currentProcessExecution = $processExecution; + } + + return $this; + } + + public function getCurrentProcessExecution(): ?ProcessExecution + { + return $this->currentProcessExecution; + } + + public function unsetProcessExecution(string $processCode): self + { + if ($this->currentProcessExecution?->code === $processCode) { + $this->currentProcessExecution = null; + } + + return $this; + } + + public function save(): self + { + if (null !== $this->currentProcessExecution) { + $this->processExecutionRepository->save($this->currentProcessExecution); + } + + return $this; + } + + public function increment(string $incrementKey, int $step = 1): void + { + $this->currentProcessExecution?->addReport( + $incrementKey, + $this->currentProcessExecution->getReport($incrementKey, 0) + $step + ); + } + + public function setReport(string $incrementKey, string $value): void + { + $this->currentProcessExecution?->addReport($incrementKey, $value); + } +} diff --git a/src/Manager/ProcessUiConfigurationManager.php b/src/Manager/ProcessUiConfigurationManager.php deleted file mode 100644 index d852b1e..0000000 --- a/src/Manager/ProcessUiConfigurationManager.php +++ /dev/null @@ -1,97 +0,0 @@ -processConfigurationRegistry = $processConfigurationRegistry; - } - - /** - * @return array - */ - public function getProcessChoices(): array - { - return array_map(static fn (ProcessConfiguration $configuration) => $configuration->getCode(), $this->processConfigurationRegistry->getProcessConfigurations()); - } - - /** - * @return array - */ - public function getSourceChoices(): array - { - $sources = []; - foreach ($this->processConfigurationRegistry->getProcessConfigurations() as $configuration) { - $source = $this->getSource($configuration->getCode()); - $sources[(string) $source] = (string) $source; - } - - return $sources; - } - - /** - * @return array - */ - public function getTargetChoices(): array - { - $targets = []; - foreach ($this->processConfigurationRegistry->getProcessConfigurations() as $configuration) { - $target = $this->getTarget($configuration->getCode()); - $targets[(string) $target] = (string) $target; - } - - return $targets; - } - - public function getSource(Process|string $process): ?string - { - return $this->resolveUiOptions($process)[self::UI_OPTION_SOURCE]; - } - - public function getTarget(Process|string $process): ?string - { - return $this->resolveUiOptions($process)[self::UI_OPTION_TARGET]; - } - - public function canRun(Process|string $process): bool - { - return (bool) $this->resolveUiOptions($process)[self::UI_OPTION_RUN]; - } - - /** - * @return array - */ - private function resolveUiOptions(Process|string $process): array - { - $code = $process instanceof Process ? $process->getProcessCode() : $process; - $resolver = new OptionsResolver(); - - $resolver->setDefaults([ - self::UI_OPTION_SOURCE => null, - self::UI_OPTION_TARGET => null, - self::UI_OPTION_RUN => true, - ]); - $resolver->setAllowedTypes(self::UI_OPTION_RUN, 'bool'); - $resolver->setAllowedTypes(self::UI_OPTION_SOURCE, ['string', 'null']); - $resolver->setAllowedTypes(self::UI_OPTION_TARGET, ['string', 'null']); - - return $resolver->resolve( - $this->processConfigurationRegistry->getProcessConfiguration($code)->getOptions()['ui_options'] ?? [] - ); - } -} diff --git a/src/Message/LogIndexerHandler.php b/src/Message/LogIndexerHandler.php deleted file mode 100644 index deced04..0000000 --- a/src/Message/LogIndexerHandler.php +++ /dev/null @@ -1,62 +0,0 @@ -managerRegistry = $managerRegistry; - } - - public function __invoke(LogIndexerMessage $logIndexerMessage): void - { - /** @var EntityManagerInterface $manager */ - $manager = $this->managerRegistry->getManagerForClass(ProcessExecutionLogRecord::class); - $table = $manager->getClassMetadata(ProcessExecutionLogRecord::class)->getTableName(); - $file = new \SplFileObject($logIndexerMessage->getLogPath()); - $file->seek($logIndexerMessage->getStart()); - $offset = $logIndexerMessage->getOffset(); - $parser = new LineLogParser(); - $parameters = []; - while ($offset > 0 && !$file->eof()) { - /** @var string $currentLine */ - $currentLine = $file->current(); - $parsedLine = $parser->parse($currentLine); - if (!empty($parsedLine) && true === ($parsedLine['context'][self::INDEX_LOG_RECORD] ?? false)) { - $parameters[] = $logIndexerMessage->getProcessExecutionId(); - $parameters[] = Logger::toMonologLevel($parsedLine['level']); - $parameters[] = substr($parsedLine['message'], 0, 255); - } - $file->next(); - --$offset; - } - if (\count($parameters) > 0) { - $statement = $this->getStatement($table, (int) (\count($parameters) / 3)); - $manager->getConnection()->executeStatement($statement, $parameters); - } - } - - private function getStatement(string $table, int $size): string - { - $sql = 'INSERT INTO '.$table.' (process_execution_id, log_level, message) VALUES '; - while ($size > 0) { - $sql .= $size > 1 ? '(?, ?, ?),' : '(?, ?, ?)'; - --$size; - } - - return $sql; - } -} diff --git a/src/Message/LogIndexerMessage.php b/src/Message/LogIndexerMessage.php deleted file mode 100644 index 760b63d..0000000 --- a/src/Message/LogIndexerMessage.php +++ /dev/null @@ -1,46 +0,0 @@ -processExecutionId = $processExecutionId; - $this->logPath = $logPath; - $this->start = $start; - $this->offset = $offset; - } - - public function getProcessExecutionId(): int - { - return $this->processExecutionId; - } - - public function getLogPath(): string - { - return $this->logPath; - } - - public function getStart(): int - { - return $this->start; - } - - public function getOffset(): int - { - return $this->offset; - } -} diff --git a/src/Message/ProcessExecuteHandler.php b/src/Message/ProcessExecuteHandler.php new file mode 100644 index 0000000..fcff027 --- /dev/null +++ b/src/Message/ProcessExecuteHandler.php @@ -0,0 +1,21 @@ +manager->execute($message->code, $message->input); + } +} diff --git a/src/Message/ProcessExecuteMessage.php b/src/Message/ProcessExecuteMessage.php new file mode 100644 index 0000000..e17b9d6 --- /dev/null +++ b/src/Message/ProcessExecuteMessage.php @@ -0,0 +1,12 @@ +command = $command; - } - - /** - * @throws Exception - */ - public function __invoke(ProcessRunMessage $processRunMessage): void - { - $this->command->run( - new ArrayInput( - [ - 'processCodes' => [$processRunMessage->getProcessCode()], - ] - ), - new NullOutput() - ); - } -} diff --git a/src/Message/ProcessRunMessage.php b/src/Message/ProcessRunMessage.php deleted file mode 100644 index 5b9682a..0000000 --- a/src/Message/ProcessRunMessage.php +++ /dev/null @@ -1,35 +0,0 @@ - */ - private array $processInput; - - /** - * @param array $processInput - */ - public function __construct(string $processCode, array $processInput = []) - { - $this->processCode = $processCode; - $this->processInput = $processInput; - } - - public function getProcessCode(): string - { - return $this->processCode; - } - - /** - * @return array - */ - public function getProcessInput(): array - { - return $this->processInput; - } -} diff --git a/src/Migrations/Version20210903142035.php b/src/Migrations/Version20210903142035.php deleted file mode 100644 index 88e869c..0000000 --- a/src/Migrations/Version20210903142035.php +++ /dev/null @@ -1,107 +0,0 @@ -addSql(<<addSql(<<addSql(<<addSql(<<addSql(<<addSql('ALTER TABLE process_execution ADD process_id INT DEFAULT NULL'); - $this->addSql(<<addSql('CREATE INDEX IDX_98E995D27EC2F574 ON process_execution (process_id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE process'); - $this->addSql('DROP TABLE process_execution'); - $this->addSql('DROP TABLE process_execution_log_record'); - $this->addSql('DROP TABLE user'); - } - - public function isTransactional(): bool - { - return false; - } -} diff --git a/src/Migrations/Version20211028081845.php b/src/Migrations/Version20211028081845.php deleted file mode 100644 index 0b2ff9e..0000000 --- a/src/Migrations/Version20211028081845.php +++ /dev/null @@ -1,36 +0,0 @@ -addSql('ALTER TABLE process_execution ADD report JSON DEFAULT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE process_execution DROP report'); - } - - public function isTransactional(): bool - { - return false; - } -} diff --git a/src/Migrations/Version20231006111525.php b/src/Migrations/Version20231006111525.php new file mode 100644 index 0000000..7a45b2a --- /dev/null +++ b/src/Migrations/Version20231006111525.php @@ -0,0 +1,87 @@ +hasTable('log_record')) { + $this->addSql( + <<addSql('CREATE INDEX IDX_8ECECC333DAC0075 ON log_record (process_execution_id)'); + $this->addSql('CREATE INDEX idx_log_record_level ON log_record (level)'); + $this->addSql('CREATE INDEX idx_log_record_created_at ON log_record (created_at)'); + } + + if (!$schema->hasTable('process_execution')) { + $this->addSql(<<addSql( + <<addSql('CREATE INDEX idx_process_execution_code ON process_execution (code)'); + $this->addSql('CREATE INDEX idx_process_execution_start_date ON process_execution (start_date)'); + } + + if (!$schema->hasTable('process_user')) { + $this->addSql(<<addSql('CREATE UNIQUE INDEX UNIQ_627A047CE7927C74 ON process_user (email)'); + $this->addSql('CREATE INDEX idx_process_user_email ON process_user (email)'); + } + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE log_record DROP CONSTRAINT FK_8ECECC333DAC0075'); + $this->addSql('DROP TABLE log_record'); + $this->addSql('DROP TABLE process_execution'); + $this->addSql('DROP TABLE process_user'); + } +} diff --git a/src/Monolog/Handler/DoctrineProcessHandler.php b/src/Monolog/Handler/DoctrineProcessHandler.php new file mode 100644 index 0000000..fee6318 --- /dev/null +++ b/src/Monolog/Handler/DoctrineProcessHandler.php @@ -0,0 +1,77 @@ + */ + private ArrayCollection $records; + private ?ProcessExecutionManager $processExecutionManager; + private ?EntityManagerInterface $em = null; + private bool $enabled = false; + + public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->records = new ArrayCollection(); + } + + #[Required] + public function setEntityManager(EntityManagerInterface $em): void + { + $this->em = $em; + } + + #[Required] + public function setProcessExecutionManager(ProcessExecutionManager $processExecutionManager): void + { + $this->processExecutionManager = $processExecutionManager; + } + + public function setEnabled(bool $flag): void + { + $this->enabled = $flag; + } + + public function __destruct() + { + $this->flush(); + parent::__destruct(); + } + + public function flush(): void + { + foreach ($this->records as $record) { + if ($currentProcessExecution = $this->processExecutionManager->getCurrentProcessExecution()) { + $entity = new \CleverAge\ProcessUiBundle\Entity\LogRecord($record, $currentProcessExecution); + $this->em?->persist($entity); + } + } + $this->em?->flush(); + foreach ($this->records as $record) { + $this->em?->detach($record); + } + $this->records = new ArrayCollection(); + } + + protected function write(LogRecord $record): void + { + if (false === $this->enabled) { + return; + } + $this->records->add($record); + if (500 === $this->records->count()) { + $this->flush(); + } + } +} diff --git a/src/Monolog/Handler/ProcessHandler.php b/src/Monolog/Handler/ProcessHandler.php new file mode 100644 index 0000000..4484148 --- /dev/null +++ b/src/Monolog/Handler/ProcessHandler.php @@ -0,0 +1,54 @@ +directory = $directory; + $this->reportIncrementLevel = Level::Error; + parent::__construct($directory); + } + + public function hasFilename(): bool + { + return $this->directory !== $this->url; + } + + public function setFilename(string $filename): void + { + $this->url = sprintf('%s/%s', $this->directory, $filename); + } + + public function getFilename(): ?string + { + return $this->url; + } + + public function write(LogRecord $record): void + { + parent::write($record); + if ($record->level->value >= $this->reportIncrementLevel->value) { + $this->processExecutionManager->increment($record->level->name); + } + } + + public function setReportIncrementLevel(string $level): void + { + $this->reportIncrementLevel = Level::fromName($level); + } +} diff --git a/src/Monolog/Handler/ProcessLogHandler.php b/src/Monolog/Handler/ProcessLogHandler.php deleted file mode 100644 index 1278e5f..0000000 --- a/src/Monolog/Handler/ProcessLogHandler.php +++ /dev/null @@ -1,66 +0,0 @@ -logDir = $processLogDir; - } - - /** - * @param array $record - * - * @throws FilesystemException - */ - protected function write(array $record): void - { - if (null === $logFilename = ($this->logFilenames[$this->currentProcessCode] ?? null)) { - return; - } - - if ($record['level'] < Logger::INFO) { - return; - } - - if (null === $this->filesystem) { - $this->filesystem = new Filesystem( - new LocalFilesystemAdapter($this->logDir, null, \FILE_APPEND) - ); - } - $this->filesystem->write($logFilename, $record['formatted']); - } - - public function setLogFilename(string $logFilename, string $processCode): void - { - $this->logFilenames[$processCode] = $logFilename; - } - - public function setCurrentProcessCode(?string $code): void - { - $this->currentProcessCode = $code; - } - - public function getLogFilename(): ?string - { - return $this->logFilenames[$this->currentProcessCode] ?? null; - } -} diff --git a/src/Repository/.gitignore b/src/Repository/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/src/Repository/ProcessExecutionRepository.php b/src/Repository/ProcessExecutionRepository.php index 9294fa7..b761ff7 100644 --- a/src/Repository/ProcessExecutionRepository.php +++ b/src/Repository/ProcessExecutionRepository.php @@ -10,6 +10,11 @@ /** * @extends ServiceEntityRepository + * + * @method ProcessExecution|null find($id, $lockMode = null, $lockVersion = null) + * @method ProcessExecution|null findOneBy(array $criteria, array $orderBy = null) + * @method ProcessExecution[] findAll() + * @method ProcessExecution[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ class ProcessExecutionRepository extends ServiceEntityRepository { @@ -18,61 +23,22 @@ public function __construct(ManagerRegistry $registry) parent::__construct($registry, ProcessExecution::class); } - /** - * @return array - */ - public function getProcessCodeChoices(): array + public function save(ProcessExecution $processExecution): void { - $choices = []; - $qb = $this->createQueryBuilder('pe'); - $qb->distinct(true); - $qb->select('pe.processCode'); - foreach ($qb->getQuery()->getArrayResult() as $result) { - $choices[(string) $result['processCode']] = (string) $result['processCode']; - } - - return $choices; - } - - /** - * @return array - */ - public function getSourceChoices(): array - { - $choices = []; - $qb = $this->createQueryBuilder('pe'); - $qb->distinct(true); - $qb->select('pe.source'); - foreach ($qb->getQuery()->getArrayResult() as $result) { - $choices[(string) $result['source']] = (string) $result['source']; - } - - return $choices; + $this->_em->persist($processExecution); + $this->_em->flush(); } - /** - * @return array - */ - public function getTargetChoices(): array + public function getLastProcessExecution(string $code): ?ProcessExecution { - $choices = []; - $qb = $this->createQueryBuilder('pe'); - $qb->distinct(true); - $qb->select('pe.target'); - foreach ($qb->getQuery()->getArrayResult() as $result) { - $choices[(string) $result['target']] = (string) $result['target']; - } - - return $choices; - } - - public function deleteBefore(\DateTime $dateTime): void - { - $qb = $this->createQueryBuilder('pe'); - $qb->delete(); - $qb->where('pe.startDate < :date'); - $qb->setParameter('date', $dateTime); - - $qb->getQuery()->execute(); + $qb = $this->_em->createQueryBuilder(); + + return $qb->select('pe') + ->from(ProcessExecution::class, 'pe') + ->where($qb->expr()->eq('pe.code', $qb->expr()->literal($code))) + ->orderBy('pe.startDate', 'DESC') + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); } } diff --git a/src/Repository/ProcessRepository.php b/src/Repository/ProcessRepository.php deleted file mode 100644 index 71140df..0000000 --- a/src/Repository/ProcessRepository.php +++ /dev/null @@ -1,66 +0,0 @@ - - */ -class ProcessRepository extends ServiceEntityRepository -{ - private ProcessUiConfigurationManager $processUiConfigurationManager; - private ProcessConfigurationRegistry $processConfigurationRegistry; - - /** - * @required - */ - public function setProcessUiConfigurationManager(ProcessUiConfigurationManager $processUiConfigurationManager): void - { - $this->processUiConfigurationManager = $processUiConfigurationManager; - } - - /** - * @required - */ - public function setProcessConfigurationRegistry(ProcessConfigurationRegistry $processConfigurationRegistry): void - { - $this->processConfigurationRegistry = $processConfigurationRegistry; - } - - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, Process::class); - } - - public function sync(): void - { - // Create missing process into database - $codes = []; - foreach ($this->processConfigurationRegistry->getProcessConfigurations() as $configuration) { - $process = $this->findOneBy(['processCode' => $configuration->getCode()]); - $codes[] = $configuration->getCode(); - if (null === $process) { - $process = new Process( - $configuration->getCode(), - $this->processUiConfigurationManager->getSource($configuration->getCode()), - $this->processUiConfigurationManager->getTarget($configuration->getCode()), - ); - $this->getEntityManager()->persist($process); - } - } - $this->getEntityManager()->flush(); - - // Delete process in database if not into configuration registry - $qb = $this->createQueryBuilder('p'); - $qb->delete(); - $qb->where($qb->expr()->notIn('p.processCode', $codes)); - $qb->getQuery()->execute(); - } -} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php deleted file mode 100644 index e03ffa9..0000000 --- a/src/Repository/UserRepository.php +++ /dev/null @@ -1,37 +0,0 @@ - - */ -class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface -{ - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, User::class); - } - - /** - * Used to upgrade (rehash) the user's password automatically over time. - */ - public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newEncodedPassword): void - { - if (!$user instanceof User) { - throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); - } - - $user->setPassword($newEncodedPassword); - $this->_em->persist($user); - $this->_em->flush(); - } -} diff --git a/src/Security/LoginFormAuthAuthenticator.php b/src/Security/LoginFormAuthAuthenticator.php deleted file mode 100644 index fc08421..0000000 --- a/src/Security/LoginFormAuthAuthenticator.php +++ /dev/null @@ -1,62 +0,0 @@ -urlGenerator = $urlGenerator; - } - - public function authenticate(Request $request): PassportInterface - { - $username = (string) $request->request->get('email', ''); - - $request->getSession()->set(Security::LAST_USERNAME, $username); - - return new Passport( - new UserBadge($username), - new PasswordCredentials((string) $request->request->get('password', '')), - [ - new CsrfTokenBadge('authenticate', $request->get('_csrf_token')), - ] - ); - } - - public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response - { - if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) { - return new RedirectResponse($targetPath); - } - - return new RedirectResponse($this->urlGenerator->generate('process_admin')); - } - - protected function getLoginUrl(Request $request): string - { - return $this->urlGenerator->generate(self::LOGIN_ROUTE); - } -} diff --git a/src/Twig/Extension/LogLevelExtension.php b/src/Twig/Extension/LogLevelExtension.php new file mode 100644 index 0000000..2c93258 --- /dev/null +++ b/src/Twig/Extension/LogLevelExtension.php @@ -0,0 +1,20 @@ +getName(); + } + + public function getCssClass(string|int $value): string + { + return is_int($value) ? + match ($value) { + Level::Warning->value => 'warning', + Level::Error->value, Level::Emergency->value, Level::Critical->value, Level::Alert->value => 'danger', + Level::Debug->value, Level::Info->value => 'success', + default => '' + } + : match ($value) { + Level::Warning->name => 'warning', + Level::Error->name, Level::Emergency->name, Level::Critical->name, Level::Alert->name => 'danger', + Level::Debug->name, Level::Info->name => 'success', + default => '' + }; + } +} diff --git a/src/Twig/Runtime/MD5ExtensionRuntime.php b/src/Twig/Runtime/MD5ExtensionRuntime.php new file mode 100644 index 0000000..40bd3f1 --- /dev/null +++ b/src/Twig/Runtime/MD5ExtensionRuntime.php @@ -0,0 +1,15 @@ +processExecutionRepository->getLastProcessExecution($code); + } + + public function getProcessSource(string $code): ?string + { + return $this->processConfigurationRegistry + ->getProcessConfiguration($code)?->getOptions()['ui']['source'] ?? null; + } + + public function getProcessTarget(string $code): ?string + { + return $this->processConfigurationRegistry + ->getProcessConfiguration($code)?->getOptions()['ui']['target'] ?? null; + } +} diff --git a/templates/admin/field/array.html.twig b/templates/admin/field/array.html.twig new file mode 100644 index 0000000..8cdf10d --- /dev/null +++ b/templates/admin/field/array.html.twig @@ -0,0 +1,16 @@ +{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} +{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} +{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} +
    + {{ _self.render(field.value) }} +
+ +{% macro render(value) %} + {% for key, item in value %} + {% if item is iterable %} +
  • {{ key }}
    • {{ _self.render(item) }}
    + {% else %} +
  • {{ key }} : {{ item }}
  • + {% endif %} + {% endfor %} +{% endmacro %} diff --git a/templates/admin/field/enum.html.twig b/templates/admin/field/enum.html.twig new file mode 100644 index 0000000..e615276 --- /dev/null +++ b/templates/admin/field/enum.html.twig @@ -0,0 +1,2 @@ +{% set class = field.value.value == 'failed' ? 'danger' : 'success' %} +{{ field.value.value }} diff --git a/templates/admin/field/log_level.html.twig b/templates/admin/field/log_level.html.twig new file mode 100644 index 0000000..2a666ad --- /dev/null +++ b/templates/admin/field/log_level.html.twig @@ -0,0 +1 @@ +{{ log_label(field.value) }} diff --git a/templates/admin/field/process_source.html.twig b/templates/admin/field/process_source.html.twig new file mode 100644 index 0000000..fea3203 --- /dev/null +++ b/templates/admin/field/process_source.html.twig @@ -0,0 +1 @@ +{{ get_process_source(entity.instance.code) }} diff --git a/templates/admin/field/process_target.html.twig b/templates/admin/field/process_target.html.twig new file mode 100644 index 0000000..15470e6 --- /dev/null +++ b/templates/admin/field/process_target.html.twig @@ -0,0 +1 @@ +{{ get_process_target(entity.instance.code) }} diff --git a/templates/admin/field/report.html.twig b/templates/admin/field/report.html.twig new file mode 100644 index 0000000..c24b268 --- /dev/null +++ b/templates/admin/field/report.html.twig @@ -0,0 +1,8 @@ +{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} +{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} +{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} +
      + {% for key, item in field.value %} +
    • {{ key }} : {{ item }}
    • + {% endfor %} +
    diff --git a/templates/admin/login.html.twig b/templates/admin/login.html.twig new file mode 100644 index 0000000..1d1d077 --- /dev/null +++ b/templates/admin/login.html.twig @@ -0,0 +1 @@ +{% extends '@EasyAdmin/page/login.html.twig' %} diff --git a/templates/admin/process/list.html.twig b/templates/admin/process/list.html.twig new file mode 100644 index 0000000..514cd4f --- /dev/null +++ b/templates/admin/process/list.html.twig @@ -0,0 +1,104 @@ +{# @var urlGenerator \EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator #} +{% extends ea.templatePath('layout') %} +{% trans_default_domain ea.i18n.translationDomain %} + +{% block main %} + + + {% block table_head %} + + + + + + + + + {% endblock %} + + + {% block table_body %} + {# @var process \CleverAge\ProcessBundle\Configuration\ProcessConfiguration #} + {% for process in processes %} + {% set lastExecution = get_last_execution_date(process.code) %} + {% set statusClass = '' %} + {% if lastExecution is not null %} + {% set statusClass = lastExecution.status.value == 'failed' ? 'danger' : 'success' %} + {% endif %} + + + + + + + + + {% endfor %} + {% endblock %} + +
    Process codeLast executionStatusSourceTargetActions
    {{ process.code }}{% if lastExecution is not null %}{{ lastExecution.startDate|date('Y/m/d H:i:s') }}{% endif %}{% if lastExecution is not null %}{{ lastExecution.status.value }}{% endif %}{% if process.options.ui.source is defined %}{{ process.options.ui.source }}{% endif %}{% if process.options.ui.target is defined %}{{ process.options.ui.target }}{% endif %} + {% if process.options.ui.upload_and_run is defined and true == process.options.ui.upload_and_run %} + + + + {% endif %} + {% if process.options.ui.run is defined and true == process.options.ui.run %} + + + + {% endif %} + + + +
    + {% for process in processes %} + + + + {% endfor %} +{% endblock %} diff --git a/templates/admin/process/upload_and_execute.html.twig b/templates/admin/process/upload_and_execute.html.twig new file mode 100644 index 0000000..53160fc --- /dev/null +++ b/templates/admin/process/upload_and_execute.html.twig @@ -0,0 +1,23 @@ +{% extends ea.templatePath('layout') %} +{% trans_default_domain ea.i18n.translationDomain %} + +{% block main %} +
    +
    +
    + {{ 'Upload you file then click on button to run process in background' }} +
    +
    +
    +
    + {{ form_start(form) }} + {{ form_widget(form) }} +
    + +
    + {{ form_end(form) }} +
    +
    +
    + +{% endblock %} From 56b0e8d91e905ea94034be1574e2ba32da8a4047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Tonon?= Date: Fri, 6 Oct 2023 15:23:43 +0200 Subject: [PATCH 02/39] Add dependency to symfony/runtime --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 157e049..fc0c2c2 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "symfony/uid": "^6.3", "symfony/string": "^6.3", "symfony/messenger": "^6.3", + "symfony/runtime": "^6.3", "symfony/doctrine-messenger": "^6.3", "easycorp/easyadmin-bundle": "^4.7" }, From d7bb55bf402b7d10c65686ffec09b1c60787f867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Tonon?= Date: Fri, 6 Oct 2023 17:14:57 +0200 Subject: [PATCH 03/39] Add migration for mysql, postresql & sqlite --- src/Migrations/Version20231006111525.php | 115 ++++++++++++----------- 1 file changed, 61 insertions(+), 54 deletions(-) diff --git a/src/Migrations/Version20231006111525.php b/src/Migrations/Version20231006111525.php index 7a45b2a..3fe032f 100644 --- a/src/Migrations/Version20231006111525.php +++ b/src/Migrations/Version20231006111525.php @@ -16,64 +16,71 @@ public function getDescription(): string public function up(Schema $schema): void { - // this up() migration is auto-generated, please modify it to your needs - if (!$schema->hasTable('log_record')) { - $this->addSql( - <<addSql('CREATE INDEX IDX_8ECECC333DAC0075 ON log_record (process_execution_id)'); - $this->addSql('CREATE INDEX idx_log_record_level ON log_record (level)'); - $this->addSql('CREATE INDEX idx_log_record_created_at ON log_record (created_at)'); + $platform = $this->connection->getDatabasePlatform()->getName(); + if ("sqlite" === $platform) { + if (!$schema->hasTable('log_record')) { + $this->addSql('CREATE TABLE log_record (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, process_execution_id INTEGER DEFAULT NULL, channel VARCHAR(64) NOT NULL, level INTEGER NOT NULL, message VARCHAR(512) NOT NULL, context CLOB NOT NULL --(DC2Type:json) + , created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) + , CONSTRAINT FK_8ECECC333DAC0075 FOREIGN KEY (process_execution_id) REFERENCES process_execution (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('CREATE INDEX IDX_8ECECC333DAC0075 ON log_record (process_execution_id)'); + $this->addSql('CREATE INDEX idx_log_record_level ON log_record (level)'); + $this->addSql('CREATE INDEX idx_log_record_created_at ON log_record (created_at)'); + } + if (!$schema->hasTable('process_execution')) { + $this->addSql('CREATE TABLE process_execution (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, code VARCHAR(255) NOT NULL, log_filename VARCHAR(255) NOT NULL, start_date DATETIME NOT NULL --(DC2Type:datetime_immutable) + , end_date DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , status VARCHAR(255) NOT NULL, report CLOB NOT NULL --(DC2Type:json) + )'); + $this->addSql('CREATE INDEX idx_process_execution_code ON process_execution (code)'); + $this->addSql('CREATE INDEX idx_process_execution_start_date ON process_execution (start_date)'); + } + if (!$schema->hasTable('process_user')) { + $this->addSql('CREATE TABLE process_user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, firstname VARCHAR(255) DEFAULT NULL, lastname VARCHAR(255) DEFAULT NULL, roles CLOB NOT NULL --(DC2Type:json) + , password VARCHAR(255) DEFAULT NULL)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_627A047CE7927C74 ON process_user (email)'); + $this->addSql('CREATE INDEX idx_process_user_email ON process_user (email)'); + } } - if (!$schema->hasTable('process_execution')) { - $this->addSql(<<addSql( - <<addSql('CREATE INDEX idx_process_execution_code ON process_execution (code)'); - $this->addSql('CREATE INDEX idx_process_execution_start_date ON process_execution (start_date)'); + if ("mysql" === $platform) { + if (!$schema->hasTable('log_record')) { + $this->addSql('CREATE TABLE log_record (id INT AUTO_INCREMENT NOT NULL, process_execution_id INT DEFAULT NULL, channel VARCHAR(64) NOT NULL, level INT NOT NULL, message VARCHAR(512) NOT NULL, context JSON NOT NULL COMMENT \'(DC2Type:json)\', created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_8ECECC333DAC0075 (process_execution_id), INDEX idx_log_record_level (level), INDEX idx_log_record_created_at (created_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + if (!$schema->hasTable('process_execution')) { + $this->addSql('CREATE TABLE process_execution (id INT AUTO_INCREMENT NOT NULL, code VARCHAR(255) NOT NULL, log_filename VARCHAR(255) NOT NULL, start_date DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', end_date DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', status VARCHAR(255) NOT NULL, report JSON NOT NULL COMMENT \'(DC2Type:json)\', INDEX idx_process_execution_code (code), INDEX idx_process_execution_start_date (start_date), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE log_record ADD CONSTRAINT FK_8ECECC333DAC0075 FOREIGN KEY (process_execution_id) REFERENCES process_execution (id) ON DELETE CASCADE'); + } + if (!$schema->hasTable('process_user')) { + $this->addSql('CREATE TABLE process_user (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, firstname VARCHAR(255) DEFAULT NULL, lastname VARCHAR(255) DEFAULT NULL, roles JSON NOT NULL COMMENT \'(DC2Type:json)\', password VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_627A047CE7927C74 (email), INDEX idx_process_user_email (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } } - if (!$schema->hasTable('process_user')) { - $this->addSql(<<addSql('CREATE UNIQUE INDEX UNIQ_627A047CE7927C74 ON process_user (email)'); - $this->addSql('CREATE INDEX idx_process_user_email ON process_user (email)'); + if ("postgresql" === $platform) { + if (!$schema->hasTable('log_record')) { + $this->addSql('CREATE SEQUENCE log_record_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE log_record (id INT NOT NULL, process_execution_id INT DEFAULT NULL, channel VARCHAR(64) NOT NULL, level INT NOT NULL, message VARCHAR(512) NOT NULL, context JSON NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_8ECECC333DAC0075 ON log_record (process_execution_id)'); + $this->addSql('CREATE INDEX idx_log_record_level ON log_record (level)'); + $this->addSql('CREATE INDEX idx_log_record_created_at ON log_record (created_at)'); + $this->addSql('COMMENT ON COLUMN log_record.created_at IS \'(DC2Type:datetime_immutable)\''); + } + if (!$schema->hasTable('process_execution')) { + $this->addSql('CREATE SEQUENCE process_execution_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE process_execution (id INT NOT NULL, code VARCHAR(255) NOT NULL, log_filename VARCHAR(255) NOT NULL, start_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, end_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, status VARCHAR(255) NOT NULL, report JSON NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX idx_process_execution_code ON process_execution (code)'); + $this->addSql('CREATE INDEX idx_process_execution_start_date ON process_execution (start_date)'); + $this->addSql('COMMENT ON COLUMN process_execution.start_date IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN process_execution.end_date IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE log_record ADD CONSTRAINT FK_8ECECC333DAC0075 FOREIGN KEY (process_execution_id) REFERENCES process_execution (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + if (!$schema->hasTable('process_user')) { + $this->addSql('CREATE SEQUENCE process_user_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE process_user (id INT NOT NULL, email VARCHAR(255) NOT NULL, firstname VARCHAR(255) DEFAULT NULL, lastname VARCHAR(255) DEFAULT NULL, roles JSON NOT NULL, password VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_627A047CE7927C74 ON process_user (email)'); + $this->addSql('CREATE INDEX idx_process_user_email ON process_user (email)'); + } + + } } From 0b945b9289d02b6d0a806dcff35b69f6bc1a67dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Tonon?= Date: Mon, 9 Oct 2023 09:41:48 +0200 Subject: [PATCH 04/39] Lint twig templates --- composer.json | 3 +++ templates/admin/process/list.html.twig | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fc0c2c2..c418223 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,9 @@ "symfony/doctrine-messenger": "^6.3", "easycorp/easyadmin-bundle": "^4.7" }, + "require-dev": { + "vincentlanglet/twig-cs-fixer": "1.4.0" + }, "config": { "optimize-autoloader": true, "preferred-install": { diff --git a/templates/admin/process/list.html.twig b/templates/admin/process/list.html.twig index 514cd4f..5682e7b 100644 --- a/templates/admin/process/list.html.twig +++ b/templates/admin/process/list.html.twig @@ -33,7 +33,7 @@ {% if process.options.ui.target is defined %}{{ process.options.ui.target }}{% endif %} {% if process.options.ui.upload_and_run is defined and true == process.options.ui.upload_and_run %} - + {% endif %} From 4b22326a3216e61228813b0ce876adc70caecaac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 16:13:15 +0000 Subject: [PATCH 05/39] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/php.yml | 2 +- .github/workflows/super-linter.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index da34859..8231bcc 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -10,7 +10,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Validate composer.json and composer.lock run: composer validate --strict - name: Cache Composer packages diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index 2f75c6f..c8b12a9 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -11,7 +11,7 @@ jobs: name: PHP-CS-Fixer runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # see https://github.com/OskarStark/php-cs-fixer-ga - name: PHP-CS-Fixer uses: docker://oskarstark/php-cs-fixer-ga From 9db1f591b2233fbb9dd5c2d36f529d5d08217951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Tonon?= Date: Wed, 13 Dec 2023 16:20:13 +0100 Subject: [PATCH 06/39] [Update symfony & easy admin] --- composer.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index c418223..ec1fd81 100644 --- a/composer.json +++ b/composer.json @@ -23,13 +23,13 @@ "cleverage/process-bundle": "dev-v4-dev", "symfony/flex": "^2", "symfony/orm-pack": "^v2.4", - "symfony/dotenv": "^6.3", - "symfony/uid": "^6.3", - "symfony/string": "^6.3", - "symfony/messenger": "^6.3", - "symfony/runtime": "^6.3", - "symfony/doctrine-messenger": "^6.3", - "easycorp/easyadmin-bundle": "^4.7" + "symfony/dotenv": "^6.4", + "symfony/uid": "^6.4", + "symfony/string": "^6.4", + "symfony/messenger": "^6.4", + "symfony/runtime": "^6.4", + "symfony/doctrine-messenger": "^6.4", + "easycorp/easyadmin-bundle": "^4.8" }, "require-dev": { "vincentlanglet/twig-cs-fixer": "1.4.0" From 0adf9a767b314bd1c2829fa3595d4db9de9023b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Tonon?= Date: Mon, 18 Mar 2024 13:25:54 +0100 Subject: [PATCH 07/39] [Fix] When multiple messages in queue log filename must be reset before new run --- src/EventSubscriber/ProcessEventSubscriber.php | 2 ++ src/Monolog/Handler/ProcessHandler.php | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/EventSubscriber/ProcessEventSubscriber.php b/src/EventSubscriber/ProcessEventSubscriber.php index 9ededc7..783e51f 100644 --- a/src/EventSubscriber/ProcessEventSubscriber.php +++ b/src/EventSubscriber/ProcessEventSubscriber.php @@ -41,6 +41,7 @@ public function success(ProcessEvent $event): void $this->processExecutionManager->getCurrentProcessExecution()?->setStatus(ProcessExecutionStatus::Finish); $this->processExecutionManager->getCurrentProcessExecution()?->end(); $this->processExecutionManager->save()->unsetProcessExecution($event->getProcessCode()); + $this->processHandler->close(); } public function fail(ProcessEvent $event): void @@ -48,6 +49,7 @@ public function fail(ProcessEvent $event): void $this->processExecutionManager->getCurrentProcessExecution()?->setStatus(ProcessExecutionStatus::Failed); $this->processExecutionManager->getCurrentProcessExecution()?->end(); $this->processExecutionManager->save()->unsetProcessExecution($event->getProcessCode()); + $this->processHandler->close(); } public function flushDoctrineLogs(ProcessEvent $event): void diff --git a/src/Monolog/Handler/ProcessHandler.php b/src/Monolog/Handler/ProcessHandler.php index 4484148..97115ea 100644 --- a/src/Monolog/Handler/ProcessHandler.php +++ b/src/Monolog/Handler/ProcessHandler.php @@ -34,6 +34,12 @@ public function setFilename(string $filename): void $this->url = sprintf('%s/%s', $this->directory, $filename); } + public function close(): void + { + $this->url = $this->directory; + parent::close(); + } + public function getFilename(): ?string { return $this->url; From a42bb492a6d6697110d515f984343912ebd78b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Tonon?= Date: Mon, 18 Mar 2024 13:26:25 +0100 Subject: [PATCH 08/39] Add context when run process async --- src/Message/ProcessExecuteHandler.php | 2 +- src/Message/ProcessExecuteMessage.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Message/ProcessExecuteHandler.php b/src/Message/ProcessExecuteHandler.php index fcff027..979c8f2 100644 --- a/src/Message/ProcessExecuteHandler.php +++ b/src/Message/ProcessExecuteHandler.php @@ -16,6 +16,6 @@ public function __construct(private ProcessManager $manager) public function __invoke(ProcessExecuteMessage $message): void { - $this->manager->execute($message->code, $message->input); + $this->manager->execute($message->code, $message->input, $message->context); } } diff --git a/src/Message/ProcessExecuteMessage.php b/src/Message/ProcessExecuteMessage.php index e17b9d6..a64e633 100644 --- a/src/Message/ProcessExecuteMessage.php +++ b/src/Message/ProcessExecuteMessage.php @@ -6,7 +6,7 @@ readonly class ProcessExecuteMessage { - public function __construct(public string $code, public mixed $input) + public function __construct(public string $code, public mixed $input, public array $context = []) { } } From f2f771a2df0066e4e353c8ba6017abb9c142a7e1 Mon Sep 17 00:00:00 2001 From: Xavier Marchegay Date: Tue, 23 Jul 2024 13:23:27 +0200 Subject: [PATCH 09/39] Update ProcessExecutionStatus.php finish -> completed --- src/Entity/Enum/ProcessExecutionStatus.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/Enum/ProcessExecutionStatus.php b/src/Entity/Enum/ProcessExecutionStatus.php index 1950783..837a9dc 100644 --- a/src/Entity/Enum/ProcessExecutionStatus.php +++ b/src/Entity/Enum/ProcessExecutionStatus.php @@ -7,6 +7,6 @@ enum ProcessExecutionStatus: string { case Started = 'started'; - case Finish = 'finish'; + case Finish = 'completed'; case Failed = 'failed'; } From 7594fdad4e7698bbcd917c091ee7449262c1e73b Mon Sep 17 00:00:00 2001 From: Xavier Marchegay Date: Tue, 23 Jul 2024 13:26:11 +0200 Subject: [PATCH 10/39] Update ProcessExecutionStatus.php --- src/Entity/Enum/ProcessExecutionStatus.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/Enum/ProcessExecutionStatus.php b/src/Entity/Enum/ProcessExecutionStatus.php index 837a9dc..1950783 100644 --- a/src/Entity/Enum/ProcessExecutionStatus.php +++ b/src/Entity/Enum/ProcessExecutionStatus.php @@ -7,6 +7,6 @@ enum ProcessExecutionStatus: string { case Started = 'started'; - case Finish = 'completed'; + case Finish = 'finish'; case Failed = 'failed'; } From 6c2bf373454801032523497231d83b0258c25903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Tue, 30 Jul 2024 15:32:32 +0200 Subject: [PATCH 11/39] Only display public process (list, execution & logs) --- .../Admin/LogRecordCrudController.php | 27 ++++++++++++++++ src/Controller/Admin/Process/ListAction.php | 6 ++-- .../Admin/ProcessExecutionCrudController.php | 23 +++++++++++++ src/Manager/ProcessConfigurationsManager.php | 32 +++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/Manager/ProcessConfigurationsManager.php diff --git a/src/Controller/Admin/LogRecordCrudController.php b/src/Controller/Admin/LogRecordCrudController.php index 5cc7f1a..0fce4b0 100644 --- a/src/Controller/Admin/LogRecordCrudController.php +++ b/src/Controller/Admin/LogRecordCrudController.php @@ -4,13 +4,20 @@ namespace CleverAge\ProcessUiBundle\Controller\Admin; +use CleverAge\ProcessBundle\Configuration\ProcessConfiguration; use CleverAge\ProcessUiBundle\Admin\Field\LogLevelField; use CleverAge\ProcessUiBundle\Entity\LogRecord; +use CleverAge\ProcessUiBundle\Manager\ProcessConfigurationsManager; +use Doctrine\ORM\QueryBuilder; +use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection; +use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Filters; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; +use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; +use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto; use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField; use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; @@ -22,6 +29,11 @@ #[IsGranted('ROLE_USER')] class LogRecordCrudController extends AbstractCrudController { + + public function __construct(private readonly ProcessConfigurationsManager $processConfigurationsManager) + { + } + public static function getEntityFqcn(): string { return LogRecord::class; @@ -70,4 +82,19 @@ public function configureFilters(Filters $filters): Filters ) ->add('createdAt'); } + + public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder + { + $queryBuilder = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters); + $queryBuilder->join($queryBuilder->getRootAliases()[0].'.processExecution', 'pe'); + $codes = array_map( + fn(ProcessConfiguration $configuration) => $configuration->getCode(), + $this->processConfigurationsManager->getPublicProcesses() + ); + $queryBuilder->where($queryBuilder->expr()->in('pe.code', ':codes')); + $queryBuilder->setParameter('codes', $codes); + + return $queryBuilder; + + } } diff --git a/src/Controller/Admin/Process/ListAction.php b/src/Controller/Admin/Process/ListAction.php index 3c1cf4c..26726b1 100644 --- a/src/Controller/Admin/Process/ListAction.php +++ b/src/Controller/Admin/Process/ListAction.php @@ -4,7 +4,7 @@ namespace CleverAge\ProcessUiBundle\Controller\Admin\Process; -use CleverAge\ProcessBundle\Registry\ProcessConfigurationRegistry; +use CleverAge\ProcessUiBundle\Manager\ProcessConfigurationsManager; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -14,12 +14,12 @@ #[IsGranted('ROLE_USER')] class ListAction extends AbstractController { - public function __invoke(ProcessConfigurationRegistry $registry): Response + public function __invoke(ProcessConfigurationsManager $processConfigurationsManager): Response { return $this->render( '@CleverAgeProcessUi/admin/process/list.html.twig', [ - 'processes' => $registry->getProcessConfigurations(), + 'processes' => $processConfigurationsManager->getPublicProcesses() ] ); } diff --git a/src/Controller/Admin/ProcessExecutionCrudController.php b/src/Controller/Admin/ProcessExecutionCrudController.php index 246cf0d..40300a3 100644 --- a/src/Controller/Admin/ProcessExecutionCrudController.php +++ b/src/Controller/Admin/ProcessExecutionCrudController.php @@ -4,14 +4,21 @@ namespace CleverAge\ProcessUiBundle\Controller\Admin; +use CleverAge\ProcessBundle\Configuration\ProcessConfiguration; use CleverAge\ProcessUiBundle\Admin\Field\EnumField; use CleverAge\ProcessUiBundle\Entity\ProcessExecution; +use CleverAge\ProcessUiBundle\Manager\ProcessConfigurationsManager; +use Doctrine\ORM\QueryBuilder; +use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection; +use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Filters; use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; +use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; +use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto; use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; @@ -25,6 +32,9 @@ #[IsGranted('ROLE_USER')] class ProcessExecutionCrudController extends AbstractCrudController { + public function __construct(private readonly ProcessConfigurationsManager $processConfigurationsManager) + { + } public static function getEntityFqcn(): string { return ProcessExecution::class; @@ -128,4 +138,17 @@ public function configureFilters(Filters $filters): Filters { return $filters->add('code')->add('startDate'); } + + public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder + { + $codes = array_map( + fn(ProcessConfiguration $configuration) => $configuration->getCode(), + $this->processConfigurationsManager->getPublicProcesses() + ); + $queryBuilder = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters); + $queryBuilder->where($queryBuilder->expr()->in($queryBuilder->getRootAliases()[0] . '.code', ':codes')); + $queryBuilder->setParameter('codes', $codes); + + return $queryBuilder; + } } diff --git a/src/Manager/ProcessConfigurationsManager.php b/src/Manager/ProcessConfigurationsManager.php new file mode 100644 index 0000000..eed55c0 --- /dev/null +++ b/src/Manager/ProcessConfigurationsManager.php @@ -0,0 +1,32 @@ +getConfigurations(), fn(ProcessConfiguration $cfg) => $cfg->isPublic()); + } + + /** @return ProcessConfiguration[] */ + public function getPrivateProcesses(): array + { + return array_filter($this->getConfigurations(), fn(ProcessConfiguration $cfg) => !$cfg->isPublic()); + } + + /** @return ProcessConfiguration[] */ + private function getConfigurations(): array + { + return $this->registry->getProcessConfigurations(); + } +} From c3ba778edb931eff840df34d707fbf94cc58371a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 31 Jul 2024 16:53:45 +0200 Subject: [PATCH 12/39] Add scheduler configuration from UI --- README.md | 79 ++++++++++- .../Admin/ProcessDashboardController.php | 2 + .../Admin/ProcessScheduleCrudController.php | 124 ++++++++++++++++++ src/Entity/ProcessSchedule.php | 116 ++++++++++++++++ src/Form/Type/ProcessContextType.php | 39 ++++++ src/Message/CronProcessMessage.php | 14 ++ src/Message/CronProcessMessageHandler.php | 24 ++++ src/Migrations/Version20240729151928.php | 43 ++++++ src/Repository/ProcessScheduleRepository.php | 25 ++++ src/Scheduler/CronScheduler.php | 61 +++++++++ src/Validator/CronExpression.php | 13 ++ src/Validator/CronExpressionValidator.php | 23 ++++ src/Validator/EveryExpression.php | 13 ++ src/Validator/EveryExpressionValidator.php | 24 ++++ src/Validator/IsValidProcessCode.php | 14 ++ src/Validator/IsValidProcessCodeValidator.php | 36 +++++ 16 files changed, 644 insertions(+), 6 deletions(-) create mode 100644 src/Controller/Admin/ProcessScheduleCrudController.php create mode 100644 src/Entity/ProcessSchedule.php create mode 100644 src/Form/Type/ProcessContextType.php create mode 100644 src/Message/CronProcessMessage.php create mode 100644 src/Message/CronProcessMessageHandler.php create mode 100644 src/Migrations/Version20240729151928.php create mode 100644 src/Repository/ProcessScheduleRepository.php create mode 100644 src/Scheduler/CronScheduler.php create mode 100644 src/Validator/CronExpression.php create mode 100644 src/Validator/CronExpressionValidator.php create mode 100644 src/Validator/EveryExpression.php create mode 100644 src/Validator/EveryExpressionValidator.php create mode 100644 src/Validator/IsValidProcessCode.php create mode 100644 src/Validator/IsValidProcessCodeValidator.php diff --git a/README.md b/README.md index 7b2ff6a..b8ef7cb 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,84 @@ ![Code Style](https://github.com/cleverage/processuibundle/actions/workflows/super-linter.yml/badge.svg) ![Composer](https://github.com/cleverage/processuibundle/actions/workflows/php.yml/badge.svg) + ## CleverAge/ProcessUIBundle A simple UX for cleverage/processbundle using EasyAdmin **Installation** * Import routes -```yaml -#config/routes.yaml -processui: - resource: '@CleverAgeProcessUiBundle/src/Controller' - type: attribute -``` +```yaml +#config/routes.yaml +processui: + resource: '@CleverAgeProcessUiBundle/src/Controller' type: attribute +``` * Run doctrine migration * Create an user using cleverage:process-ui:user-create console. Now you can access Process UI via http://your-domain.com/process + +# Features +### Scheduler +You can schedule process execution via UI using cron expression (*/5 * * * *) or periodical triggers (5 seconds) +For more details about cron expression and peridical triggers visit https://symfony.com/doc/6.4/scheduler.html#cron-expression-triggers and https://symfony.com/doc/6.4/scheduler.html#periodical-triggers + +In order to make sheduler process working be sure the following command is running +``` +bin/console messenger:consume scheduler_cron +``` +See more details about ***messenger:consume*** command in consume message section + +# Consume Messages +Symfony messenger is used in order to run process via UI or schedule process + +*To consume process launched via UI make sure the following command is running* +``` +bin/console messenger:consume execute_process +``` + +*To consume scheduled process make sure the following command is running* +``` +bin/console messenger:consume scheduler_cron +``` +You can pass some options to messenger:consume command +``` +Options: + -l, --limit=LIMIT Limit the number of received messages + -f, --failure-limit=FAILURE-LIMIT The number of failed messages the worker can consume + -m, --memory-limit=MEMORY-LIMIT The memory limit the worker can consume + -t, --time-limit=TIME-LIMIT The time limit in seconds the worker can handle new messages + --sleep=SLEEP Seconds to sleep before asking for new messages after no messages were found [default: 1] + -b, --bus=BUS Name of the bus to which received messages should be dispatched (if not passed, bus is determined automatically) + --queues=QUEUES Limit receivers to only consume from the specified queues (multiple values allowed) + --no-reset Do not reset container services after each message +``` + +It's recommended to use supervisor app or equivalent to keep command alive + +***Sample supervisor configuration*** +``` +[program:scheduler] +command=php /var/www/html/bin/console messenger:consume scheduler_cron +autostart=false +autorestart=true +startretries=1 +startsecs=1 +redirect_stderr=true +stderr_logfile=/var/log/supervisor.scheduler-err.log +stdout_logfile=/var/log/supervisor.scheduler-out.log +user=www-data +killasgroup=true +stopasgroup=true + +[program:process] +command=php /var/www/html/bin/console messenger:consume execute_process +autostart=false +autorestart=true +startretries=1 +startsecs=1 +redirect_stderr=true +stderr_logfile=/var/log/supervisor.process-err.log +stdout_logfile=/var/log/supervisor.process-out.log +user=www-data +killasgroup=true +stopasgroup=true +``` \ No newline at end of file diff --git a/src/Controller/Admin/ProcessDashboardController.php b/src/Controller/Admin/ProcessDashboardController.php index a0a323c..bd6edd8 100644 --- a/src/Controller/Admin/ProcessDashboardController.php +++ b/src/Controller/Admin/ProcessDashboardController.php @@ -6,6 +6,7 @@ use CleverAge\ProcessUiBundle\Entity\LogRecord; use CleverAge\ProcessUiBundle\Entity\ProcessExecution; +use CleverAge\ProcessUiBundle\Entity\ProcessSchedule; use CleverAge\ProcessUiBundle\Entity\User; use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard; use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem; @@ -44,6 +45,7 @@ public function configureMenuItems(): iterable MenuItem::linkToRoute('Process list', 'fas fa-list', 'process_list'), MenuItem::linkToCrud('Executions', 'fas fa-rocket', ProcessExecution::class), MenuItem::linkToCrud('Logs', 'fas fa-pen', LogRecord::class), + MenuItem::linkToCrud('Scheduler', 'fas fa-solid fa-clock', ProcessSchedule::class), ] ); if ($this->isGranted('ROLE_ADMIN')) { diff --git a/src/Controller/Admin/ProcessScheduleCrudController.php b/src/Controller/Admin/ProcessScheduleCrudController.php new file mode 100644 index 0000000..7193165 --- /dev/null +++ b/src/Controller/Admin/ProcessScheduleCrudController.php @@ -0,0 +1,124 @@ +setPageTitle('index', 'Scheduler') + ->showEntityActionsInlined(); + } + + public function configureActions(Actions $actions): Actions + { + return $actions + ->update(Crud::PAGE_INDEX, Action::NEW, function (Action $action) { + return $action->setIcon('fa fa-plus') + ->setLabel(false) + ->addCssClass(''); + })->update(Crud::PAGE_INDEX, Action::EDIT, function (Action $action) { + return $action->setIcon('fa fa-edit') + ->setLabel(false) + ->addCssClass('text-warning'); + })->update(Crud::PAGE_INDEX, Action::DELETE, function (Action $action) { + return $action->setIcon('fa fa-trash-o') + ->setLabel(false) + ->addCssClass(''); + })->update(Crud::PAGE_INDEX, Action::BATCH_DELETE, function (Action $action) { + return $action->setLabel('Delete') + ->addCssClass(''); + }); + } + + public static function getEntityFqcn(): string + { + return ProcessSchedule::class; + } + + public function configureFields(string $pageName): iterable + { + + $choices = array_map(function(ProcessConfiguration $configuration) { + return [$configuration->getCode()]; + }, $this->processConfigurationsManager->getPublicProcesses()); + return [ + FormField::addTab('General'), + TextField::new('process') + ->setFormType(ChoiceType::class) + ->setFormTypeOption('choices', array_combine(array_keys($choices), array_keys($choices))), + EnumField::new('type') + ->setFormType(EnumType::class) + ->setFormTypeOption('class', ProcessScheduleType::class), + TextField::new('expression'), + DateTimeField::new('nextExecution') + ->setFormTypeOption('mapped', false) + ->setVirtual(true) + ->hideOnForm() + ->hideOnDetail() + ->formatValue(function ($value, ProcessSchedule $entity) { + return ProcessScheduleType::CRON === $entity->getType() + ? CronExpressionTrigger::fromSpec($entity->getExpression())->getNextRunDate(new \DateTimeImmutable())->format('c') + : null; + }), + FormField::addTab('Input'), + TextField::new('input'), + FormField::addTab('Context'), + ArrayField::new('context') + ->setFormTypeOption('entry_type', ProcessContextType::class) + ->hideOnIndex() + ->setFormTypeOption('entry_options.label', 'Context (key/value)') + ->setFormTypeOption('label', '') + ->setFormTypeOption('required', false) + ]; + } + + public function index(AdminContext $context): KeyValueStore|RedirectResponse|Response + { + if (false === $this->schedulerIsRunning()) { + $this->addFlash('warning', 'To run scheduler, ensure "bin/console messenger:consume scheduler_cron" console is alive. See https://symfony.com/doc/current/messenger.html#supervisor-configuration.'); + } + + return parent::index($context); + } + + private function schedulerIsRunning(): bool + { + + $process = Process::fromShellCommandline('ps -faux'); + $process->run(); + $out = $process->getOutput(); + + return str_contains($out, 'scheduler_cron'); + } +} diff --git a/src/Entity/ProcessSchedule.php b/src/Entity/ProcessSchedule.php new file mode 100644 index 0000000..8a26e88 --- /dev/null +++ b/src/Entity/ProcessSchedule.php @@ -0,0 +1,116 @@ +id; + } + + public function getProcess(): ?string + { + return $this->process; + } + + public function setProcess(string $process): static + { + $this->process = $process; + + return $this; + } + + public function getContext(): array + { + return is_array($this->context) ? $this->context : json_decode($this->context); + } + + public function setContext(array $context): void + { + $this->context = $context; + } + + public function getNextExecution(): null + { + return null; + } + + public function getType(): ProcessScheduleType + { + return $this->type; + } + + public function setType(ProcessScheduleType $type): self + { + $this->type = $type; + + return $this; + } + + public function getExpression(): ?string + { + return $this->expression; + } + + public function setExpression(?string $expression): self + { + $this->expression = $expression; + + return $this; + } + + public function getInput(): ?string + { + return $this->input; + } + + public function setInput(?string $input): self + { + $this->input = $input; + + return $this; + } +} diff --git a/src/Form/Type/ProcessContextType.php b/src/Form/Type/ProcessContextType.php new file mode 100644 index 0000000..204aeb8 --- /dev/null +++ b/src/Form/Type/ProcessContextType.php @@ -0,0 +1,39 @@ +add( + 'key', + null, + [ + 'label' => 'Context Key', + 'attr' => ['placeholder' => 'key'], + 'constraints' => [new NotBlank()] + ] + )->add( + 'value', + null, + [ + 'label' => 'Context Value', + 'attr' => ['placeholder' => 'value'], + 'constraints' => [new NotBlank()] + ] + ); + } + + + public function configureOptions(OptionsResolver $resolver): void + { + } +} diff --git a/src/Message/CronProcessMessage.php b/src/Message/CronProcessMessage.php new file mode 100644 index 0000000..6d8bc86 --- /dev/null +++ b/src/Message/CronProcessMessage.php @@ -0,0 +1,14 @@ +processSchedule; + $this->bus->dispatch( + new ProcessExecuteMessage($schedule->getProcess(), $schedule->getInput(), $message->processSchedule->getContext()) + ); + } +} diff --git a/src/Migrations/Version20240729151928.php b/src/Migrations/Version20240729151928.php new file mode 100644 index 0000000..454a14f --- /dev/null +++ b/src/Migrations/Version20240729151928.php @@ -0,0 +1,43 @@ +connection->getDatabasePlatform(); + if ($platform instanceof PostgreSQLPlatform) { + $this->addSql('CREATE TABLE process_schedule (id INT AUTO_INCREMENT NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input VARCHAR(255), context JSON NOT NULL, PRIMARY KEY(id))'); + } + if ($platform instanceof SqlitePlatform) { + $this->addSql('CREATE TABLE process_schedule (id INTEGER AUTO_INCREMENT PRIMARY KEY AUTOINCREMENT NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input VARCHAR(255), context CLOB NOT NULL --(DC2Type:json))'); + } + + if ($platform instanceof MariaDBPlatform or $platform instanceof MySQLPlatform) { + $this->addSql('CREATE TABLE process_schedule (id INT AUTO_INCREMENT NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input VARCHAR(255), context JSON NOT NULL COMMENT \'(DC2Type:json)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;'); + } + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TABLE process_schedule'); + } +} diff --git a/src/Repository/ProcessScheduleRepository.php b/src/Repository/ProcessScheduleRepository.php new file mode 100644 index 0000000..b7b3452 --- /dev/null +++ b/src/Repository/ProcessScheduleRepository.php @@ -0,0 +1,25 @@ + + * + * @method ProcessSchedule|null find($id, $lockMode = null, $lockVersion = null) + * @method ProcessSchedule|null findOneBy(array $criteria, array $orderBy = null) + * @method ProcessSchedule[] findAll() + * @method ProcessSchedule[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class ProcessScheduleRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ProcessSchedule::class); + } +} diff --git a/src/Scheduler/CronScheduler.php b/src/Scheduler/CronScheduler.php new file mode 100644 index 0000000..e5de3b2 --- /dev/null +++ b/src/Scheduler/CronScheduler.php @@ -0,0 +1,61 @@ +repository->findAll() as $processSchedule) { + $violations = $this->validator->validate($processSchedule); + if (0 !== $violations->count()) { + foreach ($violations as $violation) { + $this->logger->info( + 'Scheduler configuration is not valid.', + ['reason' => $violation->getMessage()] + ); + } + continue; + } + if (ProcessScheduleType::CRON === $processSchedule->getType()) { + $schedule->add( + RecurringMessage::cron( + $processSchedule->getExpression(), + new CronProcessMessage($processSchedule) + ) + ); + } elseif (ProcessScheduleType::EVERY === $processSchedule->getType()) { + $schedule->add( + RecurringMessage::every( + $processSchedule->getExpression(), + new CronProcessMessage($processSchedule) + ) + ); + } + } + + return $schedule; + } +} diff --git a/src/Validator/CronExpression.php b/src/Validator/CronExpression.php new file mode 100644 index 0000000..9d1a363 --- /dev/null +++ b/src/Validator/CronExpression.php @@ -0,0 +1,13 @@ +context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $value) + ->addViolation(); + } + } +} diff --git a/src/Validator/EveryExpression.php b/src/Validator/EveryExpression.php new file mode 100644 index 0000000..2288311 --- /dev/null +++ b/src/Validator/EveryExpression.php @@ -0,0 +1,13 @@ +context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $value) + ->addViolation(); + } +} diff --git a/src/Validator/IsValidProcessCode.php b/src/Validator/IsValidProcessCode.php new file mode 100644 index 0000000..b957e86 --- /dev/null +++ b/src/Validator/IsValidProcessCode.php @@ -0,0 +1,14 @@ +registry->hasProcessConfiguration($value)) { + $this->context->buildViolation($constraint->messageNotExists) + ->setParameter('{{ value }}', $value) + ->addViolation(); + + return; + } + + if (!$this->registry->getProcessConfiguration($value)->isPublic()) { + $this->context->buildViolation($constraint->messageIsNotPublic) + ->setParameter('{{ value }}', $value) + ->addViolation(); + } + } +} From 3a768a827941f32f3e629361a8b237ce0ba06ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Thu, 1 Aug 2024 09:36:10 +0200 Subject: [PATCH 13/39] [Feature] Http post request process launcher --- README.md | 22 ++++++- src/Controller/Admin/UserCrudController.php | 29 ++++++++- src/Controller/ProcessExecuteController.php | 44 +++++++++++++ src/Entity/User.php | 16 +++++ src/Http/Model/HttpProcessExecution.php | 20 ++++++ .../HttpProcessExecuteValueResolver.php | 34 +++++++++++ src/Migrations/Version20240730090403.php | 33 ++++++++++ .../HttpProcessExecutionAuthenticator.php | 61 +++++++++++++++++++ 8 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 src/Controller/ProcessExecuteController.php create mode 100644 src/Http/Model/HttpProcessExecution.php create mode 100644 src/Http/ValueResolver/HttpProcessExecuteValueResolver.php create mode 100644 src/Migrations/Version20240730090403.php create mode 100644 src/Security/HttpProcessExecutionAuthenticator.php diff --git a/README.md b/README.md index b8ef7cb..e694372 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -![Code Style](https://github.com/cleverage/processuibundle/actions/workflows/super-linter.yml/badge.svg) ![Composer](https://github.com/cleverage/processuibundle/actions/workflows/php.yml/badge.svg) - ## CleverAge/ProcessUIBundle A simple UX for cleverage/processbundle using EasyAdmin @@ -17,6 +15,26 @@ processui: Now you can access Process UI via http://your-domain.com/process # Features +### Launch process via http request +You can launch a process via http post request +First you need to generate a token via UI User edit form. The ProcessUi generate for you a auth token (keep it in secured area, it will display once). + +It' all, now you can launch a process via http post request + +***Curl sample*** +``` +curl --location 'https://localhost:8080/http/process/execute?code=demo.die' \ +--header 'Authorization: Bearer 3da8409b5f5b640fb0c43d68e8ac8d23' \ +--form 'input=@"/file.csv"' \ +--form 'context[context_1]="FOO"' \ +--form 'context[context_2]="BAR"' +``` +* Query string code parameter must be a valid process code +* Header Autorization: Bearer is the previously generated token +* input could be string or file representation +* context you can pass multiple context values + + ### Scheduler You can schedule process execution via UI using cron expression (*/5 * * * *) or periodical triggers (5 seconds) For more details about cron expression and peridical triggers visit https://symfony.com/doc/6.4/scheduler.html#cron-expression-triggers and https://symfony.com/doc/6.4/scheduler.html#periodical-triggers diff --git a/src/Controller/Admin/UserCrudController.php b/src/Controller/Admin/UserCrudController.php index 8cc3cd4..fd964e3 100644 --- a/src/Controller/Admin/UserCrudController.php +++ b/src/Controller/Admin/UserCrudController.php @@ -8,13 +8,17 @@ use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; +use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField; use EasyCorp\Bundle\EasyAdminBundle\Field\FormField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; +use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; use Symfony\Component\Security\Http\Attribute\IsGranted; #[IsGranted('ROLE_USER')] @@ -57,7 +61,6 @@ public function configureFields(string $pageName): iterable 'mapped' => false, ] ); - yield FormField::addTab('Informations')->setIcon('fa fa-user'); yield TextField::new('firstname'); yield TextField::new('lastname'); @@ -86,6 +89,28 @@ public function configureActions(Actions $actions): Actions })->update(Crud::PAGE_INDEX, Action::BATCH_DELETE, function (Action $action) { return $action->setLabel('Delete') ->addCssClass(''); - }); + })->add(Crud::PAGE_EDIT, Action::new('generateToken')->linkToCrudAction('generateToken')); + } + + + public function generateToken(AdminContext $adminContext, AdminUrlGenerator $adminUrlGenerator): Response + { + /** @var User $user */ + $user = $adminContext->getEntity()->getInstance(); + $token = md5(uniqid(date('YmdHis'))); + $user->setToken((new Pbkdf2PasswordHasher())->hash($token)); + $this->persistEntity( + $this->container->get('doctrine')->getManagerForClass($adminContext->getEntity()->getFqcn()), + $user + ); + $this->addFlash('success', 'New token generated ' . $token . ' (keep it in secured area. This token will never be displayed anymore)'); + + return $this->redirect( + $adminUrlGenerator + ->setController(UserCrudController::class) + ->setAction(Action::EDIT) + ->setEntityId($user->getId()) + ->generateUrl() + ); } } diff --git a/src/Controller/ProcessExecuteController.php b/src/Controller/ProcessExecuteController.php new file mode 100644 index 0000000..dd745cd --- /dev/null +++ b/src/Controller/ProcessExecuteController.php @@ -0,0 +1,44 @@ +validate($httpProcessExecution); + if ($violations->count() > 0) { + $violationsMessages = []; + foreach ($violations as $violation) { + $violationsMessages[] = $violation->getMessage(); + } + throw new UnprocessableEntityHttpException(implode('. ', $violationsMessages)); + } + $bus->dispatch( + new ProcessExecuteMessage( + $httpProcessExecution->code, + $httpProcessExecution->input, + $httpProcessExecution->context + ) + ); + + return new JsonResponse('Process has been added to queue. It will start as soon as possible.'); + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index f89463e..02832af 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -33,6 +33,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(type: 'string', length: 255, nullable: true)] private ?string $password; + #[ORM\Column(type: 'string', length: 255, nullable: true)] + private ?string $token; + public function getId(): ?int { return $this->id; @@ -122,6 +125,19 @@ public function setPassword(string $password): self return $this; } + public function getToken(): ?string + { + return $this->token; + } + + public function setToken(?string $token): self + { + $this->token = $token; + + return $this; + } + + /** * Returning a salt is only needed, if you are not using a modern * hashing algorithm (e.g. bcrypt or sodium) in your security.yaml. diff --git a/src/Http/Model/HttpProcessExecution.php b/src/Http/Model/HttpProcessExecution.php new file mode 100644 index 0000000..7e770dc --- /dev/null +++ b/src/Http/Model/HttpProcessExecution.php @@ -0,0 +1,20 @@ +get('input', $request->files->get('input')); + if ($input instanceof UploadedFile) { + $uploadFileName = $this->storageDir . DIRECTORY_SEPARATOR . date('YmdHis') . '_' . uniqid() . '_' . $input->getClientOriginalName(); + (new Filesystem())->dumpFile($uploadFileName, $input->getContent()); + $input = $uploadFileName; + + } + + return [new HttpProcessExecution($request->get('code'), $input, $request->get('context', []))]; + } +} \ No newline at end of file diff --git a/src/Migrations/Version20240730090403.php b/src/Migrations/Version20240730090403.php new file mode 100644 index 0000000..205f650 --- /dev/null +++ b/src/Migrations/Version20240730090403.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE process_user ADD token VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE process_user DROP token'); + } +} diff --git a/src/Security/HttpProcessExecutionAuthenticator.php b/src/Security/HttpProcessExecutionAuthenticator.php new file mode 100644 index 0000000..ccac246 --- /dev/null +++ b/src/Security/HttpProcessExecutionAuthenticator.php @@ -0,0 +1,61 @@ +get('_route') && $request->isMethod(Request::METHOD_POST); + } + + public function authenticate(Request $request): Passport + { + if (false === $request->headers->has('Authorization')) { + throw new AuthenticationException('Missing auth token.'); + } + $token = $request->headers->get('Authorization'); + $token = str_replace('Bearer ', '', null === $token ? [''] : $token); + $user = $this->entityManager->getRepository(User::class)->findOneBy( + ['token' => (new Pbkdf2PasswordHasher())->hash($token)] + ); + if (null === $user) { + throw new AuthenticationException('Invalid token.'); + } + + return new SelfValidatingPassport(new UserBadge($user->getEmail())); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + $data = [ + 'message' => $exception->getMessage() + ]; + + return new JsonResponse($data, Response::HTTP_UNAUTHORIZED); + } +} From 458e6d9c713ddcc71d2eb7752fd08ffa0e69cfc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Thu, 1 Aug 2024 11:00:04 +0200 Subject: [PATCH 14/39] Launch process form to set input && context --- .../Admin/Process/ExecuteAction.php | 33 -------- src/Controller/Admin/Process/LaunchAction.php | 78 +++++++++++++++++++ src/Form/Type/LaunchType.php | 55 +++++++++++++ ...and_execute.html.twig => launch.html.twig} | 9 +-- templates/admin/process/list.html.twig | 45 +---------- 5 files changed, 138 insertions(+), 82 deletions(-) delete mode 100644 src/Controller/Admin/Process/ExecuteAction.php create mode 100644 src/Controller/Admin/Process/LaunchAction.php create mode 100644 src/Form/Type/LaunchType.php rename templates/admin/process/{upload_and_execute.html.twig => launch.html.twig} (68%) diff --git a/src/Controller/Admin/Process/ExecuteAction.php b/src/Controller/Admin/Process/ExecuteAction.php deleted file mode 100644 index 8da0685..0000000 --- a/src/Controller/Admin/Process/ExecuteAction.php +++ /dev/null @@ -1,33 +0,0 @@ -get('process'); - if (null === $process) { - $this->createNotFoundException('Process is missing'); - } - $bus->dispatch(new ProcessExecuteMessage($process, null)); - $this->addFlash( - 'success', - 'Process has been added to queue. It will start as soon as possible' - ); - - return $this->redirectToRoute('process', ['routeName' => 'process_list']); - } -} diff --git a/src/Controller/Admin/Process/LaunchAction.php b/src/Controller/Admin/Process/LaunchAction.php new file mode 100644 index 0000000..17acc17 --- /dev/null +++ b/src/Controller/Admin/Process/LaunchAction.php @@ -0,0 +1,78 @@ + '\w+'], + methods: ['POST', 'GET'] +)] +#[IsGranted('ROLE_USER')] +class LaunchAction extends AbstractController +{ + public function __invoke( + RequestStack $requestStack, + MessageBusInterface $messageBus, + #[Autowire(param: 'upload_directory')] string $uploadDirectory, + #[ValueResolver('process')] ProcessConfiguration $processConfiguration, + AdminContext $context + ): Response { + $form = $this->createForm( + LaunchType::class, + null, + ['process_code' => $requestStack->getMainRequest()?->get('process')] + ); + $form->handleRequest($requestStack->getMainRequest()); + if ($form->isSubmitted() && $form->isValid()) { + /** @var mixed|UploadedFile $file */ + $input = $form->get('input')->getData(); + if ($input instanceof UploadedFile) { + $filename = sprintf('%s/%s.%s', $uploadDirectory, Uuid::v4(), $input->getClientOriginalExtension()); + (new Filesystem())->dumpFile($filename, $input->getContent()); + $input = $filename; + } + $context = $form->get('context')->getData(); + $message = new ProcessExecuteMessage( + $form->getConfig()->getOption('process_code'), + $input, + $context + ); + $messageBus->dispatch($message); + $this->addFlash( + 'success', + 'Process has been added to queue. It will start as soon as possible' + ); + + return $this->redirectToRoute('process', ['routeName' => 'process_list']); + } + $context->getAssets()->addJsAsset(Asset::fromEasyAdminAssetPackage('field-collection.js')->getAsDto()); + return $this->render( + '@CleverAgeProcessUi/admin/process/launch.html.twig', + [ + 'form' => $form->createView(), + ] + ); + } +} diff --git a/src/Form/Type/LaunchType.php b/src/Form/Type/LaunchType.php new file mode 100644 index 0000000..3cd7445 --- /dev/null +++ b/src/Form/Type/LaunchType.php @@ -0,0 +1,55 @@ +registry->getProcessConfiguration($code); + $builder->add( + 'input', + "file" === ($configuration->getOptions()['ui']['entrypoint_type'] ?? null) ? FileType::class : TextType::class, + [ + 'required' => !(null === $configuration->getEntryPoint()) + ] + ); + $builder->add( + 'context', + CollectionType::class, + [ + 'entry_type' => ProcessContextType::class, + 'allow_add' => true, + 'allow_delete' => true, + 'required' => false, + ] + ); + + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setRequired('process_code'); + } + + public function getParent(): string + { + return FormType::class; + } +} diff --git a/templates/admin/process/upload_and_execute.html.twig b/templates/admin/process/launch.html.twig similarity index 68% rename from templates/admin/process/upload_and_execute.html.twig rename to templates/admin/process/launch.html.twig index 53160fc..88d0f7b 100644 --- a/templates/admin/process/upload_and_execute.html.twig +++ b/templates/admin/process/launch.html.twig @@ -2,22 +2,17 @@ {% trans_default_domain ea.i18n.translationDomain %} {% block main %} + {% form_theme form '@EasyAdmin/crud/form_theme.html.twig' %}
    -
    -
    - {{ 'Upload you file then click on button to run process in background' }} -
    -
    {{ form_start(form) }} {{ form_widget(form) }}
    - +
    {{ form_end(form) }}
    - {% endblock %} diff --git a/templates/admin/process/list.html.twig b/templates/admin/process/list.html.twig index 5682e7b..899a671 100644 --- a/templates/admin/process/list.html.twig +++ b/templates/admin/process/list.html.twig @@ -32,25 +32,9 @@ {% if process.options.ui.source is defined %}{{ process.options.ui.source }}{% endif %} {% if process.options.ui.target is defined %}{{ process.options.ui.target }}{% endif %} - {% if process.options.ui.upload_and_run is defined and true == process.options.ui.upload_and_run %} - - - - {% endif %} - {% if process.options.ui.run is defined and true == process.options.ui.run %} - - - - {% endif %} + + + - {% for process in processes %} - - - - {% endfor %} {% endblock %} From 0c6fb77a95b60c60d2bc2f2ad9f214d298a5766c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Thu, 1 Aug 2024 11:00:24 +0200 Subject: [PATCH 15/39] Fix error on Logs filter --- src/Controller/Admin/LogRecordCrudController.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Controller/Admin/LogRecordCrudController.php b/src/Controller/Admin/LogRecordCrudController.php index 0fce4b0..458d533 100644 --- a/src/Controller/Admin/LogRecordCrudController.php +++ b/src/Controller/Admin/LogRecordCrudController.php @@ -86,15 +86,14 @@ public function configureFilters(Filters $filters): Filters public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder { $queryBuilder = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters); - $queryBuilder->join($queryBuilder->getRootAliases()[0].'.processExecution', 'pe'); - $codes = array_map( - fn(ProcessConfiguration $configuration) => $configuration->getCode(), - $this->processConfigurationsManager->getPublicProcesses() - ); - $queryBuilder->where($queryBuilder->expr()->in('pe.code', ':codes')); - $queryBuilder->setParameter('codes', $codes); + if (false === isset($searchDto->getAppliedFilters()['processExecution'])) { + $publicProcesses = $this->processConfigurationsManager->getPublicProcesses(); + $queryBuilder->join($queryBuilder->getRootAliases()[0] . '.processExecution', 'pe'); + $codes = array_map(fn(ProcessConfiguration $configuration) => $configuration->getCode(), $publicProcesses); + $queryBuilder->where($queryBuilder->expr()->in('pe.code', ':codes')); + $queryBuilder->setParameter('codes', $codes); + } return $queryBuilder; - } } From 07d2f261d029bc862f3b70a016d0d9273649c84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Thu, 1 Aug 2024 15:38:30 +0200 Subject: [PATCH 16/39] Add custom filter for log crud on process code or id --- src/Admin/Filter/LogProcessCodeFilter.php | 60 +++++++++++++++++++ .../Admin/LogRecordCrudController.php | 34 ++++------- src/Entity/ProcessExecution.php | 5 ++ 3 files changed, 77 insertions(+), 22 deletions(-) create mode 100644 src/Admin/Filter/LogProcessCodeFilter.php diff --git a/src/Admin/Filter/LogProcessCodeFilter.php b/src/Admin/Filter/LogProcessCodeFilter.php new file mode 100644 index 0000000..6d738ff --- /dev/null +++ b/src/Admin/Filter/LogProcessCodeFilter.php @@ -0,0 +1,60 @@ +setFilterFqcn(__CLASS__) + ->setProperty('processExecution') + ->setLabel($label) + ->setFormType(ChoiceType::class); + } + + public function addChoices(ProcessConfigurationsManager $manager): self + { + $choices = $manager->getPublicProcesses(); + $choices = array_map(fn(ProcessConfiguration $cfg) => $cfg->getCode(), $choices); + $this->setFormTypeOption('choices', $choices); + + return $this; + } + + public function setCurrentProcessExecutionId(?int $currentProcessExecutionId): self + { + $this->currentProcessExecutionId = $currentProcessExecutionId; + if (0 !== $this->currentProcessExecutionId) { + $this->setFormTypeOption('disabled', true); + } + return $this; + } + + public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, ?FieldDto $fieldDto, EntityDto $entityDto): void + { + $queryBuilder->join('entity.processExecution', 'pe'); + if (null !== $this->currentProcessExecutionId) { + $queryBuilder->andWhere($queryBuilder->expr()->eq('pe.id', ':id')); + $queryBuilder->setParameter('id', $this->currentProcessExecutionId); + return; + } + $queryBuilder->where('pe.code IN (:codes)'); + $queryBuilder->setParameter('codes', $filterDataDto->getValue()); + } +} \ No newline at end of file diff --git a/src/Controller/Admin/LogRecordCrudController.php b/src/Controller/Admin/LogRecordCrudController.php index 458d533..d88d9bf 100644 --- a/src/Controller/Admin/LogRecordCrudController.php +++ b/src/Controller/Admin/LogRecordCrudController.php @@ -4,33 +4,32 @@ namespace CleverAge\ProcessUiBundle\Controller\Admin; -use CleverAge\ProcessBundle\Configuration\ProcessConfiguration; use CleverAge\ProcessUiBundle\Admin\Field\LogLevelField; +use CleverAge\ProcessUiBundle\Admin\Filter\LogProcessCodeFilter; use CleverAge\ProcessUiBundle\Entity\LogRecord; use CleverAge\ProcessUiBundle\Manager\ProcessConfigurationsManager; -use Doctrine\ORM\QueryBuilder; -use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection; -use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Filters; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; -use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; -use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto; use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField; use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Filter\ChoiceFilter; use Monolog\Level; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Http\Attribute\IsGranted; #[IsGranted('ROLE_USER')] class LogRecordCrudController extends AbstractCrudController { - public function __construct(private readonly ProcessConfigurationsManager $processConfigurationsManager) + public function __construct( + private readonly ProcessConfigurationsManager $processConfigurationsManager, + private readonly RequestStack $request + ) { } @@ -76,24 +75,15 @@ public function configureActions(Actions $actions): Actions public function configureFilters(Filters $filters): Filters { - return $filters->add('processExecution') + $id = $this->request->getMainRequest()->query->all('filters')['processExecution']['value'] ?? null; + return $filters->add( + LogProcessCodeFilter::new('process') + ->addChoices($this->processConfigurationsManager) + ->setCurrentProcessExecutionId((int) $id) + ) ->add( ChoiceFilter::new('level')->setChoices(array_combine(Level::NAMES, Level::VALUES)) ) ->add('createdAt'); } - - public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder - { - $queryBuilder = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters); - if (false === isset($searchDto->getAppliedFilters()['processExecution'])) { - $publicProcesses = $this->processConfigurationsManager->getPublicProcesses(); - $queryBuilder->join($queryBuilder->getRootAliases()[0] . '.processExecution', 'pe'); - $codes = array_map(fn(ProcessConfiguration $configuration) => $configuration->getCode(), $publicProcesses); - $queryBuilder->where($queryBuilder->expr()->in('pe.code', ':codes')); - $queryBuilder->setParameter('codes', $codes); - } - - return $queryBuilder; - } } diff --git a/src/Entity/ProcessExecution.php b/src/Entity/ProcessExecution.php index b701f34..82e5e05 100644 --- a/src/Entity/ProcessExecution.php +++ b/src/Entity/ProcessExecution.php @@ -86,4 +86,9 @@ public function duration(string $format = '%H hour(s) %I min(s) %S s'): ?string $diff = $this->endDate->diff($this->startDate); return $diff->format($format); } + + public function getCode(): string + { + return $this->code; + } } From cf86b78e7053e739be6cebd201eaad525ec62717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Mon, 26 Aug 2024 09:47:22 +0200 Subject: [PATCH 17/39] [Fix] Migration for sqlite platform --- src/Migrations/Version20240729151928.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Migrations/Version20240729151928.php b/src/Migrations/Version20240729151928.php index 454a14f..b3c9adf 100644 --- a/src/Migrations/Version20240729151928.php +++ b/src/Migrations/Version20240729151928.php @@ -28,7 +28,8 @@ public function up(Schema $schema): void $this->addSql('CREATE TABLE process_schedule (id INT AUTO_INCREMENT NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input VARCHAR(255), context JSON NOT NULL, PRIMARY KEY(id))'); } if ($platform instanceof SqlitePlatform) { - $this->addSql('CREATE TABLE process_schedule (id INTEGER AUTO_INCREMENT PRIMARY KEY AUTOINCREMENT NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input VARCHAR(255), context CLOB NOT NULL --(DC2Type:json))'); + $this->addSql('CREATE TABLE process_schedule (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input CLOB DEFAULT NULL, context CLOB NOT NULL --(DC2Type:json) +);'); } if ($platform instanceof MariaDBPlatform or $platform instanceof MySQLPlatform) { @@ -41,3 +42,4 @@ public function down(Schema $schema): void $this->addSql('DROP TABLE process_schedule'); } } + From 100813734d4c985a5d9f824a2512761b500a9621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 28 Aug 2024 14:16:27 +0200 Subject: [PATCH 18/39] [Fix] Context from form --- src/Controller/Admin/Process/LaunchAction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/Admin/Process/LaunchAction.php b/src/Controller/Admin/Process/LaunchAction.php index 17acc17..93f0acd 100644 --- a/src/Controller/Admin/Process/LaunchAction.php +++ b/src/Controller/Admin/Process/LaunchAction.php @@ -53,7 +53,7 @@ public function __invoke( (new Filesystem())->dumpFile($filename, $input->getContent()); $input = $filename; } - $context = $form->get('context')->getData(); + $context = array_column($form->get('context')->getData(), 'value', 'key'); $message = new ProcessExecuteMessage( $form->getConfig()->getOption('process_code'), $input, From 992aa1db6a42cdab5a2d0b6883bdf8c85ab0bb13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Thu, 29 Aug 2024 15:13:41 +0200 Subject: [PATCH 19/39] [Feature] Add default values && constraints over the execute page --- src/Controller/Admin/Process/LaunchAction.php | 26 +++++++--- .../Admin/Process/UploadAndExecuteAction.php | 3 -- src/Form/Type/LaunchType.php | 20 ++++++-- src/Manager/ProcessConfigurationsManager.php | 49 +++++++++++++++++++ .../ProcessExecutionExtensionRuntime.php | 10 ++-- 5 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/Controller/Admin/Process/LaunchAction.php b/src/Controller/Admin/Process/LaunchAction.php index 93f0acd..665bb58 100644 --- a/src/Controller/Admin/Process/LaunchAction.php +++ b/src/Controller/Admin/Process/LaunchAction.php @@ -6,14 +6,14 @@ use CleverAge\ProcessBundle\Configuration\ProcessConfiguration; use CleverAge\ProcessUiBundle\Form\Type\LaunchType; -use CleverAge\ProcessUiBundle\Form\Type\ProcessUploadFileType; +use CleverAge\ProcessUiBundle\Manager\ProcessConfigurationsManager; use CleverAge\ProcessUiBundle\Message\ProcessExecuteMessage; use EasyCorp\Bundle\EasyAdminBundle\Config\Asset; use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; -use EasyCorp\Bundle\EasyAdminBundle\Dto\AssetDto; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; @@ -37,13 +37,27 @@ public function __invoke( MessageBusInterface $messageBus, #[Autowire(param: 'upload_directory')] string $uploadDirectory, #[ValueResolver('process')] ProcessConfiguration $processConfiguration, - AdminContext $context + ProcessConfigurationsManager $configurationsManager, + AdminContext $context, ): Response { + $uiOptions = $configurationsManager->getUiOptions($requestStack->getMainRequest()->get('process')); $form = $this->createForm( LaunchType::class, null, - ['process_code' => $requestStack->getMainRequest()?->get('process')] + [ + 'constraints' => $uiOptions['constraints'], + 'process_code' => $requestStack->getMainRequest()?->get('process'), + ] ); + if (false === $form->isSubmitted()) { + $default = $uiOptions['default']; + if (false === $form->get('input')->getConfig()->getType()->getInnerType() instanceof TextType + && isset($default['input']) + ) { + unset($default['input']); + } + $form->setData($default); + } $form->handleRequest($requestStack->getMainRequest()); if ($form->isSubmitted() && $form->isValid()) { /** @var mixed|UploadedFile $file */ @@ -53,11 +67,11 @@ public function __invoke( (new Filesystem())->dumpFile($filename, $input->getContent()); $input = $filename; } - $context = array_column($form->get('context')->getData(), 'value', 'key'); + $message = new ProcessExecuteMessage( $form->getConfig()->getOption('process_code'), $input, - $context + $form->get('context')->getData() ); $messageBus->dispatch($message); $this->addFlash( diff --git a/src/Controller/Admin/Process/UploadAndExecuteAction.php b/src/Controller/Admin/Process/UploadAndExecuteAction.php index ea540eb..8ad3ae0 100644 --- a/src/Controller/Admin/Process/UploadAndExecuteAction.php +++ b/src/Controller/Admin/Process/UploadAndExecuteAction.php @@ -37,9 +37,6 @@ public function __invoke( if (null === $processConfiguration->getEntryPoint()) { throw new \RuntimeException('You must set an entry_point.'); } - if (false === ($processConfiguration->getOptions()['ui']['upload_and_run'] ?? false)) { - throw new \RuntimeException('ui.upload_and_run options not set to true in process options.'); - } $form = $this->createForm( ProcessUploadFileType::class, null, diff --git a/src/Form/Type/LaunchType.php b/src/Form/Type/LaunchType.php index 3cd7445..0a8e9bb 100644 --- a/src/Form/Type/LaunchType.php +++ b/src/Form/Type/LaunchType.php @@ -5,7 +5,9 @@ namespace CleverAge\ProcessUiBundle\Form\Type; use CleverAge\ProcessBundle\Registry\ProcessConfigurationRegistry; +use CleverAge\ProcessUiBundle\Manager\ProcessConfigurationsManager; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\FormType; @@ -15,7 +17,10 @@ class LaunchType extends AbstractType { - public function __construct(private readonly ProcessConfigurationRegistry $registry) + public function __construct( + private readonly ProcessConfigurationRegistry $registry, + private readonly ProcessConfigurationsManager $configurationsManager, + ) { } @@ -23,11 +28,12 @@ public function buildForm(FormBuilderInterface $builder, array $options): void { $code = $options['process_code']; $configuration = $this->registry->getProcessConfiguration($code); + $uiOptions = $this->configurationsManager->getUiOptions($code); $builder->add( 'input', - "file" === ($configuration->getOptions()['ui']['entrypoint_type'] ?? null) ? FileType::class : TextType::class, + "file" === $uiOptions['entrypoint_type'] ? FileType::class : TextType::class, [ - 'required' => !(null === $configuration->getEntryPoint()) + 'required' => !(null === $configuration->getEntryPoint()), ] ); $builder->add( @@ -40,6 +46,14 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, ] ); + $builder->get('context')->addModelTransformer(new CallbackTransformer( + function ($data) { + return null === $data ? [] : $data; + }, + function ($data) { + return array_column($data ?? [], 'value', 'key'); + }, + )); } diff --git a/src/Manager/ProcessConfigurationsManager.php b/src/Manager/ProcessConfigurationsManager.php index eed55c0..68c3e74 100644 --- a/src/Manager/ProcessConfigurationsManager.php +++ b/src/Manager/ProcessConfigurationsManager.php @@ -5,6 +5,9 @@ use CleverAge\ProcessBundle\Configuration\ProcessConfiguration; use CleverAge\ProcessBundle\Registry\ProcessConfigurationRegistry; +use CleverAge\ProcessBundle\Validator\ConstraintLoader; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; final readonly class ProcessConfigurationsManager { @@ -24,6 +27,52 @@ public function getPrivateProcesses(): array return array_filter($this->getConfigurations(), fn(ProcessConfiguration $cfg) => !$cfg->isPublic()); } + public function getUiOptions(string $processCode): ?array + { + if (false === $this->registry->hasProcessConfiguration($processCode)) { + return null; + } + + $configuration = $this->registry->getProcessConfiguration($processCode); + + return $this->resolveUiOptions($configuration->getOptions())['ui']; + } + + private function resolveUiOptions(array $options): array + { + $resolver = new OptionsResolver(); + $resolver->setDefault('ui', function (OptionsResolver $uiResolver): void { + $uiResolver->setDefaults( + [ + 'source' => null, + 'target' => null, + 'entrypoint_type' => 'text', + 'constraints' => [], + 'run' => null, + 'default' => function(OptionsResolver $defaultResolver) { + $defaultResolver->setDefault('input', null); + $defaultResolver->setDefault('context', function(OptionsResolver $contextResolver) { + $contextResolver->setPrototype(true); + $contextResolver->setRequired(['key', 'value']); + }); + } + ] + ); + $uiResolver->setDeprecated( + 'run', + 'cleverage/process-ui-bundle', + '2', + 'run ui option is deprecated. Use public option instead to hide a process from UI' + ); + $uiResolver->setAllowedValues('entrypoint_type', ['text', 'file']); + $uiResolver->setNormalizer('constraints', function (Options $options, array $values): array { + return (new ConstraintLoader())->buildConstraints($values); + }); + }); + + return $resolver->resolve($options); + } + /** @return ProcessConfiguration[] */ private function getConfigurations(): array { diff --git a/src/Twig/Runtime/ProcessExecutionExtensionRuntime.php b/src/Twig/Runtime/ProcessExecutionExtensionRuntime.php index eb988cb..1ad0a7b 100644 --- a/src/Twig/Runtime/ProcessExecutionExtensionRuntime.php +++ b/src/Twig/Runtime/ProcessExecutionExtensionRuntime.php @@ -4,8 +4,8 @@ namespace CleverAge\ProcessUiBundle\Twig\Runtime; -use CleverAge\ProcessBundle\Registry\ProcessConfigurationRegistry; use CleverAge\ProcessUiBundle\Entity\ProcessExecution; +use CleverAge\ProcessUiBundle\Manager\ProcessConfigurationsManager; use CleverAge\ProcessUiBundle\Repository\ProcessExecutionRepository; use Twig\Extension\RuntimeExtensionInterface; @@ -13,7 +13,7 @@ { public function __construct( private ProcessExecutionRepository $processExecutionRepository, - private ProcessConfigurationRegistry $processConfigurationRegistry + private ProcessConfigurationsManager $processConfigurationsManager, ) { } @@ -25,13 +25,11 @@ public function getLastExecutionDate(string $code): ?ProcessExecution public function getProcessSource(string $code): ?string { - return $this->processConfigurationRegistry - ->getProcessConfiguration($code)?->getOptions()['ui']['source'] ?? null; + return $this->processConfigurationsManager->getUiOptions($code)['source']; } public function getProcessTarget(string $code): ?string { - return $this->processConfigurationRegistry - ->getProcessConfiguration($code)?->getOptions()['ui']['target'] ?? null; + return $this->processConfigurationsManager->getUiOptions($code)['target']; } } From 7dbdb51572e5d44a5ca32efbf5347143634c82cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Tue, 3 Sep 2024 10:47:27 +0200 Subject: [PATCH 20/39] [FIX] Error on _em for ProcessExecutionRepository --- src/Repository/ProcessExecutionRepository.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Repository/ProcessExecutionRepository.php b/src/Repository/ProcessExecutionRepository.php index b761ff7..6125786 100644 --- a/src/Repository/ProcessExecutionRepository.php +++ b/src/Repository/ProcessExecutionRepository.php @@ -25,16 +25,15 @@ public function __construct(ManagerRegistry $registry) public function save(ProcessExecution $processExecution): void { - $this->_em->persist($processExecution); - $this->_em->flush(); + $this->getEntityManager()->persist($processExecution); + $this->getEntityManager()->flush(); } public function getLastProcessExecution(string $code): ?ProcessExecution { - $qb = $this->_em->createQueryBuilder(); + $qb = $this->createQueryBuilder('pe'); return $qb->select('pe') - ->from(ProcessExecution::class, 'pe') ->where($qb->expr()->eq('pe.code', $qb->expr()->literal($code))) ->orderBy('pe.startDate', 'DESC') ->setMaxResults(1) From b4209aeef4151b5babdd98705f0c18d128833884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Fri, 6 Sep 2024 11:18:35 +0200 Subject: [PATCH 21/39] Add message to log crud filters --- src/Controller/Admin/LogRecordCrudController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Controller/Admin/LogRecordCrudController.php b/src/Controller/Admin/LogRecordCrudController.php index d88d9bf..f831ada 100644 --- a/src/Controller/Admin/LogRecordCrudController.php +++ b/src/Controller/Admin/LogRecordCrudController.php @@ -84,6 +84,7 @@ public function configureFilters(Filters $filters): Filters ->add( ChoiceFilter::new('level')->setChoices(array_combine(Level::NAMES, Level::VALUES)) ) + ->add('message') ->add('createdAt'); } } From e116c6c619e24bb2294adcb4a5f0263501568129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Thu, 12 Sep 2024 11:18:04 +0200 Subject: [PATCH 22/39] Fix log handler (log in same file for subprocess) && messenger --- .../ProcessEventSubscriber.php | 20 +++++++++++-------- src/Message/ProcessExecuteHandler.php | 4 +++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/EventSubscriber/ProcessEventSubscriber.php b/src/EventSubscriber/ProcessEventSubscriber.php index 783e51f..99ee679 100644 --- a/src/EventSubscriber/ProcessEventSubscriber.php +++ b/src/EventSubscriber/ProcessEventSubscriber.php @@ -38,18 +38,22 @@ public function onProcessStart(ProcessEvent $event): void public function success(ProcessEvent $event): void { - $this->processExecutionManager->getCurrentProcessExecution()?->setStatus(ProcessExecutionStatus::Finish); - $this->processExecutionManager->getCurrentProcessExecution()?->end(); - $this->processExecutionManager->save()->unsetProcessExecution($event->getProcessCode()); - $this->processHandler->close(); + if ($event->getProcessCode() === $this->processExecutionManager?->getCurrentProcessExecution()?->getCode()) { + $this->processExecutionManager->getCurrentProcessExecution()?->setStatus(ProcessExecutionStatus::Finish); + $this->processExecutionManager->getCurrentProcessExecution()?->end(); + $this->processExecutionManager->save()->unsetProcessExecution($event->getProcessCode()); + $this->processHandler->close(); + } } public function fail(ProcessEvent $event): void { - $this->processExecutionManager->getCurrentProcessExecution()?->setStatus(ProcessExecutionStatus::Failed); - $this->processExecutionManager->getCurrentProcessExecution()?->end(); - $this->processExecutionManager->save()->unsetProcessExecution($event->getProcessCode()); - $this->processHandler->close(); + if ($event->getProcessCode() === $this->processExecutionManager?->getCurrentProcessExecution()?->getCode()) { + $this->processExecutionManager->getCurrentProcessExecution()?->setStatus(ProcessExecutionStatus::Failed); + $this->processExecutionManager->getCurrentProcessExecution()?->end(); + $this->processExecutionManager->save()->unsetProcessExecution($event->getProcessCode()); + $this->processHandler->close(); + } } public function flushDoctrineLogs(ProcessEvent $event): void diff --git a/src/Message/ProcessExecuteHandler.php b/src/Message/ProcessExecuteHandler.php index 979c8f2..48e1b8a 100644 --- a/src/Message/ProcessExecuteHandler.php +++ b/src/Message/ProcessExecuteHandler.php @@ -5,17 +5,19 @@ namespace CleverAge\ProcessUiBundle\Message; use CleverAge\ProcessBundle\Manager\ProcessManager; +use CleverAge\ProcessUiBundle\Monolog\Handler\ProcessHandler; use Symfony\Component\Messenger\Attribute\AsMessageHandler; #[AsMessageHandler] readonly class ProcessExecuteHandler { - public function __construct(private ProcessManager $manager) + public function __construct(private ProcessManager $manager, private readonly ProcessHandler $processHandler) { } public function __invoke(ProcessExecuteMessage $message): void { + $this->processHandler->close(); $this->manager->execute($message->code, $message->input, $message->context); } } From df0e1f7ff2801e965884c7fe4677332000589ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 18 Sep 2024 10:03:18 +0200 Subject: [PATCH 23/39] Remove custom index query builder --- .../Admin/ProcessExecutionCrudController.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Controller/Admin/ProcessExecutionCrudController.php b/src/Controller/Admin/ProcessExecutionCrudController.php index 40300a3..c25412b 100644 --- a/src/Controller/Admin/ProcessExecutionCrudController.php +++ b/src/Controller/Admin/ProcessExecutionCrudController.php @@ -138,17 +138,4 @@ public function configureFilters(Filters $filters): Filters { return $filters->add('code')->add('startDate'); } - - public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder - { - $codes = array_map( - fn(ProcessConfiguration $configuration) => $configuration->getCode(), - $this->processConfigurationsManager->getPublicProcesses() - ); - $queryBuilder = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters); - $queryBuilder->where($queryBuilder->expr()->in($queryBuilder->getRootAliases()[0] . '.code', ':codes')); - $queryBuilder->setParameter('codes', $codes); - - return $queryBuilder; - } } From 1f37a57c91729813b450d0f8c253a41fd8ff6973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 18 Sep 2024 11:04:26 +0200 Subject: [PATCH 24/39] Fix scheduler on first migration run --- src/Scheduler/CronScheduler.php | 52 ++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/Scheduler/CronScheduler.php b/src/Scheduler/CronScheduler.php index e5de3b2..b9e647d 100644 --- a/src/Scheduler/CronScheduler.php +++ b/src/Scheduler/CronScheduler.php @@ -12,6 +12,7 @@ use Symfony\Component\Scheduler\Schedule; use Symfony\Component\Scheduler\ScheduleProviderInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; +use Doctrine\DBAL\Driver\AbstractException; #[AsSchedule('cron')] #[WithMonologChannel('scheduler')] @@ -28,34 +29,39 @@ public function __construct( public function getSchedule(): Schedule { $schedule = new Schedule(); - foreach ($this->repository->findAll() as $processSchedule) { - $violations = $this->validator->validate($processSchedule); - if (0 !== $violations->count()) { - foreach ($violations as $violation) { - $this->logger->info( - 'Scheduler configuration is not valid.', - ['reason' => $violation->getMessage()] + try { + foreach ($this->repository->findAll() as $processSchedule) { + $violations = $this->validator->validate($processSchedule); + if (0 !== $violations->count()) { + foreach ($violations as $violation) { + $this->logger->info( + 'Scheduler configuration is not valid.', + ['reason' => $violation->getMessage()] + ); + } + continue; + } + if (ProcessScheduleType::CRON === $processSchedule->getType()) { + $schedule->add( + RecurringMessage::cron( + $processSchedule->getExpression(), + new CronProcessMessage($processSchedule) + ) + ); + } elseif (ProcessScheduleType::EVERY === $processSchedule->getType()) { + $schedule->add( + RecurringMessage::every( + $processSchedule->getExpression(), + new CronProcessMessage($processSchedule) + ) ); } - continue; - } - if (ProcessScheduleType::CRON === $processSchedule->getType()) { - $schedule->add( - RecurringMessage::cron( - $processSchedule->getExpression(), - new CronProcessMessage($processSchedule) - ) - ); - } elseif (ProcessScheduleType::EVERY === $processSchedule->getType()) { - $schedule->add( - RecurringMessage::every( - $processSchedule->getExpression(), - new CronProcessMessage($processSchedule) - ) - ); } + } catch (\Exception $e) { + $this->logger->critical($e->getMessage()); } return $schedule; } } + From 15c7b420606014809d0734249bf0b06f0df2a55c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Tue, 24 Sep 2024 16:08:23 +0200 Subject: [PATCH 25/39] Add context to filter on log crud --- src/Controller/Admin/LogRecordCrudController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Controller/Admin/LogRecordCrudController.php b/src/Controller/Admin/LogRecordCrudController.php index f831ada..c954bc6 100644 --- a/src/Controller/Admin/LogRecordCrudController.php +++ b/src/Controller/Admin/LogRecordCrudController.php @@ -85,6 +85,7 @@ public function configureFilters(Filters $filters): Filters ChoiceFilter::new('level')->setChoices(array_combine(Level::NAMES, Level::VALUES)) ) ->add('message') + ->add('context') ->add('createdAt'); } } From aca79fcc4d5d67c77bff620ce45beac6089aafbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Mon, 7 Oct 2024 16:03:45 +0200 Subject: [PATCH 26/39] Add timezone to user --- .../Admin/ProcessDashboardController.php | 8 +++++ src/Controller/Admin/UserCrudController.php | 3 ++ src/Entity/User.php | 16 +++++++++ src/Migrations/Version20241007134542.php | 33 +++++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 src/Migrations/Version20241007134542.php diff --git a/src/Controller/Admin/ProcessDashboardController.php b/src/Controller/Admin/ProcessDashboardController.php index bd6edd8..14befc2 100644 --- a/src/Controller/Admin/ProcessDashboardController.php +++ b/src/Controller/Admin/ProcessDashboardController.php @@ -8,6 +8,7 @@ use CleverAge\ProcessUiBundle\Entity\ProcessExecution; use CleverAge\ProcessUiBundle\Entity\ProcessSchedule; use CleverAge\ProcessUiBundle\Entity\User; +use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard; use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController; @@ -56,4 +57,11 @@ public function configureMenuItems(): iterable ); } } + + public function configureCrud(): Crud + { + /** @var User $user */ + $user = $this->getUser(); + return parent::configureCrud()->setTimezone($user?->getTimezone() ?? date_default_timezone_get()); + } } diff --git a/src/Controller/Admin/UserCrudController.php b/src/Controller/Admin/UserCrudController.php index fd964e3..895e6f5 100644 --- a/src/Controller/Admin/UserCrudController.php +++ b/src/Controller/Admin/UserCrudController.php @@ -14,6 +14,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField; use EasyCorp\Bundle\EasyAdminBundle\Field\FormField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; +use EasyCorp\Bundle\EasyAdminBundle\Field\TimezoneField; use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; @@ -69,6 +70,8 @@ public function configureFields(string $pageName): iterable yield ChoiceField::new('roles', false) ->setChoices($this->roles) ->setFormTypeOptions(['multiple' => true, 'expanded' => true]); + yield FormField::addTab('Intl.')->setIcon('fa fa-flag'); + yield TimezoneField::new('timezone'); } public function configureActions(Actions $actions): Actions diff --git a/src/Entity/User.php b/src/Entity/User.php index 02832af..03e5d05 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -33,6 +33,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(type: 'string', length: 255, nullable: true)] private ?string $password; + #[ORM\Column(type: 'string', length: 255, nullable: true)] + private ?string $timezone; + #[ORM\Column(type: 'string', length: 255, nullable: true)] private ?string $token; @@ -92,6 +95,19 @@ public function getUsername(): string return $this->getUserIdentifier(); } + public function getTimezone(): ?string + { + return $this->timezone; + } + + public function setTimezone(?string $timezone): self + { + $this->timezone = $timezone; + + return $this; + } + + /** * @see UserInterface */ diff --git a/src/Migrations/Version20241007134542.php b/src/Migrations/Version20241007134542.php new file mode 100644 index 0000000..259c62f --- /dev/null +++ b/src/Migrations/Version20241007134542.php @@ -0,0 +1,33 @@ +hasTable('process_user') && !$schema->getTable('process_user')->hasColumn('timezone')) { + $this->addSql('ALTER TABLE process_user ADD timezone VARCHAR(255) DEFAULT NULL'); + } + } + + public function down(Schema $schema): void + { + if ($schema->hasTable('process_user') && $schema->getTable('process_user')->hasColumn('timezone')) { + $this->addSql('ALTER TABLE process_user DROP timezone'); + } + } +} From 252e3ff36d56fd39c54393cd9fffc0f1c5c010c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Mon, 7 Oct 2024 17:48:57 +0200 Subject: [PATCH 27/39] Add context to process execution --- src/Controller/Admin/Process/LaunchAction.php | 13 ++++++- .../Admin/ProcessExecutionCrudController.php | 1 + src/Entity/ProcessExecution.php | 29 ++++++++++++---- .../ProcessEventSubscriber.php | 3 +- src/Migrations/Version20241007152613.php | 34 +++++++++++++++++++ 5 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 src/Migrations/Version20241007152613.php diff --git a/src/Controller/Admin/Process/LaunchAction.php b/src/Controller/Admin/Process/LaunchAction.php index 665bb58..b3e56cd 100644 --- a/src/Controller/Admin/Process/LaunchAction.php +++ b/src/Controller/Admin/Process/LaunchAction.php @@ -5,6 +5,7 @@ namespace CleverAge\ProcessUiBundle\Controller\Admin\Process; use CleverAge\ProcessBundle\Configuration\ProcessConfiguration; +use CleverAge\ProcessUiBundle\Entity\User; use CleverAge\ProcessUiBundle\Form\Type\LaunchType; use CleverAge\ProcessUiBundle\Manager\ProcessConfigurationsManager; use CleverAge\ProcessUiBundle\Message\ProcessExecuteMessage; @@ -71,7 +72,10 @@ public function __invoke( $message = new ProcessExecuteMessage( $form->getConfig()->getOption('process_code'), $input, - $form->get('context')->getData() + array_merge( + ['execution_user' => $this->getUser()?->getEmail()], + $form->get('context')->getData() + ) ); $messageBus->dispatch($message); $this->addFlash( @@ -89,4 +93,11 @@ public function __invoke( ] ); } + + protected function getUser(): ?User + { + /** @var User $user */ + $user = parent::getUser(); + return $user; + } } diff --git a/src/Controller/Admin/ProcessExecutionCrudController.php b/src/Controller/Admin/ProcessExecutionCrudController.php index c25412b..220ea6d 100644 --- a/src/Controller/Admin/ProcessExecutionCrudController.php +++ b/src/Controller/Admin/ProcessExecutionCrudController.php @@ -53,6 +53,7 @@ public function configureFields(string $pageName): iterable return $entity->duration(); // returned format can be changed here }), ArrayField::new('report')->setTemplatePath('@CleverAgeProcessUi/admin/field/report.html.twig'), + ArrayField::new('context')->setTemplatePath('@CleverAgeProcessUi/admin/field/report.html.twig'), ]; } diff --git a/src/Entity/ProcessExecution.php b/src/Entity/ProcessExecution.php index 82e5e05..b75083f 100644 --- a/src/Entity/ProcessExecution.php +++ b/src/Entity/ProcessExecution.php @@ -5,6 +5,7 @@ namespace CleverAge\ProcessUiBundle\Entity; use CleverAge\ProcessUiBundle\Entity\Enum\ProcessExecutionStatus; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\String\UnicodeString; @@ -18,35 +19,39 @@ class ProcessExecution #[ORM\Column] private ?int $id = null; - #[ORM\Column(type: 'string', length: 255)] + #[ORM\Column(type: Types::STRING, length: 255)] public readonly string $code; - #[ORM\Column(type: 'string', length: 255)] + #[ORM\Column(type: Types::STRING, length: 255)] public readonly string $logFilename; - #[ORM\Column(type: 'datetime_immutable')] + #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] public readonly \DateTimeImmutable $startDate; - #[ORM\Column(type: 'datetime_immutable', nullable: true)] + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] public ?\DateTimeImmutable $endDate = null; - #[ORM\Column(type: 'string', enumType: ProcessExecutionStatus::class)] + #[ORM\Column(type: Types::STRING, enumType: ProcessExecutionStatus::class)] public ProcessExecutionStatus $status; - #[ORM\Column(type: 'json')] + #[ORM\Column(type: Types::JSON)] private array $report = []; + #[ORM\Column(type: Types::JSON, nullable: true)] + private ?array $context = []; + public function getId(): ?int { return $this->id; } - public function __construct(string $code, string $logFilename) + public function __construct(string $code, string $logFilename, ?array $context = []) { $this->code = (string) (new UnicodeString($code))->truncate(255); $this->logFilename = $logFilename; $this->startDate = \DateTimeImmutable::createFromMutable(new \DateTime()); $this->status = ProcessExecutionStatus::Started; + $this->context = $context ?? []; } public function setStatus(ProcessExecutionStatus $status): void @@ -91,4 +96,14 @@ public function getCode(): string { return $this->code; } + + public function getContext(): array + { + return $this->context; + } + + public function setContext(array $context): void + { + $this->context = $context; + } } diff --git a/src/EventSubscriber/ProcessEventSubscriber.php b/src/EventSubscriber/ProcessEventSubscriber.php index 99ee679..3d17791 100644 --- a/src/EventSubscriber/ProcessEventSubscriber.php +++ b/src/EventSubscriber/ProcessEventSubscriber.php @@ -30,7 +30,8 @@ public function onProcessStart(ProcessEvent $event): void if (null === $this->processExecutionManager->getCurrentProcessExecution()) { $processExecution = new ProcessExecution( $event->getProcessCode(), - basename($this->processHandler->getFilename()) + basename($this->processHandler->getFilename()), + $event->getProcessContext() ); $this->processExecutionManager->setCurrentProcessExecution($processExecution)->save(); } diff --git a/src/Migrations/Version20241007152613.php b/src/Migrations/Version20241007152613.php new file mode 100644 index 0000000..b6360b2 --- /dev/null +++ b/src/Migrations/Version20241007152613.php @@ -0,0 +1,34 @@ +hasTable('process_execution') && !$schema->getTable('process_execution')->hasColumn('context')) { + $this->addSql('ALTER TABLE process_execution ADD context JSON NOT NULL COMMENT \'(DC2Type:json)\''); + } + } + + public function down(Schema $schema): void + { + if ($schema->hasTable('process_execution') && $schema->getTable('process_execution')->hasColumn('context')) { + $this->addSql('ALTER TABLE process_execution DROP context'); + + } + } +} From 374e6302697af3de61fe4dfb606f0f1077a87507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Tue, 8 Oct 2024 15:50:14 +0200 Subject: [PATCH 28/39] Delete comment on last migration --- src/Migrations/Version20241007152613.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migrations/Version20241007152613.php b/src/Migrations/Version20241007152613.php index b6360b2..c5e1ddf 100644 --- a/src/Migrations/Version20241007152613.php +++ b/src/Migrations/Version20241007152613.php @@ -20,7 +20,7 @@ public function getDescription(): string public function up(Schema $schema): void { if ($schema->hasTable('process_execution') && !$schema->getTable('process_execution')->hasColumn('context')) { - $this->addSql('ALTER TABLE process_execution ADD context JSON NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE process_execution ADD context JSON NOT NULL'); } } From c3e6378f66a12f91dfa404a1410e102a7a85c6a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 9 Oct 2024 10:35:59 +0200 Subject: [PATCH 29/39] Add locale managment to user --- .../Admin/ProcessDashboardController.php | 9 +++- src/Controller/Admin/UserCrudController.php | 2 + src/Entity/User.php | 15 ++++++ src/Migrations/Version20241009075733.php | 30 +++++++++++ templates/admin/process/list.html.twig | 12 ++--- translations/messages.fr.yaml | 54 +++++++++++++++++++ 6 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 src/Migrations/Version20241009075733.php create mode 100644 translations/messages.fr.yaml diff --git a/src/Controller/Admin/ProcessDashboardController.php b/src/Controller/Admin/ProcessDashboardController.php index 14befc2..35d9cc6 100644 --- a/src/Controller/Admin/ProcessDashboardController.php +++ b/src/Controller/Admin/ProcessDashboardController.php @@ -16,11 +16,15 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; +use Symfony\Component\Translation\LocaleSwitcher; #[IsGranted('ROLE_USER')] class ProcessDashboardController extends AbstractDashboardController { - public function __construct(private readonly string $logoPath = '') + public function __construct( + private readonly LocaleSwitcher $localeSwitcher, + private readonly string $logoPath = '' + ) { } @@ -62,6 +66,9 @@ public function configureCrud(): Crud { /** @var User $user */ $user = $this->getUser(); + if (null !== $user->getLocale()) { + $this->localeSwitcher->setLocale($user->getLocale()); + } return parent::configureCrud()->setTimezone($user?->getTimezone() ?? date_default_timezone_get()); } } diff --git a/src/Controller/Admin/UserCrudController.php b/src/Controller/Admin/UserCrudController.php index 895e6f5..18482c6 100644 --- a/src/Controller/Admin/UserCrudController.php +++ b/src/Controller/Admin/UserCrudController.php @@ -13,6 +13,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField; use EasyCorp\Bundle\EasyAdminBundle\Field\FormField; +use EasyCorp\Bundle\EasyAdminBundle\Field\LocaleField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Field\TimezoneField; use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; @@ -72,6 +73,7 @@ public function configureFields(string $pageName): iterable ->setFormTypeOptions(['multiple' => true, 'expanded' => true]); yield FormField::addTab('Intl.')->setIcon('fa fa-flag'); yield TimezoneField::new('timezone'); + yield LocaleField::new('locale'); } public function configureActions(Actions $actions): Actions diff --git a/src/Entity/User.php b/src/Entity/User.php index 03e5d05..37f2413 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -36,6 +36,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(type: 'string', length: 255, nullable: true)] private ?string $timezone; + #[ORM\Column(type: 'string', length: 255, nullable: true)] + private ?string $locale; + #[ORM\Column(type: 'string', length: 255, nullable: true)] private ?string $token; @@ -107,6 +110,18 @@ public function setTimezone(?string $timezone): self return $this; } + public function getLocale(): ?string + { + return $this->locale; + } + + public function setLocale(?string $locale): self + { + $this->locale = $locale; + + return $this; + } + /** * @see UserInterface diff --git a/src/Migrations/Version20241009075733.php b/src/Migrations/Version20241009075733.php new file mode 100644 index 0000000..1281ae3 --- /dev/null +++ b/src/Migrations/Version20241009075733.php @@ -0,0 +1,30 @@ +hasTable('process_user') && !$schema->getTable('process_user')->hasColumn('locale')) { + $this->addSql('ALTER TABLE process_user ADD locale VARCHAR(255) DEFAULT NULL'); + } + } + + public function down(Schema $schema): void + { + if ($schema->hasTable('process_user') && $schema->getTable('process_user')->hasColumn('locale')) { + $this->addSql('ALTER TABLE process_user DROP locale'); + } + } +} diff --git a/templates/admin/process/list.html.twig b/templates/admin/process/list.html.twig index 899a671..ac6d384 100644 --- a/templates/admin/process/list.html.twig +++ b/templates/admin/process/list.html.twig @@ -7,12 +7,12 @@ {% block table_head %} - Process code - Last execution - Status - Source - Target - Actions + {{ 'Process code'|trans }} + {{ 'Last execution'|trans }} + {{ 'Status'|trans }} + {{ 'Source'|trans }} + {{ 'Target'|trans }} + {{ 'Actions'|trans }} {% endblock %} diff --git a/translations/messages.fr.yaml b/translations/messages.fr.yaml new file mode 100644 index 0000000..4d276e3 --- /dev/null +++ b/translations/messages.fr.yaml @@ -0,0 +1,54 @@ +Generate Token: Générer un jeton +Credentials: Informations d'identification +User: Utilisateur +Dashboard: Tableau de bord +Process: Processus +Process list: Liste des processus +Executions: Exécutions +Logs: Logs +Scheduler: Planificateur +Users: Utilisateurs +User List: Liste des utilisateurs +Informations: Informations +Roles: Rôles +Intl.: Internationalisation +Email: Email +New password: Nouveau mot de passe +Repeat password: Répéter le mot de passe +Firstname: Prénom +Lastname: Nom +Timezone: Fuseau horaire +Locale: Locale +ProcessExecution: Exécution des processus +Code: Code +Status: Status +Start Date: Date de début +End Date: Date de fin +Source: Source +Target: Destination +Duration: Durée +Report: Rapport +Context: Context +Launch: Exécuter +View executions: Voir les éxécutions +Process code: Code du processus +Last execution: Dernière exécution +Actions: Actions +LogRecord: Logs +Level: Niveau +Message: Message +Created At: Date de création +Has context info ?: Context ? +ProcessSchedule: Planificateur +To run scheduler, ensure "bin/console messenger:consume scheduler_cron" console is alive. See https://symfony.com/doc/current/messenger.html#supervisor-configuration.: "Pour fonctionner, assurez vous que la commande \"bin/console messenger:consume scheduler_cron\" soit exécutée. Plus d''informations sur https://symfony.com/doc/current/messenger.html#supervisor-configuration." +Delete: Supprimer +Type: Type +Expression: Expression +Next Execution: Prochaîne exécution +Input: Input +General: Général +Context (key/value): Context (clé/valeur) +key: clé +value: valeur +Context Key: Context clé +Context Value: Context valeur From 6734e40e5fa006175cb130a800499571e28f3fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 9 Oct 2024 10:50:20 +0200 Subject: [PATCH 30/39] PHPCS Fix --- src/Admin/Field/EnumField.php | 2 +- src/Admin/Field/LogLevelField.php | 2 +- src/Admin/Filter/LogProcessCodeFilter.php | 7 +++++-- .../Admin/LogRecordCrudController.php | 21 +++++++------------ src/Controller/Admin/Process/LaunchAction.php | 2 ++ src/Controller/Admin/Process/ListAction.php | 2 +- .../Admin/ProcessDashboardController.php | 4 ++-- .../Admin/ProcessExecutionCrudController.php | 12 +---------- .../Admin/ProcessScheduleCrudController.php | 11 +++++----- src/Controller/Admin/UserCrudController.php | 3 +-- src/Controller/ProcessExecuteController.php | 3 +-- src/Entity/ProcessExecution.php | 3 ++- src/Entity/ProcessSchedule.php | 4 ++-- src/Entity/User.php | 2 -- src/Form/Type/LaunchType.php | 20 ++++++++---------- src/Form/Type/ProcessContextType.php | 5 ++--- src/Http/Model/HttpProcessExecution.php | 8 +++---- .../HttpProcessExecuteValueResolver.php | 8 +++---- src/Manager/ProcessConfigurationsManager.php | 11 +++++----- src/Message/CronProcessMessage.php | 2 +- src/Message/CronProcessMessageHandler.php | 4 ++-- src/Migrations/Version20231006111525.php | 8 +++---- src/Migrations/Version20240729151928.php | 1 - src/Migrations/Version20240730090403.php | 4 ---- src/Migrations/Version20241007152613.php | 1 - src/Scheduler/CronScheduler.php | 9 ++++---- .../HttpProcessExecutionAuthenticator.php | 2 +- src/Twig/Runtime/LogLevelExtensionRuntime.php | 12 +++++------ .../ProcessExecutionExtensionRuntime.php | 3 +-- 29 files changed, 75 insertions(+), 101 deletions(-) diff --git a/src/Admin/Field/EnumField.php b/src/Admin/Field/EnumField.php index 6f39f7b..6c9ab87 100644 --- a/src/Admin/Field/EnumField.php +++ b/src/Admin/Field/EnumField.php @@ -11,7 +11,7 @@ class EnumField implements FieldInterface { use FieldTrait; - public static function new(string $propertyName, string $label = null): self + public static function new(string $propertyName, ?string $label = null): self { return (new self()) ->setProperty($propertyName) diff --git a/src/Admin/Field/LogLevelField.php b/src/Admin/Field/LogLevelField.php index df0c2bf..67250c8 100644 --- a/src/Admin/Field/LogLevelField.php +++ b/src/Admin/Field/LogLevelField.php @@ -11,7 +11,7 @@ class LogLevelField implements FieldInterface { use FieldTrait; - public static function new(string $propertyName, string $label = null): self + public static function new(string $propertyName, ?string $label = null): self { return (new self()) ->setProperty($propertyName) diff --git a/src/Admin/Filter/LogProcessCodeFilter.php b/src/Admin/Filter/LogProcessCodeFilter.php index 6d738ff..e4af123 100644 --- a/src/Admin/Filter/LogProcessCodeFilter.php +++ b/src/Admin/Filter/LogProcessCodeFilter.php @@ -1,4 +1,5 @@ getPublicProcesses(); - $choices = array_map(fn(ProcessConfiguration $cfg) => $cfg->getCode(), $choices); + $choices = array_map(fn (ProcessConfiguration $cfg) => $cfg->getCode(), $choices); $this->setFormTypeOption('choices', $choices); return $this; @@ -43,6 +44,7 @@ public function setCurrentProcessExecutionId(?int $currentProcessExecutionId): s if (0 !== $this->currentProcessExecutionId) { $this->setFormTypeOption('disabled', true); } + return $this; } @@ -52,9 +54,10 @@ public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, if (null !== $this->currentProcessExecutionId) { $queryBuilder->andWhere($queryBuilder->expr()->eq('pe.id', ':id')); $queryBuilder->setParameter('id', $this->currentProcessExecutionId); + return; } $queryBuilder->where('pe.code IN (:codes)'); $queryBuilder->setParameter('codes', $filterDataDto->getValue()); } -} \ No newline at end of file +} diff --git a/src/Controller/Admin/LogRecordCrudController.php b/src/Controller/Admin/LogRecordCrudController.php index c954bc6..f840c62 100644 --- a/src/Controller/Admin/LogRecordCrudController.php +++ b/src/Controller/Admin/LogRecordCrudController.php @@ -25,12 +25,10 @@ #[IsGranted('ROLE_USER')] class LogRecordCrudController extends AbstractCrudController { - public function __construct( private readonly ProcessConfigurationsManager $processConfigurationsManager, private readonly RequestStack $request - ) - { + ) { } public static function getEntityFqcn(): string @@ -76,16 +74,13 @@ public function configureActions(Actions $actions): Actions public function configureFilters(Filters $filters): Filters { $id = $this->request->getMainRequest()->query->all('filters')['processExecution']['value'] ?? null; + return $filters->add( - LogProcessCodeFilter::new('process') - ->addChoices($this->processConfigurationsManager) - ->setCurrentProcessExecutionId((int) $id) - ) - ->add( - ChoiceFilter::new('level')->setChoices(array_combine(Level::NAMES, Level::VALUES)) - ) - ->add('message') - ->add('context') - ->add('createdAt'); + LogProcessCodeFilter::new('process') + ->addChoices($this->processConfigurationsManager) + ->setCurrentProcessExecutionId((int) $id) + )->add( + ChoiceFilter::new('level')->setChoices(array_combine(Level::NAMES, Level::VALUES)) + )->add('message')->add('context')->add('createdAt'); } } diff --git a/src/Controller/Admin/Process/LaunchAction.php b/src/Controller/Admin/Process/LaunchAction.php index b3e56cd..5d5b98c 100644 --- a/src/Controller/Admin/Process/LaunchAction.php +++ b/src/Controller/Admin/Process/LaunchAction.php @@ -86,6 +86,7 @@ public function __invoke( return $this->redirectToRoute('process', ['routeName' => 'process_list']); } $context->getAssets()->addJsAsset(Asset::fromEasyAdminAssetPackage('field-collection.js')->getAsDto()); + return $this->render( '@CleverAgeProcessUi/admin/process/launch.html.twig', [ @@ -98,6 +99,7 @@ protected function getUser(): ?User { /** @var User $user */ $user = parent::getUser(); + return $user; } } diff --git a/src/Controller/Admin/Process/ListAction.php b/src/Controller/Admin/Process/ListAction.php index 26726b1..1af4a0f 100644 --- a/src/Controller/Admin/Process/ListAction.php +++ b/src/Controller/Admin/Process/ListAction.php @@ -19,7 +19,7 @@ public function __invoke(ProcessConfigurationsManager $processConfigurationsMana return $this->render( '@CleverAgeProcessUi/admin/process/list.html.twig', [ - 'processes' => $processConfigurationsManager->getPublicProcesses() + 'processes' => $processConfigurationsManager->getPublicProcesses(), ] ); } diff --git a/src/Controller/Admin/ProcessDashboardController.php b/src/Controller/Admin/ProcessDashboardController.php index 35d9cc6..41bfcd1 100644 --- a/src/Controller/Admin/ProcessDashboardController.php +++ b/src/Controller/Admin/ProcessDashboardController.php @@ -24,8 +24,7 @@ class ProcessDashboardController extends AbstractDashboardController public function __construct( private readonly LocaleSwitcher $localeSwitcher, private readonly string $logoPath = '' - ) - { + ) { } #[Route('/process', name: 'process')] @@ -69,6 +68,7 @@ public function configureCrud(): Crud if (null !== $user->getLocale()) { $this->localeSwitcher->setLocale($user->getLocale()); } + return parent::configureCrud()->setTimezone($user?->getTimezone() ?? date_default_timezone_get()); } } diff --git a/src/Controller/Admin/ProcessExecutionCrudController.php b/src/Controller/Admin/ProcessExecutionCrudController.php index 220ea6d..fad622f 100644 --- a/src/Controller/Admin/ProcessExecutionCrudController.php +++ b/src/Controller/Admin/ProcessExecutionCrudController.php @@ -4,21 +4,14 @@ namespace CleverAge\ProcessUiBundle\Controller\Admin; -use CleverAge\ProcessBundle\Configuration\ProcessConfiguration; use CleverAge\ProcessUiBundle\Admin\Field\EnumField; use CleverAge\ProcessUiBundle\Entity\ProcessExecution; -use CleverAge\ProcessUiBundle\Manager\ProcessConfigurationsManager; -use Doctrine\ORM\QueryBuilder; -use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection; -use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Filters; use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; -use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; -use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto; use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; @@ -32,9 +25,6 @@ #[IsGranted('ROLE_USER')] class ProcessExecutionCrudController extends AbstractCrudController { - public function __construct(private readonly ProcessConfigurationsManager $processConfigurationsManager) - { - } public static function getEntityFqcn(): string { return ProcessExecution::class; @@ -49,7 +39,7 @@ public function configureFields(string $pageName): iterable DateTimeField::new('endDate')->setFormat('Y/M/dd H:mm:ss'), TextField::new('source')->setTemplatePath('@CleverAgeProcessUi/admin/field/process_source.html.twig'), TextField::new('target')->setTemplatePath('@CleverAgeProcessUi/admin/field/process_target.html.twig'), - TextField::new('duration')->formatValue(function($value, ProcessExecution $entity) { + TextField::new('duration')->formatValue(function ($value, ProcessExecution $entity) { return $entity->duration(); // returned format can be changed here }), ArrayField::new('report')->setTemplatePath('@CleverAgeProcessUi/admin/field/report.html.twig'), diff --git a/src/Controller/Admin/ProcessScheduleCrudController.php b/src/Controller/Admin/ProcessScheduleCrudController.php index 7193165..9aff0e9 100644 --- a/src/Controller/Admin/ProcessScheduleCrudController.php +++ b/src/Controller/Admin/ProcessScheduleCrudController.php @@ -4,11 +4,11 @@ namespace CleverAge\ProcessUiBundle\Controller\Admin; +use CleverAge\ProcessBundle\Configuration\ProcessConfiguration; +use CleverAge\ProcessUiBundle\Admin\Field\EnumField; use CleverAge\ProcessUiBundle\Entity\ProcessSchedule; use CleverAge\ProcessUiBundle\Entity\ProcessScheduleType; use CleverAge\ProcessUiBundle\Form\Type\ProcessContextType; -use CleverAge\ProcessBundle\Configuration\ProcessConfiguration; -use CleverAge\ProcessUiBundle\Admin\Field\EnumField; use CleverAge\ProcessUiBundle\Manager\ProcessConfigurationsManager; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; @@ -68,10 +68,10 @@ public static function getEntityFqcn(): string public function configureFields(string $pageName): iterable { - - $choices = array_map(function(ProcessConfiguration $configuration) { + $choices = array_map(function (ProcessConfiguration $configuration) { return [$configuration->getCode()]; }, $this->processConfigurationsManager->getPublicProcesses()); + return [ FormField::addTab('General'), TextField::new('process') @@ -99,7 +99,7 @@ public function configureFields(string $pageName): iterable ->hideOnIndex() ->setFormTypeOption('entry_options.label', 'Context (key/value)') ->setFormTypeOption('label', '') - ->setFormTypeOption('required', false) + ->setFormTypeOption('required', false), ]; } @@ -114,7 +114,6 @@ public function index(AdminContext $context): KeyValueStore|RedirectResponse|Res private function schedulerIsRunning(): bool { - $process = Process::fromShellCommandline('ps -faux'); $process->run(); $out = $process->getOutput(); diff --git a/src/Controller/Admin/UserCrudController.php b/src/Controller/Admin/UserCrudController.php index 18482c6..16b4fbf 100644 --- a/src/Controller/Admin/UserCrudController.php +++ b/src/Controller/Admin/UserCrudController.php @@ -97,7 +97,6 @@ public function configureActions(Actions $actions): Actions })->add(Crud::PAGE_EDIT, Action::new('generateToken')->linkToCrudAction('generateToken')); } - public function generateToken(AdminContext $adminContext, AdminUrlGenerator $adminUrlGenerator): Response { /** @var User $user */ @@ -108,7 +107,7 @@ public function generateToken(AdminContext $adminContext, AdminUrlGenerator $adm $this->container->get('doctrine')->getManagerForClass($adminContext->getEntity()->getFqcn()), $user ); - $this->addFlash('success', 'New token generated ' . $token . ' (keep it in secured area. This token will never be displayed anymore)'); + $this->addFlash('success', 'New token generated '.$token.' (keep it in secured area. This token will never be displayed anymore)'); return $this->redirect( $adminUrlGenerator diff --git a/src/Controller/ProcessExecuteController.php b/src/Controller/ProcessExecuteController.php index dd745cd..a20eb17 100644 --- a/src/Controller/ProcessExecuteController.php +++ b/src/Controller/ProcessExecuteController.php @@ -21,8 +21,7 @@ public function __invoke( #[ValueResolver('http_process_execution')] HttpProcessExecution $httpProcessExecution, ValidatorInterface $validator, MessageBusInterface $bus - ): JsonResponse - { + ): JsonResponse { $violations = $validator->validate($httpProcessExecution); if ($violations->count() > 0) { $violationsMessages = []; diff --git a/src/Entity/ProcessExecution.php b/src/Entity/ProcessExecution.php index b75083f..aff3b13 100644 --- a/src/Entity/ProcessExecution.php +++ b/src/Entity/ProcessExecution.php @@ -74,7 +74,7 @@ public function addReport(string $key, mixed $value): void $this->report[$key] = $value; } - public function getReport(string $key = null, mixed $default = null): mixed + public function getReport(?string $key = null, mixed $default = null): mixed { if (null === $key) { return $this->report; @@ -89,6 +89,7 @@ public function duration(string $format = '%H hour(s) %I min(s) %S s'): ?string return null; } $diff = $this->endDate->diff($this->startDate); + return $diff->format($format); } diff --git a/src/Entity/ProcessSchedule.php b/src/Entity/ProcessSchedule.php index 8a26e88..1bdb677 100644 --- a/src/Entity/ProcessSchedule.php +++ b/src/Entity/ProcessSchedule.php @@ -13,8 +13,8 @@ enum ProcessScheduleType: string { - case CRON = "cron"; - case EVERY = "every"; + case CRON = 'cron'; + case EVERY = 'every'; } #[ORM\Entity(repositoryClass: ProcessScheduleRepository::class)] diff --git a/src/Entity/User.php b/src/Entity/User.php index 37f2413..a65df93 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -122,7 +122,6 @@ public function setLocale(?string $locale): self return $this; } - /** * @see UserInterface */ @@ -168,7 +167,6 @@ public function setToken(?string $token): self return $this; } - /** * Returning a salt is only needed, if you are not using a modern * hashing algorithm (e.g. bcrypt or sodium) in your security.yaml. diff --git a/src/Form/Type/LaunchType.php b/src/Form/Type/LaunchType.php index 0a8e9bb..60027d1 100644 --- a/src/Form/Type/LaunchType.php +++ b/src/Form/Type/LaunchType.php @@ -20,10 +20,9 @@ class LaunchType extends AbstractType public function __construct( private readonly ProcessConfigurationRegistry $registry, private readonly ProcessConfigurationsManager $configurationsManager, - ) - { - + ) { } + public function buildForm(FormBuilderInterface $builder, array $options): void { $code = $options['process_code']; @@ -31,7 +30,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $uiOptions = $this->configurationsManager->getUiOptions($code); $builder->add( 'input', - "file" === $uiOptions['entrypoint_type'] ? FileType::class : TextType::class, + 'file' === $uiOptions['entrypoint_type'] ? FileType::class : TextType::class, [ 'required' => !(null === $configuration->getEntryPoint()), ] @@ -39,12 +38,12 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $builder->add( 'context', CollectionType::class, - [ - 'entry_type' => ProcessContextType::class, - 'allow_add' => true, - 'allow_delete' => true, - 'required' => false, - ] + [ + 'entry_type' => ProcessContextType::class, + 'allow_add' => true, + 'allow_delete' => true, + 'required' => false, + ] ); $builder->get('context')->addModelTransformer(new CallbackTransformer( function ($data) { @@ -54,7 +53,6 @@ function ($data) { return array_column($data ?? [], 'value', 'key'); }, )); - } public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Form/Type/ProcessContextType.php b/src/Form/Type/ProcessContextType.php index 204aeb8..daa48d1 100644 --- a/src/Form/Type/ProcessContextType.php +++ b/src/Form/Type/ProcessContextType.php @@ -19,7 +19,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void [ 'label' => 'Context Key', 'attr' => ['placeholder' => 'key'], - 'constraints' => [new NotBlank()] + 'constraints' => [new NotBlank()], ] )->add( 'value', @@ -27,12 +27,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void [ 'label' => 'Context Value', 'attr' => ['placeholder' => 'value'], - 'constraints' => [new NotBlank()] + 'constraints' => [new NotBlank()], ] ); } - public function configureOptions(OptionsResolver $resolver): void { } diff --git a/src/Http/Model/HttpProcessExecution.php b/src/Http/Model/HttpProcessExecution.php index 7e770dc..7f66563 100644 --- a/src/Http/Model/HttpProcessExecution.php +++ b/src/Http/Model/HttpProcessExecution.php @@ -1,5 +1,7 @@ get('input', $request->files->get('input')); if ($input instanceof UploadedFile) { - $uploadFileName = $this->storageDir . DIRECTORY_SEPARATOR . date('YmdHis') . '_' . uniqid() . '_' . $input->getClientOriginalName(); + $uploadFileName = $this->storageDir.DIRECTORY_SEPARATOR.date('YmdHis').'_'.uniqid().'_'.$input->getClientOriginalName(); (new Filesystem())->dumpFile($uploadFileName, $input->getContent()); $input = $uploadFileName; - } return [new HttpProcessExecution($request->get('code'), $input, $request->get('context', []))]; } -} \ No newline at end of file +} diff --git a/src/Manager/ProcessConfigurationsManager.php b/src/Manager/ProcessConfigurationsManager.php index 68c3e74..489f188 100644 --- a/src/Manager/ProcessConfigurationsManager.php +++ b/src/Manager/ProcessConfigurationsManager.php @@ -1,4 +1,5 @@ getConfigurations(), fn(ProcessConfiguration $cfg) => $cfg->isPublic()); + return array_filter($this->getConfigurations(), fn (ProcessConfiguration $cfg) => $cfg->isPublic()); } /** @return ProcessConfiguration[] */ public function getPrivateProcesses(): array { - return array_filter($this->getConfigurations(), fn(ProcessConfiguration $cfg) => !$cfg->isPublic()); + return array_filter($this->getConfigurations(), fn (ProcessConfiguration $cfg) => !$cfg->isPublic()); } public function getUiOptions(string $processCode): ?array @@ -49,13 +50,13 @@ private function resolveUiOptions(array $options): array 'entrypoint_type' => 'text', 'constraints' => [], 'run' => null, - 'default' => function(OptionsResolver $defaultResolver) { + 'default' => function (OptionsResolver $defaultResolver) { $defaultResolver->setDefault('input', null); - $defaultResolver->setDefault('context', function(OptionsResolver $contextResolver) { + $defaultResolver->setDefault('context', function (OptionsResolver $contextResolver) { $contextResolver->setPrototype(true); $contextResolver->setRequired(['key', 'value']); }); - } + }, ] ); $uiResolver->setDeprecated( diff --git a/src/Message/CronProcessMessage.php b/src/Message/CronProcessMessage.php index 6d8bc86..2a86db1 100644 --- a/src/Message/CronProcessMessage.php +++ b/src/Message/CronProcessMessage.php @@ -6,7 +6,7 @@ use CleverAge\ProcessUiBundle\Entity\ProcessSchedule; -readonly final class CronProcessMessage +final readonly class CronProcessMessage { public function __construct(public ProcessSchedule $processSchedule) { diff --git a/src/Message/CronProcessMessageHandler.php b/src/Message/CronProcessMessageHandler.php index d4ab4f9..8f114cf 100644 --- a/src/Message/CronProcessMessageHandler.php +++ b/src/Message/CronProcessMessageHandler.php @@ -8,12 +8,12 @@ use Symfony\Component\Messenger\MessageBusInterface; #[AsMessageHandler] -readonly final class CronProcessMessageHandler +final readonly class CronProcessMessageHandler { public function __construct(private MessageBusInterface $bus) { - } + public function __invoke(CronProcessMessage $message): void { $schedule = $message->processSchedule; diff --git a/src/Migrations/Version20231006111525.php b/src/Migrations/Version20231006111525.php index 3fe032f..9cfabc3 100644 --- a/src/Migrations/Version20231006111525.php +++ b/src/Migrations/Version20231006111525.php @@ -17,7 +17,7 @@ public function getDescription(): string public function up(Schema $schema): void { $platform = $this->connection->getDatabasePlatform()->getName(); - if ("sqlite" === $platform) { + if ('sqlite' === $platform) { if (!$schema->hasTable('log_record')) { $this->addSql('CREATE TABLE log_record (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, process_execution_id INTEGER DEFAULT NULL, channel VARCHAR(64) NOT NULL, level INTEGER NOT NULL, message VARCHAR(512) NOT NULL, context CLOB NOT NULL --(DC2Type:json) , created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) @@ -42,7 +42,7 @@ public function up(Schema $schema): void } } - if ("mysql" === $platform) { + if ('mysql' === $platform) { if (!$schema->hasTable('log_record')) { $this->addSql('CREATE TABLE log_record (id INT AUTO_INCREMENT NOT NULL, process_execution_id INT DEFAULT NULL, channel VARCHAR(64) NOT NULL, level INT NOT NULL, message VARCHAR(512) NOT NULL, context JSON NOT NULL COMMENT \'(DC2Type:json)\', created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_8ECECC333DAC0075 (process_execution_id), INDEX idx_log_record_level (level), INDEX idx_log_record_created_at (created_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); } @@ -55,7 +55,7 @@ public function up(Schema $schema): void } } - if ("postgresql" === $platform) { + if ('postgresql' === $platform) { if (!$schema->hasTable('log_record')) { $this->addSql('CREATE SEQUENCE log_record_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE TABLE log_record (id INT NOT NULL, process_execution_id INT DEFAULT NULL, channel VARCHAR(64) NOT NULL, level INT NOT NULL, message VARCHAR(512) NOT NULL, context JSON NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); @@ -79,8 +79,6 @@ public function up(Schema $schema): void $this->addSql('CREATE UNIQUE INDEX UNIQ_627A047CE7927C74 ON process_user (email)'); $this->addSql('CREATE INDEX idx_process_user_email ON process_user (email)'); } - - } } diff --git a/src/Migrations/Version20240729151928.php b/src/Migrations/Version20240729151928.php index b3c9adf..1ddabe2 100644 --- a/src/Migrations/Version20240729151928.php +++ b/src/Migrations/Version20240729151928.php @@ -42,4 +42,3 @@ public function down(Schema $schema): void $this->addSql('DROP TABLE process_schedule'); } } - diff --git a/src/Migrations/Version20240730090403.php b/src/Migrations/Version20240730090403.php index 205f650..be4aeee 100644 --- a/src/Migrations/Version20240730090403.php +++ b/src/Migrations/Version20240730090403.php @@ -4,10 +4,6 @@ namespace CleverAge\ProcessUiBundle\Migrations; -use Doctrine\DBAL\Platforms\MariaDBPlatform; -use Doctrine\DBAL\Platforms\MySQLPlatform; -use Doctrine\DBAL\Platforms\PostgreSQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; diff --git a/src/Migrations/Version20241007152613.php b/src/Migrations/Version20241007152613.php index c5e1ddf..5785598 100644 --- a/src/Migrations/Version20241007152613.php +++ b/src/Migrations/Version20241007152613.php @@ -28,7 +28,6 @@ public function down(Schema $schema): void { if ($schema->hasTable('process_execution') && $schema->getTable('process_execution')->hasColumn('context')) { $this->addSql('ALTER TABLE process_execution DROP context'); - } } } diff --git a/src/Scheduler/CronScheduler.php b/src/Scheduler/CronScheduler.php index b9e647d..e200dce 100644 --- a/src/Scheduler/CronScheduler.php +++ b/src/Scheduler/CronScheduler.php @@ -1,5 +1,7 @@ $exception->getMessage() + 'message' => $exception->getMessage(), ]; return new JsonResponse($data, Response::HTTP_UNAUTHORIZED); diff --git a/src/Twig/Runtime/LogLevelExtensionRuntime.php b/src/Twig/Runtime/LogLevelExtensionRuntime.php index bded8d2..aef2855 100644 --- a/src/Twig/Runtime/LogLevelExtensionRuntime.php +++ b/src/Twig/Runtime/LogLevelExtensionRuntime.php @@ -23,11 +23,11 @@ public function getCssClass(string|int $value): string Level::Debug->value, Level::Info->value => 'success', default => '' } - : match ($value) { - Level::Warning->name => 'warning', - Level::Error->name, Level::Emergency->name, Level::Critical->name, Level::Alert->name => 'danger', - Level::Debug->name, Level::Info->name => 'success', - default => '' - }; + : match ($value) { + Level::Warning->name => 'warning', + Level::Error->name, Level::Emergency->name, Level::Critical->name, Level::Alert->name => 'danger', + Level::Debug->name, Level::Info->name => 'success', + default => '' + }; } } diff --git a/src/Twig/Runtime/ProcessExecutionExtensionRuntime.php b/src/Twig/Runtime/ProcessExecutionExtensionRuntime.php index 1ad0a7b..0f90ef5 100644 --- a/src/Twig/Runtime/ProcessExecutionExtensionRuntime.php +++ b/src/Twig/Runtime/ProcessExecutionExtensionRuntime.php @@ -14,8 +14,7 @@ public function __construct( private ProcessExecutionRepository $processExecutionRepository, private ProcessConfigurationsManager $processConfigurationsManager, - ) - { + ) { } public function getLastExecutionDate(string $code): ?ProcessExecution From 2080fe51d6777f58e99cb7a195541e313a76d7e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 9 Oct 2024 10:53:28 +0200 Subject: [PATCH 31/39] Fix locale set on logout user --- src/Controller/Admin/ProcessDashboardController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/Admin/ProcessDashboardController.php b/src/Controller/Admin/ProcessDashboardController.php index 41bfcd1..d2fe033 100644 --- a/src/Controller/Admin/ProcessDashboardController.php +++ b/src/Controller/Admin/ProcessDashboardController.php @@ -65,7 +65,7 @@ public function configureCrud(): Crud { /** @var User $user */ $user = $this->getUser(); - if (null !== $user->getLocale()) { + if (null !== $user?->getLocale()) { $this->localeSwitcher->setLocale($user->getLocale()); } From c567e7b0041cc5248993b6c2f650ecae82f5b6b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 9 Oct 2024 14:28:49 +0200 Subject: [PATCH 32/39] Use default monolog config for process and doctrine handlers --- src/CleverAgeProcessUiBundle.php | 8 --- .../CleverAgeProcessUiExtension.php | 61 ++++++++++++++++++- .../Compiler/RegisterLogHandler.php | 34 ----------- .../Handler/DoctrineProcessHandler.php | 9 --- 4 files changed, 58 insertions(+), 54 deletions(-) delete mode 100644 src/DependencyInjection/Compiler/RegisterLogHandler.php diff --git a/src/CleverAgeProcessUiBundle.php b/src/CleverAgeProcessUiBundle.php index 90d6971..c3dcad2 100644 --- a/src/CleverAgeProcessUiBundle.php +++ b/src/CleverAgeProcessUiBundle.php @@ -4,18 +4,10 @@ namespace CleverAge\ProcessUiBundle; -use CleverAge\ProcessUiBundle\DependencyInjection\Compiler\RegisterLogHandler; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; class CleverAgeProcessUiBundle extends Bundle { - public function build(ContainerBuilder $container): void - { - parent::build($container); - $container->addCompilerPass(new RegisterLogHandler()); - } - public function getPath(): string { return \dirname(__DIR__); diff --git a/src/DependencyInjection/CleverAgeProcessUiExtension.php b/src/DependencyInjection/CleverAgeProcessUiExtension.php index addd734..16ecfa6 100644 --- a/src/DependencyInjection/CleverAgeProcessUiExtension.php +++ b/src/DependencyInjection/CleverAgeProcessUiExtension.php @@ -10,6 +10,7 @@ use CleverAge\ProcessUiBundle\Message\ProcessExecuteMessage; use CleverAge\ProcessUiBundle\Monolog\Handler\DoctrineProcessHandler; use CleverAge\ProcessUiBundle\Monolog\Handler\ProcessHandler; +use Monolog\Level; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; @@ -27,9 +28,6 @@ public function load(array $configs, ContainerBuilder $container): void $config = $this->processConfiguration($configuration, $configs); $container->getDefinition(UserCrudController::class) ->setArgument('$roles', array_combine($config['security']['roles'], $config['security']['roles'])); - $container->getDefinition(DoctrineProcessHandler::class) - ->addMethodCall('setEnabled', [$config['logs']['store_in_database']]) - ->addMethodCall('setLevel', [$config['logs']['database_level']]); $container->getDefinition(ProcessHandler::class) ->addMethodCall('setReportIncrementLevel', [$config['logs']['report_increment_level']]); $container->getDefinition(ProcessDashboardController::class) @@ -41,6 +39,63 @@ public function load(array $configs, ContainerBuilder $container): void */ public function prepend(ContainerBuilder $container): void { + $env = $container->getParameter("kernel.environment"); + $container->loadFromExtension( + 'monolog', + [ + 'handlers' => [ + 'pb_ui_file' => [ + 'type' => 'service', + 'id' => ProcessHandler::class + ], + 'pb_ui_orm' => [ + 'type' => 'service', + 'id' => DoctrineProcessHandler::class + ] + ] + ] + ); + if ("dev" === $env) { + $container->loadFromExtension( + 'monolog', + [ + 'handlers' => [ + 'pb_ui_file_filter' => [ + 'type' => 'filter', + 'min_level' => Level::Debug->name, + 'handler' => 'pb_ui_file', + 'channels' => ['cleverage_process', 'cleverage_process_task'] + ], + 'pb_ui_orm_filter' => [ + 'type' => 'filter', + 'min_level' => Level::Debug->name, + 'handler' => 'pb_ui_orm', + 'channels' => ["cleverage_process", "cleverage_process_task"] + ], + ] + ] + ); + } else { + $container->loadFromExtension( + 'monolog', + [ + 'handlers' => [ + 'pb_ui_file_filter' => [ + 'type' => 'filter', + 'min_level' => Level::Info->name, + 'handler' => 'pb_ui_file', + 'channels' => ['cleverage_process', 'cleverage_process_task'] + ], + 'pb_ui_orm_filter' => [ + 'type' => 'filter', + 'min_level' => Level::Info->name, + 'handler' => 'pb_ui_orm', + 'channels' => ["cleverage_process", "cleverage_process_task"] + ], + ] + ] + ); + } $container->loadFromExtension( 'doctrine_migrations', [ diff --git a/src/DependencyInjection/Compiler/RegisterLogHandler.php b/src/DependencyInjection/Compiler/RegisterLogHandler.php deleted file mode 100644 index a4c258d..0000000 --- a/src/DependencyInjection/Compiler/RegisterLogHandler.php +++ /dev/null @@ -1,34 +0,0 @@ -hasDefinition('monolog.logger')) { - return; - } - $loggers = [ - 'monolog.logger.cleverage_process', - 'monolog.logger.cleverage_process_task', - 'monolog.logger.cleverage_process_transformer', - ]; - foreach ($loggers as $logger) { - if ($container->has($logger)) { - $container - ->getDefinition($logger) - ->addMethodCall('pushHandler', [new Reference(ProcessHandler::class)]) - ->addMethodCall('pushHandler', [new Reference(DoctrineProcessHandler::class)]); - } - } - } -} diff --git a/src/Monolog/Handler/DoctrineProcessHandler.php b/src/Monolog/Handler/DoctrineProcessHandler.php index fee6318..d2159c1 100644 --- a/src/Monolog/Handler/DoctrineProcessHandler.php +++ b/src/Monolog/Handler/DoctrineProcessHandler.php @@ -18,7 +18,6 @@ class DoctrineProcessHandler extends AbstractProcessingHandler private ArrayCollection $records; private ?ProcessExecutionManager $processExecutionManager; private ?EntityManagerInterface $em = null; - private bool $enabled = false; public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true) { @@ -38,11 +37,6 @@ public function setProcessExecutionManager(ProcessExecutionManager $processExecu $this->processExecutionManager = $processExecutionManager; } - public function setEnabled(bool $flag): void - { - $this->enabled = $flag; - } - public function __destruct() { $this->flush(); @@ -66,9 +60,6 @@ public function flush(): void protected function write(LogRecord $record): void { - if (false === $this->enabled) { - return; - } $this->records->add($record); if (500 === $this->records->count()) { $this->flush(); From 38d80eb104f0a99ed4e51eae2b2c8fdafd32eb49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 16 Oct 2024 16:01:26 +0200 Subject: [PATCH 33/39] Fix on scheduler to set context --- src/Message/CronProcessMessageHandler.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Message/CronProcessMessageHandler.php b/src/Message/CronProcessMessageHandler.php index 8f114cf..868803e 100644 --- a/src/Message/CronProcessMessageHandler.php +++ b/src/Message/CronProcessMessageHandler.php @@ -17,8 +17,11 @@ public function __construct(private MessageBusInterface $bus) public function __invoke(CronProcessMessage $message): void { $schedule = $message->processSchedule; + $context = array_merge(...array_map(function($ctx) { + return [$ctx['key'] => $ctx['value']]; + }, $schedule->getContext())); $this->bus->dispatch( - new ProcessExecuteMessage($schedule->getProcess(), $schedule->getInput(), $message->processSchedule->getContext()) + new ProcessExecuteMessage($schedule->getProcess(), $schedule->getInput(), $context) ); } } From d9b127cc612a7dbdbbf3d975ce8029f6a27030a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 16 Oct 2024 16:01:52 +0200 Subject: [PATCH 34/39] Stop workers on scheduler updates --- src/EventListener/SchedulerStopWorkers.php | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/EventListener/SchedulerStopWorkers.php diff --git a/src/EventListener/SchedulerStopWorkers.php b/src/EventListener/SchedulerStopWorkers.php new file mode 100644 index 0000000..e41fda7 --- /dev/null +++ b/src/EventListener/SchedulerStopWorkers.php @@ -0,0 +1,33 @@ +kernel); + $application->setAutoExit(false); + $input = new ArrayInput(['command' => 'messenger:stop-workers']); + $output = new NullOutput(); + $application->run($input, $output); + } +} From 1ecdfa8cb742f79885701f0fd4d7c60a45d9883c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 23 Oct 2024 06:18:08 +0200 Subject: [PATCH 35/39] FIX - Process filter on log page --- src/Admin/Filter/LogProcessCodeFilter.php | 63 ------------------- src/Admin/Filter/LogProcessFilter.php | 52 +++++++++++++++ .../Admin/LogRecordCrudController.php | 11 ++-- .../Admin/ProcessExecutionCrudController.php | 2 +- src/EventListener/SchedulerStopWorkers.php | 33 ---------- 5 files changed, 59 insertions(+), 102 deletions(-) delete mode 100644 src/Admin/Filter/LogProcessCodeFilter.php create mode 100644 src/Admin/Filter/LogProcessFilter.php delete mode 100644 src/EventListener/SchedulerStopWorkers.php diff --git a/src/Admin/Filter/LogProcessCodeFilter.php b/src/Admin/Filter/LogProcessCodeFilter.php deleted file mode 100644 index e4af123..0000000 --- a/src/Admin/Filter/LogProcessCodeFilter.php +++ /dev/null @@ -1,63 +0,0 @@ -setFilterFqcn(__CLASS__) - ->setProperty('processExecution') - ->setLabel($label) - ->setFormType(ChoiceType::class); - } - - public function addChoices(ProcessConfigurationsManager $manager): self - { - $choices = $manager->getPublicProcesses(); - $choices = array_map(fn (ProcessConfiguration $cfg) => $cfg->getCode(), $choices); - $this->setFormTypeOption('choices', $choices); - - return $this; - } - - public function setCurrentProcessExecutionId(?int $currentProcessExecutionId): self - { - $this->currentProcessExecutionId = $currentProcessExecutionId; - if (0 !== $this->currentProcessExecutionId) { - $this->setFormTypeOption('disabled', true); - } - - return $this; - } - - public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, ?FieldDto $fieldDto, EntityDto $entityDto): void - { - $queryBuilder->join('entity.processExecution', 'pe'); - if (null !== $this->currentProcessExecutionId) { - $queryBuilder->andWhere($queryBuilder->expr()->eq('pe.id', ':id')); - $queryBuilder->setParameter('id', $this->currentProcessExecutionId); - - return; - } - $queryBuilder->where('pe.code IN (:codes)'); - $queryBuilder->setParameter('codes', $filterDataDto->getValue()); - } -} diff --git a/src/Admin/Filter/LogProcessFilter.php b/src/Admin/Filter/LogProcessFilter.php new file mode 100644 index 0000000..bbb3099 --- /dev/null +++ b/src/Admin/Filter/LogProcessFilter.php @@ -0,0 +1,52 @@ + $executionId]; + } + + return (new self()) + ->setFilterFqcn(__CLASS__) + ->setProperty('process') + ->setLabel($label) + ->setFormType(ChoiceFilterType::class) + ->setFormTypeOption('value_type_options', ['choices' => $choices]) + ->setFormTypeOption('data', ['comparison' => ComparisonType::EQ, 'value' => $executionId]); + } + + public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, ?FieldDto $fieldDto, EntityDto $entityDto): void + { + $value = $filterDataDto->getValue(); + $queryBuilder->join('entity.processExecution', 'pe'); + if (is_numeric($value)) { + $queryBuilder->andWhere($queryBuilder->expr()->eq('pe.id', ':id')); + $queryBuilder->setParameter('id', $value); + + return; + } + $queryBuilder->where('pe.code IN (:codes)'); + $queryBuilder->setParameter('codes', $filterDataDto->getValue()); + } +} diff --git a/src/Controller/Admin/LogRecordCrudController.php b/src/Controller/Admin/LogRecordCrudController.php index f840c62..adf3b6f 100644 --- a/src/Controller/Admin/LogRecordCrudController.php +++ b/src/Controller/Admin/LogRecordCrudController.php @@ -4,8 +4,9 @@ namespace CleverAge\ProcessUiBundle\Controller\Admin; +use CleverAge\ProcessBundle\Configuration\ProcessConfiguration; use CleverAge\ProcessUiBundle\Admin\Field\LogLevelField; -use CleverAge\ProcessUiBundle\Admin\Filter\LogProcessCodeFilter; +use CleverAge\ProcessUiBundle\Admin\Filter\LogProcessFilter; use CleverAge\ProcessUiBundle\Entity\LogRecord; use CleverAge\ProcessUiBundle\Manager\ProcessConfigurationsManager; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; @@ -73,12 +74,12 @@ public function configureActions(Actions $actions): Actions public function configureFilters(Filters $filters): Filters { - $id = $this->request->getMainRequest()->query->all('filters')['processExecution']['value'] ?? null; + $id = $this->request->getMainRequest()->query->all('filters')['process']['value'] ?? null; + $processList = $this->processConfigurationsManager->getPublicProcesses(); + $processList = array_map(fn (ProcessConfiguration $cfg) => $cfg->getCode(), $processList); return $filters->add( - LogProcessCodeFilter::new('process') - ->addChoices($this->processConfigurationsManager) - ->setCurrentProcessExecutionId((int) $id) + LogProcessFilter::new('process', $processList, $id) )->add( ChoiceFilter::new('level')->setChoices(array_combine(Level::NAMES, Level::VALUES)) )->add('message')->add('context')->add('createdAt'); diff --git a/src/Controller/Admin/ProcessExecutionCrudController.php b/src/Controller/Admin/ProcessExecutionCrudController.php index fad622f..c7eae3f 100644 --- a/src/Controller/Admin/ProcessExecutionCrudController.php +++ b/src/Controller/Admin/ProcessExecutionCrudController.php @@ -94,7 +94,7 @@ public function showLogs(AdminContext $adminContext): RedirectResponse ->set( 'filters', [ - 'processExecution' => [ + 'process' => [ 'comparison' => '=', 'value' => $this->getContext()->getEntity()->getInstance()->getId(), ], diff --git a/src/EventListener/SchedulerStopWorkers.php b/src/EventListener/SchedulerStopWorkers.php deleted file mode 100644 index e41fda7..0000000 --- a/src/EventListener/SchedulerStopWorkers.php +++ /dev/null @@ -1,33 +0,0 @@ -kernel); - $application->setAutoExit(false); - $input = new ArrayInput(['command' => 'messenger:stop-workers']); - $output = new NullOutput(); - $application->run($input, $output); - } -} From b0d8999eea2a04a317a69911ed44fb7c2b0e69c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tonon=20Gr=C3=A9gory?= Date: Wed, 23 Oct 2024 08:46:36 +0200 Subject: [PATCH 36/39] Fix roles error --- src/DependencyInjection/Configuration.php | 2 +- src/Entity/User.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 57473bb..64ecaad 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -21,7 +21,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->arrayNode('security') ->addDefaultsIfNotSet() ->children() - ->scalarNode('roles')->defaultValue(['ROLE_ADMIN'])->end(); // Roles displayed inside user edit form + ->arrayNode('roles')->defaultValue(['ROLE_ADMIN'])->scalarPrototype()->end(); // Roles displayed inside user edit form $rootNode ->children() ->arrayNode('logs') diff --git a/src/Entity/User.php b/src/Entity/User.php index a65df93..6deb611 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -127,7 +127,7 @@ public function setLocale(?string $locale): self */ public function getRoles(): array { - return array_unique(['ROLE_USER'] + $this->roles); + return array_merge(['ROLE_USER'], $this->roles); } /** From a3d25875ef9ab04f02c75abc0d84be26fda9c112 Mon Sep 17 00:00:00 2001 From: Xavier Marchegay Date: Thu, 24 Oct 2024 09:25:37 +0200 Subject: [PATCH 37/39] Increase the length of the message of the LogRecord --- src/Controller/Admin/LogRecordCrudController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/Admin/LogRecordCrudController.php b/src/Controller/Admin/LogRecordCrudController.php index adf3b6f..4170313 100644 --- a/src/Controller/Admin/LogRecordCrudController.php +++ b/src/Controller/Admin/LogRecordCrudController.php @@ -41,7 +41,7 @@ public function configureFields(string $pageName): iterable { return [ LogLevelField::new('level'), - TextField::new('message'), + TextField::new('message')->setMaxLength(512), DateTimeField::new('createdAt')->setFormat('Y/M/dd H:mm:ss'), ArrayField::new('context') ->setTemplatePath('@CleverAgeProcessUi/admin/field/array.html.twig') From ec7a5943261c3532e650dc168f7dd1de89190d81 Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Tue, 19 Nov 2024 10:35:21 +0100 Subject: [PATCH 38/39] Remove deprecated github actions --- .github/workflows/php.yml | 32 ------------------------------ .github/workflows/super-linter.yml | 19 ------------------ 2 files changed, 51 deletions(-) delete mode 100644 .github/workflows/php.yml delete mode 100644 .github/workflows/super-linter.yml diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml deleted file mode 100644 index 8231bcc..0000000 --- a/.github/workflows/php.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: PHP Composer - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Validate composer.json and composer.lock - run: composer validate --strict - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v3 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-scripts - - # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" - # Docs: https://getcomposer.org/doc/articles/scripts.md - - # - name: Run test suite - # run: composer run-script test diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml deleted file mode 100644 index c8b12a9..0000000 --- a/.github/workflows/super-linter.yml +++ /dev/null @@ -1,19 +0,0 @@ -# It checks the syntax of the PHP code (using PHP-CS-Fixer) -name: "Linter: Code Syntax" - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] -jobs: - php-cs-fixer: - name: PHP-CS-Fixer - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - # see https://github.com/OskarStark/php-cs-fixer-ga - - name: PHP-CS-Fixer - uses: docker://oskarstark/php-cs-fixer-ga - with: - args: --diff --dry-run From 6aaf59a304dd5a0c2b50d493e652e86e5b7605ec Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Tue, 19 Nov 2024 11:23:56 +0100 Subject: [PATCH 39/39] Fix composer.json & remove sqlite support from DoctrineMigrations --- README.md | 7 +- composer.json | 93 ++++++++++--------- src/Migrations/Version20231006111525.php | 35 ++----- src/Migrations/Version20240729151928.php | 6 +- src/Migrations/Version20240730090403.php | 2 +- src/Migrations/Version20241007134542.php | 2 +- src/Migrations/Version20241007152613.php | 2 +- src/Migrations/Version20241009075733.php | 2 +- .../Handler/DoctrineProcessHandler.php | 2 +- 9 files changed, 67 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index e694372..4e4fa1f 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,8 @@ A simple UX for cleverage/processbundle using EasyAdmin **Installation** * Import routes ```yaml -#config/routes.yaml -processui: - resource: '@CleverAgeProcessUiBundle/src/Controller' type: attribute +processui: + resource: '@CleverAgeProcessUiBundle/src/Controller' type: attribute ``` * Run doctrine migration * Create an user using cleverage:process-ui:user-create console. @@ -99,4 +98,4 @@ stdout_logfile=/var/log/supervisor.process-out.log user=www-data killasgroup=true stopasgroup=true -``` \ No newline at end of file +``` diff --git a/composer.json b/composer.json index ec1fd81..a5d2894 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,18 @@ { - "name": "cleverage/process-bundle-ui", + "name": "cleverage/ui-process-bundle", + "description": "UI for cleverage/process-bundle", + "keywords": [ + "process", + "task", + "etl", + "transformation", + "import", + "export", + "ui" + ], + "homepage": "https://github.com/cleverage/ui-process-bundle", "type": "symfony-bundle", "license": "MIT", - "description": "UI for cleverage/process-bundle", "authors": [ { "name": "Grégory Tonon", @@ -15,58 +25,57 @@ "role": "Developer" } ], + "autoload": { + "psr-4": { + "CleverAge\\ProcessUiBundle\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "CleverAge\\ProcessUiBundle\\Tests\\": "tests/" + } + }, "require": { "php": ">=8.2", "ext-ctype": "*", "ext-iconv": "*", "ext-pcntl": "*", - "cleverage/process-bundle": "dev-v4-dev", - "symfony/flex": "^2", - "symfony/orm-pack": "^v2.4", - "symfony/dotenv": "^6.4", - "symfony/uid": "^6.4", - "symfony/string": "^6.4", - "symfony/messenger": "^6.4", - "symfony/runtime": "^6.4", - "symfony/doctrine-messenger": "^6.4", + "cleverage/process-bundle": "dev-prepare-release", + "doctrine/common": "^3.0", + "doctrine/dbal": "^2.9 || ^3.0", + "doctrine/doctrine-bundle": "^2.5", + "doctrine/doctrine-migrations-bundle": "^3.2", + "doctrine/orm": "^2.9 || ^3.0", + "symfony/dotenv": "^6.4|^7.1", + "symfony/uid": "^6.4|^7.1", + "symfony/string": "^6.4|^7.1", + "symfony/messenger": "^6.4|^7.1", + "symfony/runtime": "^6.4|^7.1", + "symfony/doctrine-messenger": "^6.4|^7.1", "easycorp/easyadmin-bundle": "^4.8" }, "require-dev": { - "vincentlanglet/twig-cs-fixer": "1.4.0" + "doctrine/doctrine-fixtures-bundle": "^3.4", + "friendsofphp/php-cs-fixer": "*", + "phpstan/extension-installer": "*", + "phpstan/phpstan": "*", + "phpstan/phpstan-doctrine": "*", + "phpstan/phpstan-symfony": "*", + "phpunit/phpunit": "<10.0", + "rector/rector": "*", + "roave/security-advisories": "dev-latest", + "symfony/browser-kit": "^6.4|^7.1", + "symfony/css-selector": "^6.4|^7.1", + "symfony/debug-bundle": "^6.4|^7.1", + "symfony/maker-bundle": "^1.31", + "symfony/web-profiler-bundle": "^6.4|^7.1" }, "config": { - "optimize-autoloader": true, - "preferred-install": { - "*": "dist" - }, - "sort-packages": true, "allow-plugins": { - "composer/package-versions-deprecated": true, + "phpstan/extension-installer": true, "symfony/flex": true, - "symfony/runtime": true, - "phpstan/extension-installer": true - } - }, - "autoload": { - "psr-4": { - "CleverAge\\ProcessUiBundle\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "CleverAge\\ProcessUiBundle\\Tests\\": "tests/" - } - }, - "scripts": { - "auto-scripts": { - "cache:clear": "symfony-cmd", - "assets:install %PUBLIC_DIR%": "symfony-cmd" + "symfony/runtime": true }, - "post-install-cmd": [ - "@auto-scripts" - ], - "post-update-cmd": [ - "@auto-scripts" - ] + "sort-packages": true } } diff --git a/src/Migrations/Version20231006111525.php b/src/Migrations/Version20231006111525.php index 9cfabc3..583f88f 100644 --- a/src/Migrations/Version20231006111525.php +++ b/src/Migrations/Version20231006111525.php @@ -4,6 +4,9 @@ namespace CleverAge\ProcessUiBundle\Migrations; +use Doctrine\DBAL\Platforms\MariaDBPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; @@ -11,38 +14,14 @@ final class Version20231006111525 extends AbstractMigration { public function getDescription(): string { - return ''; + return 'Create tables log_record, process_execution and process_user'; } public function up(Schema $schema): void { - $platform = $this->connection->getDatabasePlatform()->getName(); - if ('sqlite' === $platform) { - if (!$schema->hasTable('log_record')) { - $this->addSql('CREATE TABLE log_record (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, process_execution_id INTEGER DEFAULT NULL, channel VARCHAR(64) NOT NULL, level INTEGER NOT NULL, message VARCHAR(512) NOT NULL, context CLOB NOT NULL --(DC2Type:json) - , created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) - , CONSTRAINT FK_8ECECC333DAC0075 FOREIGN KEY (process_execution_id) REFERENCES process_execution (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('CREATE INDEX IDX_8ECECC333DAC0075 ON log_record (process_execution_id)'); - $this->addSql('CREATE INDEX idx_log_record_level ON log_record (level)'); - $this->addSql('CREATE INDEX idx_log_record_created_at ON log_record (created_at)'); - } - if (!$schema->hasTable('process_execution')) { - $this->addSql('CREATE TABLE process_execution (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, code VARCHAR(255) NOT NULL, log_filename VARCHAR(255) NOT NULL, start_date DATETIME NOT NULL --(DC2Type:datetime_immutable) - , end_date DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) - , status VARCHAR(255) NOT NULL, report CLOB NOT NULL --(DC2Type:json) - )'); - $this->addSql('CREATE INDEX idx_process_execution_code ON process_execution (code)'); - $this->addSql('CREATE INDEX idx_process_execution_start_date ON process_execution (start_date)'); - } - if (!$schema->hasTable('process_user')) { - $this->addSql('CREATE TABLE process_user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, firstname VARCHAR(255) DEFAULT NULL, lastname VARCHAR(255) DEFAULT NULL, roles CLOB NOT NULL --(DC2Type:json) - , password VARCHAR(255) DEFAULT NULL)'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_627A047CE7927C74 ON process_user (email)'); - $this->addSql('CREATE INDEX idx_process_user_email ON process_user (email)'); - } - } + $platform = $this->connection->getDatabasePlatform(); - if ('mysql' === $platform) { + if ($platform instanceof MariaDBPlatform or $platform instanceof MySQLPlatform) { if (!$schema->hasTable('log_record')) { $this->addSql('CREATE TABLE log_record (id INT AUTO_INCREMENT NOT NULL, process_execution_id INT DEFAULT NULL, channel VARCHAR(64) NOT NULL, level INT NOT NULL, message VARCHAR(512) NOT NULL, context JSON NOT NULL COMMENT \'(DC2Type:json)\', created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_8ECECC333DAC0075 (process_execution_id), INDEX idx_log_record_level (level), INDEX idx_log_record_created_at (created_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); } @@ -55,7 +34,7 @@ public function up(Schema $schema): void } } - if ('postgresql' === $platform) { + if ($platform instanceof PostgreSQLPlatform) { if (!$schema->hasTable('log_record')) { $this->addSql('CREATE SEQUENCE log_record_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE TABLE log_record (id INT NOT NULL, process_execution_id INT DEFAULT NULL, channel VARCHAR(64) NOT NULL, level INT NOT NULL, message VARCHAR(512) NOT NULL, context JSON NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); diff --git a/src/Migrations/Version20240729151928.php b/src/Migrations/Version20240729151928.php index 1ddabe2..8793abb 100644 --- a/src/Migrations/Version20240729151928.php +++ b/src/Migrations/Version20240729151928.php @@ -18,7 +18,7 @@ final class Version20240729151928 extends AbstractMigration { public function getDescription(): string { - return ''; + return 'Create table process_schedule'; } public function up(Schema $schema): void @@ -27,10 +27,6 @@ public function up(Schema $schema): void if ($platform instanceof PostgreSQLPlatform) { $this->addSql('CREATE TABLE process_schedule (id INT AUTO_INCREMENT NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input VARCHAR(255), context JSON NOT NULL, PRIMARY KEY(id))'); } - if ($platform instanceof SqlitePlatform) { - $this->addSql('CREATE TABLE process_schedule (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input CLOB DEFAULT NULL, context CLOB NOT NULL --(DC2Type:json) -);'); - } if ($platform instanceof MariaDBPlatform or $platform instanceof MySQLPlatform) { $this->addSql('CREATE TABLE process_schedule (id INT AUTO_INCREMENT NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input VARCHAR(255), context JSON NOT NULL COMMENT \'(DC2Type:json)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;'); diff --git a/src/Migrations/Version20240730090403.php b/src/Migrations/Version20240730090403.php index be4aeee..6524be0 100644 --- a/src/Migrations/Version20240730090403.php +++ b/src/Migrations/Version20240730090403.php @@ -14,7 +14,7 @@ final class Version20240730090403 extends AbstractMigration { public function getDescription(): string { - return ''; + return 'Add process_user.token'; } public function up(Schema $schema): void diff --git a/src/Migrations/Version20241007134542.php b/src/Migrations/Version20241007134542.php index 259c62f..226efe3 100644 --- a/src/Migrations/Version20241007134542.php +++ b/src/Migrations/Version20241007134542.php @@ -14,7 +14,7 @@ final class Version20241007134542 extends AbstractMigration { public function getDescription(): string { - return 'Add timezone field to user.'; + return 'Add process_user.timezone'; } public function up(Schema $schema): void diff --git a/src/Migrations/Version20241007152613.php b/src/Migrations/Version20241007152613.php index 5785598..cd03460 100644 --- a/src/Migrations/Version20241007152613.php +++ b/src/Migrations/Version20241007152613.php @@ -14,7 +14,7 @@ final class Version20241007152613 extends AbstractMigration { public function getDescription(): string { - return 'Add context to process execution'; + return 'Add process_execution.context'; } public function up(Schema $schema): void diff --git a/src/Migrations/Version20241009075733.php b/src/Migrations/Version20241009075733.php index 1281ae3..fe4396d 100644 --- a/src/Migrations/Version20241009075733.php +++ b/src/Migrations/Version20241009075733.php @@ -11,7 +11,7 @@ final class Version20241009075733 extends AbstractMigration { public function getDescription(): string { - return 'Add locale to user'; + return 'Add process_user.locale'; } public function up(Schema $schema): void diff --git a/src/Monolog/Handler/DoctrineProcessHandler.php b/src/Monolog/Handler/DoctrineProcessHandler.php index d2159c1..20b9c39 100644 --- a/src/Monolog/Handler/DoctrineProcessHandler.php +++ b/src/Monolog/Handler/DoctrineProcessHandler.php @@ -14,7 +14,7 @@ class DoctrineProcessHandler extends AbstractProcessingHandler { - /** @psalm-var ArrayCollection */ + /** @var ArrayCollection */ private ArrayCollection $records; private ?ProcessExecutionManager $processExecutionManager; private ?EntityManagerInterface $em = null;