From ea733fc996544ce9201013c130fbe35a54d81097 Mon Sep 17 00:00:00 2001 From: siva kumar Date: Tue, 29 Jan 2019 18:18:11 +0530 Subject: [PATCH] [ADF-3883] Improve edit-process-filter-cloud by adding inputs to control filters, sort and actions (#4127) * [ADF-3883] Improve edit-process-filter-cloud by adding inputs to control filters, sort and actions * Fixed translate keys * * Added more properties to the editProcessModel * Fix FIlterOption model * Fix import model name * * After rebase* Cherry pick * Updated doc * Revert commit * Revert changes * * Removed obervalu from model* Added edit process/task filter to the demo shell* Refacotred edit task/process filter * Updated test to the recent changes * * Fixed failing e2e tests * Added data-automation-id * * After rebase * * Modified ProcessFilterActionType model* Added condition to get the currect filter after save as* Changed column to sort in the en.json, removed unused keys* Improved onSave editProcessfilter method * imported missing groupModule in the process-service-cloud module * * Fixed e2e test --- demo-shell/src/app.config.json | 10 + .../cloud/cloud-filters-demo.component.ts | 2 +- .../cloud/cloud-layout.component.ts | 11 +- .../cloud/processes-cloud-demo.component.html | 2 + .../cloud/processes-cloud-demo.component.ts | 23 +- .../cloud/tasks-cloud-demo.component.html | 1 + .../cloud/tasks-cloud-demo.component.ts | 12 +- .../config-editor.component.html | 14 + .../config-editor/config-editor.component.ts | 12 + .../edit-process-filter-cloud.component.png | Bin 10722 -> 9299 bytes .../edit-task-filter-cloud.component.png | Bin 12605 -> 7440 bytes .../edit-process-filter-cloud.component.md | 54 +++- .../edit-task-filter-cloud.component.md | 8 +- e2e/pages/adf/dataTablePage.ts | 2 + .../editProcessFilterCloudComponent.ts | 2 +- .../process-custom-filters.e2e.ts | 2 + .../src/lib/i18n/en.json | 29 +- .../src/lib/process-services-cloud.module.ts | 7 +- .../edit-process-filter-cloud.component.html | 97 +++--- .../edit-process-filter-cloud.component.scss | 31 ++ ...dit-process-filter-cloud.component.spec.ts | 180 ++++++++--- .../edit-process-filter-cloud.component.ts | 298 +++++++++++++++--- .../models/process-filter-cloud.model.ts | 45 ++- .../process-filters-cloud.module.ts | 4 +- .../src/lib/styles/_index.scss | 2 + .../edit-task-filter-cloud.component.html | 12 +- .../edit-task-filter-cloud.component.spec.ts | 99 +++--- .../edit-task-filter-cloud.component.ts | 125 ++++---- .../task-filters/models/filter-cloud.model.ts | 8 +- 29 files changed, 802 insertions(+), 290 deletions(-) diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index 95d7bac7bb..dfe236ac00 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -539,6 +539,16 @@ ] } }, + "edit-task-filter": { + "properties": [ + "state", "assignment", "sort", "order" + ] + }, + "edit-process-filter": { + "properties": [ + "state", "sort", "order", "processName" + ] + }, "content-metadata": { "presets": { "default": { diff --git a/demo-shell/src/app/components/app-layout/cloud/cloud-filters-demo.component.ts b/demo-shell/src/app/components/app-layout/cloud/cloud-filters-demo.component.ts index 6e90276b12..a6a193d7fe 100644 --- a/demo-shell/src/app/components/app-layout/cloud/cloud-filters-demo.component.ts +++ b/demo-shell/src/app/components/app-layout/cloud/cloud-filters-demo.component.ts @@ -49,7 +49,7 @@ export class CloudFiltersDemoComponent implements OnInit { this.currentTaskFilter$ = this.cloudLayoutService.getCurrentTaskFilterParam(); this.currentProcessFilter$ = this.cloudLayoutService.getCurrentProcessFilterParam(); let root = ''; - if ( this.route.snapshot && this.route.snapshot.firstChild) { + if (this.route.snapshot && this.route.snapshot.firstChild) { root = this.route.snapshot.firstChild.url[0].path; if (root === 'tasks') { this.expandTaskFilter = true; diff --git a/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.ts b/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.ts index 459f94d605..06d652bc74 100644 --- a/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.ts +++ b/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.ts @@ -35,14 +35,23 @@ export class CloudLayoutComponent implements OnInit { ) {} ngOnInit() { + let root: string = ''; this.route.params.subscribe((params) => { this.applicationName = params.applicationName; }); + if (this.route.snapshot && this.route.snapshot.firstChild) { + root = this.route.snapshot.firstChild.url[0].path; + } + this.route.queryParams.subscribe((params) => { - if (params.id) { + if (root === 'tasks' && params.id) { this.cloudLayoutService.setCurrentTaskFilterParam({ id: params.id }); } + + if (root === 'processes' && params.id) { + this.cloudLayoutService.setCurrentProcessFilterParam({ id: params.id }); + } }); } diff --git a/demo-shell/src/app/components/app-layout/cloud/processes-cloud-demo.component.html b/demo-shell/src/app/components/app-layout/cloud/processes-cloud-demo.component.html index 316c3aa6eb..daf84b66ac 100644 --- a/demo-shell/src/app/components/app-layout/cloud/processes-cloud-demo.component.html +++ b/demo-shell/src/app/components/app-layout/cloud/processes-cloud-demo.component.html @@ -2,6 +2,7 @@ @@ -9,6 +10,7 @@ diff --git a/demo-shell/src/app/components/app-layout/cloud/processes-cloud-demo.component.ts b/demo-shell/src/app/components/app-layout/cloud/processes-cloud-demo.component.ts index 38cf5c15f8..d4eb0de756 100644 --- a/demo-shell/src/app/components/app-layout/cloud/processes-cloud-demo.component.ts +++ b/demo-shell/src/app/components/app-layout/cloud/processes-cloud-demo.component.ts @@ -23,8 +23,8 @@ import { ProcessFiltersCloudComponent } from '@alfresco/adf-process-services-cloud'; -import { ActivatedRoute } from '@angular/router'; -import { UserPreferencesService } from '@alfresco/adf-core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { UserPreferencesService, AppConfigService } from '@alfresco/adf-core'; import { CloudLayoutService } from './services/cloud-layout.service'; @Component({ @@ -33,6 +33,9 @@ import { CloudLayoutService } from './services/cloud-layout.service'; }) export class ProcessesCloudDemoComponent implements OnInit { + public static ACTION_SAVE_AS = 'SAVE_AS'; + static PROCESS_FILTER_PROPERTY_KEYS = 'edit-process-filter.properties'; + @ViewChild('processCloud') processCloud: ProcessListCloudComponent; @@ -45,13 +48,20 @@ export class ProcessesCloudDemoComponent implements OnInit { filterId: string = ''; sortArray: any = []; selectedRow: any; + processFilterProperties: any[] = []; editedFilter: ProcessFilterCloudModel; constructor( private route: ActivatedRoute, + private router: Router, private cloudLayoutService: CloudLayoutService, - private userPreference: UserPreferencesService) { + private userPreference: UserPreferencesService, + private appConfig: AppConfigService) { + const properties = this.appConfig.get>(ProcessesCloudDemoComponent.PROCESS_FILTER_PROPERTY_KEYS); + if (properties) { + this.processFilterProperties = properties; + } } ngOnInit() { @@ -80,7 +90,10 @@ export class ProcessesCloudDemoComponent implements OnInit { this.sortArray = [new ProcessListCloudSortingModel({ orderBy: this.editedFilter.sort, direction: this.editedFilter.order })]; } - onProcessFilterAction(filter: any) { - this.cloudLayoutService.setCurrentProcessFilterParam({id: filter.id}); + onProcessFilterAction(filterAction: any) { + this.cloudLayoutService.setCurrentProcessFilterParam({id: filterAction.filter.id}); + if (filterAction.actionType === ProcessesCloudDemoComponent.ACTION_SAVE_AS) { + this.router.navigate([`/cloud/${this.applicationName}/processes/`], { queryParams: filterAction.filter }); + } } } diff --git a/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.html b/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.html index 3cd478e279..4be5d6213b 100644 --- a/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.html +++ b/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.html @@ -2,6 +2,7 @@ diff --git a/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.ts b/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.ts index 92dc27c3a4..1835016027 100644 --- a/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.ts +++ b/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.ts @@ -17,7 +17,7 @@ import { Component, ViewChild, OnInit } from '@angular/core'; import { TaskListCloudComponent, TaskListCloudSortingModel, TaskFilterCloudModel } from '@alfresco/adf-process-services-cloud'; -import { UserPreferencesService } from '@alfresco/adf-core'; +import { UserPreferencesService, AppConfigService } from '@alfresco/adf-core'; import { ActivatedRoute, Router } from '@angular/router'; import { CloudLayoutService } from './services/cloud-layout.service'; @@ -28,6 +28,7 @@ import { CloudLayoutService } from './services/cloud-layout.service'; export class TasksCloudDemoComponent implements OnInit { public static ACTION_SAVE_AS = 'SAVE_AS'; + static TASK_FILTER_PROPERTY_KEYS = 'adf-edit-task-filter.properties'; @ViewChild('taskCloud') taskCloud: TaskListCloudComponent; @@ -40,6 +41,7 @@ export class TasksCloudDemoComponent implements OnInit { sortArray: TaskListCloudSortingModel[]; editedFilter: TaskFilterCloudModel; + taskFilterProperties: any[] = []; filterId; @@ -47,7 +49,13 @@ export class TasksCloudDemoComponent implements OnInit { private cloudLayoutService: CloudLayoutService, private route: ActivatedRoute, private router: Router, - private userPreference: UserPreferencesService) { + private userPreference: UserPreferencesService, + private appConfig: AppConfigService) { + + const properties = this.appConfig.get>(TasksCloudDemoComponent.TASK_FILTER_PROPERTY_KEYS); + if (properties) { + this.taskFilterProperties = properties; + } } ngOnInit() { diff --git a/demo-shell/src/app/components/config-editor/config-editor.component.html b/demo-shell/src/app/components/config-editor/config-editor.component.html index c6484883aa..1db402d80d 100644 --- a/demo-shell/src/app/components/config-editor/config-editor.component.html +++ b/demo-shell/src/app/components/config-editor/config-editor.component.html @@ -48,6 +48,20 @@ info + + + Edit process filter + + + + + Edit task filter + +
diff --git a/demo-shell/src/app/components/config-editor/config-editor.component.ts b/demo-shell/src/app/components/config-editor/config-editor.component.ts index 1ce6792b02..eea2ae8eaf 100644 --- a/demo-shell/src/app/components/config-editor/config-editor.component.ts +++ b/demo-shell/src/app/components/config-editor/config-editor.component.ts @@ -112,6 +112,18 @@ export class ConfigEditorComponent { this.indentCode(); } + editProcessFilterConfClick() { + this.code = JSON.stringify(this.appConfig.config['edit-process-filter']); + this.field = 'edit-process-filter'; + this.indentCode(); + } + + editTaskFilterConfClick() { + this.code = JSON.stringify(this.appConfig.config['edit-task-filter']); + this.field = 'edit-task-filter'; + this.indentCode(); + } + indentCode() { setTimeout(() => { this.editor.getAction('editor.action.formatDocument').run(); diff --git a/docs/docassets/images/edit-process-filter-cloud.component.png b/docs/docassets/images/edit-process-filter-cloud.component.png index 969570e8c6ed95b20c8249f0c32d080a73919a33..14a08cc454afbd3b0a2ea98fcb381aae165bc418 100644 GIT binary patch literal 9299 zcmd6NWn5HU+b+AWr3fO@ARyh{Af3{UNQnYNw{*== z?=|=Hd%vIGIUk<0Kgi5Dv-e(WUDvv1BF1U%DjxopOCivV1PKO;15v@E~m?FzvoiPgaf%-tBhAJ2%^?Bz211^T!Jh zHAkKm7OK}&WR|_Ud3((#M-xvxJTgK+$YP|WKep3J41ljA&?*H68D%u6r9 zi+AJ?F;jx|&t-yJn7?1eeQ+^#4Uim>ggMgG)KpWub2ifa9!H}a zL@$(Do|*K>@dliCo1@P2#7SOWo{yrJL$b1}%EZXX)YS9_1@A^;@ZR2DeqP?j#zwUc zYQNYL*_cQe&RH_CadvXlYZPc5`Q?iwK58LSX`y=H_3DRu=dEcUylaX{!UDE)lHI7y z&CT9umc4_62D`WwhS$leW zhkyP|5^*2<^(!YY&&N9^zDyq(43h9M2(o4JXBf%f+Hg#`uE-orE@K|w(jd{)E~mkB9XW@hde zj~@0c_pmjcre$Y4&$SWc;!aSVpP$F`SwHr5aB#SN`}W3k!^y$=_!T1R4L{CH3n?Wf z?2L>(V`F23gGy3TnYFd(^RttiH*db&f5XVl?J!;My8BtW)t~5Se}99GUi#j>dl3;4 zr)Ou$B(x zBBxhMnyj?^b5amIxEv_?AYA6<%a_~+9YJ5eel2c9%Q4)}%E-8Wk9(ynoX%u0sW3N} zT)=JtevlKX;~Q3AUq3T518ZSZqr=BnW%;Ml)XdDu!GTi5jayW-F*WsjPY)vl!zXls z+1t0j>YT03%*+f7(hCd!4kQZYvWETm@dK^{{bXP;56#f`mE!|(i^HRBTld3_SI?fc zetDvrv6P>idsCvUtjub%y40wb)XW&B?d@9zo?n@nnU6R)IN-vWneD%Rxr`OOV$*9B zc0VkTd5B(&(Zi?ZG8u5Nvx95Pym-+!I7k*CnU$55?1f%^cL@idmYkeiKtLc}I&`3` zE2p@akDYz{@K8}fp?`4D+T6Unv^3Reyfc)B{_$h4opv(cFsP!KsHmYkpC!}aW>?nN z`FMDA>s{amVp?av#>cz6xWJP9>~GWGsX}@Qx$Z9P%y$mk6H@ZC!-Ni{OE(qPA0NB0 z#;h+cO0n3Tp$}SGTJA8Z6H6R!OmM-F3ksN1l~-0*O)V`IDgpundj|#{`$ECTPlsh@ zE=D|(U(x5};u30-vd+J-q`<~H_9wpTH#<99SXh|;JUA^RQ$Y+vsR>%6W5=fPi3Ve7wnFH6l4#PeQ^cHulx4SFy##TN@MQ@QpuXW27`R$x%@} zp6Gp?D};u|#!14i2qh&p4vrxtQcOS~c5s=7-zKHL{&cQAkdly|hv(pTg*lycNWBNj z4jS6fFe4;H(aGts#(p^`)bKe|_&mimnkc}si8@^7HZ^roYHF!*|2;zg$bHEk zD=Qv{6DBZA(xKF6=O^oRkSU`a$B_+u` z{2UZCGB!3{X-N^^uP7%c2n&=Z6}S@3OlscZOK`M>%qCgT^FEWJ3N`#fKks)9*9T_3 zFIfx;$go=<0kgEw9Z5tb2%y!ryhlJA!>S7?8?7@oJ>9scyaLVUdTGA|S>EbiX&AdRp~3AAQal{1_XFp+?k zk}$iluzzYQv0Z-$m6rL8TcP&E7sI0$YJN6u-N>JM~2V zEsP&lYJ^Ad%g2u&0R|;LB_|`7`r`vXe}1eGw=g%CmYXY!zq7v|6cF&L%n$}l#Q1CL>+U5p8M2Xcb90PLObP&A{rze$UkX+|dGcgvY%EJ~0iRZfRYOnj zVl-camzUR<_|Ba>Fkfp=M3cpAW}do1BmipYYJl$RFg?R`IBd&cP z5rMF@EHo_6dwTyamvLW%`_WQgtSX!iIK|DyrM_I4{M)y0(>t3E@f+yHYFe!0x5_)= zf^wSL+JGzGUqr&8HuE)#$3{n^dQ<=b?%z-O_HCrOc?PC&5H~h97PY@R3{q#Fn@Nh50x_ zRrP3Ng5>sXZj%8@9Gnkd{yplPx6b#6l|UW2A8i`dMp!i(t~Bi*9H=4?adC0A^*Dti zn~W$w>yaFlWbboSB)yWIy*&vD36M9Eyxy=_XYEbQvLyutg%SuYgO2OaBxZ#EtuUpp z>zoQVEET@8(=sz#0Oi0C=$;3qTo!Ul9w~7UIK=NMqR|b-x-Z2kdP@Qq`@1yd17!5g z8lBCPPU#28@?XFxM+XO>qiXlPF;e98H0RCME|15t4S-i<XgLuceLMF3PMM_Wxcvl1^}yZ}W>q>fEf^``Y> zZeE@f^@J)kD832;5wUTNnT;*Rg6p5=ce0U>SzeX34G%{)-e8RH&-##6RAdERVs6fK zp#1PwRCM&A%dgLx%pt+SJiNR~+@`lg;52mw1sjQCqyPN#PfBLy!S-w`Tvh2S`)uN3 zx7X~J>@Lfi@fp_kCH?XVXcNVM4|w6-dZv~!N9T7noMvZrW16l?OcU zr_|J-fV}lfH5qQpO-qRJ@*V<+-nelCx@|C7EPKm}->@qT^gG-Updx_ajT^zo$H$*P ze}+%&mj`TXCaICjhijwxAn)LPRDyth+b7y*AC>jOVA$u!^P!(UePZ1*oSm5o`|_nt zzw{#!jbeYe%Jh%aXW$NcdwYch1*wHyw#W6n4+@r8WgDfYifOP!xu5wH@^Eu6?6?S1 z5B~U}+}LFJ>s8E7xDW9Jmz_``6$y!fxw(*j8-6Yd@P&|&5Y*DXh{D2RFI$ta$MH5$ zaejV23o~S(d)%-Zf>esF97J$qdEEbl%PD=*p}^T zKpKmRijH^?5rtM)SAV!d(M12EO_;o(s;3rSC>^AxyGn^945FjlCIu+-7jU2L4% zeAsn_WVKu$E8Maec6YwLrFJ$yZg(k#Xmo50=-1fC%L8OIXt+Jk@^p2-LN-7&}W@w|qw!$YjE>?gvIi zMKv^toS7eBVTm*^?`dmkJzV}InFbmZjw+T1G4k4i)<=m8?y|v9EhB^Gj#l?O`_B^D z7bvj|hek#uJ#gP0NuD{VAN_$Cpl7l3{fSu}Hw_I9IXU3=hsKC9|uu-RAypAf`z4}_vwywZg!Sp;@aQ84WJiuQNZ^gsKme(Ra7`yf5AP| zGBsT#5<3k`N_wrLl3Gw8Y&MbuUE=EEvN2W1$HEbxrV+nS40Z+Vq8n&ixT;C1rf(QneAR5lLbyp7 zI8d5I>h>vW4~AOjxE}4}(}Fyan6(B0vkg)hF$}C2N-YBBNvFZBB0O9LNjeq_aw0So zS@PyXR+h2eIQCi4DM^MVGa(U?UWM5xp!owjI?!-*M+(jUY(V1>9rR`cp~B>hpQ-6q z+oU!$oPprXl1K&m`E--c!^5Lmrzj%gR$|(*hiGUg1$fMnISA!|cQE>De}A&fNPbyS z$jHdZtz~r7ajMo4sH)ECFQ~=zKP3|u@1Oo~U5t20u>$KQN8j!8-?%FAP{ zqn`Mab}IwoE8R7*Xv!m|t1-Qq$TeW>U%!6w^Yi0gzWf_r{rRK1=PLX-H`C@4ejq&f_q1&$S?BF~^>IU{34>vdiaZXIs zq@>J%2}KZ;H8dpl{*CgYd=9CYVgi4u)s!GQkngE;mp2k-6(X|)@L-Sxg@z7hDa0Fk zZ?;GW2on85eVY7WU~6yBl{~m?!ym!*h%UwgG5{|xFW_L%VlX4fQUm{uLt*OTii#)| zKkIS`tY83bZIXh9`zh364?EA+EJ5*fgwl9J>L!SClw~I6s% zfGL%t`ATM$GYfU7bfSrjjLcgzGfsB)YR7eTaA?ZP#Iy?#>_Bh;`V*n1R#8!r$Z2%d zv#qzUuO7Z5CFS?@K0BP&**Uv0e7ec|0#+CdAcS%uAtC!uW0nV#=^s6c`tk+hPY4$k z<9UP8adALgNlEAE19M0)!|9}J>*|Kxx1a&QSdD|6LZQ)4uRL5`QSE_b$zt9E^E*&F zU!tNwh*eokNZ<>&Y|m8N&O_{Z?bTO3Y65=T<1zaJIO^c=ZIbWOnZz=k^rUa`?1&j$tvpb^4c z&_Z^+@wy0vM1PalS#4F-5hx8(7A*(pYNYo?A~B5q*3Fx5OifYkWO^2p)k6@GrKG$g zMrdk&4GUw3EChyn1s4|t#O|(LtpsaK9iR*3fl#^w2jJ0tKYj#%Qltl`M;}=5p1?}) zmt()5K_9F`V4+=Q1)yoKqA~=_%+13C3$>ekaheS+$HEe9_T=_ex(5#^DJh+{rW@b{ zppt!keUCh>Q?0veh;~7ZgPhOkxN{RTxp1mlvr$GVDJhf{J{qsP_&`LIbbWmtayyI# zgggVPw5+lc#B36miJ~*QvbCiJ5CnjmfEGdlE)EXRo7b*i&!B)i38oT?j`P}Ey5ZmY z3J4V3yqmH~419NeZLL->^g5KmmyJ&qWo772FOkSzuSXw7^!|cQhjomyu(>$*#KZGr zYdmg&9Rx78-@kwVkMf3KAj7`_?1)2CHl-->?(d4LSFgh86cnnyeY<{_y}7f~r0vs9 zUt&UkI?_N9Vd0#;aUcRdtI6%{?UHZb9{NI5k3K#=J>44wnu8n_3_iSgsZ<#k_j|I& zK358XK)|SS?B{?!A*~b3&lAqqdX38gefso<6y z5c2Zt&}@mtGetQ$F3?dBy+H1eBk&;nlb}>lW+so@{t86j90nbCDJYj>s>I6p;!MJBv zo}Qj&T6%ik$VN{vf`IwqweM<80LqhuT_wnVB)S>i6?S3QWhAcI)Ifmz5;E?)i%zYr zt=({bt)?~#_X0fwGW8s=jRs?5CG->{{8zz zwnisg+nZPYvK15KAPN^zWhRxs@hm1!FRx_)Z`h~~woCZ=>rZ%|i9ses4!CD+?G0Nt z-LcSu5V1g6!$~9OD`%3^%gUg*6v}#o+x5ZMS5aj%jQrSw#ROuK8xZqEJGi(U|G5

p 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%0AGUT3K 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 F{11T6^V9$U diff --git a/docs/process-services-cloud/edit-process-filter-cloud.component.md b/docs/process-services-cloud/edit-process-filter-cloud.component.md index 8baffbe0b6..ce4bdf4574 100644 --- a/docs/process-services-cloud/edit-process-filter-cloud.component.md +++ b/docs/process-services-cloud/edit-process-filter-cloud.component.md @@ -24,8 +24,11 @@ Shows Process Filter Details. | Name | Type | Default value | Description | | ---- | ---- | ------------- | ----------- | -| appName | `string` | | The name of the application. | -| id | `string` | | Id of the process instance filter. | +| appName | `string` | | (required) The name of the application. | +| id | `string` | | (required) Id of the process instance filter. | +| filterProperties | `string []` | `['state', 'sort', 'order']` | List of process filter properties to display. | +| showFilterActions | `boolean` | `true` | Toggles edit process filter actions. | +| showTitle | `boolean` | `true` | Toggles edit process filter title. | ### Events @@ -38,7 +41,7 @@ Shows Process Filter Details. ### Editing APS2 process filter -Use the process filter id property to edit process filter properties: +Use the application name and process filter id property to edit process filter properties: ```html ``` +By default these below properties are displayed: + +**_state_**, **_sort_**, **_order_**. + +However, you can also choose which properties to show using an input property +`filterProperties`: + +Populate the filterProperties in the component class: + +```ts +import { UserProcessModel } from '@alfresco/adf-core'; + +export class SomeComponent implements OnInit { + + filterProperties: string[] = [ + "appName", + "processInstanceId", + "startDate", + "startedAfter"]; + + onFilterChange(filter: ProcessFilterCloudModel) { + console.log('On filter change: ', filter); + } + + onAction($event: ProcessFilterActionType) { + console.log('Clicked action: ', $event); + } +``` + +With this configuration, only the four listed properties will be shown. + +```html + + +``` + + +All Available properties are: + +**_appName_**, **_initiator_**, **_state_**, **_sort_**, **_order_**, **_processDefinitionId_**, **_processDefinitionKey_**, **_processInstanceId_**, **_startDate_**, **_lastModified_**, **_lastModifiedFrom_**, **_lastModifiedTo_**. ![edit-process-filter-cloud](../docassets/images/edit-process-filter-cloud.component.png) diff --git a/docs/process-services-cloud/edit-task-filter-cloud.component.md b/docs/process-services-cloud/edit-task-filter-cloud.component.md index c325568f3e..d29cbbc5c3 100644 --- a/docs/process-services-cloud/edit-task-filter-cloud.component.md +++ b/docs/process-services-cloud/edit-task-filter-cloud.component.md @@ -25,10 +25,10 @@ Edits Task Filter Details. | Name | Type | Default value | Description | | ---- | ---- | ------------- | ----------- | | appName | `string` | | (required) Name of the app. | -| filterProperties | `string[]` | | List of task filter properties to display. | -| id | `string` | | (required) ID of the task filter. | -| showTitle | `boolean` | true | Toggles the title. | -| toggleFilterActions | `boolean` | true | Toggles the filter actions. | +| id | `string` | "" | (required) The id of the Task filter. | +| filterProperties | `string []` | `['state', 'assignment', 'sort', 'order']` | List of task filter properties to display. | +| showFilterActions | `boolean` | `true` | Toggles edit task filter actions. | +| showTitle | `boolean` | `true` | Toggles edit task filter title. | ### Events diff --git a/e2e/pages/adf/dataTablePage.ts b/e2e/pages/adf/dataTablePage.ts index dda2778120..ea702d3f1f 100644 --- a/e2e/pages/adf/dataTablePage.ts +++ b/e2e/pages/adf/dataTablePage.ts @@ -243,10 +243,12 @@ export class DataTablePage { checkSpinnerIsDisplayed() { Util.waitUntilElementIsPresent(this.spinner); + return this; } checkSpinnerIsNotDisplayed() { Util.waitUntilElementIsNotOnPage(this.spinner); + return this; } checkRowIsDisplayedByName(filename) { diff --git a/e2e/pages/adf/process-cloud/editProcessFilterCloudComponent.ts b/e2e/pages/adf/process-cloud/editProcessFilterCloudComponent.ts index 7a591a3b23..aae0cb198d 100644 --- a/e2e/pages/adf/process-cloud/editProcessFilterCloudComponent.ts +++ b/e2e/pages/adf/process-cloud/editProcessFilterCloudComponent.ts @@ -29,7 +29,7 @@ export class EditProcessFilterCloudComponent { } setStateFilterDropDown(option) { - this.clickOnDropDownArrow('status'); + this.clickOnDropDownArrow('state'); let stateElement = element.all(by.cssContainingText('mat-option span', option)).first(); Util.waitUntilElementIsClickable(stateElement); diff --git a/e2e/process-services-cloud/process-custom-filters.e2e.ts b/e2e/process-services-cloud/process-custom-filters.e2e.ts index 962dfb2b8c..bd42f16abb 100644 --- a/e2e/process-services-cloud/process-custom-filters.e2e.ts +++ b/e2e/process-services-cloud/process-custom-filters.e2e.ts @@ -98,6 +98,7 @@ describe('Process list cloud', () => { it('[C291783] Should display processes ordered by id when Id is selected from sort dropdown', async() => { processCloudDemoPage.editProcessFilterCloudComponent().clickCustomiseFilterHeader().setStateFilterDropDown('RUNNING') .setSortFilterDropDown('ID').setOrderFilterDropDown('ASC'); + processCloudDemoPage.processListCloudComponent().getDataTable().checkSpinnerIsDisplayed().checkSpinnerIsNotDisplayed(); processCloudDemoPage.getAllRowsByIdColumn().then(function (list) { let initialList = list.slice(0); list.sort(function (firstStr, secondStr) { @@ -107,6 +108,7 @@ describe('Process list cloud', () => { }); processCloudDemoPage.editProcessFilterCloudComponent().setOrderFilterDropDown('DESC'); + processCloudDemoPage.processListCloudComponent().getDataTable().checkSpinnerIsDisplayed().checkSpinnerIsNotDisplayed(); processCloudDemoPage.getAllRowsByIdColumn().then(function (list) { let initialList = list.slice(0); list.sort(function (firstStr, secondStr) { diff --git a/lib/process-services-cloud/src/lib/i18n/en.json b/lib/process-services-cloud/src/lib/i18n/en.json index d3829d0bf1..3463572c7c 100644 --- a/lib/process-services-cloud/src/lib/i18n/en.json +++ b/lib/process-services-cloud/src/lib/i18n/en.json @@ -126,21 +126,30 @@ }, "ADF_CLOUD_EDIT_PROCESS_FILTER": { "TITLE": "Customize your filter", - "STATUS": "Select a status", - "ASSIGNMENT": "ASSIGNMENT", - "COLUMN": "Select a column", - "DIRECTION": "Select a direction", + "LABEL": { + "APP_NAME": "ApplicationName", + "STATUS": "Status", + "INITIATOR": "Initiator", + "ASSIGNMENT": "Assignment", + "SORT": "Sort", + "DIRECTION": "Direction", + "PROCESS_DEF_ID": "ProcessDefinitionId", + "PROCESS_DEF_KEY": "ProcessDefinitionKey", + "PROCESS_INS_ID": "ProcessInstanceId", + "START_DATE": "StartDate", + "LAST_MODIFIED": "LastModified", + "LAST_MODIFIED_DATE_FORM": "LastModifiedFrom", + "LAST_MODIFIED_TO": "LastModifiedTo", + "PROCESS_NAME": "ProcessName" + }, + "ERROR": { + "DATE": "Date format DD/MM/YYYY" + }, "TOOL_TIP": { "SAVE": "Save filter", "SAVE_AS": "Save filter as", "DELETE": "Delete filter" }, - "LABEL": { - "STATUS": "Status", - "ASSIGNMENT": "Assignment", - "COLUMN": "Column", - "DIRECTION": "Direction" - }, "DIALOG": { "TITLE": "Save filter as", "SAVE": "SAVE", diff --git a/lib/process-services-cloud/src/lib/process-services-cloud.module.ts b/lib/process-services-cloud/src/lib/process-services-cloud.module.ts index 2b9eeaec79..169701b12d 100644 --- a/lib/process-services-cloud/src/lib/process-services-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/process-services-cloud.module.ts @@ -20,12 +20,14 @@ import { TRANSLATION_PROVIDER } from '@alfresco/adf-core'; import { AppListCloudModule } from './app/app-list-cloud.module'; import { TaskCloudModule } from './task/task-cloud.module'; import { ProcessCloudModule } from './process/process-cloud.module'; +import { GroupCloudModule } from './group/group-cloud.module'; @NgModule({ imports: [ AppListCloudModule, ProcessCloudModule, - TaskCloudModule + TaskCloudModule, + GroupCloudModule ], providers: [ { @@ -40,7 +42,8 @@ import { ProcessCloudModule } from './process/process-cloud.module'; exports: [ AppListCloudModule, ProcessCloudModule, - TaskCloudModule + TaskCloudModule, + GroupCloudModule ] }) export class ProcessServicesCloudModule { } diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.html b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.html index c93ab1d40b..69787087f6 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.html +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.html @@ -1,60 +1,65 @@ - + - {{processFilter.name | translate}} - - {{ 'ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE' | translate}} + {{processFilter.name | translate}} + + {{ 'ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE' | translate}} +

+ + + + + +
-
- - - - {{ state.label }} +
+ + + + + {{ propertyOption.label }} - + + [formControlName]="processFilterProperty.key" + type="text" + placeholder="{{processFilterProperty.label | translate}}" + [attr.data-automation-id]="'adf-cloud-edit-process-property-' + processFilterProperty.key"/> - - + + {{processFilterProperty.label | translate}} + + + +
+
+
{{'ADF_CLOUD_EDIT_PROCESS_FILTER.ERROR.DATE' | translate}}
+ warning +
+
- - - - {{ column.label }} - - - - - - - {{ direction }} - - - -
- - - -
-
+ +
diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.scss b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.scss index e69de29bb2..498ba1f835 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.scss +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.scss @@ -0,0 +1,31 @@ +@mixin adf-cloud-edit-process-filter-theme($theme) { + + $warn: map-get($theme, warn); + + .adf-edit-process-filter-date-error-container { + position: absolute; + height: 20px; + margin-top: 12px; + width: 100%; + + & > div { + display: flex; + flex-flow: row; + justify-content: flex-start; + } + + .adf-error-text { + padding-right: 8px; + height: 16px; + font-size: 11px; + line-height: 1.33; + color: mat-color($warn); + width: auto; + } + + .adf-error-icon { + font-size: 16px; + color: mat-color($warn); + } + } +} diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts index a1f6ab913f..5cfc342c3a 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts @@ -19,7 +19,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleChange } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { setupTestBed, IdentityUserService } from '@alfresco/adf-core'; +import { setupTestBed } from '@alfresco/adf-core'; import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; import { MatDialog } from '@angular/material'; import { of } from 'rxjs'; @@ -28,20 +28,24 @@ import { EditProcessFilterCloudComponent } from './edit-process-filter-cloud.com import { ProcessFiltersCloudModule } from '../process-filters-cloud.module'; import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model'; import { ProcessFilterCloudService } from '../services/process-filter-cloud.service'; +import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service'; +import { fakeApplicationInstance } from './../../../app/mock/app-model.mock'; describe('EditProcessFilterCloudComponent', () => { let component: EditProcessFilterCloudComponent; let service: ProcessFilterCloudService; - let identityService: IdentityUserService; let fixture: ComponentFixture; let dialog: MatDialog; + let appsService: AppsProcessCloudService; + let getRunningApplicationsSpy: jasmine.Spy; + let getProcessFilterByIdSpy: jasmine.Spy; let fakeFilter = new ProcessFilterCloudModel({ name: 'FakeRunningProcess', icon: 'adjust', - id: 10, + id: 'mock-process-filter-id', state: 'RUNNING', - appName: 'app-name', + appName: 'mock-app-name', processDefinitionId: 'process-def-id', assignment: 'fake-involved', order: 'ASC', @@ -57,14 +61,15 @@ describe('EditProcessFilterCloudComponent', () => { fixture = TestBed.createComponent(EditProcessFilterCloudComponent); component = fixture.componentInstance; service = TestBed.get(ProcessFilterCloudService); - identityService = TestBed.get(IdentityUserService); + appsService = TestBed.get(AppsProcessCloudService); dialog = TestBed.get(MatDialog); spyOn(dialog, 'open').and.returnValue({ afterClosed() { return of({ action: ProcessFilterDialogCloudComponent.ACTION_SAVE, icon: 'icon', name: 'fake-name' }); }}); - spyOn(service, 'getProcessFilterById').and.returnValue(fakeFilter); + getProcessFilterByIdSpy = spyOn(service, 'getProcessFilterById').and.returnValue(fakeFilter); + getRunningApplicationsSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance)); }); it('should create EditProcessFilterCloudComponent', () => { @@ -72,12 +77,12 @@ describe('EditProcessFilterCloudComponent', () => { }); it('should fetch process instance filter by id', async(() => { - let change = new SimpleChange(undefined, '10', true); - component.ngOnChanges({ 'id': change }); - fixture.detectChanges(); + let processFilterIDchange = new SimpleChange(undefined, 'mock-process-filter-id', true); + component.ngOnChanges({'id': processFilterIDchange}); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); + expect(getProcessFilterByIdSpy).toHaveBeenCalled(); expect(component.processFilter.name).toEqual('FakeRunningProcess'); expect(component.processFilter.icon).toEqual('adjust'); expect(component.processFilter.state).toEqual('RUNNING'); @@ -87,27 +92,26 @@ describe('EditProcessFilterCloudComponent', () => { })); it('should display filter name as title', () => { - let change = new SimpleChange(undefined, '10', true); - component.ngOnChanges({ 'id': change }); - fixture.detectChanges(); + let processFilterIDchange = new SimpleChange(undefined, 'mock-process-filter-id', true); + component.ngOnChanges({'id': processFilterIDchange}); fixture.detectChanges(); const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-process-filter-title-id'); const subTitle = fixture.debugElement.nativeElement.querySelector('#adf-edit-process-filter-sub-title-id'); expect(title).toBeDefined(); expect(subTitle).toBeDefined(); expect(title.innerText).toEqual('FakeRunningProcess'); - expect(subTitle.innerText).toEqual('ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE'); + expect(subTitle.innerText.trim()).toEqual('ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE'); }); describe('EditProcessFilter form', () => { beforeEach(() => { - let change = new SimpleChange(undefined, '10', true); - component.ngOnChanges({ 'id': change }); + let processFilterIDchange = new SimpleChange(undefined, 'mock-process-filter-id', true); + component.ngOnChanges({'id': processFilterIDchange}); fixture.detectChanges(); }); - it('should define editProcessFilter form', () => { + it('should defined editProcessFilter form', () => { expect(component.editProcessFilterForm).toBeDefined(); }); @@ -129,6 +133,7 @@ describe('EditProcessFilterCloudComponent', () => { })); it('should disable save button if the process filter is not changed', async(() => { + component.toggleFilterActions = true; let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); fixture.detectChanges(); @@ -139,6 +144,7 @@ describe('EditProcessFilterCloudComponent', () => { })); it('should disable saveAs button if the process filter is not changed', async(() => { + component.toggleFilterActions = true; let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); fixture.detectChanges(); @@ -149,6 +155,7 @@ describe('EditProcessFilterCloudComponent', () => { })); it('should enable delete button by default', async(() => { + component.toggleFilterActions = true; let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); fixture.detectChanges(); @@ -159,33 +166,35 @@ describe('EditProcessFilterCloudComponent', () => { })); it('should display current process filter details', async(() => { - let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); - expansionPanel.click(); fixture.detectChanges(); fixture.whenStable().then(() => { - let stateElement = fixture.debugElement.nativeElement.querySelector('#adf-process-filter-state-id'); - let sortElement = fixture.debugElement.nativeElement.querySelector('#adf-process-filter-sort-id'); - let orderElement = fixture.debugElement.nativeElement.querySelector('#adf-process-filter-order-id'); + let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + let stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-process-property-state"]'); + let sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-process-property-sort"]'); + let orderElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-process-property-order"]'); expect(stateElement).toBeDefined(); expect(sortElement).toBeDefined(); expect(orderElement).toBeDefined(); - expect(stateElement.innerText.trim()).toBe('ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.STATUS'); - expect(sortElement.innerText.trim()).toBe('ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.COLUMN'); - expect(orderElement.innerText.trim()).toBe('ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.DIRECTION'); + expect(stateElement.innerText.trim()).toBe('RUNNING'); + expect(sortElement.innerText.trim()).toBe('ID'); + expect(orderElement.innerText.trim()).toBe('ASC'); }); })); it('should enable save button if the process filter is changed', async(() => { + fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const stateElement = fixture.debugElement.query(By.css('#adf-process-filter-state-id .mat-select-trigger')).nativeElement; + let stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-process-property-state"] .mat-select-trigger'); stateElement.click(); fixture.detectChanges(); + const saveButton = fixture.debugElement.nativeElement.querySelector('#adf-save-id'); + const options = fixture.debugElement.queryAll(By.css('.mat-option-text')); + options[2].nativeElement.click(); + fixture.detectChanges(); fixture.whenStable().then(() => { - const saveButton = fixture.debugElement.nativeElement.querySelector('#adf-save-id'); - const options = fixture.debugElement.queryAll(By.css('.mat-option-text')); - options[2].nativeElement.click(); - fixture.detectChanges(); expect(saveButton.disabled).toBe(false); }); })); @@ -194,7 +203,7 @@ describe('EditProcessFilterCloudComponent', () => { fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const stateElement = fixture.debugElement.query(By.css('#adf-process-filter-state-id .mat-select-trigger')).nativeElement; + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-process-property-state"] .mat-select-trigger'); stateElement.click(); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -207,7 +216,7 @@ describe('EditProcessFilterCloudComponent', () => { fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const sortElement = fixture.debugElement.query(By.css('#adf-process-filter-sort-id .mat-select-trigger')).nativeElement; + const sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-process-property-sort"] .mat-select-trigger'); sortElement.click(); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -220,7 +229,7 @@ describe('EditProcessFilterCloudComponent', () => { fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const orderElement = fixture.debugElement.query(By.css('#adf-process-filter-order-id .mat-select-trigger')).nativeElement; + const orderElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-process-property-order"] .mat-select-trigger'); orderElement.click(); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -228,48 +237,117 @@ describe('EditProcessFilterCloudComponent', () => { expect(orderOptions.length).toEqual(2); }); })); + + it('should able to build a editProcessFilter form with default properties if input is empty', async(() => { + let processFilterIDchange = new SimpleChange(undefined, 'mock-process-filter-id', true); + component.ngOnChanges({'id': processFilterIDchange}); + component.filterProperties = []; + fixture.detectChanges(); + fixture.whenStable().then(() => { + const stateController = component.editProcessFilterForm.get('state'); + const sortController = component.editProcessFilterForm.get('sort'); + const orderController = component.editProcessFilterForm.get('order'); + fixture.detectChanges(); + expect(component.processFilterProperties).toBeDefined(); + expect(component.processFilterProperties.length).toBe(3); + expect(component.editProcessFilterForm).toBeDefined(); + expect(stateController).toBeDefined(); + expect(sortController).toBeDefined(); + expect(orderController).toBeDefined(); + expect(stateController.value).toBe('RUNNING'); + expect(sortController.value).toBe('id'); + expect(orderController.value).toBe('ASC'); + }); + })); + }); + + describe('Process filterProperties', () => { + + beforeEach(() => { + component.filterProperties = ['appName', 'processInstanceId', 'processName']; + }); + + it('should able to fetch running applications when appName property defined in the input', async(() => { + fixture.detectChanges(); + let processFilterIDchange = new SimpleChange(undefined, 'mock-process-filter-id', true); + component.ngOnChanges({'id': processFilterIDchange}); + const appController = component.editProcessFilterForm.get('appName'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(getRunningApplicationsSpy).toHaveBeenCalled(); + expect(appController).toBeDefined(); + expect(appController.value).toBe('mock-app-name'); + }); + })); + + it('should able to build a editProcessFilter form with given input properties', async(() => { + fixture.detectChanges(); + getProcessFilterByIdSpy.and.returnValue({ appName: 'mock-app-name', processInstanceId: 'process-instance-id', processName: 'mock-process-name' }); + let processFilterIDchange = new SimpleChange(undefined, 'mock-process-filter-id', true); + component.ngOnChanges({'id': processFilterIDchange}); + fixture.detectChanges(); + const appController = component.editProcessFilterForm.get('appName'); + const processNameController = component.editProcessFilterForm.get('processName'); + const processInsIdController = component.editProcessFilterForm.get('processInstanceId'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(getRunningApplicationsSpy).toHaveBeenCalled(); + expect(component.processFilterProperties).toBeDefined(); + expect(component.editProcessFilterForm).toBeDefined(); + expect(component.processFilterProperties.length).toBe(3); + expect(appController).toBeDefined(); + expect(processNameController).toBeDefined(); + expect(processInsIdController).toBeDefined(); + expect(appController.value).toBe('mock-app-name'); + }); + })); }); describe('edit filter actions', () => { beforeEach(() => { - let change = new SimpleChange(undefined, '10', true); - component.ngOnChanges({ 'id': change }); + let processFilterIDchange = new SimpleChange(undefined, 'mock-process-filter-id', true); + component.ngOnChanges({'id': processFilterIDchange}); + fixture.detectChanges(); }); it('should emit save event and save the filter on click save button', async(() => { - spyOn(identityService, 'getCurrentUserInfo').and.returnValue({username: 'currentUser'}); + component.toggleFilterActions = true; const saveFilterSpy = spyOn(service, 'updateFilter').and.returnValue(fakeFilter); let saveSpy: jasmine.Spy = spyOn(component.action, 'emit'); + fixture.detectChanges(); - let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const stateElement = fixture.debugElement.query(By.css('#adf-process-filter-state-id .mat-select-trigger')).nativeElement; + fixture.detectChanges(); + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-process-property-state"] .mat-select-trigger'); stateElement.click(); fixture.detectChanges(); + const saveButton = fixture.debugElement.nativeElement.querySelector('#adf-save-id'); + const stateOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); + stateOptions[2].nativeElement.click(); + saveButton.click(); + fixture.detectChanges(); fixture.whenStable().then(() => { - const saveButton = fixture.debugElement.nativeElement.querySelector('#adf-save-id'); - const stateOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); - stateOptions[2].nativeElement.click(); - fixture.detectChanges(); - saveButton.click(); - fixture.detectChanges(); expect(saveFilterSpy).toHaveBeenCalled(); expect(saveSpy).toHaveBeenCalled(); }); })); it('should emit delete event and delete the filter on click of delete button', async(() => { - spyOn(identityService, 'getCurrentUserInfo').and.returnValue({username: 'currentUser'}); + component.toggleFilterActions = true; const deleteFilterSpy = spyOn(service, 'deleteFilter').and.callThrough(); let deleteSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); - let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const stateElement = fixture.debugElement.query(By.css('#adf-process-filter-state-id .mat-select-trigger')).nativeElement; + fixture.detectChanges(); + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-process-property-state"] .mat-select-trigger'); stateElement.click(); fixture.detectChanges(); - let deleteButton = fixture.debugElement.nativeElement.querySelector('#adf-delete-id'); + const deleteButton = fixture.debugElement.nativeElement.querySelector('#adf-delete-id'); deleteButton.click(); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -279,13 +357,15 @@ describe('EditProcessFilterCloudComponent', () => { })); it('should emit saveAs event and add filter on click saveAs button', async(() => { - spyOn(identityService, 'getCurrentUserInfo').and.returnValue({username: 'currentUser'}); + component.toggleFilterActions = true; const saveAsFilterSpy = spyOn(service, 'addFilter').and.callThrough(); let saveAsSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); - let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const stateElement = fixture.debugElement.query(By.css('#adf-process-filter-state-id .mat-select-trigger')).nativeElement; + fixture.detectChanges(); + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-process-property-state"] .mat-select-trigger'); stateElement.click(); fixture.detectChanges(); const saveButton = fixture.debugElement.nativeElement.querySelector('#adf-save-as-id'); diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts index e5c1156d3b..307c26a717 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts @@ -16,25 +16,34 @@ */ import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; -import { FormGroup, FormBuilder } from '@angular/forms'; -import { ProcessFilterCloudModel, ProcessFilterActionType } from '../models/process-filter-cloud.model'; +import { FormGroup, FormBuilder, AbstractControl } from '@angular/forms'; +import { MatDialog } from '@angular/material'; +import { debounceTime, filter } from 'rxjs/operators'; +import moment from 'moment-es6'; + +import { ApplicationInstanceModel } from '../../../app/models/application-instance.model'; +import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service'; +import { ProcessFilterCloudModel, ProcessFilterActionType, ProcessFilterProperties } from '../models/process-filter-cloud.model'; import { TranslationService } from '@alfresco/adf-core'; import { ProcessFilterCloudService } from '../services/process-filter-cloud.service'; import { ProcessFilterDialogCloudComponent } from './process-filter-dialog-cloud.component'; -import { MatDialog } from '@angular/material'; @Component({ - selector: 'adf-cloud-edit-process-filter', - templateUrl: './edit-process-filter-cloud.component.html', - styleUrls: ['./edit-process-filter-cloud.component.scss'] + selector: 'adf-cloud-edit-process-filter', + templateUrl: './edit-process-filter-cloud.component.html', + styleUrls: ['./edit-process-filter-cloud.component.scss'] }) export class EditProcessFilterCloudComponent implements OnChanges { public static ACTION_SAVE = 'SAVE'; public static ACTION_SAVE_AS = 'SAVE_AS'; public static ACTION_DELETE = 'DELETE'; + public static APPLICATION_NAME: string = 'appName'; + public static APP_RUNNING_STATUS: string = 'Running'; + public static DEFAULT_PROCESS_FILTER_PROPERTIES = ['state', 'sort', 'order']; + public FORMAT_DATE: string = 'DD/MM/YYYY'; - /** The name of the application. */ + /** The name of the application. */ @Input() appName: string; @@ -42,6 +51,16 @@ export class EditProcessFilterCloudComponent implements OnChanges { @Input() id: string; + /** List of process filter properties to display */ + @Input() + filterProperties: string[] = EditProcessFilterCloudComponent.DEFAULT_PROCESS_FILTER_PROPERTIES; // default ['state', 'sort', 'order'] + + @Input() + showFilterActions = true; + + @Input() + showTitle = true; + /** Emitted when an process instance filter property changes. */ @Output() filterChange: EventEmitter = new EventEmitter(); @@ -54,67 +73,129 @@ export class EditProcessFilterCloudComponent implements OnChanges { changedProcessFilter: ProcessFilterCloudModel; columns = [ - {key: 'id', label: 'ID'}, - {key: 'name', label: 'NAME'}, - {key: 'status', label: 'STATUS'}, - {key: 'startDate', label: 'START DATE'} - ]; - - status = [ - {label: 'ALL', value: ''}, - {label: 'RUNNING', value: 'RUNNING'}, - {label: 'COMPLETED', value: 'COMPLETED'} + { value: 'id', label: 'ID' }, + { value: 'name', label: 'NAME' }, + { value: 'status', label: 'STATUS' }, + { value: 'startDate', label: 'START DATE' } ]; - directions = ['ASC', 'DESC']; + status = [ + { label: 'ALL', value: '' }, + { label: 'RUNNING', value: 'RUNNING' }, + { label: 'COMPLETED', value: 'COMPLETED' } + ]; + + directions = [{ label: 'ASC', value: 'ASC' }, { label: 'DESC', value: 'DESC' }]; + applicationNames: any[] = []; formHasBeenChanged = false; editProcessFilterForm: FormGroup; + processFilterProperties: any[] = []; + toggleFilterActions: boolean = false; constructor( private formBuilder: FormBuilder, public dialog: MatDialog, private translateService: TranslationService, - private processFilterCloudService: ProcessFilterCloudService) {} + private processFilterCloudService: ProcessFilterCloudService, + private appsProcessCloudService: AppsProcessCloudService) { } ngOnChanges(changes: SimpleChanges) { const id = changes['id']; if (id && id.currentValue !== id.previousValue) { - this.retrieveProcessFilter(); - this.buildForm(); + this.processFilterProperties = this.createAndFilterProperties(); + this.buildForm(this.processFilterProperties); } } /** * Build process filter edit form */ - buildForm() { + buildForm(processFilterProperties: ProcessFilterProperties[]) { this.formHasBeenChanged = false; - this.editProcessFilterForm = this.formBuilder.group({ - state: this.processFilter.state ? this.processFilter.state : '', - sort: this.processFilter.sort, - order: this.processFilter.order, - processDefinitionId: this.processFilter.processDefinitionId, - appName: this.processFilter.appName - }); + this.editProcessFilterForm = this.formBuilder.group(this.getFormControlsConfig(processFilterProperties)); this.onFilterChange(); } + getFormControlsConfig(processFilterProperties: ProcessFilterProperties[]): any { + const properties = processFilterProperties.map((property: ProcessFilterProperties) => { + return { [property.key]: property.value }; + }); + return properties.reduce(((result, current) => Object.assign(result, current)), {}); + } + /** * Return process instance filter by application name and filter id */ - retrieveProcessFilter() { - this.processFilter = this.processFilterCloudService.getProcessFilterById(this.appName, this.id); + retrieveProcessFilter(): ProcessFilterCloudModel { + return new ProcessFilterCloudModel(this.processFilterCloudService.getProcessFilterById(this.appName, this.id)); } /** * Check process instance filter changes */ onFilterChange() { - this.editProcessFilterForm.valueChanges.subscribe((formValues: ProcessFilterCloudModel) => { - this.changedProcessFilter = new ProcessFilterCloudModel(Object.assign({}, this.processFilter, formValues)); - this.formHasBeenChanged = !this.compareFilters(this.changedProcessFilter, this.processFilter); - this.filterChange.emit(this.changedProcessFilter); - }); + this.editProcessFilterForm.valueChanges + .pipe(debounceTime(500), filter(() => this.isFormValid())) + .subscribe((formValues: ProcessFilterCloudModel) => { + this.changedProcessFilter = new ProcessFilterCloudModel(Object.assign({}, this.processFilter, formValues)); + this.formHasBeenChanged = !this.compareFilters(this.changedProcessFilter, this.processFilter); + this.filterChange.emit(this.changedProcessFilter); + }); + } + + createAndFilterProperties() { + this.checkMandatoryFilterProperties(); + if (this.checkForApplicationNameProperty()) { + this.getRunningApplications(); + } + this.processFilter = this.retrieveProcessFilter(); + const defaultProperties = this.createProcessFilterProperties(this.processFilter); + return defaultProperties.filter((filterProperty: ProcessFilterProperties) => this.isValidProperty(this.filterProperties, filterProperty)); + } + + checkMandatoryFilterProperties() { + if (this.filterProperties === undefined || this.filterProperties.length === 0) { + this.filterProperties = EditProcessFilterCloudComponent.DEFAULT_PROCESS_FILTER_PROPERTIES; + } + } + + checkForApplicationNameProperty(): boolean { + return this.filterProperties ? this.filterProperties.indexOf(EditProcessFilterCloudComponent.APPLICATION_NAME) >= 0 : false; + } + + private isValidProperty(filterProperties: string[], filterProperty: ProcessFilterProperties): boolean { + return filterProperties ? filterProperties.indexOf(filterProperty.key) >= 0 : true; + } + + isFormValid(): boolean { + return this.editProcessFilterForm.valid; + } + + getPropertyController(property: ProcessFilterProperties): AbstractControl { + return this.editProcessFilterForm.get(property.key); + } + + onDateChanged(newDateValue: any, dateProperty: ProcessFilterProperties) { + if (newDateValue) { + let momentDate; + + if (typeof newDateValue === 'string') { + momentDate = moment(newDateValue, this.FORMAT_DATE, true); + } else { + momentDate = newDateValue; + } + + if (momentDate.isValid()) { + this.getPropertyController(dateProperty).setValue(momentDate.toDate()); + this.getPropertyController(dateProperty).setErrors(null); + } else { + this.getPropertyController(dateProperty).setErrors({ invalid: true }); + } + } + } + + hasError(property: ProcessFilterProperties): boolean { + return this.getPropertyController(property).errors && this.getPropertyController(property).errors.invalid; } /** @@ -125,12 +206,24 @@ export class EditProcessFilterCloudComponent implements OnChanges { return JSON.stringify(editedQuery).toLowerCase() === JSON.stringify(currentQuery).toLowerCase(); } + getRunningApplications() { + this.appsProcessCloudService.getDeployedApplicationsByStatus(EditProcessFilterCloudComponent.APP_RUNNING_STATUS) + .subscribe((applications: ApplicationInstanceModel[]) => { + if (applications && applications.length > 0) { + applications.map((application) => { + this.applicationNames.push({ label: application.name, value: application.name }); + }); + } + }); + } + /** * Save a process instance filter */ onSave() { this.processFilterCloudService.updateFilter(this.changedProcessFilter); - this.action.emit({actionType: EditProcessFilterCloudComponent.ACTION_SAVE, id: this.changedProcessFilter.id}); + this.action.emit({ actionType: EditProcessFilterCloudComponent.ACTION_SAVE, filter: this.changedProcessFilter }); + this.formHasBeenChanged = this.compareFilters(this.changedProcessFilter, this.processFilter); } /** @@ -138,7 +231,7 @@ export class EditProcessFilterCloudComponent implements OnChanges { */ onDelete() { this.processFilterCloudService.deleteFilter(this.processFilter); - this.action.emit({actionType: EditProcessFilterCloudComponent.ACTION_DELETE, id: this.processFilter.id}); + this.action.emit({ actionType: EditProcessFilterCloudComponent.ACTION_DELETE, filter: this.processFilter }); } /** @@ -152,19 +245,19 @@ export class EditProcessFilterCloudComponent implements OnChanges { height: 'auto', minWidth: '30%' }); - dialogRef.afterClosed().subscribe( (result) => { + dialogRef.afterClosed().subscribe((result) => { if (result && result.action === ProcessFilterDialogCloudComponent.ACTION_SAVE) { const filterId = Math.random().toString(36).substr(2, 9); const filterKey = this.getSanitizeFilterName(result.name); const newFilter = { - name: result.name, - icon: result.icon, - id: filterId, - key: 'custom-' + filterKey - }; - const filter = Object.assign({}, this.changedProcessFilter, newFilter); - this.processFilterCloudService.addFilter(filter); - this.action.emit({actionType: EditProcessFilterCloudComponent.ACTION_SAVE_AS, id: filter.id}); + name: result.name, + icon: result.icon, + id: filterId, + key: 'custom-' + filterKey + }; + const resultFilter: ProcessFilterCloudModel = Object.assign({}, this.changedProcessFilter, newFilter); + this.processFilterCloudService.addFilter(resultFilter); + this.action.emit({ actionType: EditProcessFilterCloudComponent.ACTION_SAVE_AS, filter: resultFilter }); } }); } @@ -186,4 +279,115 @@ export class EditProcessFilterCloudComponent implements OnChanges { const regExt = new RegExp(' ', 'g'); return name.replace(regExt, '-'); } + + showActions(): boolean { + return this.showFilterActions; + } + + onExpand(event: any) { + this.toggleFilterActions = true; + } + + onClose(event: any) { + this.toggleFilterActions = false; + } + + isDateType(property: ProcessFilterProperties): boolean { + return property.type === 'date'; + } + + isSelectType(property: ProcessFilterProperties): boolean { + return property.type === 'select'; + } + + isTextType(property: ProcessFilterProperties): boolean { + return property.type === 'text'; + } + + createProcessFilterProperties(currentProcessFilter: ProcessFilterCloudModel): ProcessFilterProperties[] { + return [ + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.APP_NAME', + type: 'select', + key: 'appName', + value: currentProcessFilter.appName || '', + options: this.applicationNames + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.INITIATOR', + type: 'text', + key: 'initiator', + value: currentProcessFilter.initiator || '' + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.STATUS', + type: 'select', + key: 'state', + value: currentProcessFilter.state || this.status[0].value, + options: this.status + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.SORT', + type: 'select', + key: 'sort', + value: currentProcessFilter.sort || this.columns[0].value, + options: this.columns + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.DIRECTION', + type: 'select', + key: 'order', + value: currentProcessFilter.order || this.directions[0].value, + options: this.directions + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.PROCESS_NAME', + type: 'text', + key: 'processName', + value: currentProcessFilter.processName || '' + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.PROCESS_DEF_ID', + type: 'text', + key: 'processDefinitionId', + value: currentProcessFilter.processDefinitionId || '' + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.PROCESS_DEF_KEY', + type: 'text', + key: 'processDefinitionKey', + value: currentProcessFilter.processDefinitionKey || '' + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.PROCESS_INS_ID', + type: 'text', + key: 'processInstanceId', + value: '' + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.START_DATE', + type: 'date', + key: 'startDate', + value: '' + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.LAST_MODIFIED', + type: 'date', + key: 'lastModified', + value: '' + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.LAST_MODIFIED_DATE_FORM', + type: 'date', + key: 'lastModifiedFrom', + value: '' + }), + new ProcessFilterProperties({ + label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.LAST_MODIFIED_TO', + type: 'date', + key: 'lastModifiedTo', + value: '' + }) + ]; + } } diff --git a/lib/process-services-cloud/src/lib/process/process-filters/models/process-filter-cloud.model.ts b/lib/process-services-cloud/src/lib/process/process-filters/models/process-filter-cloud.model.ts index 2e77410b4e..86f1f0c0b2 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/models/process-filter-cloud.model.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/models/process-filter-cloud.model.ts @@ -21,11 +21,19 @@ export class ProcessFilterCloudModel { key: string; icon: string; index: number; - processDefinitionId: string; appName: string; + processName: string; + initiator: string; state: string; sort: string; order: string; + processDefinitionId: string; + processDefinitionKey: string; + processInstanceId: string; + startDate: Date; + lastModified: Date; + lastModifiedTo: Date; + lastModifiedFrom: Date; constructor(obj?: any) { if (obj) { @@ -35,15 +43,46 @@ export class ProcessFilterCloudModel { this.icon = obj.icon || null; this.index = obj.index || null; this.appName = obj.appName || null; - this.processDefinitionId = obj.processDefinitionId || null; + this.processName = obj.processName || null; + this.initiator = obj.initiator || null; this.state = obj.state || null; this.sort = obj.sort || null; this.order = obj.order || null; + this.processDefinitionId = obj.processDefinitionId || null; + this.processDefinitionKey = obj.processDefinitionKey || null; + this.processInstanceId = obj.processInstanceId || null; + this.startDate = obj.startDate || null; + this.lastModified = obj.lastModified || null; + this.lastModifiedTo = obj.lastModifiedTo || null; + this.lastModifiedFrom = obj.lastModifiedFrom || null; } } } export interface ProcessFilterActionType { actionType: string; - id: string; + filter: ProcessFilterCloudModel; +} + +export interface ProcessFilterOptions { + label?: string; + value?: string; +} + +export class ProcessFilterProperties { + label: string; + type: string; + value: string; + key: string; + options: ProcessFilterOptions[]; + + constructor(obj?: any) { + if (obj) { + this.label = obj.label || null; + this.type = obj.type || null; + this.value = obj.value || ''; + this.key = obj.key || null; + this.options = obj.options || null; + } + } } diff --git a/lib/process-services-cloud/src/lib/process/process-filters/process-filters-cloud.module.ts b/lib/process-services-cloud/src/lib/process/process-filters/process-filters-cloud.module.ts index 0c0595b243..b7d8105547 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/process-filters-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/process-filters-cloud.module.ts @@ -27,6 +27,7 @@ import { ProcessFilterCloudService } from './services/process-filter-cloud.servi import { HttpClientModule } from '@angular/common/http'; import { EditProcessFilterCloudComponent } from './components/edit-process-filter-cloud.component'; import { ProcessFilterDialogCloudComponent } from './components/process-filter-dialog-cloud.component'; +import { AppListCloudModule } from './../../app/app-list-cloud.module'; @NgModule({ imports: [ FormsModule, @@ -40,7 +41,8 @@ import { ProcessFilterDialogCloudComponent } from './components/process-filter-d } }), FlexLayoutModule, - MaterialModule + MaterialModule, + AppListCloudModule ], declarations: [ProcessFiltersCloudComponent, EditProcessFilterCloudComponent, ProcessFilterDialogCloudComponent], exports: [ProcessFiltersCloudComponent, EditProcessFilterCloudComponent, ProcessFilterDialogCloudComponent], diff --git a/lib/process-services-cloud/src/lib/styles/_index.scss b/lib/process-services-cloud/src/lib/styles/_index.scss index 96dbccb9bd..1558379abe 100644 --- a/lib/process-services-cloud/src/lib/styles/_index.scss +++ b/lib/process-services-cloud/src/lib/styles/_index.scss @@ -4,6 +4,7 @@ @import './../task/task-filters/components/edit-task-filter-cloud.component.scss'; @import './../process/process-list/components/process-list-cloud.component.scss'; @import './../task/start-task/components/start-task-cloud.component.scss'; +@import './../process/process-filters/components/edit-process-filter-cloud.component.scss'; @import './../task/start-task/components/people-cloud/people-cloud.component.scss'; @import './../group/components/group-cloud.component'; @@ -13,6 +14,7 @@ @include adf-cloud-app-details-theme($theme); @include adf-cloud-task-filters-theme($theme); @include adf-cloud-edit-task-filters-theme($theme); + @include adf-cloud-edit-process-filter-theme($theme); @include adf-process-filters-cloud-theme($theme); @include adf-start-task-cloud-theme($theme); @include adf-cloud-people-theme($theme); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.html index 2762237df8..de51e41fec 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.html +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.html @@ -4,8 +4,8 @@ {{taskFilter.name | translate}} {{ 'ADF_CLOUD_EDIT_TASK_FILTER.TITLE' | translate}} -
- +
+ @@ -22,24 +22,24 @@
- + - + {{ propertyOption.label }} - + - + {{taskFilterProperty.label | translate}} { let fixture: ComponentFixture; let dialog: MatDialog; let getTaskFilterSpy: jasmine.Spy; - let getDeployedApplicationsByStatusSpy: jasmine.Spy; + let getRunningApplicationsSpy: jasmine.Spy; let fakeFilter = new TaskFilterCloudModel({ name: 'FakeInvolvedTasks', icon: 'adjust', - id: 10, + id: 'mock-task-filter-id', state: 'CREATED', - appName: 'app-name', + appName: 'mock-app-name', processDefinitionId: 'process-def-id', assignment: 'fake-involved', order: 'ASC', @@ -70,7 +70,8 @@ describe('EditTaskFilterCloudComponent', () => { name: 'fake-name' }); }}); getTaskFilterSpy = spyOn(service, 'getTaskFilterById').and.returnValue(fakeFilter); - getDeployedApplicationsByStatusSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance)); + getRunningApplicationsSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance)); + fixture.detectChanges(); }); it('should create EditTaskFilterCloudComponent', () => { @@ -78,10 +79,11 @@ describe('EditTaskFilterCloudComponent', () => { }); it('should fetch task filter by taskId', async(() => { - let change = new SimpleChange(undefined, '10', true); - component.ngOnChanges({ 'id': change }); + let taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIDchange}); fixture.detectChanges(); fixture.whenStable().then(() => { + expect(getTaskFilterSpy).toHaveBeenCalled(); expect(component.taskFilter.name).toEqual('FakeInvolvedTasks'); expect(component.taskFilter.icon).toEqual('adjust'); expect(component.taskFilter.state).toEqual('CREATED'); @@ -91,8 +93,8 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should display filter name as title', () => { - let change = new SimpleChange(undefined, '10', true); - component.ngOnChanges({ 'id': change }); + let taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIDchange}); fixture.detectChanges(); const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-title-id'); const subTitle = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-sub-title-id'); @@ -105,8 +107,8 @@ describe('EditTaskFilterCloudComponent', () => { describe('EditTaskFilter form', () => { beforeEach(() => { - let change = new SimpleChange(undefined, '10', true); - component.ngOnChanges({ 'id': change }); + let taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({'id': taskFilterIDchange}); fixture.detectChanges(); }); @@ -135,7 +137,7 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should disable save button if the task filter is not changed', async(() => { - component.showFilterActions = true; + component.toggleFilterActions = true; let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); fixture.detectChanges(); @@ -146,7 +148,7 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should disable saveAs button if the task filter is not changed', async(() => { - component.showFilterActions = true; + component.toggleFilterActions = true; let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); fixture.detectChanges(); @@ -157,7 +159,7 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should enable delete button by default', async(() => { - component.showFilterActions = true; + component.toggleFilterActions = true; fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); @@ -230,27 +232,15 @@ describe('EditTaskFilterCloudComponent', () => { }); })); - it('should able to fetch running applications', async(() => { - component.appName = 'mock-app-name'; - component.filterProperties = ['appName', 'processInstanceId', 'dueBefore']; - let change = new SimpleChange(undefined, 'mock-task-id', true); - component.ngOnChanges({ 'id': change }); - const appController = component.editTaskFilterForm.get('appName'); - fixture.detectChanges(); - fixture.whenStable().then(() => { - expect(appController).toBeDefined(); - expect(appController.value).toBe('mock-app-name' ); - expect(getDeployedApplicationsByStatusSpy).toHaveBeenCalled(); - }); - })); - it('should able to build a editTaskFilter form with default properties if input is empty', async(() => { + let taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIDchange}); component.filterProperties = []; fixture.detectChanges(); + const stateController = component.editTaskFilterForm.get('state'); + const sortController = component.editTaskFilterForm.get('sort'); + const orderController = component.editTaskFilterForm.get('order'); fixture.whenStable().then(() => { - const stateController = component.editTaskFilterForm.get('state'); - const sortController = component.editTaskFilterForm.get('sort'); - const orderController = component.editTaskFilterForm.get('order'); fixture.detectChanges(); expect(component.taskFilterProperties).toBeDefined(); expect(component.taskFilterProperties.length).toBe(4); @@ -263,28 +253,46 @@ describe('EditTaskFilterCloudComponent', () => { expect(orderController.value).toBe('ASC'); }); })); + }); + + describe('Task filterProperties', () => { + + beforeEach(() => { + component.filterProperties = ['appName', 'processInstanceId', 'priority']; + }); + + it('should able to fetch running applications when appName property defined in the input', async(() => { + fixture.detectChanges(); + let taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIDchange}); + const appController = component.editTaskFilterForm.get('appName'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(getRunningApplicationsSpy).toHaveBeenCalled(); + expect(appController).toBeDefined(); + expect(appController.value).toBe('mock-app-name'); + }); + })); it('should able to build a editTaskFilter form with given input properties', async(() => { - getTaskFilterSpy.and.returnValue({ processInstanceId: 'process-instance-id', priority: '12' }); - component.appName = 'mock-app-name'; - component.filterProperties = ['appName', 'processInstanceId', 'priority']; - let change = new SimpleChange(undefined, 'mock-task-id', true); - component.ngOnChanges({ 'id': change }); + fixture.detectChanges(); + getTaskFilterSpy.and.returnValue({ appName: 'mock-app-name', processInstanceId: 'process-instance-id', priority: '12' }); + let taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIDchange}); + fixture.detectChanges(); const appController = component.editTaskFilterForm.get('appName'); - const propertyController = component.editTaskFilterForm.get('priority'); + const priorityController = component.editTaskFilterForm.get('priority'); const processInsIdController = component.editTaskFilterForm.get('processInstanceId'); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); - expect(getDeployedApplicationsByStatusSpy).toHaveBeenCalled(); expect(component.taskFilterProperties).toBeDefined(); expect(component.editTaskFilterForm).toBeDefined(); expect(component.taskFilterProperties.length).toBe(3); expect(appController).toBeDefined(); - expect(propertyController.value).toBe('12'); + expect(priorityController.value).toBe('12'); expect(processInsIdController).toBeDefined(); expect(appController.value).toBe('mock-app-name'); - expect(processInsIdController.value).toBe('process-instance-id'); }); })); }); @@ -292,13 +300,14 @@ describe('EditTaskFilterCloudComponent', () => { describe('edit filter actions', () => { beforeEach(() => { - let change = new SimpleChange(undefined, '10', true); - component.ngOnChanges({ 'id': change }); - component.filterProperties = ['state']; + let taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIDchange}); + fixture.detectChanges(); + }); it('should emit save event and save the filter on click save button', async(() => { - component.showFilterActions = true; + component.toggleFilterActions = true; const saveFilterSpy = spyOn(service, 'updateFilter').and.returnValue(fakeFilter); let saveSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); @@ -321,7 +330,7 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should emit delete event and delete the filter on click of delete button', async(() => { - component.showFilterActions = true; + component.toggleFilterActions = true; const deleteFilterSpy = spyOn(service, 'deleteFilter').and.callThrough(); let deleteSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); @@ -341,7 +350,7 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should emit saveAs event and add filter on click saveAs button', async(() => { - component.showFilterActions = true; + component.toggleFilterActions = true; const saveAsFilterSpy = spyOn(service, 'addFilter').and.callThrough(); let saveAsSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts index 4870a1296b..c944f72e80 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts @@ -16,21 +16,20 @@ */ import { Component, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core'; -import { AbstractControl , FormGroup, FormBuilder } from '@angular/forms'; -import { TaskFilterCloudModel, FilterActionType, TaskFilterProperties, FilterOptions } from './../models/filter-cloud.model'; +import { AbstractControl, FormGroup, FormBuilder } from '@angular/forms'; +import { TaskFilterCloudModel, FilterActionType, TaskFilterProperties } from './../models/filter-cloud.model'; import { TaskFilterCloudService } from '../services/task-filter-cloud.service'; import { MatDialog } from '@angular/material'; import { TaskFilterDialogCloudComponent } from './task-filter-dialog-cloud.component'; import { TranslationService } from '@alfresco/adf-core'; -import { debounceTime, map, filter } from 'rxjs/operators'; -import { of, Observable } from 'rxjs'; +import { debounceTime, filter } from 'rxjs/operators'; import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service'; import { ApplicationInstanceModel } from '../../../app/models/application-instance.model'; import moment from 'moment-es6'; @Component({ - selector: 'adf-cloud-edit-task-filter', - templateUrl: './edit-task-filter-cloud.component.html', + selector: 'adf-cloud-edit-task-filter', + templateUrl: './edit-task-filter-cloud.component.html', styleUrls: ['./edit-task-filter-cloud.component.scss'] }) export class EditTaskFilterCloudComponent implements OnChanges { @@ -40,6 +39,7 @@ export class EditTaskFilterCloudComponent implements OnChanges { public static ACTION_DELETE = 'DELETE'; public static APP_RUNNING_STATUS: string = 'RUNNING'; public static MIN_VALUE = 1; + public static APPLICATION_NAME: string = 'appName'; public static DEFAULT_TASK_FILTER_PROPERTIES = ['state', 'assignment', 'sort', 'order']; public FORMAT_DATE: string = 'DD/MM/YYYY'; @@ -57,7 +57,7 @@ export class EditTaskFilterCloudComponent implements OnChanges { /** Toggles the filter actions. */ @Input() - toggleFilterActions = true; + showFilterActions = true; /** Toggles the title. */ @Input() @@ -75,21 +75,21 @@ export class EditTaskFilterCloudComponent implements OnChanges { changedTaskFilter: TaskFilterCloudModel; columns = [ - {value: 'id', label: 'ID'}, - {value: 'name', label: 'NAME'}, - {value: 'createdDate', label: 'CREATED DATE'}, - {value: 'priority', label: 'PRIORITY'}, - {value: 'processDefinitionId', label: 'PROCESS DEFINITION ID'} - ]; + { value: 'id', label: 'ID' }, + { value: 'name', label: 'NAME' }, + { value: 'createdDate', label: 'Created Date' }, + { value: 'priority', label: 'PRIORITY' }, + { value: 'processDefinitionId', label: 'PROCESS DEFINITION ID' } + ]; status = [ - {label: 'ALL', value: ''}, - {label: 'CREATED', value: 'CREATED'}, - {label: 'CANCELLED', value: 'CANCELLED'}, - {label: 'ASSIGNED', value: 'ASSIGNED'}, - {label: 'SUSPENDED', value: 'SUSPENDED'}, - {label: 'COMPLETED', value: 'COMPLETED'}, - {label: 'DELETED', value: 'DELETED'} + { label: 'ALL', value: '' }, + { label: 'CREATED', value: 'CREATED' }, + { label: 'CANCELLED', value: 'CANCELLED' }, + { label: 'ASSIGNED', value: 'ASSIGNED' }, + { label: 'SUSPENDED', value: 'SUSPENDED' }, + { label: 'COMPLETED', value: 'COMPLETED' }, + { label: 'DELETED', value: 'DELETED' } ]; directions = [ @@ -97,10 +97,11 @@ export class EditTaskFilterCloudComponent implements OnChanges { { label: 'DESC', value: 'DESC' } ]; + applicationNames: any[] = []; formHasBeenChanged = false; editTaskFilterForm: FormGroup; taskFilterProperties: any[] = []; - showFilterActions: boolean = false; + toggleFilterActions: boolean = false; constructor( private formBuilder: FormBuilder, @@ -108,19 +109,18 @@ export class EditTaskFilterCloudComponent implements OnChanges { private translateService: TranslationService, private taskFilterCloudService: TaskFilterCloudService, private appsProcessCloudService: AppsProcessCloudService) { - } + } ngOnChanges(changes: SimpleChanges) { const id = changes['id']; if (id && id.currentValue !== id.previousValue) { - this.retrieveTaskFilter(); - this.initTaskFilterProperties(this.taskFilter); + this.taskFilterProperties = this.createAndFilterProperties(); this.buildForm(this.taskFilterProperties); } } - retrieveTaskFilter() { - this.taskFilter = new TaskFilterCloudModel(this.taskFilterCloudService.getTaskFilterById(this.appName, this.id)); + retrieveTaskFilter(): TaskFilterCloudModel { + return new TaskFilterCloudModel(this.taskFilterCloudService.getTaskFilterById(this.appName, this.id)); } buildForm(taskFilterProperties: TaskFilterProperties[]) { @@ -141,21 +141,28 @@ export class EditTaskFilterCloudComponent implements OnChanges { */ onFilterChange() { this.editTaskFilterForm.valueChanges - .pipe(debounceTime(500), - filter(() => this.isFormValid())) + .pipe(debounceTime(500), + filter(() => this.isFormValid())) .subscribe((formValues: TaskFilterCloudModel) => { this.changedTaskFilter = new TaskFilterCloudModel(Object.assign({}, this.taskFilter, formValues)); this.formHasBeenChanged = !this.compareFilters(this.changedTaskFilter, this.taskFilter); this.filterChange.emit(this.changedTaskFilter); - }); + }); } - initTaskFilterProperties(taskFilter: TaskFilterCloudModel) { - if (this.filterProperties && this.filterProperties.length > 0) { - const defaultProperties = this.defaultTaskFilterProperties(taskFilter); - this.taskFilterProperties = defaultProperties.filter((filterProperty) => this.isValidProperty(this.filterProperties, filterProperty)); - } else { - this.taskFilterProperties = EditTaskFilterCloudComponent.DEFAULT_TASK_FILTER_PROPERTIES; + createAndFilterProperties(): TaskFilterProperties[] { + this.checkMandatoryFilterProperties(); + if (this.checkForApplicationNameProperty()) { + this.getRunningApplications(); + } + this.taskFilter = this.retrieveTaskFilter(); + const defaultProperties = this.createTaskFilterProperties(this.taskFilter); + return defaultProperties.filter((filterProperty: TaskFilterProperties) => this.isValidProperty(this.filterProperties, filterProperty)); + } + + checkMandatoryFilterProperties() { + if (this.filterProperties === undefined || this.filterProperties.length === 0) { + this.filterProperties = EditTaskFilterCloudComponent.DEFAULT_TASK_FILTER_PROPERTIES; } } @@ -163,6 +170,10 @@ export class EditTaskFilterCloudComponent implements OnChanges { return filterProperties ? filterProperties.indexOf(filterProperty.key) >= 0 : true; } + checkForApplicationNameProperty(): boolean { + return this.filterProperties ? this.filterProperties.indexOf(EditTaskFilterCloudComponent.APPLICATION_NAME) >= 0 : false; + } + isFormValid(): boolean { return this.editTaskFilterForm.valid; } @@ -200,17 +211,15 @@ export class EditTaskFilterCloudComponent implements OnChanges { return JSON.stringify(editedQuery).toLowerCase() === JSON.stringify(currentQuery).toLowerCase(); } - getRunningApplications(): Observable { - return this.appsProcessCloudService.getDeployedApplicationsByStatus(EditTaskFilterCloudComponent.APP_RUNNING_STATUS).pipe( - map((applications: ApplicationInstanceModel[]) => { + getRunningApplications() { + this.appsProcessCloudService.getDeployedApplicationsByStatus(EditTaskFilterCloudComponent.APP_RUNNING_STATUS) + .subscribe((applications: ApplicationInstanceModel[]) => { if (applications && applications.length > 0) { - let options: FilterOptions[] = []; applications.map((application) => { - options.push({ label: application.name, value: application.name }); + this.applicationNames.push({ label: application.name, value: application.name }); }); - return options; } - })); + }); } onSave() { @@ -232,16 +241,16 @@ export class EditTaskFilterCloudComponent implements OnChanges { height: 'auto', minWidth: '30%' }); - dialogRef.afterClosed().subscribe( (result) => { + dialogRef.afterClosed().subscribe((result) => { if (result && result.action === TaskFilterDialogCloudComponent.ACTION_SAVE) { const filterId = Math.random().toString(36).substr(2, 9); const filterKey = this.getSanitizeFilterName(result.name); const newFilter = { - name: result.name, - icon: result.icon, - id: filterId, - key: 'custom-' + filterKey - }; + name: result.name, + icon: result.icon, + id: filterId, + key: 'custom-' + filterKey + }; const resultFilter = Object.assign({}, this.changedTaskFilter, newFilter); this.taskFilterCloudService.addFilter(resultFilter); this.action.emit({actionType: EditTaskFilterCloudComponent.ACTION_SAVE_AS, filter: resultFilter}); @@ -260,16 +269,16 @@ export class EditTaskFilterCloudComponent implements OnChanges { return name.replace(regExt, '-'); } - toggleActions(): boolean { - return this.toggleFilterActions; + showActions(): boolean { + return this.showFilterActions; } onExpand(event: any) { - this.showFilterActions = true; + this.toggleFilterActions = true; } onClose(event: any) { - this.showFilterActions = false; + this.toggleFilterActions = false; } isDateType(property: TaskFilterProperties): boolean { @@ -284,21 +293,21 @@ export class EditTaskFilterCloudComponent implements OnChanges { return property.type === 'text'; } - defaultTaskFilterProperties(currentTaskFilter: TaskFilterCloudModel): TaskFilterProperties[] { + createTaskFilterProperties(currentTaskFilter: TaskFilterCloudModel): TaskFilterProperties[] { return [ new TaskFilterProperties({ label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.APP_NAME', type: 'select', key: 'appName', - value: this.appName || '', - options: this.getRunningApplications() + value: currentTaskFilter.appName || '', + options: this.applicationNames }), new TaskFilterProperties({ label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.STATUS', type: 'select', key: 'state', value: currentTaskFilter.state || this.status[0].value, - options: of(this.status) + options: this.status }), new TaskFilterProperties({ label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.ASSIGNMENT', @@ -317,14 +326,14 @@ export class EditTaskFilterCloudComponent implements OnChanges { type: 'select', key: 'sort', value: currentTaskFilter.sort || this.columns[0].value, - options: of(this.columns) + options: this.columns }), new TaskFilterProperties({ label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DIRECTION', type: 'select', key: 'order', value: currentTaskFilter.order || this.directions[0].value, - options: of(this.directions) + options: this.directions }), new TaskFilterProperties({ label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PROCESS_INSTANCE_ID', diff --git a/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts b/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts index ed880ec310..5af3ee1899 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts @@ -15,8 +15,6 @@ * limitations under the License. */ -import { Observable } from 'rxjs'; - export class TaskFilterCloudModel { id: string; name: string; @@ -108,10 +106,10 @@ export interface FilterOptions { export class TaskFilterProperties { label: string; - type: string; // text|date|select + type: string; value: string; key: string; - options$: Observable; + options: FilterOptions[]; constructor(obj?: any) { if (obj) { @@ -119,7 +117,7 @@ export class TaskFilterProperties { this.type = obj.type || null; this.value = obj.value || null; this.key = obj.key || null; - this.options$ = obj.options || null; + this.options = obj.options || null; } } }