From 08e9a50c6493d25d30cc348f60dac7ca135189a3 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Thu, 8 Aug 2013 23:20:01 +0000 Subject: [PATCH 001/587] Rebranding unsupportedModules to externalModules SVN r27812 |2013-08-08 23:20:01 +0000 --- .gitattributes | 47 + PepDB.iml | 15 + build.xml | 67 + lib/jstl.jar | Bin 0 -> 20682 bytes lib/standard.jar | Bin 0 -> 393259 bytes module.properties | 1 + .../dbscripts/postgresql/pepdb-0.00-0.02.sql | 228 +++ .../dbscripts/postgresql/pepdb-0.00-2.21.sql | 226 +++ .../dbscripts/postgresql/pepdb-0.02-0.05.sql | 90 ++ .../dbscripts/postgresql/pepdb-0.05-0.08.sql | 23 + .../dbscripts/postgresql/pepdb-0.08-1.00.sql | 27 + .../dbscripts/postgresql/pepdb-1.00-1.50.sql | 5 + .../dbscripts/postgresql/pepdb-1.50-1.75.sql | 29 + .../dbscripts/postgresql/pepdb-1.75-2.00.sql | 12 + .../dbscripts/postgresql/pepdb-2.00-2.10.sql | 4 + .../dbscripts/postgresql/pepdb-2.10-2.20.sql | 8 + .../dbscripts/postgresql/pepdb-2.20-2.21.sql | 33 + .../dbscripts/postgresql/pepdb-create.sql | 11 + .../dbscripts/postgresql/pepdb-drop.sql | 7 + .../dbscripts/sqlserver/pepdb-0.00-0.01.sql | 19 + resources/schemas/pepdb.xml | 590 ++++++++ .../atlas/pepdb/PepDBBaseController.java | 672 +++++++++ .../atlas/pepdb/PepDBContainerListener.java | 39 + .../scharp/atlas/pepdb/PepDBController.java | 1299 +++++++++++++++++ src/org/scharp/atlas/pepdb/PepDBManager.java | 393 +++++ src/org/scharp/atlas/pepdb/PepDBModule.java | 79 + src/org/scharp/atlas/pepdb/PepDBSchema.java | 162 ++ .../scharp/atlas/pepdb/PeptideImporter.java | 351 +++++ src/org/scharp/atlas/pepdb/PoolImporter.java | 403 +++++ .../scharp/atlas/pepdb/model/GroupType.java | 34 + .../atlas/pepdb/model/OptimalEpitopeList.java | 34 + src/org/scharp/atlas/pepdb/model/Parent.java | 34 + .../scharp/atlas/pepdb/model/Pathogen.java | 32 + .../atlas/pepdb/model/PeptideGroup.java | 84 ++ .../scharp/atlas/pepdb/model/PeptidePool.java | 125 ++ .../pepdb/model/PeptidePoolAssignment.java | 47 + .../scharp/atlas/pepdb/model/Peptides.java | 237 +++ .../scharp/atlas/pepdb/model/PoolType.java | 34 + .../atlas/pepdb/model/ProteinCategory.java | 44 + src/org/scharp/atlas/pepdb/model/Source.java | 108 ++ .../scharp/atlas/pepdb/view/PepDBWebPart.java | 35 + .../atlas/pepdb/view/importPeptides.jsp | 48 + .../scharp/atlas/pepdb/view/importPools.jsp | 45 + src/org/scharp/atlas/pepdb/view/index.jsp | 48 + .../scharp/atlas/pepdb/view/pepDBWebPart.jsp | 11 + .../atlas/pepdb/view/peptideDetails.jsp | 58 + .../atlas/pepdb/view/peptideGroupSelect.jsp | 133 ++ 47 files changed, 6031 insertions(+) create mode 100644 .gitattributes create mode 100644 PepDB.iml create mode 100644 build.xml create mode 100644 lib/jstl.jar create mode 100644 lib/standard.jar create mode 100644 module.properties create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-0.00-0.02.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-0.00-2.21.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-0.02-0.05.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-0.05-0.08.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-0.08-1.00.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-1.00-1.50.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-1.50-1.75.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-1.75-2.00.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-2.00-2.10.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-2.10-2.20.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-2.20-2.21.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-create.sql create mode 100644 resources/schemas/dbscripts/postgresql/pepdb-drop.sql create mode 100644 resources/schemas/dbscripts/sqlserver/pepdb-0.00-0.01.sql create mode 100644 resources/schemas/pepdb.xml create mode 100644 src/org/scharp/atlas/pepdb/PepDBBaseController.java create mode 100644 src/org/scharp/atlas/pepdb/PepDBContainerListener.java create mode 100644 src/org/scharp/atlas/pepdb/PepDBController.java create mode 100644 src/org/scharp/atlas/pepdb/PepDBManager.java create mode 100644 src/org/scharp/atlas/pepdb/PepDBModule.java create mode 100644 src/org/scharp/atlas/pepdb/PepDBSchema.java create mode 100644 src/org/scharp/atlas/pepdb/PeptideImporter.java create mode 100644 src/org/scharp/atlas/pepdb/PoolImporter.java create mode 100644 src/org/scharp/atlas/pepdb/model/GroupType.java create mode 100644 src/org/scharp/atlas/pepdb/model/OptimalEpitopeList.java create mode 100644 src/org/scharp/atlas/pepdb/model/Parent.java create mode 100644 src/org/scharp/atlas/pepdb/model/Pathogen.java create mode 100644 src/org/scharp/atlas/pepdb/model/PeptideGroup.java create mode 100644 src/org/scharp/atlas/pepdb/model/PeptidePool.java create mode 100644 src/org/scharp/atlas/pepdb/model/PeptidePoolAssignment.java create mode 100644 src/org/scharp/atlas/pepdb/model/Peptides.java create mode 100644 src/org/scharp/atlas/pepdb/model/PoolType.java create mode 100644 src/org/scharp/atlas/pepdb/model/ProteinCategory.java create mode 100644 src/org/scharp/atlas/pepdb/model/Source.java create mode 100644 src/org/scharp/atlas/pepdb/view/PepDBWebPart.java create mode 100644 src/org/scharp/atlas/pepdb/view/importPeptides.jsp create mode 100644 src/org/scharp/atlas/pepdb/view/importPools.jsp create mode 100644 src/org/scharp/atlas/pepdb/view/index.jsp create mode 100644 src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp create mode 100644 src/org/scharp/atlas/pepdb/view/peptideDetails.jsp create mode 100644 src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..be1312f2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,47 @@ +* text=auto !eol +/PepDB.iml -text +/build.xml -text +lib/jstl.jar -text +lib/standard.jar -text +/module.properties -text +resources/schemas/dbscripts/postgresql/pepdb-0.00-0.02.sql -text +resources/schemas/dbscripts/postgresql/pepdb-0.00-2.21.sql -text +resources/schemas/dbscripts/postgresql/pepdb-0.02-0.05.sql -text +resources/schemas/dbscripts/postgresql/pepdb-0.05-0.08.sql -text +resources/schemas/dbscripts/postgresql/pepdb-0.08-1.00.sql -text +resources/schemas/dbscripts/postgresql/pepdb-1.00-1.50.sql -text +resources/schemas/dbscripts/postgresql/pepdb-1.50-1.75.sql -text +resources/schemas/dbscripts/postgresql/pepdb-1.75-2.00.sql -text +resources/schemas/dbscripts/postgresql/pepdb-2.00-2.10.sql -text +resources/schemas/dbscripts/postgresql/pepdb-2.10-2.20.sql -text +resources/schemas/dbscripts/postgresql/pepdb-2.20-2.21.sql -text +resources/schemas/dbscripts/postgresql/pepdb-create.sql -text +resources/schemas/dbscripts/postgresql/pepdb-drop.sql -text +resources/schemas/dbscripts/sqlserver/pepdb-0.00-0.01.sql -text +resources/schemas/pepdb.xml -text +src/org/scharp/atlas/pepdb/PepDBBaseController.java -text +src/org/scharp/atlas/pepdb/PepDBContainerListener.java -text +src/org/scharp/atlas/pepdb/PepDBController.java -text +src/org/scharp/atlas/pepdb/PepDBManager.java -text +src/org/scharp/atlas/pepdb/PepDBModule.java -text +src/org/scharp/atlas/pepdb/PepDBSchema.java -text +src/org/scharp/atlas/pepdb/PeptideImporter.java -text +src/org/scharp/atlas/pepdb/PoolImporter.java -text +src/org/scharp/atlas/pepdb/model/GroupType.java -text +src/org/scharp/atlas/pepdb/model/OptimalEpitopeList.java -text +src/org/scharp/atlas/pepdb/model/Parent.java -text +src/org/scharp/atlas/pepdb/model/Pathogen.java -text +src/org/scharp/atlas/pepdb/model/PeptideGroup.java -text +src/org/scharp/atlas/pepdb/model/PeptidePool.java -text +src/org/scharp/atlas/pepdb/model/PeptidePoolAssignment.java -text +src/org/scharp/atlas/pepdb/model/Peptides.java -text +src/org/scharp/atlas/pepdb/model/PoolType.java -text +src/org/scharp/atlas/pepdb/model/ProteinCategory.java -text +src/org/scharp/atlas/pepdb/model/Source.java -text +src/org/scharp/atlas/pepdb/view/PepDBWebPart.java -text +src/org/scharp/atlas/pepdb/view/importPeptides.jsp -text +src/org/scharp/atlas/pepdb/view/importPools.jsp -text +src/org/scharp/atlas/pepdb/view/index.jsp -text +src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp -text +src/org/scharp/atlas/pepdb/view/peptideDetails.jsp -text +src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp -text diff --git a/PepDB.iml b/PepDB.iml new file mode 100644 index 00000000..27a46577 --- /dev/null +++ b/PepDB.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/build.xml b/build.xml new file mode 100644 index 00000000..161021f2 --- /dev/null +++ b/build.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/jstl.jar b/lib/jstl.jar new file mode 100644 index 0000000000000000000000000000000000000000..a02abecc8b888f09e4d1f9e4c9790dc482426d4f GIT binary patch literal 20682 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 literal 0 HcmV?d00001 diff --git a/lib/standard.jar b/lib/standard.jar new file mode 100644 index 0000000000000000000000000000000000000000..bc528acb949096eeb2e3048de1ff5b9ab12a66c5 GIT binary patch literal 393259 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 wDbZ}uEfD0{QkE2q)_s;rJwKw@ znf)b~vqMGft!r1^`<{={Gz#)ulTGv|KU?)D0k%nbTzxQe=4UA5j5*++E^oNqijI225FL4Y z&D>i94H1DcI1@B0G-cCb9%bQ)r($x)b@8OFHlxt697ryMPz42kWzmlhvH$Kg{N2^* z%J%39?Dz8xh^Sz-S2z0}5D{v?Qs}zNjp1Pkpbo=Qm`e8f-eRtFsQFt|0Mj`l`=F1O zZy8u=-OHe_QD_*9q5L@^Mqu9;;+!TpA&>y0@{3&qI(d#0h09ub^FWws5iV_*u3S^GR!qvEf5*bA@V6&@fC1 z(>>rh?rJ5q5`*(=?BuBRZ;?v120|*RdQ8q)XLU}iQ7ImA@^1_xsCN|2T^3tSs;pgKuLG?i8|ZTA@_{tVYSH%5 zr6dx@pkFO*5b2(=>VzO%abXP5K9+#UtxT#BuL5i`JL_#ui0(y_E-CF@vOE8xF$Su_ zj!1o<( zAFh~_Mer_gD>A}OB%dpthgcz0ZZSSxG#P>EDUw~g&@J;!6qnOQ+5(4ki3tu3ch@3u zbdF2x8-4r(E8Qv#hfgu5Nat8Z%H2UbU1Kbz~9uNkM1(7p9m^!#gDwk{u3o%+4`svRF` zOm82}*ZVaoz4f+%2-ClJ)m?jy`TE@0%T@{Va;Hgyv9$v(nVpx;K@Umb4@}Jj1%!6kk4(OU`-@3S=a}7z7}b7OZiG6 znLk8}Ua9jUd}UjrVN=Mg2p_-$TgDS=wvK6};%14bn_wI1$IfWR$`G0-jZ|@d>u*q1 z-W;0lSxN6%S!(I)WY(06=q#G8MGq@+(AM5$D12Sn+tXLjn`)P_Y82bR>@<4L_JDsD zYnvcWY#*#)fjps;h3bsC*_&E8xAU;T>T=Jl0tNe5(*;lI#d;0SNXS?Rl<5A4bMsn5 zNW9}j{wgR;yu;UfuYPu@k}e|Osk$B< zehfBWihF3n-1cJhCFbv96ot!cf!s3v1cq&}`xO7(3oqnI?mzp>TgrH zv}uEty?_0P8QLH`B3Yew32b`XCuXE8l$|TMubGLDEQ`kpw=VgMmD=#1;QK#~50Lv+ zlG2Pf<_-UM^legD>E7xK)F1bSDHZ?krVAxgM^{s47g0N7dlO5$|3p+2sc0*&i(&J@ zHJJtrh3yyGR?W^`58P(mk9xRqBnJj2ZXUeLXhxu`*DW0CO_nIAcXb2gt*Ni9j*J=bPf_-~AgSaMp z^{8AHe+dKY%16BpH(B{pjw|1n%IKOWpSF|fCywvV04|V$9<^(SS@I;h2J2V;S?K~} z%v3=|KKAoBEcgkkJHiOaE%hU5pQ1dYo$quA){r(TI8w0P`Z1yn$8bi&y8jCGi>1aD zQS&Nr3E?bvhzVzx@VEJ;Dwb9>wDh{X{ZEqva0yu_I%R9&>)%hza5oE2j`mxmJMj$Q zeWJ_CN~RP%k9Cb&+*nAM2fNs)&%U{un&=6K%e#e(n5&^C>{`8(eXNsyvpqZH1Ooa=`fHaJYxe6~Q5X+QWP5 z^t=%J|3!9LQ#N5?aYY-^BeM??6WbR#CCBzP7I?Bu8Id#0vvhFr$n#Z{Ku8XEog4NaM=g$=?1-gg_w-eCS!&^-SK4lGQ~N&MP70j_vl|p ztEC}PQC64ScC=uEUCHLuNB6vBtPhC`z$=m9-l8j3=Naigmj%v(7XangbH4t1&i|!i z`&Fgze^EPBwUy_8qkmv3%@yyGNOcBz(2L@sH(EeIfDalbrja0ZS)?D(|OXTwAlsTcAHrEZ%dV81Z7q0c!k2YswrTyOZVNk?2x z)MoX2|3%rcIzqjjmi7j+*<$T@01EVcWvZVjH~e_{BJc7_1x&%fpiqA$SH}WynNJ^? zR#fRH&37!kzw;7J3GHMlg@2UF|CkJFJJFt-v6IOy0%OeSBPe=hLP9gddfo{_;f#5J z^T=|kd=-6u*0Af&SbXX?#bmkk?DTpmUB3t6170?E3&}1MI{@||Q|)9Pr&Mn}vpGk6 zN|&#~+;2hL00)_2CP>wdc}F5q0wkt#Be`K=UM1#gk>pcdhpMRU+9tyghjz3d_1WVj~^^kp9i6v@m zuzJ^c8OPQ)PzNzQr5Qc|(kn21+YN_B#2x@jamGe#CDTlP>G%8hvk;Q&Z+s6U*2q)+ zyKP7^TWEEp4K#BT7IKGOe`Zf!umNTxh{(_mDSu!kj36m3i<$!+VN3d3Sea+&Di8Fy zhuvl~l-=_10W>6nSPBI*zCai7K?lrZ65ucHMbfk*3itRG@mZm-rcX^+6qZULv>^W| ztSR5Iam5QXZBoXD@b-5l`j!%vbtya@8h~`X1AU8P)NFRJxA@m*Rp<&$1-H{%qG~Sl zIY#C{O6IR3Kj{PEaG|vZ9D%n4(t~eUf;kY@AXiE=#CsW#dx=*0FeUGty*={n){|$_DY2ADT(R!jPXBTVRNhyaPTYdB~0-D@(N|_ zjSc@dJ4fUH4|`1v-5^_lYy1of3ASPDi3(2E7*Pyn{#&Do9v2t?ccftVdjUMF$x+@< zPP_Rf%fH;V8(vAqC5>pIT#9I`$;d3>^gPsKTAa4&S9Td!mRcp!XKb?jA5UGJ{qP3C z-C^=io!-~~v%BX!OfLKV+qnBCRM#IXzmuTHdpcP1M%9x@6!P3a)VxP6zzs?xLCjI@ z*JOW-A_N!3Fg1j+hf2%@r_S5NAj{vFq$aeZ}hS~#1ITsI}Iy^KtZ(jL*q?tc| zu)pPDZ9f*^_#X_r-wdB!$}imJlXNlNCDLs#`LMp{4WQ$sY1H6qq+yT zOLwo6ppO@CkgT4?+20a~KBKN*K#b4XSldqp1fOL9f93vitLFf$W^d57-dkvov%$?y zKn!6(1Dw=aLU;p)q{ff<=U`2Vjtq#8e!gax`-kSIM^|9dKG+j=BR#ZBR#aw0@P7P& zM9b5FB&*dWriZ+!5_8?i_Eob|?ErN1=Bb5Krg@RF!sMj}uM>&gm2~kCNwVnQDibzT z&iO$0;fsAVH9Ep3bx$98HbTjg^Mum)e#j1!ANn>QJD^<{t+4$j{^x>=W>)- zN_!1MI+YL2m~vu)n7mp^E}Sjb<#0pOMYDi8;$w+n46Er7j^{Rn^|mymhF(AQHsn=y zd}#_L{A)QVIS;CEC<&>Sp3qsiwcZy-V)o8mMr_iNR*cpg zl+&O(7L^gtHX1ohTH3IBh~MQ`dY)|Lv5y)|%d3^jk`xnRN5ax4-8p+8(kGQw96Nw2 zd_-m|`t9GuJ=fr5MAK%MBTFWSa>-sX27ekM5)s*twklIOt#+*KqCYeWZ)Z*-=TXCx z@e&=|5gKNg7!j&Wa+EDsH0}yliKNR$dw#EJD47G+Vlviwp>PuzmVditw|C`IA-8>9 z!k;xzEv_ACeiiE$x_QRjXB*O!7u-=*<`yIzpDJB0k*PP+IewU|MOX_gJq=z(QQ;pu zegP+ClHfNE@Cb_RV@oCsdz;Fsy9;SVNz)8aTq-6|s?I|1-!5<>Qo^F*J&9e9Qh6Q! zF5wmPOl4HBFBxwyR_T{|ZkQPvMM*xSXHFtAq?Ly4|82*`%+aQH|LV;P5!5yUZpJW* ze~-E1%%-7Nw#q)+vvf|+8G9KphxqRs)QMVkblr;Y)GSWrc7tmZV-D3#v0V4hJ8AOEltp6sW(1gtQ<1=&_oasin z!_EMH6kajL7JlPLaU1ug2=Q>ZkV}4C1Q3~&#-zllx(7KP><1u^kB5YaNCZ zb&e1@r=@KbOqf{XbdTur(A4beihq5|x3#!b|LL&)fmn^Ms0&h7rdA6grg9(QZbKr( z<=xuyZaG%i&Q+I!*Ca2}qh>W}1c%ooJq+Ao=_{Fdl1a6sMJG@V8DuF8tdT0x=@0fy zqO&nTo)F-y3jv?--vgn=pR}_M@dvIWlqQ`g@{qx6GP=Tdyn(Y=Y3}d- zz^{W4A+;ERTs=wh`T|2e05*Oi0BqDWAQxxCmOJK5B)tPo3ZPXvZ?5? znN2cmur5AuaM62*4#y;t%iNe3D^EfAv2?n~6=f>q%6#14U(-(8CtbAMArBEUuu7<%|Fb5wQ7h>T&2LP$XOV0vqMSKP%x#kvDXkzNC0pnkaqr+Eg-<*7P zC+omrpI_XHjKWE(3YD&w-z+(0TbQdwi_o(!#)naaHkcylEtx59 zqM>14TWb)e=!BLkAjoM4au|1Uq1KCKSzPFjkvsnz6y@s^+QoWENlN>m@0Lezpi}aJ z=rlc<%tG$)HI+TiKXLuSDEua_b^U3Gf7C$m19wN|4>kX%F zJE_rqQ#^=qbIC=~S-ZPSS0>&DdBZ1iqJ1IIim9^JhG(NYmE|N2IdCy%6tuT*lf)#~Vz zA~Mwsoo&KZx(3qA5wlt_{$MS(Am}2yIII03obq-)g4$KeBHh+4i(as{A$hqkt@3*`D+~FElRS+1ypj`i9G8@j5t0gBJ1ubXM zW|2HQS*Yx7+MA2t`E47b&PDf+KXEt1GqnfxV(PK_(G41Wugh~INF@NX!Vz-NGwRK2 zpD6@)Mux^#C^X!QM+QGpuOE9i+vo{(P+6sa4udx~pY~?D=On#PkKG%E+#FfSK1Gow zS~XzKGBIm3Zh;HW=h>_#op$h1>UXUc8kPM)Py>dQm?7;ruX%kPKw$!u<)tQKnN+rz zSRX!PTTn-UO^$zEY`do;d2qs-3E>&W#%$%Rw5}mRUpV!xd$Kl{XWNTI!jiDYDb9^Y zCQ>EiglR=cs__^Gc9AFLaA&q$Ajf%4SP=17Ny4M$FNtizs;s0dgV=^PShuzy)5-Z3 zGl!D)9WV_W+1&R0({DOJW`TW?PzE3h?e4XaE? zU16Imw1*BXXfj}KD6*x>8}>EpRJ7vQMm;G~Tp(Lm=)hk6`xYPWCD16e| zxA#7pOSjw`fPyU5$9L#kI%5fQ@M^enl{;ip|q?PR<7!;$1@6tUGQ353uj78u+w?BYphIxUVR3$)6TpdrZtP~ zp=+jLg^OtLiPHww<;`O}p{lA|M>q`)Ud1~|x3=2lC1cJI)PIUA#9Ro;saW{EQRTg1 zC%#Z8e6C{5PrBxPdMEhyLxL6B;l`?STStCp7yx%gtoebb9{L2LOta;tFGmKa7( z7^?K!FP>R`Tp_Dhv7SG9vF;(t#!&Fl;Vb_6TF zpz@RG_jcZ@^8Rj%9p$Y5bvu-^VzD*{iHQ*J3Ac0{^Ax<4Ze-hIIsbJ60NwxgD8hq0ECFJ<9HtXO^dyM2& zs^GdUYfGAY0}^CTcbK+Ce)`YJdUk7In}MwKl1h0xSBBVdJTDK~Ok2OG=JbmTZQ73r{e+#$8qK;60|J3J$R#nAWF-iju) z;lpCOe0G-(arXSfOj;Yb7r&^~waNn&A01;ngY{eOikLEY51s9gfzKcRxg&m0f#i<< zQflPCPP@YYo$&gl)Tmh6nrhkG{byXPNOj$5UJYHBGM-)=WdIu*0+uZVb2tz4P);}# zz$OWU$jM_z$(nUq>tiHhIw~%z(iU4mg;kP>L+HY= zc3#o>cqrljem7qIO$vy|74tWMw}K8qeetFhw+g2v1`lV0%>lgGWrHJbFbn>w>;;a1 z71m|Chi(jK0_Pd^j{E9d!$9JpWgP?lWejd>KY6?}`Jn|QwBiJa%DZ$Q86jkfdi1Cv zQcDWhZ-h?nI`)X6Ln~%dHJ`zJqaeVo=F0Y1u;mm{*hk>J-Cf_Pyz5bXeJ-NN;UVtV z8AQ^;ymokPts%2-l_PBs8ttS&NE2nQY7f%G{W;Uvf+$UGXvmYS&27o z+qP}nsI+a{wr$(2v~Am2Y4hYdd$+dRTKim_?-$Ig8Dos-(Vx&3K{>h7tQDxo2s;6F}cPO>Rb2g(^y~B|fy`FWj>=>EAtyMi&VTI@~f5?oO ziXa+;{#VTMP_+(=!k19jR@QU%{=L7!j{f6)!o} z7*s2#`g*c)AH`u@+CqK?**V$ddhr=2#q|sO*rKQB5qxL9Ss+(A8YKyuTH(=|um4e) zqj|hbyae{^7dhZRLV*8Q!vFvCSI<_}QbSTh{vr*MhA1=urJ>A2hYv96YS!#i6{ypM zSeJ+2s1RcOJ4=j2h6LztRn^j>tEAJJDCND|59|)Ba;dJMbE*Cc`AVtrF=Y&b59oNK zH=SNHz4q?8|2HwY^ZoTj<_B>FP9IbjrH^3>a!54ozz?Z)n4?!B-*G1cxep6|%deDG+o`aFlerH7h z1%~-8>*pw_9au_KRnWNxP5J@}=x@^|4H+4WGYV%n1JT%xEyRzO_<6cC^Q#rKC&!j} zWif3EmY9)5O;#+%>hidhx%~x?(dCkaVPV#!=4L0sMnpmQh|Kb&bXb{^%h?2b>eR8c z6dHZQu`%y{tgYq21gp+UJ?DKp*{CRsQZc{nEmKh|XIA4NtkH zhQ%N!c5+w7QuSJqO(oM>l!t2O{QG26^~t|0sEsakSLK|AZgAB4a_*T+bBZ?@F%vJ^ zeP}O=eQIr_2bN$f@u~JgeQarob>)Jn607Z;qNWWP!=+_~yBolFnNTi;Lj4NMQbqD~ z2(7w=Qjdh7Ked&@>U8mpnkWy3{C%!3J-f%gDcShvc|kg`PhpWff|p*GMUFR$t_k*1 zeb$@tb)HjJmn=w*UH0`C%xZ^4K}|5mXZQ`BxFS=<|&l9VVL2PO6gKA(H0P zUrY5aZ>eadsht$6F+L=vB#p+jTclg={@xUg+S!O1Ia=LLVmAXEvYOhvmFnn?0IgPt zJePnfj*tS8rdpkg?g1sn{ zfA_ld=f%lniw-=NS*sZaa$JZwwaZIWnrc;a$`SRc)^#qp9i`OV?gD0>t&_%!nTHW3@RCh{UXE(9>HPm@qMb975MM>7bf7me;torFQ>{9%uQvGmT`+9~)q{j4l#Ukoz1Xf?`09+iEP-mBcmxj) zICs29_Cy!&PilS{GqwMsRV-2PfP`t2cC$Zm%U|$w25c&f)KmkH9|BJ71<$&`gbdB(DR%%FxPoHWCew#JI#)$9Gvil3UhOm;;H52GFufV;6ZJ{d= zjnF+rXdoAAy%I!De)-%xvh#Buv&R^xYj$|p$ZXD0P*`5d0ZUDdA(xHbZ$8a`Z4%P0 zZsi-oPN8QGsR(cWb1!p!pWq8+*)t6LPl}bn3Gk+ak!<#x6jAr={<_=4LY2H0o~PZR zIUepzy)S9)43Q2r>X0F51U!0i6~b{{vGSj*`Xf@f+*AqA9t`-rjBZf29P{|H2Y{X6 zhNPPo$AP~835|;ccM+kmW38>WE$?u zPlf3$I^iB^km3Q}P(s;w1Z-e2jXRd`)AKoT^N3duq*%Lm1N*zZr}-sRNINu>SgsxK zZd&y@-HGN~NzO{nk;h`hkeoQ@@5^Y_@XNLACpi^bAeYK)F-aO%?w;S(NerYX0e7pX zXIHOwc$QQ>W8}nFPI8DiE;f!QXpBA7K!GvdI)8*hF`3L=+>%6gSaRMQFD#gDR`6OS zNv|KKN9xMGpTB{78T^jeF8EvX)?ht3kMUN48qN0aHEDCssG;5YphcipiVk(E!c@g- zY>@$VnJLFb2^p*yKXHc56dnp| zWDNgR&a}tl;EE8#$-BJmZ`2twoI*_&972{Kb07k^dWKnYwC)U5DVbyAVAd|D7A_F1 z(P)ji3w+IXjM;K4jY$WQCctx+#IDrVM{R3H=Yi>4w>@&QqsBvh$N+Ta0L&j}g%M%3 zk>DI-8+?L%1!O2~Vo9i}wSq~5&<%kO^3p0v&T>Cm7h(0?)*Ph|guQ=rBa3ci^L(AQ zVKRGE_JQPnY;*QhoRTLV16#_3$s6hQcCRTSmB-$GxyA<~+vGTTBsYU@ZRduds!g6TMtfBI2PJ==Gj9=(SWz zHB3Sr?IqS&RI%9dq?B=DyEZ7^u8#R$=zM=H;fwkYu1x{=btc(Vh<4GY_OJic@t6}~ zgyA^5oQ5>|5A;$3AE$SkWaqp^SBYNmNb=}GTV=O~=K7fh^Q{--Us{cAVP$Mpj9a$4 z;cw{`lFp0VMi&voXx12s;S!y@l22Rk3yHFy&W57rB)5|6X3saa*2zc3q{?EzZ7 z)}8z5(gOp?uqNrhZfVMxm*=$?xL7sFV`7XnVmZzntK1hPl3`YkWD1-0Ya2Wk@ND#E zOukvUOBMdg?R04wZ&Q&wE+FL=X4GwKprL$w7pcDD(%)AJ~baCP~bt){3<$ND&j3a+-I zu(SdnDx5`D+^v6HW;4mB+bUDmQb6`Vef-yo;@U6om6*efHt`TR+5UkOZOuv?FI+a) zdaY63XtOTlhlc-twtIfl?me~4LS41Z&tV+CW#eQMxVSoxLU@JH-LmXNH$$wh4m@eB zvkOef%~^sMkkhXi>1IrhPS&+cVCw zOEIOsO>%q*$&E4Yiq36&!me zj=k8w`q%TxZqumi!#*$-D0s4PNm6yCV_e0kTK*vxQ9l zQ|u}D|KE}L?+1>WgT9@evBUo=_$*e@a@-I`{#v%D8A-`xC6=9FIYP_=EdUNiO#lzb zo-G<*k`){%t~wf07*2^IZhaN&>IXEXGlJN1-xQ)xOE%DOsnQJu&RERk+-F~FyL`^{ z%6);>{fh51rjFi6v@xxVnkjxE3`Fsw5K+KQDpF52pb4zC6N_*L!!qBT;vX>qr))vC zqFPm`o1j;9oc!HS!P<6Vb6cW^z9RPmQp3`wvNE1ysQD-YepIB&^7QN!;dZV%Qv zyiso1H56@_@7}ddUs-ePu~}hXN{W)9&z9Lz+|I|+g!t^XX2TW2?&MZUf=$SA1y#$O zHf^-$#hYMY^0)|3Gt)$))|V5|5CCNkX;7GGk;3}EWp!jznrT-<+f`t#(H{p7Pzqkd zk0D;+!dI0DbQvT%cC&}*C5STY`m|B!?PV}1+HhW|NR!cT<>^Jc09fSlIZ4a0BNUWW z|ATKq!-ii@=_D%_FSGI4tsbp8QkL6Xl&*fEXfQUqSrzkBk@JVVj|U_6;B$DAs*%sU%yZ3V|%=#Ie&$yV3wBvh=ic z*{Bc+6O~@3k?3b?=MHBuXBN(8Qw0dM*$fg3mOETR(2}j1amCbiP5Trup(9XPEEv(B z=CD6{MRZJAGLBVxAIB4MNPzri7UWysW9i^A+Kx4dn-;;ptRle$4~_%7B*Rh8jJn&Z z`F-6-+Ulm$u#}-*XjsW(U4gy##)#Tl|jPQp-=p$BwLiK@Uef>uB+ILm;tYQNW2)n`yPT~C{JCa5qOszJoMmh2V4dmwvhZQ zi3Za+sCeRMCh~{%daqC)aLL3Wfwv-K+Pu)r%X%!trzj}Ll8Ef9bDYLBU_=tmV2ij) zi$VPr{$guSy)aNcGN>6jkOjp6g&qs_ftdOfuyEod^k#c#u5d{9DLNB3=PRmOF&(#b z;(Incu7sQ?-iat$FL`?<|Hw*cZ`$7mIX|P%Tji9Xx@3BzJGx8D5Vtk{3G}$_b_GDg zGrX+zZzq5MyWp?SmXF=+*3UQdT2AsNNIhEyMqrbG=cyW2IC|n!9T_O*pv2urcv={ZnDsRY!c8P z3HlE}$^Tn|jQ?L4!vAbbD^}HUMHEB#Ati*)WD#bzkdcWlARYXH`7R7sFvF7|B7zyt zS#i6zUG>F zpX_Sux_!mDJ5Ao3Fd149Wc&{F=Z0_c;kL*|zN z)ljuZRzpdHQbm7rC{H(HMaxas8a+Bm0PHLmKn@%rpGoIiwb=+qtq{?=s!nW1R<)_T zxgUjj)?$z8N|9Z3@yoU(Gf4tGH_&CEY@v`Qw_Cf6uhPN5zugxYyP*M{qM|3^14TMT z%|hl2`{|m-@mw5+^^+ndE`8)PSTPir$9XeatU4i5<{K1eqD$n|j{kL+cAQ%h85S9! zhyd!=Q=@DtMiNRN&)P#rDM+nWscbnwAj#G1gC}2?s3Z?Z%0-^5#Dr}%J8qgRtYK?h zrm)m8qA_$alDS

i%2VPwJ!D*P`0qSlrv2PdE@BpS{7dE>fHGrV(P8jfAn0*FGFcpsU+@HKGn;RFd;yWImteJ?{or~3DAs>p%sAWRSx8Uy)h^6U%W?c zHP@A|ugzR~+Y!Q$HD4Ct6jFvvzl=;T3OpR-$ACz`Opq56*;bSv;i+ZwE#Vrnm%VG= zDyd}Mj!jq_-2U?TGTDC_t<9P&4n>uoub3R(hDi(YhHK%Z%+uI^My^r;^%F*p;KUK3 z2vO%-$>^%xWAdV%{}#-W%E;^YgcTnUS&w|$3u+4ONU$;FCP$wTw4RkUK0Y{87EGzu z`yd$z{#FA5jJPJ?v*|uRxG0w$l+lw*wInhKyI6#KGHp7KMe2W#nzdG%cr)Y(_{Ph_x*DIgy**ar4veKLpSd^?6si|x zC(Jvcd{(xR~9~oVA^t|uaTA}rm{+sB=CDBcIKxvPhG9M2} z!ISwHV9=E9@zf{pNv47HbnFx(s%Air@5mG5kjCQu6bHIXNPk)(%)Uz|W%<{_DMvWg z4(tQ3hCvP6xQ{45bKkT)Qqz-r8K`yexpjzv8`?F3X3$&6EYCw$#MiG!48gr^7_(Qr zeBOXuQP#Q>*N5VfoP1xTe6KcN5YH6^AH+{c|5ADKaBstcZ|JAzyq)iVtdRudi=^WD z6o3Q9a);!!BJ(2+X#?2?&RO;Lv2zFt9)T&ut0A0h8Mox)Y+~6t#`)uonH?`H-|Z_hifIT-AEe189$`0nf=b;qSrG(poMzD zlyVbSwTxZPuxMh22y+!u3Y2zi?Vp3G`w3OACgXVmb~s)5;B;U=0 z*?Hp%_18h{EwDp&l0o#HQ#+coTm7Y0m%f?&o0cV(jpvy8rvm_rINK zaA1stTS4KC3i1L)h@{nk)QEY9Mi>})NWE_SY+@k@m~?t3peD3R>*nVk=jt!uKW};~ z752?FOFhk&k7CY`Tuh1dgJIp=hgpr!YbWndN0&@LHp3quu-QmkkkdhFB;CO(^y5K0 ziQx?(?O_xQHDm2W27;!0gSM*?!Cz1&z{uMOR7km z5sx+Wd#F{fBUEIoR0k>P;vRoNsiOoE3;_BC6^1jSoH4L`Hg66}f{y3TPB&K{`o*ot zzy3EVnEoG-rSRn6SPH#s0FP)?X7pwvx zrZqTZv!s+33K3n3qc1Z|q7Ic8vXs@_hI_MQOD>6EOeE>#iA3Cs%q&6-<$Fso5iw_s z?=>vr3`|$DKzG7&j<;*nS!-ob)eZA_icK7TDkKrUN>zwxkmHkj8VXK`jxA^zEx0%fBiJ*gryPtRJ-KoxgKo(AGjAQp z8H0n6WD;?bktE~J*Ch3#la7V0eyYXmlm`gYaqtdO+j>`on15V&C{@*&h0G3SLyQl# z`m&Ua*+@~zPNTBm026o;d4J|lx7hHBB*O>f(&ENS)vZ9#F=M3)cAC97>jngUJxh+M z2;!`C*MWIfYJue2BGRfnv2qrR7y-T9i@44=+Dpxj^AX)P}_pieKc9Ab)};E#Mo;Y zA;yjH7;MlFJBE$5oS6edLn8Lou)_D27POf*j+jL>BoVVIrU}z66fY31D4Pf$WViJJ z6!n;c?qK%yn242;29M$D-VvQXn@TqrylOXKJ=C7d7aq$}GcSz}-GHW*z54?yus;V_ zkpDJTRW%<^Sw%CmJF`{t6(7F7=aY#ER`lqXpUo^cJ!+>rl*|!X61U?Nx8LErL;0TxSh%H zCS6W1)yuZ4*R9m^J~4m^#q5wW`@j|88hk&6ew~N_*P6pkn1~S7n!~nmKz3N|C85Hp zLb>4_A^0lC(8m$gsD_@L!-`@auZnUcJHEAV+drV6;oyZ`ieZz-xkf&S<|EnWMYC*U zRjtFM5~%w5)?=qSQB=wF@LQmB?Fsic`uLI9D#FDv%9=)h%{x2iE&5a{9+B{8j=U74 zr>ErVNO{nS2kzeMPS6=N$tBvoKO$^IdvlP`UannQuO3@y2jcoqjbrhoZ@A}+qc+Tf z#x7LzN|{o-BMyzaUY!JIW8G^Jfn$599uYE@!Kd#T|WuS^8W{z}AM-fdI z3Q`)eNcIb0Iq!}`nHO%UK112ENRw;{CScnOT&0Ut1s|4jS; zKQG+bKPw{}qCc;G)|B`qL2dCfFsO1VDl?J#PZ7IZBKl63no z$!~t$x7m$f)+QO;Buw5{*Jlq^m+&KP5+!b{L_f=4PwzcErjILpz8{eI(VmR72N6M) zh&5vnyM`D7+Nlg7!2(^;Ll7xO4Y^@^AwijlywQ3Z!%(WmQM-1XDn0B2L{C`uI-NU2fAK6 z*0a58k)1m$`@tJ)(vcT3GfmY6s`~5;$)+m8N*R6q-V>D$Gm9AgQYFS7Z0G^uM&Sfa z7FbR1^2<$bJwK$e(z0YrURHD{hF zM&=QCqzapri(=C_a$TIE9ER7UqL2!WCJnd>uzK@!goP2+}v2GJIx44XvG3h*ET9{p^$_A28NvUzCaYT+saa)5H=>iOsDweB z@F{8B^hZNcEaoabrQYgn^U><0=1hfajSa~=qYy;5X`nnpwr4X}z&kww*7Z}-W$B-( z9AKf=tQg5*j&ZErLMvxydEq=jc&Yj<*60(PV9mhOS-Q008RS%^UJ zNiO$u+X9i(iG@sxppp|u?3@lBzxW~nkBIQMpFZ|Jow(ehGPCS_resPyjyMj%M=_B70}&D}pslp^&RvPTvMmFVfmzQ!a+PdiCq5b8$7_Kr?u1jq0O05WrSrPI>qkr z<9eSa3iC=NQ1QR=&ohi;AgqXoTITEn8yS`x*5x+9A#A2|2%kcL#CmxP7Rm2|Jtlnl zn3~f{t1t8{BFk}2y>kty-51Q9&iDRzH32WWD!cj_S?!_!$7-VBZ0z9ve?8yTAb*fy zC|}n`32f=y_(65SI)aSS_<_q{$%Otvm>JUeka~-lWK48pyGGmW_rQIM{|wlwk(-+f ztaUb=>Z_rN1uFC^^_H%jE48$=I<@{8xIXgP>Y9++t6SZBx4Uk8pX4}R`F=TGxyN?- zTz~$uNS=wDIUt2$vuqoJMzeV=_|@uO57g>ekNb!G_(lW+-1sE{@F!)DiqT6Ygv;n9 z1Mnwx4~ud0Y)Gd~Yk)^L>EH?RvQJk7_fBs$65(>et`-etwRSI)ai?_82j(|7`{@wK zOA6ZkjWrx@QfTPm9;^LPT*v?>?o&Tm&Fc*g9PVv_Cil~xg2Hzi+Ro5(S6cM-#0>}x z-@~q$ZdT9+_s>F&Pr!)C91@8eqMW(gdgQ{blKX!#rF5&{Rc7|w5xhhnx_A3#j0R?m zUxGu>cwUOp9$%7w)4AVTew2XrqO|&)^j&wWhl(BL`Nr+V7Caim_|D%%gs8E6Xyofz zl9&I87fBNfa#-*_k3TCFM)Y%5xqKQ_CZv!63#GJpNXt@am3-z}E&$7rt;xxnp&yk^ zI^2kO9Z{5h9fyT*(}%XYh&G18%sby-)lo|_fAKY$=vYD{zi3DV%yl(t1f`_J+RjoB z2F;AfV%U5*6M{s@JH(h+L`k!3Ct?^_W-jTUD06d8g=WV_V!j)tk*$(CR!p#Bxg^t? z5{6_@f468c%6Yg*r(!ZQc5F}rf<_r2)7#smD?yaXv86TCWBm;YlF6JxdYa=6b50_0 z$a>>IwN_tb07`_a;I^zeA@qr6dvKtNfe6$}QHj|G^!>df%E+^f%0$R4M5taS-$*$p zTcmXf>!@6nd2WV(ea7#=RBusE1ET1&sc&u8mblfFr-;ku)-R_BV3`=?T5(-{i?YLw zY$o-)eHGRvu5Erro_M|`-~5w&@&K9r78PvVh@H?RGbjdk)s$2VYJW?)5#Lnq?a*?{ z=+CGHIs_IZSqhlR3Tj3E!-G|mIhWdSraL}kPS_fJ>}rcPB=5|$jaDx6D1b=Nmb!2z z3>uMkPuyL#WJ&ySq>5E&TW0FEwwuk|CK29Fr&Le$w}SM#Co<~#9f*HY{3a$-)!!85 zfNxc5F$Pyl1V@=ssAtN_nfuBQ~80dn~WG8Z3;ofKvF(ZrgN8Ah@*W@475?$s2zbI=d?g*3~i8eR%|rq zy76kz6y{gW0p1JQgeGc7hY@3w&dtIN9nKHP+UWzW7731PzZq7*QeswLOF;X+v( z#`ZKwyFSKvW){6Q3YVyy3T?Dz{uXFf#h;!28vX3@FC(kKaxo$Y@Iv}}_LTEUQ;U_& zh4!D7_2t>M+4c4L3JNv`rQjebl-sg)gXlgPXPM~7yB`7xq_)V{2@T2*$!JFH+0}KT zox37UnBB0arV9{7snAhEdTLR?EuE0B4BLZW`+g`96Rv1avpT=Fth=&3y9sY)+I}c- zp24NIvgA6y5Dk-&QB}#vA~I5XV=j|?VNsD$VmH1a-r~e!eknBw8h_T2;eSdFGMs`W zGqtD^oKZZ$-`Y=!SuTTB?i{rkgBc&Z-h_~e;LfBuVrti2V^YasZv3t0uJ`xfjSD!| z`a0G**Tu#9#kwYIOO5&Lk~U-!&grFvshy!xV#)EDMUQ4)InppT2N{GhhnDDBKB@t~ zTCzoaO@(rEKgRbvOojZXDydy0gC3Y_W;{be50PXGS?dOxK*{KPtb_L|H*5bVtPk;0 z8{uGN@rHECwJDu#qzO@S0S+qD&c4^wM#BNO&#E-0e>62^m!>D<7XNmLKid0X{ZZyK zn^rJU({HPHNkpgkl~fUKb-ugJ+>1tF)0pMjRzf2#iTW$@czai|gQm4zu8@wx9isG- zC8X#^iHOJhMO+!V7z+uKEYUsVR4O_!X^yT9qZnCQE4vX^c+zwWLLAEg@Y$v>L*i8F8 zs@G=QvHyJ3y^zjA2ur!NTn-0t%E_wO@3dHbL$X6cbP(MN4_OV`rw*k5^gIrWLMrI9 zo0N=}a)^U6|w zdO5j+3kEDw;U9tTKeQ@nH+GwLOp4cLzEiBFmAYXQH8bhP+FDQCS10O}pOR!jnQ26* z7;X#7Pns3s5no2X^15w8zl?PQ(KZ#paGJW9|1xE@$l978Tw}uG_T=61xlXB>$)U4b z=K5!v>g4k@*)x+v^yC@4c_e@8|8e#)M4E7b%9}l=VNd%6R~YuauJbe;$u27lG-6T# zrziq#Fl_==2xqS~JxSgX;BaTt-m6E)?X#j=@qm`qiMHKM9?!B>`(zLRKr&7v7AglM zeqMExPpnsqK#r&)BO=ogP@LqOA||wR9|{WY6Ol_nj!AHDngF&1%uMMd}vBk z(@u+gVP=eL-mX{jG|gLDrlq7fFpttfhWI5;o>n+Ip}bp)(i=xDm^+jDOCFZ}6bw^| zjw_hv?JsKM8^+;Dt!|Pcym{f>;Kai*K9t34*{i$Crc7;LK?>*ZDwD-M( zc+>^$Gx!f<7VMYapauAp<5%*mxdJHuK%8kdh zHYlIV!x)`SM;uq3XqC1QulAfy+x;)C@;#HQ-e6msR-n84n@Wf^mw=Ok5_}+JKUzT) z7kKf#>1J53)OE9OSzx{Uu^C4q-`03rt~*ll2F~fAGKo$Dp`&Yw)sN zK*jI&iehVAR%Wui{0KAAEk^Yhs_tT|vi5pPQ`$Ly!*b6BXM2_V`Jxt5t_B=ja;`qv zyA4aUo+ggS^o`BuaJaR9|HS4N zi3+(Bs82dZ>&1nS}UTcEqzTu#0)IE3BG^l>h`_NlRfbcZ{5UMo|H-?@B( z-8Bq*FX-iZp&+Gq671I%9C2eYgj`W{M@B(Zi3qfarw%=MODzH4Y9^8^s$|ub#NeS; zRZie?IZu1k{%c!F6uOC5`!(TdF%bY6Dl;s%^vYVhPnF4i*KaSM2D*Mr3|kX>++t0Y zb*n%GSF@lmKTsK!PfTbP! zaFgZ+@scv$;2Lt8+bXZLW{j2Ridn*4a-0FE>F+UO)_tQpr#+0|ZJq_5AX3s+@#Rf( zv;}MFEN8=nJ7?Z*py-{?!eK$zGKFjBo-{SzkKdEZNRb-{~Y7_cO;_J=LJHZP;l?iN2r9{n>-cZT|e;%>3_f3E$AZXR{wyTJ&ce$n~E& z6N<);&Q?wm)^=9^%a+bom9axIM)&ENBF&U(p=qLK4Gsw7lTavt0Mk08gFzJ6T2C@4 zS8o|pY_Vz3cU{0+MB@4DX8&*~Ai|T!gdG>JPY5y~5Q;IAlVv{~CMk;n`R zhwIY&?CG2H{QU0X>;7=y{fp~XAHtfQlmI2b&ydl>E~{5ID1O)uod`YIF2Qm5#U!hd z$%~6%FDXa@Lm&$_AIH{l4B0muQfSBt{d%`5$n}+zXfF#iIM-y%8x}tZIQCpO$rbnA;Yf(re0$H=0 z7u~jV1~xH;@ak2d-vj}~n~7|-n+=aRml}0oY~e^38zce@`p3@zIOc_Uu}OE09X$C{ z8G~oiS{hSXi&lZxmC`1&i3T@xW+r1w6(bCsT(zd|yNV>s$Ly94?S#*Fs#tf^>a0Vu zR`Up3(BMFqGFngC+Ef;6&>ovh$I^z&;aoMUqvkF#O#2#Cxvfwz<;8a*iN8QIC)t|D zGYP~Ye%P=yidF8`;SlpE)?{+Q8=L(`>aNWMQ%j@F+I6BcWUBX;RO_m$uGl}#1p2ytp5n9oY@x+^R5>bogtd8oMFG}sHqn|SG9i!7a!4&TO7Y0*-Ykn8dvNX-KL+i&j(3mwj)a3)FjO?YU7 zdO&p7%@@5YP7y&URF%oEl-w$4jxB)Lqr!f0W8A}X!>Hvaq%%F6A(ZU}UzF^MSosyZ z9=uY%m9;K|8X)$MU|fvjGsfqR81cAvRg9bO=!RkYrVA#nL_8(%;j&bco|pQel)%l5 zrz;I^&ySPR3$JK1x4OvB)&uRvxYj+q zz|*fS?9J=@&nTOGsGTi&zfrS#NmoI z!0ONjQLhtZ8$(P9xoqeJvY@k|%YB39{si(lOU+TDEzMEtz0K#VC}qk-7etyYre2|PffjUIPYh2Kfq2C~BC@sof`o>IA6iNxP zWFRfx{&(&edz1#GBO_rHMxPF3u2dV^a97lUx?UljQIo*Q6mujXvI+nasefoh-Ir8H zy(b04QR(S1PRWTOgkL~&VcC(f0?pw}-EC#mWeHsEiAu3Cnig3Sj{@%qJnz7oou0G! zgeUDDqcJ+ymr`X|gE6hA>$;`j9egt&vSK$bl&QKx6Pky)6=l4TAPxn(>?DT2Xo@Ob zn_DvXgyFxNmt6dbIz^3MIF#J9Cw+F-HM25)`iil|;I69+@Q^B@ikqb-D?M+?(O^`T zn>m_ls_e+o9X*##L0gP!ZZJSmc4et80njOgJ4ntHN3>XTV0*`VbiQp;|#!LSe_AFCTMaxgKs$6;q<%qD~f z3s~bPE7tkfrX6@6zcdHf&i)wFs5|*;{UBg)x8l12*Z8_F@MOOq++NugqOWzBA$O== zfcT&`y*?>X+5cdn;IOcAH?N=1Q+151$K=a;n&i}oTrJQ1qtC;fRV^mw4(Gkpk6-ZU!4o)y=?@X(Z<;I>m{JiA{oFR2~4Z0r| zFES7;h{7|5&ooFtDtgSe47;6gECXvjnO$z?%1K5vPnOSJInqyI_-x;&nA2;ToN}Vo zI^N1b?y2+Q9{t3&=S5;+#r}l*CQxKmiX2lZS4j2$VQ#-h?E3rfR7&@minI9oM&m6A zL?5*MbB=rzXLe=r(Q8Y0xxV4*#u?;U@XyGnJ4ApM#n`jCOLPHS177 zX52G&c3$CR5rTKzErV=fu0fZqm8~*|H60gyjy{F z$q-|BT;~K3*e?sPnqB}}gfskvuVFM&UpQrzJQ&qsX-YkB0X-(Vly-3!nE7?;>NUhA zVSfa!@$b+kvb0kD8-^I}p}jTn3D_8`gVV;+ut}Rj=b)z|9cI^t?f!Z)Gc50jmbFnV z9W6JRVb%4pAsO)IJ?w6Vzw%?pd{`7k1wg`;{n7Oea0tsG%2qP6jiQKExqIj zsC3ruvzusCcSan6(90($!wZ!tIM<1&OxMvPF-@@KEw^$ittnqmk-svpvdTV;Z!c8?U3m zgvG3}wg|}c@NZp-SAL>G++DNa>YLv=1Kp~*l@-kP0s zpJD*r(iiO_8BnonFypF@T3*JK@)%ywEU7;=5kAkzSU`@F9?;dOrwOk)HcP3LaAT=f z(Xno|786ECU`HOzbXi)!j%5rFk3-pRs>ITLlxFB%p54g)bucA=MOw6;v3PN;Yz9U; zh-JC%UP)ma#`-Vom?cF$Uzz;6nPjAg+<==lp}l%x#L|0H?c9_eV%Z9I%RW8<(M55) z0Yd~8WlsM>zq`Z`(~B(fj>;`{PUOBPWlt=V!Uz8l+-+Qh=OE2*k8Q!S{7BM%5Jk$B zO2@{=BT1Q|dpCih$QJ~q)kZMns3WRmhvF~sp5b)b22J$DbL2sY&pP(GEoxBwL@czb#5?&`+5Re&==vhzt) z+Az{r*d{_xy?n;WrrZkjk^=)3I~4Ahub5b0w$_4_koVL?P_yEOB}eKZ091y1y>JP| zh!>{3u(N?^OWOD#QfIs-XHF+|h5SXj7U;CuHFSN^wAFL4MDLG1)zw-J@MG*J9LD8U z5XTICJ4`rzmnU?2^~7`UFZQObag2+h=UM>Vt641M$*B^R^3^Qi+K~-ecoCFAAwY?s z^L&kCtvH|?f0be8NlDtMJSV)jRxG%sq_EWdZ9&FXw-h8Awjs?h^SEHyO1DN!o);4`m8@b>F$fWQM0 zp&siM2O(1Nu8rj=^Fp!bI{sQ<-LCPjD|XSo2c=KuPhh|4h^OKs`b_re!7DiqD*S-? zrkvzsviXzWA{PQXWg=q3ZPOj^+r0NR5n<2U&&A(IqE%ErE|v*b6`>R8v~6+ECs9m!RrT za1P2KXorXo$t1jSOshykF%3q02=^9opDi99H9&jh6c!@j$&l*?mei!^XmF3uaK(Ak z;|0^E6Tv-|A8wy9huDs9@I9Sl7kh^DDNn#WvRK`K9sEvR6pZR)&h&u$oAv;h21naJ zQmu}3p)NOHR{#-?NGcZ4kr)?9Z;!~a%|i z4s;V;*OVdipIPm$^x2!eePQCmbBF#gqRNbIGGqn`2XG_V*?Xt`5=iiM)2n=c zXT7_jb=*Y!V9gE z;D;D7`UBLPD5YQ|$m<6{CHM_1Z;g*hFgVlMdffZ?&6}660;HO&Ew>glH#*`)kT1cL zE!ZlZEt}`pE2^uvim={x-hT$w`oJ&g*Aw0MS6nBXj$ik=j@KK$r}2&EdbOGd9j9Y`Iti$uN;^Q~Bi&amc*4qOGlJu@8QwWKTf2Y3>opMDc`L(rY~aao z)z=y{W{Y;f)usk`E^ph3rPQW~kt!xOlCF#HuLm{A-0Q8pA~05~8ZXq*TQ4GtF@ ztkW98#lw9(F!EXny@nJ+KYP9nH29Ez>0Sx-_Mt|j-HyhC#EK2q?_GTzp~O|Y4T{Ju zdnpeHnCd9bWA2jJsR~8(XrNj&(7H8RVFW{(S70(Gm(M_!ZhDBsbkeeA@T@Ll(9CZ) zOhjldrzc5?H>w<;SvYi~}T0}M9 zk`r~NDow2{Je7mXq|qls<*=yrSd+4?Jl^#Gkam_`bw=5iCIokPcbCJ#Ex5b8ySqam zaBz2bx8MW~?(XjH4uL>Z)%W%tRiC=L$9R9j-s@d^uDPCR3psQUtjcWbJXVmxDAm69 zb598}WNdGwwKJgyqJk5U=FKKfLKV6vMLxSW_lY3!mJk*;5r60x--w8`@R+gj7{XnM zVEK!{?GMrw6e0JI2#MP(g|HBb@Wj)?K8e+6{iZPs>Qvgdv);b$hduJeuym@|plHA$ z{VB?2-n<+f$6w4K zlUS##$mbUs9)yaYonDVc3&vU;&c={-r^1e;@M1r5hEF%_P}&78@n%xjq00npNgk;pSiB6#a+tx zvo8%cnwjVz!qp`CkV)B6XNZ#M0Vg#Fi&CK_{}79?(UUtM-x=3S7U<|ED8j(z88Juo zrT~=MpScxem>uUqY@FJl2CHWi3v6j=Tx{Z~v5RV~@YMR|_-)_{_(oJ~`3%9Neglmd z5S+iG)>vnANK1)WyW{5Gab8(cWakQ7JJpk``k9u6|2!5}Q>2t_cA^83s`{qZm-(Ct zLL{l2Bx$iBv|nmluOZxN)+}>t)sr!`xpUU(qjo9>b?*_!)IF)vFq{lNoMkSxNZ{#c z{Dm;{txE`L;c$>oeU{qiesh@O|{;n}$*(>Y9B>%agtV&#bc=*L%PyO3^D7YM%flCYxbqxVX z&tmQ(M!!yHdsx?z8FBWuXa<_rL9W9w%4KAm-^1F154TbV0N-qtP1w@DL1v8OFT;-KhAjRE#I10(lAD!=TTd$WtCV<5mx$llt1HB5*4*S~Hq#vIFnhXO{dDHmh@& zgVbV^WPkm<>u5uGvO|CPms;0TxC$!VB(q``>EIR-@`E>H*e#|vCJ9``a9za0+76AAZY5n(QLssy zfRe7U|fJ*tiaf8pjd6Df_+SX3t-&cZQdB- ziF}#WT^m=+jJw~ZxHs#VQIy(hE80|EsXvCb@PrDwg zJlT*vwDR>Wr(z{C+|_mxY_wKlglXK4KASTb9f~zTp$87`#RlY503E+gCV+ulU_8Tu zbzPaDQXJ&3D&|~JjSK5G0|qW%7Aab$n%#ljz@-+M+m2$(>yN_z#%~1%F9QdOMvoI6 zLx&yC77qI}TY)$16_X1|!w9NokWcrAwyun^fkZTZS}eX)ex~G0W_bWU%jwhn zDOa>eRtNuT)*~m%DB$(Jt`hxD@W*gAgOMQ~7_q^=6ocfSu5mf>ijbKK`RW;<3Cs%r zt10qjO8_>9oU^$4UX|7(gCtjh#Q~;NVp#jpGNtE!u2b1^K=uku9ckyQaQ&hFpY(!M zox1gCXH01>fq~Le+!MC4-!8VApSdd7Gh}xcrYu7w0 zujZhDwJ;UpW|l_`rImria4 zRT$<>D3-)cT&T3l)}++?4ul_NS;7gB|NS`U{Z|d=w_Rg3>!&;W=ckUt_kXY9c-q-A zi+MUatGGEjIyk%j*AP_IMgd(2gMW^RVWY+Yx10{6-`b9R_Ypniqd$6}KIX)>Mnb?+$E0gOtq-Vs5Akzk++ zAsyDPHT=xG!zdp*aaGoe%`QI-7oAgXTzhyo1roo-?JX;um+BI5FFuiH&$kqOh@<-ja21Q?df=+5>^lAyh(m)9{ zd-@g3RWXd-MGG}z(}6fb1EpYlch9^FTGFuMmePUsZzTqHaZe?5Z!@Oe1$ z=d&l&Y;0MdJ?HP+ATB5|UT_X$eKamC=}9edw3IvSZ9LI-tAUFIFxCC^6?XBMsB#E- z*YR5kpTsHom|^{6Qhc1BQo=0a!5+^ zGP2J5H0hU<&dcWU@#l4E3oL494QI3GNGWR|C`gPb)fi?E9!pBy;f`UT;XuA>8!1u^ z(hAasP&3x(tB1*PFO`n%fTZm|Ep82-zip7c9>WGq)Dd&cG)=W8Xyf2=U>e)&Ng8s# z1voD)bKVCT9XvE1rK$TX=S|eR%wir_+o$j~NHs5l<`m;kZa+ixV^A z{S)WLUDkOo1KAcM`aEWypWJ|jt>)4jl+2bzzMw~Z+*|(-uI?a75%1s}cW2LhnQ*Y8rtM&Mn);uCJrUA7}j_C0#Yo++L zxUg5if20X^NJI2GxZ6~{py#y}T(Wqh>53e~_jUSk!YmaKe2Sw-MQu0@A% zI6}7a>m6lG#$mF#BO{Tq9%F|h-fESJTZ`M__TXvW!8uA#`9@%uZgW5^kiOO>mhN`h z2t#Fn!Cft@&kNHah=a*cvnK}*zBz)dt6-M*r1&@FV?kB>?8@i*z?NI&p9CF>C+iVZ zF81z@jI?we&m?XA!jEcEp1P9XN;|zKr)PAVeUz9jTuBO=Nk40M*>qK%ep{z6SgCiL zKhRdPRr&vD{k^b6C;Fv&*&o0IQGjJiU{l)XBu%wYrtjOEeR zQ#0}zw+MuoqG!W@U7?=Ct_8%22S-N61jZCGX$?;bbLG{8s?rjBsk!Bw)ebX(^%^3g zs09J{kjCI!BH?e}C7*}Ton(RitQai7UHzT~u&2+oP=8sLl4h~(wfG^7HF#H09Km1o zQ;%c6@S5uP%cEm%(W|S~V`4@6|uq83FTEp!#m`zaumJ4+70DkUOSS@N=L3>AGq`H5Qk)YFJ$tui(C9N(Yj zfBQUJ@u9w|+GKF19m*AWol#8i7yMEHB8MIB&|8*dTG}_4cx772qfewK0-D9{ zzci-DfivUAOnK!kt98odDVF(Sl?z6~Z$hxQtprK$c~ms93{%t-EpVtMT{G=po+kW~vNP#*0BA-1-w1b#aA;Gs1XJUfR1XQ6$*P ztl;PiBA41XZH_vXYJ-9hBOAt=#malh>R@yZhoB`_6u%}m8Us}`@fYo zb0TzO+L`ALr=uZ97f7t7G3kyJWePvpg0W_{Nn46aKMW+PEt|j%+_iIjoqkq&?%9f( zm8a2gQ(xoPIDHi?H#Q(yu!84ngL$}!0fah4ouRIHFFLsncW^~Q_s2GsyYZ)uKYc!d zCYThh87(DZ{4bq7)Rj*GXLeCW>w`drL>%n0Hw8NmBI9(W+0WHCyX98X!YKB@DUQ1G(* zd-~0gc8eL`vGp|#@`E01gWf$$y|1z4Yw-&}?Cc)nejHPQ)UrsPMsf#k!UU(lE62YS zGRhDY9b?79Sa+Zaf`=Og2!v53QH=jq{3Q=q;+Q>;;4OEG!SC=%GSRys2Pwptr$iXU z6&voeITL#u(xaxpHm7_mokVL_w10vU18+%Sgs`7${n5S=!6`ZMN4D-BmYh03DK}0@ zVM>WjkFGXt93=nLR;x&XmV)zpm27d$#m>U}eS%`tr~BX5;RRYBHoUp}doS*(tG6jH`ELJ@XWZWe z>LS~cql%79VRxNeAZ{boI$xo*$lh`2I+Cu`;9ms&$s|~~!YO+5F5W#&gdj(A1)qX) z_9d2u!Bv3BQ-l%jB|B(G^VOYi2&GjD2J{|fbh4sNS=$!fwsB$(pvuVL4uvRe9;zfFe z<}zW-7w9Qv@(Rl;Vt4) z>BEdtjBW(4f{n^lNIPB2taa(J%9rb0eGvQI!M84OG9Ja8C+K?kUAk4vRN-Cxg5j>=TDTF@X16=L06RSL+xi`j_ra|I=2()d%vT9O<_ z9wL!@$5CG$#<}{wB6SRJTl)P*wJ4HxQw2$U(A>0VqL4(#u*p+-Ty6SHdQ81cdMMl; z8FarPjVfVmIu;4fb*6lLaRlQ52?qlSU{lNb83-PD86bVJQij zIAJmGcTq>M=uJb0%d&+{wWCWj=Z=VBIWiY_=wiT0XH{z>XJ(`tN-IYoCogvcpq zl=LK{nJW-rPL__Bjw7QG6BU!4gO@t3)Wm?}F0bGdV|HY@%%feeH}$gcxg_<8~NRAiL$suq9QUzRKl-9dLxh4Nth(JXfG83$u~M z=G`c)o`J6JxdFj3Hc0mSu*TeV7c^n!ReUeIGLW%w}$ME;Ct2q`dmyG3%w^UHq~FnitKjr)7Besds` z`ouVOfU0$UvhT1&^BgBLdv6eH2!y_~<#isR7UdYs0G?n1L*oK&t34cbCE&O1hH<{( zZ}!N;K)woiqPIU|c(&Vk4jofYs{3_pQb}H+S`qKz+{SaLSKCM$FAkKgOioAV;2rMZ zl`P(|?Qdd25R>2M#o*nV;QD-#t1PrSesICI^iu<#d?~a%*i7t7N{aL0O~MRz5V;7W z*f)D4xp=|al5u+BU0-W}Q|nzfRD8#a*JSau^d(NEzvQ&64F#@zcg53C84IThtTsGG;u88GZWkB6E<}X-F5xaAzo6`faLSM zMIk+g<9b_;YJulq#M~>&D`jVE#veI93-*w|OB26U9NO!`k5Io$dK(a_c4gse+NGaM z-Sp>k!}CToo0z;u>w8MKk)Vg!CGl@ZIe0&R_VkwdvhVoJG!4Zy<3G6Ok8xYKw!Iq4 zkBr0(9vr70kbMeEkplY%4PsRiZw>guLp;6qZvT;=lh!dZ6x+ zp;7*%JbYmMKeaWQibk%MLW)xVyDAT8d}xqsV19H^j!^W)G1#u4(J{)jtTDu8TibMh zvkTc^fM{R{!H^n@5Wx{%9FfgR|7lgd`lo4CyUAc)U8geO%j5m;W*_<2AKrXJ1`FL{ z0m@fCY~!G~5Y-mOEVio&zo(6<&RmbJ&cyEb*T2YMSv^-~S^Wu7yh_LY=$;8vDKC3y z0lp!E>ZZP*=R*y4fM(IOKW|J^LJAax*rj=Bol5B|C(en0i<#zYE~a!4VUTJlu`}G7 z?BJ@j7n#YbE2P(}0)YC7F)-)*1{DxD$@W|l>>%~bA+omS%w>#p?7%I#j8^Vp@Y0Jmn2&vZs+-rUDr=>jC`_)obqlK9a zKjf0yBqzrXY$#%C<)Dq_Gb`q(0^Y`YU}%a4xOK&_U!SJ&w-E88$YJhnAq<6K!qPzp$2)*&Yh?-NsU+?gd=# zo|g+%i$xR%l(HURtmIP3FtDu`D$LpuJP3?`Sl-1nvQj{u?eWaxJL3RGkzRNkw+w6^ z053;nGS~ybt_FXSXu9(6Zq5h}MR8V#ylA-~0tH@asWPsF3yL@?CtXjkzuG3wlY3_y zX^N_1*CdvlROz^jOsf^{6ggO{g6Y2na|=-1^ce@{UqKB@ zQfj+;*ieos6S&p)!mqfMHh}U|fhN673v_KK9B9vr)L1nZ$ych_v>aphvEP?V%mq0r z)m*8|o}xKu4(UW3th=W=bO5Uq%$n@&Wx33EpyiF_jmc|%xXWHzNE4jY>#CrmYr^;2 zuplc_OxfM4pu4n$Ydy%Vz9(Ge3v=o|ScQd|3po*wg)w=xY^MsZ>iIkuTPu4q!On#A z+-|P!dRHfBDi@C0ZRqFx;3Ld87{SB$&b)20x8U}Bw7#1~pi5`2^mBSp?w%QB?Y0_d z4KLaPi`qF>VZWh4g+9IbmzVt;dqr2M1uW$5%E+N}dU^QzX#bmP13PTD;O}^%~2ce#Z{7TO?;E@Z-iOgmfi?u#Usk6SZK? z6v}|8rAYg6t*5U(MF=3ZFk|ITw_tt|*_I*^AG)^NC(a*+k!eq=;3AQ|m92ED;?|MJ z8>3!eNo4CJfkz?aWgE!nd%gGuveVlk&f1+6^E%hJxv~+;Gq9uQY}X@F7SCx{f|<}* zsl<9S$;g$_K{D0B8(nTTyR&plkZb!8^bLcI9F-9v`9cjReVVt#Ispb(duP9EeaN+Pp zYw}_S>P!Lk2oqqm5H_#xAhJ?mIaVSw-KI`@9Hw4QNQK%@au% zz$TlOXAnf)^MS##seSCl*;x(*;Pv(K8V2NzpcWnDqV`VhHFgJLutcHanc#?JG(I0v zPY0qz-|WQCRB{0_{gDX~bOJHc#T7FX;xJP{{C0o5D&aJCTcL9?7x$hCE-CH8->=Bm zk=7MvQL0D4Z@v)MiPp13Zv>}xMNDhb;9A4B$*!qG9Q$p8z#4hME*^zm&V~3y^H@b8 zeJzps9t?@Qz5$4Yv3nXSVWtw0qKtmfbmXQ;r3MZwZx3gjSW#34LQ8UaKD*vMh()*$~xW6#%*h({$^0-Oth z;ZMWuq+CRbK#}jYS%TBwt{pqk7Uv}rY;PWk=6qeTrD3F9Vz~D8p0q2utSayTijmym z$J`K!AuGlpqAU^d<0n{IprQDShYtLBzr^o1e5rSNt`AFew!v;Ze%VE4CS0S^4{$Br z^+KJcX+0dHLPA?;ev^Elm&^IPBOvJ#G1h)BrA`u1&yIz*`4B@P(%Zc^g5oia{RHa- zK#5e1JJB^hE^_XXc+csn-p!8H=j)?@e3$z3AxvF(MS;~zlE~6qz}08m8al&NVi@5T zuKCg_Ux3Z_jCO%bA>iEl%{B^!xdFp17?7vIf=ZV1a$az4#yuQ=Kq8UygFx%i#gy5^ z<(>BLkVVzc&=SW4oL!|ShdqwidPzXmWwhgH^I&hgYlvT4Z&1xOlb%s76DgMb^&dR+ zJ=1SfD8=y0V7uq;_YFc>h9aU9*^&*3X{M!)F&l)_XhYxgC}juqu^L6gtYf2C`1t|7 zyT(@uN-)DT5T+Qufai18aZbH7$o;>Ita3cQ79YINq&401z0CHbZIdrrcokPL7W%pchQ`j7&VgPn?gs%QN$|MAcIpID}Uo&2fW{DaBF z4?kER(%Xk9C8DFPEMkk!fHqq~8#w>niewPwJZ=_SPbU)9M21L;Wgq)MM(`e17BF|O zl72#b+H4BwKmh9~D90 zTDoiJi&acTo~gAmF2=$2`G*sDcmq=essPt$FP^ocVie8haoILC3VB96D zRh>w{_u#k3U1u#K)o-?8UyHa}s<QXQ)H1btN8`Fr)wXcN@9NNDbJ{|&%tj1>K5?cU9B`Z<-T36emFAg>5&$1rY<{|bulKY62^LTNGi zfa+U#4DYyLTy9U9Ss!}J%FBj-tWm*&FPU%XR?BJ(R{UAB!%BrKz=a%t?|qWc;#cZb zx8V2>e(5zN2M}RTW<_@)E^dNkbLN_9bB-nW3AaRY*i~fMmBjRqsHBx7XRvHy+=)9W z`+9#7sP>w@@A_n(@t2#l4*b?wWqQ;2?cr#lv5}KNyO!?QK0G|V%@|x)yl%JMPp`Q~ z>*anAxKiz87U#x4gJS z>-&lOLsKGA-!XT{JVH&osPp7X%2Dd`ab@~=*L|Vc&Bv}pD#^&&azuWupNCfyw|TVC z9%#qHz_T}hKDX!Z{Y17dGU8mKW5{NPe6VSV5;H|)R2Qn2MV>e4?W}-kfvkym)-s~Y zRT))|kWBVtG!}ULEo+r1C_zC3hDS)&Fj!o>54W)O@Rc&-+Z-^#WrOySo~%B+SwwDj zvn6l05UcDGEP^x~!%L>D`wi#2^eJ`O&~daIvC!Hx?x{-fh;uOfuR4=^eu*jZswWt~ z(BlBsM_%Kxas!l!SYnf49*4hy@V74CBZtBUp>7ps(4&wYX#=wV#NT|?y@U_gDoUr~ z0VD{G$C*B{fDi`wC4!Gm9wv=p;qT`+OV7;1NB;y8i&qG-!LBsoiJ^@waZkFR z<>}zFJSqNX66t4oa{2G%ItpnQS6eZAS1VVqe-(YI)juh6>L~9>9K%@BU_X9Af=fx7 zprbd*(SDgN`XTx0@z3+20KXZHIfg3o1?j5qp?$p)xoo)C@2 zfq~q4{HDE|+AWy3E{R@ZIlJHBwy}sRj|VPDyswqO3h4v}(e+ zbOkAVO;cu##>J!)NX%#rS}||j4&-B_mJ;sih;Uy|*>{mjfWuOCHB#!S4`RlsVtED_ z#~P9RGC9ZdX&cMl0LT8cG0dh$6Xe!0_B{1dh|KBD0S5R%mGJ>TqifinEzTJ%ETuBA z3>dKI*0tDYArpcV+y`l@KH14zHp1P*nM@~#dP-qBy%-AWtv1;1zxwhh86tVvAp6|7 zS11yj6X&86;zwNcaDD})ySr)Ns4qI!E9Y698_zV~cNz!QJL~4qrAjxGgXDQjE10;5 zu4r*CeFjb-`$kV_chc1DZyPDJ<%Go@VMBw}=pvK!+yPm90BOKEA?rnkJl8r+0+S+H zS_)!(Dp%I6^q%?ByR{kBlG(LW&0gtO0w}v=3wF8CHFGg|{ zz3O_%>=N!XXofv)IsV0OXxa-c|K#?O!vlz+IByuJc*;|V3}~y)>u34+h=vzATZ>AW z<>e#C@!p!)F6v|^^T>ZNC7kDx?m@j2QEBGUwKyz@#R;$00AK19MgJOE0i-GIXV}qr@i$b?sA?&Vh>`3) zitN!%XG}hv<{U%1JUb0rMHM7BHSXDMN!|faVMw!U44s_QwLBfj9KLtdz>ca_T~cpU zfr5bE*lv`5eBI&}`6;}mYa+#iB8|2a$FHcf@d_z<=yF9qL_X2tk7QW4YTE%ILk2~m zJ>hc;AphOIw3@%&vjk8TC9G5U=a)$G+IYe5ZMfD{GIqq+{&)A-0TN^MjR4tapSLce zZoUn5A%t11$P=V~2*7p{^R{yFDPCGrfaBF4Hw1Ue9uF1i*;V zt}M%$nNdV3rJ_B@Yj7MFAheua4- zij))Xm(=itusy1CW!ze@?_%uK&Yf@<$$q4;{nH*;*C)FO;IGxE;oCYFTf1xHue!~I z2>mxLqW$2W-`|%o^eru~GqA4j7}H zloP{$%5XWJnsG)nOr|}Dv$m=-Jq^Y8dgx|jz1ZPFJ3%85(NYbpLudmmQ9RoG! zo)df$OJdBu)2yXe~HE1$wA5AP+SzJPwVy zq>7LWxSXasrVOoBoEW@`zkY<`1&s5kto^ZdlZr+BNibsp7BeP~Al&eS6l{?jMT=Tg zpa_4wqXZxgNK2k{oDgDYQB})~)#SQLcG&zlv{x4#JW9TMGFP+w$5KP5Y)Cm+eo`Ug zrYCE#anT@a0Xl-Uw()+&Mc?8l{o>J|+NbIZVOa3V${PT^mwZ$i!)+x)mBAI7RpV zn%h(f&RE&PXLjOeWo>FEo2$bujS{SMv7~mZM&;lQyDMl%#bp;pM96&_ie96PLyBws zDAeZQD|Lx^JuRw8ltuwU;t;D#{Az}N3(MAyC1mt^P8{Fq8`sg=nu=M~jQst&mp#)a#nv2u~Jvp;l_rC4g3#)auCa!6GH{Ck)lZiI1w? zCm8V3wekJeq?PY zt^HIPQg1w42Du}4FWi9pm+n1zDh&|_cLb-hzbU1B6#cAN0`AcV37yF=vVyr?W>=Ze zoYt$S7`C7SUeQ4@f_uzx4HxheGTpq!X+3%)o{lPj$=icXjm>n%2gd3%4-SV-+Azk4 ziE4a~7GSl*%yq;k5zoQ+L?C39t-_J#JtpAznfkqWXN%|k$H(b2@p~nhxt6)2t1lR_ zDvgu^I^os8D+bldDLb%biIbyhZKci2W^DC=M}NQuFIT%*HK#X8_d&_XTphU;0Vsb! zFFUR*f_XYw6-1Zy(RNx|!q-NpsL!c;a#St4wch_;AZQ%Xl~Ll`MyKj8hj}8cF#wmT zf7Hj}VEaLaCJj`p_~g+EsT{Orlu?~j2&PkuJ;3Rjd%C$YIE*2rMat{@AV-s1n?FLrcX{4|-hhF_T*+Y53=(iG}BvhKa5f zj+ANs3ExHx$A-s2c7Oya5GcdK52@aD0`q=qnJ8h2J5-F@XR-i^>g!dN6tc4a9&O^_ z&R(6@-p(Gq=$?2^o0OUs1!wrij00XF zva5Cr>!D4tkg}6BosEtIS+fM5?U_=x-|BgT!|-sy_XFPv`ifcijFod8Veq}!+`r>% zYKih=?_S0ZI@5Q`^xCzk;gOK`&?owlvy%{a>yDhuYCPtqXFNx$*;JZjzsSz4+9^!^ zbta`9hGttD3Zh*-cM-9K#_r}SoGlr^$QGrPmy&Uf=US>&Al0WaOL7YgDAxMxCJfu> zynf~Ojtyxo@|wVsFHcjutuihR?3nsNclPve(h*j3V#MOlFwqX}l@mQyl3x_z;{VEw zoD|KjHcwObi9}-AP^L~5TtjQ}S6Q{5$-D7vHB#HfT!oh~% zs^iPgG@ZPk*l8B{ebou)C3FY2td9m|7qbBN29_3M7h4(QngGO9;7!4hLJr|{n z#~N-fTa3zPHK>cHB*tcK)%z={GpSzULHqEycsi10rc7f~{8Ws0{_QvEv%ImkAr55$ z^oJLe5}nCA$tXAgw;y8W5~UGcv!^E@!5XPhxACC+HvC7L>(|kk0%ISq0~RV|qfyos zu1V(MZx19T;bNhR)2c3LM~=>muvwISBqh1OAuKq;aOI%48FN=u??}uqhWfuvIE|y_ zi*U8yfMGd$H8=3B>F4yB>71@BmFJ-Ea|e3?s;Oq68Pi`1$6o4n#}5Ek+0hbphCUA{ z6*1Y~;tqj`FO_M`g||le>?!1i(Q`@{q9^E4kkinJO6JlC$zg25u(wYpsIMKx%Rd%y z3;|bW#`>daotZ1Q{-VosLpP+8OhW>XQ5p?yYg6aHzH01{2!QrP{ULG(_gbN*_Ov^~ z&YqJU`Po-vmmI&cw<%%nQdzq;WQ6II%~V@M**Da0qnRJdAD%LG{NQ%v-6P5e!9BG% zz#NEqRo{lGNmlPEiYBh`a%Wmjpk+<`G?yBoW8WH~n`;X`M}0Z-xO?Y20>>;eAQF^N zghWOvqOc6~aax91gN+CsQTgp;GXV=^GYt7r7GHNvW8JcZlz#~mfSFUCQ>fXY zSHK(W61&~OH!u4@(1RA^ybUvXRP_gDGcI)X$Cp8u6CsU!Fg0-g^B#WHLl$#G6c?6Y z%2wpc<9tNV?~q40krO{sdS%LzVw$21i90z%IqjJ@&CG_>hhWn6M$71I9*M9@`Nkyl zWA4(Sy>7D)aaJt1gp(j*jMGh5Z!<`%a26(Q^0OK#a_wB&l%*YU8^p94Y?Ys*k0pNv zQ;UggCMT1bs=4H)I3E&>!;FzfT^6QPeV@1*r7INEVbC&*sS-ASHXC1hLnu#eR~}k+ z7vfkWnlf4oCG=Xc+v7|o7gLsU-6Ew;NPzmGIg$QEaesOX4}CNEl}qCtxzJxwx(Lj( zdH{DpqR#vkWf5+U$H!}fWYI+hOsj(<%9<~c(WIAV|t6tVuB!eZy{_9*_qw5-}JsHWD!qEsfBTk?7sT zFof7l!hJ}^8B0|uP&|dBaVyZ5^bB`1#}K%Tz%v@t2)fc zx`wKFaW_{*Q~Lp~mKk*B=G3V!NFsKexKek^R)>XZAvgUZr$pp>YzwPPT zzA$Z1e6By7eU2?T|1+6E#mx18jD%`>D)?$R{_^!B2@`Oik#NR3^sAujI!2Oa#k%i` zB6Aq{5sLT5=!-vP6DH2>S@s0$G~U4+vQA1Tx%R5r-YJKUJC40S&0l{-8i5-?ThUjGOo{d>;D#%`dBdqjS(OReS(RI zTir5s)SmQb&~y#a>U+MerdX}=O@(@)a`UZxt4SnthHjN1f%nVwqglfVgrD-J)zcMr z3@(}+S#(?P?%8^Ci(RM$vPg^US?d+V1E7|`-f8KVo!Q2>w8Ad(E?Z!PJfmx;b>3c0 z&?Q^C`)^6~K=i}4bpFg!x$6Cxa%2z6?yunX#mmD!Zr#VWZ3#7|^p9F~95)<;a=KVf zL`Sk&m^WC17+CKLzEt(C)J5FrPjz;?E_~DRoOM57(UFBdKn4%mJF*@&6L&Z^4;WPo zUwFD$>O%d{*>IL_hWo#CskXkaifN>wT!JvU-lbMxtM~G#Bui1X`6ica*rzOlm>&z8 zgS1OIWKE;R&-B@gihGM~nf=@04c)e6{=$onS|nxt?BiaVDmXj6HEn?HPG{B*)gKsj zvhZNY|CxN=kmf6{4IXxs<3GqbHYa4@}@1jA8fIfY ztL9Q&7EeeT*Zdt4#^&FuE5hdfQalN9pPhn$ACR=gL=q9!D6mBafd1@$^A__>#c8&7 ztkboXsAs40pGEy`g_#wR5+KrkfV>b4zhM6Y=osquw#%&_Ak9!|ICEsFE1+WL7dMrL zxxI)FL#%&8;50IX73t_0<6b{B)i4Ue>V4s5G@geRB=5@g2mi|rRj+b_eg6pCXu)zR zcT{3PHlpkwTRAtA%0jL=JJ@J&GiVYOLF5}%bew;MLOkaz+)K?Pg6ad>IYuUYs$pTc z-g7Yi;nmjnyZQRd6nf0a<=ll`J znhIKHrbh``@NNg~*am!K zg1)1>JY|V5{fS5jxo-Ylm4U_8A=aU_xpi2NkRXyLg68CBoJL*I9?>Wr9gi&+LY;Z? zYE*Q4CM*j<8WAzxWYM7u`rXR>yr?-9doJEou}cG81mG&&!D7y@_GBO9OLFM_v0OdO)4HKf*-{ zXHqGbO^`zS0cXEcVtzDJm865{8lem)GNq|{wsFV_y?hSQ?WL>8yr zZPf0Y&mah$8+P~z=ZSALX6O8pe1h6E5rEv4U&|)ozX!TdE&xI9xZ0{M5?eqU%ef>k z|M=KBoO$4v?9QLJ_9k;4pFy_y{^;kKxmf6mSM2hAQ~QHQ^W!%i_|}r$W`5IGMlk%q zUZ(uyV#g@cjV|^Wa;SL^v}7oX-D7G(qxvI76snENKx~+L>TrXZ#IVLHq4Z9aXDGYy zzMXa(m>9&ju<4~^lBS%zi$ai{4_QGCDZw*Vb^K!8)pWY$4JDjkaWuck+BuJ39q93k z_u_QCYC!~W`3{EqPrx@rM4!S@6<4S@1o0otj#s#34e@^a_8&ZLIj>Y3-FZi}NGud0 zGm0`m)u(UIQcYiGuF)yI+Etq+Ft;|g;&=@t{6WXIz}A?S9NQF@$iJ#_^s2p;Fm*P^ z=2`?spUy`6ZG`raNL!@Bb0p{Sq8{DgF=@|X2H&2PYs`k5G_|@>-40Xm?gqSfL*!Gt zG!c4s)gdsn`J&L}P>&O06_nU`P~aSl5vEj!1g{#0af4p1BgnL>%O}_UOVTx^KwfrG zswg_~fygYq+bG1f5`Su}Q~jo`cyLA!tmVHW8b>*90h?dIz(PKoEZP6h@~L8EZl>XE z<@(>o-D+hyhq-SkerH@xHkr*3qZTso7%<_gxnM#{%&3?tWL%Bb$ozI$ZVkha;_Vz4 zTf(~{yT6gQB}mP)rz2p(zY@4VU5uY+dNthr9a?e#YrYwlK*x`Cth1WYLw@47W&NoF zO77u(&yi3Tlh95PzG7+A9~Dk%N6_H8n;Z&7us4W?=)3)fwJVp593rsY7sYT9DDCM! z+|>A#KEpUP%kuonhpB=FVqt>If5qh#kGp#Kc9IyT;0eG|8c_gn1LJAESa-cm5tRyV zbunkOa>SrBu&LB|wrZ)+i^rWZ<|szRN1C^gbij+uyN(Nr#JSw>~1s$z}+!1cPS-N>Fw)0+7TerVib| zfz=Ta2)_TMFaQfTzl3-1CQG9H+6|ic-QKpD zjkb5Fd5F(WND6x;SdElW(E7OIhA3;laW>g48R$#;jx5*Q&t+G5>8gdQoyG<){r%i; z{vlMFP)PF*id^b&gW67T^0B=vnC~bA*^giG`aKnwpDT*ZQbJH%_$UzWK{>2|fp3v5 z4P5J#tc-OTvhNJKOv{jm`)UXO_cFZepaaE+@HGp>{~rN${zLcu-^=iSslIh!{nUYk zf2r1wD4CIANJ;jE!0fm}AyH9LIE3fI2smJ+Dx;a#Ga5S96+C~6&gov4Y1qctw}$Js zTFGgYu4QiK6CyHvfTAOa{lHz z%tnZRt3inGWXTlKedN?m&_@+v;L;(5^gOYD>GBdz;Bomv^(O`J>Eb2)=Q^nd+WiXH z&T+K}N(^_jeNjXrTA7c_jGvE`lN99rN_#<#TBtDP5eU>A!o**AAzB@W3Ds)}FCC;W#z>v znJ3Q>?QR2=9m5_;O1HQ5B6Tf`kcx6Fnk#IR;rsq!g0$Kq+46H;EetFBmBshxs&86Z z3LZR721D=okmVHv?O92cm{C4j;W-<6b&0(_f+!F43t4|97oKwQ+6sK0UW1ee3?=nF z$9?e3CDxSuCDoxsHQjw}OE9ar@k7!%j622=>bc3kzvgYZt?PlgO)N|}l>%+svJ~_f ze%rS}fXAsgE^&zkupeaL@02^)+VYfAxVY8YI@-cZl`%1$-l9+p zDdSKTJY#Hd*aAG`D|j?Qk^>jUsOCHTG?Xst0w_Mn^EN-|-~5ZH(*Pz69+{Y#%@B}1 zNdglJo`$7|IUL=?C>_CGEWLi1w7HnvYJI0}p6lkq3g2`oiYn;#vGsV_y~f6-hRdg)19vS*4`SpAptWk}Aw*{bx#Aa?tRirh&LSQ^ zM)|V{pG*lS9EU0#-)ay`mPOqFB{Nmi7+QD%pOocv$|hoXJGo6GPP$gu<6wl!7&@vjBtGVvmd;h+Q<65@J{S?Qhn+r})dg z+@7$+>_k6UhiC9TepkzylhE8=2 z;Z!hjD+Pk$GBw<1-a5Uh+-dmkP1oGOWn!^GxTPm)P>yg#B3O(gH;1NqeZ?Z|qlqC; zaW$_4*Zjsl;{Bk4=TT_G1ah-$6xIzdSrFFR45tk!lub7xb!T@Yjjv5fzYl zUB(_ex{99JvmxRR%~S2z;i}A0IdjFY%hi5xw_aN6Cz~-eAm$BInW9o3BK#q|7l`cv z;GCWZ&BqD69!DEz=CYgO%ylYM(LJ-c4JD+q3C2!|X;W2%(5yur4BIRz zs;buv<&E6{&%%RQ1$pS>8=)zxEwv*?68GmFE7e8lCdkhXo^H4i7`n&Utt z^quD90FPm~RXhArIq1@Q1^=u)r^A)~pT{24RxmV3!le__*BQUl4_B}wPrnO9;gLM4 z<%Ho$Bc(5oq{YtGb|~AmZy_&J|HMXDPsS`F-`SO2L%n=qs!ymS>GbYqZclW&|KrIvZ9PNnhRut)EZ_73gsDo#eq6D;9x!c{8 zIyCGo^kj-iB*I8!t|~OL<(13pA9%tqogo<3?Hs9(bZ97`z%+|;uVX@3Bo~i%z{_{! zeLQS&vOy(#3+tC$|By0UfrG)cFb1sXoG69xtbPMLv+?RnVOu8J6vRC9G5vAY&vklw zoB&EKbiA-DR%I5{^6G$J#?>#pNRSc8$-FX9A3F}j$$+I{D6dc%@RhiEAex zOQ>We2g<7^)#PYfgzEaI!&x|;Oft*7vIR^wo6H(Zt_=AzAf39a%=!0fFQJSc1c$LQ z+=8$HukQr;rs~6evg1EZJ$hz7e8FG6*d=02{DgE$Jy}-KlKxT-S-Rfvgb2u8(VIt| ziz=R@)1+PoGhz7nG`0lcLpn3$OoP(jOS+t#k(nx)E`ed=7pi^4lk9Ubm}nmA1oYV5 z(fZB4%BX;9L;WRX&76T?@^@+M_rl}K44fpi` z_51o>{MY=Zsn5#sz~n+~TYNa7(rEqb@2~TgnS6idnHZcNS!+wYDFRK46k1S0SeyYl zeElKDUf7Uc$Pyg!QZQ5KObjSG+LRniQ3U#AE{zgrk)r zkA42P2|`(Bg1FMx1K07(&;37uVE@O%M8m=5KaHryCU)i$UZ&(rB3I;3(|?r|$eZAmkdmVuUNOc6fm3)<~N6e|3G6_XKCw-AwdqHzTH`GZB0ndH|iQQb} zCcN!G$m532xC>05dQ%=X(W2Yu$J&i|EAaKrD?_3x`wD~c- zEp}roj)I8qzZQ~W6b2_Q1qu9D8_d?m?!wTBo*zv?%0Ry%($m0%!k*eD-6Ut{f z)<@%d=GZ*zJPMeMX2`Q2(5o5yw)XuUBSr4T0R>a;zV$mJ>)?OL(NKEhioEAxgf zCR-dcn}gT&aNo}(p{?DI{~KsWHLJ_f^6Piqf3uv7(1zIB1kUL5L>N8l5=~qaNcPmd*UYzch~0>akRRzx_(Rol>Q5RKQ^N zu$cMdo6mc?optl0&kwvIIywv%gF!o!y=K#T%92*QA#u1EH(g$vqvn`H);bkeHR++W z1edA?`as!9>4nj7vFNg$>+Di&)$@@GpzYi_lend-FrN6kt8eSv1CMPB9Z@OU>=@=Z zlj!R3XNJtS2YI-xMi?U#*nSJ;YVmPrT%?rlivlXwH2j}RMXA@G)!Ubvwuk4?bv{B> zV=^+u6_tR+<@&orz9(F?UM9e2v(wKTxb1){gM_h_lzehmhjAiaNL3k=p8m<#UVyCoY%E*k&dx0OM}y>7TmgA(AFy^kzqlFPB|zb7Q!Bcx!X_ z7-)|;g#dHnr%tN_N@Zd;V`iU^K=OVOHRq2=urGX7DbC9%3uf*tCZ! zB%Y#hu$^hc72_b{;=s~i)T!nsV3g4oJLGM^eMBxGrnUOXUdtc8AC5|EOqR_`t0lgM zUcG-`iw?Vls2QF+PZ<_kYVi#t3%9++LRdZT49fY8=9aO4s8W3;XOsG+Oq`^WproSa zU#)}N#Oh`-=O0Ff)w~9?!qu3cXT#44a^%w7d4v5w=Mia~>P@F}(`3$Y3kZ)I5fG)F3ipBK@D$osg*Uur{hDE$-%O}5Am@6q~5jQfMpzhZXV zWnYp*nd!CTe@nmEK=y@iQpD0)jhM4JVDIva-@DImRqZjAn#a;tj(&$k@6a2h=p)6t zQ^0+W9PDNLlXA<5yPNLZPvSQk0}U#SjTqiDW+oaeVe6yc_cgQ|cs3WH>Pz?v?g*gC zCYE_=jHNdF5EtQhNCK(B{vh|N9n^L?ryJTpVrNBJl580(Bx-31^0L!n%WUBwucj;x zMv2V?0LoDb2;-rByE$#0YA|DdldFHZEmjI0fMg8U51FXVGZeWiPHqQy?Wp>77V6Yf z=aINRmNs4)`DgPUz^sEy7IIy`ob<>j=kPN$c#4Gr^HT-m=5!2tX{g!DrLRn?xW&`# z?(m1mG=wLQbwW*gDb*xZQ3(wAgHbh={2W@MTq!hW#LP!#XlvTUA;G;g6$f zl!fRR?!uE*8?8vh2#kBAR$ks9{+p!!D$$>F8fDE!DLI~}Q#T-|DM_YYipiO;Z*$~f z!Z3V&q|w6y!Dp`DPo%?dFUI14*01{+Drcyhe0 zek|j@-CGuMP$@npO8V`=A?ciGTT=>aq;~VnGUrcBs0F)Sd1ZTb<(>RgSWLL(%URK* zF$~j{0yc8vW;dbJ>SV=^(NXLMS^U1=ziXk6q|kLa=ZF5Re4#|D_LAl3N1LX3LcOqz z!6iLh4Ye78d=Y^7T3ne)OAQX%XaOY$?Js%JyNA}$Dg5j@vC%*F5_+tG)DlCan7@YD z>HfxAYJ5P>R=w2BxlO3#V;i5{_c*dMOOpWjM!ikMRy)D|2A44I4S?g?2i0E< z)iNqq#%Zaap^9kUa(`?WDUB0|F8*L6f{8>#;PLg*KD8uujUgA2_Dxbw%kXHk9&w*0 zVaU?aM^a16$S8shVpd?C?c_(YHN;Xi$`?ah_&cs7a~DQK zUxYgzz|Kl=HU9J<9^`$DSe&fGbI&>Sh{cI$V)5Bm#O6E%RADm{c$-jE_;}CctNovP}{N;?vRZZ01UOQ*+lR#^`yDZMQ7k+;uO4 zLIqM1-#+r4$4JMuoWDiyri4F3iGSw57b<=^1QG?JCi4F!K-niDT%yN9k5pO;aSLyafIvb$A;bKN=ITggqb^)=iIofnF;+$Jz z{lnu0>sVzuMca=2Vzg1*N$HvJN0@K$v;ki{I;3ZkqfWjaXVfuQyD>rB6P{>3e}f6y z^i^J3pD>A_mO*$IQqO5ULTr0W3E4kIK8qc`-k*y=^TKz9R;>sqhml_ ziU7GWue?jy;d#;yXXn{KA|wXt8De0LW7ksLt7S8LWdwdaPW|AYb!>f=yIZ^ggeTUF-MF&J-t?VXFx+noNJE|YGgxyO}HWpbUf)x9$l6L6C!o5#>4$*YXe-RxO>DQ&$sl`zVejkYVV>Vw9` z1WBtI0{fSqra&lr`RftsSl{xyN)G9XpBZ(is8StHPWF~{r2SSL`*_3Z;?da{BoUUDeG9(z_^0pBL2}^ybGAFWgOtm3f#~xG zab6mFhm66Y!|H|kjJmDqZEdX@db2*Zw%6zXdb8HYzv^@0-!ELO-{||!^PlHFN0*0w zB?7QO(Rrf?U#I-o`&)U~+2jUkD0rsC!Dt}c2a23mS1K_)r9MSSEF~M$NE3yAu-vN( z(>@z4y`8o-fDU)Ti5FbMwPqx|l}q0kkjABN4p`=@J#4j2c4Ex82e{gKF+HzR?)yBf zHSB~VqH$4)a8+|{83CvqxF*klaa>zwV#F!)WJN#2!N@|0zQgL7XV$8D6a5Hg3ul>1 z;0mxi8O6%C%5bI+7!5z&bW%n$4BYerb)y;50Q`C)sr#uH$AB_(!5JV^eoy8!5F4)S ztqbh$L}S#OE|}j(Sv6+TK*Fsq-H|-6xY*T$=hsHLp-YOMopA0k*2rGK-fL|HC!L&XrUmS9|1t}{*x6)yBECvUy}*Dw};PMetEmU zj52??Tq5H4WraHK2LwETsD34bX3_fMSgd4duJq1feFPtcT6y-6Yp#tU%BF6#LV!X_ zG^ODv$vJ*OKFA@YBg)UrSy6<5FB6 z1JlyvCVi@;4fS=^Rdwy3&^hsHP_B~lItkZPH(+~+dnpN-Ta$e}i}I(}0&}1o!mS@U z9w%&KM|`^JOQE>mml|wME0;Bv;e}lqYOd=BO_apg$Bh!1D%%=XPj#pQPF5bG{Mf6wJcixv`OzD;-)N5nl_2I&DasxLuj<=oZ zOH1jaWF#%=BX5W>olrptD;Kb6d8Ar0g++A|j}h1{-u`hmPDK@@1TYR|l*Nri##;mQ zFc|o#;Z__DSu1-s2rZc`weRV|C7vN#_WB0Lu-xQR)<&iiRA+X1C3YbtS1?iniFK~2 z3iy`H2oQ3x4J(VGiA3gx*&0piwry{KX`8i8;pqI!M_<4_YLg4p+(>B}%*uf>h(2O7B19qhfBk>XCoKX9I< zT%l+2(9L8pvqxNAO+1bUd8te1FIHE0;(rc3KugO;x0i9EAk@V1d8iVEb;{m#=r}*| z3JEJlW3$gI@!2>o`$^hS(d)SAdSnn%*P?q!0Q47ECfuYjP#Wp-7g)u^nAoZ#1t~et z0DSFHBAiPnvRBzo?%@xZ{@;$EL*R(rfrXeE z)MgisNpmSJ9;&7am(TXKrm3ko7>_1(k6xtBPR~w1#6Y zSzAeUWxY21;6SxFOMv5$STVndSAPc~$VhrOuuMb-vwx85IGeAO9Pmr|}3UHTm3{~T?mwJe2c~?jGNF8q|k+4G3XJF*PG^76zVZ3a1 zD@Ghy*xMZ-+MxE;grK2nd&Msi$@LY{SJvs;P8W|D9F~`kQ#~Uojlwgb1GFJgWlH9p3gqOLY$y zg+j))LZ$~7g>;iYKgZ}X&w_~MUgCpq4jAAR!L#_SYp_en3csgS;=2_Zv7o4f1pR&n zVb4h*d#ME>btna$?tf={>+RFeiceH>Sod zCTwPUgw|JhnGL3R=5X)(tdOPA-`U~9H4{tz^?TSalLm7f5nnN_Z-;1^h`6H3Kt2J_zfc!DKKS3U;0SVqRP%-PQL}FGjxq@U zd$O6s!<3?2k2l#@@TQ6UNKHN1rcehWGf1%c`wH7J#$Hd-1bDmpP# z0o5a5j7%n|VjowACR!ScR=uwv(o3e4j!dDH9>gFZT@M58jc74YfZ>%uWRi$e z8;QUw9>l$;5DMJYBqG_w7DJVM%t6FpmFK~n8o7K)Jp%s$3NDwKV&=(71xJ;H68YtIF7F$JU zR!3SNAV|n#vs;Y;<($<(dfhYTPTP46f{)S)aThuomz#-A+lV+oA7~5l(Ti3dS;5ks{i;LV7uWqvAq1k;dX|JWp=QJXNrjz+vZoIR8WH zQBO)mfQmrEIJn*sz9xHbf~?BROL4svedp9{IFH0f#%mHxWSM6xv?V8(a|5+P&<^2= zaz&tJCSDDgHwBYX*i_LtKTNE)fOm##bs%G3SY*qV!j%@l>wh`F)}mONaOG&&U$saF z$yo~(m&0c{69t?EK_RJWP?r|B)5I%qn<#o3=FY78>bmj!slfv9k{EZm~$vWR}WWzlQed!jRWVwcJa~wppGm z^OG_Onahr!`div~an^?DmN;i_M|bej9%UK?F=lV;9ju7!eJ_gGa9qBQC>8QT>8ZXEM z5d|Q!sX@kVOEyl&Y_uUvR>S&5#&|)W;XK-QdCjR&zl~jwJbGT+1e{N-38()Q_dqA}G$&@2eVF)O z@0-w_8SA-?>pIQPLtVdfe<+cHj&DGLa{;MSYB|Elw;UIFntS2{)He1qwoy~JmXKoH z_MN)?I~GCW-)_c-gCXS*CDyZ;9pE8*0>%N?b)&Ug*&x8P!xJ0kyBG0ouXGowBOxEK zAK>*3g{ZVG(Cy*}#xk+!XGF;xpz@!z`Y)bO0lZ~mh0k7Cw1`~`guEafV_Q{<5kPE zF^{Mt{?8rjo3A7O`jbHb`s2o1clNVj=nE5|VsD4iY8Z%p{_1*BM^ziV6XJ>-oNCMa ztxaFx+tXGLcwmC{d!P6$X`=9~Ny89X!=bTs5ZaI$9J8(KhL0CyK+!%i!f&)R=^y=} zrf*Cuc)HA}P4_K?KEn0Qrp`-~FNhfS3;rxB)Cb_Vm`veC#K72gr?*QJZeF4H;)n7aj{F1py zGm1Fth$SfUJz@T!c#qhqg!aRn40njEZI(7-2%UIma1tDEh^B61zaKnQJXHA@@dTmK z!KX&?=HZ+GU8ggLOw~l=ybaitEm*vKeW`r*AXLf|UUX9w`*xF2Zsy}_PK6ixOsQE) zvq(8o-1MY%&187ZK?-do_M&hL+wl&q1y@Y3LL{ysU6{f32E>^l!UiOdL7z1-_Gb~n zPH%GFJt&voRBUe+ULl3MK?)$+m%ZJ&U=Nr=@EHIq%J419ko8=6voSjs0IN3GLjbxj z1b^QTVGyzdv27oz1I2St!V6kI7I}#Bg~x7G%!#%SCgZTi39|3I)_z3;cz<|JSYRF* zVm+qV-n$8bK6UxvqY2d(N%Wp%BZLE*yHkudbaz)XhS`9BS7C19$7o0?!-{=>?m5=%F2zG33*O z#+5u~=Vxz^vMVA_KDz49a_x0xC_ZeUG`#%{ZU-g=L`4R|!ii`XW%@%3 z<`~gw=6w%Rq!}X4Bo)j++bY<%znCV!+X?b)6~D`uc~A`A@3qKhn70IyESmBr7BmI7ugGy zvz~mQ-+2_-KcfOM_FgO8#DTBrwo~h*ixrv`0c9B zZ=a{U2-&K*DWdJ1>A@Jp7JaC}`t(UEb4s2(GfI)CB+XZlkwCuWr8ue-N zO`vpPW&uoeB8k|VQD`Em9u12Q1;8x{j&kU`9FRnt6beAc3bqoHG)E#Cq!b%&Z-Sst z#kOz3iuDIgXO46PP+|h+785l`?!rOnK<%gx#|GDuh_ot3s4GM6VO7YI6BuRIDVP|I zX1%7k6t+j$V%;TVbMaPLby-qgkDS#dWOVUnsJ7ak-POksUw?fB=_BryJyN+9R{T$9 z)nM-&5llzECsTqfPl3!mMfx;R`ZP&;mLe%%g3O>W%Dl0!aojBFKh}|3%D7+3xC&Lu zIaEq-Ev&pKFtQHV5`Xzw3iI<=&dS_R5u0@N!NLJ@+)PbxctA!>Hae9b31dD$Yv z_d2R|)^>t5H(xT4XOx$e`Hz6&=!Vg0$x{^I;MXZ^%c;BS89Z@vvnJfjKd_(5AX$N) zy@jTFl)B}CXH#Azm{>N;jepN!{op441O~3;BN7xOUU&Ab8=z;Lm6REwh_Cl9*?zUQ z1R4A}*p8o^cZX`_tjDkH@!}g%a1k8HA9bqPiCM7#TXz&#LvYF(D!R^6DWWjHDMfOm z=t1QvvSvRmy@e>|TNoE>+6>r))f?k$a}(u9ZIX$wm{VN)^3fI>7?-hE~JAF6ry{1)1CN@R5ZO{I#R(;PFwGNZD zWDfk9JWQY_UTo2)w*$Q!7ib_&5_?WY*n-|S+8e`95=0)SpjTeCVZAMf)dXKKKwX7( z{Jfd%-N^U}xn(A2ZwAsEpxPjnu?xd+{?0SZg~PxUta5-hhQ2utVlLBm;blP6QWoe; zJ{Z@Sqwx^d))#X=(p@9<800+QTcdOc-~57X^>?5@#SjmoIx`E<{|UhceuC(4zxB`) z1{R{4Btp=SBJZ6mF z`^yKOUMVkve^7rP3f)2xVK^S3ABg#>_Jl+LeP6n~h!{kv4m1=9+suNrDQy79k+_`WSZw z%E}a%d>Rr2i;d4a0_J&QO-TvYo9ChcRT4n7c%zTR2tjqIskx!hkDg@a#jV;u5oK#ZpwUb>_k3=trPdEp0}@ zjA7Dqk|;^4W1t*(u$~^?pREgj(){@m-%z=Fk)i(%Aa`5FY8Lg2=EP+`rN5 z&(*qhM+Ru*uwq+dL!8rZRam*9U0D-$N~GyuwT0iG{xF-8cEvjNWjL`Zesv>vr} zmR|~C;cqj(?zLcKLh{rNk8e<>Pu;ce17YLj zbh3lE)y=xlEtA9Va6J8X#mGwUIr`ub*YA=bYfoPX#RCzE^-cy(m;QE}_fc)?C-07?QdcBd>YN?%t2XoZeao%U5MX%y&H1xb z4L4r|RivFd!{ZhzaiE>1cOK_e)$H%{v$&KDjE(zLD-p!@|6A@*c#8k zyiY=y zZ0hJ@{-0Tt=B}<^`pN&$H~p`fP^GMWUmZ>PpsCon+Ba;X(;!9oMqJZk0mo zvA%g9q8wt<+L`kVumF_o*`pYC$Wbkk!K>-aOWavd!Q^*aFfd# zSU7LzS@;l%1k}9I?3sl$pQoN?ttu zF653!?8J#(mM7DWIR8Q)Y9{WOVwt^NBd!q|PtnK-pd3ul+F4>YwFd|pOc=+XLs_U< z6?WCpbSV6(iVTN!7PO0}lhb8eTzQm~a#ol~lqz*|01^itQEnjz%!}LwM?YWwn;b8a z?VkP5*Jkzn@+SV@Xi5Ly4J-NI5*yPrxBnxA=HGU*#iE-WX&I!1o)BCMEtj&gI7(R= z&;)NzxY4gm#$mDU++l4yb%pszO<4rwYbH5L{nJmmB{d26GM1g?Fy=<+<)nY#YvvD^ z!Y1gk;WXdX_0Nspmg~%P!N1Qp{6G&s%#i>B<+ZhAjLEVKs=mf%$6&ZRH(L@gyKXm@Ev8xtx)#J0N;K<0>_NPzCJT1!G zb_Pr@=saT*?7TxDKcNlG@e^`NG7YHzfd)sD7weSJBi z63%Y1FwYP{vFgWc@19U*zB8`?&~(xiZZuQ0;mU$rUpPO?g&+FoOFP7vb&ja-l@YVH)DKYW0-UB<4PK3-H(8u;=+nqG*Mi$lG z?V9uK+uqYEi;-b5iT9g=>A#S)8bLGA8d4hsxU7i3MbX0a9V*0L6)<1oqxWe;3?0_q zZTrlbH;Rvyc;ZF;st?%h%A$*@RQDS`cqhOAp~9s0K7`+?bla2#Vj-E&%hT)}*Sw%P zS7Wa#56-(b=B1a#jk@PlS02v88}4;VjfebV(HT2ch+(L=_%UMA)*|V;5QVtu{YJ1W zyTx%|AH{j!3Zos*%X$GOcc|Xw6LiUzRv`V-+ zX=qoz`h_~+t`nM`+ZxBsCV~OOo_Kii_v|TWKCCQo%XdYFDQVqW(s@>n2Tj4Bhc7Dq zO?eJJu|xzGThieCQrA?P{G+d4LHUbEcSFXL$AnT){qmgGKRSPnq>cmntg~*9cCViB zZ`Zhi*wzP%7nD;OBc*C44<5640l77PUGPfv8Ab2|?T=2D%}A!w2z)mh;ZZR+Gk%%s zirCY}5Y54ms1{r+IauY$zG~uV;ScU_XB35h*hu)#;pY^o{SC}N;WF+o&G|5>``;$s zT4UtBg}cu&a(>6RDWJg03+KVRe@i=~W=8H(M$GR*1`?cR_ny-t>jDblPlJi5=Exy@ z9*7F@{pYriMQm~1=Jl21gw-Ij`=G5Nkl$2M-6D|j%;tdXUORaRXohVN^xDI4U1{P< zB>kd7D~8ED(~l~HRTu@2;x2|!eK^>`-1&UfXCSq~4E))-Obt<%%eg*%k z>H6)5+^LESi--ep3EiQjB;aGGv@LRjaQk1jkpu2`odhho6~ypq-i&ASU7xphy}#Elo8DnyscEZ^@w4JOP%Y#rK5rryT%(^>Y~`k1HzhBp>Q+}X zsh0|-R9@SuCOF zDVyJ39*HXTL+PHDq4b_nfuBkfvtg^-Xfo$9mz^|e1{>;m5A}FN8p~Ap>$A-OS37np zoKH@JW60RxcM*XEaNu0<5x}o5>5L9ez_Vtj(&JQg&~a$O!x@Tr#>f>_H#(P(1dE~8 z;6sqI&be|;lV4=P>`7RJ<0_6Dr-CGu_h7Xi;ey=E@Nc?d*35}(JI$_SHcY&`Nz8zFp>7U_6=gi6?k{WKtq zYL6`8jQ=`Ul@MpDAQ*dV@o3$-E%wg~Gin1hQaD1DJ}pX@8=rGmAuGu%(h%)uV)zD9H)!>8 zDyIVYoCo7Y$$svf_Vzv5KJEVCg)66E{}H>KOoE0YArf|KmY8`ecNJ7P@9J@O_i@_K zE9Jue@gNk+Yci|=o=Ys{n9=~IGax5%7}<8psg$J{ImVnlBp?S%{c(bnFbT8d8-DFpB-g zg3ECP?ZNz}vOqT!3k1M*55(yP?CI=#ehDnMIrS+&@U}w!IiKK~sUV5g{0x>NLr|*R zL5DdpR2>7vRMyBnH`<8i)oVkbN&`6LML%CXHeNOG=bUN%pZH-3bu45b+Q21BP8(uV zS*KeXD4=>M0lC&t#mFGaF-ARf2`BhIX5+d5|6)5ObkrsJd$CKnkLZI<3%FU+6Y9yE zd{)jIaOzHf;n%g>Md|R~={`Uh@zdiKIl2o3O^|b)@xDj-Cpx;`(2%hvZf7W2vHGB- zOopdHafv$#9ZB(VvHGOs_shemys|C165JfPJZWbvLiqyetf4fd*g~%w1ER(PpOgN8|M$|q|X7%{}4tgl0git7dh%SrhKri z5Klvtq8EklWrynxQ4ZimJ76b_5zIdjW6s6>ZW%N>=UzCyb)G+Z-~<^ZE16Bg{U`s>Kazv{~)jb=ly>;aB+5Ln&t`Wx6N=nhsyR%R6p1*Za@)`CMNv zFNh-W+8w{0Rr(@h4G91X&o_OADOiVFf53JnOXg5H6#Su*s?1SB41L)^X$;~;acG-0 z)g^mW0-Kt9)<{c4ZHN!=x8u(-LVOF4@`BO$Wa4<(Uz6m!C#6H%sai?BSFNR(^qTMp zf!UEqs-zq@p0RuMLm48sjlf9UDt|{)aD+F|MXWa5Y`Lc9IWm71h)wFNFv6r? zehno*ODFMw*1B%k&Xq}Hc^KxEkFWCeO|~tMwg&5>3-#-;a&lzG@l=1KS?S zmFC|N{DAz)PhA$z9bTLPv)!r?m2?ndd+pNx_-=ZQjD=Ux99}B9*2aY9cOjqyGz8yo#Ipi zBGAjym0Ymv$5UD%eh!XR-MmF)Zgg(OAiVNp-3My#8+c3EX(WT7OgJ&^A7=%J_!NhoE6(h*cmc<&I@IgrdR*?o;ufub)2Sd$pNY~2sAk6jWm z7O=?djS`%0$%Zn{o=m=^nM=aI-7h?|{G%NN)Cx@P+CI@RBY3(#__b^ymTz04k)h*Y zd*oCZr77l&5jx9Vay=c2+l*AR`^02Fi`~r~5*PdM8)=_V(}Y)|d-WbI^S6jm%tY2E z4u$VHP9fk=_b)*vFAc{9FblkNE6dy>uaR9dp4!PRhlT0Uhg!sS<296e;a^qAAl;Dez?(ODP*fL{`(f_DwRAso{QG!2 zq!N3j^=-Ay`%X~&Pcl~j3ruVs?4ACVv6`Z0`<=0h>Sq^VqlqrO6xeEINu>rkiGCK9 z5~&PLXPdbQt~ei=HA6;g*LZdfekQOm=QhglVPLI;$z%Lc%<>8AAAZQy#Y2SBDl#E^ z-Sd0LIqxC6t?TRk`7gnbQAHJ|m?~&9)bE_b{_05f=?cz}3u5>w>xTpTURRv``>tNXY8^XPjuv0R0NMuH8vJ+$CVe8J`=5B9^r1{piwlW49Q{f7R(E% z=Cs9hjn#XRZToJ6?^lZ2EfMt>t z7Y9GW`k@v(hyC1>ux)o|FcOX^A z$@`^mo!9%%A>VIi%$kf+_D<_YJaj~{%~ZO*NR~nSDwXiRLXflhCe6H<&n{V9_{45@ zCCy_miJR_my!?gksdT(Ti@G@fP`u?92J+wCV3%<;D9&9MVMB2cgtqp5t*Z z!-smMx$YL_TnD)-T)(;p9sqM5ZG!l~W3=rZLfGt^^V-}$FZ3qL+?5gD^D4NGC{3EU z6Zy^bh;B}v!ZhFHAF4TYDe! z@N4Sq8?E}kJ=U!Mi_u5H^EXklRuU14B>H1Wtc)z2{zY zZoD6!1^V8>4N!MU9q~9&i_x}Zi9=

C)v6P)`y$BM{Pk#%)q3d~h1t(rFoAB}Z)0 zqPlgJ@0h0i2B&jKufO~3_|*(5$M2Zy3iAc;oie19Mxgy=sMXPYQbh@9!K|Stfg@a( zDkr^xD;_I5?66a{=&`lts8XU1fvtq-v(%=F9*SKa%*1x|o5If~bV@J&G>Hp*uPsm1 zO?OaE?F2JSuz)S6_$j?<7HN)2P(f6HB{C7nZ8j6hy*)Zq={+?-wL?QOKo64KgrHqY zWlS?FYLAOq>`!PXfYv>iv>9`dANI|*bf`LjjV(8+u+2uM>m-L*qSUE=W7nFe!j!7) z!HH;#@3<8+={%jt!SiX=H|3r&{?6(Z*I8=m{>~Hcx}>fLLzPI3qR&v$JEL+c}!@n zk<*<`~xh@)DaCRrj;aRM7V-C4%}6aa?#vnfyqUqYWCdszR#9R!3w!u3$h8HdJZfeaO3(<+p~^wm z81d46y3c=A@{%IX1Lfg(hVm@+w=6`C#+M6iar%PYoGT>c=QZvJ#Uk+u&Jjtn$`4XQ z<&nt41m9unm(}e7kz0a$RRKws{MM9tW<{{*jbr$C&HUk*8 zgl|j{54Qi_F&*xv5_joX+buLzuk;vR)~V^pA;74vZoR8&Jq`aAp`vb9 znSXbnE4fJ5R}2fP=_!7`PWdY$bXDz@4w`~UqV5zUrG=Vo(Km6pZ$LUo&Tp# z^uILB6jeRvO;tp`HAmVJ^@~+okdl&SvJlY%b!`5P#CTv!U2R>8J=Q&8L%Sm#387Tp z#B8QMfIz>%UI+v<-Aq61?I^53q@f0K#26CFTfTRTxBJ{RKHhfU=jSoU4>exO1MFIB z51k=lBraNXrcn1t&X^M7+%Y))9n%Ls{hd5Q=Zv&0ZgrRCm#5TN1kOu#9QVi=V66-Uspw-YsVQi{mtg$ zc06Fn?R;87N1l@L=W|djnfKz3vN|bGZ4;KZA!$!5?8aq%vh^>&{AxC=@$1f`)hOFO ztO-J58hXDhT^bObC*rYZVK_M;I>p*VBD6(eG`4UEfXWht=iaDmUGK6fS2`OAk?qbFFgOB5zoY9CBn2yZ!a;uGOKM^ zkv`%I#2e9}6)@*32S0D(9Ye9Swzt&Xngd{5h;mB1?4#{?kkNXMH8q+qXvq+z1Z|+y zNop`AwDa}m`3?ES0Qgr**)PmB5>U{jYrItQW4xu~%!%I#`!vlkt4v}n^D9JzIk^!x z&jBx=VCYb%;^5=3e4h8|kJDy<)TYm^LlUi)*J`GF^@om8Eg~A*eFn090~d=DO4XTS zTQCioBczdDVx*49D}QN@QmQA_uYQ4C*hHrs;t-B>;v>K%%c^WqG)JG{&wq~0dz7Fu zj=4)p|K*rJXiC-MQs%Pa7eA9o{u1qQvcVYB5h!@agFa$9D{hQJ@!vZ_$v#+@y2lI~ zp`h3W)50xfA)&h;A|S7yqOh^^a?M7}lY$Q3~h# zdDWnq4_$W{v#v9c)sT&XwM2mD?)F6R?{ILEpu+SI9Dsg{NL>F5I8Zh+Q_vDv!zUr<^E8eDiVC?qtBn-D2pc@BV`wv@yqmhoTYWyq4z}so+X6^Pvs1h&RZ)EwI#z;YguuX2JVS{>rkm1 zs)Xa$X0rBWS7TKVV>m1;7%Z9ZndmYNhEz|b4**5wZ;Qb>IT|f2D3O^DHNeXzrmOX!SwtdYUdtE= zK!d4DN8|KH%i2~~xMifmMxPXpA&MCeLQm!Fz8Af^%a`pN7t8nFrP-PRa~{6L02LSHeX($y~@@r0SgF7 z86r9Q@XE>8ZNMQcO=EMf)S!lAtX+AM3s&sHlf-pl8!S%r39k+`DKer&psm!QXJjQS zOy<}WLwwPg2e!RW(C82-*u8CPcG?&%IaMS0NP7T>CAy78rCGkUh@hv}Xx=68T?p(n z(tHeRyroxTGSw1G(&UuvVT)vz)$oNnbzPey6HsQE-WI<`u4m}r5|T0;ZlQlL5;?dU z6mVZdxNKo!tZ9r99g6w`LB0H#CRQ&ra`nYs0YJEe_8LVSx&wk$0GC^z2x$7dpnd!k zF506;jlM^fTlW(w8|<78j}QG z?OqP2&fv=WEC|jwBCvG*`5&F*8cEP-1>f8X<$uYo{(IFbR*IE4Ms8Q7Rtm)XEe4zCsc1024KE%Oa(;7*xd+w>OT2|tKl^OlK3|=b^otc z>s+JnpVhZiT5#YVILsv*5yXl?5T5m9fR;@I6L+|#F0(C5JMBC>b0;lgo}2V=4UhQ- z^Uq)UX*-1tc2eYLsBqT5Ze{h?lJ5+b9>K_+Vl`Ib)+p_y`4}ftJFYMM4A#kpXc~mm zQuSSUD=*HhJkjudb5xsF3SB^!8hpWUP=OS*7jMtfiy4AU;ZyrF5{d?l zcBt|axQu}DNEw!m#BqNK08|o(Y(bq`&%G9(Uoipc)zH(pGT*H{_B?J!!o5aX8o=D1 zJ%a{plSj(L&<*{+)Nyr=DAf5F7`NU@Za|&oN9fsv$0|`!^8!`wBAsTB#@$5|aDUH0 zPg1iBap7{q%NXpC7#hipP?CGjt9H*0;qc1tbw|R`@`V|q5ez8b z@yckys=)ig`+93GQO_QL0r2^)vg5GhCRInW+N+IlT7e}(qP{EQ_n~w@zM%47B=Cw3 z=tamc^oop%BV>CXy)&DNjbik$1ipJlWOIZ0ZK+jt*X{zfnJYUPdhlex-%@YRIegj% z%C1X;l{IWQ^>@}wktLIw9#kj9GtkQ<#C5ib^$hu;YiToaN@=0O)hhAQT4j0M2&;w- z8QqhI2MY$4{T=G?X$Oe zXKI{w%_nMfqSaM3TV{I9G#@=tlUoa}Szr+C7Q|dFAnpcjz`7<8XON^m5UKt&(GN0; zFrK!d#}Cl21(0JlJGV_Nfem(T2c&TJO9y)zRj+9qyRsS6A+Ij>8JLPxWBv?=(7ZAv z^kS7msREVwNzt(#?S-)IQh*CyTNa9a_?K0>+#(2h!>`D+lZ9_1?$jfAM_@i@;_`5L z65-T09MqEa&Do2<{LlO|VnsH&|K{LS97Bf*8w<|}PO&LMf0OI8#fC|n#UT|I?$*Wu@pET*Qv0pHFO%xK)bD5ax@O>^5{)g zn^0ir=YI1{HMyqtGAdelYrmjp6t(m>p*Ok>q}Q511T%2}U3N@QKAZN`G>Rr-750mZ z=l!XC$1nQj1yuQ9iz(`omXC^+a_GW)N0&lG>F&9@>PBqg=xL-5x{Y3`iB&=RghTNO z6?>XgfGA5vDl1d)G%)&TdsZK{hrjyXlHB*YgyeF41nZ3_^@1Vod(aTpnjG~2F@STJ zAyoFZyX8y5lIts>8#J&0V|~ONT=ozd{wxlor{>;1+%$Ij@4ht0jee|ba=>2z4I>s? z5jor(7i*`*WUj+QUug^_o%>}Tr)6BWQ1K;4I8~oj_ zy@3nL{JywFP$iz~hc&IgkT$&z=6g_I$62W1B&T!q-GhLuk_k(=k#x;^>pzcoG;*WA zl1B4t=CLW(xPW-r{^ef&87H0lU0xi7Gg&Z1>EOIL!0|?}wOjUh;9NU=l_{LAiKvlu zvrTjy{mr&oNNm+cJiTJ&7p6Fb?D5!8KTO-pELJ-b1c`wX$l6gXy9L!#%!D3PU!j~H zEfI?P*DQF274y|+l>5#!hheqEFQcP8a?A$!WOra)OhF~f?9a!D5c_)jk%9E&fHI00 zWN1v^PEB$7AP)E+3k6$Gyd2vZr`boE8#nWSZkU>9;tnjqUw2Cn`3pTA3+9pY^Pq&{_o^%Lb zI)%&BbTl4x21Z>}Ii0yyDhW;&aHbw1Q8~Fzb+EZ)F>6=(*F@ar1PuMjdrPF zD^?4Jck?P4i@q6m>e+cQF)4Mc5D~sjHsViT0rFCV);??0_HS;S=k`%Q>nISUY(S zB-GIqE1mz@4HrLM$8S#e$$KgY{YDD)e(4!vSpLCNQDZSD@4d~9+JXv53Mq2jfo+yS zd~DbtbAPfav=UNXbk~-EJQ-6KsVei-o37VL<}6w=GEujtz;4f2w`?yK#JJsOPZ@3U z4Bj7++cWnT*5;rV;3zv`HrU^6Q#)~jtj~IjOs}FY8J##xDVAZh2dB&7fB*;g4CfQ5 zfQ25%vzofTgKp>1ywZEP1ut1`Ca@QFN=~4hpEzm0&`R|5VJjfPA9>anmuyJ93V7Un zX1W!_d`^QsaXuj#x_k)6uBm+T=G#{~h~{;+NOndD)@W%x%YK4KSZ%-2!NbnZnjTZT zafqvy8H9TYnAeY;xQPLQxc|u=SsnOC#OmZe|M$WP>D+zn@nemkhjprqfvTY;zC2Q0 zoiXy~ct$<2Q8+T*fyE@Y0C}{JF5u_ipQ0u+*fWd(b}H~lLcH+#aYat-qMfH<5l>$v zSn^Y1{GusdAp|vur^QA=!LlE(GD>lg;Ttrp)Js_lJSyvAk)Im~R%bNJxrJ2(>7vM< zfs};bJ9U>)wQgy9a%)C#N<0WvV#1_=Qlo&xX1UyMvUv_pAwC1PEUI_V4++?>*o5ka zu%s9X%ujTxOse?fWqiFeu|~duGx0yN>`hUZO9%Fxe=&deR`Cv46un?#M0T~ms9vd! z;`;gviyIdMI|QXqFV31y8{}5QEbk~^jz&1bV^EzE*W`>zb%(B@OTjeHiKwKUFerq@5~a@&*-FgpuA5|#u3>jno3y%1?kT4ULfg>N zaQSpX-i2rkDx4>%q}flKt}}_ zm3jjlmbSwcU&(51T1yP1@-A{4fBp~%5Ey_#K&70S&lp6RzAeJMPFWsK3ODnZuf4wR zF`wh(-st;!y#foM;+;NZuElJ(+AHE!PM<0 z<)$@io@C?TZcQJe+u^)K2RWhB@M^$rG1XIlhwBM;G#25*piX5u$!yr3wO&^K!E0lq zFmUl*ad~Uz)3%=?#5~Mqn#GE80*!@TPT>rjbXu|3{Z6Q~CN#r)sP1DRrjQH!Hy0@=G@3 z{em!TR$8<0Y#AQ6z%f{!eTJ&6&a5K`hwK*?mHTQtH}1Cj`}M5mG;GC?AyI)#FpQW$ zW8-cxf+l`bTC=GJmpkeJORS{YJQu6W_%2dIe&^-IE+cd11t;fgRtk_f*xiVaEKT9J zG>h;|XP1gs>d)0xnTCmB@0@u>UcmX-6+#d4I3s5FZ+20kG01AG&QMM~^X(4vZE-ka zV|Z{HC-Pa=dwO8pWYoW0!DX{LLQ{Y>GJKdE$aRxBq%V|V`cj{753`!dR`0`r{LIuG z3*xj?Bt(gVO^xU_Hwq~_`?~l6pn*tP?vWpJhM27Ev0Hm^rIp!*Pwrb$-`Iie{BxDh zlfOBRZ#f|&H&+*YJ<*n|Pee104mLJQk?2<~oaQEzTcws4%cHmBzjJN}e4M7gA_Ivh zx^<;TKrc4?_>OjloUX9@yF?4DSB~+7MuWJF9FdHBc;O`PI)EKKrCnuH)s-)-$%P;% z-`hUij++^|r(|7*)%=%8zCBQIf&s*$M`|HgCfE$u1s3xoro!N$Gb=m_Dq|GuPFbOh zM;I}S^eE=+6770d@{C{_L_p)?k>5JdK^I0kMg9d8;o)PkrD-5OO@ao0#2Pc&HlaQT zil^LxFf6Nrnvc(DrcdNx5?`^pBc!`X@B$@`_#Ov(f|Rc8DD$(?Hbr&I_iTY?RPs_6 z*H9ZO2Ld*Qi~vao*fP%S!?vhH1Sx&l2{-+U?f1M%Q1h920}IJxe8&)vB-!Nb{$vU5 zlL%F(-Qa4BIUwMCLmi`J{2CUk^2pwWr^nnrRwG>$OzK@CH@juSAH>^xw%Em(xu-*8 zsR^DXg|)^x7`u0dtx@=ld}P6WqE-zb+O~>LkOtqmDhEEgLkg!94AvAg3 z@B8;TqeJF5oZz=1=?UgPJ|_PM8@~NWwuUaEhA#iYha5E>XH+rNPwwDL6Bbg#j&ev) z#sw1*H(=mGAyj821C)6~gks$(v`B%4NA$rP29vcE2Q4>?P}7a!L81U{e%gPs#TpgTZEQ8<%CLC6y|!^)CQ z2Uw(B4r!C5%;uOpTQk;wr}NGo@g~8@%95*K-}7RyYF2a1$TCG>twT*SKLmn@{Z0hu z0E$a9g~Rat!v@ML1BaITn^pIqqtY<zNN~t{yfi3$a77rq4^3!{vVd~6#w5ep%>ADI4NpguU-4DP4 zJH=Egvxs^3o=EjdeMlZgp>^xD_($K)1A>Vwe+@&@23HV+I6C3AvD%Dn1F(B z?N7bd37XlVkTl6Arq5rpvS2zP;_1b7Wfs0XW4s2YTfmaYtsUM+TsLvlhcal_{!>F} z;7_S(hzizufEzv`C*qpdpZgZXq!8iMLM)pg=@3=cMuT9(r?G9~Wl2IJE?%V@T z3!rB$1btZ@5$!2$F`fw@@_jSt+8xW`f;&i<1mS{f?i4sN#94WWS+{t)(vT)akOYeo zBqiWg(W!QIu^#pTTw_B&)RJkK=9(?4K9=P{188&Y4w^~`A0QT|OdLa_C!S1?1%X|x z65|Y~ih9bltBHM!dndWzeNbChTp+ygZj5#mY?$2PYf-6;0Y8{; zLmVjm*yFUV`yhFwWAYXe0i2)6YWwKDWUy3eR*SjD>VE&^7*FDmo!5!?Y2C8y7tuM0 zHk$)Yc?Ux#%Yu!lb6+OgH6-K~0_Al-^z|OId>I-W!NSlm+&t~N(HnQB1!*nyJVZq6 zqCX<(x?~{s6)!4PlQ#&baMNBZ+1ZeEEqL*Q#ituo_*Z2=AyCY40IzUSf*XE`BkgLP zN%l_)DJmJin9;W<%nTU7x&Zrb^0l7e)CzamtN|Z6>fov(weTJqML-T_i z7PY1~N%g)*HQbZtZ>WnwYE56HRY_iTYd&>AeYILWGt%neou=)RCcEKGZJ?>Broyms z%bIaV@NrGxN?kqn3j~s3jB+j47@p-*gp_dcf_PENC^)v4Y+%)T#H%UUA`+J6-mbi6 zCoy4=3%Ogd3eJ*1{xP<)e<5AV890012Y!Y9yeN+)_b(?99se;rnD!*h5AwIgiY^aV zxJ}LdYemB><#lzH_1*xyVH-tg9?zCmVF;!gX+w;S_z3(5~UoI+KOh3I~C&tH9g@LXg{KKkoSXxCs_y^2i!14|ed;ooS zWS;{XMm)W>WP+FNC0A^pwi0#Ym{06wbSb?+cY1%!t9ds6l3>>vf7ny|#;CHuB@j?s zwl}H@$(a}?BYJRzsGMuT=J5iSCjU}=f&4ozr(obmgn!R#Pe}icYgAxP700A~kK(6t&cCmLwDAjPyZ*&9#fqlb&loz9xvz zUk~sB*goiCF}0ZNqYjq1NG@Yc@O+NhfT;scyfl`Mz!jPdT{gh7oNTR`6I_yYhVIyr z7dZ!GN5X`hteu$?Op;E<*67i9hy-JY%Anr#O{xQez?r`Kb=09BZ~tNtGCPaEd)l*kXLmmhLKw*T$o$vi#lnK znAZ#BR;FG0g0xp0rpxdG1;S3H3D*i7P~@4MNpZ3il}DS(6?QhhE=hy#ZIwN-iv0Cf zt8-HMNtcY$N^>u(7fG8VK*wDG9wOa+5> zQlOPX($g|UXIGvQ(d@KRxt3XVnlYebYOtBd2qIAg-!D|3S;=Vd*JM_uxX2$*V`GuE zi8HaS@);*=7ia-fu;G}om!KxghGjOU*vQN1P)(^nvX9spi%8FuY&^JC4lq7M@&!z_ zM6_Ymv>I&`bLm(RtN6N$(>YXDW4b{P%cj5^E*z4|w5&OlfK673t7kqFkfg1s+659> zrsz0uBT%*UCaNU*qPLyaY82I70w+y05-WPlVN>?>qprDo3eJ$*J6L`O6ycD zn3^qv$6WA`Db{0)M;N9>Z|7If;=-sJyv55z?Q^bFT-7F7VwXm9G)Rp8TJJAb3myHo z2x(U*HRP7CG>DGscas&RmT;3l$zrFoGJ9req%}Um%gE98Of52}{%L5=E_uF1p8+w~ z9=8~U1}N_sz}eT2&NM{xx?tTL24V6i-yyx3ZiV1-@I0kNrvS$jrK?=2mZ?e7H9`0K(Lpt{uGDQa>Ll|jB-3u2bQVdc)`_oV*SpI# zEa}vMu@$vg^9l`fW2=8kzpYXSwP3ZPkGI;}ZiGi8=)jt7w{bQIX4SDlwq-Rk6?vL) ztHW{4J6e6w?9WcnakNwvysF*EvkvJNoJKBbEcVBvv@@AleUy#h857}@T+QRFyklb> zVmc3=lxMr$L_YHuXds|fYBq(=`AaQ9>~I!BNz8MMVLg(m%kJv4CQC-%N;-Muj9u{U z?A?jSF+6!Bjiq_|bbRZY;)oVNxVFcPwkwQRw0Y{%$TM<lx`=u4TOpJmMw>Yj z*@rfsxC=>2!77kONUQPWU9+4RzmYI_9VfB?=x+g}Z*m@{h~a%aLS?jtC^4X1d46Ih z9RYq9BF|_Dn`_X}R*tcFt#vRB10Yt#lz#8SCaweth5X|}<<3mGg9k2{p_%8M-iox2 z+a4SkaDnuH1dCv~Exq@OFQ9IO3DjK>Y0j5(8zRuTRy;M&9I6uu)l=)a z#Rn>%>zVg8ON6R%4UZ)TtZwT!2BQ>5#Uw$a9H4+QLG5CcgT)b4;UIFR^Hl=#(~vDV z0Dq!^JC{Lh5g_Qy=PQKfX`mjdfwx(qEs`KI;UH=Z=0yVY*N{%1K)r@2^QU;{V;$zD z!E?|-dqM>5$>ul3f%L8e#9v@0L5`_!Bxeh>$ks5nCoDKrdu(0U|ym z$x*@@m!pRM6p=c<*vbC<3l8s%q|``iX*y~*eK~|gDtA8SI5C_+`(%XfJ?GYA&b8A7 z-u=8^&W|f6-GJf%D`O0ynxK3DI&Elq`9obgC%`Io6rM9RxzUeS5_e3P!D?OL_-&2z zlMUo^X61uYUj1fhD~Gcefg;|MSz^|>0ONt{UX;VB7lq4T^e8Y#b3H6+Tz58sqRD|V zp)9^5kOZUQpKpmBi6jcf`v$}q2rcU^=~9o5i>>p^yOAA_dL4*$OPbq48pR0DMhI-1 z`aXhrHxQ>o?+8B8eRmP*fVp8>A9}0X5>BL_5iKv;)ngomAiK~YyAgkHd)A`~hFM;u zY)0k|oZpFi`|A#L-pT!jZxS#+!)1r_5-MJ-Pvym^!L_-RW1}otp?78E4!*#5cp5|O z$aV)roj8VDPkJ!hTD>SD-3;HmW>Y8&H!<$yFU{jA6n*xHK>ybOM4s>6fO-QipM>uN?iXboi`1kY z(cm4C^Uc<@&%?}C#?kk-=L1g=Ngkcxpffa%)hAmFnnlZHPaNIefIZ5&eZnYq#S3@5-JRvTWiDMPqvoceF?ocdy{=$zG6*LMJ3qQIbPjY;;b_(v zpldm_#SS;-av?7E+NzU>K3}P0HYQRj4VblVwXV7V#v3A;5_MXw zk^<}`UQOPwnyI}ZOjnX*uzNFP_0D+tq^n=J5i%;>zjqq4%UbJUPge7|u`vRhcCszi z-2sU3f-o_om?5XRJ)LKNWojsu)=9z??UojGl#=00GtA#|rHrJ-vVue|V@bMdTtjB# z@#_kZ?}&kO3=$*L$xH1uZ?WglsZ4a34V8_<%1A2ZfYOr_k)d*};9|5p+=~5DdB7jp zG{@DBRf0#epK3emo{{y^!NR~29Kzu49Zv3_ck}@MtwV0gz}iW*MohB%?1L9~ml7M* zK7C+|mqfc`d`C}zhIzQ{346|{w8AvZ1EW^S##R{1;V6_JCxXf)%2XK;`^;2Q1YTh% zCxLg-pyZw5vW{6SEiP9@GN)+hH)FKrDR$mX?5>}1-IKoVnGl4;gL!07FS1Sv1<%cX zD{AoW-A@4V!^`9ZA{6Z-o*vO(X>lft5TTloeMRa~q0@ZQ)VwU$>Sg9ZYf zjfF3T?VDp)v>2b-V>Xo;ph-4fUT<5r4Hvjxw`Fj~SW%J+1enE7al{k|M`qP-V#1J% z`pu9@4|oO6zyKk=l^d+b^+c9a09Gm#!G-$-$4wIV?zmRtC@wj0nxQ;qKw;_lS=pM@ z0J>cCW|c|4kL!C2VEsU~(6>lCuq9^63d&g4`*KmlJ4paO{@Pr-VsikYm zkOb$O$mfF)N`rbN0`R{f%oiZCju7-=K3ZvlgVTkp1(J2r&6kIg*Q~S#|NK$<@__Yo z*!>iCS1Tm1z&}bRM3>>JGq2!=;qgTNim2@#<*iqOA_w(!=8P}<6~)|rK|xyK^cMO` zo`2{EPd7FfY%n|sUD%r2omZ5;fGJG-{GQSkh%SJ*kjGZFMr18#U%^ebQ=lhrZd@Hb&8ER&^YI7?Us9e&M0$Q^eL=~^v?AbQY)4R&~ z`ux4N3p8!q70~6e+s_9=fEqs#1{Q755J2Ul+E3;)eSo5#rb!!@@J1E&*R%TeZxb)S zc^*V{GK{8Jh=w&dKK6?+p*tZD-&2cVLni}5@pgjUX6;44d<}3nIi~~5Q#8S@nDSu>7V_;O{*7Fn1oraa)ipbv9j=NF{OpaU~Ioi#4$Fc>y|iDZI$U$%$8#*gg zl;nW!WDT~;;v0Wktx&*gRF+BEcPzN$Tmy$-e${xvzLzQgqz=yZXi%_s8X$^_mm zjozoByVEWGzhW_qt2kEBRRo2>S)aR<(J))5Yw4-<)Vu&3O3gj5Fro*_yZL-%Y1|P{ zG2MdBy!W&h0GDs{f%1`g0;=-|fM?tYKzMxsZE1EHU6l;pT-rfq53Y^-V{(b*&j%CY z&$51Ipqk=<>G>qGDL5~?(tGOSJMEHu=sRthV^^3G&{M-@NQ13R9?}H9U@lm-Axie} zV>3`Tye9q3r`|q;~=`ZkHjhfBQ`;a3dy11n~g>%13f7X2fT>eyyp1jsdBCk=# zN}<<~gcDTu8Jd`1W0?Fjc#NaumN{aPicLS*anx~2-shVfT_rx=-;WHFp3i1%3i1CV4pp2)yrTk%{kUk^k@p_y7H;JDdKy7_5Hg{4F_r^~g2yXpyfuS@3dV$g&f` z1P34wlR$(?h1-OaLv#Mhu_3>{re#TrM@*2pLst~yde8rP8>H<7s%$`|s?{LQvtp%T{_xNq!xPEp1?7QapJ0{TP4>TW87vup~2T{)u@FDc#Kp0NkjZ};sJhW7ewevyO^W>3Ug+QTz+|J zt44mim_j))8lJZJ6}A*Yxp*9Nyz;9iu1KAc1#MT<$ZEV9G^^{WIP-VY{YyM{OR|YH z4r5Vii3xIK!d^&uW+Z%RjRglmX^QfEk%M|56rUMYs955aKLuS&%*aZ0$r%<)KpYrw zXr?k+l8;oW)ss^!82TD-x-mSzAT?jIg*1+14NHaFaBhZ45(;uxjtdN}!!zeT@bMpY z`ePSS`M7@Jl$$if&C$kCt4v(N@5B*nB}mjheny|KD;}y+=eL|AnCd^!TZbn;#us4f z=yon=>RtSaQb)=r*0n_s!;eTLwXN2;4r+dhIGDr%3MM|0J-}1HMNl=ls}@T(rEAXf zi%c*(i4Iw3zbQOCxsK_j+2YKRSo64rIenxn^Xvf&)pPg`(Afv6p9|~vT}f4P9zPbAIQ7YAqGR~$qHf#-W*df+FS&6b z2C!Y-1n2)l**ito0dMWzjcwbut;V)(+iJSfpmAf{wr$(CjizzF)qURcZhSZU+^vzd zt}@2Ne?D{mp1wTxc}LbJbg@0X+{pZ&%7te4aQ!gm19K|MmeNDgeiUDDu$)-F%hDTS zTD=iCi{FUXfglVc`ltow>P-Vu2Q7(x1u8+iAv4>_G)1R z=Nq~lLVRTvi{AUgHK3_(Gha+;5?Dt2J%?*I!?Uvygnu9I$-1tLp{|YEp5I2%V@Gc$ z-llgxA6M%=rEgM;G06?Qk*w}r0qfq0KJg2^^dvjnBStHPP*jgPo(0~l%gfY{$na4; z(!t}NOjEjj?+)Sm9jss=n#z^%mstM8D?ak4?UyxZt57H@x+oJJ>-^a(9L*WJDh1ZF zycQ4Np~g%-9^v)T@J*(l3-YKmSwVWxzhPx=XvJ48MH!qI^r?@Y5V_Mbd1mYxOR`1A zt1vU%h;}ch`|plx-W~;5mlz!@?!H!l^o36Y8hZzQM)At?km;ngL|u(I+O!C^v=W20 zX!>M;U(ODmJq(r64)9884dH9aGioGrX_O$_Ce$EK0vbn$?rx!gzbFLV>w}TJ$UZ4P zu~M5W$x&9wk1UtiI36ucGt~;;*9u1ShQRjde17shXAh}P;E(T0`MJ>(Xk8H%gvsiO z5b;e?-0BuR=|O)ukM3q(Cdh*@wJ+aOSs-j!in<deO@;8%h(~W1qVl&3seO9qH=< zLobO^slIDY%SL3qY>H%}hiS40C|1}DP`Daj6+i4P#0{4dd0TLX68CrKQuc$R=w1_7 zm!r6J-T$*rXXy%YRor)82ol{u4}v8A20i7S(#N81ReSgQ{d>%pG-rmO;u|+)s6cM) zuP}(g%wVCSTEUiIwZbe@@r`e((K;gs3@Du`VJPE(au#{~HwefWFxco?=kVZOUa zBQ;KXfWiFAo?Vu5qo2da=c}WkHKv&2oSK#Q$Q_G?U?oOganH%1m0P%tCn>X46?=(u z%tgz|*ede62M)B}COE9S!>bP~xf3P`yA%51og@2bUx=-pCP$+%nY0vp3J(zgyo2F% z<=H5Hv$uAQO5sgm0&ozb1V9VzExy94iAObDj3GYC*miHF$N8~(G)XqYC%uoV6mQo+ zaEi&(m7d}VY%y_2Di%c==++YmWX@EAD_XG@{zl-{;mY>GPZC9wNnw#}#*foO@|aV5 zb<(rI@0VK_$v}bDiQ{jy<{C^=K!VaSQt0mlSp4}>P?w!2&phDndy84(7994|7md%A zr8wRR9o&Z8QpMBk)oCcMY0^OHr)m|>rKmz$Cs_Rb#b62|+D#yRO-G8SxQZt025C)& zQ|`DM?}QyM3ksB`D+d9Mg+*n{1v8MY6K}dYK`hcyy2o&Vp~Xs2)U%f%lYB~RAMc+P zXP%lA-t4R~NOod$R}#J{mnTO!j7HnUvJ5Xj$djR2o7Rd+X`51Eeup?Uf9NSII@qs8 z#T@a+?nofLv!>)^$N=MdualUuq~+%y|H8MGQs$gn^ZbFbniQm$!h$~ITK?(`H9P7R zn+|CHoeI-}2IoZ*KjJSKM0@$fjJ1BLGk0amgAx+uQ@QeL3hq;i0Sbc{dg`|<*}j{f zv@n3l`PIS6UJ*8ZE4*t`&V0y=}ySM~<%1WRp(>Uwve?CQn?6Lli zc->~~d%WQCd-Y1a!rJ}J8Z;kOeJDa}$1PT-<2q6^O&p)Xb3`&~eTjY8ihc?2EhN_S zxy5*7Ut7Q&tATC1D7J6yg@{p9j+gLeKwXQ)9WU)7bnSf8u)9@-sHl?;p08Tegr?K8 z{G?aoB&?{8swyaAi$M3yIZCL?T{75~5cC{GbB$SgErlu(O;RAlicY$cT}3&M-UScj zC6G&RMqZ5Wv>F7uBw+%Bbrqh`Kodvz20JRS9nHGA&4-6LxT~(3^7~T^W7sxg4m#EU5eiOTVW(&WV1ob2Zz_R@ypAQ zWl=S@(Y0*-D)a}zxFWzb^|4z(K=~zhN@+lwfv3E?%M(zWfSmNwV3d!rA3<`482*V5 z>PjK7nNubp0_d&yOb@rRPu>q#SuG+^!~tTRKP9?EpMeJ0m zgxKF0o$If>_-SEP-(r%IS&apI5m}%KNYByGX3Zzm)T1xBO6`DBJd$=UBs(D_J40#S z!U;5X$3H*`_UikE3*F*;^tr!9O%L+>kznr1)`A+gm+E{yZHcgYq3O%`nt7ce(EjcHD5m?%M4q(?VK#P) z^^PhUkDrAJbhDh?PE*)?OaHt+aKVisGVaM6B;u`8vj@wrYq~F)vP_Fr$<%nY2Mi1e zLc*h9S#=u*{e0a;axLp&-9t6BYP$+rjyEeZkEThk)||E$Is^Zvst$=KhBM&ichX#y zXboJ|^_e&zckUrgxC^%-p~q|Uw|f5as?x4%>n=?+F87>neD1hQ*~B>m7i!4@uoD@t za%)PcG+%e+BITQ#a~!Q1gw^$aw2)j^T=*k^ahi4w&S3IKjn%y#gS`=+Lz*R`T1x$# zudIBtCkFnPUC@{+Yu?XbsZ$QdpdPVPCz3sMZW4fMI2>hT+3)t;a8k&8w|HI{z0tKy z<#N3Mg`GJOQL>qFk!i=v+eiUN)5ID*Ct7w;+jDbFMTJb&E{M-_}7H>~E)Mwcwv z&&;F+i#_xZ5v$y23^+^Wy;D)RdHzK2wxDs$syiE0OYo3=$hsTCEiCTdlKM#AbaF>y z$Ypx=8k?QkV8{? z`mf3h1SP9afI1Z@dq`tZj}xVdra8I=q&oS&(>b>#d))K5M%*Wz-!vP~FS}kbe+Wjp zU#bdBPN;VsuDMJZtfz_fVg&)g3+s-28au<0SANBm*30mDVxin?ev8pkrZuSLcQ+U;w0^0LaRcbE}V^1+e}Q{*vQNEMNP*FdYRX@11>M0eB;( z(Zom2S>$xiLlCe)v`cp!7uY857up%@cWfkb?^ovEmTpKod45;35V1gv$WN6+_VRVVdDRbc}=oD1LmZ73t(**s^9u{<2!< zV0K$3hfg-e>Nc@S_Vlb3dXM=twJUdeYYX89Uf8YY_@ViE`z`Euu!Qo{#IAPlvBb@F1%t&eD@`e_g; zkCY%2rHf}j#)aR~b3Mc(bSh3=M!8He;Q?#@obMf7?+%x)BFIo zLu$%UF{Q#jn}MKZZQuAMf0(C>*502@{Hxk$ILxc`l05(b7j7Ilp7bLBV8>-h+nOXl49M?3U9f}I-az;Ldwjsr*BT=G6n3jd{nKA8JNGV-{NC6)s!x?P2@!oZ8>K#>}Z2y6_@vB7+1z=SKj5}$HXpNheEBX zdrY-9EH2EsgUoN&SJdGjsF1 zPSN~AIf}m;^|?q0dd+5acS|k`lW!Usl2G>vRuh~NKbYFdVY_fwQUPf};sPD~o6+CE zzRn#Pf5klVqPcZJA<}M{XvfJw8M%y(4)(mM>XC-bvfO!Yae*_}tDWg718~3@?xzn} zHwa)RVcXpXyv=|n=I5<_t3LL1l;DJV?;<)WKojLt>uoA;=)`!u95xOpP>+XdlUA+Y zg^IFLn5degWB*bo+jqLBx3-#k65n{{tAjwZ35;t$+gHQ`Lw{W!6gO>8P@U2(l>)l3GY5n9O4 z_N7!Ezh5Va8O@ghx?}%nHnS&`wYN=LiBKUbdx7F|CT@97+`^{NIVM#Y?C z$`i!!=aAX%03zjbz~C(ri$WTzw!)Rs83!zr)0t5k5p)$tT#;W%3{MwaTo8f{rX+?8 zAF|N$=fH~3v!|Ki-by)e1X;ia^G#0vs?juq)$i+Pcm+dGv;|ET37{OMJosNtzW)Za zblH982{545|9cF;-{&AOpyeD*jsIm|JyT6z6<-`(pp$-rHPX)x6nC;&%=lO405V6M z5j=>T9;O?N%z{xp`?yj&n=V@?`Ui;NJ5JY=kqL8xMyaCCVN6|zr^R=39MMFi`Ay*a z&vm-fzX&KeByWE~CzkJskp^pq-{6exO$bBuGul(%U~z^dx^qgNv?jcAkLT&{w?lO0 z4@l_ufAsDd8m0_SIaa3$g~-Bk1#&NnJLvJWSZ|baY;~8?Y}APjH{#akN~NpM65Mxu z7Nr3jP4IfB*1SYz3uf4e_4CYp2uv0BrQw*^RxlYBMB(R*0sE)&g_|-mMv)4oj;&ec z#o)XW63GyC^B--fCvfNQv|4et9D@^Yz<)OzT3!5P#g>~W08d<~gtv}_E`4ma zPRDimE7iiBZgMZLBX`-6-9n?P25(@l7vqH9pK+iJQ9=Fbzm~RIS`y;n4-9m_Na4i< zcp6PcHJ{XVa%OIq4tcq)NWDA*6s{`SorJwDnMw-}TmY=7L{Ztk2eh8_51CVQi{rGO z!^ac1U#=fzYlxQcnv(M;w07&5dEOuSxp!N7+ly}~8Wk+K&u1ceT za>QFtA1tAE3w&tkGK1d!u05fIu`c%UqAj=WF8&m(n}kZe6CoD(3r<_lOt6*|W|e7Q zAJ)@ck0vIe>0%##SCOw2yVb6W?oQXQm-SaGV*r}LC(#;m3O`NR-tu{uCwtw79s8Rw z6~nsoR}8!51dpb^kb7dghKGjg%1gDK5TXRL%&PGUS^-6wSanu~n8>2{H~|FS%twJr z59-a}y<5MCP{aeF_dFNU6Xv5U{4qC5%qpS@*}o|O;<1tb#y;G^umuf{CzyJcBpUHE zmMUR(Eli6sUed6_ES}sY#6W^rwwj72Mgs?vXp(1%KrcZwldmxjjeRpv>W; zAvRTp0Ck+@%K8fk;1Ct5AFyRi)C5rgWfxb@=o%+n7oU#jRDnAgFbQHt|C3(z6pav~DDZO&HTD_P^*xl_!e72}_cpTL0x(7)*_j2G+bG=Gl`GH5 z7nUd9;alA^$=B-S;&kVVk?YU%9X{bAkA&f!P-NG={?|e` zcZ>!+ZEJM_#zPeGmfg z<~&o$74>F@M5x-S`w=?OE{~)w>2I{gK(ECw96tU9Mf*2>Ojw+rcmdM~2e2Rbf0!oy zMFv>go9j3_{ByGcxX|v1CW-zrnwrhk+`r?NJfEgT((@FHr1hxb}X4UucVdRQ06-HHy zJk@vJcD^nykAGXXy}FGgtwz32+eW|z_5=%ZeqPHwkSsSpf}#ErL$Hf_PTzl(5kYLK zraLPkKLLV6pJw|kTShjIF=00*ojehb8RYIS$SEO0$ab~3gB8J}SlC8FFk@$cRkd?iO(2M zY;I}l@+@WlpN;_a+E*I(ErYZ>#>Yh9e-z4Qw-JsPHk5#;U?e)}J@Q*QC3DclD){kG z3my$ba6yPq__WxqlFz^j2waG${6S6ZT#JsMP2Z&7mCO8cY*Gd}0$ZZz&9t4Fr*`P0 z0uyCs;n}yL67o4;&Iys1m48vzw`79eVKmZv7nR5t-?7enC0ft3-nM0ror5w`3d<^! z3MZ%ght*<%6=<+THbNV-M#~_34UOja^;`ox0+FPDbOeyb0+*)ugmnEeyrXlx-rki; z6S{*!ERTHAZ=ttuv-**aZve7xqac299iFC$eWq9<{(YX!@-0t^U13yLJ;b>AvILr~ zM5e2+ZyL`~qImTIB(Jdj)%ly+CK=KS^a@;KYr(5bzT>P` zUyHZvfPfDKBarR2mJo5cWd{1ykzF)AvZsY1o{buo;OUmvjLGy3hM0Ko^6%^XRp>Vm zOxwo$RZ&dJyQ>0l>Tu`S_euU+v}$LcL9pirlTuj*oSDcU9K;>iFA#~}OBv|#ViGUnObKIDJkzPQhvb2+i< z5Q;$7NJ6LZ`CV^OMD}+i7G!MjD`=8NR~$+qt=O2I3k3L7aC`qi5U?r?A|_2 zL_#BjojK|6pje2aXU6z$iuP0@I-zU5qJEb<_IkTl1W*)LM=icY$pjO37yLyK5~icv zUSN)>R1*3|3&CP|h+`?9O8sfUZ9m-YxLcwe!7)ESm(g3i=ZZ8|E<<|h@bKOZm=-7n zZhN8as&F-WYrEu1BtHv2I)~~4RCAPIaMz}T8#@h(d1avBc0Lox8 zp0ytQ+e5}*1~m;l)3AAETG~TN%PH&Ney71h%f(w5(kkx^!!LF&#g8!m_*>Z-4Rg_= zS@gmV;+eW%dIKTkSJwg!{1?*?r6t<}nBKuy?I721cihGsU z$^mHxd^2z5~WMmbYbdMe2L*Lh;L?Epecg1HoSi96o3$v+zk8qm}Ijpy#F4!h6^l2|Jck=`SZkV~&u@{_leKudrmPb=N5^doU1n)Mqr(iH&xqtO6|YTQ&bcyPU`!8a=EqX~ zGyKE%X7RV@Thl~&WQN1Fc0!3KHHu|!t4zF!_LA2Ou~*YR!JaL#{Ryw9(DwDhvv1;{ z8nN@?iFWr_bPI$;F;z&3LAWg3fBy^d?{9JTvXa8>1jcg3fB#r_b+%*ryIJt>%S4&~ z_pqHiX+NCOc~i-XCYRfYFKSU~Eujc}Z2-^|jpqa%6cgH5!82kC~%>nIqC zI>^GP=mwnajXK1aqS1q3e&Qx)v(s~0!1sDl`xLnJ=ntP0S$ zD*?&0y9txg3yXAZZsnEj&o$pNn6=2hw4!ZnD)0m8CN~6Fq^0*6rx)2d*%!RL75j@q zV!#{xPu(hI)QNRB<6q~T3f5WA#+o;m>V#zkPJ-Hb=3_;ESxR-4+fj8>CQ>j9AUBLb zho_bZIW1Nd+Nl~@5J!qU|4jYqxKdTA0X_sfXb(6jTPANCExXidbZ9qz)~(ys%1E@7 zvDAIxT(1R6^OwRqGK=@RAs14v%d81@JRW4spcI=$59t@o^uy3y9`IA{sxNh&6s;w0 zMy2+Qx;t*WR*M-i62BN|^@-;n&SYM~zxJCs#+T2rRYundaLwG*_RvJ6OIfP<5NyMO zZsow{&p{kXWU}NrhHd5b1n)+r6)$yGt^4^{{Ol0ZUg)fH)H&z$H1nw$U{$p$IbQ)) zo+2EkcIrYZ^(L{JY$N)Mig))5v?r*}9qET4VA`*UcIC&mo7&Io@hTxFsNU)5k*1`t zR~pMmjh59~0G`^+*W~{d>_y>2uu%>$yACvE^_N-3$yG^UEY-h}rkCz2oV7&MgC>85 zqJnEgN3eE!th>gDEwyJxAD6c{3Xs*j`46iM8O*R;foB4S6cB%BCAe?j+ty8v*=XDR zI3U$EJAfTj{&OuB>Y*v1sV%^JwRkpr&-wbLuUfD=56Fpk;d;@j6g&I}IgU_hYN*uT7*1ZlMc%iyqC@J-ht#V+BOl=h{App9A&>>wf3^K^=6+8nY zk$i-d^E;m{mrIewm84%TC>+OY@bs>?a8~csxC+!6H5_x9)Be3&bt$oKY87WteV?UFO9PKm0J7B8cN#*FZ}f zo*RJ2SIr}K#n^qb7_Q_HOya-!Ti-oC*VV)YK$3`57MR<7@{+eu!36UR^dkH!i9oxd zyEC`=3NBZQ!DW_016g-;16f@GEqR-E%owat(qed6R|X>KrSP=jn&mQjKHY+u0TZk< zL&E~E@RlCCWTNq-Zcimvg#4OBBv?b7Jpu127X&pkdfo!t@i<@pjGz)HWVRbg29So7 z_RW3XcOaw4>oA9g&SYzG_3d;SR0LAxc&IV9fK+myRjoCX5rG)pU5S_N{hk6@$G z3Ax{g+l(Xv3|@aWQ5U<1STy_b*F2%i?V=8Le@ig`6%Msl%CJ9Ny_eB0xde8`X9r4% zt*^FPppU?fr&eNmlmxMQoZ&c3u-<);i$;IQJ++-3o$$3GM_z~lqFLB-X_bY1$uKFr zcN8ZWTKwgUui+1cdqA~VL>~gyXVf3J2;sLkhgGqveyw&@D-Lo9szeBV$12e0l-C0} zmH2RMQhla%_LVBB4QKnHIQ+SjlWC(E_9jizu7}pl>;O=cpsa{*J}$f`ij|_}kyp4g z?O)zyXTx8@hx!{_tt{9*SI?!oqb8zfQlfiIxn=S((5yT&EZPj=sfM`ni3ggB1^ZU& zILX|C>?FvLL|N!=b2N2Q5ar&bE!UEzOs3X|jQm3FE!lj2BW?=Kq;qi9INl1h8Is8V zl&4yP^bs@>8ol!G#PJg9EbZZj&nTi=-l7cPgb_?r+d)?k?n)*13cF|+sOSWFEP8^- zj8M(~r6alAJi;qp>UKdrIbdO89qxo{Ny;w!1b~)QD;t$ceaHLv{yE~e`TQ~P%s>YS z(ffahFJ*Ug7w`X7z7jR0U4hN>$668xnK$aXi%ETu9d}rzk>Xe45PFMSRD84HF#I3= zsLh2<{L$=W(~MMDoPo~IYB-fy-E)0L<6v78SXbHth)JiXq**VE{%)1a>YR4 zSZ|>`%%RcMAK@AAg>+r-raz22tukB}f*;{c5Ppn70D(|0eGxi%&OyNC#b`rqT>}x> z@nF7G+QP$;iXvscbP&QGF~^vqz1?`xrwu@K>>Np#o=&a`7;GJl5R=xBe$cua$0$5T zs5$@Aj;N<&pvq&IQw-veVGqp^+iCtL5{AbEt07uCSqE!@E^a%@37|Dgth<(Tjk_^rP$$CS!uzp*O)iRad=;2~OO0 zmF?8Y!=TMuBc;r0E_ZSq<8Ui289zur>yFB%7IlTsE1)DriE7n6pWAsPK?n^pyySk# zC$_Q>+wHbeXeh2!Y3_-g>%1~Zoyy}jB3wg#*omQ3Fs(bfb+3u$q zKYV&mtj2{7d48pj%8t|hP{qC?4+fv%KDt&GankN-F+Ji@Rpv=#O?yZkz&#QU-w!>l z2k22)frc$#k~lNsPO$tPZu)zeGNA%CcVIrGpBg-T9s58(BvTAfPEOAlHq|ofgd+rgh*mPjZ28F4nJ3n3&?jOc zFH;xH_TnvMng`-~QcJ1tc@Vn_wYIi_0AhqLPC$^D_)D}M^G9oH@jWHhSVv;I=50Z< zn@`e1ZTci^L=sMrCF7Z5B=PfE?2M!C-1o=Qqn72%5@Q`!KR}g>{aKXJ z6{)8fYZfHo1;R8(?}VF=(gRUvwm$W6%V2f7g89HZ;oyTQqCjwvL{1fQ1~*AYo%Ccj zu~&$Dq6Lm?IU~kac^|~J^c|zxc<@IF^U`=F3}vd3C0nmFXa?yG91<}%Iv~qhe#VND zThc3;Nccu8NIV(ul4Z#%ArQRK2%%G(Yj^rSBMz6XVy2CIcA(TW>Iby2_oa_ zcGCC{lr>J^sWg^s`pE~FerXn;6vM~&D@SB1`7{roYolq*$2c=PQ|`<&Q){$$yLU0G zO(Ly7*IJ*^RLwW&45;henLcU3@<8hX%dfBgfcLJp_m7ENdxc&_?wa5q1qyY?B$$N5 z_f7%}mek%kpR)EX3Mkrn_s28|u&9_CwINJO1Ig68bP2Z{0;U0d{o4i&wyF6>}1!9-%Odrag$ zv=0oAY^0%Ozgn)rf=I*|K;GXahgfe#jARY!Lh z(|;W%u4|%MB7gcL*QM9MLISorFiF5CeM(gZRWJe|a6F1_)=r zE(zr_`CHkSW;|vX7}zO(X1r|8r*2LTB2V)8k4*U0vvoUmwI2Q*Kwt>t8V<08o}&uJ zIgv#(Zbate5e+-zuVP0VXGuqoGEl1|kHCn8v7CfspsC&p2iD6m0gr0*L#^xeaKDCvHUI9TbX+1O}kPx?k1 zCl-SvCg7XOdRoX$y1T9wIbyF~;yW%h}*59)q*`$wJ63zbFA{mwi_8`&E~0pg=J8ft}-2l}_PH zX4j#0IH}iBM(-+~oxEh4F}Yt=&j1D>KT`<;kxR~{$&tOh8bTu4ln<8En1(y}UTzL{ zYw#y19tH%J7Opw#5c#NCnmnU%IGR?BY%jM8=)0e4nxhPqK=F>Kp7ITJG`J=pv@Fov zP8spXm4;K@VD*yAdu6Kn4R*r)RhcR6iUms_TFws4xQqQ4^om>Wy8zDgo<@Z%abMT@ zrZu1A^2G2D}W1p)W|$`Y{X1*lj9zADqm?^~hV z^6Y1HNW1XL6wE!^8Dxw%gj1nY%cE@(UXC^Mn$D@wa8bj z@1zCK-z%(QAe~6jmOjMKB&DWfWhh>>e8NA3W|O&VA_mToPCg-(=L$~7dl(U~jD8iY zxMs*ugaChG-1LpChVZd6)OM+Rf0Cd>JhbKdhEq0^U@KR&`r7Gd;S^%%Y0eX(q}JJm zQ-g9SyF2Bt!mxCzXU;`?6r`H3>u-o%eFA<_uTNk|w^;0NKU7p*3@@asCO&zMD_z*p z^y0*9u=d(7m`)5bpI~Bvr#q8>qLmKY4RCw)I+60+`qlOB8)0avr@fhHLF>OAr2eNI zYxm5WF$8>`W&ihj%HJfY=3?yNYHaFe?db5oO=PCJF_1=x?4Qj2mqu9DkP9xe78h8V zPy#WhqVu&hJ^_80fg5=FkrLtOoS;ljHro3J@&U^RPn5$>4#t+CXb!}Z*3j`ZrF*5} zbZuN#Zgs8uH+#MuZ*PBsGykY|o7hVWImg|aW%-WB3wc`=UEh*A4A(N)@5?`Ncq2I7 z?D`~J{EWyX!+TWQvt)wk>HWbRN`devWrvJ#3^$2G4LmX}(wxI8BwB|WF6EG+mCRo8 z#l0Ojw92rXhWSDQKxN8xgTs2|#Ts6O%{ACk`g*QA76zg4dmss0H23+;^IJ!$$?_>$ ziUaYl&chc6ekx`y>MB?>84-CPN=)BSJ#GD*RxkEEYwrM5(x;r&&3n_sVo#n6wZ#Cj zuZ;n3ZdJ{L{yeLla8;T9Jq_L#sJ2*2ZhJk-P`^v@>P?0poGFOWW?lazb}DjG15+3$ z-k1X*gZeO(46#OrkxTo9>A^8bay+>f>V$wU65}6YA`x6Yb%&Hf}}(W zCS)HyF@``u=fQ%T2d)1+zkzTd!a4?DUk7YcHLcmP2QoX1FV&G@TzSzpHRpx+Gub?x z@VZF(fY_5k)48N0%2$_rX^Y2J%CxPnF6N0* zj!Uw6&p;rno_a4*rb}3>KYlGoQPJ_+_bgaWa7Jd7%^vv*$dJ)bxc%e*^3s~sF#Ad_ zbE*{0xjkY!cCtuqPuJC-`_}XaFAw%Ulj-w}2);0PS!!1F$F6}}a zAy)iSVP9Nl6?pxi<2&*Zr_>>1%ITcdj~*of+e6VIQQvb;-SFcY)#v+>v8@kQTH>mB zm8^5zmgg@HF>+oAwnZ;kN7dRvZ&%1c#8bU=_VI#e#_TLFxB<i;_lvE{u8wdO$IX_TxP$bVz@8Hv@O&a-;DC`g|^78L6mf zpwss?3-v{U)ed;&ZHe}d?})JV(mxPV1>gmw^VkZ8d)PuPJQqgi5H+1RZqpisfUPiC zAnPvu_6O3xzb?sPe(3$>725;T!++`F@;5!GJDL3}IxJCL`S0%4heMjh{N-E{1TCpf zPu{6U9Rx~(iTYeaptUgAVqUas`bN?E>J?*qI_~Fpp?*z6Kc#}XS)2zL%Qxld(8NAj<`G^8^{JLvYGomRlJ^I9OggSZ?K4ch8heFR2`n}O&Uo53?0K0U3NxK9 zaLKajA3QF5Gf|m+@~5=~kb$wS@>^@i%1|wg1)W{xbt-~O{bB(M zyl4Tm^*s7`01i|%I8_~BH|j_YxDDSgwXyZp5B5B4E7wXgOuj0ECou5LSoN9@17}o! ztFayzUgAn57%w`nkn}*8ttRP*=9*sPdwgdex@4_JsBI4rnV!J2gU8 zec!sVt%Wy*Rb1F2*KF0-6agwe1-qQa^Yjo?NThg5d2+9LKra@evTq#+%^ecS0wPnh z-%3{~LgR_fEmRM@5Io`iV0CvUjNL%1e-o<fX@&Cgw{hE4AFpj zI#2h|aSs-rU{3W|chEBT28mPB7cm$s!L37T+_r`=!B^~d0hn$v)T0smdKS8JEH9aB zyX_#8ydB}{L0v!pq(B@Y!Ii$QuXp|N)2hS(|Ay^3w$KF32rYjzLQfY4gugrfwx$h; zeLqsUOZt?gW0{w@c&y#ro|m!3OegOWof(6YyJf#J0*JGr5qnNc-1T6Y{MRB{sKkv^ zke{Uwd1CSM`RsJ<8;D=y#9zkw=IlpTkTeS<&2ohe3K>=2-oImh%cZ(J1^H|A+3>tZ z5GN53HOILZfjj2-%2ei!98~{MmRVrQCW}Pzn^qg=N-gbvBReM_JZ@2K+a`tH#u0aV zcC1LdX4xllg9&Evoc##(*jjGhs^Ck;FdjQxN7z>?uhbcwG|9?UeJc$QtY^YO?9L71 z73NAeQj@Y#^sQWz2>1HHRL0Y7^Aa@t_@ti@@v`cBm$m*X0+hseyaAj%0#FT-K-B)16@?O`6jo=MnrA{SQy#Un|cjgj1;{zd$= zi`t=~VP3&rd4%SbC$(W5cJ_^>8T1)*E8VV*i&jH8bUfdsF7vjeWe#2C9j{FafcAQ0 zpHsBvk(c>Zh)Puh5bI@n#oe9 zBypwdupLe=8E-uD9W+m#&h!-(@KeU4B(o=xeFOcO5;Eeia?7sXQ1oJ8nIL|lUD%;h zMzrEGX*?Jy76^O2>k~lh30VKA^bt6W;%t2Bst%-9HZ=-iUGX$Gy9(~|rh6DZNE6JXUYw7HP1A@$ z6uBNd;ZzSXNzrbDtwYD$VM|ll^5KSUhTz5R96< z*B09p=j{o&eq*V|sYUUeWxgU=k6B-nl+7gcHA(PNq~kYgqw0f4pbg^>S`m6wW+V7a zXPjy!rj{fiwWulzE>cdF{Lerelo%O@Bt(|U{1F620QrfKy+fG z_}ea4M&T|A;2s=Kig%rWw}KGmcMW{D7_7voI?E5P6Rx<@#Te`C{b34{RdPV8S*_of zThVZ%VyB z*7VJZtB|nIp6nf=n+PI1ppA>%IoJQ`5*$bn&W(gFI(@JFr(6vlSO1!13V|qzvHd`G zumYNY`$59^CG;W-PVMkJSlqxm+^SgIb27n^nK9noN#kZ0)ecm9&b5r!z~cR3EiuvV zhsDb-bk6>xgInHFZCzsfq-V5}WdB>3*l;zreXgcplhvlncC9i_*hW)Fzl*?LXYQU7 zhjsMVC^D&2+jQjw_8o4izvb)t7*{$=^mr7(;zQU=h;{l$N6*ZHn?A9&2WO&imC0J! znmt!SJ1t()UPnez!`U3iYBXZ}N7E_HggSn+`AD>#16_YI!?>MS$g!-iYFoOW5%u^< zBZH}UTU{q@Yr#a3>V4sPJjQ@QUVgBFs=MEOtpI`{v{hAI!UJ{m5ly$uI|?_KdxKDh zXBx7*11iNJ1WbnV2K#Awu)(l;)^LNG!wM8&+*yo8Uhd%toD!0$EV}!|?Q-%*B-jD{ zqLOQygDOMXMi$!i8ADxrh&usZ3$4>BHz(j$RB91MxOq_n+C_hO@s^@%?v~fWJ9Xif zlj-_Kr6*E>wy0UtS!9@-@mC9P26SVHc+oy7BTU8~w0N`<{P^-MW7XLc?zWXz&c5yl z^ujh7fh>qIu@nIsm|Sj|+lHiQkC%u#w4TsZ{%ON{wEv0vxd(6bx#as9PL_)&!yz7@ z!EnpI`wMOw-U&~b&A79uvboM!wrA_nYMp z$y@nh^`Tt;Wxx46;R;<9u}0h2-=dLnhBxGDE^>Vd5QjE5pI&l2?*uSH&Zp1P7zBG9 zxYozk;gr@6ac?IKy|4uWlVv}M!9i{Jcw}qBtvls3!aPJ?rFL0V9;}eS(zvVyfV`8I zuMdu~swj?eg`q}r#%UhCFI4Pk`qyMK^K5tXASl!BI|8J%gTM4(XpzyyZnDwiH7OaZ z(+}Jt?r4XP%#Aon)2NsbvuP(TNM4IHIr!PKg%l217aX`k+>~;#WW9Yt$ApJM>GddP|4FFhq{(!kc%X>+P5cqXC4eMZ zs4-^#yqowBnTnfAPWqeR)LU4+FZb|@GVd&rabzu1@Jj6$zDM}>&}69eE^$L!JlmDi z@v?6Rd_~?(;?L@%F}9;vk0jr!-T=A3Q@4NlMeY^@Jmb=mewq{n#xIG$siN=d5YyjL zz1P%9o(Hvw?Fxn! zsTW^2Da`B8VJ=ufhgH)BdOroG+qJdl8ZRJCtuS2@-4H!Q5P?USyGgtQy`TC5_PnMX zpsiOBFQ1IBJC84bHsCJPtw1-F03aU=6shkCd>LbcIt793yJEl9SJp_^6u5nQ75)t) z=d93a9qEoy(X>!q68fN;k@0mBdKQn=;6&!D9ajT?ls^5e>)@M0(9Gm=5;L!<5Mqpq zTh?MDeG!A+5MkFsqYbp5lw1$;`fLv-3bK3}T`71dR*lVel9(2C;7Ut(A!H0X?&k2D zO{S4CEP@`U?xGg>fPHs-v`FLd2aQ?nWCg+f`dKz|r?FRKomOmbwEEvNcGiqMm$>NA z2IE-wQzgQxpFN~J{KgiA!ji0p38gN^>N?F2>Y^^t77|>T?sU^G>Z=zO$M%3+@>p9R z>FEmD$yry4Z;MNIl1vuvbmOq{xVWoJNABZAo%sITwnYS+E>bS`)4y!W;$kVN95L-7 z43$;rt)(`TGH6dP_%>DpxKl5@e=*BKh~`9Lp4!-qiX$rS1Z^xt)m@F0%8UZ z>qwcs3;y8LD*>_H+A6Y%2&NMx_2uT2<-Bqr5S{FB56?U&u3mt#Up=iuL^SiMoa2Lb z0`563v32pB;~EkwxYQ(l39uOhnphMtO_=A))zF(wM8Ld>RyotwI88F0CXt##c z3AdD;qe79qy^xaC;P1~sO=zd?XQ0Ac3J11kSmtobz2M$KQJUh6=wPb9tNTT=U{Gvm z3QO4ivOqBh%zVQu`N?&$QN1H&DyY6;-Y!yYs|J5Jq`*4_?)@tV`1(@kKHoee>(rPp zA2Cujdi%`o{Qekz$i(~>XJOkucc?Pjka2da&aSJI9^%%3&|WaPR>*6~i!1Y;32R>F z6oNO$NrBf@?cFCjDE=Mn+p>yo*oQ)jxTMC&#_55vFVHi!&(J$;XY+!-#pMVl^ zD~ugR=VQ1l{5?!S&zBvU+v9>>-qJ*kg&=lHDcoG`8qN=Rk(UJfWVQTT>`vDlNc$m* zyq3ra@=`)q)kXDj$pzLX8<7gq@u&ei2I-hceUeB2{1dvOT!y`UVvq z;g26f7~wIH{dH61fXPiP=9;AKjg3?hdBa#%(b)uv%5!$KhY@w%b7rzFob9c0G4oR|7iWamsEi_|tDf!kNaz)pB0$xtrq6C`0@$f@f|8Bf;on=fqJCi*1X#8IcRp_;a}}<2%N9 zJUIuM7K&X?CUqEDzlI(;7DNP3FZH;ZiF!t3ijY6#*b|8qDg3&qwEW#nz0tk9g?OSI zA?g6BO1lkIDKpIlb#)fC`ho*}T`t*tP}a-CYu+l2ESk8ftTfXWcF)@l@|e~CkFt0E zuRGqdhl8fE?Z!46yRmKCb{gAuPHZO)+Ssd&J}8B=EulFc^D%zS-cm?yS~6ETgY znbQrgn8i$@E-gU2p0t@Jw=vjW0BVf31AZs#!fhy5n$GI_BJf zl1K=ESu3gK>r?TL@p=H;+>v?-uE`L(G1elKc%l7hlU`uD-94mGVJQs;1%%@?tKR;!FoC0hiHtndafK#rJ7`w7Lrl=svC44t+sY%XLSzDM9<0a4;T@?>P^OJ-%@cEt)UBHLYrY4cfI2M z&79>&=-tt|N)4^zXf4p?ZP2*Snu}DTCs^X>&PKt3N6afUSNDBPj(NUe@^yY0#i*0? z&%V=RM(=v=4g&dovi<=BjPQ3%6gA zk7|K>oQ9P4{BB29`%*)^S~Kr`Uelw<3-uxo8+B=bP$eD=Sv|Ahi#AoTF6KCWABe@Y zks%sA4`@ED2uXlM{u$kP@FS+B|LFUN>V79$h!Iyl&WK{N4cHqz@x?OnstgtmiOj?>KH}x@D-&C%|BCqRL=9lXqYb`) za|W2?6~WNA)Iz0TU~7o53#uwv&lLtiZrZL|wQ^>skHH1i?1N`KY*mC4j<`=W(pnJV z5FXIp=Q#Pg9q`{To}crBNE=ZEvtCKu0J==6MGvsQ%vwv!<~p(Vnx@QtjyY(*40sg+ zV?b4x)~E6}@qZ|9cBv(wO3vg#3bXyp1`Kx#tsmUB%1PfCXfUdbrDRn!A!lZ@zF`z} z1MVO&BxBS#ifAfAN8pUtXV5oqz%3RF)pj9fVaLjMshk!s@3JRH?(R)|MDteFx{J%L zGR5{I#$r`6$|zw9U1cjq;2Vveq2*n&%H2+I@Kx$$rogRd7Xz7=3F>iPc%W1oLO&doDYo zIqy^S_tY7K%Zh$dCmnxk#kc2+Vh#;BsJ( zD~wDi_HrB^*0yKH8Wh3(d32k#0V`|A{S)3HqHmdGV%6_puZJqp59D8|K2eGktpF4R z>G`0Fq~lu#=iL;;p~zUO87a;3m!f+r81UmwTM2{T2MDd85zhcN`H zHcw>cq?bqeBP#N48t#P$+35qnYYWPD8XN;g4ql$*06k=wK zE2AV)&HV8o(piOnPFm@5$t@5GyXQ=}kaki&J06N>153>4Nyw`5-vE}vU*SXU{4@+X z>q>KZ5v&Q7T1yy2Y#mri7 zA)`8a;L#zJDLaJXcfPnkhy+5qOQ@Jo6}QVkR#iiwFZ#zoIZNl=;lzc~%{d}X3KlKU zeb=K2JI`1*>n%!-v^m-9H7lf7cd@$X_)Y-}IKHzuXdq!tDbLbpX~%0XeEJpJrpz=* zU2Om#c6*wR>$hLLom4~k2j=>+L^MS}%z1&uWsZLba~pd*VAspQjNZuFz{x2&e$*C~ z2`$8r!Xg9#9N^!m4XYvFRHhuL4#>AHC-x_1r&{NZVRK-s`$AAMVpS&~PU{Xr0OKHw z1H)Q{L^9s`_>voYdd}bB3tk&)8$nJ`Z3yxEDZeJ|HVr5N8|PS9cK;;5x$zm<9PcR} z+5EPOg|Ijg06l1_E?=~MuZd1Utv-R-FNU`&xuvohs$H&lR2Q$XAB?YBA2AZGjO8F0 zLfM2U){&XS*JqS`K0A>dPAE(?wS1Oa&O!#e@Ax^ayUjb-tW3_M>Bo14 zOV;`_Tl;>5DqFXmtSfTl8?R-!(o(KqIs_)@**v8I@vUWrUWPg{6hJ%1y&g7D?T zzh=8>dm?lS5P4)E@_*B9{O3a_=kh;cmy!cx0*bnQs+NfS9dNxvL2m^)T}EB+tS|z` zK6e@^jYZn_!qral{}&$wnxoB3ut zWIqL!)GMmD^ew?^+ufFX4}jtOr8EZGzNi(Sa+ra{hb)V$SDn&D${ktopgfw}fuV)M zJ5;avys+xHIQi!Flio{?=}_XEva9Ic5wegH&7zy3P(4Yz-fb^wpy*S>6o!w-`t>2v zJTA%5@FY?@Kr2JA(AwiHV=x9WqXs)8n0A0x+F+%J_JEdFKOS(`$;62FMphfB%W%Oj zGG4lgm?ReL%Lv|}s9%g;|C!6-D$3jQC-jLwJMn52%6UD)%U@BGu>h)LF;VRC-=GR3DMc6cz~T z?)(gVGp}@=u)q=pDt6O7;q%g}fip;T%^156=lr zV{q+A%CCjd7lz3n`DP$EkPZh8H$G_6i`Z|2KgjQ-&*ADKfq(uv#{cu(k^K7R6t7%lnIvTAPBs+aTN`a3z2{%n*cpSp+_ewPO_CJpp(-KB`7R=JC2v>yX|K&^nF2Y~ z97hkUshJ3>q7b_1iD~7A7)o&M@4O(rZ+a;LgLSfe%PX&}^8?J*E!ej1#mIl+N!Akah8AC3Mz2sk&3(<%^pLDcVY!R>>xAQ%_F2xK z&A{N7A&USYqJ2O_|E93@Pec_>j4d2ZjQ%@)vw@(316lk1hMh~b>ed$mGeVUS)SmpkRHHvC>I8LIdW?$$7Cf`>?vYf_SO;LOOiAN{LHwFrpNrqTCkh{7wlGuKS zz1qE=`cQPl3b`Pd3Y+Kc{S)xiq3ytD@= z(URB*$3Rp`iCf}-qdJ~cqS{Aipjq7&z3&f}0 z!w5s+t6Cqcr05u+&;BAdl2Q5H5u$B&`cloTAd_@uDlF4Bfk3XPHM(&Bly%$8LTKwA z*D&!-o#1RZpf#N;W;#O2H0e4SvX&bKJ4%W6CKC683Ih^8?GwaIVp=}E8DDXh!+P=$ zFJrzn-_ADCoKrpngDEyrPSMRsah5`-q|uv+H|cTT64kJ_dsr9SAg)a}hVOs4?Nz-- z!88DYrT;f6`M3AR|9*)Rhi#zZbX$_N7$R9!j8v^PT>?L3Ld6x21Z1-?DXAr14(%%M z)Tt|r(>{UtQ1;caNGiYzjqUK8#k^l1Jv==9jHLk~IdPflQ^h69`RsAF7o31Mw|R@ zD0-$vJ?H+MfTdCD3v*ecGlUkI4z(|HTik%}kN)T?1&?2?RfZ-% z{A{G3TPK6K67&ql$U$lm`o<|LR>$;$a2K6`(^d#zuq7gnPWiYd981j0L!dlk z7X2q(^U@uhR7qmT{HAMQVkiHDu74*skgoqTu{YBWHG^bA45@9th=2#o-D$ZQQo57w z#C@w$a9DCK*b7a39l5vp`0kB1)JKh9Lcnez^PL4>>vKJv?nU)WGeMewE+xn5*xpS@ zZ!$2g2Y4_>N)7!1mz^|>c<&}q&Yd;8h)P~@z_ga;K6^oyE5a6=xoT(KPPGski={CL zeUtx3T1!x2SoBk|j66P(Eb)fJNut5-?y!4D>`Q=~iu>Qt9+Zxz%TbwWyS-k3rptZf zuRcpKvy)cOah8FWomL~BAwI&}hxsAJuhgJd=8{kFCRF8HurP48&+)H9ODqy!GYJUj zEfCPZ*&qBTUH|JL`ZyUW5<#SpO<8gl)2#J}s*3gi!qBup;jTnvg%5(zUQCqk$I=;v zWa$XJ_F5DuRVD+J8)#cUqM{d7vPah$==of?^)S(r67&5C;sI*`& zxVs2HPKJ#!)>?gh7bu>=V@9jsPIY$52U=OpB$YjI<1_}_Ub^OTImsknypb)=EO^Ya z@TN1(WlFjk@$E^-^@X@S*Jt;qZ(D>O$Jq3(^o7q5cl-)(>~Dt;5s2*A!WJ9}m*k!i zPv%11qZ$gb_#CQxyV6=ufZYSjHWSX7e0NG|cDiw)MF5Cm?8%2`VgHb381_Dk(fj5W zAp-xA`t^$o6AnT$`%)hKbE8*CfKN_6%Kl#D4yy`07k2jk3M!UQuopE<80DKt{0=#f zb-X<;e`y3?$Wir99t)~qzeFCjzblk-Cdd_IWGf!p&pgFb(2aGhop#gL$QD*trr2O@ zZ}C|OZXGSJpnD4<=XH`9Jno*EL1o$6be$NTnVpB&yHdj#m8>onJ8DS(8*GW5awcg1g%YK(gU1c4WSJ z&N7FiZn=pM*Db-3l&)0PY(mSV9Oh}T*Ys;kx)%ZjDco+AHKPB0A9G$;B~C7Z0;v(lg#At598zF@Mc*{BmaCz5e*Gxm)j5VL}816#Vb2 zfxkihzm7X6=-8nsAcg#t*Y~e(R@CaW41Q9O)QVv%))l~sUWp7Th5qf{oI=8D-065H zu#&qU%ybEK$c$y4poMbqPZRIqZeTg*N#`~5dwY5V2eGu*{>pQ*PXvjc;FCZ-3_46S z91p<;%Nb}b{5X=utX_8FhE=!;yr-mJRp$L@aOhHG8(AHhY{+6Cw2G`beO;Kwo=RRc5Ej>l*OHn&W%-$I^L~MtV94PzLG&2mW~et}gT6 zf%m7Wf6}TVD)8;_`b3JPI+6D^zf=Hf1O_HwFo~txehMVTolPT!QTa~39hI6V1v5!S z_qQN0aYKk3BPTN{zst?>YwOIGmIIb&`29e;SiQrA7`|Hs61Ynxe9|EG_);%&m9qyv z%sdP;Og)S<%r*RRn0y#Rf*EWT`C;hOcS>ubR4mp(@>r}O)oj#6K{Lsv1KW*}v<=Y7 zy{RLK&mSl89pt9ITZinWUtrS~;xSY>rQ(_u(|fQ}Fn63u+2-1x2f?i;K9!N(!VfmI zRN6N{f2``Digl-tt$kDfm8ECTJ9t{zk@}efK0S}FNjqu5D_|5Z{}`gzxyas{AB^fF z@*ci+8#O&0uYp-)OB^lz{Rg%9kojkA$=L?vebs!~Mz!l4vy}mpBX8CM(-ocMxQ<3< z*%-l40G7z|P1g6wbFQ0l)HTVIG}k$z7tLIJmc#xNd|mOCnX!zGj3JmKCD8=x6*;1LK^TmcpzuixF`v1nf_rtE1)>%f7n znq#gUV@ApM^1SC_w|JH!-?R3o=5ylbrvQV32o9#xj1W%rxzaalmCf=OdYY+L!TE9t z#2Z`rrt+EgDt(erFkKnJ+J0fs^EME%OYmvf8!Je$Xfgb*^ca%fI>JYdUT za1rR>dna9-Qv0G_YK6(McTILx2KZ?Z)*!#dL~IJa(C^&iZ0SK zfYq@~P9_N%EwJD#4I!!^5Dj^`QYdI*fq*VDN=RE=uVY(>?+czf5E~J~!F~RaTL7d6 z**Vo{r1GNd^z7`%@z9s&$w@sB&qLPjT~BbAHT#`n)Nff!7Y?gmG68SeMI#;hrLF`~ zSl>HhNP9;Fimr^9u%)ggqEN3IKXRcBi(~DdIhB~*D^Jw&PLUrFqi*NysrH5A{N(vY z932#%6^J|PMv>wd=Rt2c6603tNPp_}m~2qRvV;j4KtUY+IE9luz+IjC3-f+O)7#j* zBPWEpL9(PZmKzS<@CWtG5L**(uZ#!%xF9jz<|GRjJCzpYp!{ag=;`s?NEuz^V>rUI zt0i_$7F+fT_OG6xt7yktwV0BV&x&82%s(uN&~)ioL$rk6g51~jU)+p&8M)teX35Ku zbMdBg(AG=i;#B?MTyEzfIhp3d23vWOvKkjB4bIXDO^2JWPCy2KmIjX*mC1$5@QT@) zD%KkdeJ-+7IPhTO+|c zV#;y(LPfA{v(A-DSBA_~Zz++)Ef?S~l$WxaF55zIdpXZs6lTolgf(^`2(`Lmps-|| zrK`1vhQs~L!j~e!Kv}TocE18Qp)r>XH!Fiaa?E-t?nA-F=VCcNgzBqhZgT7}%M?p( zz-cYkHYiH_{mXFl;bVE?MPFDgjq@TqP!$j{zNnfbuZ3?g^@n9XRUc9U`d+H<-x{94-St+FV|8i=k)+o)|AyC;;WlNh_;|h8x;qr zR5D%ot(b5=MQ)11U}{Vf6wxdZo1YKMLg6LMxAZKp+7? zU!}h+v;8NSPJf(oOVm-Cl?Q&iP0chCVUz|$lY0j*~IyAC)Oq#k)IyJXb@ca2Z!hDLfffgPbV1x=D z)uF#{S^LR6Y7#U0Jq*&@2dh6IOca`#Kw`*1qOU#-9Wm1%J4Id}x>*M#+6!dw8CSe@|%VpMxoE@fl>N4AK zZnRV?Nc7^9((00$im1C(C$RjSi96*6$-;Q$H}R^nh%!x+qAu-T>YwhsuT7pj)7TiF zZ1Akwdf`^DM0hYhq1fH`UBX^trsKV|fN^i{4@k;nhPp;eA{okPDebiP=!yxfjSV%+FU$KXb{aV9F7I(xUl)mZdGqD@R@uT8DKDP$zmAL4Hqfm15W{bYo01!j+7X#t3Gu?}0>qtXI1eK(2# zbM#v+T279zd2MZwcfngV0ZaH^Y}7xyo>9iKKjf73Ge>w!zj2QF9RUqbhXn!d2dThP zi3MksjCEt%zMWD9$x~*zr6;dx)4N>}rn4#}WRaX}dwsUcRMN*It^H0)c>e=&&zuc3 zO$+)%Ilq7l|0q7~Ptgw(q+;Uxo?v1Q$lk#9$@Kb%13JxTb9`|#+38e-T;e&AK4fB< z=>aNn@#oTMPfzfEx;E*c?~<-*3syJUh!-^Q-a)&)GWC024I-+VrSV^JwlN!`JsY^2 zI>)(K!|@ zifxb1Kqzhc$d>u&e>CRSw?UjU;VpcEHscjV_HihMe`l?|P3tX+Q#7L_7CBe1jIsGh zm#08UPu}l3t@}8|vR;r)!X!$>5|5n$?^06&}l#zPob^7yaR`BS_m zYAMYM0H-JbWB{VI5m7qbF4zVsZL*v&*M%f5lN1;pHgBWd;@#7gw9r!js+?Qc{h3Nu$`B)S;4ta}2EnQp_RVW2 zxF}e*s4J%X^EJNat?ZW{25Aw{cJnzt+ zar&G(mKNb56z=6)=}Yz1+Z~^5K2aH*b)qHIK7Dg4)}n5eeO}gHzSho_!oox+I0n6@ zVc&gH*gC2KkDY?wE{zBDe2z_D@b8B~Y5SpS@oU`90)$P^)NV8cUz-@u#ru&EW|B!4 zoMR-X<&a5Nc`$B1sm%dAY8G<=@-i`466+IgNxE!Kkg(Y=mcm%p!!CUv)WazxjjCqQ zUM&B(2bk&^dI$GNZ^*|p<+g|z<;Dnc)agj5Wm5dCpa|tKoHCo5g1)dEOrkFqcw~Or zZyY!eOkY~<#J8XN1Cuwt9pR|WSg)-oz0n~O zyI(*)6!d+wm<|lk|73SJo|*og^dG80bE%BD%M=23iMl{RED_*bjIz%K2OIhYJY-8K zmbBMWj_isH7qO@ny*Dk*Q!bR40II;7p90*ihE}+}M5a%ay2oocJIs)+ag8fJDhS&n z0C(_wAZBxDP+D_QMu=BY!kH$Yy*EGE_R~((RWpJYq9L(6Z9>?6Hj&J#ol4f&!ELTl zh@~6O$Q)Jn5_5Ce`SDoLmEt3OCO_fw6*e21Lb(A%1zgGdM$YyNgI(#MQTEHhxtT?I zw9(QvtW0aNDT>q}q4h|OwdAE9LkRDa3FwOo$aRGhozfsQw!!5C#0eE4gCgDj9zC_P z6$@y&IC=94Vr%UX$R~r(L>TtBCL*;wzwqlTw74BvhrbYgLS za}?FcFnua%;F!1`;|#Ik?+enhAV4{K7&>@aIY8s#{@8pJ0QOMw;b5=`M-hnI_F6Z~ zlF?->%ieM@_D;)7< z4_J&in+_HJkM^nbaTRp{u*)(${O*@U`j7T0LY8|cMq7^^RMm;++*n$31a^!S;I1VsXf z#HNf|eZ(nPF>emeA;0wKIZj-Aw<;PoBJ<^Ex42ysKx2!}o@Xv(@h(R>i7%dg^g}l| zVV{e-PsY$F%zt-Wmao8<@X@LEs16biFksLkg+a;7z~+_G$dlP}6=gMCS{-BOLfhk< z-G&=dky1j+k-Jr$^+YlXxP)6p-gz*5^NR<|O!8HI0W;uH3aO~~BQ*b-oAEfq;l@CW zHGvra8v^A2JYt|ABi93D*5E6uh=4p@X-;z_)H`FIJnbj~xM}d9z;8vBwS}kbr$P@} z1X1CTeqZ@V9-6VDJsImHHcv9|O}T9JHgohq8tc@80#CtaO84a_4MK^$ios0U;EoQa zr-UyA!tV$<;waqP=aU7iixJgK&Uy&*2yVOt1&Bbmx8$W^& zE@n7*T;pLQY&sr`2U>(j7rcbqpQCw#okRXV6!qDe=kZ*!;*d=><@6DUIpx?g8dc~`*48yncZC3B9&IZTDAM7uw$-2i1*XD!H&G1jSuG%st>*6N@0U=i>Mo!g4*UE z>X8m%Nc=ak$^yar%aQZn>hXUY*8ewhF!}$996M7^pFNW&qX;4r!bVDcPeSu*BkZ=_ zKgN?l-zaxi4PU|_jS@SSInKwOS2{X)J3-8iYM>xjAqo{n0Xe-0!gtybBhJ_};{`;4 zPJqD#z?zc(_w;8HZ1}z*lH!%w>WdI!#pd@<_$(?+y%us`Q0axXWyv@UcghsTj7S3N z!Uj^d;Rhf{NMo!L4E=E(W)PhMm4-?iJA zStTJI5ZdSNZ+RT|lk2CcH*@BjcTkvzQfmE~dxzhznX_KW9GlLMw?b?A5ry%wLqei! z_v)fBa#+1FLSMKLhVQTOr}8#;x^$@+aZ@Z~xMYW*SgI&$LyfaaGGvB)&`MKOVJQux z;}kk&wN%&0q?b<@E=d&iei`-<#2qj?l-4x!;hlu@p?y|4JVCOZ(UF9 zY6gU@!z_UNWG(D?DElNsKS}opFjs5X~1MLJ|#nUsE~~iF!R4FN?Dr+m9W)hc zp<0V6F?jjIDLWS8)&7~7=D~Obd*=>PKrp?ESy;Zg?$jBi(t6oHM$LR%p_@`F z_)LT6skJcCy)lM}hDeB((JS|_C|pH_OMn6qxCB<7{|yxW@ET6oR0Mu94|En071_Y3 zck$;DQ>(+U?s9q(X)-v^O(^3-^#;4GvZgRvD6(r!4_@W(M*W`d+IN@5Sy@5YO^dX1 zo6NahwUfJBJSXG_RaO`$35~`(uut7581+e!Nuf)D)JSSr!eGpO1k_l8rC;6EeZ6G1 z^ZkweNyP+v7LcL9rDou9WRrXDB{nXq13IGoKc;>?IYZ*xt z9pU+d#pBT>^F>VzE4EIUI9)Ol{}KUYxn03l4cj(5F|oAX(57*&Pmdt34j-kbxk=4t z2H~Z7o;s04U~*JA76&KY*ljm|ea+=Xb$_aX0d|7(FKVbu z7Ei?_6NSkp2cLhi{;HV0)qYPrZz@$Igus}+gX>+&gG>bXYe4emP>fWKHAlm%@(z6v zOTQ|f7BGLD8ru}Xo@Nc@|l#wNVl}FU`9qJaZ3ivGyp0 zz)Q&>A$%6J)oj?18Hv3|xZ4h>*V)UV%l$8BACxU_NKzp&wPAitM3A{qp@gKqiX+ex zINi^DLnHjqBT*@GIkh`83n4{K^N6C}T(e={e`-#vV+a?6rJAseBiyh~ctv9i!GouM zo((uJC-JE*=P9@@MGwBm-Ni){*n-4Oc9R37m^1{MWO1f-SXQ=1;Atr0JaId_I3qKw!i+C!8 zE6>0dOb6Qdb4~U%iQi5f2(hJQi3CdG)$hCe#sUbN&pf^cB`m=s=>H0ut3< z3hduO^?$lrCdk+ULn>rbCTf<{7WR>nFj(l(GC@*@w3HJ~MO;8ZL4_3Mw#&wd;Xh(( zhOi$Zyc`8G2Jb;3Oktl>QB{yCOG57b)!N5JPb+JYdhg-T7Q)qyj0 zr*A=CWNT)n<*W$73Jy2LgYv7m5hUHh$nfrq=IHRvSZfs2il&)>B}+m^&dD7CX4Q<7DnO%p8r4jA11c{-eo_7RkjRZjal{*0+#U+_!qG+?-p5B>=VE)HeqA{Z%k zSMM=FRC9&iYh+83zh&vqeg>*`fsPdoenG%)RvxD)ZJxMmG>n$B9k(!@%#2f#4%^fi+SHQM zp(}z5-CX(=wMG)^c{kiBuTg7@(B~9Uzt{;{tI+T0n}^;Z*U`?Jei>}XzC0y2L^8jS z<2isAJ@;<*y=@nd+T-V|{@hhihC`K&2U6JYcFbSSu>X#>KTQ(jf7e-%LiE0o1*1?V zo!iBM2*xuS(iwtwD|RF4>pLYPMa__M(!bDu4C?Db-1v6*em|) z*a#4e>Xsx`SypwB4Xn1HDRfP0xph?F78gV;Qk1_gOhJh_p7jz!eWAgIz|K$0SY0`n z%6A*4n2BO}n%}IBU!gRb8ekryz+S+YsM0(q9C2!kY9uh|`X;Pmn~jcu4r={eXH?7!m~(DoqdmP@p51XXz9`kPm>rzZ`u1-}ZO%yugJNEMFMNA&-ds z`a?r!fsl&C$dLXm5pBl4+T;TNjP~OqH7LK)?ljKd1Cj&Z21Uh;LhnzXZywE`Z(lE= zKZy*6hPtAv;IY}SbwLZi$`*~>VVAF0Fa^5Rm?Z$%l*_!e$(1Z}`dG;edFSH8274$u zQuE9(sm)plELnaMqfQGs(j3%f902?4+0ZObARH+*iD`%n*pK6O*a6xrf&}5xd=yOB zE)?`XsJCWy-;Pud2Vt87KPl)(dSl4mK41_mOcW{U%%5f+ZLH%g ziM`%Zg&5QMY8gl_o2^iDyV0#1xr<+Z3&tayXjx0tHAwH`33n&ykh_FFc$VD(s8@N0 z@D$7#?Ey3xBqP{>x8z$9ri z*axf=U#aJcA>b}pb1ga6j?1^m1DM%DPPP>iiQKT8$a=kXg0Gq}8w-7r=yI9&J@;Tb zsT%9=CyghZm3!$R9YG4~EjG9D&kYuNQ>=xTVv4}6`Nnja8*xXtWQENn#u z;*xtk)ovi}MGr4Srm1fxW)*rGwR0+&(P}6;7xMmF_36O)D^iA{!~g_R zbwMyie{6s9*A{>&_iT5-Sd2eya^*<&pruDb!Tb-^Cq(mE1WhU@J^S_N>ZSWXW*56K z&YHIk3W6cl?2C`b#>s8j95TDT2+Zo%O-k?2&RJM>O995}&^yu{xFz6a{1Jje>9To3 z@h7nO?YFGUwV`zeO9)9ZEkmO2l&$CrD2Q+zs>Z^AQ2OqsebI~yL?PVrXxBq+l2gG9 z#>%^Zcuo`^WR1Q}L^0+@?&?I(ZCCDwOt@1uQA#J=iZNCPvmVbfp%5~dr`+h|qfCR~ zwQpP#`3c|6W5}{KAcP|+Yj*o2$7?reI>*`UM7k000)}OwulQtCteiyVEcK#YC@o}m zHQ%mZwa%0YWoVx`Bc1pb?1u-@wN19(KTC#~y_Z9&WR#y2mh(?I710SVHYwPX+$s>$ z5vFH#vV9he{TWHWj*fVy0pbLoXfqv=Xhz@u2!8?#!PYqJY2pYjW&9fc^@Z6Zghibw znP!7b&*|4Vq@DtInQiK_m+~wPG$utkRktWBEwKxwZ5+Scd9acqAa<`81LYjD>eQ9u2Rfo7b{V#s7NUx{i!@ANf zk;L)agR2pu3@tRvLG9^=5m3n6wu1U`+_Y=EsxS6R7m?u|WHTOR+)}2`mQ)j8l#&_e z*2jG=$+BFyU#rfSY{(llo+K@uW4+>CiKmq(48Gy6-0X9sspfwm@K4sX>YBj1Ri7$3 zZwEg+EIuN~O8l)cWN-nuPR5%$a=u zUYuuI%<)tIe)=FAera5X3Q@qjHmUVis0PS2+SfFaJe@vtQMoCGxlXXhD8Y}`jgOof z!BXa@A|O)oVscx|v;`w*g!z+LE61qwnH9p}$m3 zNC2;+ZLG~%+56zIWft>^{Cb0<-f)cYw37X0RLGZ}*~abz*2;PdQYL36zequD@8wwo zYXR)z^5qIv$=-NE@|RN0d1rVA%`LK&KDbFJocGV)|2aU_Q6B0#3S=ukAX_p1|Bbzx zSTKl~0N0NlO_W6?{$me0TInA|#hb}lX9Y$T;@DY8M&+VZPlyy0WFAk9?JkZ6?Y^=U zc|5cf|D&u2^Ad(Z8LXQIg)lg+saKSG>cL>ZiN|ST?Br?nK2{Iprq;#?UQI^3q1IrR z%T~R`Vml=o#x~PDeaX0VGd4k-SHJYE#pvU%0*-h{F(%p7(aWufDG5_Jw8Qr~%u99S z_?Xl@075~Q!K73}Tx9wMBAFU-RDr~V6Ip_QF{m2z&ht#HDESx2K)(1;#aB`s(i+8& zc7vG_Bz0S@V=;zh53IQs#I5q)q&+?Wkxie+^DKU#od#6tSA%0`#Slp}#xmC zuwHcwndGmzA2-fVh0ikaDpN<2JCTL%CB_-F^H5c7mFGGkiS5%B(%i z`Bef|tR~(ph)gj#wCEb!y@8oKnNWqP5Bqb7edGgcoovoFPnL;;q=(AYJs;re`>`^A z*C7{My`I76+i&VLBn7T;*Ws7x3FThBZk-}G{9w*gV&9M~lZ~Z_UR^;kCCX*8?Y0!B zANTX0M*h$!rM~^|Q^3IZJrn&mf$>lKng0q5MH#tO0VJQyB?qWti*<|@kRtiGD}}m2 zf>KY)G-|~mG9pNN^F&(Hc#SW|@FRLuo&HjCRi1~4hq2{c>}WqJP}W$zeXX}9$I zCh4Tpv8|4+8QV$6wr$SXM#r{o+qP}nMkk$}_q^+zv)A)sziVID9QXV@uDk9U^{cA! zuf?egoaqSy$8G0zBZ>qjT78Hh{%;<@XqG-R-#7bsJjPP zZq2V~7m`_bDOQvSRl88?pRAyuS#hRcLaX})=3-wPm1;%>BInwaY21u7 zA(#IP{h5m)^ArB-MmuUBQzR7$$y)!K;rR9I>bY*W&pUWc^d;4pt5$D3Ro0$5L^@$s zh%2ESF5VLab|*|)yc9*`VGnlZ5F}W+n0lI~#ARq)DsqX#sd+chbwgkHwNs|?qX*oh zGM3*iK}4%=X}|QSDP)}3(`gTf zN|98~)p>ACM&LQO(U322z|6+>TxBJ<$Fu_Oh1K0+W$|=qD{vJ&rIO$^$iafJ*jBqb zlf-b0n3&`V8WmN$Pus3@SSoUczDm@AuIl~NR!|%JCDjxPqB%~-lxlCE1CHoRa%?=~ z;LbMt?^aGkHF@{FO41LhDjmr#E-T~Rd~Httv#$}d+ciV|CC0(Qfq;>O z@kwBJ3Hw2HLN~#{bUoRz`h_pvpfv%B$TSm43?XHaZa6T3eOXZrtKFWpZvDB@D*B<6C}0IXXUuXQXwb*UFL+5lpgf_ z>WxHj_Uk_wV0gv0E=1Q-B$PgzTc66$oI9O|6N($U1 zh;4@!kHWTQyhiP@Qw~PfC6Fr#mbI{}8m~+LS7TYoBxk?>Eo1VQV%8jesbT4ceodKo zwnY-oSUlvuzUp`DStn!1gl!ZXF>tCrrn$LdDx(#fmtNoS*pp0&s=*%R%@{G{H=+|D zEt(ITV>;0ffiOK0c`2_(P7Ta(EX|q~QUXx!Drzw%; zKr8^Q5AMT(CEBTGDwhj6)sz7iHS@VJI4iU|>TRG3f5{PoIiLPCfqbfAM=sSTBUa=+ zo8Mz8{6Lq>9+Q>M;$x)>T|Wz2lW1MBOhSP+&u57Cha17zuv&<;3sg;PFe=H0hYVhSKY<%R@2PTp{vD5Seb?rweXtlX^#*OmCqOedGq@(X&*s9CO$ zRcGe2QS1)k234AyNkY|-g{e^95z=MS*KPiK>mOCLC;?5$r7usw{+}@KR}}eWPyfH_ z=l^F@3bPhp6LERI4h2SOO>GtnD{DoRU)U!*@d#)F7J-5{x$SU;w(&-FnSfQe`g+3d zyzLo`Y*d1G5I%{Hb~pvpin@!=XRWIYk0XX{AJ32PJ0O=-TFg|g8iVLf)UGPMQQ_Jc z(%+9K2*6>tb`)s9mx-WTZNHKaLcx#u>=G5B7jh>`fCSmMzviskxLq8CFA^eSU1Te# z@L{JESu-<7{33T=oH*hrp|MF=F5Kwu6kqW|+35;QB0~q}0NH^7-4MoCykNm1;o1Zw znh5-g7gnVpMTJM1S?hCrr0TR1V}7#eo4GiwN@o44pP9QGH_ECVDLnN1u? z9h?0}AI94hV3jx0a@Y^E*gV$L3q2g|3q3wvrEW4!+OaZD{KGf= zP6AbCk zD2UGwSfkPNt+g6YnlTCd4wEYFix@XQhTz&m(LZ6f1^eB_KC?x7o!z;aB(Z)$JcGje z61^EIu42W0UPa=ohCk;hzRoPI*(^QpD85M>?lI+Mi%-n_lf=h!!{bXcJL@A0xYhCY zfuZ|-M#2_G53~C9b`TkNF!}w(FtwYeKO5ZjMN$1D71oEYU-jTYcjSzo<*NnK*i~A_ zs}a>FvfBIw2ehkd*Y@M39po>5HOxB#;N1}9GthSAW*XqB80-VJqxrH7@)z!tF5$1x zl$U0|&v&_KHs*I#kaVNJ*C0(haD0EseLn|3&0l0d;9hJfyF#ulU736t!ew_-M`T63 zsn8d2K$YUezE{KY7D=bU{#Bs*lGNlc#6ATiL)3(ise@^t)}|1r;;X;lu65oH0z?X$#Q{BDxullYf=}VPy7XCk6&V6cV!i%^hfG?BV0%qAK=B4!`QqG#|2@R;_E0^cY`Sv}}|EIq$A*V+$INexpfLI#@-Bd3z5A+ja zgOsO<869by={%!9eIhHlK|Rf8gSq-p0f$wTah$p8A1gBS=oR**Ew&^nsR1%5%R6U} zhy*KX%-R-?sk$sZ1N;6l;r*kA)DcHUItFtSc&xD7meB-TTeAz9B68yT`P)$A3DKen z*D)#0*np$J*vR84s8OC;VVe4A5oj57Vyc6gP|TX+z;5i9rh{FLSAi}q z7o7c{3|wiXrE157=>@I9M3=k4w)`IBKPDl+ORO}^GklCID{W}7PS3`NJpfdmJd~_( z`u9{7lSpwu1IMKO&AJriUAz9WPB*loS~;DWp}JG%KmIkggQ1Ge6;@P=VbHeI9 z75aR2Z(vk-O$w^3zWKnk?V#AvA3a9wa;Zwc<56%1aq5F%y$caG3W6aVkHjtwjiV%{ zZtn8Z;ZCwy0fvgug{6gpLo{C#Rkxk$zc-~8a+o8o+3d_}3l)->IFZa3#RWs0XG|R= z1r+bM9*#70E)>I-^YkZ+)!(gYVpl<%xeRLrV_*epCb#$L#J!KmYxj`FZ7`F6AWNvX zBygaohl;ayHtWBFn3gJgVk8as-`X714gG%R(8b}9_lPu4%8iqrO}~&RF=b*;N`O>t zuiyzxU#^o)yqi36q+%LAO_A%J5lEt*667#)d@37Tet&0DfK_?z=_p2#V7^uZ&{a1Z zO^D+<(}glN`$`FoHm5_$yf?b7&XMIg8$_2F6{b3$8F3Efjq`LgRnfhBbalK7AnsRbj$CEZ-%>JS2`dI3r9$FC#fNW7Do`-!IQ%FJ4O$NhSZSb0pG zhYFHZ4i-mL2=fimfq_)>(t6>$Is2HRKb#a(rOIfLWA`gnSZ6ttGNSBU^5^QJLKyOh zqvY|T4P?ZSDe<6sGetFqqoHgxMwDn+0%SB#og)bzgc6xcYLVCEBYGgBp-3XJ+3SYiyKK5M0Orn%FZlx5Xa!*3A{+1hoD z&AGf;@64-w=f2=Xm=7DoCS&)cuTj;yg&Pgncew|Z8}Q&!Me+e79mTbDh9@A9#0k|Ls-t{ zOS7X&x-K1;64=VrTRC+t3KVWvQuH+=(@*xWMy2=aL9rLiL!;_$!fzc$YKmHlNduE6 ze2WX#8elDl!2!5Qj7K582kICQx{66}1Dj5>;vqfXVTTfH|61Dj<4Y%w>CtoD4LyfOYP)?vsa+rs97J{X z5sZKrP#yQ`>{~^+N1+rz^{xyXx~*uxvZ0Gd)c^8lc9s=5hKJsijrezHR%qMW#jSgl zD_yowRSQ$kjIa2Z&2_Te!uKM9`gza90MJk12Aaf_SsOJ7PO+jafibx{6RIf*e}Ioc zP(rscS6fX!xgm?Yu2Oth;~54G$m8`fZ zIRrml3$F5SqTjD-5aWf(P>Idg>_S7P$rEG@)*VBB<^)s#xajmRcX)8@QTsv45EJOa zENAUsSA###?1&I}M{WY#Zg_*YlOV56Um)*x{$T#?W_2 zHHj3uR+w*st4$h;&!0fq1>dRFkyDE*?F9E7kT8O%R)A+zrU%ziA>_-V*3U^ZS7B!s z_fQT$xye1Q0|le*-k5V3v%OP?81zwbN0f^kG9$_z?6MMg;>3Gtx*?WsA##`moJpt4 zy?Aj;2(OtWx+q3$@o$uthEYSIX$$>L)ZssN|07F8B%MtSji$}F5krn=8gi-8jeci| zRd+ac;)?C z4?ntIb9*pjYtq@>|MdeG=!xLGN7?nEK$0> z341+I?|HxPikz)SXZrd;o``|3&vzkAdCfP~(dPQG{{p4+2uOC>CFkgYUD_?anV)%) zg8J-Ecga>NCwFxct=#ITeoEe#ROczp3avnYKKsalNUp2*cyn-N%DE zy4tzKsHvO;q8B!$;v5TQ9VLaEQ3e03>cP01(iKd}&*fOD^Ic;2ge2x>9^Zq3Wiks9FTf0VRV5Z^&D`h0GxyH5_7^)_WuY%we%2Gqecbp( zD2w_Aop1PUW7K`~mBylvd)p~k_E*A|*LUpnLB)41$(EmMUe=4hpRRL}b$43ciCA|e zSN!OyC$G9h!x7pvJVzGy`lE{5IQ&HuinDgv>x>EH+Ccn@?0_@<$X99y7m5j8dF(8! z#ac3;$%;LPs5@$iG9q`<2Q$eD#DKAYheh6iv!Ww(owK})D9gk~b#E5g)=Q88A==GQFv)22LYdt_cqG)MVws<+7s z1}oRPE*;8u$!R#DTY8}toWrP`D$S4a_a*9o=C`>5KZEV1|Ga9#!PE-)d`ks z|NSoKv=B^-k9Sz%S?;XpNdwej1L?HFB3-ma6#?>h>}Ij-BPHUx?gSXlu5&zb11=<6#2DmWh)p|b<+CzJZaJl{*)rT{7Mnix%<|JJ8V6RQtwBiu1 z`MI}2YM$!Ahh(mZx7TE^%}BS~2R`k9Uk;*P4%0NYi`Y|hoftnlGy{wOV$r4I+h9HI zH7kX!Pg{?gx*hH}&xCFdUDxQVc`a)JU$l*@5V`gM+q4G1Rc|<&eHIpAN*+1}Z0ptV zDeQ4tJ?y;0y7S#W3=7>u^#rqr%i(k9X%b${nb>@AQdC@Sx7u#T2}G1zs!DA@g{t+I zclJdbpC)aCF~rOB#5Zpg5j2Z28o6x?c$Y6hEB50!%lxgFymg(PychNmZbHhgKm79^ z#6JH~Bq_RIs4M=;pIv>e|53pCUpOoDCD#9ivm}iF67r3dhVElP4AOlMuhe!0$G95! zJ}rlc+8eazofiBTQ9<0Gx4iWR1YaVe&Hfzc`;p;}NA&R}{7;d-UJircml>T3rE?AH z;L|0y%tjSFm*R%pD9pV6#TSWJ4F@UAq)J+dOmP(nS1PAWu3hd_e=EX{tAS|!E&~vu zI!4h|sd(U6P*k(38K7|GI5I9v|271*KIdXL-5V1eTkmC;!V32Tl3}#mCkc*=K{ZOL)q^2FbHHj!RC{29EPJaa^D{+@YOie29q;a6Q^8jt#B=yH zJu3H4g_Y_Zfmxql;D7DVllGAa@#JnKA-WZAEN49y!nRvnPZ%M((XzaYcb)}5TVP~& z5TLV^ZW8>9(YW(>qM`pD3}kDU4Al2+0{i~lL;ua&fqz0nJ@h6oWaieHdgH|W|WX8}Ga-WlsymDh9Hw0?Rh-RSz zVr6h323=MuT}`NBWwbCX+Q*GZkbM~r*$=$fgbD=qz)c9*vF`>hO}oL2?QSV5d9HHOa} zg(^<7`HV5q>LUH(KyMw8m{CxYAVQZ|C+?&WC4pi_6yG1}4^*ggWh@HmD8wIQWH2jyK2kYd6m4dZ=7n$BO>QHRW>ALU7WHn-doe3Gd@88b zp@x2D}) zCSbpP9Ek=|oL^HMd35W1&xr8W7^t&3s5ZRc`=c9v$!kdin>tGzPoslw4+aN@1rzSuwB}*HlH8RIwK4V;skGeFdg~2Ll$YZLj zt)nOmMGh+CARixVFB7eN`91B;Kx)B9F041&uIa&NS$#mVL##>)`nO~1_1 zvxKs@#*Ks_-UpC1#phf9GYkV$GS1ZjYuow!yt%9G@$fkL_7EfGcGJXja}R4cdQK*k z5AyugZ}@}axhirK$FaxVZThZR6^+<|kbw-qR=hWIY zOLpfc3O@O}pVdy}2Xm-o3(&bO13ZjD8~7{EK(=DY_@E`8u=NYj7R3~;HUvfXE$-ZI zlvhgJL*BG>RBlS#`2cuDzD6U&d6JEgQ(EJv53_`1t~*@(^`E?AE%=G}npuX3Oe6^$ zJkHTTL?+UQ9R3N~TlUFWb!3GU?IPEN50e=Dpyciu0WX4Q0Z?jd+aj&)R^ zx1{&lAz>8N8Yug7mM4Nd!XvYW?OFyj);b#&CW8LJPSyRX;SSn&ugHj|#{+0y=y3X? z6>Y}Q1I-6DG_YD7Q}t)YcvPXuANs5%#s_-F@TCCn*(Y@Mch~?f>yZnR{)#CEE;owb zfM+U~RxB|B^kQ-}cmiQ%#Fs!d>Fd?h?M4}GZ5 z`MaV_8r!cvs-)=wSbbc!&|6R-^lE;}6Uu+0WYj}yX&`FPBn=GtiZJ)2QK zj{@-)-84VQg8f=ujmVHf72KGq-uyW+5 z$v<~Ld1DZEh+pk-nb~iZOxiOiGBzj9piR$>t6+OZJJbSzFQRq~+iZK`>=@3yzlAnN z=aN^VU{A+s#Dg92CF=+?r(4!s-@&xZ0MwOwy6{nqn@YTF9j*6w`Rie^;xx4|sM zHM@vkbGcEe#qfVC%-dP*AuZCbkjEP(wXhX&2-?jSX&pbxaX0ZAd007AF$F2UXeF*j za@O59?2el-D&;#~7!9L3Fc@+%253>_SkaK9Jtlsy8F;EqqGxn^4%=+>3!|RI5V~s59~P70s)vmhle&f86V~%iMj=Gca@2Q{ zT|X7pK0~B@A{5NUWKhR)&E#2u4LF95VttJGslH8Ecfvy0{3{282{7eVJ?e9spJjc8 zHq925!*~A!K!w7Qt)9YP&lr0|lnsqJDu{ZI@Pek~Z$TwOOWR}KfXGQJSZqI|a?`6Z z3V9~{ZM85$ga!JRR;#atfy&$nG8j66w47fZ0HcKb$zF?34!41#0%0O^^^5=$?*Z6F?=kNLTO&e{gNR**vPedubQSJ{P!P$cCjfF7)FmG^VqCQ zy*4PUeRxj{)jQaGdD2}E(LY|^d!dlY1JTXe?+39dan7}(uk`)}UXP=P19l zIRD4?gn!42|Hg{JL`}tY(Ju|7z9ICgAHuE7((}wv4VXxBKMqvl8lq?oPy+B?!I-T_ z4CtFmVzk6rJu99B`lYz$pPz)B+*OLIiZUkxi=#{&r>-wnf$7?((R@Cya6QN!uqL+> zqHbVJD+#rvMk1ohBg+6OLQBAzAgd8`&_+@%@j+FDZICDNJ{Mtk@~z4U57u>{5#TAv zYSav7)EM{2YV0i99P%i_{0m{gdd)n$M89}{A_$bksC`RvbLurvSFmj)qiK={s?@Vq zp%e>w;f@f8VberLtQsQg(DAEhMZY7!7xvVJg zYy5>`!<!RGL(isSTz` zt==57Glr=x=TaS9tm=V?L{TpzS)N8*YF8QY`g`fI7?*XWGo@9B7nM)5Y!9ac%VIcBi#+{tmtu##^1+fr?%mo7$TbL#TaH;)|K7r#sw9vyl%bA~(L zRh4uIRQ;Hb>w_&rj#;lOI4k^<5O3LnU2;DiQh7a4&2*o8@8Tnd%tOxfjptEOmI8Nt zz8aa5>n>Ayavp$W%B!yw^CdijsOir5h3CTG(b&pA4UOZM_|Kl5JqT*sf7fL%%38gu z2bXCXTTfSnlZ0g;bOjO?&)TY{(I$lQ%c9yhQ1o%0NHgwTmGt+-SdxNmjp!`b1n3fyeDXP>64o@6^kVHe64(t|Ez4|{;q`iY${ zhE{ApAig(dK!bA5IEAtK7e588Tld~ zQH{bEbqlvg&e#9Utipx-u_AXnv*^8dyXo6k%yWC9I)%h=`Jw26Y=- zL`6u(@+cTeDV+&#FR!?Wn9Tg>5K5iq&N48Z=1x06p61RoP@LvYJYbsU&NOh$CpMZr z>w$-n@1q~r0IM?x@_Ruk40^K%uR{>sSYxw*&`Lg4*)ss!ZE1APHw@3+`4*jryO?6E z#?ez+XOAtZKfJz4$1nrnD?^3$4T4wmYt zKo))Jg8qv#(Qe`^TC)qwDXPm~ylxz71|Ygm#2V@~+(fLB_#b~6!V}oA`INzxf21qs z{}>|wSDNPhYXQENt%E7OzTMZbL}PkKeN!uQ10X%n@hdH%?_fml_%DAVYVG*HWoaTK zB)_sWn1RR7%9=3l;22fH*xUhfa^M)h|8!fQ1f)oy!01c{LEgw2ECgwqVy5WtcyQag zfFKDXWQF)5UYtwDkF_YpF>GO$@5S?2MavJ;qP%}7uM4gL9vO&e&d4x4j~Zh79ID0! zei*8oZW#5Isl0>KlY$1?RW@3w{EmzbW^gkMGRO?qRv2ACKKf*0bQ*x~I^*oG#DB}a zYzMz0htp0l{Xnv}p8xLy#J&eaVo^rykCcFYA{tJ=!Nwc@5QXu)zNY89H*n7Jih znxBo0Ybv*Xm5WQJ<&Piz%M+b!vfoIYd@`6!uQF0?k0vHKJkGWe_`c_W`2h3L37ie_ zc;mztl)1Go`&euaG%_sRUMLWjZ|yp9JZ>1V*jkW!A^|oI`}-}aso8eB@iwS;RXi<0 zrLv8 z1gY!0vz2ZW#Y}hT(G-a+r1;LC{62%hfJs;+ok!|UQ|Q-D+m<7)HEX|GhGUt{E#i6? zwMzEsv>8|$iacsVpUc2l%VcO{CdMYiE6)!rv2XS5HUdaUXoBM^kHUlT71E{6U;}QQ zX*VmHhNWEM^jVhjit;;}QsKW-?`ViY8)|6qLy;kCwEgzawS3TDC7GaaNQ|L}4l97n zr>vKWuybTG}d3W z-YbfgKy>^pm6^a8|DQ|o3n_VuH@pj!C1L^oaArz1NRf27s&-)H5C@m;Wpguaa1h%= zlzycJ9kgnys){%zmYVgsZHqyssGgA1RIc#bXeqp`QI>$rObv}*`K3?pE-`YY(w#r9 z0=?m1IXHwHAq*Mn7bI^fV;(9^t(P$W?7ay8>|JAs>YW7tY^Fyl@7$eaSLvLQ!1)>E z5*U?ch_v){e@j-DWjl?)c*@8@SwPz|+WYT87`2X~uG-edO41qO!79sO^=Ohe%l9FJ zicz}{IX=QR4(#P75|oG6=8o6 zrKbt?o~1RtrO8pqY4&4Mq|mj4DxBlMD>CStAw|uQb>!|y!w1ZzN)0RbpW%Q9!k(<{ zP)%zomwzuE-7FjefL|fJ?7LoF<{_8*TCN zDw*9j6O7Hn!!2$`eTRZ`>@h@$@sp2x$D&^;)+K-qTJhrbg7E3pg-DX=;(?kVP=D0c zpb-(ds$Sw;l$yXqrb?F*kb{+I$?-(l7VeE&;g~UFjoY(d6FGHNjZs`rl{Z|l{w;d7 zGSj6EgHtYX%6pd$Bcd@sIkxyA9e}FLBv48q6`Oc`r5O-B6mWE+lwZr(6WoSPsH#YL z7K6G0^=hHaP?{K7Fo=|*0;tk&UX6U5l)oC-Pb+R=?;jO5Wd^)Cl&C6}i!Smpp0HE? zG>kIB?Y2f|$-cctD#PM6{Nta43KhlfbiPDI1r>E3Bc%q%GUk#b#ih}*Q35BoD#BF) zX4zIc;>eR$kZ$#{-f22iuenlq1~;nL$BeqCL&i{lP=UcH;XwyO~AJ0saXbpQCw+lqaGv@@bs`d>MuPz;ccg6!T?4k$Go=Xl;dXF zy(sYMhE#ew(&UCd`Nh#|-kqt^nJ8q5zqFbl&Q_kl!IQ(w?j?$_xshOsx5YCL0l;+c zYDd6|)}1|s1rGX)5P_4<7nB~55~;1`i;$PtCDn{-2vfxXm%PRR&g;uM?m0y`7b^z+ zoS5mm$K{`KM{{>R&Le>i!DEL@mel@L)xwyDU>*}=Bn>Y;9G3fa!#%<04^y9VM43kz zeyXmu5I`61W4{X0fs8*Ku{5@^Z#JfWMdRR(_br9e4n$c;EEWh-PomV;(WRu~NM4Vn_ty}?kSg*$wX915hR!M&| zQXEp9qO`f)zHIdbxgHfD*gCf)<-=7~;D9CfqW^8)feo(}0H!4?-UW%VD=P7xenRk0 zd(u;>1m7GxaM^<8qDOEwPunzPbhUIh<>j9G_q1VIS$1 z86U0qHjq7le6ZYABFzs)0ZK+9++IQ>%(TFSo7%b`sKAz$T-6so=j1Bh22)(v`7m{u zw=R0v5IuG)aRv^^y%8cE3;N*{>U@SSqrpZf#SUz}_#Pfl5qS_O?u1O5suWm)8-FI4 z;EBExh39;{B8&YCQYEF>1J-Xzq@M0{IE|)^3J-PU448al_sbN8dk2=%17iN9mpwge z7kGmW@#aKRyv)?zI00Z;kW~2@%-&B*C14{?mPkSs;z*NIwV>Cx8P)*zETF+{ z3D~?*M-1L5!HeLOcU}mq%a_sS6vF+kE6oJV=o0xr>~G3E+Da((s3K)7SQoEkH}ZB- z5V|VDQ~6A;S;yrQZtZgk5JrF2qn_lP^nL$Fuvy84! zpNi{_nBHK8aHJ7f#~HxiKw#1hX+UkMArWn0TUNNfy2gW z=HDxV!`CkJ-i)oQ9dddXx9#K8KaZp2r`bLqZ(P1u?BSpD-+umw)c9XhyNrLv|rgH{dtp|6%5>1%>Xkvbgr zx-}gIrnXJH_s@+Sx{Rs|(t{=m>I?=!Z(7|3XEuqzxMRmMGv)HtI9g<# z%@Wl5#?CnvMpI=on!WNR2&v~Ci6+t3GmXX$)%qq&Xk-;0%^c_B#idHTvPH{g)he~} zS8E6%)cQd~?s{!5H6@0W)Dp!jhjszOXf!L;q_#@0)5U=1tKfANOdaUPBVLPLE zUd5oMWo8jETwHM`MCswJXK9sO!j51pcJiY%0`~IC9F>xI(X%kSi{Kw6J>o~*L|p+K zEMBLX>;Q)74|tc@Vn21VdLpn|hTF4Efkz##_Dl4_YZNgi60R)n23hmVDzRzqSwfB8 zPDw|i&2&HuI}!o_W%j9Fd#NZdC1K?x^~*%PN7X$((Err3= zH!PXxy5D1`6#A1#9zwN}CCwO8FbVTanh$K&NiE_^54`#Ge#@6FuFLl`_vc342utQC zNBBCgJZTV}@TkI>D8+zzy2*gO=v#(?Q2PfVUHwDGm{necv9- zW}YC<-?d*z^eMg|pP<=O>)kk%9r@@osTkFS7&>G^lhPq$C;!UZ%WCVFs6+6X+c;%pl`N z>BrV*3U1x*$Lx}EWes*}J2H6rzAMJ)8GqvmyG``Y9&C%^jok0T@Qb(8-0?Fwg)cP! zn#VbUi6WR6(O_lUHOM?t8yQjO3v{Fi9_5)S6{4ed*|1=99o^6CJhG9(-~^ViD{mi6f}%ZiBG z5{8J-k?RKWkc<_)FdoGT?Z8bEF9%oAdmNue*(-Ul#oT60VDfyreiN$&o&R|I)&Rbp zy$@5z>Gph5;U_c3z6y!?@{b`j)&;czMgNzes%UTZKY{8`3jeRP+<$>;jK%*ER0kdl z#Sh#53skYo_v$$>qW&LHeJQghv`%%$L_&9phvBu?_@&RGYHSc#Tft-l&{wAR_Fatw z5p0>&V5J@$`X!XoQ7c$GHOO0PWC3;mnVQ0B0IBPgxvPTkG5w?s_Kfnq3ho~sbBvnA zSnG>ZrBVKKPW{)e|G}vqo{EoXpMP!U4_G_ZexO$55HVOuu7eQ$(zh@tE|Mf5DXhpT zZyvSAZjqkbz*(~S-PT%LE1kK%utbT1#h$51CS58e%r;xwT71vQwprv+_dGuRchz~S z^ArfGk<2&6(+!-ye(&nOpWu1DCXWIw>07ta3Oe6h3B`d9T^~FU#jtT8-cH(>I1rNJ zoj5=zBhbH-XLH^+1n^!BC9V+fqu6*NXB+HR(rp|KS#5RWG&4P$W4YYiGgbUiNB5~h zblIcBxS|v0`<}9)7z**i4ScSP{gZN$cng8`TyIB^?&BNu$JjGA8`20j;Wj6N{`QOz z3xVl`e%v)sxtnNT9s?h^kBn>Zs%p$lw_g(>urxJ-8D}p%R1-nI@-F}Vkp&~mC~!E1 zo0OP#pZ(fvEyz^srH$WPSTao;tdCx9C4Nc4%ZY{o+ytVjoJ-kuE#h=5rZm{40!40a zG}OW(pUOlc631dCf^?6#j!2tDD!a@H4UAjrjGI^dQeOEZeNU;T_0n0P4 z&d1==5ch{9erk2PACH5RbdwQ5qeE|M+$K77JG7Zmv2`k-V%9{0JK9TpNW&6haNC?Zu9omD|(B68d8t=9;vmIZh(biB*I7Dr9kan&D!i zww+ISTWuAKLAF2mch<~Bd^mG8^k3!9w5N=M_!l2o=cR?h9H}a_(e`PmuszfVm*r=E z(@JG2BMOH5mkStcFqhJyE$s6^gGphhFz1O2FtJkpFo=gCN97JV{sLFgy0ry&X^8Cs zalceYsdhfZ8|w|1bXO$C7rcIfE7xj1&fFKcO7uRj{C&Rt_FK8ppI}b} zg0D{!T4#uwuA^*c*fkQ`Q*f*01_gp|@Em%pXBzb#mmKZgzm@Ga$zOL*6>JbaV`wSO zx4WvdsF3tzyQXz;7z$a2ghj30%dK1kXk9R)@jt0t^EiIG^b2Hnu4dlD! zSJy*JtKI(5@K=@if-SZqJfDq)yo(f-clqAzmnWHac?DUVWg>{X2z>3{>{|?IPb1tZ zw=TUJ%9X7Xn8ug0A9j+`CFa1Iq#n8xz@$;*@_4Z_)~Ty9`|M>U0*gobB87E=0n*ci zF4{YsH0slWN!xlpg(Ta_DxcSXI!2hI4Nyj!&G!c%c7JKM~q(jq6I zQmFywApaKSp#fF)8<|Cn{A9zMef^?v(vcaOaqCdD;<K8n9=Pqjq-3(iG%|b4f&_8%iKkNH~j*l znX=fmlY6QN`7kUUEz6rvgm#-~*HDO>d|qPyLfUJT(OS+3rQ_L2B?Oi1dGtonw1T=R z^l*f|kMsx9-}$89PADTrL>#T?but5jZ!{~vve5t}_+10y+}4$*KmS;b@S0y}$fk8H zF`4o3aJQcFjeMbN_5t&}`G=0iW-zzLH`N3L0V-YA(2lzf0rs(IqHgOSQvxkRYHks< z6c_<^^%jIEhcQlCII|+)sNgle{Fy?&5=2UWig8RFbXAVvw4>L?*tc0u~i`4=# zF@BXeCmtlX1l#8d>oQBa*@}dXd$-F8e#cNAC%uEh(G8Ot-roBHStdtxL=4TdC|Gnj zy)Gxe!7S25QuygO{SL`KBpRMSBIpq-EiD90v^a%{ig)~zBSUrGTJG4|vact>eie_# z>gOG5D!Zj4ISwH8b>&nVkA{quT3D`s_wK24#jE=tga;_J?Jb621%_S9AWj9 zzbD&_QdQt9#{j7-u#_oMDte&Ii;q_H4f(Cd7n@+XlZ|VfeF5vS%H;AaVOH-Dfj&RO zeKx`0pR6mbCEQ4p{X1Pck2m{UxDf)BC52Izolb$r5UXU`bH17o0| zi25Dpic*8?#9YcuIEZ-UY$#?mm7RAV=JM@ETIyb~UBwdx*giAGAE%Luni?ZsPU*v+ z{SCqu)aDI0O+;4+V>V<)p+i{J6YKmGr3AlPK2WC^*CXKllT3S;b{yi6^<5qVlNgDZ z4u^5C4k=%~HlD=umeaj%K)uWAhXHK({FP+Bg+H#&RXUxd8=TgC*OX6AYoXKB%Bop2|Ek&xoa+afTij)*r+_bn67@b7Ys_E`+4p zmyISYl%f8X5xs5!w_Ew9Jm{~leg3|{^blnb+3Z-tQA23`FUsC2NVb4$5^bKgZQHhO z+qT_(+QwG)|MD%p}tAify4xrjpJ_~%zk zy7PH3G<*KM8o1Su;A;Lb-d~Z-Sv5-~u_xzlB_N?;rtYGB=8iX@RT`dEjC-RzbwqQ= zR+DG2VNFQChCe55mi^t%?W-7!H)a*7B`9CJW}3hUqlY?I(KI~4&9VIMq~_)s=}_iU z^8bCNMO1=MSyvY{udPyd-2WaorseK{cvWRV@34LJjng3sv{pP`hgvj-i1rU`kd>LKv6KnjWSiBQ>AtjJgfd;6 zX*9&@jEO#D^cXX#)QN0VqSR=({mn<&li#b0;*3YGP@2^;BQvXAoQ9)8aKQ()C88-N z+%o{pRuPqy&`O~ydS7B=@N>bE2eVANIn>4SPA$k6}jh% zE-6i_8)BJNW}4y7QuT|7$Q@#@zf4l4auXFtW?MVMW?mB-cB#hYhmz@Z~uxh5}tu;MwKsa&{{WkYd zKP>3pp;s4Z1fusXFIb47hTerJJq#JtP)oywr}Fy@5p{=AP+ymImW&Q5{;v=%HH)CR z>?38(Sydz)BihU4OI6L42m(W9iU7pg=F~^w2Bt+saffQfYL{U02%M#IYx*9`63w<&99 z;yQ$Ui`?}buAtZ|k&X;il89AKxXbOw>f%!jac=67xYM|y=T1(BCVHf4|FJF7ho5=} zlllOOLU4Q>H{q!}%CPg20QoY=;rT+%(U)+igH_iMwWhUuv;ES8;{bUi+R}`Kf+z$`hAdO!S`$mvDJW!F)F}n$ zigPELKcYxt!GLtfuJ1$L*`X9|*;#AOo&;qB)-4P3v#c!3L`=6dT`t~Dm~^d)!f8lV^&l4> z)D@?##h62(gehUp0cNWaH`eY~(5%R2pRzAZ zj6%1kCH9A;77#)tNztju5G}I3DRIF+j-~jV z#uT-^VQ1aBcTox6l}fD&>xm?m|LKg7`_26>K+D4#Y@3)Unu?qZw8*s#sb+W|hzjXR z%*0kC4of!#pwiO8fo)Wmz@SMXPEK6h)SgsWQrfH!Nxfo4Nm=L9X68P7r^;LOSS8=2 znh5*U&Xy))Ws^Gbs>!bXGlHVjSZNARR%Tng=%(HwOZ_V9G!4~xR|*8S8b?z$ngleK zr7pwwuboRj-}rYlm9sih4$|p%HseE6P>)i6L2#A;rKN=EfOIu+tx_zt<{x!%n|7_K z8SZKgNAyip+kJ7dDja{+TlC%1c)R5#wXjta$`_Pfq;U#8y8*>=KPkC#ySsaCV_%H{ zA5Vk-B6ER-3tP?YT}2{OlrJNQ}5`VRg~kTS(N*6V}>lDDcZo>p{TD{s9`md$v=Ox(117m;5qB z0|~n6l*z|eM5VpO&P=X}_&)v2#8^-6Fj2AINlHiN?|rE^cpAXDGT#LX9?dBvJwM{N zQa&UBz1py7D=P;ZSYGImiE%4&(+{W4r_`wh85oM64HH;l*r`~p^b4P@kjfz_CPCFxjULua=LJ_oONNA zq>4#cLM=3yr*G(PPIW|aC&-j`CWxFh#b@e)68t|^G1(#S&D#Z>$yc{r;?l!lrQGmK zjqsUdMMolsO<1O-*|ac28JMwDf9RKtWz{9!JxqHYG*||`UgGW5Mq{SXn9tmjh@wzZ zM*oj`YR3o1=ARuqYoJQL{N+z2^(djrhNtvw2t4)1;*f!;)>Z}9iPoa2@ZJ>@ko~MC zM5PQ*nT{MHKRUcYL$_}cIukl`064t4nD~;M?L6oVD9IoUb4ehAC`o-NWOE&AhLR79IFKze@YXs6(F&9|<*)s+)rJ3vXUAiMvJxAJN#7Hp0@2KNW_Hs>_3%RE#A@0US_K z7o9_sSwjUeR`F@Dl{eBjS?3pG5kgJeZ+ZE7t!~b`r;Exw-{Dgr9RSI)#iAt}NB^Ic zU^G`p&ZQmi7#=yEEceD8VQU|U#P2pvTys-rR(O~U7319+=hX>=UTv}e+YV)ESnm+ZcoXRJHbr)E|3tfUW z`JLP}VGriio|vj9ecb;y__FuYK-$Rg{b#ypD;o1BJgS*fu>&)k)=Lv-B?eP8e@Qig z3fXqBRgD@KLg7Y3zuMV7&pVUezWDN6PH3&)biCs6)9K63XF62@Mw6Fa#-8104jEB& zQI%=mN4{B}0r_KE`|xbsJG{}b6L}X)v`MRR)VW~8&AM~nl83E;xy*X_A^hL*Wne6_ zo^cMdX)1wgNLYGWJWb9ueMu>$)#$&nSvn_0jv5bz5#}9QVCqi(JSS#OAC*S1I&=n> z?0ea-a)A`eg7^GeQHpo0t{sC1wP&TyX7;(xw!=)d$TsP*G_hocvLJf+?s8F)~;57GnIa7R3M z8h{))(E)NO>Z?eGwiLHgD1wtmVgdUWd_sm4!eGKxkcz6TN#VboF*Sefhl}qFQ%o0)zDD$VK_&QVH^AfsBB@2J?cAO22z&@x4D3xWqRevJ6YJ5ADl8gm@(qehq8fw zUvTm4AFAU7uAU?bgw?t{RogD?ky!gW{E=*CQw$wRzzz>$Q>hUX3yi&IL$0X_$^bM( zPQF=H&y}n}NnC!}MpT9mkm%KJ6kvIQ=|Q_MVuUv5EMl=@6s&PITDPE|ggjsH$OOHo z*kb9jO&Qe)smCQPYC@APcJLPyI1>{nMzmJDGU}nWQ#Dqb^t~&Rv?EkXc9JiC%vNe3 zSaCv50Ba+%;(+OYst8wtEEWUSP%+I` zr=2U>C-)Zf0NlQf7Qj609O~SWYB3*0;F*Vm-nJz^<003TW5k*5Vmo)*y0@>cz!=j_ zeIx@L>D&`v*Q=d)DNeI-L2s^MRjWUfY8`z{y)o%rbKjfmuUB?1-ujYg+MxYv#zyfc zywvV34y&-jHJJD`8=VZ3CbO0Dk$p8p=hP-O-i=Gno~_+Rn%^<@1utncosQdnYhQ2o z3UcVwZc6tbtd>J`7u`_X+i2sdP8~cMd+rfH%zgtk%lcR&w#RQK8FqIGOZu9CgZ85Qnm=tz2=&QmAH+ zfm2&Ik=UZ873Vm9#5!`wad~NRi+63O98J;#aHz?JD{h>(bUbvOWu?P~?WW(CL!v4a zchcGl}gp9_f)-QDQ`joh==)Z5)yeGa%ey-qDEY67e$)Ck<1C zw@d%Fjvyf7=Npbg?&BV26P+lfku8$XRX8eohR5>7kzFGOF6?ASHdI|R20rL41(Q6} zX6`4_QvtR6c?9H*3y}Tzh)`k+x5#HR@x^}DN0nQe9XeU&hBvdv`4hTabb%wpqB(2EPhpY zy{_3l5e=$FCz(}xlBb;q(myL*m8{R`&+L&e@gT)z<8@`Vhrv{xAbWg2&SH90* z_pUEDD_BjxX~mXJuPzR|qW`I^UW5Vi$JYsnS0|_cgf6?Td!a)Zz4*Q4`eN!f5D_%N zAX(L(UXglYg<1HwA{MYUuz$0`I=m!9l#T~!mKYf)IsQ_59zFV^+2b6Rl|(cTJuqX?)guI9QQCB8$rTp(Dz;1I+7 zYgpW8M?zDg7(7ax`+PzO27r?wyYpRz+6ZuIGd7@ikjdjx?e-_7wT<%8kjibc94nJ< zQ_DeC?R7OAjlQV8l|69obH!00dIo0nYvz*aQUsMt1 zN&L}O ziBu4aSwYG}LC7K26m8zz5vNRdHB8c)qN_Qsz^tjeOZFQb@OiOY3`MH5;7b|057MbP z=~@nnj)t(s03}cxO%gd$ouWbho=FixG-{KCOlxn^77YzO-q7b(snsw$^qapeLBvk{ zijV>AsekJgHojB+6U2~TsI5i)s>!o5Vb>H!E{!w@pmr8yJ1=KKw2IvnOceDjNW${| z3v`btWt4rNF7j$Up_}!mxgPu+;t_djFyfp$WKxKSX6!cA`4bBneJ1M<#$s=-9H9?W zYh6yEmCy#%Sq7tE)mF8Cmgxl#d1e6H0EC^kXH2 zXR_|#oom_Qr<`6@ zDqnKakQ`~Qqpx}^XWY=tlVKRy7}j1_ubybr9^Ygjz&21>f!79>=~{F<0uxkKi{-U3 zaE$3?I#(Hx9kBq(TBwQ~o05$94wp{%C)!kEQn#g=}tgz|1vxt;6Gj0V~RowvihPc_Y{>fY!?xJ|^@d zQcF{c>iN{LE}(*yZWv345EeCL%YdcaVbbc3{NDcq@nLc`EX7}sI zt?&YCKl$$y2OSK!2umRxptrr8WDgxSI)!tZdo|mshtBgBE^$$UjCm)FFEkRHc*ymU zbZyDdC)UwBfs8@YgFTv)22-6$=b|XedZtQZk?Y5Od6H=Vz%_WFJp-$grMdXKcT<6_ zg`Ro3Im2~`m%b#g4tT0X@Uk|{)|@G5D8xoBbl!c_(m}p^YbRUT%R9fw07-lXxn@cBb&|; z$f_sHxM6GAV4j3p)$c1@eEv<#GbRhwE?mM9`m!)om%jnm?m&_*JpK_dh_oZ37JL); zNHYi|HOd4uH4!iqEfnX9_)J{G#vwBsac(|SJ%xwg(^}O3+U@Xu!T*T?E$5*mE_4D1 zQSZ@YR_^F;N}=|ARLU8j7u=Lj){zc5jLVEmr|@Vn>cAgDLOts!Jv~4b&dp3Z^c44^ z$@mK>BxtFAH$#K7_Z!^-9nPpvx`5(+R}RQ!fZ4XYxxno+z?WfjA-w}gUSM6YAUuTk zAr-bhYq_(J6GxjRYRInksyBP6+E<`#)13`~Tx3g-MpQ6X;I(hNX550 z+~PMt-76C*WA}7>j2yx-uA&syGcd92Pp%lfvBxO-JP-?5+~{w{?j-mR6X;Zv&uHcK zunmUFpBGMysIeabw?j*id#;XT1&@$p2)KUdDLkBmaIgpBFm6KW+HqyU?Yn|_SE@K` zD!jo(rig2~XJ$sj3wqHXmN_vfc7}*57#Eq)Su*?xCVTL8vznfJmd|(tot8a9b=IW$FYewTz~~DbGSWj{@5qsxIEs^$n=nFVRAsONrCzJq_}zS*0_2GO1hX~ zoIFd8Bpq_a79@ooXFmB~<*~Fsh?#>>${)G<#s+u|b~@mm{I}h${9WOoY5|CahRrcS zYFJVkr0v(n#5C}=xj(Z5?XQg^Kh3FaziX`^CD==3;98#7R1{16&^Havx{Njop5LjU zrwg@&=Rh9a;|nu%#vU;3^sUuwJV?p5+ZjtjKzAV@rK*=sVY-GAuv(on$C){fWA8`< zw;M`+O1unp+bfbTF}7yTGlMtxQ$Y@0>2mos z0fn*>OR=`j1_iI^RRA0Z%A9@zK)~+-^cnC6;Bq1GB{n}SiJwS%l9eeb@GRXi-Z25D z>JHeUc*$$-Qk}~*H(F@h5-O*68`6)LTsp9d3wrs?hCdMztS+gTkyM^M+YBtCN4b;_ zB+XerFyEdv(xAQEG@s!FTNT-07O$A)u#_I>d$9i$WQLCq+r=uus_Il5LjoNM)^(w3 zRkgN=sVZ%22RqYR4l=819JHmnh-fjNbWO1WvXP<;N?O=M><`qX+qm>IxpAeqMJSD= zPI-way83jcq$uIQ4!q{*425ghf^w69m2ZSV$z4Ijp4 z63aCH&I_Q8e7l?R0_G>P*Jr4N%CtETEaF21#ZF}b*Co!=z1NsSrOk4sHbpsiMAxxi znW8$-AKcZ2>0lXDc39K?BE;E~@8>KA>=-7@nax)9m689n^Vtv_88=}eWmFhy)%{TS)WuOYWuCVtn2w!EkmFR4r;(A>Fm*?*!Dcf^7+xG&!GXuj zPQU5=%ggo1!cljXQ?=4OBgVBsBS~LiB?(@j>`I<;@$Bu5z9Z2NRw}n{Q-2veLJU>?riFPD2UK< zMVFzT3Y7KlvK=!jPAlAl2wd)lMA3WcSN+QFLwpW7MmFuIJS1Drb1)>R+?MS%^bSvF z^oQE*R&FP zl2xl*6#cx0#^XaD&S1A$qwQZhm8(>nO}6h=7qmh7#P@Sw>*s%S3cqyy_Fnw)%=+^K z0sZ7@|9=MN{}`4!b`JkFENwrbiTf|m9gmvdnVapyYHR{g&$U_4TOB~@7T8s^mnA1- zzdx^Ik?Fc(ONQkyT4?6k6UIqy04@%sY%j0gmFu#YG;UA#XI8(RhecD*RLu|5B=YF@ zaKBxL{rLYB1|FJ_g~MiAJRL6#cYJvD`Hxuewu+%scyESld`>^rm=KkHZJ4e;)KY$IzV9f(IOPV!o~$yG+Ia5eMFZ|q zW_s|$vPSmizW5{S>@LD#F?m5@<p z8m6HXo&GKdb3wUImroqjBxfPdF%!w5&sUpdmD-o43qo|tv4IgT?$WaBQ|H{wH>gR@ z)8~4vx%kIayRUjEjE%HZ&G3quj`Zf^#W`~*Y9Vh5FZpCNZ{T%+<8&H>i!M*=q$zgl zkxsZo(k~S6JQ|t2A$oed7lR{32=EpOmei#PdK+*6B4ivqqdKHTLGQW)+Zzd>-|rl! z=~(FGw6bc~Mrozz(wBdh2}z?29_Jo?7>w|Twi5N`K9~e~9lB{DhgW42%o7e#Z;?2j ztTmK)aX8jG6_$6Ihesz&$>p>p$Tb2&ET^YKieJFi;~8eVkcH78976JpW?etIBG>snt@#FJv&wiVSff<3Ti@#cMc-IgFku9 zNUBg^^t$}LYBw(2?*Ss<@yJeUS~TV)+=@hQN_O;!1qqe&^tTo9-(pbzt*a4oAfgYd z_I(Yb^*8)k?31&Oh6uI?=v?pk@MN#jPMv2Xo^J0WU3-#H(Q3ZtTIzc7}KaVG7}4*Yl5csYhH((bWoO^Glgv%yY(NLLMl!&8!f1op0D9y042j9-~{NHZPNXTgNC|V4SR^uj2OYTHhjRKMYZgb7K@Vw!;HvHEaebX4l-h~ zrqPyp;HAR$Rml24uQA$$>r-f!ARVbCZDTJs$IXJ|8tZU|-rqf_guW!(WI`1FcsfSr z((NykCrtS6FZ*7#Ztw~Jf`)5pp|py5U`_i9q3eB%p3Q13nwD> zk9$+0>?eG7ikfo2duL=vR0M2uQSQDJRGtz_fA?jGFS9R2^vdYw(q?Hh)=ysulDF!SE#;|jp)W2AuEVw3gina>6(wX z)CzQ75tr&kpJu^9^paXjaJ&PX@y@3-`9tRoCm{X8)z*gla$)>>ihKKv-LhUpx> zE1lZE~kAWX*)jU=1iG{+ZHPKfr#ItBM zRHcP^`Tngs7hH_|i9s9c&}~8U9%^}jOQS8~^5hrdxm>3o-v(mC2XBRg4|lV<=)K$y zk9AMmj&bVRb~u=(P|V-QH-Z=29(2Hgw-Vtqzk$9CYSlD$d`g{rsS7gkRel=l5$=Wo zA%#?~Bse1{|0TMK5d}k`_7Qt(w%4kWZPgndY_KyOG@6U$39E#iP*t?8yd=E|ID5;B zgq$&8!4i^>P=S}JuHm<$jILA+wtW@0QMvXG=e(ULufv0=l-s=>00wQ*e*O4n==W2@ zi|^gt+J4eW0F4&mP}k!p(yHE7{-md_oqgI7;ZB!bxFqZg@}pMa!YGIBr;`^;)Hu36vz>-~4Go*^em2+46{3MiHk5~WA zOE-m#_(jU$3HWvM_#G)>^%|5wX$7gu-g@)|KAXe=A&3ooe$;vh_+A+3e?7SP&y(Cp zSPT)^=hj;E$=OGiIt>P~2$Sd4jy|syuRVWS%}$WOlqiNS##g#H&xy=EJD77E8F3S_ zU?(xbkfwlRd1CbXzZZl<_Jpo(F!X*4r!A{+JqstiCB%O85uWvxPs5pc^C9Fi@cv#D zA+L1{`3%SIdziwCaRd?ZFB?fGlpQ%RKGVR73DkzHc+W0n%q)A;x+pNPVn**UE%KE$ zNS6O_BQPHTT?&hA8OB0nZ$J8sH)oX$m=2Y# zSj9wC<_0mM?wN(1P9&!-)RqL*{@g)8sZ6$EBOo$C2pj_UoDH8;$-Ix49ABDP^yz=y zF7amnS^<9bNpQDxsHae-#o{L_h!>oUwG=uLbfdomo()~sI#{2ik$3^AVlK|Y*-S0c zu9JFUfhcj9qQ!f&G2mWGJQ?s>Tu<5@O7|r27+{oO@?|H4XGK(6KmcFse}r6=H34$p zgl{*6)<)_9Qf?#%1W99C$7h+f?!l~w2&K`Ev)KZ&A?5|RR8Q>)GLX_kELqDU2gkDZ z&`{v2WH+x#=-|X#N=O@7svk#p87b#YkQTu%zzsXYtacyM1^2TFn#ddzf9e_!TWdZ- z1zpfj7s)OXrOnax4wV z6t_=$i3C_6H?mWxtjiPUIHOi1Ct=ocwq`HSd6<@g$%Jm&J zrGwgPo)ye&N@BJkFOEZjXZmb-f+rHYbx=^kt@$*0T=9#TLX=Cx z@Jb~s?aa?s#5yIvw6kmQ7fMeN{gZ1m`SPYd_S>YGA z{XS!)g5dpAWc82_T_JqC*^Ie%aiHYpDT+*jMY&>LeX>=aoV3Psd$~zG2kmHOC1?!h z4K_(#0FqS78*MM^l*PTXDOD9k%>IuDkW@()vVporapQk&kAR zzEr%elgqWbp-rNp>^FhCVoE4GioK z-$&EO*_70i>v~DsP^<)JDt3BHiO%QCuw1M>g+B^gWyZq0NYpt>ufBCjTSm^xE7soo zyE+D{6gKJVvyldsf6om)kr$KVdmDq_FV)=M*S&EZ9zuR||$@q%aBIvKLj zEknO~ayUECMZy`)GvebEn>TCb9>XU$8QVue32D?YAWb%~+2IKit{Be&3#xjrT4sCL zx4@M{2^Re-6K0nY6(pA=8s-ZDgrZ~{K7Rg5HRSveOW?pP7Cg%#vuQQl^apCsP!Z3J zjPsG3S>bl0!KhGFb4|w)mS(QTpL&t~PQ`pHVt+RANG&VDIAH~&ir6q2$CJ#spgx)T z6EQr;GWAeAf(0J96XG~eX!%Ym)gYO}VR6%24f-{;GM(Y3pTeZyqil%XsfZHH7;AJP zmd6{d=vc8C6pn95m9705&dfaaeJ-a<9A@mGZbjXKb-{^rm~^4#s?ckekGye!DeWZc ztmE?;@5sq15Q^`duov^17=t_+P^fa21jr%}OK2}0IloWkyNJS9bn!LSEU49buAJWX zj2SwH+b)<=^}gO%M^G=CrAd!vEROnfei}Qs(}^s|N5^U%EhrAAn`S`VQMs1Sqcx8; z(;u7HG2wk;G5LUkUyJOBBx9CC_gvBUtZBL1M+8E~7Q!no*H<$UY4M8kUQHJbW?{1gq=cTY}4JKF6Wx#p}S4+a8Fl+7`1?I?* z`ye64orz%v9O6XqmgL$ZZL@fx{0S{*vq;qN73RD$`Zi!}&^RC-m26_yN1C}{@xRi+ zgZ4H@83~LlwXvPVExdH$if|9*Gnq_7g;pIVvNuo0Z&`pv{z1akG+GW=Ed*$nK4czp zWX7d7buyot_m3tB*uO`4BSIhCWtU+aFy!%}iTHUKty_8(VoOyS@Zjlu)*pR-@-kXY zy9zvG3hkQGi@P=H>a#9SX$C2DtAD(QQ4|5DWu6WRbEX~bQ!JV{+3v1fQgK~ zKlmzYCOZ2JYOc`k#VyiQ^A`(2M~v(!4^GZ+oLh$>qZ&0kCe>evG?u1)SRvb0^G=nd zAMeK8N{_?>K68V-DNeDX;Loat_4TpVdaX52OWFUHV1#h4P-_1(;%9Wx%8Z9HB{pj^ z!>NwP&7cPMXdeaLj-}{?(WGUT0rLKlX1ie9do4h^gMYVMWYod1lhtOAgJLkD%^?PE zY`Dpk$p{y?fT$>YNvq$8Zk}Jlrd$coWs0!!Im0 zY<}r=hgH<_%l5DnNDMsL9YP=gG?wuz8r5SH3;X*Gb7a>W5i2X>G&cYjeG)npuHiFv zf-K|AJ532)Ogame9!oN7okOqJVJe`GhI z>?3&k6ZdJcX}Mu9+e&&J*;icO+2XDn-6#%0)1~;M-yR&m1vylU1jhiG}UO(u|MrPKFzP-`~pD+UV2`xO-KW!$f4vNgRF4qO(yz}QA1J3zNyJ>fO3>{m2 zPyVo9|E-i3Q{q;q9vTQJS{4Wh@xKj*|1lJF?Hq78kiUL^poQ>1*!F9W%o451;9}VS z=I5+ODN>Fe@`WnblP+v>AnQn6G4N+0eHL4k*4Oz#bnNr1yJX>07UuZ;*~d>0Y44Q9 zCiE$i2XQqfH*(SU>k1%92m-BsH+rr9_6r8HkX$Ram{I(pCk=1w*j72cH?G&~>0Z>) zd;*c&j#VgCvpZ0ybi#nKW?%n0(FH%|49}!Ced6$(Bo2pIp6{NH>o*eohSrD_^M<@< z)q@^pj#`vFtADl#uu=r=dl|QW@A4(?_=yweZAlIe-9BjRL@==OK*207P+xA?j>j?y z2PE0P*H5+Q4IZ*1cXLi#7F3tv+3?6%Of|ra7qEHml|3 z?%1@=J4x9Cl4Us=H0z>&kSpvEOp5L~c4vz@t3*J|;lI!7O}LF{f@a2OQGu5tn_2jC zd>r3hXp$NNc$O7N%W7eo^Fe1_$pOC%1!tOp^p3Su{t>E5*t`>|RN?}I41#hF9Ogd8 z-815Xkb^3Oo7}MQzM2*Ltod_pv87UD{N3(ftkiPrP*;uPO!=6Lq4~KLPMTiRMaQal zCH`fU!;|tL>Ycmlz1<&$?^m0IeKMuwUYcwtU*AkI%>T zPJu-2Kc@xfmbk)`iT7;ESfpCJwFTBt(tL=&B7{_#Bfueii-cS+?q|pGiG4LF-}H?f zNMCZiUl4Ji`*PXx2IMHG12`&B)K2WkyWGiV@Xy9`@A zx~yLziPH|NRdB1J$=shzEjvoCxJC@x3eOpEwS@>>J<7^}VZi~d)`6`&q9Mw0;Xh|fpee5; zth?NC?Wp{hx)c{%Uc5&{8gwGkF>n34QmiKJXOcSK9p`~j5UoMQ2mjvDEttO^84RUsOMCd2CQDBfQ@Em!@ z1J~bROYpR<9KBLo=mTlEFzo8p)^~5!r>cgAk(9T#j)^BD8Ns_qJU%F#k%z8Qw96RC}hvX>qjF)*kdYJQ+44G*DG6)xP>g9mo zHb;ye7^MFO)@El)yE7$ItspQJg^)k=BSb{o zbM1(N$mc-CN?3jI36*<|Y6EQvlQnL*NH^DGP+YI%Oa067>ZGV=W$&vSX!88bjIG*M z_x)h+KVcWHL*+n28QS@+nXZ?qUu{s?K71q&*L)+#oJX5oDiz@?PAHjcDSmkof1iz; z74RsJz*jl!H7hA=5$l86xkQHBLkEt~Od-kZCNDSY|MuUTkN?IOLa#L!vasZX?`{xp zEsx%YW=0HWeOk zcd|iwsXQ;F$#P?fY-Yaj7hR)ObLUBeJ4BuLHRS~Hglnc1Gm?>JZbU?xD`gU`7-aOM z(2@s+*W8{_#||E@{d!MC<2VzEb*~_7gXv#u-4}7|@jy}`pe_?iH$k>xLgQzR)%{Q` zvv5bCfoD)B#Jk#qdT#u2!A)ug#EVhkgT;+)E?V`mVf6Z+uZ-`S|2js3>P`sTg>W=D z^cZ-I&ULVa?UXpnD?cF$?|2Qhv`TtLL^>FUyXs(X9{iff8F8!|aU92Jp>x)EpUS`r zL@h1HCey-9X35(iN+P@V*V-a`KcVbjTJ0awO;BRi3d5yKpkdF9gU-m=a_wL#YwgCl z1r}+o1wIEtcwTQ;lt}R%*0n4r0Wk#$EFVb^Um5}-*Rg|U&axrFZ~!wX#M)(Y&o9xF zwgu&Bkw1?CT_58_A0mq6(c4?6x}}Uho$#Q`y1Bh|6-!bdhM=0;+~CZa zy9|@$oH8)FY3GsT{o9^$$WkT%Z$7IFnDCsVrwy-jKH@r-)GCKdXJ>%p83*V{_}!VWpR)fDJMD1hA@cPp z8Yk)GK94&k$Q#9iLwyP~HVMtSn|BA}RcSE482f6bv^8fITYz$z!?$wYb3uVB zPfBsT>)U!^TnIqA&i+n3@G5&_?a|P2F03OfJ6-dfk(U4WRwwkw%GbyvD{I5pccPs8 zu`ya!$Sk&2QeTCEX%Cpn=cwDWO}rY_v`N^JK}HrKy1l}=<-T!S^q(`zY+f=&G9K_O zA&-W$_U|>GBD8U2F$s(o5Ut9yv%*53M(1?~u`+3?wr#7h3hA;AZ2-5t4)JX&r(J0O zR49j60tiDLur^*jX-?|vzL-`2!Pde-k+bsWmopRTvpVMNa^(_QhKzNHe~G#xl~_`t zs$^PP;e;?%=4?tKjf3im^uhypav=_sGg6ZE-`y{NSnH~~lV}Z9v0nrvfUM{OjnFU4 zQ2Q?!Kag8!5~c8{E7*M5ZGAY33Ig#SRd_WuQLXEOfB-anx4RO9DZ#WFy%vB_v% zcWgwvx<9I#Diifm9Ti+I&(<87Dqi)k^3_-B?o zfM!jS%p3X3NdP2SS1vR=NA!k4|FJi2|Mg|x&~$?q1%=SbO1`k8WrR_RegiF9j>@F= zKBN@cDsjqW_zWoM$k9raq(_tXV1%P#vFWINkARfGkBjfrWm-!?(j?=^CNZFLq9D z{&H<9K|kyoEOR_xGnbm`OyYBlzGy)FYe@(8>=HPNjY!1^CH()Cb{_ClziR-O6_OB2 zA*7JV$VidBS4P9i!LjGDWu%N0vNIDIscadMWR|j%NHVf_wv79p`q%&e6URB-^SSQ- z-ut=d`Tm~qKJW8B@B97^RSKQ~FD+4zoTgN1c>|e(mv%CCnqHkO>8+b}^OlJWQ-~LC z;qS@vyGCXLsCiOx$n$PAQ{Tej+F#Po1$RBf-&~b-(KaLFs+w7q0=JeKjdFNhF!tkk z=TWDV24Clkoz@9HNRw!FUb=k)Pdm5tb#`|ybF_PnUp_o?VUlTOB}<*;uslcSQO?(= z6E4MAKd$7H)4Y9gkS?A=76+$+xP|{_3jtduDb={EyzlwOTz8nJ(e70V^dn{>2Lx_xMI{wDyhxgo`RW&zrYYNQUu*q^^GagYQ zZGLs5&ZE%fD{EWKleiJd&-AVtU2Rpl#GI8h$20^6*l1c~bV>{_?0vfTny2jJg`@m& z%!QLsEE4|`!z>><{Ij|dW!HB-4q=5K_==LXG|s7|&-t8+(UaS&h^u&KxJ5;*N|lk; zQitT`+UGf+!rWYh(VfnvX^9NWE83b@*+=@{T)JuPIefyj@s7erswMEUB`Ql7gu*YIAHp1wHn=!?qJ)JpK9Pz`YlvY*agvopiuUmS!jHNP zVSZ@B^L8IW+1VT24?f-R=fDnDeVIt55lhGIYwbR88gc&l6SGDZmxSkvNv9b0q@F8m zh?)0Hq0;o-&2X((W1Kp#N4>Dg;{e_{hr%04B}vDfi-NLqyVxu5$>}DRgu8t|aNk2q z$Ssz|fg;@R{b0$xn}-i-I*dvpt7;-q$Mdo+}}udvc_-Qd@_?!QReQI8{zssEqKg=6*?``=0wt+<8Q~m+v>xE7RTR zRb&&2dwkaE+^TrSi=l^1RlLHRUy^7O8{xma3v7OYJV^WB9B2baSen|~!wuDczSJL? z5q;DY?{;#M3WZ(es)H-%cqCQM$6h2vxm_b3>>o2|szOf=FS8z~WY}$lQ>o+Oy82=FK_^2jNp`|%mY9n$?h}z$ z^$aW$rp%^s`ikhoxoN$cM=A1NiaYQRFT|LiqL2Ncb(iv5^jy8Wz|_DikMz4@rC3?Z z-5D>k0=rArx^3u)hf;E!Z|0hI?Zscy;6J!-eb(BQSLdXTv=f>A{dazM?lEs{`YD8( zCamjwu(7b7gZoX84O2SujcbP|M3s&e6UxFj?tZq*cn?Q>8#t4u8Llk@(44$~C{wCe^D^9cQoD{B4-=`=E27WFdth~NL;}PJ=6r_aTrC(V3QZHfGkxTN3 zJMV5j`_!e&qxna*Nr62avV+i*ZtKL6pyZ#B?;CE*WRuv`<6|e|Do`RG$NhOE ze1+Oauk7{Q<>nis#7*L6%|$Ym^sNamxW!-Y48iMc_lmk|nv$^o=*6)vE@{FlTVI;S4^Gv-TOpWm#pS4^&Z^X0GnkVb(+>ram8d`VhPVjOXfqSZ} zpnC(2A6tw6Zn$68QvSqA;W2b6Zr&wucT2KmouFy>Zk0lZ{1E=*abcJJ!^*Sp7#5r2W1(jqyaf;9Zrl%Gc2XuRPo9cS8B#VI^Oi z@IY9_5GT1>W&I(?FrgZfvV~iTg*KTh3dqir8O2d?Xe_43+*VxTQGOM<2e-EN6e5l5pmll&5ea0_&Y2L2b8a$iJI_{V;jAW=g;SC#ZQv9FHnrxpK0?f=&Acr3p=xbU>@x&_jfzjap+&H zJmg=v@_NYMmQa!>+QR=4NxB3dE^n{;fiaKF2=M`yef{A&xvPrw6sjEuL>i`93kl{; zuanCsW!oI@!WQ_5$B+A|T@EHu+O2ZMz6Tt_n5MCtx+N}0@1!mMirvJg>2}>-+hhBk z8g8+MSfqU5+KbIIp%^p~T;p(4{*=sTy_%Gif%++`s$Lg3i@C62?QWH~Esu&33-y}b zZJ}4|Co5(BU0xP6Mbe39Ik4tTdG+1DGxi`8XOERvu=^3w_k?&2-bsw)M7R->dJm15 zgPT3d2zo3Pn^`)ldPv%G`vwQQPG5K_#Cw2nT;(`lyT*{&i+!~6%X31raVz&$%8Lx# z7v|*TJd^~-)f&mJUU1F+M88aShR!wgy<9>)i%Th;NAxi*vb#d^R#tJh59>OP#o9h7 z8Y+0hS?VaN?nk0HygySP$Flov#U8EU6Iw71qZ=`-_&rLrjB8=Wi~&Bjix$MEOxEMb zPO=qx498Vz#X2QjTu@)15OjQ_n)5rBA-lUD&H5%M~6# z2|q?wVC*%2Bh%clRW7qX;u&|R>Fq3)*?Y!ao(s;!8h6Op#^d591xsD<8{MrF#92;^ zPVrdYmX0@^QC|JhyJ={?)D_?t1f{?f7?+TpV7s3~^GDPFvf^EE5Jsr|rGB5?NrwY0 z!G~_&bbR5bQxvFncCRauiK&IHe28Y~x7wGD!J+UsIfbI7M>v9CJmsYkCNb_l9CEum zgJ+lgdtbKYf@@@}U3lR+az)yu6Wt2ki`}Km1MS_C2fFtPHb3e8{$%8Qzj?L=!_re6 z&h@45!-GW6UvS1zkgxaTTr@H`X*9^&^;&?Sve%xVFpNu~16#^1HM+Z8(uUf%-P4gU zF;+8nv2r?y{fZtDb=8SI`(wS?;^rq6bU6l$+{>2SsGDT&F|uah#2mS@()`4c^TA#T z*xNpBg4AAHY9Ga$cAV)g1f zAolq#?rY~y=L{A*>~iKO>M9m{b}6Q1dbo&H{q{Hb`#>hY+`Fs zMH3&c_Ue)xbBVss?k8i(!*ti5tOJr|!gjlNkMY&ls(`g|h zrxjbPh?7U&^J?L;VE0*ae%^H5o!aj32ABDg_Zz{Ly~R`8TSM^L1qApK%BqeH{l zi_Nil4X>Q0;cw<<`Y!97+oP=T^qXDJlk+*H&J@PSdM6nAh{e)LI8JrT*R?jA)_;9i z>pW_uD&@*I@pW0|`@@9ONzMx%2yMZ26XzJjYG7Z8509wR>WNeQlL6!570LHh+=cCS z^QWbLij%HrcM2K7=6zLdXl#fo29hhiflDZ4b_UE8Bs3dxn! zunPnBH|#%GFRQ1zX;IYrw>&?WAQdUQf3%s?1sJ(>+|}5cOOPxv2O@ z?Oer6IRVm?6w*0^@KZxgVujlC*cl|QXD^lB(lO~zlFDvg8n-?mrR`R4K1aS0H;#Ag zlw|2rsB8##n&O;QcHztt54S7pGzn9u4U2{fwozPhMNeQfA8D5u6MJClf~ZO62Suhl z#}aJ(gxoloy=j0?itrpO532)0*4k#MU;0e(T1oyIrF`I>X8!#5b!^oc{j3j;$8x{* zI`--ehf7UFUN*VaF5>YU)D$Hxax?U{smgR- zi}5^^Oj_sr^}!`P{uOoCRmImI;|@u4y|>ThJq@#am)0qATA*ztHP&RnCLt>Np{Vtc z9EE-ZHJ9Yeg5e2$L5cC2=bVJDRvAjTv$ybjx^7q}N_t-owJ~Sa4&^$eVV9tcx4Y6# zA(?>6BREP=p5)9dl?tI%_r5GbxhhG{&!wN^sJ&}o4&KCb_pE$9bwd(G;dZqYv358- zkp!$=zE=Dj$HujWTRZO^|JGa9d)KF&sVIuebjES5n4xb;kpC4kv3{@5p-XiSrg~it zevzUOz07W~EL-+9v|^q(+)>Z&{;lYq5kdk#RXUPfW*T}kTyrn~GXvT%_><@;1NO8k z>5OCQ&*rCYs=w~O_-u_M5>}2?bHutgr^g6~HG$B_pOsWn;dH_XS405EAcIEXvA~H^ z-Bpns{QgF^*1O$RY*fS7x(*LtDaR453Gw3mC^58@YbceS{#9dd2muw976Yvh;o~Aj z-6vD#gz~G&SJd9tt0JUr->aNAyJ7HEILhnl$)NrxcyDtw6F1JHB*`CL9W!^5B&DD{=qwhbL z^sf5&g}q0^6_*7aL!vo9xVSt$+?SG1W|tV{ZB$EWK~#Id?!p13CuGuxjy7Zw$-I92 zB3Z>6W@Jy*m!p#fdjj{pX0Yh$pxyiWqo$!G{^QrFQLvBh*u+uEt@xy3cs5QYOZS*| zg?%_0Soe9Iu$xKMP=+S>L{Zwk3Zq8K!D|(*kupgPRn2rl;w*@hV(Xd|44R&F(n}N; zG=#D&`|pJJIfMlU30EJIxydUckRX>jICrSfpMCb2bNOOI()rzFEUg@dY{K~p_z0==LI>jC!v|$npnaEza0QhNOZ&C7o&C7pd{dIIUF|tv z;Qf+2&ZW$6znEchmkY7c9;ela>K(&nf9EXU92Uw|hDs#wC_%n(g+G;d7#Ojx$aR3=7R*xo^C|t4*myA>8AT zM;>V{`9|2L4d=rWXTgUxE<%HwCO%;Ujw0DAdJKnG4NPt<@6vX5TP2(4Od+aOr5aWX zyC``FH-o>yxPOV4cuv{$i09p~E6=>_+CSOJ;8+KEldT^Av_dr()SjihSopm(?zD@G zJpUd$3FDws`j?J)XLwp9y*5jnX$?OW6|lya{!--RGG)4h_Xb}Le@j40Mss#8&A01f zL84bp!wT#kg|27fGM4t}mCBD3wv7rQ=is@wOa z7(BIBK0EfE{h+IKk!hH{FL68PZp+rRfNZ>rN(I`ciajrKc?yCeXm?@>sU(Y z#lsO+L$nxLEDc%TT&Fg7aPtg5Hl9ZuP$0jDu|WE10`C2(gu9I$117I)+-mR<`bAfA z4W_CVPpz9I`dq{f_LyGN5+1r=PkyQ?=ZM(w7k}BI52?!+lO62b?D~4cs9Zy{7F1sD zSxQusXsX0|zFtG*{~(&zKP>ZQnFuyt3%(%XT}f+Og9l?->`k-pUUwUKbZe>zGY;F! z-0~JTRja;Nyf9#LPOm*-_xRhQG3#@-Nfr1~sUjW%vU6W#4=_Bei;)OZAJl#me3Q>m zZ|xehN>`_DUSj>be%5`Ay#t(A8nKndti<-c?<$c<%QE1lTaD0~e#zAv$?s@X-Pkl- zT*dsf>6M?w2?0a=_SDX7Zr!d<*J}KI3KWZq$IF`L%@tFm+9kJuD%U~rQaa;?GU?&YQj63m4g*$xCo9%XlXq6YC zJv=EnkaTZDTn-*7M~Y*dXibDBcMgHN(STcPsFIKEBmL#7E;mYs*pu=DPDH}pl8i3= zH}4V~IPN*b2%Ai%%-6?>;Fg?%qMkUzt>8e1l)f3t(=kIej z&K~r!uR0!G8LYGKj0A!9?Ke^>S`k-^s&)lP2-$jCxs+dIA+x9&zfV15eLRrhnjZJN z(r;rixJ@-tMbX2G#YwB$S5KUC$+muWWSC1CFB0~Q?TJpocU6H^Z?-mS`m}kXz|aTh zKidXgz;RL0lOE>?-9>jEkMQzU@{>owrJ$T8iFEV4(;DP^hG5UTp4>39tjMSb6RL#y zG3Re<$6MoKpPT06oU*w7c`R`CQG>As#et~&`LLD6uBIUF5>Zz6#No;CrbaW}Kx>ks z9K2?!wWF5jB0B;{!#&y}wU$gIjn3+AJc-EAq3vafC2bu`fB48v&Q^WA$n$YTi<4tm zvMG@n->t`rk(X2V53EosVHx7n8m5QHSiACoL1&u79@a!jj zmDK~g47bd^bV1&)F>AQGIA~vaNFmb-^O^v$43#zSYwPOD7tSMo-xm!}P&zKUFP>x5 zy;iJq7)Ki0(di}a%o&~Y!3#4=p{x%Wt}|2eN4Tkm6LK4VN?FM~+rHr>Of26d+lIN!PD#6b#bX3M;k7qwlkscCm;Q9S? zNC9I?&ZjEfzQI>HN=~&U{Zs3~lP_Y2LhH*fC$`CcXIV+97da+KY3ABecF<$I%w=}W z`tw7+YwhQU*X$Q&?B^QW&e*w~s9w6B^W>$+5$(8KNB$tc6#XL-`uRGScNYs89ysCO zw`5^o!{K+yqAJaTzQ#8e@9b^e+L`49%lAzUacn{TOEHbkhVqoh1n;Wdt10B1)cwSG zQd0G5P+4ivwBliQ!uP^;i{P~%974knab6UOGEk|j^*evQGlr|59nF{d#F=Ex*7+nTFeH-S5F-$=~M7wXS#~p{I=JmT-fvATpxbNte0W{3k~~JN0WsYAo;SXjNMC-80$PNdjE_nD(LDL9|*m=jZ;J;5;&GB?sf`|wmLmiF1; z;5QXBrOO9J>_R@6?q{Hl60!AVJKKEx<$jA`-@VJ7Y;2#!^Bq=)^<~M~8ZVZeUN}kM zUWe<8b(BPBPt^o{HEn*-2%&ub`^QpFEBF*E#{%}Yh<-UZ8<0B^m8E|>^o;5mcEjkm zMF!_&AJ~jr;aE`E2XO1PapbmO6B4}jX1rQTq@!~zLku;whO3_t6VS+aS>kdesAdkts5Xe$ zk5a-|U!BaN!1XbR=XmaaktDqG>GRR*y>fahos_GGZH&fG4{KTTMRP1EiQhga`!;QG}}mbx6uP##m`#4 z!q;xpyx=UXz`k(rjccRL)cVcD8t-fRaiUUDGRevg|E+ zq3U&txNp(JgSm~Dy5Nd>=Z85$Pn32~XxV(c;ho%@=V{#YMfIS}_lEFWrK34Dy(YcE zJ^MKb6C7GvLa&Ww7CwB87*o8Gk-J~(gSd!7!Q0bfuc{5xWTomSbUkQ&&8n_zCv!8! z!IK012{j(3B(vWUBYGF}{c`dCD)RP3ok>n5S-}sQ*q6?8gc^07+waRKb@WxseBb4o z6ShIuKhrgN;!2(sbGU;QIPv9V?8(e|A!l>8UvNxMXEw&aGxT1FOg4>aWK+mL<_k{%=i6KX7`A1t*t40*qMxIa>c&gsS z&#fRBH`hqdOO#`mF28xP)-QkdW9sDYjET+_%Jh#jJ#)f}={>GvCN;@&G`>thvkXcF z@n+eRD&vBYffsQ(@1AO!%h@+@CGynOgKK1_<}y#;9N*n{P2KC3O*f%g!zmTTV+&uC z=jxDIn2(Q3(3tj#e3;6eEE2gWW7?L&eS zO1J8`7i%x~VD;PkK52B;=6OA&U6V>h;=;3w9OV_C$rQ1*GtOjs+g@O?2&28bpmQK= zBa2X2K(5TtYP@+t zHYaxaBPfd?L$p2Hpi`_MAt^nQKwL-djb_zs-|84r#L>Pz__ zl3o5b)(Z)yee>=^6?P9abjQ=W zy(kt~UA|sAg)ds0dzN(tH#Kc=oW6kXtYzU;NAQKPwA1RchkZNKO}`}Ac#x7V+1e@B zN~zCEKP01lks|lynQ~B2(5J$eI`b7#%;PG_HMyrxUOHrJ)>R%s6zwoo0F%#!l?wH( zRbC5op@|uJvluodZEwN&5?=j^(5A`ZZcmm{mEt2w->jy!_a()QwR2JB=3!zoX+mc! zGf0nzSs_M{Rb9zE~# z7a5n74jbbMxW7)m6{XQ>Fm|%EV59hh8H=7bMIJ1eSRk+1*BB>7*53~+(AQP=4)HrH z$AfZ43j8M5>sa3&91eHgd+;{V-Kd~P5#lR^-AjBS{FlBECCW1n(zz|vptN&u%Cl5F}%D#))F@WF<66y+Uv|u+{pl=>nyEu2Y85*mZ*IbhDtWN%3jD zaquJdhQ9{K(XY5sK6?0Z^wOM{hOrdLsSJmuIG=Uy3Zh{f$GuoCe*w|3o7fwlgq+Nm z{xGk<4_V}l7P0$2TdkIY`(0uI;Gc#4Yb&wH0>O z;uv_Z!qAXhCs;w_Qhl5!GRl+^8+QJw(>lqNXm}o>dKp3qE+%k;Q6T-<^{{{v=d)8G z-JZ7{X3wei6~!B#W2re%?e%GGH1N@J(`?w^)6GcYS2hC_Q4sv<3 z=Mo1TI8369^_|Sk@mfOJ^bK9G0)scfNc`}Zcx*z#@0`$W3Vx-2-~&FFgs8wfm2s7W z%?HRE4#;sZlqWTZ-7-EGNPHnL?J}zyYu!p-&{8(3`Y_$;CpDqcr$0np=udI$c~dNB zpmEUR9LL^&WkaOg6yEqW8Fib99U~*F z!@(i!I)}*j$1j}krz~dlGO=%3dd`Ta`gx;ZsQSuG<l;-7_XJ5z2-eH;W-lJH2rGLyMd?+;b0@s9 z5wWq9uV(Ew=srdhk$R91;(b&q5l*)P1^Q4X~gce3c-Zgn|hTDcJKaG;!sE) z4pJ$~et$jVxFS=o>1)O1>tpi)0*RlV5ocbg3PQvaJ=$kLd5m|z`l|Wc$H}@^w05@_ zc8vDr$aH=9uA0POj!XQ$Nz#AvcCFV%LeoPLW2EMzs`ON`iupJ8-j7MF%9!}JYbam) zJy|v03&rFEvoqvX>TlQm)rDE!Rp}kZxfP@y#hl%kPtKl<%XZ>I`|IkC25anu7yW%l za!!B7GuWkBGi)hyG@@x10kbS<(8!hcp18)oVL~i2WuP~HkFVj(`>@QK2VbA`^5orT9)wIYh5VL&xoyjuM!nLbjtPC+sQb{+lU@<-z{d4~}QO^eA@g ze@7MqBlop6n_C*8{(!MSva^_8g13X-8;!Qc!oPn=PJ)R>zC8%9^a10(U=K*-yI?4Vv=)RmxXSs zD)^yR?11=NCq~ZWbD37Ra`wMbd{?Y9wGy#9F&g>U@k^Ow^zBZ|wOk6UI$=%85al;F zX;Z&$l(gmPFZBgAJQ((TUZeY(dn0jwxb8q;&Xwi@R;xQ^*o$F{%tEf5?)%nXjC_Ck zMfKYXg0ue6N*LDR;(qXx?yH6J6`@Q{P6GplC%nur2Ni|;vAMk*C=<3>Q)G*ZtMTSP zulFpwThLW9cLSGgWI`uCJC&1&is<3;<*Qm5-~BqijP$VTxZuv~?(ceMe4sVii*r9` zi``YHXRi-53AVEbg!q)O9uVUn@HNO&7M;HE+>ho&RLh0k!&a16H;x;vRY#Uk2T=)& z-`u$Lw%x<2K)A5#PVU55-FcBZIyWmSiiFsZwuD%XH{$9%yq&%6MkyIZo?c^C)BGy( zyRa#6z(p(YnKr)yuHpXs0q>>y=R=A$A&UHuzuzVN@7D;hroh*5nyHQTor8^yH3It7 zsJ-X8gIEOI&e{QC0EY(HW|{PmN3GmJCKzid?TSokKR-yj?gC9OREi^|8CY9dT3ewM z3SI&8AE8p{3pKDtKvUf2(FKjKVDu$}x6q(`WT@Z{^aX=$b_@hFf}MIa4eV?Q6}&kP zj{dUoj}&W;hF~~U;AY!GbL>;}1peW%&5sEUuy5!|-Rv#YLiu;^Y*e88W>29O>V}?B z%)Imp4WT=v6sVW5L6>Bwcme`3^pXsEQZe&VAbLVE^HK|XLU-~KJ!C8$0L3Coqry!g zLw<80L8WQzB+bI{Khg+(6p-TCwM6$o=|Bha(7#g?7FM!X*bdSz!Vz|$M)~QHiRF%gSCAH-RD4>=AI7<<_iy z)FVCppm}FbhlM5mn}k&1J)mR8(h#>cgiET4?+Ep1rQv%C%2pXr9|%IAG%OfE_3f-J z9PHsLpnlw1xywdofpx$?62Lzq5DpLcr4qE2v$sc>>LWo2xS^>5%pSfqjm5LVcpfCq zJ$tdR&OvBepp-+Grh=^AB%KZ5Hpn7&Ytkou+D_!s2M%n=4Iw=~jR|QxyWM}&&-E3^ zRRHq&A@XN=F_4eiT=AaD)6Bp*SfDh52Ej?dMAuxZa0_s>%MnEo@HZaNs|Bk~FA&dX zAj&)e{}o-18ZyeHOfBr;h^>7x!g=TB8DKA7ppqyA^F{(g%#RR2Fv-b@!z?W9w#Kn` zoy}+i2R-ibY}N`U^;fv9F%Nh=A{T*1VqgS`L9`0g{ws_)SeSv8sr@!;DTb5`(^6w$ zm4P^xgz&yz{VSfjwK?2M5oQH5-p+ABw+}@SfQXOWzwHY1q4p&?P zajpgCLFj;e<%R*n9&WR>d=Ir~@&_RJ=7Fm~K>YqBdzpBlFaL+Fwg&8GJ5?VJVt@|} zq_e-pBd3WgH!%b#8Y3hT2X*^Ko$CM4}BH_=A!0cWMt~Rwbc%Pv-;cz6cPaniT`#BG6IGk zpbHTL3w&8CBWqbJL%1{CPz7NPY-sPYH4rXxR5lER8`t5@A$Q^-ra+Vlp!dcBk&i&L zT|hJFyiJvfDG0Snmfz?X(*a2qq#NjzCzFjf$-Q(w>*{lKIpWrEgaNWaxHaNxW>VH(poSTUJ%QfT;!VV}YPvVC2R)Z|Fe z!_`#)Sp^VisW>u&HpJ4#9>wrPVh$MZr8^HCqX`y?P|Ue`v@uo=mdN=BZm0;`q9{OO z?i%DT7J+P|4N%a$*0PK?#?}F5VY-t-)3o9`P$f&GW8~%E#TEinS%Enl>X;l{ z%o$*HZ)&wg-H9YUTPVJd3;_i|6M^Ki3IcRVQWn-=g=J-|Vr^<=k1{#auyB@t1VvRm z$Z61;=OYo?w0~+hSu00tu+-ji35?Wi@GU|8Fz``1;GKtPMof+_OB%FAz+9tl0<%IL zcgHT}V66ezD1bqmBo`?E28NJC4yzr4-FTLkP6spwL#76%_R9KZ za07cgCa$fgJhlgIZL(XUv=9F+ihFAmrx{0*{FW#zy1zy7Y>hfPvGiDBOH?ZT-=a=$ zjXM3DDqRqur2p`h;?ciF@otUcr78(H4^T>fp!$yeEsAezRK}@=F(H5w{R8F3^0z4d ztx?^E#B35ENil;F1`T!!w!cLQY>j#z!}pL6Xm$Dzt@1ej7IkK8)Wf8P{gR-_lLY4z zpsQvHuD?WW4Z@EzP?`ezjDS*46p}0Y1keV7^G~2jf=yV! zD^yc>Pz zvgoV!+6ZzX$p6PeFy9aZ6zYP{^hnOQDbPn334#nz8)LK~4i*+Dvs-MRTE_qYsDms3 zbqoh!qc>!f5vUarYak{^4x4J=7-%!Y*aBUK%8tP-BJ)N499Ya^gLw`b%rRDIQxK-$ z6>MN9=bu`2>xoV!#Vn-**sKxQ4BFG+bVi?K{iE%;UAGgNZc6l6zO)0o;ejZFE@fcX z(B=HxnzFWnS#0kkk6o_O>j=oE2Y zxwX<~0R_2kf!a^@KDwlT4p2%e?6eV!blA)(UWZRdK_gdpbHy-_@Rz`?Q=fbj*1b0% z*qy<_V+lxPP*?pW2*qYBQnluZaw;r(EG!F=&20~fy1yi%-i}2=WASz4a6lo+4?4=C z5H-_BF@)}Pl;X#DTbe@1gR#bXXmh;m`Gz4*%@o=6M|I!^2_zJxef1CoDDEFKa^X6L zz-{(xkrZjE_I~7ktq52U0`L9)Bzt9JVWU}F{4)%Gx@_A`TO=)m@$!dg;Io_Hhy}Eg zNy0~$_Ty+Va)WT29+g7%s!BPq+j(F&DB~^(#*A&6Q!S6}G8jPOs09pYntDizIYZLW zbj$5wq{X~TB=*gNADQuC*xbIP+m8|HpKTM+CshY$v$xJ1&$U7o(t&%dL3suZ1{)@f zU@G7g1;`op;FuY*v$!=a=*;V@$difBKo=OggmOBGAq{zseS5VctXwaefO@6{)J@QY zcn}=IL9c%P5p`(l@exXspJWAvKUYY>As{-_H3&SFITdD2}9W&i?Yi|ub@ zgnTdG@E--^7R>saw8Izv3ziDP)Dbkhe+)3#){ZMQm}&)WDXgPl1VHVn3L1_W*z>1$ z{~>bgZ%tgf)r2<&biDV6jsqh9C(&SQA~mH_WTN4ot4)~ynj`be= zi2?GjPqLSj_}>s6^g%}-MV?gF+Es{*n+U)Y{{;j8O!oRF@&EC*aU#cDg2yQUqX%IF z1qw<1UqJ3{;&g=n;JN!iqj%s>8lqvn^j`yIK~_T;!3^NrObuT%%R0g4ELQa&ZfpYA zoOT=>ziy%YTaBaG??w*)g`5onB9PofK^B#TD0xio|Kn|Y76yq;&G8k#0cOANU^Nfj zVU1Kljs5pl46@7m<9^Ja0|L1WCGLJYngMFquS}blyXNYcBeq}e?qPahs0OT82yz*; zCuO6DF$eW>cQkPi9Jzjy1oHxP1y5ug!>IzJBoi^J>RX~0KbX9>o!I;bc z^#uPskSy{z)3$|Reh9*N5|H6Q|BDd1#7MBpKs0cxpX>Z30T-0@I)az<=dF?K+Owo z68;Xbt#N`u6{ZNSMj_$36)1~f6Zeguvui?qaGaZ$vu zd2w+j^#6E-tzn80SyfKJyv4w!G7x=}k}!iQfrh#0ue%l~Za^S|=rj4c$sRy$1%J@a zpJy^=RAqY;IKs&kMKT=YrJ3XcQ}GSpQD`z8Ou>-l@GE+@wx#@nP;&yX;d}50ow977 zVnzb3511wRH@MX{YeMJyMFR|AL{x)yp*+NG<{5v1`ZXwia8XtmTBNVvAm>RQU`?p! z=5ziYytO?gYWmiD)#0G)|y7Db3#S~|0 z?YRA<3724QL9Qnv01RqFlXA>JYVcnwZFe^4L4%Fqb+DZ01qbq>(H~Xux2&zbWnWbx z?TIuUSkFUAh_{%LG!Paj9VP0(Pt^%x!eRgBUXna`696XVjfTDHHf<1|xXJAwsR zQe>Ma@IxaQu|RQR3A2{~Z3&dT?2RvsexMt?3zR1k5KFqw`~^?R!BQWNKuLT=`@yvn z6h!5qVFo>1Xt041Q4t&m0X;L6u+l}5k3t|w;(Nz)M0 z<3KMsh!RnVCAmp3McKi3blmA#70Wd+znFkp1lk!WJb*b44ZqcktInB$oEQwU2(&Xm zNskdw6>jSQ+JKT)2G)k)=qpO2@;_YmWd_hG0EHU$>rsrLAQz)C`U>xgJ*ozyL7a9o z&I~*S2H^s53gC$C4ugHyJ$%7Hpq(=CBh-gM;IIc~ICWD?xTdug>fxX{*u&HY>H`+A zDFe-$Zf7t8qD2efir1w8jw=Aw2y~=pfsF<<)kEHe2ZI5o_M`2w{gmMAiqm@(jC2+- z^@u@+AHCFH!ceXQ=o_cw4ggc)0vQC zh@f2!djDJ9r$mlanRBVKY8-eBnXgRu)PJP8EIlfyWjvv zxa?#@Prp}-CcuVLz-!PdG8g=g7zQB;){5YO*!CMW-dC=NPy^5&pqdOM9PKPH0)b5O zZ}{(|UN&!pGV+AwYmmdFAk=TR7*W*`;B7%*2L-wHwL^8(?^rJ^2h8bAxjARxDk0cH^tL(o~xC=_E9cr66DHH++eZ-2I!`IS72Do9ovprVB8h8=}5@G|mU z1fnXkTf55lDAuS1a8uPE^<-88MyMaT5oP%0a|SOWZ(7G!=Y+o(d2=RoVZC zSA!XWh9&a!_}13_Vs!;y5L65*hc~BZ-SYp7yfrQ*>aK(`P}Caa8t4qP_zokSh85z+ zphsRU*r8vk8vWcFd1`qU_!oK(_i+P8%zv5zTWgkKTQ9B$rh*nKaAyMILSt~w9L>@J zIr03+FvvpoN9g>zKJm}dkFD80Aqef~)L2-qpk{%Vemq@&$KIM#;Y_4-30RUAq;Kft zD!H&T2fSbG$3EM?`>@-W>$kXPc_snH34|upXA4W{lXjX}k3KO75(I^U8mNb$b`#vd zi1KqQWZUe*;^2LE6%1`AkgIthvGEQMcgNWHWj7Slccog;#1#<|f0g!SK<){^+)lFQ32q-UF^t0V+Yg{{*~10gd?j-51>wfjo|)q^P``7UbVIApb(g zVuUy*h@bt?Z85_HY~$Dfvm0Qbt9n8?%rGeYg4E|SeeY>sU@~=JGH6H3Pz7z!rvB%) zS_OR!CvXKYK>&jqPg)&q7)UTb`<~lkWFODQ4+D(GANlg&WlS(X`<>fj-WiPyI|B?a z&<5(GOJJ3QX0##GNA$l2Kv zfS{#JjSJeK-z~4L83_xMvu^++6EL6^Kr=X{hQ^rr)#uuFuw4I?tIz`o$TQ_o=Z|}# zO+X!AkC#KtGJ*4z{)n$5zL+4kIYfdC;Rwq~MdS^Ac~AgAGbQ~Uv=P4-_S+iE>Tp*; z3|JkJgIWWcS&V{r#{7B(F{(8SQkm=*!b%ST#RE{F)y%~(j3_A9EJ#k6JQD@-uFN1{ zLDwu7BGKn;vt~g8^pqIbQUE{*#20kUA{m7NVEZ)-5@J*mMu9Qp**W5j3mwdoDOr~(XV058X*&p^3mL8_9m7C@8-FdTmv zt0e(_%yw%QBq2a0F~k!P@_`~yceI1W(oPxc*AUrm&4MII-J#V4cS3)>jT4%&9z8^# zu#%U^-`L{M@;$D&>iCWxKZEO$r`)B(&m2mok39Gj0BW(U8l67Ok7zRMbEGU%G6 ztPpJwx@(rBdtXq)0PHe=L5+947;V_jYnE)r&cZ-|S@;7ZP=X0&hc!#cp*Q~Ey%$)> zqh?Sa(Y(L}v*T)lo#eF)(rn0Al0hqrjxw}CJ5ml0&7KBt*uYBr!)n3hXi?Bxv#b-3 z*zy3(Zh(P~QKbsBF{sDrw*Y_HT|oQ{$Td*J`%1JCsMjoApJhTd*%5ux6>7nT%!zj6v`R ztpJ+dp~cv7%|d?GQsg#}j@-_MI$yQ{Z361}5)Q1s)ddh!f5g{uu!)F98G}>;AW*DX zRN7t{u>cK_r%|ApQlkZJ#LjD$vd4aoZ-FMBKod?#PyhY<9WdY*K7L&=lH6iD96237 z@~GZu0@9Hc22}dQhn=PWS_y3@-H|wwy9h{M24*@1dG90RseJTe0Eb=8;7-gBd^u{K}ujRvYU`jM^fDS)t7j4)f9sr9=7InqpH;Ex;p{rV((6@yC=Jh~vbVP`0*_Wpd8VRA87tZKO|%o^&-^3lui0 zV`GAA0~;xVBi&FNT_(dwK1v%&eeXD@2y9df793C;5go>avbBu{u2qa}9Y-nPmzB_o zBao9&*yx3EK_3kC-3ktDwo%*>jO3%V(KvZ+moTu=<3DVq2HwSwz7|{CsM%0)P8LYs z1118>Ujq&NGui742j0KGc51-Rb(NDHVUPR)_Ra7AY8@DSy9=~wIZlH9AVh=~?`9EO zeSfLM&9w!DnkTqm0&?%~PqJ6Qx&MBDtM6X_x+Gf`)#YOzHAd4;lB)K#SkEX_V-tj5AO&2&zHfaPVgE$ z@LG4!;;@C_M5~SAbs-C^Q8mKs^#^`lpbs9)sMqV3r%`u};6)BiL3@30r-eGVqZ0 zE5N4*9)XSuef$4>+Zlxlgf$$aDj=wqfm5KP;-brD5nDY%TSp&1HJCcEL0*K8iVvVY d{yVare|{IaNd{_)T|B#1oUyPD`vEZ4{{TiL>Nx-a literal 0 HcmV?d00001 diff --git a/module.properties b/module.properties new file mode 100644 index 00000000..c92ebaaa --- /dev/null +++ b/module.properties @@ -0,0 +1 @@ +ModuleClass: org.scharp.atlas.pepdb.PepDBModule diff --git a/resources/schemas/dbscripts/postgresql/pepdb-0.00-0.02.sql b/resources/schemas/dbscripts/postgresql/pepdb-0.00-0.02.sql new file mode 100644 index 00000000..fa0f718c --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-0.00-0.02.sql @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2003-2005 Fred Hutchinson Cancer Research Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +BEGIN; + +CREATE SCHEMA pepdb; + +CREATE TABLE pepdb.clade ( + clade_id serial NOT NULL, + clade_desc text, + + CONSTRAINT PK_clade PRIMARY KEY (clade_id) +); + +CREATE TABLE pepdb.pathogen ( + pathogen_id serial NOT NULL, + pathogen_desc text, + + CONSTRAINT PK_pathogen PRIMARY KEY (pathogen_id) +); + +CREATE TABLE pepdb.group_type ( + group_type_id serial NOT NULL, + group_type_desc text, + + CONSTRAINT PK_group_type PRIMARY KEY (group_type_id) +); + +CREATE TABLE pepdb.pep_align_ref ( + pep_align_ref_id serial NOT NULL, + pep_align_ref_desc text, + + CONSTRAINT PK_pep_align_ref PRIMARY KEY (pep_align_ref_id) +); + +CREATE TABLE pepdb.peptide_group ( + _ts TIMESTAMP DEFAULT now(), + CreatedBy USERID, + Created TIMESTAMP, + ModifiedBy USERID, + Modified TIMESTAMP, + + peptide_group_id serial NOT NULL, + peptide_group_name text NOT NULL UNIQUE, + pathogen_id integer, + seq_ref text, + clade_id integer, + pep_align_ref_id integer, + group_type_id integer, + + CONSTRAINT PK_peptide_group PRIMARY KEY (peptide_group_id), + CONSTRAINT FK_peptide_group1 FOREIGN KEY(pathogen_id) REFERENCES pepdb.pathogen(pathogen_id), + CONSTRAINT FK_peptide_group2 FOREIGN KEY(clade_id) REFERENCES pepdb.clade(clade_id), + CONSTRAINT FK_peptide_group3 FOREIGN KEY(group_type_id) REFERENCES pepdb.group_type(group_type_id), + CONSTRAINT FK_peptide_group4 FOREIGN KEY(pep_align_ref_id) REFERENCES pepdb.pep_align_ref(pep_align_ref_id) +); + +CREATE TABLE pepdb.protein_category ( + protein_cat_id serial NOT NULL, + protein_cat_desc text, + + CONSTRAINT PK_protein_category PRIMARY KEY (protein_cat_id) +); + +CREATE TABLE pepdb.optimal_epitope_list( + optimal_epitope_list_id serial NOT NULL, + optimal_epitope_list_desc text, + + CONSTRAINT PK_optimal_epitope_list PRIMARY KEY (optimal_epitope_list_id) +); + + +CREATE TABLE pepdb.peptides ( + _ts TIMESTAMP DEFAULT now(), + CreatedBy USERID, + Created TIMESTAMP, + ModifiedBy USERID, + Modified TIMESTAMP, + + peptide_id serial NOT NULL, + peptide_sequence text NOT NULL UNIQUE, + protein_cat_id integer, + amino_acid_start_pos integer, + amino_acid_end_pos integer, + sequence_length integer, + child boolean, + parent boolean DEFAULT false NOT NULL, + src_file_name text, + storage_location text, + optimal_epitope_list_id integer, + hla_restriction text, + + CONSTRAINT PK_peptides PRIMARY KEY (peptide_id), + CONSTRAINT FK_peptides1 FOREIGN KEY(protein_cat_id) REFERENCES pepdb.protein_category(protein_cat_id), + CONSTRAINT FK_peptides2 FOREIGN KEY(optimal_epitope_list_id) REFERENCES pepdb.optimal_epitope_list(optimal_epitope_list_id) +); + + +CREATE TABLE pepdb.peptide_group_assignment ( + _ts TIMESTAMP DEFAULT now(), + CreatedBy USERID, + Created TIMESTAMP, + ModifiedBy USERID, + Modified TIMESTAMP, + + peptide_group_assignment_id serial NOT NULL UNIQUE, + peptide_id integer NOT NULL, + peptide_group_id integer NOT NULL, + peptide_id_in_group integer, + frequency_number float, + frequency_number_date date, + in_current_file boolean DEFAULT false, + + CONSTRAINT PK_peptide_group_assignment PRIMARY KEY (peptide_id,peptide_group_id), + CONSTRAINT FK_peptide_group_assignment1 FOREIGN KEY(peptide_id) REFERENCES pepdb.peptides(peptide_id), + CONSTRAINT FK_peptide_group_assignment2 FOREIGN KEY(peptide_group_id) REFERENCES pepdb.peptide_group(peptide_group_id) +); + +CREATE TABLE pepdb.parent ( + _ts TIMESTAMP DEFAULT now(), + CreatedBy USERID, + Created TIMESTAMP, + ModifiedBy USERID, + Modified TIMESTAMP, + + peptide_id integer NOT NULL, + linked_parent integer NOT NULL, + + CONSTRAINT PK_parent PRIMARY KEY (peptide_id,linked_parent), + CONSTRAINT FK_parent1 FOREIGN KEY(peptide_id) REFERENCES pepdb.peptides(peptide_id), + CONSTRAINT FK_parent2 FOREIGN KEY(linked_parent) REFERENCES pepdb.peptides(peptide_id) +); + +CREATE TABLE pepdb.pool_type ( + pool_type_id serial NOT NULL, + pool_type_desc text, + + CONSTRAINT PK_pool_type PRIMARY KEY (pool_type_id) +); + +CREATE TABLE pepdb.peptide_pool ( + _ts TIMESTAMP DEFAULT now(), + CreatedBy USERID, + Created TIMESTAMP, + ModifiedBy USERID, + Modified TIMESTAMP, + + peptide_pool_id serial NOT NULL, + peptide_pool_name text, + pool_type_id integer, + comment text, + + CONSTRAINT PK_peptide_pool PRIMARY KEY (peptide_pool_id), + CONSTRAINT FK_peptide_pool1 FOREIGN KEY(pool_type_id) REFERENCES pepdb.pool_type(pool_type_id) +); + + +CREATE TABLE pepdb.peptide_pool_assignment ( + _ts TIMESTAMP DEFAULT now(), + CreatedBy USERID, + Created TIMESTAMP, + ModifiedBy USERID, + Modified TIMESTAMP, + + peptide_pool_assignment_id serial NOT NULL UNIQUE, + peptide_pool_id integer NOT NULL, + peptide_id integer NOT NULL, + + CONSTRAINT PK_peptide_pool_assignment PRIMARY KEY (peptide_pool_id,peptide_id), + CONSTRAINT FK_peptide_pool_assignment1 FOREIGN KEY(peptide_pool_id) REFERENCES pepdb.peptide_pool(peptide_pool_id), + CONSTRAINT FK_peptide_pool_assignment2 FOREIGN KEY(peptide_id) REFERENCES pepdb.peptides(peptide_id) +); + +CREATE VIEW pepdb.parent_child_details AS +select par.peptide_id AS child_id,pchild.peptide_sequence AS child_sequence, +pchild.protein_cat_id AS child_protein,pchild.sequence_length AS child_seq_length, +pchild.amino_acid_start_pos AS child_AAStart,pchild.amino_acid_end_pos AS child_AAEnd, +pchild.optimal_epitope_list_id AS child_optimal_epitope_list_id,pchild.hla_restriction AS child_hla_restriction, +par.linked_parent AS parent_id,pparent.peptide_sequence AS parent_sequence, +pparent.protein_cat_id AS parent_protein,pparent.sequence_length AS parent_seq_length, +pparent.amino_acid_start_pos AS parent_AAStart,pparent.amino_acid_end_pos AS parent_AAEnd +from pepdb.parent par LEFT JOIN pepdb.peptides pchild ON (par.peptide_id = pchild.peptide_id) +LEFT JOIN pepdb.peptides pparent ON(par.linked_parent = pparent.peptide_id); + +CREATE VIEW pepdb.group_peptides AS +SELECT src.peptide_id, src.peptide_group_id, src.peptide_id_in_group, + pgroup.peptide_group_name,pgroup.pathogen_id, + p.peptide_sequence,p.protein_cat_id, + p.sequence_length,p.amino_acid_start_pos,p.amino_acid_end_pos, + p.child, p.parent,p.optimal_epitope_list_id,p.hla_restriction, + src.frequency_number,src.frequency_number_date,src.in_current_file +FROM ((pepdb.peptide_group_assignment src LEFT JOIN pepdb.peptide_group pgroup ON ((src.peptide_group_id + = pgroup.peptide_group_id))) LEFT JOIN pepdb.peptides p ON ((src.peptide_id = + p.peptide_id))); + +CREATE VIEW pepdb.pool_peptides AS +SELECT src.peptide_id, p.peptide_sequence, src.peptide_pool_id, pp.pool_type_id, pp.peptide_pool_name, pt.pool_type_desc +FROM +pepdb.peptides p, +pepdb.peptide_pool_assignment src +LEFT JOIN pepdb.peptide_pool pp +LEFT JOIN pepdb.pool_type pt ON(pp.pool_type_id = pt.pool_type_id) ON (src.peptide_pool_id = pp.peptide_pool_id) +WHERE (src.peptide_id = p.peptide_id); + +SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('pepdb.peptides', 'peptide_id'), 500000, true); +SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('pepdb.peptide_pool', 'peptide_pool_id'), 500000, true); + +COMMIT; + + + + + + + diff --git a/resources/schemas/dbscripts/postgresql/pepdb-0.00-2.21.sql b/resources/schemas/dbscripts/postgresql/pepdb-0.00-2.21.sql new file mode 100644 index 00000000..3d37bdc4 --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-0.00-2.21.sql @@ -0,0 +1,226 @@ +CREATE SCHEMA pepdb; + +CREATE TABLE pepdb.clade ( + clade_id SERIAL NOT NULL, + clade_desc text +); + +CREATE TABLE pepdb.peptide_group ( + _ts timestamp without time zone DEFAULT now(), + createdby public.userid, + created timestamp without time zone, + modifiedby public.userid, + modified timestamp without time zone, + peptide_group_id SERIAL NOT NULL, + peptide_group_name text NOT NULL, + pathogen_id integer, + seq_ref text, + clade_id integer, + pep_align_ref_id integer, + group_type_id integer +); + +CREATE TABLE pepdb.peptide_group_assignment ( + _ts timestamp without time zone DEFAULT now(), + createdby public.userid, + created timestamp without time zone, + modifiedby public.userid, + modified timestamp without time zone, + peptide_group_assignment_id SERIAL NOT NULL, + peptide_id integer NOT NULL, + peptide_group_id integer NOT NULL, + peptide_id_in_group character varying, + frequency_number double precision, + frequency_number_date date, + in_current_file boolean DEFAULT false +); + +CREATE TABLE pepdb.peptides ( + _ts timestamp without time zone DEFAULT now(), + createdby public.userid, + created timestamp without time zone, + modifiedby public.userid, + modified timestamp without time zone, + peptide_id SERIAL NOT NULL, + peptide_sequence text NOT NULL, + protein_cat_id integer, + amino_acid_start_pos integer, + amino_acid_end_pos integer, + sequence_length integer, + child boolean, + parent boolean DEFAULT false NOT NULL, + src_file_name text, + storage_location text, + optimal_epitope_list_id integer, + hla_restriction text, + peptide_flag boolean DEFAULT false, + peptide_notes text +); + +CREATE TABLE pepdb.group_type ( + group_type_id SERIAL NOT NULL, + group_type_desc text +); + +CREATE TABLE pepdb.optimal_epitope_list ( + optimal_epitope_list_id SERIAL NOT NULL, + optimal_epitope_list_desc text +); + +CREATE TABLE pepdb.parent ( + _ts timestamp without time zone DEFAULT now(), + createdby public.userid, + created timestamp without time zone, + modifiedby public.userid, + modified timestamp without time zone, + peptide_id integer NOT NULL, + linked_parent integer NOT NULL +); + +CREATE TABLE pepdb.pathogen ( + pathogen_id SERIAL NOT NULL, + pathogen_desc text +); + +CREATE TABLE pepdb.pep_align_ref ( + pep_align_ref_id SERIAL NOT NULL, + pep_align_ref_desc text +); + +CREATE TABLE pepdb.peptide_pool ( + _ts timestamp without time zone DEFAULT now(), + createdby public.userid, + created timestamp without time zone, + modifiedby public.userid, + modified timestamp without time zone, + peptide_pool_id SERIAL NOT NULL, + peptide_pool_name text, + pool_type_id integer, + comment text, + archived boolean DEFAULT false, + parent_pool_id integer, + matrix_peptide_pool_id text +); + +CREATE TABLE pepdb.peptide_pool_assignment ( + _ts timestamp without time zone DEFAULT now(), + createdby public.userid, + created timestamp without time zone, + modifiedby public.userid, + modified timestamp without time zone, + peptide_pool_assignment_id SERIAL NOT NULL, + peptide_pool_id integer NOT NULL, + peptide_id integer NOT NULL, + peptide_group_assignment_id integer +); + +CREATE TABLE pepdb.pool_type ( + pool_type_id SERIAL NOT NULL, + pool_type_desc text +); + +CREATE TABLE pepdb.protein_category ( + protein_cat_id SERIAL NOT NULL, + protein_cat_desc text +); + +CREATE TABLE pepdb.temp_peppoolgroupassign ( + peptide_pool_assignment_id integer, + peptide_group_assignment_id integer +); + +ALTER TABLE ONLY pepdb.peptide_group_assignment + ADD CONSTRAINT peptide_group_assignment_peptide_group_assignment_id_key UNIQUE (peptide_group_assignment_id); + +ALTER TABLE ONLY pepdb.peptide_group + ADD CONSTRAINT peptide_group_peptide_group_name_key UNIQUE (peptide_group_name); + +ALTER TABLE ONLY pepdb.peptide_pool_assignment + ADD CONSTRAINT peptide_pool_assignment_peptide_pool_assignment_id_key UNIQUE (peptide_pool_assignment_id); + +ALTER TABLE ONLY pepdb.peptides + ADD CONSTRAINT peptides_peptide_sequence_key UNIQUE (peptide_sequence); + +ALTER TABLE ONLY pepdb.clade + ADD CONSTRAINT pk_clade PRIMARY KEY (clade_id); + +ALTER TABLE ONLY pepdb.group_type + ADD CONSTRAINT pk_group_type PRIMARY KEY (group_type_id); + +ALTER TABLE ONLY pepdb.optimal_epitope_list + ADD CONSTRAINT pk_optimal_epitope_list PRIMARY KEY (optimal_epitope_list_id); + +ALTER TABLE ONLY pepdb.parent + ADD CONSTRAINT pk_parent PRIMARY KEY (peptide_id, linked_parent); + +ALTER TABLE ONLY pepdb.pathogen + ADD CONSTRAINT pk_pathogen PRIMARY KEY (pathogen_id); + +ALTER TABLE ONLY pepdb.pep_align_ref + ADD CONSTRAINT pk_pep_align_ref PRIMARY KEY (pep_align_ref_id); + +ALTER TABLE ONLY pepdb.peptide_group + ADD CONSTRAINT pk_peptide_group PRIMARY KEY (peptide_group_id); + +ALTER TABLE ONLY pepdb.peptide_group_assignment + ADD CONSTRAINT pk_peptide_group_assignment PRIMARY KEY (peptide_id, peptide_group_id); + +ALTER TABLE ONLY pepdb.peptide_pool + ADD CONSTRAINT pk_peptide_pool PRIMARY KEY (peptide_pool_id); + +ALTER TABLE ONLY pepdb.peptide_pool_assignment + ADD CONSTRAINT pk_peptide_pool_assignment PRIMARY KEY (peptide_pool_id, peptide_id); + +ALTER TABLE ONLY pepdb.peptides + ADD CONSTRAINT pk_peptides PRIMARY KEY (peptide_id); + +ALTER TABLE ONLY pepdb.pool_type + ADD CONSTRAINT pk_pool_type PRIMARY KEY (pool_type_id); + +ALTER TABLE ONLY pepdb.protein_category + ADD CONSTRAINT pk_protein_category PRIMARY KEY (protein_cat_id); + +ALTER TABLE ONLY pepdb.parent + ADD CONSTRAINT fk_parent1 FOREIGN KEY (peptide_id) REFERENCES pepdb.peptides(peptide_id); + +ALTER TABLE ONLY pepdb.parent + ADD CONSTRAINT fk_parent2 FOREIGN KEY (linked_parent) REFERENCES pepdb.peptides(peptide_id); + +ALTER TABLE ONLY pepdb.peptide_group + ADD CONSTRAINT fk_peptide_group1 FOREIGN KEY (pathogen_id) REFERENCES pepdb.pathogen(pathogen_id); + +ALTER TABLE ONLY pepdb.peptide_group + ADD CONSTRAINT fk_peptide_group2 FOREIGN KEY (clade_id) REFERENCES pepdb.clade(clade_id); + +ALTER TABLE ONLY pepdb.peptide_group + ADD CONSTRAINT fk_peptide_group3 FOREIGN KEY (group_type_id) REFERENCES pepdb.group_type(group_type_id); + +ALTER TABLE ONLY pepdb.peptide_group + ADD CONSTRAINT fk_peptide_group4 FOREIGN KEY (pep_align_ref_id) REFERENCES pepdb.pep_align_ref(pep_align_ref_id); + +ALTER TABLE ONLY pepdb.peptide_group_assignment + ADD CONSTRAINT fk_peptide_group_assignment1 FOREIGN KEY (peptide_id) REFERENCES pepdb.peptides(peptide_id); + +ALTER TABLE ONLY pepdb.peptide_group_assignment + ADD CONSTRAINT fk_peptide_group_assignment2 FOREIGN KEY (peptide_group_id) REFERENCES pepdb.peptide_group(peptide_group_id); + +ALTER TABLE ONLY pepdb.peptide_pool + ADD CONSTRAINT fk_peptide_pool1 FOREIGN KEY (pool_type_id) REFERENCES pepdb.pool_type(pool_type_id); + +ALTER TABLE ONLY pepdb.peptide_pool + ADD CONSTRAINT fk_peptide_pool2 FOREIGN KEY (parent_pool_id) REFERENCES pepdb.peptide_pool(peptide_pool_id); + +ALTER TABLE ONLY pepdb.peptide_pool_assignment + ADD CONSTRAINT fk_peptide_pool_assignment1 FOREIGN KEY (peptide_pool_id) REFERENCES pepdb.peptide_pool(peptide_pool_id); + +ALTER TABLE ONLY pepdb.peptide_pool_assignment + ADD CONSTRAINT fk_peptide_pool_assignment2 FOREIGN KEY (peptide_id) REFERENCES pepdb.peptides(peptide_id); + +ALTER TABLE ONLY pepdb.peptide_pool_assignment + ADD CONSTRAINT fk_peptide_pool_assignment3 FOREIGN KEY (peptide_group_assignment_id) REFERENCES pepdb.peptide_group_assignment(peptide_group_assignment_id); + +ALTER TABLE ONLY pepdb.peptides + ADD CONSTRAINT fk_peptides1 FOREIGN KEY (protein_cat_id) REFERENCES pepdb.protein_category(protein_cat_id); + +ALTER TABLE ONLY pepdb.peptides + ADD CONSTRAINT fk_peptides2 FOREIGN KEY (optimal_epitope_list_id) REFERENCES pepdb.optimal_epitope_list(optimal_epitope_list_id); diff --git a/resources/schemas/dbscripts/postgresql/pepdb-0.02-0.05.sql b/resources/schemas/dbscripts/postgresql/pepdb-0.02-0.05.sql new file mode 100644 index 00000000..e89bde9b --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-0.02-0.05.sql @@ -0,0 +1,90 @@ + +INSERT INTO pepdb.clade VALUES (1, 'A'); +INSERT INTO pepdb.clade VALUES (2, 'B'); +INSERT INTO pepdb.clade VALUES (3, 'C'); +INSERT INTO pepdb.clade VALUES (4, 'D'); +INSERT INTO pepdb.clade VALUES (5, 'E'); +INSERT INTO pepdb.clade VALUES (6, 'G'); +INSERT INTO pepdb.clade VALUES (7, 'M'); +INSERT INTO pepdb.clade VALUES (8, 'Other'); +INSERT INTO pepdb.clade VALUES (9, 'Unknown'); +INSERT INTO pepdb.clade VALUES (10, 'A1'); +INSERT INTO pepdb.clade VALUES (11, 'C/A1/D'); +INSERT INTO pepdb.clade VALUES (12, 'D/A1'); + +SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('pepdb.clade', 'clade_id'), 12, true); + +INSERT INTO pepdb.pathogen VALUES (1, 'HIV-1'); +INSERT INTO pepdb.pathogen VALUES (2, 'HIV-2'); +INSERT INTO pepdb.pathogen VALUES (3, 'TB'); +INSERT INTO pepdb.pathogen VALUES (4, 'Malaria'); +INSERT INTO pepdb.pathogen VALUES (5, 'Flu'); +INSERT INTO pepdb.pathogen VALUES (6, 'FEC'); +INSERT INTO pepdb.pathogen VALUES (7, 'EBV'); +INSERT INTO pepdb.pathogen VALUES (8, 'Other'); +INSERT INTO pepdb.pathogen VALUES (9, 'CMV'); +INSERT INTO pepdb.pathogen VALUES (10, 'AD5'); + +SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('pepdb.pathogen', 'pathogen_id'), 10, true); + +INSERT INTO pepdb.group_type VALUES (1, 'Consensus'); +INSERT INTO pepdb.group_type VALUES (2, 'Autologous'); +INSERT INTO pepdb.group_type VALUES (3, 'Mosaic'); +INSERT INTO pepdb.group_type VALUES (4, 'Toggle'); +INSERT INTO pepdb.group_type VALUES (5, 'LABL CTL Epitope'); +INSERT INTO pepdb.group_type VALUES (6, 'Other'); +INSERT INTO pepdb.group_type VALUES (7, 'Vaccine-matched'); +INSERT INTO pepdb.group_type VALUES (8, 'PTE'); + +SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('pepdb.group_type', 'group_type_id'), 8, true); + +INSERT INTO pepdb.pep_align_ref VALUES (1,'HXB2'); + +SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('pepdb.pep_align_ref', 'pep_align_ref_id'), 1, true); + +INSERT INTO pepdb.protein_category VALUES (1, 'GAG'); +INSERT INTO pepdb.protein_category VALUES (2, 'POL'); +INSERT INTO pepdb.protein_category VALUES (3, 'VIF'); +INSERT INTO pepdb.protein_category VALUES (4, 'VPR'); +INSERT INTO pepdb.protein_category VALUES (5, 'TAT'); +INSERT INTO pepdb.protein_category VALUES (6, 'REV'); +INSERT INTO pepdb.protein_category VALUES (7, 'VPU'); +INSERT INTO pepdb.protein_category VALUES (8, 'ENV'); +INSERT INTO pepdb.protein_category VALUES (9, 'NEF'); +INSERT INTO pepdb.protein_category VALUES (10, 'gp160'); +INSERT INTO pepdb.protein_category VALUES (11, 'Integrase'); +INSERT INTO pepdb.protein_category VALUES (12, 'p17'); +INSERT INTO pepdb.protein_category VALUES (13, 'Antigen 85A'); +INSERT INTO pepdb.protein_category VALUES (14, 'BZLF 1'); +INSERT INTO pepdb.protein_category VALUES (15, 'CFP10'); +INSERT INTO pepdb.protein_category VALUES (16, 'EBNA3A'); +INSERT INTO pepdb.protein_category VALUES (17, 'ESAT-6'); +INSERT INTO pepdb.protein_category VALUES (18, 'IE1'); +INSERT INTO pepdb.protein_category VALUES (19, 'p24'); +INSERT INTO pepdb.protein_category VALUES (20, 'p2p7p1p6'); +INSERT INTO pepdb.protein_category VALUES (21, 'pp65'); +INSERT INTO pepdb.protein_category VALUES (22, 'Protease'); +INSERT INTO pepdb.protein_category VALUES (23, 'RT-Integrase'); +INSERT INTO pepdb.protein_category VALUES (24, 'Other'); +INSERT INTO pepdb.protein_category VALUES (25, 'Gag_Pol_TF'); +INSERT INTO pepdb.protein_category VALUES (26, 'RT'); +INSERT INTO pepdb.protein_category VALUES (27, 'Protease-RT'); +INSERT INTO pepdb.protein_category VALUES (28, 'p17-p24'); +INSERT INTO pepdb.protein_category VALUES (29, 'p24-p2p7p1p6'); +INSERT INTO pepdb.protein_category VALUES (30, 'Gag_Pol_TF-Protease'); + + +SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('pepdb.protein_category', 'protein_cat_id'), 30, true); + +INSERT INTO pepdb.pool_type VALUES (1, 'Pool'); +INSERT INTO pepdb.pool_type VALUES (2, 'Sub-Pool'); +INSERT INTO pepdb.pool_type VALUES (3, 'Matrix'); +INSERT INTO pepdb.pool_type VALUES (4, 'Other'); + +SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('pepdb.pool_type', 'pool_type_id'), 4, true); + +INSERT INTO pepdb.optimal_epitope_list VALUES (1, 'A'); +INSERT INTO pepdb.optimal_epitope_list VALUES (2, 'B'); +INSERT INTO pepdb.optimal_epitope_list VALUES (3, 'None'); + +SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('pepdb.optimal_epitope_list', 'optimal_epitope_list_id'), 3, true); diff --git a/resources/schemas/dbscripts/postgresql/pepdb-0.05-0.08.sql b/resources/schemas/dbscripts/postgresql/pepdb-0.05-0.08.sql new file mode 100644 index 00000000..0a5305c7 --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-0.05-0.08.sql @@ -0,0 +1,23 @@ +DROP VIEW pepdb.group_peptides; +ALTER TABLE pepdb.peptide_group_assignment ALTER COLUMN peptide_id_in_group TYPE float; +CREATE VIEW pepdb.group_peptides AS +SELECT src.peptide_group_assignment_id,src.peptide_id, src.peptide_group_id, src.peptide_id_in_group, + pgroup.peptide_group_name,pgroup.pathogen_id, + p.peptide_sequence,p.protein_cat_id, + p.sequence_length,p.amino_acid_start_pos,p.amino_acid_end_pos, + p.child, p.parent,p.optimal_epitope_list_id,p.hla_restriction, + src.frequency_number,src.frequency_number_date,src.in_current_file +FROM ((pepdb.peptide_group_assignment src LEFT JOIN pepdb.peptide_group pgroup ON ((src.peptide_group_id + = pgroup.peptide_group_id))) LEFT JOIN pepdb.peptides p ON ((src.peptide_id = + p.peptide_id))); + +DROP VIEW pepdb.pool_peptides; +CREATE VIEW pepdb.pool_peptides AS +SELECT src.peptide_pool_assignment_id,src.peptide_id,src.peptide_pool_id, +p.peptide_sequence,p.protein_cat_id, +p.sequence_length,p.amino_acid_start_pos,p.amino_acid_end_pos, +p.child, p.parent, + pp.pool_type_id,pp.peptide_pool_name,pt.pool_type_desc +FROM pepdb.peptide_pool_assignment src LEFT JOIN pepdb.peptide_pool pp LEFT JOIN pepdb.pool_type pt ON(pp.pool_type_id = pt.pool_type_id) ON (src.peptide_pool_id = pp.peptide_pool_id), pepdb.peptides p +WHERE (src.peptide_id = p.peptide_id); + diff --git a/resources/schemas/dbscripts/postgresql/pepdb-0.08-1.00.sql b/resources/schemas/dbscripts/postgresql/pepdb-0.08-1.00.sql new file mode 100644 index 00000000..247fe43a --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-0.08-1.00.sql @@ -0,0 +1,27 @@ +DROP VIEW pepdb.pool_peptides; +CREATE VIEW pepdb.pool_peptides AS +SELECT src.peptide_pool_assignment_id,src.peptide_id,src.peptide_pool_id, +p.peptide_sequence,p.protein_cat_id,pg.peptide_group_id,pg.peptide_id_in_group, +p.sequence_length,p.amino_acid_start_pos,p.amino_acid_end_pos, +p.child, p.parent, + pp.pool_type_id,pp.peptide_pool_name,pt.pool_type_desc +FROM pepdb.peptide_pool_assignment src LEFT JOIN pepdb.peptide_pool pp +LEFT JOIN pepdb.pool_type pt ON(pp.pool_type_id = pt.pool_type_id) +ON (src.peptide_pool_id = pp.peptide_pool_id), pepdb.peptides p, pepdb.peptide_group_assignment pg +WHERE (src.peptide_id = p.peptide_id) and (pg.peptide_id = p.peptide_id); + +DROP VIEW pepdb.parent_child_details; +CREATE VIEW pepdb.parent_child_details AS +select par.peptide_id AS child_id,pchild.peptide_sequence AS child_sequence, +pchild.protein_cat_id AS child_protein,pgchild.peptide_group_id AS child_group, +pgchild.peptide_id_in_group AS child_lab_id,pchild.sequence_length AS child_seq_length, +pchild.amino_acid_start_pos AS child_AAStart,pchild.amino_acid_end_pos AS child_AAEnd, +pchild.optimal_epitope_list_id AS child_optimal_epitope_list_id,pchild.hla_restriction AS child_hla_restriction, +par.linked_parent AS parent_id,pparent.peptide_sequence AS parent_sequence, +pparent.protein_cat_id AS parent_protein,pgparent.peptide_group_id AS parent_group, +pgparent.peptide_id_in_group AS parent_lab_id,pparent.sequence_length AS parent_seq_length, +pparent.amino_acid_start_pos AS parent_AAStart,pparent.amino_acid_end_pos AS parent_AAEnd +from pepdb.parent par LEFT JOIN pepdb.peptides pchild ON (par.peptide_id = pchild.peptide_id) +LEFT JOIN pepdb.peptides pparent ON(par.linked_parent = pparent.peptide_id) +LEFT JOIN pepdb.peptide_group_assignment pgchild ON(par.peptide_id = pgchild.peptide_id) +LEFT JOIN pepdb.peptide_group_assignment pgparent ON(par.linked_parent = pgparent.peptide_id); \ No newline at end of file diff --git a/resources/schemas/dbscripts/postgresql/pepdb-1.00-1.50.sql b/resources/schemas/dbscripts/postgresql/pepdb-1.00-1.50.sql new file mode 100644 index 00000000..f6d331da --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-1.00-1.50.sql @@ -0,0 +1,5 @@ +ALTER TABLE pepdb.peptide_pool ADD COLUMN archived boolean default false; +ALTER TABLE pepdb.peptide_pool_assignment ADD COLUMN peptide_group_assignment_id integer; +ALTER TABLE pepdb.peptide_pool_assignment ADD CONSTRAINT FK_peptide_pool_assignment3 FOREIGN KEY(peptide_group_assignment_id) REFERENCES pepdb.peptide_group_assignment(peptide_group_assignment_id); +ALTER TABLE pepdb.peptides ADD COLUMN peptide_flag boolean default false; +ALTER TABLE pepdb.peptides ADD COLUMN peptide_notes text; diff --git a/resources/schemas/dbscripts/postgresql/pepdb-1.50-1.75.sql b/resources/schemas/dbscripts/postgresql/pepdb-1.50-1.75.sql new file mode 100644 index 00000000..10e6d578 --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-1.50-1.75.sql @@ -0,0 +1,29 @@ +DROP VIEW pepdb.group_peptides; +CREATE VIEW pepdb.group_peptides AS +SELECT src.peptide_group_assignment_id,src.peptide_id, src.peptide_group_id, src.peptide_id_in_group, + pgroup.peptide_group_name,pgroup.pathogen_id, + p.peptide_sequence,p.protein_cat_id, + p.sequence_length,p.amino_acid_start_pos,p.amino_acid_end_pos, + p.child, p.parent,p.optimal_epitope_list_id,p.hla_restriction,p.peptide_flag,p.peptide_notes, + src.frequency_number,src.frequency_number_date,src.in_current_file +FROM ((pepdb.peptide_group_assignment src LEFT JOIN pepdb.peptide_group pgroup ON ((src.peptide_group_id + = pgroup.peptide_group_id))) LEFT JOIN pepdb.peptides p ON ((src.peptide_id = + p.peptide_id))); + +DROP VIEW pepdb.parent_child_details; +CREATE VIEW pepdb.parent_child_details AS +select par.peptide_id AS child_id,pchild.peptide_sequence AS child_sequence, +pchild.protein_cat_id AS child_protein,pgchild.peptide_group_id AS child_group, +pgchild.peptide_id_in_group AS child_lab_id,pchild.sequence_length AS child_seq_length, +pchild.amino_acid_start_pos AS child_AAStart,pchild.amino_acid_end_pos AS child_AAEnd, +pchild.optimal_epitope_list_id AS child_optimal_epitope_list_id,pchild.hla_restriction AS child_hla_restriction, +pchild.peptide_flag AS child_peptide_flag,pchild.peptide_notes AS child_peptide_notes, +par.linked_parent AS parent_id,pparent.peptide_sequence AS parent_sequence, +pparent.protein_cat_id AS parent_protein,pgparent.peptide_group_id AS parent_group, +pgparent.peptide_id_in_group AS parent_lab_id,pparent.sequence_length AS parent_seq_length, +pparent.amino_acid_start_pos AS parent_AAStart,pparent.amino_acid_end_pos AS parent_AAEnd, +pparent.peptide_flag AS parent_peptide_flag,pparent.peptide_notes AS parent_peptide_notes +from pepdb.parent par LEFT JOIN pepdb.peptides pchild ON (par.peptide_id = pchild.peptide_id) +LEFT JOIN pepdb.peptides pparent ON(par.linked_parent = pparent.peptide_id) +LEFT JOIN pepdb.peptide_group_assignment pgchild ON(par.peptide_id = pgchild.peptide_id) +LEFT JOIN pepdb.peptide_group_assignment pgparent ON(par.linked_parent = pgparent.peptide_id); \ No newline at end of file diff --git a/resources/schemas/dbscripts/postgresql/pepdb-1.75-2.00.sql b/resources/schemas/dbscripts/postgresql/pepdb-1.75-2.00.sql new file mode 100644 index 00000000..fbe187d0 --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-1.75-2.00.sql @@ -0,0 +1,12 @@ +DROP VIEW pepdb.pool_peptides; +CREATE VIEW pepdb.pool_peptides AS +SELECT src.peptide_pool_assignment_id,src.peptide_id,src.peptide_pool_id, +p.peptide_sequence,p.protein_cat_id,pg.peptide_group_id,pg.peptide_id_in_group, +p.sequence_length,p.amino_acid_start_pos,p.amino_acid_end_pos, +p.child, p.parent,p.peptide_flag,p.peptide_notes, + pp.pool_type_id,pp.peptide_pool_name,pt.pool_type_desc,pp.archived +FROM pepdb.peptide_pool_assignment src +LEFT JOIN pepdb.peptide_pool pp LEFT JOIN pepdb.pool_type pt ON(pp.pool_type_id = pt.pool_type_id) ON (src.peptide_pool_id = pp.peptide_pool_id) +LEFT JOIN pepdb.peptide_group_assignment pg ON(src.peptide_group_assignment_id = pg.peptide_group_assignment_id), +pepdb.peptides p +WHERE (src.peptide_id = p.peptide_id); \ No newline at end of file diff --git a/resources/schemas/dbscripts/postgresql/pepdb-2.00-2.10.sql b/resources/schemas/dbscripts/postgresql/pepdb-2.00-2.10.sql new file mode 100644 index 00000000..920bf777 --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-2.00-2.10.sql @@ -0,0 +1,4 @@ + +ALTER TABLE pepdb.peptide_pool ADD COLUMN parent_pool_id integer; +ALTER TABLE pepdb.peptide_pool ADD CONSTRAINT FK_peptide_pool2 FOREIGN KEY(parent_pool_id) REFERENCES pepdb.peptide_pool(peptide_pool_id); +ALTER TABLE pepdb.peptide_pool ADD COLUMN matrix_peptide_pool_id text; diff --git a/resources/schemas/dbscripts/postgresql/pepdb-2.10-2.20.sql b/resources/schemas/dbscripts/postgresql/pepdb-2.10-2.20.sql new file mode 100644 index 00000000..5f56c4c3 --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-2.10-2.20.sql @@ -0,0 +1,8 @@ +CREATE OR REPLACE VIEW pepdb.pool_details AS +SELECT peptide_pool._ts,peptide_pool.createdby,peptide_pool.created,peptide_pool.modifiedby,peptide_pool.modified, +peptide_pool.peptide_pool_id,peptide_pool.peptide_pool_name, +peptide_pool.pool_type_id,pt.pool_type_desc, +peptide_pool.comment,peptide_pool.archived, +peptide_pool.parent_pool_id,peptide_pool.matrix_peptide_pool_id,p.peptide_pool_name AS parent_pool_name +from pepdb.peptide_pool LEFT JOIN pepdb.peptide_pool p ON(peptide_pool.parent_pool_id = p.peptide_pool_id) +LEFT JOIN pepdb.pool_type pt ON (peptide_pool.pool_type_id = pt.pool_type_id); \ No newline at end of file diff --git a/resources/schemas/dbscripts/postgresql/pepdb-2.20-2.21.sql b/resources/schemas/dbscripts/postgresql/pepdb-2.20-2.21.sql new file mode 100644 index 00000000..ce546a32 --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-2.20-2.21.sql @@ -0,0 +1,33 @@ +BEGIN; + +DROP VIEW IF EXISTS pepdb.parent_child_details; +DROP VIEW IF EXISTS pepdb.pool_peptides; +DROP VIEW IF EXISTS pepdb.group_peptides; + +-- RT #103413. Modify peptide_id_in_group's column type to varchar because the underlying data source +-- (an uploaded text file) changed format. +ALTER TABLE pepdb.peptide_group_assignment ALTER COLUMN peptide_id_in_group SET DATA TYPE varchar; + +CREATE OR REPLACE VIEW pepdb.parent_child_details AS + SELECT par.peptide_id AS child_id, pchild.peptide_sequence AS child_sequence, pchild.protein_cat_id AS child_protein, pgchild.peptide_group_id AS child_group, pgchild.peptide_id_in_group AS child_lab_id, pchild.sequence_length AS child_seq_length, pchild.amino_acid_start_pos AS child_aastart, pchild.amino_acid_end_pos AS child_aaend, pchild.optimal_epitope_list_id AS child_optimal_epitope_list_id, pchild.hla_restriction AS child_hla_restriction, pchild.peptide_flag AS child_peptide_flag, pchild.peptide_notes AS child_peptide_notes, par.linked_parent AS parent_id, pparent.peptide_sequence AS parent_sequence, pparent.protein_cat_id AS parent_protein, pgparent.peptide_group_id AS parent_group, pgparent.peptide_id_in_group AS parent_lab_id, pparent.sequence_length AS parent_seq_length, pparent.amino_acid_start_pos AS parent_aastart, pparent.amino_acid_end_pos AS parent_aaend, pparent.peptide_flag AS parent_peptide_flag, pparent.peptide_notes AS parent_peptide_notes + FROM pepdb.parent par + LEFT JOIN pepdb.peptides pchild ON par.peptide_id = pchild.peptide_id + LEFT JOIN pepdb.peptides pparent ON par.linked_parent = pparent.peptide_id + LEFT JOIN pepdb.peptide_group_assignment pgchild ON par.peptide_id = pgchild.peptide_id + LEFT JOIN pepdb.peptide_group_assignment pgparent ON par.linked_parent = pgparent.peptide_id; + +CREATE OR REPLACE VIEW pepdb.pool_peptides AS + SELECT src.peptide_pool_assignment_id, src.peptide_id, src.peptide_pool_id, p.peptide_sequence, p.protein_cat_id, pg.peptide_group_id, pg.peptide_id_in_group, p.sequence_length, p.amino_acid_start_pos, p.amino_acid_end_pos, p.child, p.parent, p.peptide_flag, p.peptide_notes, pp.pool_type_id, pp.peptide_pool_name, pt.pool_type_desc, pp.archived + FROM pepdb.peptide_pool_assignment src + LEFT JOIN (pepdb.peptide_pool pp + LEFT JOIN pepdb.pool_type pt ON pp.pool_type_id = pt.pool_type_id) ON src.peptide_pool_id = pp.peptide_pool_id + LEFT JOIN pepdb.peptide_group_assignment pg ON src.peptide_group_assignment_id = pg.peptide_group_assignment_id, pepdb.peptides p + WHERE src.peptide_id = p.peptide_id; + +CREATE OR REPLACE VIEW pepdb.group_peptides AS + SELECT src.peptide_group_assignment_id, src.peptide_id, src.peptide_group_id, src.peptide_id_in_group, pgroup.peptide_group_name, pgroup.pathogen_id, p.peptide_sequence, p.protein_cat_id, p.sequence_length, p.amino_acid_start_pos, p.amino_acid_end_pos, p.child, p.parent, p.optimal_epitope_list_id, p.hla_restriction, p.peptide_flag, p.peptide_notes, src.frequency_number, src.frequency_number_date, src.in_current_file + FROM pepdb.peptide_group_assignment src + LEFT JOIN pepdb.peptide_group pgroup ON src.peptide_group_id = pgroup.peptide_group_id + LEFT JOIN pepdb.peptides p ON src.peptide_id = p.peptide_id; + +COMMIT; diff --git a/resources/schemas/dbscripts/postgresql/pepdb-create.sql b/resources/schemas/dbscripts/postgresql/pepdb-create.sql new file mode 100644 index 00000000..6c2e685a --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-create.sql @@ -0,0 +1,11 @@ +CREATE VIEW pepdb.group_peptides AS + SELECT src.peptide_group_assignment_id, src.peptide_id, src.peptide_group_id, src.peptide_id_in_group, pgroup.peptide_group_name, pgroup.pathogen_id, p.peptide_sequence, p.protein_cat_id, p.sequence_length, p.amino_acid_start_pos, p.amino_acid_end_pos, p.child, p.parent, p.optimal_epitope_list_id, p.hla_restriction, p.peptide_flag, p.peptide_notes, src.frequency_number, src.frequency_number_date, src.in_current_file FROM ((pepdb.peptide_group_assignment src LEFT JOIN pepdb.peptide_group pgroup ON ((src.peptide_group_id = pgroup.peptide_group_id))) LEFT JOIN pepdb.peptides p ON ((src.peptide_id = p.peptide_id))); + +CREATE VIEW pepdb.parent_child_details AS + SELECT par.peptide_id AS child_id, pchild.peptide_sequence AS child_sequence, pchild.protein_cat_id AS child_protein, pgchild.peptide_group_id AS child_group, pgchild.peptide_id_in_group AS child_lab_id, pchild.sequence_length AS child_seq_length, pchild.amino_acid_start_pos AS child_aastart, pchild.amino_acid_end_pos AS child_aaend, pchild.optimal_epitope_list_id AS child_optimal_epitope_list_id, pchild.hla_restriction AS child_hla_restriction, pchild.peptide_flag AS child_peptide_flag, pchild.peptide_notes AS child_peptide_notes, par.linked_parent AS parent_id, pparent.peptide_sequence AS parent_sequence, pparent.protein_cat_id AS parent_protein, pgparent.peptide_group_id AS parent_group, pgparent.peptide_id_in_group AS parent_lab_id, pparent.sequence_length AS parent_seq_length, pparent.amino_acid_start_pos AS parent_aastart, pparent.amino_acid_end_pos AS parent_aaend, pparent.peptide_flag AS parent_peptide_flag, pparent.peptide_notes AS parent_peptide_notes FROM ((((pepdb.parent par LEFT JOIN pepdb.peptides pchild ON ((par.peptide_id = pchild.peptide_id))) LEFT JOIN pepdb.peptides pparent ON ((par.linked_parent = pparent.peptide_id))) LEFT JOIN pepdb.peptide_group_assignment pgchild ON ((par.peptide_id = pgchild.peptide_id))) LEFT JOIN pepdb.peptide_group_assignment pgparent ON ((par.linked_parent = pgparent.peptide_id))); + +CREATE VIEW pepdb.pool_details AS + SELECT peptide_pool._ts, peptide_pool.createdby, peptide_pool.created, peptide_pool.modifiedby, peptide_pool.modified, peptide_pool.peptide_pool_id, peptide_pool.peptide_pool_name, peptide_pool.pool_type_id, pt.pool_type_desc, peptide_pool.comment, peptide_pool.archived, peptide_pool.parent_pool_id, peptide_pool.matrix_peptide_pool_id, p.peptide_pool_name AS parent_pool_name FROM ((pepdb.peptide_pool LEFT JOIN pepdb.peptide_pool p ON ((peptide_pool.parent_pool_id = p.peptide_pool_id))) LEFT JOIN pepdb.pool_type pt ON ((peptide_pool.pool_type_id = pt.pool_type_id))); + +CREATE VIEW pepdb.pool_peptides AS + SELECT src.peptide_pool_assignment_id, src.peptide_id, src.peptide_pool_id, p.peptide_sequence, p.protein_cat_id, pg.peptide_group_id, pg.peptide_id_in_group, p.sequence_length, p.amino_acid_start_pos, p.amino_acid_end_pos, p.child, p.parent, p.peptide_flag, p.peptide_notes, pp.pool_type_id, pp.peptide_pool_name, pt.pool_type_desc, pp.archived FROM ((pepdb.peptide_pool_assignment src LEFT JOIN (pepdb.peptide_pool pp LEFT JOIN pepdb.pool_type pt ON ((pp.pool_type_id = pt.pool_type_id))) ON ((src.peptide_pool_id = pp.peptide_pool_id))) LEFT JOIN pepdb.peptide_group_assignment pg ON ((src.peptide_group_assignment_id = pg.peptide_group_assignment_id))), pepdb.peptides p WHERE (src.peptide_id = p.peptide_id); diff --git a/resources/schemas/dbscripts/postgresql/pepdb-drop.sql b/resources/schemas/dbscripts/postgresql/pepdb-drop.sql new file mode 100644 index 00000000..776177e9 --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/pepdb-drop.sql @@ -0,0 +1,7 @@ +DROP VIEW IF EXISTS pepdb.group_peptides; + +DROP VIEW IF EXISTS pepdb.parent_child_details; + +DROP VIEW IF EXISTS pepdb.pool_details; + +DROP VIEW IF EXISTS pepdb.pool_peptides; \ No newline at end of file diff --git a/resources/schemas/dbscripts/sqlserver/pepdb-0.00-0.01.sql b/resources/schemas/dbscripts/sqlserver/pepdb-0.00-0.01.sql new file mode 100644 index 00000000..c41cedfa --- /dev/null +++ b/resources/schemas/dbscripts/sqlserver/pepdb-0.00-0.01.sql @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2013 Fred Hutchinson Cancer Research Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +-- Tables and views used for Peptide module +CREATE SCHEMA pepdb; +GO \ No newline at end of file diff --git a/resources/schemas/pepdb.xml b/resources/schemas/pepdb.xml new file mode 100644 index 00000000..8e5112e2 --- /dev/null +++ b/resources/schemas/pepdb.xml @@ -0,0 +1,590 @@ + + + + + + + + + + + + + + + + + + + Peptide Id + + + Peptide Group Id + + + Lab ID + + + Peptide Group Name + + + Pathogen + + pathogen_id + pathogen + pepdb + + + + Peptide Sequence + + + Protein Category + + protein_cat_id + protein_category + pepdb + + + + Sequence Length + + + AA Start + + + AA End + + + Is Child + + + Is Parent + + + Optimal Epitope List + + optimal_epitope_list_id + optimal_epitope_list + pepdb + + + + HLA Restriction + + + Peptide Flag + + + Peptide Flag Reason + + + Frequency Number + + + Frequency Update Date + + + + + + + + + Created By + + UserId + Users + core + + + + dd-MMM-yy + + + Modified By + + UserId + Users + core + + + + dd-MMM-yy + + + + 1 + true + + + Pool Type + + pool_type_id + pool_type + pepdb + + true + + + 1 + + + + + + + + + + + Created By + + UserId + Users + core + + + + dd-MMM-yy + + + Modified By + + UserId + Users + core + + + + dd-MMM-yy + + + + 1 + true + + + Pool Type + + pool_type_id + pool_type + pepdb + + true + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Child ID + + + Child Sequence + + + Child Protein Category + + protein_cat_id + protein_category + pepdb + + + + Child Peptide Group + + peptide_group_id + peptide_group + pepdb + + + + Child Lab ID + + + Child Sequence Length + + + Child AAStart + + + Child AAEnd + + + Child Optimal Epitope List + + optimal_epitope_list_id + optimal_epitope_list + pepdb + + + + Child HLA Restriction + + + Child Peptide Flag + + + Child Flag Reason + + + Parent ID + + + Parent Sequence + + + Parent Protein Category + + protein_cat_id + protein_category + pepdb + + + + Parent Peptide Group + + peptide_group_id + peptide_group + pepdb + + + + Parent Lab ID + + + Parent Sequence Length + + + Parent AAStart + + + Parent AAEnd + + + Parent Peptide Flagged + + + Parent Flag Reason + 2 + + + + + + + + Created By + + UserId + Users + core + + + + dd-MMM-yy + + + Modified By + + UserId + Users + core + + + + dd-MMM-yy + + + + 1 + true + + + Protein Category + true + + protein_cat_id + protein_category + pepdb + + + + AAStart + true + + + AAEnd + true + + + true + + + Is Child + true + + + Is Parent + true + + + Source File Name + 1 + true + + + 1 + + + Optimal Epitope List + + optimal_epitope_list_id + optimal_epitope_list + pepdb + + + + 1 + + + Peptide Is Flagged + + + Peptide Flag Reason + 2 + + + + + + + + Peptide ID + + + Peptide Pool ID + + + Peptide Sequence + + + Protein Category + + protein_cat_id + protein_category + pepdb + + + + Peptide Group + + peptide_group_id + peptide_group + pepdb + + + + Lab ID + + + Sequence Length + + + AAStart + + + AAEnd + + + Is Child + + + Is Parent + + + Peptide Flag + + + Flag Reason + 2 + + + Pool Type + + pool_type_id + pool_type + pepdb + + + + Peptide Pool Name + + + Pool Type + + + Archived + + + + + + + + + + + + + + Created By + true + + UserId + Users + core + + + + true + dd-MMM-yy + + + true + Modified By + + UserId + Users + core + + + + true + dd-MMM-yy + + + + 1 + Peptide Group Name + + + Pathogen + + pathogen_id + pathogen + pepdb + + + + 1 + + + Clade + + clade_id + clade + pepdb + + + + Pep Align Ref + + pep_align_ref_id + pep_align_ref + pepdb + + + + Group Type + + group_type_id + group_type + pepdb + + + + peptide_group_id + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/org/scharp/atlas/pepdb/PepDBBaseController.java b/src/org/scharp/atlas/pepdb/PepDBBaseController.java new file mode 100644 index 00000000..54d5b249 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/PepDBBaseController.java @@ -0,0 +1,672 @@ +package org.scharp.atlas.pepdb; + +import org.labkey.api.action.SpringActionController; +import org.labkey.api.data.*; +import org.labkey.api.util.DateUtil; +import org.labkey.api.attachments.AttachmentFile; +import org.apache.log4j.Logger; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.beanutils.ConversionException; +import org.springframework.validation.Errors; +import org.scharp.atlas.pepdb.model.PeptideGroup; +import org.scharp.atlas.pepdb.model.Peptides; +import org.scharp.atlas.pepdb.model.ProteinCategory; +import org.scharp.atlas.pepdb.model.PeptidePool; + +import java.util.*; +import java.io.Writer; +import java.io.IOException; +import java.sql.SQLException; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Jul 6, 2009 + * Time: 12:43:07 PM + * To change this template use File | Settings | File Templates. + */ +public class PepDBBaseController extends SpringActionController +{ + private final static Logger _log = Logger.getLogger(PepDBBaseController.class); + + private static final String VIEW_DISPLAY_PEPTIDE = "displayPeptide.view"; + protected static final String QRY_STRING_PEPTIDE_ID = "peptideId"; + + public static class PeptideQueryForm + { + private String queryKey; + private String queryValue; + private String message; + private TableInfo tInfo; + private SimpleFilter filter; + private List cInfo; + private Sort sort; + private String AAStart; + private String AAEnd; + private String labId; + + public String getMessage() + { + return message; + } + + public void setMessage(String message) + { + this.message = message; + } + + public List getCInfo() + { + return cInfo; + } + + public void setCInfo(List cInfo) + { + this.cInfo = cInfo; + } + + public String getQueryValue() + { + return queryValue; + } + + public void setQueryValue(String queryValue) + { + this.queryValue = queryValue; + } + + public TableInfo getTInfo() + { + return tInfo; + } + + public void setTInfo(TableInfo tInfo) + { + this.tInfo = tInfo; + } + + public SimpleFilter getFilter() + { + return filter; + } + + public void setFilter(SimpleFilter filter) + { + this.filter = filter; + } + + public Sort getSort() + { + return sort; + } + + public void setSort(Sort sort) + { + this.sort = sort; + } + + public String getQueryKey() + { + return queryKey; + } + + public void setQueryKey(String queryKey) + { + this.queryKey = queryKey; + } + + public String getAAStart() + { + return AAStart; + } + + public void setAAStart(String AAStart) + { + this.AAStart = AAStart; + } + + public String getAAEnd() + { + return AAEnd; + } + + public void setAAEnd(String AAEnd) + { + this.AAEnd = AAEnd; + } + + public String getLabId() + { + return labId; + } + + public void setLabId(String labId) + { + this.labId = labId; + } + + public boolean validate(Errors errors) throws SQLException + { + if(getQueryKey() == null || StringUtils.trimToNull(getQueryKey()) == null) + errors.reject(null, "The Search Criteria must be entered."); + String qValue = getQueryValue(); + if (getQueryKey() != null && getQueryKey().equals(PepDBSchema.COLUMN_PARENT_SEQUENCE)) + { + if (StringUtils.trimToNull(qValue) == null) + errors.reject(null, "The Parent Sequence must be entered."); + } + if (getQueryKey() != null && getQueryKey().equals(PepDBSchema.COLUMN_CHILD_SEQUENCE)) + { + if (StringUtils.trimToNull(qValue) == null) + errors.reject(null, "The Child Sequence must be entered."); + } + if (getQueryKey() != null && getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)) + { + if (StringUtils.trimToNull(qValue) == null) + errors.reject(null, "Peptide Group must be selected to get peptides in a group."); + if(StringUtils.trimToNull(getLabId()) == null) + errors.reject(null, "Lab ID must be entered."); + } + if (getQueryKey() != null && getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_POOL_ID)) + { + if (StringUtils.trimToNull(qValue) == null) + errors.reject(null, "Peptide Pool Name must be selected to get peptides in a pool."); + } + if (getQueryKey() != null && getQueryKey().equals(PepDBSchema.COLUMN_PROTEIN_CAT_ID)) + { + if (StringUtils.trimToNull(qValue) == null) + errors.reject(null, "Protein Category must be selected to get peptides in a protein category."); + else + { + ProteinCategory pc = PepDBManager.getProCatByID(Integer.parseInt(getQueryValue())); + if(pc.getProtein_cat_desc().trim().contains("-")) + { + if(StringUtils.trimToNull(getAAStart()) != null || StringUtils.trimToNull(getAAEnd()) != null) + errors.reject(null,"When you select a hyphanated Protein Category : "+pc.getProtein_cat_desc()+" AAStart & AAEnd values are not allowed."); + } + else + { + if(StringUtils.trimToNull(getAAStart()) != null && validateInteger(getAAStart().trim()) == null) + errors.reject(null, "AAStart must be an Integer."); + if(StringUtils.trimToNull(getAAEnd()) != null && validateInteger(getAAEnd().trim()) == null) + errors.reject(null, "AAEnd must be an Integer."); + if(StringUtils.trimToNull(getAAStart()) != null && validateInteger(getAAStart().trim()) != null + && StringUtils.trimToNull(getAAEnd()) != null && validateInteger(getAAEnd().trim()) != null + && validateInteger(getAAStart().trim()) > validateInteger(getAAEnd().trim())) + errors.reject(null, "AAStart must be less than or equal to AAEnd."); + } + } + } + if (getQueryKey() != null && getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_ID)) + { + if (StringUtils.trimToNull(qValue) == null) + errors.reject(null, "The Peptide Id range must be entered."); + if (qValue != null && qValue.length() > 0) + { + if (!(qValue.matches("\\d+-\\d+"))) + { + errors.reject(null, "To get the peptides in the range you should specify the Range of numbers.\n" + + "The format for specify the range of peptide is - Example would be 30-100"); + } + else + { + String[] range = qValue.split("-"); + if (Integer.parseInt(range[0]) > Integer.parseInt(range[1])) + { + errors.reject(null, "The minimum value which is before '-' should be less than the max value which is after '-'.\n"); + + } + } + } + } + if(errors != null && errors.getErrorCount() >0) + return false; + return true; + } + + } + + public static class DisplayPeptideForm + { + private String peptide_id; + private boolean modify = false; + private String manufactureStatus; + + public String getPeptide_id() + { + return peptide_id; + } + + public void setPeptide_id(String peptide_id) + { + this.peptide_id = peptide_id; + } + + public void setModify(boolean modify) + { + this.modify = modify; + } + + public boolean getModify() + { + return this.modify; + } + + public String getManufactureStatus() + { + return manufactureStatus; + } + + public void setManufactureStatus(String manufactureStatus) + { + this.manufactureStatus = manufactureStatus; + } + + public String toString() + { + return "DisplayPeptideForm [peptideId:" + this.peptide_id + + ", modify:" + this.modify + + ", manufactureStatus:" + this.manufactureStatus + "]"; + } + } + + public static class PeptideAndGroupForm extends DisplayPeptideForm + { + + private String peptide_group_id; + + public String getPeptide_group_id() + { + return peptide_group_id; + } + + public void setPeptide_group_id(String peptide_group_id) + { + this.peptide_group_id = peptide_group_id; + } + + public String toString() + { + return "PeptideAndGroupForm [peptideGroup:" + this.peptide_group_id + "] - " + super.toString(); + } + + } + + public static class PeptideAndPoolForm extends DisplayPeptideForm + { + private String peptide_pool_id; + + public String getPeptide_pool_id() + { + return peptide_pool_id; + } + + public void setPeptide_pool_id(String peptide_pool_id) + { + this.peptide_pool_id = peptide_pool_id; + } + + public String toString() + { + return "PeptideAndPoolForm [peptidePool:" + this.peptide_pool_id + "] - " + super.toString(); + } + } + + public static class PeptideForm extends BeanViewForm { + public PeptideForm() + { + super(Peptides.class, PepDBSchema.getInstance().getTableInfoPeptides()); + + } + public PeptideForm(String peptideID) + { + this(); + set("peptide_id", String.valueOf(peptideID)); + } + + } + + public static class PeptidePoolForm extends BeanViewForm { + public PeptidePoolForm() + { + super(PeptidePool.class, PepDBSchema.getInstance().getTableInfoPeptidePools()); + + } + public PeptidePoolForm(String peptidePoolID) + { + this(); + set("peptide_pool_id", String.valueOf(peptidePoolID)); + } + + } + + public static class PeptideGroupForm extends BeanViewForm { + public PeptideGroupForm() + { + super(PeptideGroup.class, PepDBSchema.getInstance().getTableInfoPeptideGroups()); + + } + public PeptideGroupForm(String peptideGroupID) + { + this(); + set("peptide_group_id", String.valueOf(peptideGroupID)); + } + + public void validate(Errors errors) + { + PeptideGroup bean = getBean(); + if(bean.getPathogen_id() == null || StringUtils.trimToNull(bean.getPathogen_id().toString())==null) + errors.reject(null,"Pathogen is Required"); + if(bean.getClade_id() == null || StringUtils.trimToNull(bean.getClade_id().toString())==null) + errors.reject(null,"Clade is Required"); + if(bean.getGroup_type_id() == null || StringUtils.trimToNull(bean.getGroup_type_id().toString())==null) + errors.reject(null,"Group Type is Required"); + if (StringUtils.trimToNull(bean.getPeptide_group_name()) == null) + errors.reject(null, "Peptide Group Name is required."); + } + + public void validateName(Errors errors) throws SQLException + { + PeptideGroup bean = getBean(); + PeptideGroup pg = PepDBManager.getPeptideGroupByName(bean); + if(pg != null) + errors.reject(null, "Peptide Group with the name : "+bean.getPeptide_group_name()+" with a different ID already exists in the database."); + } + } + + public static class FileForm + { + private String message; + private String actionType; + + public String getMessage() + { + return message; + } + + public void setMessage(String message) + { + this.message = message; + } + + public String getActionType() + { + return actionType; + } + + public void setActionType(String actionType) + { + this.actionType = actionType; + } + + public boolean validate(Errors errors, AttachmentFile file) throws Exception + { + if (file == null || file.getSize() == 0) + errors.reject(null, "File is required.File should be tab delimited text file and the number of field vary depending on file type."); + else + { + if (!(file.getFilename().endsWith(".txt"))) + { + errors.reject(null, "File name must end with in .txt.\nFile should be tab delimited text file and the number of field vary depending on file type."); + } + } + if (getActionType() == null || getActionType().length() == 0) + errors.reject(null, "File Type is required"); + if(errors.getErrorCount() > 0) + return false; + return true; + } + } + + public class DCpeptideId extends DataColumn + { + public DCpeptideId(ColumnInfo col) + { + super(col); + } + + public void renderGridCellContents(RenderContext ctx, Writer out) throws IOException + { + ColumnInfo c = getColumnInfo(); + Map rowMap = ctx.getRow(); + if (!PepDBSchema.COLUMN_PEPTIDE_ID.equals(c.getName()) && !PepDBSchema.COLUMN_PARENT_ID.equals(c.getName()) && !PepDBSchema.COLUMN_CHILD_ID.equals(c.getName())) + super.renderGridCellContents(ctx, out); + else + { + Integer peptideId = (Integer) rowMap.get(c.getName()); + try + { + String href = "displayPeptide.view?" + PepDBSchema.COLUMN_PEPTIDE_ID + "=" + peptideId; + out.write(""); + out.write("P"+peptideId); + out.write(""); + out.write("PP" + peptidePoolId); + out.write(""); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + + public String getFormattedValue(RenderContext ctx) { + StringBuilder sb = new StringBuilder("PP"); + sb.append(super.getFormattedValue(ctx)); + return sb.toString(); + } + + public Object getDisplayValue(RenderContext ctx) + { + ColumnInfo c = getColumnInfo(); + Map rowMap = ctx.getRow(); + if (!PepDBSchema.COLUMN_PEPTIDE_POOL_ID.equals(c.getName())) + return super.getValue(ctx); + else + { + Integer peptidePoolId = (Integer) rowMap.get(c.getName()); + try + { + return ("PP" + peptidePoolId); + } + catch (Exception e) + { + e.printStackTrace(); + return "EXPORT/OUTPUT ERROR"; + } + } + } + + public Class getValueClass() + { + return String.class; + } + + public Class getDisplayValueClass() + { + return String.class; + } + } + + public class DCparentPoolId extends DataColumn + { + public DCparentPoolId(ColumnInfo col) + { + super(col); + } + + public void renderGridCellContents(RenderContext ctx, Writer out) throws IOException + { + ColumnInfo c = getColumnInfo(); + Map rowMap = ctx.getRow(); + if (!PepDBSchema.COLUMN_PARENT_POOL_ID.equals(c.getName())) + super.renderGridCellContents(ctx, out); + else + { + Integer parentPoolId = (Integer) rowMap.get(c.getName()); + try + { + String href = "displayPeptidePoolInformation.view?" + PepDBSchema.COLUMN_PEPTIDE_POOL_ID + "=" + parentPoolId; + if(parentPoolId != null) + { + out.write(""); + out.write("PP" + parentPoolId); + out.write(""); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + + public Object getDisplayValue(RenderContext ctx) + { + ColumnInfo c = getColumnInfo(); + Map rowMap = ctx.getRow(); + if (!PepDBSchema.COLUMN_PARENT_POOL_ID.equals(c.getName())) + return super.getValue(ctx); + else + { + Integer parentPoolId = (Integer) rowMap.get(c.getName()); + try + { + if (parentPoolId != null) + return ("PP" + parentPoolId); + else + return null; + } + catch (Exception e) + { + e.printStackTrace(); + return "EXPORT/OUTPUT ERROR"; + } + } + } + + public Class getValueClass() + { + return String.class; + } + + public Class getDisplayValueClass() + { + return String.class; + } + } + + public static Integer validateInteger(String value) + { + try{ + Integer intValue = new Integer(value); + return intValue; + } + catch(NumberFormatException e){return null;} + } + + public static String toLZ(String s) + { + if (s.length() > 6) return s.substring(0, 6); + else if (s.length() < 6) // pad on left with zeros + return "000000000000000000000000000".substring(0, 6 - s.length()) + s; + else return s; + } + + public static java.util.Date isValidDate(String sDateIn) { + + try { + java.util.Date dDate = new java.util.Date(DateUtil.parseDateTime(sDateIn)); + return dDate; + } catch (ConversionException x) { + return null; + } + } + + public static Float validateFloat(String value) + { + try{ + Float floatValue = new Float(value); + return floatValue; + } + catch(NumberFormatException e){return null;} + } + + +} diff --git a/src/org/scharp/atlas/pepdb/PepDBContainerListener.java b/src/org/scharp/atlas/pepdb/PepDBContainerListener.java new file mode 100644 index 00000000..437c0dd0 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/PepDBContainerListener.java @@ -0,0 +1,39 @@ +package org.scharp.atlas.pepdb; + +import org.labkey.api.data.ContainerManager.ContainerListener; +import org.labkey.api.data.Container; +import org.labkey.api.security.User; +import org.apache.log4j.Logger; + +import java.beans.PropertyChangeEvent; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Mar 16, 2009 + * Time: 10:18:45 AM + * To change this template use File | Settings | File Templates. + */ +public class PepDBContainerListener implements ContainerListener +{ + + private static final Logger _log = Logger.getLogger(PepDBContainerListener.class); + + public void containerCreated(Container c, User user) + { + } + + public void containerDeleted(Container c, User user) + { + } + + @Override + public void containerMoved(Container c, Container oldParent, User user) + { + } + + public void propertyChange(PropertyChangeEvent evt) + { + } + +} diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java new file mode 100644 index 00000000..04771153 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -0,0 +1,1299 @@ +package org.scharp.atlas.pepdb; + +import org.labkey.api.action.*; +import org.labkey.api.view.*; +import org.labkey.api.data.*; +import org.labkey.api.query.FieldKey; +import org.labkey.api.security.RequiresPermissionClass; +import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.security.permissions.UpdatePermission; +import org.labkey.api.attachments.AttachmentFile; +import org.labkey.api.query.QuerySettings; +import org.apache.log4j.Logger; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.validation.BindException; +import org.springframework.validation.Errors; +import org.springframework.beans.PropertyValues; +import org.scharp.atlas.pepdb.model.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.List; +import java.util.ArrayList; +import java.util.LinkedList; +import java.sql.SQLException; +import java.io.IOException; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Jul 6, 2009 + * Time: 12:19:21 PM + * To change this template use File | Settings | File Templates. + */ +public class PepDBController extends PepDBBaseController +{ + private final static Logger _log = Logger.getLogger(PepDBController.class); + private static DefaultActionResolver _actionResolver = + new DefaultActionResolver(PepDBController.class); + + private static final String JSP_PATH = "/org/scharp/atlas/pepdb/view/"; + private static final String PAGE_INDEX = "index.jsp"; + private static final String PAGE_PEPTIDE_GROUP_SELECT = "peptideGroupSelect.jsp"; + private static final String PAGE_IMPORT_PEPTIDES = "importPeptides.jsp"; + + public PepDBController() + { + setActionResolver(_actionResolver); + } + + public ActionURL peptideURL(String action) + { + Container c = getViewContext().getContainer(); + return new ActionURL("PepDB", action, c); + } + + protected HttpServletRequest getRequest() + { + return getViewContext().getRequest(); + } + + protected HttpServletResponse getResponse() + { + return getViewContext().getResponse(); + } + + @RequiresPermissionClass(ReadPermission.class) + public class BeginAction extends SimpleViewAction + { + public ModelAndView getView(DisplayPeptideForm form, BindException errors) throws Exception + { + JspView v = new JspView(JSP_PATH + PAGE_INDEX, form, errors); + return v; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Begin", peptideURL("begin")); + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class SearchForPeptidesAction extends FormViewAction + { + public ModelAndView getView(PeptideQueryForm form, boolean reshow, BindException errors) throws Exception + { + ViewContext ctx = getViewContext(); + HttpSession session = ctx.getRequest().getSession(true); + PeptideQueryForm form1 = (PeptideQueryForm) session.getAttribute("QUERY_FORM"); + if (form1 != null && form.getQueryKey() == null) + form = form1; + JspView v = new JspView(JSP_PATH + PAGE_PEPTIDE_GROUP_SELECT, form, errors); + return v; + } + + public boolean handlePost(PeptideQueryForm form, BindException errors) throws Exception + { + String actionType = getRequest().getParameter("action_type"); + if (actionType != null && actionType.equals("Get Peptides")) + return true; + else + return false; + } + + public void validateCommand(PeptideQueryForm form, Errors errors) + { + return; + } + + public ActionURL getSuccessURL(PeptideQueryForm form) + { + ActionURL urlTest = new ActionURL(GetPeptidesAction.class, getContainer()); + urlTest.addParameter("queryKey", form.getQueryKey()); + urlTest.addParameter("queryValue", form.getQueryValue()); + if (form.getQueryKey() != null && form.getQueryKey().equals(PepDBSchema.COLUMN_PROTEIN_CAT_ID)) + { + if (form.getAAStart() != null) + urlTest.addParameter("AAStart", form.getAAStart()); + if (form.getAAEnd() != null) + urlTest.addParameter("AAEnd", form.getAAEnd()); + } + if (form.getQueryKey() != null && form.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)) + { + if (form.getLabId() != null) + urlTest.addParameter("labId", form.getLabId()); + } + return urlTest; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Search For Peptides By Criteria", peptideURL("searchForPeptides")); + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class GetPeptidesAction extends SimpleViewAction + { + public ModelAndView getView(PeptideQueryForm form, BindException errors) throws Exception + { + if (!form.validate(errors)) + { + JspView v = new JspView(JSP_PATH + PAGE_PEPTIDE_GROUP_SELECT, form, errors); + return v; + } + PropertyValues pv = this.getPropertyValues(); + ViewContext ctx = getViewContext(); + HttpSession session = ctx.getRequest().getSession(true); + session.setAttribute("QUERY_FORM", form); + GridView gridView = new GridView(new DataRegion(), (BindException) null); + if (form.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)) + { + gridView = getGridViewByGroup(form, pv); + } + if (form.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_POOL_ID)) + { + gridView = getGridViewByPool(form, pv); + } + if (form.getQueryKey().equals(PepDBSchema.COLUMN_PROTEIN_CAT_ID)) + { + gridView = getGridViewByProtein(form, pv); + } + if (form.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE)) + { + gridView = getGridViewBySequence(form, pv); + } + if (form.getQueryKey().equals(PepDBSchema.COLUMN_PARENT_SEQUENCE)) + { + gridView = getGridViewByParent(form, pv); + } + if (form.getQueryKey().equals(PepDBSchema.COLUMN_CHILD_SEQUENCE)) + { + gridView = getGridViewByChild(form, pv); + } + if (gridView == null) + { + HttpView.redirect(new ActionURL(SearchForPeptidesAction.class, getContainer())); + } + return gridView; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Get Peptides By Criteria", peptideURL("getPeptides")); + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class DisplayPeptideAction extends SimpleViewAction + { + public ModelAndView getView(DisplayPeptideForm form, BindException errors) throws Exception + { + String pepId = form.getPeptide_id(); + if (pepId == null || pepId.length() == 0 + || (!pepId.trim().toUpperCase().startsWith("P") && validateInteger(pepId.trim()) == null) + || (pepId.trim().toUpperCase().startsWith("P") && validateInteger(pepId.trim().substring(1)) == null)) + { + errors.reject(null, "Peptide Id is required and It has to be an Integer with or without prefix 'P'."); + JspView v = new JspView(JSP_PATH + PAGE_INDEX, form, errors); + return v; + } + if (pepId.trim().toUpperCase().startsWith("P") && validateInteger(pepId.trim().substring(1)) != null) + pepId = pepId.trim().substring(1); + Peptides p = PepDBManager.getPeptideById(Integer.parseInt(pepId)); + if (p == null) + { + errors.reject(null, "Peptide Id not found in the database."); + JspView v = new JspView(JSP_PATH + PAGE_INDEX, form, errors); + return v; + } + _log.debug("DisplayPeptideForm: " + form.toString()); + VBox box = new VBox(); + PeptideQueryForm queryform = new PeptideQueryForm(); + queryform.setQueryValue(pepId); + DetailsView dataView = getPeptideDetailsView(queryform, p); + if (!p.isChild()) + { + dataView.getDataRegion().getDisplayColumn("optimal_epitope_list_id").setVisible(false); + dataView.getDataRegion().getDisplayColumn("hla_restriction").setVisible(false); + } + box.addView(dataView); + JspView detailsView = new JspView(JSP_PATH + "peptideDetails.jsp", queryform, errors); + box.addView(detailsView); + PropertyValues pv = this.getPropertyValues(); + if (p.isParent()) + { + PeptideQueryForm parentform = new PeptideQueryForm(); + parentform.setQueryValue((pepId)); + GridView gvChildren = getGridViewByParentId(parentform, pv); + box.addView(gvChildren); + } + if (p.isChild()) + { + PeptideQueryForm childform = new PeptideQueryForm(); + childform.setQueryValue((pepId)); + GridView gvParents = getGridViewByChildId(childform, pv); + box.addView(gvParents); + } + return box; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Display Peptide Details", peptideURL("displayPeptide")); + } + } + + @RequiresPermissionClass(UpdatePermission.class) + public class EditPeptideAction extends FormViewAction + { + public ModelAndView getView(PeptideForm form, boolean reshow, BindException errors) throws Exception + { + Peptides p = PepDBManager.getPeptideById(form.getBean().getPeptide_id()); + UpdateView uView = new UpdateView(form, errors); + ButtonBar bb = new ButtonBar(); + //bb.add(new ActionButton("editPeptide.post","Save Changes")); + //ActionURL editPeptideUrl = new ActionURL(EditPeptideAction.class, getContainer()); + + bb.add(new ActionButton(new ActionURL(EditPeptideAction.class, getContainer()), "Save Changes")); + + ActionURL backURL = new ActionURL(DisplayPeptideAction.class, getContainer()); + backURL.addParameter(PepDBSchema.COLUMN_PEPTIDE_ID, form.getBean().getPeptide_id()); + ActionButton cancelButton = new ActionButton(backURL, "Cancel"); + cancelButton.setActionType(ActionButton.Action.LINK); + bb.add(cancelButton); + uView.getDataRegion().setButtonBar(bb); + if (!p.isChild()) + { + uView.getDataRegion().getDisplayColumn("optimal_epitope_list_id").setVisible(false); + uView.getDataRegion().getDisplayColumn("hla_restriction").setVisible(false); + } + uView.setTitle("Update Peptide data for : P" + form.getBean().getPeptide_id()); + return uView; + } + + public boolean handlePost(PeptideForm form, BindException errors) throws Exception + { + Peptides bean = form.getBean(); + Peptides dbBean = PepDBManager.getPeptideById(bean.getPeptide_id()); + bean.setCreated(dbBean.getCreated()); + bean.setCreatedBy(dbBean.getCreatedBy()); + PepDBManager.updatePeptide(getUser(), bean); + return true; + } + + public void validateCommand(PeptideForm form, Errors errors) + { + Peptides bean = form.getBean(); + if (bean.isPeptide_flag() && (bean.getPeptide_notes() == null || bean.getPeptide_notes().length() == 0)) + errors.reject(null, "If a peptide is flagged then you must enter Peptide Flag Reason."); + if (!bean.isPeptide_flag() && bean.getPeptide_notes() != null && bean.getPeptide_notes().trim().length() != 0) + errors.reject(null, "If a peptide is not flagged then Peptide Flag Reason must be blank."); + } + + public ActionURL getSuccessURL(PeptideForm form) + { + ActionURL url = new ActionURL(DisplayPeptideAction.class, getContainer()); + url.addParameter(PepDBSchema.COLUMN_PEPTIDE_ID, form.getBean().getPeptide_id()); + return url; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Edit Peptide", peptideURL("editPeptide")); + } + } + + @RequiresPermissionClass(UpdatePermission.class) + public class EditPeptidePoolAction extends FormViewAction + { + public ModelAndView getView(PeptidePoolForm form, boolean reshow, BindException errors) throws Exception + { + PeptidePool p = PepDBManager.getPeptidePoolByID(form.getBean().getPeptide_pool_id()); + UpdateView uView = new UpdateView(form, errors); + ButtonBar bb = new ButtonBar(); + bb.add(new ActionButton(new ActionURL(EditPeptidePoolAction.class, getContainer()), "Save Changes")); + //bb.add(new ActionButton("editPeptidePool.post","Save Changes")); + ActionURL backURL = new ActionURL(DisplayPeptidePoolInformationAction.class, getContainer()); + backURL.addParameter(PepDBSchema.COLUMN_PEPTIDE_POOL_ID, form.getBean().getPeptide_pool_id()); + ActionButton cancelButton = new ActionButton(backURL, "Cancel"); + cancelButton.setActionType(ActionButton.Action.LINK); + bb.add(cancelButton); + uView.getDataRegion().setButtonBar(bb); + uView.setTitle("Update Peptide Pool data for : PP" + form.getBean().getPeptide_pool_id()); + return uView; + } + + public boolean handlePost(PeptidePoolForm form, BindException errors) throws Exception + { + PeptidePool bean = form.getBean(); + PeptidePool dbBean = PepDBManager.getPeptidePoolByID(bean.getPeptide_pool_id()); + bean.setCreated(dbBean.getCreated()); + bean.setCreatedBy(dbBean.getCreatedBy()); + PepDBManager.updatePeptidePool(getUser(), bean); + return true; + } + + public void validateCommand(PeptidePoolForm form, Errors errors) + { + + } + + public ActionURL getSuccessURL(PeptidePoolForm form) + { + ActionURL url = new ActionURL(DisplayPeptidePoolInformationAction.class, getContainer()); + url.addParameter(PepDBSchema.COLUMN_PEPTIDE_POOL_ID, form.getBean().getPeptide_pool_id()); + return url; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Edit Peptide Pool", peptideURL("editPeptidePool")); + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class ShowAllPeptideGroupsAction extends SimpleViewAction + { + public ModelAndView getView(PeptideQueryForm form, BindException errors) throws Exception + { + TableInfo tableInfo = PepDBSchema.getInstance().getTableInfoPeptideGroups(); + PropertyValues pv = this.getPropertyValues(); + form.setTInfo(tableInfo); + List columns = tableInfo.getColumns("peptide_group_name,pathogen_id,seq_ref,clade_id,pep_align_ref_id,group_type_id,createdby,created,modifiedby,modified"); + form.setCInfo(columns); + form.setSort(new Sort(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)); + form.setMessage("AllPeptideGroups"); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); + ActionURL insertUrl = new ActionURL(InsertPeptideGroupAction.class, getContainer()); + ActionButton insert = new ActionButton(insertUrl, "Insert New Group"); + insert.setActionType(ActionButton.Action.LINK); + rgn.getButtonBar(DataRegion.MODE_GRID).add(insert); + DisplayColumn col = rgn.getDisplayColumn(PepDBSchema.COLUMN_PEPTIDE_GROUP_NAME); + ActionURL displayAction = new ActionURL(DisplayPeptideGroupInformationAction.class, getContainer()); + col.setURL(displayAction.toString() + "?" + PepDBSchema.COLUMN_PEPTIDE_GROUP_ID + "=${" + PepDBSchema.COLUMN_PEPTIDE_GROUP_ID + "}"); + GridView gridView = new GridView(rgn, errors); + gridView.setTitle("All the Peptide Groups in the System are : "); + return gridView; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Display All Peptide Groups", peptideURL("showAllPeptideGroups")); + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class DisplayPeptideGroupInformationAction extends SimpleViewAction + { + public ModelAndView getView(PeptideAndGroupForm form, BindException errors) throws Exception + { + _log.debug("PeptideAndGroupForm: " + form.toString()); + PeptideGroup pg = PepDBManager.getPeptideGroupByID(Integer.parseInt(form.getPeptide_group_id())); + DataRegion rgn1 = new DataRegion(); + TableInfo tableInfo1 = PepDBSchema.getInstance().getTableInfoPeptideGroups(); + rgn1.setColumns(tableInfo1.getColumns("peptide_group_id,peptide_group_name,pathogen_id,seq_ref,clade_id,pep_align_ref_id,group_type_id,createdby,created,modifiedby,modified")); + ButtonBar buttonBar1 = getButtonBar(); + ActionURL backUrl = new ActionURL(ShowAllPeptideGroupsAction.class, getContainer()); + ActionButton goBack = new ActionButton("List All Groups", backUrl); + buttonBar1.add(goBack); + if (getContainer().hasPermission(getUser(), UpdatePermission.class)) + { + ActionURL updateGroupUrl = new ActionURL(UpdatePeptideGroupAction.class, getContainer()); + updateGroupUrl.addParameter(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID, form.getPeptide_group_id()); + ActionButton updateGroupButton = new ActionButton("Update Group Data", updateGroupUrl); + buttonBar1.add(updateGroupButton); + } + rgn1.setButtonBar(buttonBar1, DataRegion.MODE_DETAILS); + DetailsView dataView = new DetailsView(rgn1, new Object[]{form.getPeptide_group_id()}); + dataView.setTitle("Group Information from peptide_group table for group : " + pg.getPeptide_group_name()); + VBox vBox = new VBox(); + vBox.addView(dataView); + PropertyValues pv = this.getPropertyValues(); + PeptideQueryForm form2 = new PeptideQueryForm(); + form2.setQueryValue(form.getPeptide_group_id()); + GridView gv = getGridViewByGroup(form2, pv); + vBox.addView(gv); + return vBox; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Display Peptide Group Details", peptideURL("displayPeptideGroupInformation")); + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class ShowAllPeptidePoolsAction extends SimpleViewAction + { + public ModelAndView getView(PeptideQueryForm form, BindException errors) throws Exception + { + TableInfo tableInfo = PepDBSchema.getInstance().getTableInfoViewPoolDetails(); + PropertyValues pv = this.getPropertyValues(); + form.setTInfo(tableInfo); + List columns = tableInfo.getColumns("peptide_pool_id,peptide_pool_name,pool_type_desc,parent_pool_id,parent_pool_name,matrix_peptide_pool_id,comment,archived,createdby,created,modifiedby,modified"); + form.setCInfo(columns); + form.setSort(new Sort(PepDBSchema.COLUMN_PEPTIDE_POOL_ID)); + form.setMessage("AllPeptidePools"); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); + ActionURL importUrl = new ActionURL(ImportPeptidePoolsAction.class, getContainer()); + ActionButton importB = new ActionButton(importUrl, "Import New Pools"); + importB.setActionType(ActionButton.Action.LINK); + rgn.getButtonBar(DataRegion.MODE_GRID).add(importB); + GridView gridView = new GridView(rgn, errors); + gridView.setTitle("All the Peptide Pools in the System are : "); + return gridView; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Display All Peptide Pools", peptideURL("showAllPeptidePools")); + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class DisplayPeptidePoolInformationAction extends SimpleViewAction + { + public ModelAndView getView(PeptideAndPoolForm form, BindException errors) throws Exception + { + _log.debug("PeptideAndPoolForm: " + form.toString()); + PeptideQueryForm queryform = new PeptideQueryForm(); + TableInfo tableInfo = PepDBSchema.getInstance().getTableInfoPeptidePools(); + PropertyValues pv = this.getPropertyValues(); + queryform.setTInfo(tableInfo); + queryform.setCInfo(tableInfo.getColumns("peptide_pool_id,peptide_pool_name,pool_type_id,parent_pool_id,matrix_peptide_pool_id,comment,archived,createdby,created,modifiedby,modified")); + DataRegion rgn = getDataRegion(getContainer(), queryform); + rgn.setShowBorders(true); + rgn.setShadeAlternatingRows(true); + ButtonBar buttonBar = getButtonBar(); + ActionURL editUrl = new ActionURL(EditPeptidePoolAction.class, getContainer()); + editUrl.addParameter(PepDBSchema.COLUMN_PEPTIDE_POOL_ID, form.getPeptide_pool_id()); + ActionButton editButton = new ActionButton("Edit Peptide Pool", editUrl); + buttonBar.add(editButton); + rgn.setButtonBar(buttonBar, DataRegion.MODE_DETAILS); + DetailsView dataView = new DetailsView(rgn, new Object[]{form.getPeptide_pool_id()}); + dataView.setTitle("Pool Information from peptide_pool table for pool : PP" + form.getPeptide_pool_id()); + queryform.setQueryValue(form.getPeptide_pool_id()); + GridView gv = getGridViewByPool(queryform, pv); + + // Because this page has potentially two data grids, we must give each grid a different + // action url (in order to distinguish the two tables). + gv.getDataRegion().setButtonBar(getGridButtonbarPeptidesInPool(pv)); + VBox box = new VBox(); + box.addView(dataView); + box.addView(gv); + if (PepDBManager.getChildrenPools(form.getPeptide_pool_id()) != null) + { + GridView childPools = getGridViewByParentPool(queryform, pv); + childPools.getDataRegion().setButtonBar(getGridButtonbarPoolsInPool(pv)); + box.addView(childPools); + } + return box; + + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Display Peptide Pool Details", peptideURL("displayPeptidePoolInformation")); + } + } + + @RequiresPermissionClass(UpdatePermission.class) + public class UpdatePeptideGroupAction extends FormViewAction + { + public ModelAndView getView(PeptideGroupForm form, boolean reshow, BindException errors) throws Exception + { + PeptideGroup pg = PepDBManager.getPeptideGroupByID(form.getBean().getPeptide_group_id()); + UpdateView uView = new UpdateView(form, errors); + ButtonBar bb = new ButtonBar(); + //bb.add(new ActionButton("updatePeptideGroup.post","Save Changes")); + bb.add(new ActionButton(new ActionURL(UpdatePeptideGroupAction.class, getContainer()), "Save Changes")); + ActionURL backURL = new ActionURL(DisplayPeptideGroupInformationAction.class, getContainer()); + backURL.addParameter(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID, form.getBean().getPeptide_group_id()); + ActionButton cancelButton = new ActionButton(backURL, "Cancel"); + cancelButton.setActionType(ActionButton.Action.LINK); + bb.add(cancelButton); + uView.getDataRegion().setButtonBar(bb); + if (pg.getPeptide_group_name().equalsIgnoreCase("Optimal Epitopes")) + uView.getDataRegion().getDisplayColumn("peptide_group_name").setVisible(false); + uView.setTitle("Update Peptide Group data for : " + pg.getPeptide_group_name()); + return uView; + } + + public boolean handlePost(PeptideGroupForm form, BindException errors) throws Exception + { + PeptideGroup bean = form.getBean(); + PeptideGroup dbBean = PepDBManager.getPeptideGroupByID(bean.getPeptide_group_id()); + bean.setCreated(dbBean.getCreated()); + bean.setCreatedBy(dbBean.getCreatedBy()); + PepDBManager.updatePeptideGroup(getUser(), bean); + return true; + } + + public void validateCommand(PeptideGroupForm form, Errors errors) + { + try + { + form.validate(errors); + form.validateName(errors); + } + catch (SQLException e) + { + errors.reject(null, "There's something wrong with database when trying to get all the existing groups."); + } + } + + public ActionURL getSuccessURL(PeptideGroupForm form) + { + ActionURL url = new ActionURL(DisplayPeptideGroupInformationAction.class, getContainer()); + url.addParameter(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID, form.getBean().getPeptide_group_id()); + return url; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Update Peptide Group", peptideURL("updatePeptideGroup")); + } + } + + @RequiresPermissionClass(UpdatePermission.class) + public class InsertPeptideGroupAction extends FormViewAction + { + public ModelAndView getView(PeptideGroupForm form, boolean reshow, BindException errors) throws Exception + { + ButtonBar bb = new ButtonBar(); + //bb.add(new ActionButton("insertPeptideGroup.post","Add New Peptide Group")); + bb.add(new ActionButton(new ActionURL(InsertPeptideGroupAction.class, getContainer()), "Add New Peptide Group")); + ActionURL backURL = new ActionURL(BeginAction.class, getContainer()); + ActionButton goBack = new ActionButton(backURL, "Cancel"); + goBack.setActionType(ActionButton.Action.LINK); + bb.add(goBack); + PeptideQueryForm qForm = new PeptideQueryForm(); + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPeptideGroups(); + qForm.setTInfo(tInfo); + qForm.setCInfo(tInfo.getColumns()); + DataRegion rgn = getDataRegion(getContainer(), qForm); + rgn.setButtonBar(bb); + InsertView iView = new InsertView(rgn, form, errors); + return iView; + } + + public boolean handlePost(PeptideGroupForm form, BindException errors) throws Exception + { + PeptideGroup group = form.getBean(); + group = PepDBManager.insertGroup(getContainer(), getUser(), group); + form.setBean(group); + return true; + } + + public void validateCommand(PeptideGroupForm form, Errors errors) + { + try + { + form.validate(errors); + form.validateName(errors); + } + catch (SQLException e) + { + errors.reject(null, "There's something wrong with database when trying to get all the existing groups."); + } + } + + public ActionURL getSuccessURL(PeptideGroupForm form) + { + ActionURL url = new ActionURL(DisplayPeptideGroupInformationAction.class, getContainer()); + url.addParameter(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID, form.getBean().getPeptide_group_id().toString()); + return url; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Insert Peptide Group", peptideURL("insertPeptideGroup")); + } + } + + @RequiresPermissionClass(UpdatePermission.class) + public class ImportPeptidesAction extends FormViewAction + { + private List resultPeptides = new LinkedList(); + + public ModelAndView getView(FileForm form, boolean reshow, BindException errors) throws Exception + { + JspView v = new JspView(JSP_PATH + PAGE_IMPORT_PEPTIDES, form, errors); + return v; + } + + public boolean handlePost(FileForm form, BindException errors) throws Exception + { + try + { + List importFiles = getAttachmentFileList(); + AttachmentFile importFile = null; + for (AttachmentFile a : importFiles) + { + if (a != null && a.getSize() != 0) + importFile = a; + } + if (!form.validate(errors, importFile)) + return false; + if (isPost() && form.getActionType().equalsIgnoreCase(("PEPTIDES"))) + { + PeptideImporter importer = new PeptideImporter(); + if (!importer.process(getViewContext().getUser(), importFile, errors, resultPeptides)) + return false; + return true; + } + } + catch (Exception e) + { + e.printStackTrace(); + _log.error(e.getMessage(), e); + errors.reject(null, "There was a problem uploading File: " + e.getMessage()); + return false; + } + return true; + } + + public void validateCommand(FileForm form, Errors errors) + { + + } + + public ActionURL getSuccessURL(FileForm form) + { + ActionURL url = new ActionURL(DisplayResultAction.class, getContainer()); + url.addParameter("message", "The file has been successfully imported."); + return url; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Import Peptides", peptideURL("importPeptides")); + } + } + + @RequiresPermissionClass(UpdatePermission.class) + public class DisplayResultAction extends SimpleViewAction + { + public ModelAndView getView(FileForm form, BindException errors) throws Exception + { + PeptideQueryForm form1 = new PeptideQueryForm(); + PropertyValues pv = this.getPropertyValues(); + GridView v = getGridViewByLastImport(form1, pv); + return v; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Display Results Page", peptideURL("displayResult")); + } + } + + @RequiresPermissionClass(UpdatePermission.class) + public class ImportPeptidePoolsAction extends FormViewAction + { + ActionURL url = null; + + public ModelAndView getView(FileForm form, boolean reshow, BindException errors) throws Exception + { + JspView v = new JspView(JSP_PATH + "importPools.jsp", form, errors); + return v; + } + + public boolean handlePost(FileForm form, BindException errors) throws Exception + { + try + { + List importFiles = getAttachmentFileList(); + AttachmentFile importFile = null; + for (AttachmentFile a : importFiles) + { + if (a != null && a.getSize() != 0) + importFile = a; + } + if (!form.validate(errors, importFile)) + return false; + + PoolImporter importer = new PoolImporter(); + if (!importer.process(getViewContext().getUser(), form, importFile, errors)) + return false; + url = new ActionURL(BeginAction.class, getContainer()); + return true; + } + catch (Exception e) + { + e.printStackTrace(); + _log.error(e.getMessage(), e); + errors.reject(null, "There was a problem uploading File: " + e.getMessage()); + return false; + } + } + + public void validateCommand(FileForm form, Errors errors) + { + + } + + public ActionURL getSuccessURL(FileForm form) + { + url.addParameter("message", "The file has been successfully imported."); + return url; + } + + public NavTree appendNavTrail(NavTree root) + { + return root.addChild("Import Peptide Pools", peptideURL("importPeptidePools")); + } + } + + + @RequiresPermissionClass(ReadPermission.class) + public abstract class PeptideExcelExportAction extends ExportAction + { + public void printExcel(Object bean, HttpServletResponse response, BindException errors, PeptideQueryForm form) throws Exception + { + try + { + RenderContext context = new RenderContext(getViewContext()); + DataRegion rgn = getDataRegion(getContainer(), form); + context.setBaseFilter(form.getFilter()); + context.setBaseSort(form.getSort()); + ExcelWriter ew = new ExcelWriter(rgn.getResultSet(context), rgn.getDisplayColumns()); + ew.setAutoSize(true); + ew.setFilenamePrefix(form.getMessage()); + ew.setSheetName(form.getMessage()); + ew.setFooter(form.getMessage()); + ew.write(getResponse()); + } + catch (SQLException e) + { + _log.error("export: " + e); + } + catch (IOException e) + { + _log.error("export: " + e); + } + catch (Exception e) + { + _log.error("export: " + e); + } + + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class PeptidesInPoolExcelExportAction extends PeptideExcelExportAction + { + public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception + { + PeptideQueryForm form = new PeptideQueryForm(); + PropertyValues pv = this.getPropertyValues(); + form.setQueryValue((String)pv.getPropertyValue("peptide_pool_id").getValue()); + getGridViewByPool(form, pv); + printExcel(bean, response, errors, form); + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class PoolsInPoolExcelExportAction extends PeptideExcelExportAction + { + public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception + { + PeptideQueryForm form = new PeptideQueryForm(); + PropertyValues pv = this.getPropertyValues(); + form.setQueryValue((String)pv.getPropertyValue("peptide_pool_id").getValue()); + getGridViewByParentPool(form, pv); + printExcel(bean, response, errors, form); + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class PeptideDefaultExcelExportAction extends PeptideExcelExportAction + { + public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception + { + ViewContext ctx = getViewContext(); + HttpSession session = ctx.getRequest().getSession(); + PeptideQueryForm form = (PeptideQueryForm) session.getAttribute("PEPTIDE_QUERY_FORM"); + _log.error("Form " + form.getMessage() + " had filter : " + form.getFilter()); + printExcel(bean, response, errors, form); + } + } + + + + @RequiresPermissionClass(ReadPermission.class) + public abstract class PeptideTextExportAction extends ExportAction + { + public void printText(Object bean, HttpServletResponse response, BindException errors, PeptideQueryForm form) throws Exception + { + try + { + ViewContext ctx = getViewContext(); + RenderContext context = new RenderContext(getViewContext()); + DataRegion rgn = getDataRegion(getContainer(), form); + context.setBaseFilter(form.getFilter()); + context.setBaseSort(form.getSort()); + TSVGridWriter tsv = new TSVGridWriter(rgn.getResultSet(context), rgn.getDisplayColumns()); + tsv.setFilenamePrefix(form.getMessage()); + tsv.write(getResponse()); + } + catch (SQLException e) + { + _log.error("export: " + e); + } + catch (IOException e) + { + _log.error("export: " + e); + } + catch (Exception e) + { + _log.error("export: " + e); + } + } + } + @RequiresPermissionClass(ReadPermission.class) + public class PeptidesInPoolTextExportAction extends PeptideTextExportAction + { + public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception + { + PeptideQueryForm form = new PeptideQueryForm(); + PropertyValues pv = this.getPropertyValues(); + form.setQueryValue((String)pv.getPropertyValue("peptide_pool_id").getValue()); + getGridViewByPool(form, pv); + printText(bean, response, errors, form); + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class PoolsInPoolTextExportAction extends PeptideTextExportAction + { + public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception + { + PeptideQueryForm form = new PeptideQueryForm(); + PropertyValues pv = this.getPropertyValues(); + form.setQueryValue((String)pv.getPropertyValue("peptide_pool_id").getValue()); + getGridViewByParentPool(form, pv); + printText(bean, response, errors, form); + } + } + + @RequiresPermissionClass(ReadPermission.class) + public class PeptideDefaultTextExportAction extends PeptideTextExportAction + { + public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception + { + ViewContext ctx = getViewContext(); + HttpSession session = ctx.getRequest().getSession(); + PeptideQueryForm form = (PeptideQueryForm) session.getAttribute("PEPTIDE_QUERY_FORM"); + printText(bean, response, errors, form); + } + } + + + + protected GridView getGridViewByLastImport(PeptideQueryForm form, PropertyValues pv) throws Exception + { + //PeptideGroup pg = PepDBManager.getPeptideGroupByID(Integer.parseInt(form.getQueryValue())); + TableInfo tableInfo = PepDBSchema.getInstance() + .getTableInfoViewGroupPeptides(); + form.setTInfo(tableInfo); + //_log.debug("Creating a Filter for peptideGroup." + PepDBSchema.COLUMN_PEPTIDE_GROUP_ID + ": " + form); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_IN_CURRENT_FILE, true); + Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_GROUP_ASSIGNMENT_ID); + form.setFilter(sFilter); + form.setCInfo(tableInfo.getColumns("peptide_id,peptide_sequence,protein_cat_id,peptide_group_name,peptide_id_in_group,pathogen_id," + + "sequence_length,amino_acid_start_pos,amino_acid_end_pos,child,parent,optimal_epitope_list_id,hla_restriction,frequency_number,frequency_number_date")); + + form.setSort(sort); + form.setMessage("Peptides_IN_Last_File"); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); + GridView gridView = new GridView(rgn, (BindException) null); + gridView.setFilter(sFilter); + gridView.setSort(sort); + return gridView; + } + + protected GridView getGridViewByGroup(PeptideQueryForm form, PropertyValues pv) throws Exception + { + PeptideGroup pg = PepDBManager.getPeptideGroupByID(Integer.parseInt(form.getQueryValue())); + TableInfo tableInfo = PepDBSchema.getInstance() + .getTableInfoViewGroupPeptides(); + form.setTInfo(tableInfo); + _log.debug("Creating a Filter for peptideGroup." + PepDBSchema.COLUMN_PEPTIDE_GROUP_ID + ": " + form); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID, Integer.parseInt(form.getQueryValue())); + if (form.getLabId() != null) + sFilter.addCondition(PepDBSchema.COLUMN_PEPTIDE_ID_IN_GROUP, form.getLabId()); + Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_ID_IN_GROUP); + form.setFilter(sFilter); + if (pg.getPeptide_group_name().equalsIgnoreCase("Optimal Epitopes")) + form.setCInfo(tableInfo.getColumns("peptide_id,peptide_sequence,protein_cat_id,peptide_group_name,peptide_id_in_group,pathogen_id," + + "sequence_length,amino_acid_start_pos,amino_acid_end_pos,child,parent,peptide_flag,peptide_notes,optimal_epitope_list_id,hla_restriction,frequency_number,frequency_number_date")); + else + form.setCInfo(tableInfo.getColumns("peptide_id,peptide_sequence,protein_cat_id,peptide_group_name,peptide_id_in_group,pathogen_id," + + "sequence_length,amino_acid_start_pos,amino_acid_end_pos,child,parent,peptide_flag,peptide_notes,frequency_number,frequency_number_date")); + form.setSort(sort); + form.setMessage("Peptides_IN_Group_" + pg.getPeptide_group_name()); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); + GridView gridView = new GridView(rgn, (BindException) null); + gridView.setFilter(sFilter); + gridView.setSort(sort); + gridView.setTitle( + "There are (" + + PepDBManager.getCount(Integer.parseInt(form.getQueryValue())) + + ") peptides in the '" + pg.getPeptide_group_name() + "' peptide group."); + return gridView; + } + + protected GridView getGridViewByPool(PeptideQueryForm form, PropertyValues pv) throws Exception + { + PeptidePool pp = PepDBManager.getPeptidePoolByID(Integer.parseInt(form.getQueryValue())); + TableInfo tableInfo = PepDBSchema.getInstance() + .getTableInfoViewPoolPeptides(); + form.setTInfo(tableInfo); + _log.debug("Creating a Filter for peptidePool." + PepDBSchema.COLUMN_PEPTIDE_POOL_ID + ": " + form); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PEPTIDE_POOL_ID, Integer.parseInt(form.getQueryValue())); + Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_ID); + form.setFilter(sFilter); + form.setCInfo(tableInfo.getColumns("peptide_id,peptide_sequence,protein_cat_id,peptide_pool_id,peptide_pool_name,pool_type_id," + + "peptide_group_id,peptide_id_in_group,sequence_length,amino_acid_start_pos,amino_acid_end_pos,child,parent,peptide_flag,peptide_notes")); + form.setSort(sort); + form.setMessage("Peptides_IN_Pool_PP" + pp.getPeptide_pool_name()); + DataRegion rgn = getDataRegion(getContainer(), form); + ButtonBar bb = getGridButtonbar(pv); + rgn.setButtonBar(bb, DataRegion.MODE_GRID); + GridView gridView = new GridView(rgn, (BindException) null); + gridView.setFilter(sFilter); + gridView.setSort(sort); + gridView.setTitle( + "The Peptides in the pool " + pp.getPeptide_pool_name() + "(PP" + form.getQueryValue() + ") are : "); + return gridView; + } + + protected GridView getGridViewByParentPool(PeptideQueryForm form, PropertyValues pv) throws Exception + { + PeptidePool pp = PepDBManager.getPeptidePoolByID(Integer.parseInt(form.getQueryValue())); + TableInfo tableInfo = PepDBSchema.getInstance() + .getTableInfoViewPoolDetails(); + form.setTInfo(tableInfo); + _log.debug("Creating a Filter for parent peptidePool." + PepDBSchema.COLUMN_PARENT_POOL_ID + ": " + form); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PARENT_POOL_ID, Integer.parseInt(form.getQueryValue())); + Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_POOL_ID); + form.setFilter(sFilter); + form.setCInfo(tableInfo.getColumns("peptide_pool_id,peptide_pool_name,pool_type_desc,parent_pool_id,parent_pool_name,matrix_peptide_pool_id,comment,archived,createdby,created,modifiedby,modified")); + form.setSort(sort); + form.setMessage("Peptides_WITH_Parent_Pool_PP" + form.getQueryValue()); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); + GridView gridView = new GridView(rgn, (BindException) null); + gridView.setFilter(sFilter); + gridView.setSort(sort); + gridView.setTitle( + "The Sub-Pools of Parent Pool " + pp.getPeptide_pool_name() + "(PP" + form.getQueryValue() + ") are : "); + return gridView; + } + + protected GridView getGridViewBySequence(PeptideQueryForm form, PropertyValues pv) throws Exception + { + TableInfo tableInfo = PepDBSchema.getInstance() + .getTableInfoViewGroupPeptides(); + form.setTInfo(tableInfo); + _log.debug("Creating a Filter for peptideSequence." + PepDBSchema.COLUMN_PEPTIDE_SEQUENCE + ": " + form); + SimpleFilter sFilter = new SimpleFilter(); + sFilter.addWhereClause(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE + " LIKE ?", new Object[]{"%" + (form.getQueryValue() == null || form.getQueryValue().length() == 0 ? "" : form.getQueryValue().toUpperCase()) + "%"}, + FieldKey.fromString(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE)); + Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_ID); + form.setFilter(sFilter); + form.setCInfo(tableInfo.getColumns("peptide_id,peptide_sequence,protein_cat_id,peptide_group_name,peptide_id_in_group,pathogen_id," + + "sequence_length,amino_acid_start_pos,amino_acid_end_pos,child,parent,peptide_flag,peptide_notes,optimal_epitope_list_id,hla_restriction")); + form.setSort(sort); + if (form.getQueryValue() != null && form.getQueryValue().length() != 0) + form.setMessage("Peptides_WITH_Sequence_" + form.getQueryValue()); + else + form.setMessage("All_Peptides_In_DB"); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); + GridView gridView = new GridView(rgn, (BindException) null); + gridView.setFilter(sFilter); + gridView.setSort(sort); + if (form.getQueryValue() != null && form.getQueryValue().length() != 0) + gridView.setTitle( + "The Peptides Containing the Sequence string '" + form.getQueryValue() + "' are : "); + else + gridView.setTitle("All the Peptides in Peptide DB : "); + return gridView; + } + + protected GridView getGridViewByProtein(PeptideQueryForm form, PropertyValues pv) throws Exception + { + ProteinCategory pc = PepDBManager.getProCatByID(Integer.parseInt(form.getQueryValue())); + TableInfo tableInfo = PepDBSchema.getInstance() + .getTableInfoViewGroupPeptides(); + form.setTInfo(tableInfo); + String title = "Peptides in Protein Category " + pc.getProtein_cat_desc(); + _log.debug("Creating a Filter for proteinCategory." + PepDBSchema.COLUMN_PROTEIN_CAT_ID + ": " + form); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PROTEIN_CAT_ID, Integer.parseInt(form.getQueryValue())); + if (form.getAAEnd() == null && form.getAAStart() != null) + { + sFilter.addCondition(PepDBSchema.COLUMN_AMINO_ACID_START_POS, Integer.parseInt(form.getAAStart()), CompareType.GTE); + title = "Peptides in Protein Category " + pc.getProtein_cat_desc() + " and after AAStart " + form.getAAStart(); + } + if (form.getAAEnd() != null && form.getAAStart() == null) + { + sFilter.addCondition(PepDBSchema.COLUMN_AMINO_ACID_END_POS, Integer.parseInt(form.getAAEnd()), CompareType.LTE); + title = "Peptides in Protein Category " + pc.getProtein_cat_desc() + " and before AAEnd " + form.getAAEnd(); + } + if (form.getAAStart() != null && form.getAAEnd() != null) + { + sFilter.addBetween(tableInfo.getColumn(PepDBSchema.COLUMN_AMINO_ACID_START_POS).getFieldKey(), Integer.parseInt(form.getAAStart()), Integer.parseInt(form.getAAEnd())); + sFilter.addBetween(tableInfo.getColumn(PepDBSchema.COLUMN_AMINO_ACID_END_POS).getFieldKey(), Integer.parseInt(form.getAAStart()), Integer.parseInt(form.getAAEnd())); + title = "Peptides in Protein Category " + pc.getProtein_cat_desc() + " and between AAStart " + form.getAAStart() + " and AAEnd " + form.getAAEnd(); + } + Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_ID); + form.setFilter(sFilter); + form.setCInfo(tableInfo.getColumns("peptide_id,peptide_sequence,protein_cat_id,peptide_group_name,peptide_id_in_group,pathogen_id," + + "sequence_length,amino_acid_start_pos,amino_acid_end_pos,child,parent,peptide_flag,peptide_notes,optimal_epitope_list_id,hla_restriction")); + form.setSort(sort); + form.setMessage("Peptides_IN_Protein_" + form.getQueryValue()); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); + GridView gridView = new GridView(rgn, (BindException) null); + gridView.setFilter(sFilter); + gridView.setSort(sort); + gridView.setTitle(title); + return gridView; + } + + protected GridView getGridViewByParentId(PeptideQueryForm form, PropertyValues pv) throws Exception + { + TableInfo tableInfo = PepDBSchema.getInstance() + .getTableInfoViewParentChildDetails(); + form.setTInfo(tableInfo); + _log.debug("Creating a Filter for parentID. : " + form); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PARENT_ID, Integer.parseInt(form.getQueryValue())); + Sort sort = new Sort(PepDBSchema.COLUMN_CHILD_ID); + form.setFilter(sFilter); + form.setCInfo(tableInfo.getColumns("child_id,child_sequence,child_protein,child_group,child_lab_id,child_seq_length," + + "child_aastart,child_aaend,child_peptide_flag,child_peptide_notes,child_optimal_epitope_list_id,child_hla_restriction")); + form.setSort(sort); + form.setMessage("CHILD_Peptides_WITH_Parent_P" + form.getQueryValue()); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); + GridView gridView = new GridView(rgn, (BindException) null); + gridView.setFilter(sFilter); + gridView.setSort(sort); + gridView.setTitle( + "The Child Peptides of Parent peptide ID P" + form.getQueryValue() + " are : "); + return gridView; + } + + protected GridView getGridViewByParent(PeptideQueryForm form, PropertyValues pv) throws Exception + { + TableInfo tableInfo = PepDBSchema.getInstance() + .getTableInfoViewParentChildDetails(); + form.setTInfo(tableInfo); + _log.debug("Creating a Filter for parentSequence. : " + form); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PARENT_SEQUENCE, form.getQueryValue().trim().toUpperCase()); + Sort sort = new Sort(PepDBSchema.COLUMN_CHILD_ID); + form.setFilter(sFilter); + form.setCInfo(tableInfo.getColumns("parent_id,child_id,child_sequence,child_protein,child_group,child_lab_id,child_seq_length," + + "child_aastart,child_aaend,child_peptide_flag,child_peptide_notes,child_optimal_epitope_list_id,child_hla_restriction")); + form.setSort(sort); + form.setMessage("CHILD_Peptides_WITH_Parent_Sequence_" + form.getQueryValue()); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); + GridView gridView = new GridView(rgn, (BindException) null); + gridView.setFilter(sFilter); + gridView.setSort(sort); + gridView.setTitle( + "The Child Peptides of Parent Sequence " + form.getQueryValue() + " are : "); + return gridView; + } + + protected GridView getGridViewByChildId(PeptideQueryForm form, PropertyValues pv) throws Exception + { + TableInfo tableInfo = PepDBSchema.getInstance() + .getTableInfoViewParentChildDetails(); + form.setTInfo(tableInfo); + _log.debug("Creating a Filter for childID. : " + form); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_CHILD_ID, Integer.parseInt(form.getQueryValue())); + Sort sort = new Sort(PepDBSchema.COLUMN_PARENT_ID); + form.setFilter(sFilter); + form.setCInfo(tableInfo.getColumns("parent_id,parent_sequence,parent_protein,parent_group,parent_lab_id," + + "parent_seq_length,parent_aastart,parent_aaend,parent_peptide_flag,parent_peptide_notes")); + form.setSort(sort); + form.setMessage("PARENT_Peptides_WITH_Child_P" + form.getQueryValue()); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); + GridView gridView = new GridView(rgn, (BindException) null); + gridView.setFilter(sFilter); + gridView.setSort(sort); + gridView.setTitle( + "The Parent Peptides of Child peptide ID P" + form.getQueryValue() + " are : "); + return gridView; + } + + protected GridView getGridViewByChild(PeptideQueryForm form, PropertyValues pv) throws Exception + { + TableInfo tableInfo = PepDBSchema.getInstance() + .getTableInfoViewParentChildDetails(); + form.setTInfo(tableInfo); + _log.debug("Creating a Filter for childID. : " + form); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_CHILD_SEQUENCE, form.getQueryValue().trim().toUpperCase()); + Sort sort = new Sort(PepDBSchema.COLUMN_PARENT_ID); + form.setFilter(sFilter); + form.setCInfo(tableInfo.getColumns("child_id,parent_id,parent_sequence,parent_protein,parent_group,parent_lab_id," + + "parent_seq_length,parent_aastart,parent_aaend,parent_peptide_flag,parent_peptide_notes")); + form.setSort(sort); + form.setMessage("PARENT_Peptides_WITH_Child_Sequence_" + form.getQueryValue()); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); + GridView gridView = new GridView(rgn, (BindException) null); + gridView.setFilter(sFilter); + gridView.setSort(sort); + gridView.setTitle( + "The Parent Peptides of Child Sequence " + form.getQueryValue() + " are : "); + return gridView; + } + + protected DetailsView getPeptideDetailsView(PeptideQueryForm form, Peptides p) throws Exception + { + TableInfo tableInfo = PepDBSchema.getInstance().getTableInfoPeptides(); + form.setTInfo(tableInfo); + form.setCInfo(tableInfo.getColumns("peptide_id,peptide_sequence,protein_cat_id,sequence_length,amino_acid_start_pos," + + "amino_acid_end_pos,child,parent,optimal_epitope_list_id,hla_restriction,storage_location,src_file_name,peptide_flag,peptide_notes,createdby,created,modifiedby,modified")); + DataRegion rgn = getDataRegion(getContainer(), form); + rgn.setShowBorders(true); + rgn.setShadeAlternatingRows(true); + ButtonBar buttonBar = new ButtonBar(); + ActionURL homeUrl = new ActionURL(BeginAction.class, getContainer()); + ActionButton homeButton = new ActionButton("Peptide Home", homeUrl); + buttonBar.add(homeButton); + ActionURL editUrl = new ActionURL(EditPeptideAction.class, getContainer()); + editUrl.addParameter(PepDBSchema.COLUMN_PEPTIDE_ID, p.getPeptide_id()); + ActionButton editButton = new ActionButton("Edit Peptide", editUrl); + buttonBar.add(editButton); + rgn.setButtonBar(buttonBar, DataRegion.MODE_DETAILS); + DetailsView dataView = new DetailsView(rgn, new Object[]{p.getPeptide_id()}); + dataView.setTitle("Peptide Detail Information from peptides table for peptide : P" + p.getPeptide_id()); + return dataView; + } + + private DataRegion getDataRegion(Container c, PeptideQueryForm form) throws Exception + { + DataRegion rgn = new DataRegion(); + List columnList = new ArrayList(); + List displayColumnList = new ArrayList(); + + for (ColumnInfo col : form.getCInfo()) + { + if (col != null) + { + columnList.add(col.getName()); + DisplayColumn dc; + + if (PepDBSchema.COLUMN_PEPTIDE_ID.equals(col.getName()) || PepDBSchema.COLUMN_PARENT_ID.equals(col.getName()) || PepDBSchema.COLUMN_CHILD_ID.equals(col.getName())) + { + dc = new DCpeptideId(col); + } + else if (PepDBSchema.COLUMN_PEPTIDE_POOL_ID.equals(col.getName())) + { + dc = new DCpeptidePoolId(col); + } + else if (PepDBSchema.COLUMN_PARENT_POOL_ID.equals(col.getName())) + { + dc = new DCparentPoolId(col); + } + else { + dc = col.getRenderer(); + } + displayColumnList.add(dc); + } + } + rgn.setColumns(form.getCInfo()); + rgn.setDisplayColumns(displayColumnList); + rgn.setShowBorders(true); + rgn.setShadeAlternatingRows(true); + rgn.setMaxRows(Table.ALL_ROWS); + ViewContext ctx = getViewContext(); + HttpSession session = ctx.getRequest().getSession(true); + session.setAttribute("PEPTIDE_QUERY_FORM", form); + + + if (columnList.contains(PepDBSchema.COLUMN_PEPTIDE_ID)) + { + ColumnInfo ci = rgn.getTable().getColumn("peptide_id"); + QuerySettings qs = new QuerySettings(getViewContext(), rgn.getName()); + qs.addAggregates(new Aggregate(ci, Aggregate.Type.COUNT)); + qs.setMaxRows(Table.ALL_ROWS); + rgn.setSettings(qs); + } + return rgn; + } + + private ButtonBar getButtonBar() + { + ButtonBar buttonBar = new ButtonBar(); + ActionURL homeUrl = new ActionURL(BeginAction.class, getContainer()); + ActionButton homeButton = new ActionButton("Peptide Home", homeUrl); + buttonBar.add(homeButton); + ActionURL searchURL = new ActionURL(SearchForPeptidesAction.class, getContainer()); + ActionButton searchButton = new ActionButton(searchURL, "Peptide Search Page"); + buttonBar.add(searchButton); + return buttonBar; + } + + /* + * Returns a ButtonBar whose excel and text buttons will + * render whatever grid view is set in the user's Session. + */ + private ButtonBar getGridButtonbar(PropertyValues pv) + { + return getGridButtonbarForClasses(pv, PeptideDefaultExcelExportAction.class,PeptideDefaultTextExportAction.class); + } + + /* + * Returns a ButtonBar whose excel and text buttons explicitly point to an + * action that will generate the Peptide Pools in Pool report. Use when + * multiple grids exist on the same view. + */ + private ButtonBar getGridButtonbarPoolsInPool(PropertyValues pv) + { + return getGridButtonbarForClasses(pv,PoolsInPoolExcelExportAction.class,PoolsInPoolTextExportAction.class); + } + + /* + * Returns a ButtonBar whose excel and text buttons explicitly point to an + * action that will generate the Peptides in Pool report. Use when + * multiple grids exist on the same view. + */ + private ButtonBar getGridButtonbarPeptidesInPool(PropertyValues pv) + { + return getGridButtonbarForClasses(pv,PeptidesInPoolExcelExportAction.class,PeptidesInPoolTextExportAction.class); + } + + private ButtonBar getGridButtonbarForClasses(PropertyValues pv, Class excelActionClass, Class textActionClass) + { + ButtonBar gridButtonBar = getButtonBar(); + //ActionURL exportUrl = new ActionURL(PeptideDefaultExcelExportAction.class, getContainer()); + ActionURL exportUrl = new ActionURL(excelActionClass, getContainer()); + exportUrl.setPropertyValues(pv); + ActionButton export = new ActionButton(exportUrl, "Export to Excel"); + export.setActionType(ActionButton.Action.LINK); + gridButtonBar.add(export); + + ActionURL exportTextURL = new ActionURL(textActionClass, getContainer()); + exportTextURL.setPropertyValues(pv); + ActionButton exportToText = new ActionButton(exportTextURL, "Export All To Text"); + exportToText.setActionType(ActionButton.Action.LINK); + gridButtonBar.add(exportToText); + + return gridButtonBar; + //rgn.setButtonBar(gridButtonBar, DataRegion.MODE_GRID); + } +} diff --git a/src/org/scharp/atlas/pepdb/PepDBManager.java b/src/org/scharp/atlas/pepdb/PepDBManager.java new file mode 100644 index 00000000..1850fc2f --- /dev/null +++ b/src/org/scharp/atlas/pepdb/PepDBManager.java @@ -0,0 +1,393 @@ +package org.scharp.atlas.pepdb; + +import java.sql.*; +import java.util.Arrays; +import java.util.HashMap; + +import org.apache.log4j.Logger; +import org.labkey.api.data.*; +import org.labkey.api.security.User; +import org.scharp.atlas.pepdb.model.*; + +/** + * Handles Data Operations. + * + * @version $Id: PeptideManager.java 34530 2009-08-10 20:06:30Z sravani $ + */ +public class PepDBManager +{ + + private static Logger log = Logger.getLogger(PepDBManager.class); + private static PepDBSchema schema = PepDBSchema.getInstance(); + /** + * Static class + */ + private PepDBManager() + { + } + + public static PeptideGroup[] getPeptideGroups() throws SQLException + { + Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID); + return Table.select(schema.getTableInfoPeptideGroups(), + schema.getTableInfoPeptideGroups().getColumns() + , null, sort, PeptideGroup.class); + } + + public static PeptidePool[] getPeptidePools() throws SQLException + { + Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_POOL_ID); + return Table.select(schema.getTableInfoPeptidePools(), + schema.getTableInfoPeptidePools().getColumns() + , null, sort, PeptidePool.class); + } + public static PoolType[] getPoolTypes() throws SQLException + { + Sort sort = new Sort(PepDBSchema.COLUMN_POOL_TYPE_ID); + return Table.select(schema.getTableInfoPoolType(), + schema.getTableInfoPoolType().getColumns() + , null, sort, PoolType.class); + } + + public static ProteinCategory[] getProteinCategory() throws SQLException + { + return Table.select(schema.getTableInfoProteinCat(), + schema.getTableInfoProteinCat().getColumns(), null, + new Sort(PepDBSchema.COLUMN_PROTEIN_CAT_ID), ProteinCategory.class); + } + + public static OptimalEpitopeList[] getOptimalEpitopeList() throws SQLException + { + return Table.select(schema.getTableInfoOptimalEpitopeList(), + schema.getTableInfoOptimalEpitopeList().getColumns(), null, + new Sort(PepDBSchema.COLUMN_OPTIMAL_EPITOPE_LIST_ID), OptimalEpitopeList.class); + } + + public static Peptides[] getPeptides() throws SQLException + { + TableInfo tInfo = schema.getTableInfoPeptides(); + return Table.select(tInfo,tInfo.getColumns(), + null,new Sort("peptide_id"),Peptides.class); + } + + /** + * @param peptideGroup + * @return the number of peptides in a given group + * @throws SQLException + */ + public static Integer getCount(Integer peptideGroup) throws SQLException + { + SimpleFilter containerFilter = new SimpleFilter(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID, peptideGroup); + Integer count = Table.executeSingleton(schema.getSchema(), + "SELECT COUNT(*) FROM " + + PepDBSchema.getInstance().getTableInfoViewGroupPeptides() + + " WHERE peptide_group_id=? ", new Object[] { peptideGroup }, + Integer.class); + return count; + } + + public static Peptides insertPeptide(User user, Peptides p) throws Exception + { + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPeptides(); + Peptides dbPeptide = peptideExists(p, tInfo); + if(dbPeptide == null) + dbPeptide = Table.insert(user,tInfo,p); + return dbPeptide; + } + + public static Peptides updatePeptide(User user, Peptides p) throws Exception + { + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPeptides(); + return Table.update(user,tInfo,p,p.getPeptide_id()); + } + + public static PeptidePool updatePeptidePool(User user, PeptidePool p) throws Exception + { + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPeptidePools(); + return Table.update(user,tInfo,p,p.getPeptide_pool_id()); + } + + public static Parent insertParent(User user,Parent parent) throws Exception + { + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoParent(); + return Table.insert(user,tInfo,parent); + } + + public static Parent parentExists(Parent p) throws SQLException + { + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoParent(); + Parent dbParent = null; + SimpleFilter sFilter = new SimpleFilter("peptide_id",p.getPeptide_id()); + sFilter.addCondition("linked_parent",p.getLinked_parent()); + Parent[] dbParents = Table.select(tInfo, + tInfo.getColumns("peptide_id,linked_parent") + ,sFilter,null,Parent.class); + if(dbParents != null && dbParents.length>0) + dbParent = dbParents[0]; + return dbParent; + } + + public static Peptides peptideExists(Peptides p,TableInfo tInfo) throws SQLException + { + Peptides dbPeptide = null; + SimpleFilter sFilter = new SimpleFilter("peptide_sequence",p.getPeptide_sequence()); + Peptides[] dbPeptides = Table.select(tInfo, + tInfo.getColumns(),sFilter,null,Peptides.class); + if(dbPeptides != null && dbPeptides.length>0) + dbPeptide = dbPeptides[0]; + return dbPeptide; + } + + public static Source insertSource(User user, Source src) throws Exception + { + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoSource(); + SimpleFilter sFilter = new SimpleFilter("peptide_group_id",src.getPeptide_group_id()); + sFilter.addCondition("peptide_id",src.getPeptide_id()); + Source dbSrc ; + Source[] dbSrcs = Table.select(tInfo,tInfo.getColumns(),sFilter,null,Source.class); + if(dbSrcs != null && dbSrcs.length>0) + dbSrc = dbSrcs[0]; + else + dbSrc = Table.insert(user,tInfo,src); + dbSrc.setIn_current_file(true); + java.sql.Timestamp date = new java.sql.Timestamp(System.currentTimeMillis()); + String sql = "UPDATE pepdb.peptide_group_assignment SET in_current_file = true,modified = ?,modifiedby = ? WHERE peptide_group_assignment_id=?"; + Object[] params = { date,user.getUserId(),dbSrc.getPeptide_group_assignment_id() }; + Table.execute(schema.getSchema(),sql,params); + return dbSrc; + } + + public static PeptidePool insertPeptidePool(User u,PeptidePool pPool) throws Exception + { + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPeptidePools(); + PeptidePool dbPool = null; + dbPool = Table.insert(u,tInfo,pPool); + return dbPool; + } + + public static PeptidePoolAssignment insertPeptidesInPool(User user,PeptidePoolAssignment src) throws Exception{ + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPoolAssignment(); + SimpleFilter sFilter = new SimpleFilter("peptide_pool_id",src.getPeptide_pool_id()); + sFilter.addCondition("peptide_id",src.getPeptide_id()); + PeptidePoolAssignment dbSrc ; + PeptidePoolAssignment[] dbSrcs = Table.select(tInfo,tInfo.getColumns(),sFilter,null,PeptidePoolAssignment.class); + if(dbSrcs != null && dbSrcs.length>0) + return null; + else + dbSrc = Table.insert(user,tInfo,src); + return dbSrc; + } + + public static Source[] getSourcesForPeptide(String peptideId) + { + Source[] sources = null; + try + { + SimpleFilter sfilter = new SimpleFilter("peptide_id", Integer.parseInt(peptideId)); + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoViewGroupPeptides(); + sources = Table.select(tInfo, + tInfo.getColumns("peptide_group_id,peptide_id_in_group,peptide_group_name,frequency_number,frequency_number_date"), + sfilter, + null, Source.class); + if (sources == null || sources.length < 1) + return null; + } + catch (NumberFormatException e) + { + log.error(e.getMessage(), e); + } + catch (SQLException e) + { + log.error(e.getMessage(), e); + } + return sources; + } + + public static PeptidePool[] getPoolsForPeptide(String peptideId) throws SQLException + { + SimpleFilter sfilter = new SimpleFilter("peptide_id", Integer.parseInt(peptideId)); + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoViewPoolPeptides(); + PeptidePool[] pools = Table.select(tInfo,Table.ALL_COLUMNS,sfilter,null,PeptidePool.class); + if (pools == null || pools.length < 1) + return null; + return pools; + } + + public static PeptideGroup insertGroup(Container c, User user, PeptideGroup pg) throws SQLException { + PeptideGroup resultGroup = Table.insert(user, PepDBSchema.getInstance().getTableInfoPeptideGroups(), pg); + return resultGroup; + } + + public static PeptideGroup updatePeptideGroup(User user,PeptideGroup pg) throws SQLException{ + return Table.update(user, PepDBSchema.getInstance().getTableInfoPeptideGroups(),pg,pg.getPeptide_group_id()); + } + + public static HashMap getPeptideSequenceMap() throws SQLException + { + HashMap peptideSequenceMap = new HashMap(); + Peptides[] peptides = getPeptides(); + for(Peptides peptide : peptides) + peptideSequenceMap.put(peptide.getPeptide_sequence().trim().toUpperCase(),peptide); + return peptideSequenceMap; + } + + public static HashMap getPeptideGroupMap() throws SQLException + { + HashMap groupsMap = new HashMap(); + PeptideGroup[] peptideGroups = getPeptideGroups(); + for(PeptideGroup pGroup:peptideGroups) + groupsMap.put(pGroup.getPeptide_group_name().trim().toUpperCase(),pGroup); + return groupsMap; + } + + public static HashMap getPeptidePoolMap() throws SQLException + { + HashMap poolsMap = new HashMap(); + PeptidePool[] peptidePools = getPeptidePools(); + for(PeptidePool pPool:peptidePools) + poolsMap.put(pPool.getPeptide_pool_name().trim().toUpperCase(),pPool); + return poolsMap; + } + + public static HashMap getPoolTypeMap() throws SQLException + { + HashMap poolTypeMap = new HashMap(); + PoolType[] poolTypes = getPoolTypes(); + for(PoolType pType : poolTypes) + poolTypeMap.put(pType.getPool_type_desc().trim().toUpperCase(),pType); + return poolTypeMap; + } + + public static HashMap getProteinCatMap() throws SQLException + { + HashMap proCatMap = new HashMap(); + ProteinCategory[] proCats = getProteinCategory(); + for(ProteinCategory proCat : proCats) + proCatMap.put(proCat.getProtein_cat_desc().trim().toUpperCase(),proCat); + return proCatMap; + } + + public static HashMap getProteinCatIDMap() throws SQLException + { + HashMap proCatMap = new HashMap(); + ProteinCategory[] proCats = getProteinCategory(); + for(ProteinCategory proCat : proCats) + proCatMap.put(proCat.getProtein_cat_id(),proCat); + return proCatMap; + } + + public static HashMap getOptimalEpitopeListMap() throws SQLException + { + HashMap optListMap = new HashMap(); + OptimalEpitopeList[] optLists = getOptimalEpitopeList(); + for(OptimalEpitopeList optList : optLists) + optListMap.put(optList.getOptimal_epitope_list_desc().trim().toUpperCase(),optList); + return optListMap; + } + + public static Peptides getPeptideById(Integer peptideId) throws SQLException + { + TableInfo tInfo = schema.getTableInfoPeptides(); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PEPTIDE_ID,peptideId); + return Table.selectObject(tInfo,sFilter,null, Peptides.class); + } + + public static Peptides[] getParentPeptides(Peptides p) throws SQLException + { + TableInfo tInfo = schema.getTableInfoPeptides(); + String sql = "select * from pepdb.peptides where peptides.protein_cat_id = ? and ? between peptides.amino_acid_start_pos-2 and peptides.amino_acid_end_pos\n" + + "and ? between peptides.amino_acid_start_pos and peptides.amino_acid_end_pos+2 and child = false"; + Peptides[] peptides = Table.executeQuery(schema.getSchema(),sql,new Object[]{p.getProtein_cat_id(),p.getAmino_acid_start_pos(),p.getAmino_acid_end_pos()},Peptides.class); + return peptides; + } + + public static Peptides[] getHyphanatedParents(Peptides p) throws SQLException + { + String sql = "select * from "+schema.getTableInfoPeptides()+" where peptides.protein_cat_id = ? " + + " and peptides.peptide_sequence LIKE '%"+p.getPeptide_sequence()+"%' " + + " and child = false"; + Peptides[] peptides = Table.executeQuery(schema.getSchema(),sql,new Object[]{p.getProtein_cat_id()},Peptides.class); + return peptides; + } + + public static PeptideGroup getPeptideGroupByID(Integer groupId) throws SQLException + { + TableInfo tInfo = schema.getTableInfoPeptideGroups(); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID,groupId); + return Table.selectObject(tInfo,sFilter,null, PeptideGroup.class); + } + + public static ProteinCategory getProCatByID(Integer procatId) throws SQLException + { + TableInfo tInfo = schema.getTableInfoProteinCat(); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PROTEIN_CAT_ID,procatId); + return Table.selectObject(tInfo,sFilter,null, ProteinCategory.class); + } + + public static PeptideGroup getPeptideGroupByName(PeptideGroup pg) throws SQLException + { + PeptideGroup [] groups = null; + TableInfo tInfo = schema.getTableInfoPeptideGroups(); + SQLFragment sql = new SQLFragment("SELECT * FROM "+tInfo+" WHERE UPPER(peptide_group_name) = ? "); + sql.add(pg.getPeptide_group_name().trim().toUpperCase()); + if(pg.getPeptide_group_id() != null) + { + sql.append("AND peptide_group_id != ?"); + sql.add(pg.getPeptide_group_id()); + } + groups = Table.executeQuery(schema.getSchema(),sql, PeptideGroup.class); + if(groups != null && groups.length >0) + return groups[0]; + else return null; + } + + public static PeptidePool getPeptidePoolByID(Integer poolId) throws SQLException + { + TableInfo tInfo = schema.getTableInfoPeptidePools(); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PEPTIDE_POOL_ID,poolId); + return Table.selectObject(tInfo,sFilter,null, PeptidePool.class); + } + + public static int updateInCurrentFile(User user) throws SQLException + { + java.sql.Timestamp date = new java.sql.Timestamp(System.currentTimeMillis()); + String sql = "UPDATE pepdb.peptide_group_assignment SET in_current_file = false,modified = ?,modifiedby = ?"; + Object[] params = { date,user.getUserId() }; + return Table.execute(PepDBSchema.getInstance().getSchema(),sql,params); + } + + public static Source getSource(Integer peptideId, Integer groupId) throws SQLException + { + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoSource(); + SimpleFilter sFilter = new SimpleFilter("peptide_group_id",groupId); + sFilter.addCondition("peptide_id",peptideId); + Source dbSrc ; + Source[] dbSrcs = Table.select(tInfo,tInfo.getColumns(),sFilter,null,Source.class); + if(dbSrcs != null && dbSrcs.length>0) + dbSrc = dbSrcs[0]; + else + dbSrc = null; + return dbSrc; + } + + public static PeptidePool[] getChildrenPools(String peptidePoolId) throws SQLException + { + SimpleFilter sfilter = new SimpleFilter("parent_pool_id", Integer.parseInt(peptidePoolId)); + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoViewPoolDetails(); + PeptidePool[] pools = Table.select(tInfo,Table.ALL_COLUMNS,sfilter,null,PeptidePool.class); + if (pools == null || pools.length < 1) + return null; + return pools; + } + + public static Integer[] getPeptidesInPool(Integer peptidePoolId) throws SQLException + { + SimpleFilter sfilter = new SimpleFilter("peptide_pool_id", peptidePoolId); + TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPoolAssignment(); + Integer[] peptideIds = Table.executeArray(tInfo,"peptide_id",sfilter,null,Integer.class); + if (peptideIds == null || peptideIds.length < 1) + return null; + return peptideIds; + } + +} \ No newline at end of file diff --git a/src/org/scharp/atlas/pepdb/PepDBModule.java b/src/org/scharp/atlas/pepdb/PepDBModule.java new file mode 100644 index 00000000..5c5960f6 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/PepDBModule.java @@ -0,0 +1,79 @@ +package org.scharp.atlas.pepdb; + +import org.labkey.api.module.DefaultModule; +import org.labkey.api.module.ModuleContext; +import org.labkey.api.data.ContainerManager; +import org.labkey.api.data.Container; +import org.labkey.api.data.DbSchema; +import org.labkey.api.view.*; +import org.labkey.api.util.PageFlowUtil; +import org.scharp.atlas.pepdb.view.PepDBWebPart; +import org.apache.log4j.Logger; + +import java.util.*; +import java.lang.reflect.InvocationTargetException; + +public class PepDBModule extends DefaultModule +{ + private static final Logger _log = Logger.getLogger(DefaultModule.class); + public static final String NAME = "PepDB"; + + public String getName() + { + return NAME; + } + + public double getVersion() + { + return 2.21; + } + + protected void init() + { + addController("pepdb", PepDBController.class); + } + + protected Collection createWebPartFactories() + { + return new ArrayList(Arrays.asList(new BaseWebPartFactory("PepDB Summary") { + public WebPartView getWebPartView(ViewContext portalCtx, Portal.WebPart webPart) throws IllegalAccessException, InvocationTargetException + { + return new PepDBWebPart(); + } + }, + new BaseWebPartFactory("PepDB Summary", WebPartFactory.LOCATION_RIGHT) { + { + addLegacyNames("Narrow PepDB Summary"); + } + + public WebPartView getWebPartView(ViewContext portalCtx, Portal.WebPart webPart) throws IllegalAccessException, InvocationTargetException + { + return new PepDBWebPart(); + } + })); + } + + public boolean hasScripts() + { + return true; + } + + public Collection getSummary(Container c) + { + return Collections.emptyList(); + } + + public void doStartup(ModuleContext moduleContext) + { + // add a container listener so we'll know when our container is deleted: + ContainerManager.addContainerListener(new PepDBContainerListener()); + } + public Set getSchemaNames() + { + return PageFlowUtil.set(PepDBSchema.getInstance().getSchemaName()); + } + public Set getSchemasToTest() + { + return PageFlowUtil.set(PepDBSchema.getInstance().getSchema()); + } +} diff --git a/src/org/scharp/atlas/pepdb/PepDBSchema.java b/src/org/scharp/atlas/pepdb/PepDBSchema.java new file mode 100644 index 00000000..0a3f46cf --- /dev/null +++ b/src/org/scharp/atlas/pepdb/PepDBSchema.java @@ -0,0 +1,162 @@ +package org.scharp.atlas.pepdb; + +import org.labkey.api.data.DbSchema; +import org.labkey.api.data.TableInfo; +import org.labkey.api.data.dialect.SqlDialect; + +/** + * Singleton for storing DB information + * + * @version $Id$ + */ +public class PepDBSchema +{ + + private static PepDBSchema _instance = null; + private static final String SCHEMA_NAME = "pepdb"; + + public static final String TABLE_PEPTIDE_GROUP = "peptide_group"; + public static final String TABLE_GROUP_TYPE = "group_type"; + public static final String TABLE_PROTEIN_CATEGORY = "protein_category"; + public static final String TABLE_PEPTIDES = "peptides"; + public static final String TABLE_PEPTIDE_POOL = "peptide_pool"; + public static final String TABLE_POOL_ASSIGNMENT = "peptide_pool_assignment"; + public static final String TABLE_SOURCE = "peptide_group_assignment"; + public static final String TABLE_PATHOGEN = "pathogen"; + public static final String TABLE_PARENT = "parent"; + public static final String TABLE_OPTIMAL_EPITOPE_LIST = "optimal_epitope_list"; + public static final String TABLE_POOL_TYPE = "pool_type"; + + public static final String VIEW_GROUP_PEPTIDES = "group_peptides"; + public static final String VIEW_POOL_PEPTIDES = "pool_peptides"; + public static final String VIEW_PARENT_CHILD_DETAILS = "parent_child_details"; + public static final String VIEW_POOL_DETAILS = "pool_details"; + + public static final String COLUMN_PEPTIDE_GROUP_ID = "peptide_group_id"; + public static final String COLUMN_PEPTIDE_GROUP_NAME = "peptide_group_name"; + public static final String COLUMN_PROTEIN_CAT_ID = "protein_cat_id"; + public static final String COLUMN_GROUP_TYPE_ID = "group_type_id"; + public static final String COLUMN_PEPTIDE_ID = "peptide_id"; + public static final String COLUMN_PEPTIDE_POOL_ID = "peptide_pool_id"; + public static final String COLUMN_PARENT_POOL_ID = "parent_pool_id"; + public static final String COLUMN_PEPTIDE_SEQUENCE = "peptide_sequence"; + public static final String COLUMN_OPTIMAL_EPITOPE_LIST_ID = "optimal_epitope_list_id"; + public static final String COLUMN_AMINO_ACID_START_POS = "amino_acid_start_pos"; + public static final String COLUMN_AMINO_ACID_END_POS = "amino_acid_end_pos"; + public static final String COLUMN_IS_CHILD = "child"; + public static final String COLUMN_PEPTIDE_ID_IN_GROUP = "peptide_id_in_group"; + public static final String COLUMN_POOL_TYPE_ID = "pool_type_id"; + public static final String COLUMN_PARENT_ID = "parent_id"; + public static final String COLUMN_PARENT_SEQUENCE = "parent_sequence"; + public static final String COLUMN_CHILD_ID = "child_id"; + public static final String COLUMN_CHILD_SEQUENCE = "child_sequence"; + public static final String COLUMN_IN_CURRENT_FILE = "in_current_file"; + public static final String COLUMN_PEPTIDE_GROUP_ASSIGNMENT_ID = "peptide_group_assignment_id"; + public static final String SEQUENCE_PEPTIDE_TABLE = SCHEMA_NAME + ".peptides_peptide_id_seq"; + + + /** + * Singleton + * + * @return the only instance allowed of this class + */ + public synchronized static PepDBSchema getInstance() + { + if (_instance == null) + _instance = new PepDBSchema(); + + return _instance; + } + + private PepDBSchema() + { + // private contructor to prevent instantiation from + // outside this class. + } + + public String getSchemaName() + { + return SCHEMA_NAME; + } + + public DbSchema getSchema() + { + return DbSchema.get(SCHEMA_NAME); + } + + public TableInfo getTableInfoParent() + { + return getSchema().getTable(PepDBSchema.TABLE_PARENT); + } + + public TableInfo getTableInfoPeptideGroups() + { + return getSchema().getTable(PepDBSchema.TABLE_PEPTIDE_GROUP); + } + + public TableInfo getTableInfoPeptideGroupTypes() + { + return getSchema().getTable(PepDBSchema.TABLE_GROUP_TYPE); + } + + public TableInfo getTableInfoPeptides() + { + return getSchema().getTable(PepDBSchema.TABLE_PEPTIDES); + } + + public TableInfo getTableInfoProteinCat() + { + return getSchema().getTable(PepDBSchema.TABLE_PROTEIN_CATEGORY); + } + + public TableInfo getTableInfoViewGroupPeptides() + { + return getSchema().getTable(PepDBSchema.VIEW_GROUP_PEPTIDES); + } + + public TableInfo getTableInfoPeptidePools() + { + return getSchema().getTable(PepDBSchema.TABLE_PEPTIDE_POOL); + } + public TableInfo getTableInfoPoolAssignment() { + return getSchema().getTable(PepDBSchema.TABLE_POOL_ASSIGNMENT); + } + public TableInfo getTableInfoViewPoolPeptides() + { + return getSchema().getTable(PepDBSchema.VIEW_POOL_PEPTIDES); + } + + public TableInfo getTableInfoSource() + { + return getSchema().getTable(PepDBSchema.TABLE_SOURCE); + } + + public TableInfo getTableInfoPathogen() + { + return getSchema().getTable(PepDBSchema.TABLE_PATHOGEN); + } + public SqlDialect getSqlDialect() + { + return getSchema().getSqlDialect(); + } + + public TableInfo getTableInfoOptimalEpitopeList() + { + return getSchema().getTable(PepDBSchema.TABLE_OPTIMAL_EPITOPE_LIST); + } + + public TableInfo getTableInfoViewParentChildDetails() + { + return getSchema().getTable(PepDBSchema.VIEW_PARENT_CHILD_DETAILS); + } + + public TableInfo getTableInfoPoolType() + { + return getSchema().getTable(PepDBSchema.TABLE_POOL_TYPE); + } + + public TableInfo getTableInfoViewPoolDetails() + { + return getSchema().getTable(PepDBSchema.VIEW_POOL_DETAILS); + } +} diff --git a/src/org/scharp/atlas/pepdb/PeptideImporter.java b/src/org/scharp/atlas/pepdb/PeptideImporter.java new file mode 100644 index 00000000..230035cc --- /dev/null +++ b/src/org/scharp/atlas/pepdb/PeptideImporter.java @@ -0,0 +1,351 @@ +package org.scharp.atlas.pepdb; + +import org.scharp.atlas.pepdb.model.*; +import org.labkey.api.security.User; +import org.labkey.api.attachments.AttachmentFile; +import org.springframework.validation.Errors; + +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import java.sql.SQLException; +import java.io.InputStreamReader; +import java.io.BufferedReader; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Jan 8, 2010 + * Time: 10:51:10 AM + * To change this template use File | Settings | File Templates. + */ +public class PeptideImporter +{ + private HashMap peptideGroupMap; + private HashMap proteinCategoryMap; + private HashMap proteinCatIDMap; + private HashMap optimalElitopeListMap; + ArrayList peptideIdList; + + public HashMap getPeptideGroupMap() throws SQLException + { + if(peptideGroupMap == null) + peptideGroupMap = PepDBManager.getPeptideGroupMap(); + return peptideGroupMap; + } + + public void setPeptideGroupMap(HashMap peptideGroupMap) + { + this.peptideGroupMap = peptideGroupMap; + } + + public HashMap getProteinCategoryMap() throws SQLException + { + if(proteinCategoryMap == null) + proteinCategoryMap = PepDBManager.getProteinCatMap(); + return proteinCategoryMap; + } + + public void setProteinCategoryMap(HashMap proteinCategoryMap) + { + this.proteinCategoryMap = proteinCategoryMap; + } + + public HashMap getProteinCatIDMap() throws SQLException + { + if(proteinCatIDMap == null) + proteinCatIDMap = PepDBManager.getProteinCatIDMap(); + return proteinCatIDMap; + } + + public void setProteinCatIDMap(HashMap proteinCatIDMap) + { + this.proteinCatIDMap = proteinCatIDMap; + } + + public HashMap getOptimalElitopeListMap() throws SQLException + { + if(optimalElitopeListMap == null) + optimalElitopeListMap = PepDBManager.getOptimalEpitopeListMap(); + return optimalElitopeListMap; + } + + public void setOptimalElitopeListMap(HashMap optimalElitopeListMap) + { + this.optimalElitopeListMap = optimalElitopeListMap; + } + + public ArrayList getPeptideIdList() throws SQLException + { + if(peptideIdList == null) + { + peptideIdList = new ArrayList(); + Peptides[] peptides = PepDBManager.getPeptides(); + for(Peptides p: peptides) + { + peptideIdList.add(p); + } + } + return peptideIdList; + } + + public void setPeptideIdList(ArrayList peptideIdList) + { + this.peptideIdList = peptideIdList; + } + + public boolean process(User user, AttachmentFile peptideFile, Errors errors, List resultPeptides) throws SQLException + { + try{ + String fileName = peptideFile.getFilename(); + InputStreamReader inStream = new InputStreamReader(peptideFile.openInputStream()); + BufferedReader br = new BufferedReader(inStream); + String line = br.readLine(); + boolean validFile = validateFirstLine(line,errors); + if(!validFile) + return false; + getPeptideGroupMap(); + getProteinCategoryMap(); + getOptimalElitopeListMap(); + HashMap peptideSequenceMap = PepDBManager.getPeptideSequenceMap(); + int lineNo =1; + ArrayList newpeptidesList =new ArrayList(); + HashMap lineMap = new HashMap(); + while((line = br.readLine()) != null) + { + lineNo++; + if(line.length()>0) + { + if(validateLine(line,errors,lineNo)) + { + Peptides peptide = createPeptide(line,fileName); + if(peptide != null) + { + newpeptidesList.add(peptide); + lineMap.put(peptide.getPeptide_sequence(), line); + } + } + } + } + if(errors.getErrorCount() >0) + { + errors.reject(null,"File Import Failed.\nThere are "+errors.getErrorCount()+" errors in the file : "+fileName); + return false; + } + getPeptideIdList(); + PepDBManager.updateInCurrentFile(user); + for(Peptides p : newpeptidesList) + { + if(peptideSequenceMap.containsKey(p.getPeptide_sequence())) + { + if(p.getPeptide_id() == null || p.getPeptide_id().toString().length() == 0) + p.setPeptide_id(peptideSequenceMap.get(p.getPeptide_sequence()).getPeptide_id()); + insertGroups(p,user); + resultPeptides.add(p); + } + else + { + Peptides dbPep = PepDBManager.insertPeptide(user,p); + peptideSequenceMap.put(p.getPeptide_sequence(),dbPep); + peptideIdList.add(dbPep); + p.setPeptide_id(dbPep.getPeptide_id()); + insertGroups(p,user); + resultPeptides.add(p); + } + } + getProteinCatIDMap(); + for(Peptides p : peptideIdList) + { + if(p.isChild()) + { + Peptides[] parents; + if(proteinCatIDMap.get(p.getProtein_cat_id()).getProtein_cat_desc().contains("-")) + parents = PepDBManager.getHyphanatedParents(p); + else + parents = PepDBManager.getParentPeptides(p); + for(Peptides par : parents) + { + Parent parent = new Parent(); + parent.setLinked_parent(par.getPeptide_id()); + parent.setPeptide_id(p.getPeptide_id()); + Parent dbParent = PepDBManager.parentExists(parent); + if(dbParent == null) + dbParent = PepDBManager.insertParent(user,parent); + if(!par.isParent()) + { + par.setParent(true); + PepDBManager.updatePeptide(user,par); + } + } + } + } + + } + catch(Exception e) + { + errors.reject(null,e.getMessage()); + return false; + } + return true; + } + + private void insertGroups(Peptides p,User user) throws Exception + { + Source src = p.getSrc(); + src.setPeptide_id(p.getPeptide_id()); + PepDBManager.insertSource(user,src); + } + + private boolean validateLine(String line,Errors errors,int lineNo) + { + String [] fields = new String[10]; + for(int i =0;i0) + return false; + return true; + } + + private Peptides createPeptide(String line,String fileName) throws SQLException + { + String [] fields = new String[10]; + for(int i =0;i 0) + return false; + return true; + } +} diff --git a/src/org/scharp/atlas/pepdb/PoolImporter.java b/src/org/scharp/atlas/pepdb/PoolImporter.java new file mode 100644 index 00000000..c4d4c2f5 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/PoolImporter.java @@ -0,0 +1,403 @@ +package org.scharp.atlas.pepdb; + +import org.labkey.api.security.User; +import org.labkey.api.attachments.AttachmentFile; +import org.scharp.atlas.pepdb.PepDBBaseController.*; +import org.scharp.atlas.pepdb.model.*; +import org.springframework.validation.Errors; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Arrays; +import java.sql.SQLException; +import java.sql.Array; +import java.io.InputStreamReader; +import java.io.BufferedReader; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Oct 16, 2007 + * Time: 8:30:05 AM + * To change this template use File | Settings | File Templates. + */ +public class PoolImporter +{ + private HashMap peptidePoolMap; + private HashMap poolTypeMap; + private HashMap peptideSequenceMap; + private HashMap peptideGroupMap; + public HashMap getPeptidePoolMap() throws SQLException + { + if(peptidePoolMap == null) + peptidePoolMap = PepDBManager.getPeptidePoolMap(); + return peptidePoolMap; + } + + public void setPeptidePoolMap(HashMap peptidePoolMap) + { + this.peptidePoolMap = peptidePoolMap; + } + + public HashMap getPoolTypeMap() throws SQLException + { + if(poolTypeMap == null) + poolTypeMap = PepDBManager.getPoolTypeMap(); + return poolTypeMap; + } + + public void setPoolTypeMap(HashMap poolTypeMap) + { + this.poolTypeMap = poolTypeMap; + } + + public HashMap getPeptideSequenceMap() throws SQLException + { + if(peptideSequenceMap == null) + peptideSequenceMap = PepDBManager.getPeptideSequenceMap(); + return peptideSequenceMap; + } + + public void setPeptideSequenceMap(HashMap peptideSequenceMap) + { + this.peptideSequenceMap = peptideSequenceMap; + } + + public HashMap getPeptideGroupMap() throws SQLException + { + if(peptideGroupMap == null) + peptideGroupMap = PepDBManager.getPeptideGroupMap(); + return peptideGroupMap; + } + + public void setPeptideGroupMap(HashMap peptideGroupMap) + { + this.peptideGroupMap = peptideGroupMap; + } + + public boolean process(User user, FileForm form, AttachmentFile poolFile,Errors errors) throws SQLException + { + String actionType = form.getActionType(); + PeptidePool [] peptidePools = PepDBManager.getPeptidePools(); + //HashMap peptidepoolMap = new HashMap(); + /*for(PeptidePool pPool : peptidePools) + { + peptidepoolMap.put(pPool.getPeptide_pool_id(),pPool); + }*/ + if(actionType.equalsIgnoreCase("POOLDESC")) + { + try{ + InputStreamReader inStream = new InputStreamReader(poolFile.openInputStream()); + BufferedReader br = new BufferedReader(inStream); + String line = br.readLine(); + boolean validFile = validateFirstLine(line,actionType,errors); + if(!validFile) + return false; + getPeptidePoolMap(); + getPoolTypeMap(); + ArrayList newPPools = new ArrayList(); + int lineNo =1; + while((line = br.readLine()) != null) + { + lineNo++; + if(line.trim().length()>0) + { + if(validateLine(line,errors,lineNo)) + { + PeptidePool pPool = createPeptidePool(line); + if(pPool != null) + newPPools.add(pPool); + } + } + } + if(errors.getErrorCount() >0) + { + errors.reject(null,"File Import Failed.\nThere are "+errors.getErrorCount()+" errors in the file : "+poolFile.getFilename()); + return false; + } + for(PeptidePool pPool : newPPools) + { + if(pPool.getPool_type_id() != poolTypeMap.get("POOL").getPool_type_id()) + pPool.setParent_pool_id(peptidePoolMap.get(pPool.getParent_pool_name()).getPeptide_pool_id()); + PeptidePool dbPeptidePool = null; + if(peptidePoolMap.get(pPool.getPeptide_pool_name().trim().toUpperCase()).getPeptide_pool_id() == null || peptidePoolMap.get(pPool.getPeptide_pool_name().trim().toUpperCase()).getPeptide_pool_id().toString().length() == 0) + dbPeptidePool= PepDBManager.insertPeptidePool(user,pPool); + if(dbPeptidePool != null) + peptidePoolMap.put(dbPeptidePool.getPeptide_pool_name().trim().toUpperCase(),dbPeptidePool); + } + } + catch(Exception e) + { + errors.reject(null,e.getMessage()); + return false; + } + } + if(actionType.equalsIgnoreCase("POOLPEPTIDES")) + { + try{ + InputStreamReader inStream = new InputStreamReader(poolFile.openInputStream()); + BufferedReader br = new BufferedReader(inStream); + String line = br.readLine(); + boolean validFile = validateFirstLine(line,form.getActionType(),errors); + if(!validFile) + return false; + int lineNo =1; + getPeptidePoolMap(); + getPeptideSequenceMap(); + getPeptideGroupMap(); + ArrayList newpeptidesInPools = new ArrayList(); + HashMap> newpoolPeptides = new HashMap>(); + while((line = br.readLine()) != null) + { + lineNo++; + if(line.length()>0) + { + if(validatePPLine(line,errors,lineNo,newpoolPeptides)) + { + PeptidePoolAssignment poolAssign = createPoolAssignment(line); + if(poolAssign != null) + { + newpeptidesInPools.add(poolAssign); + if(newpoolPeptides.containsKey(poolAssign.getPeptide_pool_id())) + newpoolPeptides.get(poolAssign.getPeptide_pool_id()).add(poolAssign.getPeptide_id()); + else + { + ArrayList peps = new ArrayList(); + peps.add(poolAssign.getPeptide_id()); + newpoolPeptides.put(poolAssign.getPeptide_pool_id(),peps); + } + + } + } + } + + } + if(errors.getErrorCount()>0) + { + errors.reject(null,"File Import Failed.\nThere are "+errors.getErrorCount()+" errors in the file : "+poolFile.getFilename()); + return false; + } + for(PeptidePoolAssignment pAssignment : newpeptidesInPools) + { + PepDBManager.insertPeptidesInPool(user,pAssignment); + } + } + catch(Exception e) + { + errors.reject(null,e.getMessage()); + return false; + } + } + return true; + } + + private PeptidePoolAssignment createPoolAssignment(String line) throws SQLException + { + String [] fields = new String[3]; + for(int i =0;i0) + return false; + return true; + } + + private boolean validatePPLine(String line,Errors errors,int lineNo,HashMap> newPoolPeptides) throws SQLException + { + String [] fields = new String[3]; + for(int i =0;i0) + return false; + return true; + } + + private boolean validateFirstLine(String line,String importType,Errors errors) + { + String [] fields = line.split("\t"); + if(importType.equalsIgnoreCase("POOLDESC")) + { + if(fields.length != 4) + errors.reject(null,"Line number : 1 must be tab delimited with the fields 'Pool Name','Pool Type','Parent Pool Name' and 'Matrix Pool Id' in the Pool Descriptions file.\n"+ + "The header line does not match this format. Please check the file and try to upload again."); + else + { + if(fields[0] ==null || fields[0].trim().length() == 0 + || fields[1] ==null || fields[1].trim().length() == 0 + || fields[2] ==null || fields[2].trim().length() == 0 + || fields[3] ==null || fields[3].trim().length() == 0) + errors.reject(null,"Line number : 1 must be tab delimited with the fields 'Pool Name','Pool Type','Parent Pool Name' and 'Matrix Pool Id' in the Pool Descriptions file.\n"+ + "One of the fields in header line is null or empty. Please check the file and try to upload again."); + else + { + if(fields[0] != null && !fields[0].trim().equalsIgnoreCase("POOL NAME")) + errors.reject(null,"Pool Descriptions File Import Failed.\nThe first field in the first line (Header) must be 'POOL NAME'"); + if(fields[1] != null && !fields[1].trim().equalsIgnoreCase("POOL TYPE")) + errors.reject(null,"Pool Descriptions File Import Failed.\nThe Second field in the first line (Header) must be 'POOL TYPE'"); + if(fields[2] != null && !fields[2].trim().equalsIgnoreCase("PARENT POOL NAME")) + errors.reject(null,"Pool Descriptions File Import Failed.\nThe Third field in the first line (Header) must be 'PARENT POOL NAME'"); + if(fields[2] != null && !fields[3].trim().equalsIgnoreCase("MATRIX POOL ID")) + errors.reject(null,"Pool Descriptions File Import Failed.\nThe Fourth field in the first line (Header) must be 'MATRIX POOL ID'"); + } + } + if(errors.getErrorCount() > 0) + return false; + return true; + } + if(importType.equalsIgnoreCase("POOLPEPTIDES")) + { + if(fields.length != 3) + errors.reject(null,"Line number : 1 must be tab delimited with the fields 'POOL NAME','PEPTIDE SEQUENCE'and 'PEPTIDE GROUP' in the Peptides in Pool file.\n"+ + "The header line does not match this format. Please check the file and try to upload again."); + else + { + if(fields[0] ==null || fields[0].trim().length() == 0 ||fields[1] ==null || fields[1].trim().length() == 0 || fields[2] ==null || fields[2].trim().length() == 0) + errors.reject(null,"Line number : 1 must be tab delimited with the fields 'POOL NAME','PEPTIDE SEQUENCE' and 'PEPTIDE GROUP'in the Peptides in Pool file.\n"+ + "One of the fields in header line is null or empty. Please check the file and try to upload again."); + else + { + if(fields[0] != null && !fields[0].trim().equalsIgnoreCase("POOL NAME")) + errors.reject(null,"Peptides in Pool File Import Failed.\nThe first field in the first line (Header) must be 'POOL NAME'"); + if(fields[1] != null && !fields[1].trim().equalsIgnoreCase("PEPTIDE SEQUENCE")) + errors.reject(null,"Peptides in Pool File Import Failed.\nThe Second field in the first line (Header) must be 'PEPTIDE SEQUENCE'"); + if(fields[2] != null && !fields[2].trim().equalsIgnoreCase("PEPTIDE GROUP")) + errors.reject(null,"Peptides in Pool File Import Failed.\nThe Second field in the first line (Header) must be 'PEPTIDE GROUP'"); + } + } + if(errors.getErrorCount() > 0) + return false; + return true; + } + return true; + } +} diff --git a/src/org/scharp/atlas/pepdb/model/GroupType.java b/src/org/scharp/atlas/pepdb/model/GroupType.java new file mode 100644 index 00000000..ebdb4f18 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/model/GroupType.java @@ -0,0 +1,34 @@ +package org.scharp.atlas.pepdb.model; + +/** + * Created by IntelliJ IDEA. + * User: slangley + * Date: Dec 21, 2007 + * Time: 1:08:27 PM + * To change this template use File | Settings | File Templates. + */ +public class GroupType +{ + private Integer group_type_id; + private String group_type_desc; + + public Integer getGroup_type_id() + { + return group_type_id; + } + + public void setGroup_type_id(Integer group_type_id) + { + this.group_type_id = group_type_id; + } + + public String getGroup_type_desc() + { + return group_type_desc; + } + + public void setGroup_type_desc(String group_type_desc) + { + this.group_type_desc = group_type_desc; + } +} diff --git a/src/org/scharp/atlas/pepdb/model/OptimalEpitopeList.java b/src/org/scharp/atlas/pepdb/model/OptimalEpitopeList.java new file mode 100644 index 00000000..c3ba0e31 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/model/OptimalEpitopeList.java @@ -0,0 +1,34 @@ +package org.scharp.atlas.pepdb.model; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Jan 20, 2010 + * Time: 2:10:36 PM + * To change this template use File | Settings | File Templates. + */ +public class OptimalEpitopeList +{ + private Integer optimal_epitope_list_id; + private String optimal_epitope_list_desc; + + public Integer getOptimal_epitope_list_id() + { + return optimal_epitope_list_id; + } + + public void setOptimal_epitope_list_id(Integer optimal_epitope_list_id) + { + this.optimal_epitope_list_id = optimal_epitope_list_id; + } + + public String getOptimal_epitope_list_desc() + { + return optimal_epitope_list_desc; + } + + public void setOptimal_epitope_list_desc(String optimal_epitope_list_desc) + { + this.optimal_epitope_list_desc = optimal_epitope_list_desc; + } +} diff --git a/src/org/scharp/atlas/pepdb/model/Parent.java b/src/org/scharp/atlas/pepdb/model/Parent.java new file mode 100644 index 00000000..2ec2cf3b --- /dev/null +++ b/src/org/scharp/atlas/pepdb/model/Parent.java @@ -0,0 +1,34 @@ +package org.scharp.atlas.pepdb.model; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Nov 1, 2007 + * Time: 10:28:37 AM + * To change this template use File | Settings | File Templates. + */ +public class Parent +{ + private Integer peptide_id; + private Integer linked_parent; + + public Integer getPeptide_id() + { + return peptide_id; + } + + public void setPeptide_id(Integer peptide_id) + { + this.peptide_id = peptide_id; + } + + public Integer getLinked_parent() + { + return linked_parent; + } + + public void setLinked_parent(Integer linked_parent) + { + this.linked_parent = linked_parent; + } +} diff --git a/src/org/scharp/atlas/pepdb/model/Pathogen.java b/src/org/scharp/atlas/pepdb/model/Pathogen.java new file mode 100644 index 00000000..81630720 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/model/Pathogen.java @@ -0,0 +1,32 @@ +package org.scharp.atlas.pepdb.model; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Jul 12, 2007 + * Time: 1:19:11 PM + * To change this template use File | Settings | File Templates. + */ +public class Pathogen{ + private Integer pathogen_id; + private String pathogen_desc; + + public Pathogen() { + } + + public Integer getPathogen_id() { + return pathogen_id; + } + + public void setPathogen_id(Integer pathogen_id) { + this.pathogen_id = pathogen_id; + } + + public String getPathogen_desc() { + return pathogen_desc; + } + + public void setPathogen_desc(String pathogen_desc) { + this.pathogen_desc = pathogen_desc; + } +} diff --git a/src/org/scharp/atlas/pepdb/model/PeptideGroup.java b/src/org/scharp/atlas/pepdb/model/PeptideGroup.java new file mode 100644 index 00000000..62f46e94 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/model/PeptideGroup.java @@ -0,0 +1,84 @@ +package org.scharp.atlas.pepdb.model; + +import org.labkey.api.data.Entity; + + +public class PeptideGroup extends Entity +{ + private Integer peptide_group_id; + private String peptide_group_name; + private Integer pathogen_id; + private String seq_ref; + private Integer clade_id; + private Integer pep_align_ref_id; + private Integer group_type_id; + + public Integer getPeptide_group_id() + { + return peptide_group_id; + } + + public void setPeptide_group_id(Integer peptide_group_id) + { + this.peptide_group_id = peptide_group_id; + } + + public PeptideGroup() { + } + + public Integer getPathogen_id() { + return pathogen_id; + } + + public void setPathogen_id(Integer pathogen_id) { + this.pathogen_id = pathogen_id; + } + + public String getPeptide_group_name() { + return peptide_group_name; + } + + public void setPeptide_group_name(String peptide_group_name) { + this.peptide_group_name = peptide_group_name; + } + + public String getSeq_ref() + { + return seq_ref; + } + + public void setSeq_ref(String seq_ref) + { + this.seq_ref = seq_ref; + } + + public Integer getClade_id() + { + return clade_id; + } + + public void setClade_id(Integer clade_id) + { + this.clade_id = clade_id; + } + + public Integer getPep_align_ref_id() + { + return pep_align_ref_id; + } + + public void setPep_align_ref_id(Integer pep_align_ref_id) + { + this.pep_align_ref_id = pep_align_ref_id; + } + + public Integer getGroup_type_id() + { + return group_type_id; + } + + public void setGroup_type_id(Integer group_type_id) + { + this.group_type_id = group_type_id; + } +} diff --git a/src/org/scharp/atlas/pepdb/model/PeptidePool.java b/src/org/scharp/atlas/pepdb/model/PeptidePool.java new file mode 100644 index 00000000..6d55a76c --- /dev/null +++ b/src/org/scharp/atlas/pepdb/model/PeptidePool.java @@ -0,0 +1,125 @@ +package org.scharp.atlas.pepdb.model; + +import org.labkey.api.data.Entity; +import org.apache.log4j.Logger; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Jun 7, 2007 + * Time: 12:23:53 PM + * Scott changed getDescription() + */ +public class PeptidePool extends Entity +{ + private Integer peptide_pool_id; + private String peptide_pool_name; + private String comment; + private Integer pool_type_id; + private String pool_type_desc; + private boolean archived; + private Integer parent_pool_id; + private String parent_pool_name; + private String matrix_peptide_pool_id; + private static Logger log = Logger.getLogger(PeptidePool.class); + + public PeptidePool() + { + } + + public PeptidePool(Integer peptide_pool_id, String peptide_pool_name) + { + this.peptide_pool_id = peptide_pool_id; + this.peptide_pool_name = peptide_pool_name; + } + + public Integer getPeptide_pool_id() + { + return peptide_pool_id; + } + + public void setPeptide_pool_id(Integer peptide_pool_id) + { + this.peptide_pool_id = peptide_pool_id; + } + + public String getPeptide_pool_name() + { + return peptide_pool_name; + } + + public void setPeptide_pool_name(String peptide_pool_name) + { + this.peptide_pool_name = peptide_pool_name; + } + + public Integer getPool_type_id() + { + return pool_type_id; + } + + public void setPool_type_id(Integer pool_type_id) + { + this.pool_type_id = pool_type_id; + } + + public String getComment() + { + return comment; + } + + public void setComment(String comment) + { + this.comment = comment; + } + + public String getPool_type_desc() + { + return pool_type_desc; + } + + public void setPool_type_desc(String pool_type_desc) + { + this.pool_type_desc = pool_type_desc; + } + + public boolean getArchived() + { + return archived; + } + + public void setArchived(boolean archived) + { + this.archived = archived; + } + + public Integer getParent_pool_id() + { + return parent_pool_id; + } + + public void setParent_pool_id(Integer parent_pool_id) + { + this.parent_pool_id = parent_pool_id; + } + + public String getParent_pool_name() + { + return parent_pool_name; + } + + public void setParent_pool_name(String parent_pool_name) + { + this.parent_pool_name = parent_pool_name; + } + + public String getMatrix_peptide_pool_id() + { + return matrix_peptide_pool_id; + } + + public void setMatrix_peptide_pool_id(String matrix_peptide_pool_id) + { + this.matrix_peptide_pool_id = matrix_peptide_pool_id; + } +} diff --git a/src/org/scharp/atlas/pepdb/model/PeptidePoolAssignment.java b/src/org/scharp/atlas/pepdb/model/PeptidePoolAssignment.java new file mode 100644 index 00000000..78cee773 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/model/PeptidePoolAssignment.java @@ -0,0 +1,47 @@ +package org.scharp.atlas.pepdb.model; + +import org.labkey.api.data.Entity; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Nov 5, 2007 + * Time: 9:01:23 AM + * To change this template use File | Settings | File Templates. + */ +public class PeptidePoolAssignment extends Entity +{ + private Integer peptide_pool_id; + private Integer peptide_id; + private Integer peptide_group_assignment_id; + + public Integer getPeptide_pool_id() + { + return peptide_pool_id; + } + + public void setPeptide_pool_id(Integer peptide_pool_id) + { + this.peptide_pool_id = peptide_pool_id; + } + + public Integer getPeptide_id() + { + return peptide_id; + } + + public void setPeptide_id(Integer peptide_id) + { + this.peptide_id = peptide_id; + } + + public Integer getPeptide_group_assignment_id() + { + return peptide_group_assignment_id; + } + + public void setPeptide_group_assignment_id(Integer peptide_group_assignment_id) + { + this.peptide_group_assignment_id = peptide_group_assignment_id; + } +} diff --git a/src/org/scharp/atlas/pepdb/model/Peptides.java b/src/org/scharp/atlas/pepdb/model/Peptides.java new file mode 100644 index 00000000..33654b0c --- /dev/null +++ b/src/org/scharp/atlas/pepdb/model/Peptides.java @@ -0,0 +1,237 @@ +package org.scharp.atlas.pepdb.model; + +import org.labkey.api.data.Entity; + +import java.util.List; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Jul 12, 2007 + * Time: 1:11:40 PM + * To change this template use File | Settings | File Templates. + */ +public class Peptides extends Entity +{ + private Integer peptide_id; + private String peptide_sequence; + private Integer protein_cat_id; + private Integer amino_acid_start_pos; + private Integer amino_acid_end_pos; + private Integer sequence_length; + private boolean child; + private boolean parent; + private String src_file_name; + private String storage_location; + private Integer optimal_epitope_list_id; + private String hla_restriction; + private Source src; + private List peptideGroups; + private ProteinCategory proteinCat; + private Pathogen pathogen; + private Parent parentPep; + private List parents; + private boolean peptide_flag; + private String peptide_notes; + + public List getParents() + { + return parents; + } + + public void setParents(List parents) + { + this.parents = parents; + } + + + public Parent getParentPep() + { + return parentPep; + } + + public void setParentPep(Parent parentPep) + { + this.parentPep = parentPep; + } + + public ProteinCategory getProteinCat() + { + return proteinCat; + } + + public void setProteinCat(ProteinCategory proteinCat) + { + this.proteinCat = proteinCat; + } + + public Pathogen getPathogen() + { + return pathogen; + } + + public void setPathogen(Pathogen pathogen) + { + this.pathogen = pathogen; + } + + public Integer getPeptide_id() + { + return peptide_id; + } + + public void setPeptide_id(Integer peptide_id) + { + this.peptide_id = peptide_id; + } + + public String getPeptide_sequence() + { + return peptide_sequence; + } + + public void setPeptide_sequence(String peptide_sequence) + { + this.peptide_sequence = peptide_sequence; + } + + public Integer getProtein_cat_id() + { + return protein_cat_id; + } + + public void setProtein_cat_id(Integer protein_cat_id) + { + this.protein_cat_id = protein_cat_id; + } + + public boolean isChild() + { + return child; + } + + public void setChild(boolean child) + { + this.child = child; + } + + public List getPeptideGroups() + { + return peptideGroups; + } + + public void setPeptideGroups(List peptideGroups) + { + this.peptideGroups = peptideGroups; + } + + public boolean isParent() + { + return parent; + } + + public void setParent(boolean parent) + { + this.parent = parent; + } + + public String getSrc_file_name() + { + return src_file_name; + } + + public void setSrc_file_name(String src_file_name) + { + this.src_file_name = src_file_name; + } + + public Integer getAmino_acid_start_pos() + { + return amino_acid_start_pos; + } + + public void setAmino_acid_start_pos(Integer amino_acid_start_pos) + { + this.amino_acid_start_pos = amino_acid_start_pos; + } + + public Integer getAmino_acid_end_pos() + { + return amino_acid_end_pos; + } + + public void setAmino_acid_end_pos(Integer amino_acid_end_pos) + { + this.amino_acid_end_pos = amino_acid_end_pos; + } + + public Integer getSequence_length() + { + return sequence_length; + } + + public void setSequence_length(Integer sequence_length) + { + this.sequence_length = sequence_length; + } + + public String getStorage_location() + { + return storage_location; + } + + public void setStorage_location(String storage_location) + { + this.storage_location = storage_location; + } + + public Integer getOptimal_epitope_list_id() + { + return optimal_epitope_list_id; + } + + public void setOptimal_epitope_list_id(Integer optimal_epitope_list_id) + { + this.optimal_epitope_list_id = optimal_epitope_list_id; + } + + public String getHla_restriction() + { + return hla_restriction; + } + + public void setHla_restriction(String hla_restriction) + { + this.hla_restriction = hla_restriction; + } + + public Source getSrc() + { + return src; + } + + public void setSrc(Source src) + { + this.src = src; + } + + public boolean isPeptide_flag() + { + return peptide_flag; + } + + public void setPeptide_flag(boolean peptide_flag) + { + this.peptide_flag = peptide_flag; + } + + public String getPeptide_notes() + { + return peptide_notes; + } + + public void setPeptide_notes(String peptide_notes) + { + this.peptide_notes = peptide_notes; + } +} diff --git a/src/org/scharp/atlas/pepdb/model/PoolType.java b/src/org/scharp/atlas/pepdb/model/PoolType.java new file mode 100644 index 00000000..c7304547 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/model/PoolType.java @@ -0,0 +1,34 @@ +package org.scharp.atlas.pepdb.model; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Jan 25, 2010 + * Time: 2:13:19 PM + * To change this template use File | Settings | File Templates. + */ +public class PoolType +{ + private Integer pool_type_id; + private String pool_type_desc; + + public Integer getPool_type_id() + { + return pool_type_id; + } + + public void setPool_type_id(Integer pool_type_id) + { + this.pool_type_id = pool_type_id; + } + + public String getPool_type_desc() + { + return pool_type_desc; + } + + public void setPool_type_desc(String pool_type_desc) + { + this.pool_type_desc = pool_type_desc; + } +} diff --git a/src/org/scharp/atlas/pepdb/model/ProteinCategory.java b/src/org/scharp/atlas/pepdb/model/ProteinCategory.java new file mode 100644 index 00000000..354b655a --- /dev/null +++ b/src/org/scharp/atlas/pepdb/model/ProteinCategory.java @@ -0,0 +1,44 @@ +package org.scharp.atlas.pepdb.model; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Jan 9, 2007 + * Time: 1:34:16 PM + * To change this template use File | Settings | File Templates. + */ +public class ProteinCategory +{ + private Integer protein_cat_id; + private String protein_cat_desc; + + public ProteinCategory() + { + } + + public ProteinCategory(Integer protein_cat_id, String protein_cat_desc, String protein_cat_mnem, Integer protein_sort_value) + { + this.protein_cat_id = protein_cat_id; + this.protein_cat_desc = protein_cat_desc; + } + + public Integer getProtein_cat_id() + { + return protein_cat_id; + } + + public void setProtein_cat_id(Integer protein_cat_id) + { + this.protein_cat_id = protein_cat_id; + } + + public String getProtein_cat_desc() + { + return protein_cat_desc; + } + + public void setProtein_cat_desc(String protein_cat_desc) + { + this.protein_cat_desc = protein_cat_desc; + } +} diff --git a/src/org/scharp/atlas/pepdb/model/Source.java b/src/org/scharp/atlas/pepdb/model/Source.java new file mode 100644 index 00000000..e7077ae4 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/model/Source.java @@ -0,0 +1,108 @@ +package org.scharp.atlas.pepdb.model; + +import org.labkey.api.data.Entity; + +import java.sql.Date; + +/** + * Created by IntelliJ IDEA. + * User: sravani + * Date: Jun 21, 2007 + * Time: 11:49:31 AM + * To change this template use File | Settings | File Templates. + */ +public class Source extends Entity +{ + private Integer peptide_group_assignment_id; + private Integer peptide_id; + private Integer peptide_group_id; + private String peptide_group_name; + private String peptide_id_in_group; + private Float frequency_number; + private Date frequency_number_date; + private boolean in_current_file; + + public Source() + { + } + + public Integer getPeptide_group_assignment_id() + { + return peptide_group_assignment_id; + } + + public void setPeptide_group_assignment_id(Integer peptide_group_assignment_id) + { + this.peptide_group_assignment_id = peptide_group_assignment_id; + } + + public Integer getPeptide_id() + { + return peptide_id; + } + + public void setPeptide_id(Integer peptide_id) + { + this.peptide_id = peptide_id; + } + + public Integer getPeptide_group_id() + { + return peptide_group_id; + } + + public void setPeptide_group_id(Integer peptide_group_id) + { + this.peptide_group_id = peptide_group_id; + } + + public String getPeptide_id_in_group() + { + return peptide_id_in_group; + } + + public void setPeptide_id_in_group(String peptide_id_in_group) + { + this.peptide_id_in_group = peptide_id_in_group; + } + + public Float getFrequency_number() + { + return frequency_number; + } + + public void setFrequency_number(Float frequency_number) + { + this.frequency_number = frequency_number; + } + + public Date getFrequency_number_date() + { + return frequency_number_date; + } + + public void setFrequency_number_date(Date frequency_number_date) + { + this.frequency_number_date = frequency_number_date; + } + + public String getPeptide_group_name() + { + return peptide_group_name; + } + + public void setPeptide_group_name(String peptide_group_name) + { + this.peptide_group_name = peptide_group_name; + } + + public boolean isIn_current_file() + { + return in_current_file; + } + + public void setIn_current_file(boolean in_current_file) + { + this.in_current_file = in_current_file; + } +} diff --git a/src/org/scharp/atlas/pepdb/view/PepDBWebPart.java b/src/org/scharp/atlas/pepdb/view/PepDBWebPart.java new file mode 100644 index 00000000..a1cda8a2 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/view/PepDBWebPart.java @@ -0,0 +1,35 @@ +package org.scharp.atlas.pepdb.view; + +import javax.servlet.ServletException; + +import org.apache.log4j.Logger; +import org.labkey.api.view.JspView; + +/** + * @version $Id$ + */ +public class PepDBWebPart extends JspView { + + private static Logger _log = Logger.getLogger(PepDBWebPart.class); + + /** + * + */ + public PepDBWebPart() { + super("/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp", null); + setTitle("PepDB Web Part"); + } + + /* (non-Javadoc) + * @see org.labkey.api.view.WebPartView#prepareWebPart(java.lang.Object) + */ + protected void prepareWebPart(Object object) throws ServletException { + super.prepareWebPart(object); + /* try { + getViewContext().put("peptides", + PepDBManager.getPeptideGroups()); + } catch (SQLException e) { + _log.error("Error retrieving list of PeptideGroups.", e); + } */ + } +} diff --git a/src/org/scharp/atlas/pepdb/view/importPeptides.jsp b/src/org/scharp/atlas/pepdb/view/importPeptides.jsp new file mode 100644 index 00000000..8f45f4c7 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/view/importPeptides.jsp @@ -0,0 +1,48 @@ +<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> +<%@ page import="org.labkey.api.view.HttpView"%> +<%@ page import="org.labkey.api.view.JspView"%> +<%@ page import="org.scharp.atlas.pepdb.PepDBController" %> +<%@ page extends="org.labkey.api.jsp.JspBase" %> + + +
+ <% + JspView me = (JspView) HttpView.currentView(); + PepDBController.FileForm bean = me.getModelBean(); + %> + +
+ + + + + + + + + + + + + +
+

Import Peptides

+
File Type : +
+ File : + +
+ <%= generateSubmitButton("Import Peptides") %> <%= generateButton("Back", "begin.view") %> +
+
+ Note: The File must be .txt extension and It should be tab delimited.

+
+
+
diff --git a/src/org/scharp/atlas/pepdb/view/importPools.jsp b/src/org/scharp/atlas/pepdb/view/importPools.jsp new file mode 100644 index 00000000..758d4f16 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/view/importPools.jsp @@ -0,0 +1,45 @@ +<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> +<%@ page import="org.labkey.api.view.HttpView"%> +<%@ page import="org.scharp.atlas.pepdb.PepDBController" %> +<%@ page import="org.labkey.api.view.JspView" %> +<%@ page extends="org.labkey.api.jsp.JspBase" %> + + +
+ <% + JspView me = (JspView) HttpView.currentView(); + PepDBController.FileForm bean = me.getModelBean(); + %> + +
+ + + + + + + + + + + + + +
+

Import Peptide Pools

+
File Type : +
+ File : + +
+ <%= generateSubmitButton("Import Peptide Pools") %> <%= generateButton("Back", "begin.view") %> +
+
+ Note: The File must be .txt extension and It should be tab delimited.

+
+
+
diff --git a/src/org/scharp/atlas/pepdb/view/index.jsp b/src/org/scharp/atlas/pepdb/view/index.jsp new file mode 100644 index 00000000..8af64fce --- /dev/null +++ b/src/org/scharp/atlas/pepdb/view/index.jsp @@ -0,0 +1,48 @@ +<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> +<%@ page import="org.labkey.api.view.ViewContext"%> +<%@ page import="org.labkey.api.security.User" %> +<%@ page import="org.labkey.api.security.ACL" %> +<%@ page import="org.labkey.api.view.HttpView" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBController" %> +<%@ page extends="org.labkey.api.jsp.JspBase" %> + +<% ViewContext ctx = getViewContext(); + User user = ctx.getUser(); %> +

If you see some of the links disabled then you don't have permission to enter data. + If you need to enter any data contact Atlas Administrator.

+

Peptide Groups :

+ +

Peptides :

+ + +<% + PepDBController.DisplayPeptideForm form = (PepDBController.DisplayPeptideForm) (HttpView.currentModel()); +%> +
+Lookup Peptide by Id: "/>   <%= generateSubmitButton("Find") %> +
+

+

Peptide Pools :

+ diff --git a/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp b/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp new file mode 100644 index 00000000..9649d5a4 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp @@ -0,0 +1,11 @@ +<%@ page import="org.labkey.api.view.HttpView"%> +<%@ page import="org.labkey.api.view.ViewContext"%> +<%@ page import="org.labkey.api.view.ActionURL"%> +<%@ page import="org.scharp.atlas.pepdb.model.PeptideGroup" %> +<%@ page extends="org.labkey.api.jsp.JspBase" %> +<% + ViewContext context = HttpView.currentContext(); + PeptideGroup[] peptides = (PeptideGroup[]) context.get("peptides"); +%> +This container contains <%= peptides.length %> peptide groups.
+<%= generateButton("View Grid", new ActionURL("PepDB", "showAllPeptideGroups", context.getContainer())) %> \ No newline at end of file diff --git a/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp b/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp new file mode 100644 index 00000000..0ac155f0 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp @@ -0,0 +1,58 @@ +<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> +<%@ page import="org.labkey.api.view.HttpView"%> +<%@ page import="org.scharp.atlas.pepdb.PepDBManager" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBBaseController.*" %> +<%@ page import="org.labkey.api.view.JspView" %> +<%@ page import="org.scharp.atlas.pepdb.model.PeptidePool" %> +<%@ page import="org.scharp.atlas.pepdb.model.Source" %> +<%@ page import="java.util.List" %> +<%@ page import="java.util.Arrays" %> +<%@ page extends="org.labkey.api.jsp.JspBase" %> +<% + JspView me = (JspView) HttpView.currentView(); + PeptideQueryForm bean = me.getModelBean(); +%> + + <% + Source[] sources = PepDBManager.getSourcesForPeptide(bean.getQueryValue()); + if (sources != null && sources.length > 0) + {%> +

Peptide P<%=bean.getQueryValue()%> is a member of these Peptide Groups:

+ <% List sourceList = Arrays.asList(sources); + for (Source source : sourceList) + { %> + + + + + + <% }} // end for loop %> +
+ <%= textLink(source.getPeptide_group_name(), + "displayPeptideGroupInformation.view?peptide_group_id=" + source.getPeptide_group_id().toString()) %> + (LAB ID =<%=source.getPeptide_id_in_group()%>) + <%if(source.getFrequency_number() != null){%> + - Frequency Number = + <%= source.getFrequency_number()%> + <%}if(source.getFrequency_number_date() != null){%> + - Frequency Update Date = <%=source.getFrequency_number_date()%><%}%> +
+ + <% + PeptidePool[] pools = PepDBManager.getPoolsForPeptide(bean.getQueryValue()); + if (pools != null && pools.length > 0) + { + List poolList = Arrays.asList(pools); %> +

Peptide P<%=bean.getQueryValue()%> is a member of these Peptide Pools:

+ <% for (PeptidePool pool : poolList) + {%> +
+ + + <% }} // end if statement %> +
+ <%= textLink("PP"+pool.getPeptide_pool_id(), + "displayPeptidePoolInformation.view?peptide_pool_id=" +pool.getPeptide_pool_id()) %> - + <%=pool.getPeptide_pool_name()%> + <%=pool.getPool_type_desc()%> +
\ No newline at end of file diff --git a/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp new file mode 100644 index 00000000..89a42a24 --- /dev/null +++ b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp @@ -0,0 +1,133 @@ +<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> +<%@ page import="org.labkey.api.view.HttpView"%> +<%@ page import="org.scharp.atlas.pepdb.PepDBManager" %> +<%@ page import="org.scharp.atlas.pepdb.model.PeptideGroup" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBBaseController.*" %> +<%@ page import="java.sql.SQLException" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBSchema" %> +<%@ page import="org.labkey.api.view.JspView" %> +<%@ page import="org.scharp.atlas.pepdb.model.PeptidePool" %> +<%@ page import="org.scharp.atlas.pepdb.model.ProteinCategory" %> +<%@ page extends="org.labkey.api.jsp.JspBase" %> +<% + JspView me = (JspView) HttpView.currentView(); + PeptideQueryForm bean = me.getModelBean(); + if(bean.getMessage() != null){ +%> +<%=bean.getMessage()%><%}%> + + +
+

Search for Peptides using different criteria :



+ + + + + + + + + + + <%if(bean.getQueryKey() != null && bean.getQueryKey().equals(PepDBSchema.COLUMN_PROTEIN_CAT_ID)){%> + + + + + + + + + + + <%}%> + <%if(bean.getQueryKey() != null && bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)){%> + + + + + + <%}%> + + + + +
Search Criteria : + +
+ <%=(bean.getQueryKey() != null && bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE))?" Equals / Contains : ":" Equals : "%> + + + <%if(bean.getQueryKey() == null || bean.getQueryKey().length() == 0){ %> + + <%} + else if(bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)){ + PeptideGroup[] peptideGroups = new PeptideGroup[0]; + try { + peptideGroups = PepDBManager.getPeptideGroups(); + } catch (SQLException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + }%> + + <% } + else if (bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_POOL_ID)) { + PeptidePool[] peptidePools = new PeptidePool[0]; + try { + peptidePools = PepDBManager.getPeptidePools(); + } catch (SQLException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } %> + + <% } + else if (bean.getQueryKey().equals(PepDBSchema.COLUMN_PROTEIN_CAT_ID)) { + ProteinCategory[] proteinCategories = new ProteinCategory[0]; + try { + proteinCategories = PepDBManager.getProteinCategory(); + } catch (SQLException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } %> + + <%} + else if(bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE) || bean.getQueryKey().equals(PepDBSchema.COLUMN_PARENT_SEQUENCE) || bean.getQueryKey().equals(PepDBSchema.COLUMN_CHILD_SEQUENCE)){%> + "/>   + <%}%> +
+ Amino Acid Start Position: + "/> +
+ Amino Acid End Position: + "/> +
+ Lab ID: + "/> +
+ +
+
From 12126acce03e5ca8855837e4f4668545d52613ac Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Tue, 3 Sep 2013 20:41:51 +0000 Subject: [PATCH 002/587] Update external modules for new build method SVN r28159 |2013-09-03 20:41:51 +0000 --- .gitattributes | 1 - build.xml | 67 -------------------------------------------------- 2 files changed, 68 deletions(-) delete mode 100644 build.xml diff --git a/.gitattributes b/.gitattributes index be1312f2..84585cbc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,5 @@ * text=auto !eol /PepDB.iml -text -/build.xml -text lib/jstl.jar -text lib/standard.jar -text /module.properties -text diff --git a/build.xml b/build.xml deleted file mode 100644 index 161021f2..00000000 --- a/build.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 1fb306fa270e91ddca3229e0ff0928d4c6da4d10 Mon Sep 17 00:00:00 2001 From: Scott Langley Date: Mon, 16 Sep 2013 18:58:41 +0000 Subject: [PATCH 003/587] Fix for RT#110519: Specify modules as PostgreSQL-only. SVN r28397 |2013-09-16 18:58:41 +0000 --- module.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/module.properties b/module.properties index c92ebaaa..ad58f873 100644 --- a/module.properties +++ b/module.properties @@ -1 +1,2 @@ ModuleClass: org.scharp.atlas.pepdb.PepDBModule +SupportedDatabases : pgsql From c2bda6ece3dd038c6a1603e4d21513ae149f3fa0 Mon Sep 17 00:00:00 2001 From: Dax Hawkins Date: Mon, 25 Nov 2013 18:59:38 +0000 Subject: [PATCH 004/587] merge Scharp external module changes from modules13.3 (29650) including release 13.3 (29578, 29579, 2980) SVN r29654 |2013-11-25 18:59:38 +0000 --- src/org/scharp/atlas/pepdb/PepDBController.java | 3 ++- src/org/scharp/atlas/pepdb/PoolImporter.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 04771153..5fd2cce6 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -373,7 +373,8 @@ public ModelAndView getView(PeptideQueryForm form, BindException errors) throws rgn.getButtonBar(DataRegion.MODE_GRID).add(insert); DisplayColumn col = rgn.getDisplayColumn(PepDBSchema.COLUMN_PEPTIDE_GROUP_NAME); ActionURL displayAction = new ActionURL(DisplayPeptideGroupInformationAction.class, getContainer()); - col.setURL(displayAction.toString() + "?" + PepDBSchema.COLUMN_PEPTIDE_GROUP_ID + "=${" + PepDBSchema.COLUMN_PEPTIDE_GROUP_ID + "}"); + displayAction.addParameter(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID,"${" + PepDBSchema.COLUMN_PEPTIDE_GROUP_ID + "}"); + col.setURL(displayAction.toString()); GridView gridView = new GridView(rgn, errors); gridView.setTitle("All the Peptide Groups in the System are : "); return gridView; diff --git a/src/org/scharp/atlas/pepdb/PoolImporter.java b/src/org/scharp/atlas/pepdb/PoolImporter.java index c4d4c2f5..8ad7cf8d 100644 --- a/src/org/scharp/atlas/pepdb/PoolImporter.java +++ b/src/org/scharp/atlas/pepdb/PoolImporter.java @@ -316,7 +316,7 @@ private boolean validatePPLine(String line,Errors errors,int lineNo,HashMap Date: Thu, 5 Dec 2013 21:57:15 +0000 Subject: [PATCH 005/587] Bulk migration - replace calls to SimpleFilter that were passing string arguments to use FieldKeys instead SVN r29804 |2013-12-05 21:57:15 +0000 --- src/org/scharp/atlas/pepdb/PepDBManager.java | 27 ++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBManager.java b/src/org/scharp/atlas/pepdb/PepDBManager.java index 1850fc2f..42a66824 100644 --- a/src/org/scharp/atlas/pepdb/PepDBManager.java +++ b/src/org/scharp/atlas/pepdb/PepDBManager.java @@ -6,6 +6,7 @@ import org.apache.log4j.Logger; import org.labkey.api.data.*; +import org.labkey.api.query.FieldKey; import org.labkey.api.security.User; import org.scharp.atlas.pepdb.model.*; @@ -117,8 +118,8 @@ public static Parent parentExists(Parent p) throws SQLException { TableInfo tInfo = PepDBSchema.getInstance().getTableInfoParent(); Parent dbParent = null; - SimpleFilter sFilter = new SimpleFilter("peptide_id",p.getPeptide_id()); - sFilter.addCondition("linked_parent",p.getLinked_parent()); + SimpleFilter sFilter = new SimpleFilter(FieldKey.fromParts("peptide_id"), p.getPeptide_id()); + sFilter.addCondition(FieldKey.fromParts("linked_parent"), p.getLinked_parent()); Parent[] dbParents = Table.select(tInfo, tInfo.getColumns("peptide_id,linked_parent") ,sFilter,null,Parent.class); @@ -130,7 +131,7 @@ public static Parent parentExists(Parent p) throws SQLException public static Peptides peptideExists(Peptides p,TableInfo tInfo) throws SQLException { Peptides dbPeptide = null; - SimpleFilter sFilter = new SimpleFilter("peptide_sequence",p.getPeptide_sequence()); + SimpleFilter sFilter = new SimpleFilter(FieldKey.fromParts("peptide_sequence"), p.getPeptide_sequence()); Peptides[] dbPeptides = Table.select(tInfo, tInfo.getColumns(),sFilter,null,Peptides.class); if(dbPeptides != null && dbPeptides.length>0) @@ -141,8 +142,8 @@ public static Peptides peptideExists(Peptides p,TableInfo tInfo) throws SQLExcep public static Source insertSource(User user, Source src) throws Exception { TableInfo tInfo = PepDBSchema.getInstance().getTableInfoSource(); - SimpleFilter sFilter = new SimpleFilter("peptide_group_id",src.getPeptide_group_id()); - sFilter.addCondition("peptide_id",src.getPeptide_id()); + SimpleFilter sFilter = new SimpleFilter(FieldKey.fromParts("peptide_group_id"), src.getPeptide_group_id()); + sFilter.addCondition(FieldKey.fromParts("peptide_id"), src.getPeptide_id()); Source dbSrc ; Source[] dbSrcs = Table.select(tInfo,tInfo.getColumns(),sFilter,null,Source.class); if(dbSrcs != null && dbSrcs.length>0) @@ -167,8 +168,8 @@ public static PeptidePool insertPeptidePool(User u,PeptidePool pPool) throws Exc public static PeptidePoolAssignment insertPeptidesInPool(User user,PeptidePoolAssignment src) throws Exception{ TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPoolAssignment(); - SimpleFilter sFilter = new SimpleFilter("peptide_pool_id",src.getPeptide_pool_id()); - sFilter.addCondition("peptide_id",src.getPeptide_id()); + SimpleFilter sFilter = new SimpleFilter(FieldKey.fromParts("peptide_pool_id"), src.getPeptide_pool_id()); + sFilter.addCondition(FieldKey.fromParts("peptide_id"), src.getPeptide_id()); PeptidePoolAssignment dbSrc ; PeptidePoolAssignment[] dbSrcs = Table.select(tInfo,tInfo.getColumns(),sFilter,null,PeptidePoolAssignment.class); if(dbSrcs != null && dbSrcs.length>0) @@ -183,7 +184,7 @@ public static Source[] getSourcesForPeptide(String peptideId) Source[] sources = null; try { - SimpleFilter sfilter = new SimpleFilter("peptide_id", Integer.parseInt(peptideId)); + SimpleFilter sfilter = new SimpleFilter(FieldKey.fromParts("peptide_id"), Integer.parseInt(peptideId)); TableInfo tInfo = PepDBSchema.getInstance().getTableInfoViewGroupPeptides(); sources = Table.select(tInfo, tInfo.getColumns("peptide_group_id,peptide_id_in_group,peptide_group_name,frequency_number,frequency_number_date"), @@ -205,7 +206,7 @@ public static Source[] getSourcesForPeptide(String peptideId) public static PeptidePool[] getPoolsForPeptide(String peptideId) throws SQLException { - SimpleFilter sfilter = new SimpleFilter("peptide_id", Integer.parseInt(peptideId)); + SimpleFilter sfilter = new SimpleFilter(FieldKey.fromParts("peptide_id"), Integer.parseInt(peptideId)); TableInfo tInfo = PepDBSchema.getInstance().getTableInfoViewPoolPeptides(); PeptidePool[] pools = Table.select(tInfo,Table.ALL_COLUMNS,sfilter,null,PeptidePool.class); if (pools == null || pools.length < 1) @@ -359,8 +360,8 @@ public static int updateInCurrentFile(User user) throws SQLException public static Source getSource(Integer peptideId, Integer groupId) throws SQLException { TableInfo tInfo = PepDBSchema.getInstance().getTableInfoSource(); - SimpleFilter sFilter = new SimpleFilter("peptide_group_id",groupId); - sFilter.addCondition("peptide_id",peptideId); + SimpleFilter sFilter = new SimpleFilter(FieldKey.fromParts("peptide_group_id"), groupId); + sFilter.addCondition(FieldKey.fromParts("peptide_id"), peptideId); Source dbSrc ; Source[] dbSrcs = Table.select(tInfo,tInfo.getColumns(),sFilter,null,Source.class); if(dbSrcs != null && dbSrcs.length>0) @@ -372,7 +373,7 @@ public static Source getSource(Integer peptideId, Integer groupId) throws SQLExc public static PeptidePool[] getChildrenPools(String peptidePoolId) throws SQLException { - SimpleFilter sfilter = new SimpleFilter("parent_pool_id", Integer.parseInt(peptidePoolId)); + SimpleFilter sfilter = new SimpleFilter(FieldKey.fromParts("parent_pool_id"), Integer.parseInt(peptidePoolId)); TableInfo tInfo = PepDBSchema.getInstance().getTableInfoViewPoolDetails(); PeptidePool[] pools = Table.select(tInfo,Table.ALL_COLUMNS,sfilter,null,PeptidePool.class); if (pools == null || pools.length < 1) @@ -382,7 +383,7 @@ public static PeptidePool[] getChildrenPools(String peptidePoolId) throws SQLExc public static Integer[] getPeptidesInPool(Integer peptidePoolId) throws SQLException { - SimpleFilter sfilter = new SimpleFilter("peptide_pool_id", peptidePoolId); + SimpleFilter sfilter = new SimpleFilter(FieldKey.fromParts("peptide_pool_id"), peptidePoolId); TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPoolAssignment(); Integer[] peptideIds = Table.executeArray(tInfo,"peptide_id",sfilter,null,Integer.class); if (peptideIds == null || peptideIds.length < 1) From b56597a9527112c76448dd3e14080a11fa5944d8 Mon Sep 17 00:00:00 2001 From: Nick Kerr Date: Fri, 7 Mar 2014 22:55:09 +0000 Subject: [PATCH 006/587] Buttons in externalModules. SVN r31369 |2014-03-07 22:55:09 +0000 --- src/org/scharp/atlas/pepdb/view/importPeptides.jsp | 2 +- src/org/scharp/atlas/pepdb/view/importPools.jsp | 2 +- src/org/scharp/atlas/pepdb/view/index.jsp | 2 +- src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/view/importPeptides.jsp b/src/org/scharp/atlas/pepdb/view/importPeptides.jsp index 8f45f4c7..f7496e3e 100644 --- a/src/org/scharp/atlas/pepdb/view/importPeptides.jsp +++ b/src/org/scharp/atlas/pepdb/view/importPeptides.jsp @@ -39,7 +39,7 @@ - <%= generateSubmitButton("Import Peptides") %> <%= generateButton("Back", "begin.view") %> + <%= button("Import Peptides").submit(true) %> <%= button("Back").href("begin.view") %>
Note: The File must be .txt extension and It should be tab delimited.

diff --git a/src/org/scharp/atlas/pepdb/view/importPools.jsp b/src/org/scharp/atlas/pepdb/view/importPools.jsp index 758d4f16..a2dd3e98 100644 --- a/src/org/scharp/atlas/pepdb/view/importPools.jsp +++ b/src/org/scharp/atlas/pepdb/view/importPools.jsp @@ -36,7 +36,7 @@ - <%= generateSubmitButton("Import Peptide Pools") %> <%= generateButton("Back", "begin.view") %> + <%= button("Import Peptide Pools").submit(true) %> <%= button("Back").href("begin.view") %>
Note: The File must be .txt extension and It should be tab delimited.

diff --git a/src/org/scharp/atlas/pepdb/view/index.jsp b/src/org/scharp/atlas/pepdb/view/index.jsp index 8af64fce..7c113dc6 100644 --- a/src/org/scharp/atlas/pepdb/view/index.jsp +++ b/src/org/scharp/atlas/pepdb/view/index.jsp @@ -34,7 +34,7 @@ PepDBController.DisplayPeptideForm form = (PepDBController.DisplayPeptideForm) (HttpView.currentModel()); %>
-Lookup Peptide by Id: "/>   <%= generateSubmitButton("Find") %> +Lookup Peptide by Id: "/>   <%= button("Find").submit(true) %>

Peptide Pools :

diff --git a/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp b/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp index 9649d5a4..71f9169a 100644 --- a/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp +++ b/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp @@ -8,4 +8,4 @@ PeptideGroup[] peptides = (PeptideGroup[]) context.get("peptides"); %> This container contains <%= peptides.length %> peptide groups.
-<%= generateButton("View Grid", new ActionURL("PepDB", "showAllPeptideGroups", context.getContainer())) %> \ No newline at end of file +<%= button("View Grid").href(new ActionURL("PepDB", "showAllPeptideGroups", context.getContainer())) %> \ No newline at end of file From d004519848f1649f4b4b6ea1e0a366497080ea00 Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Mon, 2 Jun 2014 15:45:57 +0000 Subject: [PATCH 007/587] Issue 20245: **Moving container with assay data to a location where the design is no longer in scope is broken SVN r32717 |2014-06-02 15:45:57 +0000 --- .../scharp/atlas/pepdb/PepDBContainerListener.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBContainerListener.java b/src/org/scharp/atlas/pepdb/PepDBContainerListener.java index 437c0dd0..14509ff1 100644 --- a/src/org/scharp/atlas/pepdb/PepDBContainerListener.java +++ b/src/org/scharp/atlas/pepdb/PepDBContainerListener.java @@ -1,11 +1,14 @@ package org.scharp.atlas.pepdb; +import org.jetbrains.annotations.NotNull; import org.labkey.api.data.ContainerManager.ContainerListener; import org.labkey.api.data.Container; import org.labkey.api.security.User; import org.apache.log4j.Logger; import java.beans.PropertyChangeEvent; +import java.util.Collection; +import java.util.Collections; /** * Created by IntelliJ IDEA. @@ -31,7 +34,14 @@ public void containerDeleted(Container c, User user) public void containerMoved(Container c, Container oldParent, User user) { } - + + @NotNull + @Override + public Collection canMove(Container c, Container newParent, User user) + { + return Collections.emptyList(); + } + public void propertyChange(PropertyChangeEvent evt) { } From 30d7257c4c2e21885fb9e95ad6d7faad03f2a2d8 Mon Sep 17 00:00:00 2001 From: Matthew Bellew Date: Wed, 20 Aug 2014 15:17:46 +0000 Subject: [PATCH 008/587] CSRF for all site admin actions use everywhere SVN r33967 |2014-08-20 15:17:46 +0000 --- src/org/scharp/atlas/pepdb/view/importPeptides.jsp | 4 ++-- src/org/scharp/atlas/pepdb/view/importPools.jsp | 4 ++-- src/org/scharp/atlas/pepdb/view/index.jsp | 4 ++-- src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/view/importPeptides.jsp b/src/org/scharp/atlas/pepdb/view/importPeptides.jsp index f7496e3e..9f435656 100644 --- a/src/org/scharp/atlas/pepdb/view/importPeptides.jsp +++ b/src/org/scharp/atlas/pepdb/view/importPeptides.jsp @@ -11,7 +11,7 @@ PepDBController.FileForm bean = me.getModelBean(); %> -
+
@@ -44,5 +44,5 @@
Note: The File must be .txt extension and It should be tab delimited.

- + diff --git a/src/org/scharp/atlas/pepdb/view/importPools.jsp b/src/org/scharp/atlas/pepdb/view/importPools.jsp index a2dd3e98..65102f94 100644 --- a/src/org/scharp/atlas/pepdb/view/importPools.jsp +++ b/src/org/scharp/atlas/pepdb/view/importPools.jsp @@ -11,7 +11,7 @@ PepDBController.FileForm bean = me.getModelBean(); %> -
+
@@ -41,5 +41,5 @@
Note: The File must be .txt extension and It should be tab delimited.

- + diff --git a/src/org/scharp/atlas/pepdb/view/index.jsp b/src/org/scharp/atlas/pepdb/view/index.jsp index 7c113dc6..c22939f5 100644 --- a/src/org/scharp/atlas/pepdb/view/index.jsp +++ b/src/org/scharp/atlas/pepdb/view/index.jsp @@ -33,9 +33,9 @@ <% PepDBController.DisplayPeptideForm form = (PepDBController.DisplayPeptideForm) (HttpView.currentModel()); %> -
+ Lookup Peptide by Id: "/>   <%= button("Find").submit(true) %> - +

Peptide Pools :

    diff --git a/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp index 89a42a24..d752ebd9 100644 --- a/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp +++ b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp @@ -22,7 +22,7 @@ } -
    +

    Search for Peptides using different criteria :



    @@ -130,4 +130,4 @@
    - +
    From 91c99a9fee0690b2c02086bd5b9b97be5315ec70 Mon Sep 17 00:00:00 2001 From: Karl Lum Date: Fri, 3 Oct 2014 20:40:32 +0000 Subject: [PATCH 009/587] Pepdb module changes to implement rollup queries and expose pepdb schema to query (read only) SVN r34645 |2014-10-03 20:40:32 +0000 --- .gitattributes | 1 + .../dbscripts/postgresql/pepdb-create.sql | 59 +++++++++++++++++++ .../dbscripts/postgresql/pepdb-drop.sql | 6 +- resources/schemas/pepdb.xml | 56 +++++++++++++++++- src/org/scharp/atlas/pepdb/PepDBModule.java | 26 +++++--- .../atlas/pepdb/query/PepDBQuerySchema.java | 50 ++++++++++++++++ 6 files changed, 188 insertions(+), 10 deletions(-) create mode 100644 src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java diff --git a/.gitattributes b/.gitattributes index 84585cbc..8d274570 100644 --- a/.gitattributes +++ b/.gitattributes @@ -37,6 +37,7 @@ src/org/scharp/atlas/pepdb/model/Peptides.java -text src/org/scharp/atlas/pepdb/model/PoolType.java -text src/org/scharp/atlas/pepdb/model/ProteinCategory.java -text src/org/scharp/atlas/pepdb/model/Source.java -text +src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java -text src/org/scharp/atlas/pepdb/view/PepDBWebPart.java -text src/org/scharp/atlas/pepdb/view/importPeptides.jsp -text src/org/scharp/atlas/pepdb/view/importPools.jsp -text diff --git a/resources/schemas/dbscripts/postgresql/pepdb-create.sql b/resources/schemas/dbscripts/postgresql/pepdb-create.sql index 6c2e685a..a1bcfa3c 100644 --- a/resources/schemas/dbscripts/postgresql/pepdb-create.sql +++ b/resources/schemas/dbscripts/postgresql/pepdb-create.sql @@ -9,3 +9,62 @@ CREATE VIEW pepdb.pool_details AS CREATE VIEW pepdb.pool_peptides AS SELECT src.peptide_pool_assignment_id, src.peptide_id, src.peptide_pool_id, p.peptide_sequence, p.protein_cat_id, pg.peptide_group_id, pg.peptide_id_in_group, p.sequence_length, p.amino_acid_start_pos, p.amino_acid_end_pos, p.child, p.parent, p.peptide_flag, p.peptide_notes, pp.pool_type_id, pp.peptide_pool_name, pt.pool_type_desc, pp.archived FROM ((pepdb.peptide_pool_assignment src LEFT JOIN (pepdb.peptide_pool pp LEFT JOIN pepdb.pool_type pt ON ((pp.pool_type_id = pt.pool_type_id))) ON ((src.peptide_pool_id = pp.peptide_pool_id))) LEFT JOIN pepdb.peptide_group_assignment pg ON ((src.peptide_group_assignment_id = pg.peptide_group_assignment_id))), pepdb.peptides p WHERE (src.peptide_id = p.peptide_id); + +CREATE VIEW pepdb.peptideGroupRollup AS + SELECT + pg.created, + pg.createdBy, + pg.modified, + pg.modifiedBy, + pg.peptide_group_id, + pg.peptide_group_name AS name, + seq_ref, + p.pathogen_desc AS Pathogen, + c.clade_desc AS Clade, + gt.group_type_desc AS GroupType, + ar.pep_align_ref_desc AS AlignRef + FROM pepdb.peptide_group pg + LEFT JOIN pepdb.pathogen p ON pg.pathogen_id = p.pathogen_id + LEFT JOIN pepdb.clade c ON pg.clade_id = c.clade_id + LEFT JOIN pepdb.group_type gt ON pg.group_type_id = gt.group_type_id + LEFT JOIN pepdb.pep_align_ref ar ON pg.pep_align_ref_id = ar.pep_align_ref_id; + +CREATE VIEW pepdb.peptidePoolRollup AS + SELECT + pp.created, + pp.createdBy, + pp.modified, + pp.modifiedBy, + pp.peptide_pool_id, + pp.peptide_pool_name AS Name, + comment, + pt.pool_type_desc AS PoolType, + archived, + parent_pool_id, + matrix_peptide_pool_id + FROM pepdb.peptide_pool pp + JOIN pepdb.pool_type pt ON pp.pool_type_id = pt.pool_type_id; + +CREATE VIEW pepdb.peptideRollup AS + SELECT + p.created, + p.createdBy, + p.modified, + p.modifiedBy, + p.peptide_id, + peptide_sequence, + protein_cat_desc AS ProteinCategory, + amino_acid_start_pos, + amino_acid_end_pos, + sequence_length, + child, + parent, + src_file_name, + storage_location, + optimal_epitope_list_desc AS OptimalEpitopeList, + hla_restriction, + peptide_flag, + peptide_notes + FROM pepdb.peptides p + LEFT JOIN pepdb.protein_category pc ON p.protein_cat_id = pc.protein_cat_id + LEFT JOIN pepdb.optimal_epitope_list el ON p.optimal_epitope_list_id = el.optimal_epitope_list_id; diff --git a/resources/schemas/dbscripts/postgresql/pepdb-drop.sql b/resources/schemas/dbscripts/postgresql/pepdb-drop.sql index 776177e9..bac42b4c 100644 --- a/resources/schemas/dbscripts/postgresql/pepdb-drop.sql +++ b/resources/schemas/dbscripts/postgresql/pepdb-drop.sql @@ -4,4 +4,8 @@ DROP VIEW IF EXISTS pepdb.parent_child_details; DROP VIEW IF EXISTS pepdb.pool_details; -DROP VIEW IF EXISTS pepdb.pool_peptides; \ No newline at end of file +DROP VIEW IF EXISTS pepdb.pool_peptides; + +DROP VIEW IF EXISTS pepdb.peptideGroupRollup; +DROP VIEW IF EXISTS pepdb.peptidePoolRollup; +DROP VIEW IF EXISTS pepdb.peptideRollup; \ No newline at end of file diff --git a/resources/schemas/pepdb.xml b/resources/schemas/pepdb.xml index 8e5112e2..a25b06db 100644 --- a/resources/schemas/pepdb.xml +++ b/resources/schemas/pepdb.xml @@ -587,4 +587,58 @@ - \ No newline at end of file + + + + + diff --git a/src/org/scharp/atlas/pepdb/PepDBModule.java b/src/org/scharp/atlas/pepdb/PepDBModule.java index 5c5960f6..dfebdffa 100644 --- a/src/org/scharp/atlas/pepdb/PepDBModule.java +++ b/src/org/scharp/atlas/pepdb/PepDBModule.java @@ -1,17 +1,26 @@ package org.scharp.atlas.pepdb; -import org.labkey.api.module.DefaultModule; -import org.labkey.api.module.ModuleContext; -import org.labkey.api.data.ContainerManager; +import org.apache.log4j.Logger; import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerManager; import org.labkey.api.data.DbSchema; -import org.labkey.api.view.*; +import org.labkey.api.module.DefaultModule; +import org.labkey.api.module.ModuleContext; import org.labkey.api.util.PageFlowUtil; +import org.labkey.api.view.BaseWebPartFactory; +import org.labkey.api.view.Portal; +import org.labkey.api.view.ViewContext; +import org.labkey.api.view.WebPartFactory; +import org.labkey.api.view.WebPartView; +import org.scharp.atlas.pepdb.query.PepDBQuerySchema; import org.scharp.atlas.pepdb.view.PepDBWebPart; -import org.apache.log4j.Logger; -import java.util.*; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; public class PepDBModule extends DefaultModule { @@ -25,12 +34,13 @@ public String getName() public double getVersion() { - return 2.21; + return 2.22; } protected void init() { - addController("pepdb", PepDBController.class); + addController("pepdb", PepDBController.class); + PepDBQuerySchema.register(this); } protected Collection createWebPartFactories() diff --git a/src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java b/src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java new file mode 100644 index 00000000..4f0b00ee --- /dev/null +++ b/src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java @@ -0,0 +1,50 @@ +package org.scharp.atlas.pepdb.query; + +import org.jetbrains.annotations.NotNull; +import org.labkey.api.data.ColumnInfo; +import org.labkey.api.data.Container; +import org.labkey.api.data.DatabaseTableType; +import org.labkey.api.data.DbSchema; +import org.labkey.api.data.TableInfo; +import org.labkey.api.module.Module; +import org.labkey.api.query.DefaultSchema; +import org.labkey.api.query.QuerySchema; +import org.labkey.api.query.QueryUpdateService; +import org.labkey.api.query.SimpleQueryUpdateService; +import org.labkey.api.query.SimpleUserSchema; +import org.labkey.api.security.User; + +/** + * Created by klum on 9/17/2014. + */ +public class PepDBQuerySchema extends SimpleUserSchema +{ + public static final String SCHEMA_NAME = "pepdb"; + public static final String SCHEMA_DESCR = "Provides peptide and peptide pool information."; + + static public void register(Module module) + { + DefaultSchema.registerProvider(SCHEMA_NAME, new DefaultSchema.SchemaProvider(module) + { + public QuerySchema createSchema(DefaultSchema schema, Module module) + { + return new PepDBQuerySchema(schema.getUser(), schema.getContainer()); + } + }); + } + + public PepDBQuerySchema(User user, Container container) + { + super(SCHEMA_NAME, SCHEMA_DESCR, user, container, DbSchema.get("pepdb")); + } + + @Override + protected TableInfo createWrappedTable(String name, @NotNull TableInfo sourceTable) + { + TableInfo table = super.createWrappedTable(name, sourceTable); + if (table instanceof SimpleTable) + ((SimpleTable)table).setReadOnly(true); + + return table; + } +} From 74c2fcf3cb8e0d08832f83b65f34f6e4d6b11a66 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 19 Dec 2014 21:57:50 +0000 Subject: [PATCH 010/587] Eliminate crufty, old ACL-based hasPermission(User user, int perm) SVN r35699 |2014-12-19 21:57:50 +0000 --- src/org/scharp/atlas/pepdb/view/index.jsp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/view/index.jsp b/src/org/scharp/atlas/pepdb/view/index.jsp index c22939f5..bb43af47 100644 --- a/src/org/scharp/atlas/pepdb/view/index.jsp +++ b/src/org/scharp/atlas/pepdb/view/index.jsp @@ -1,8 +1,8 @@ <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page import="org.labkey.api.view.ViewContext"%> -<%@ page import="org.labkey.api.security.User" %> -<%@ page import="org.labkey.api.security.ACL" %> +<%@ page import="org.labkey.api.security.User"%> +<%@ page import="org.labkey.api.security.permissions.UpdatePermission" %> <%@ page import="org.labkey.api.view.HttpView" %> +<%@ page import="org.labkey.api.view.ViewContext" %> <%@ page import="org.scharp.atlas.pepdb.PepDBController" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> @@ -13,7 +13,7 @@

    Peptide Groups :

    • List Peptide Groups
    • - <%if(ctx.getContainer().hasPermission(user, ACL.PERM_UPDATE)){%> + <%if(ctx.getContainer().hasPermission(user, UpdatePermission.class)){%>
    • Insert a New Group
    • <%}else{%>
    • Insert a New Group
    • @@ -22,7 +22,7 @@

      Peptides :

      • Search for Peptides by Criteria
      • - <%if(ctx.getContainer().hasPermission(user, ACL.PERM_UPDATE)){%> + <%if(ctx.getContainer().hasPermission(user, UpdatePermission.class)){%>
      • Import Peptides
      • <%}else{%>
      • Import Peptides
      • @@ -39,7 +39,7 @@ Lookup Peptide by Id: Peptide Pools :
          - <%if(ctx.getContainer().hasPermission(user, ACL.PERM_UPDATE)){%> + <%if(ctx.getContainer().hasPermission(user, UpdatePermission.class)){%>
        • Import Peptide Pools
        • <%}else{%>
        • Import Peptide Pools
        • From c6825aa29351a01cc8c21b01f188403aa8e76d58 Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Mon, 29 Dec 2014 15:55:12 +0000 Subject: [PATCH 011/587] 21907 : Disallow colliding names for web parts We now register a single WebPartFactory per name. It specifies a defaultLocation, and additionalLocations if it wants to be available in multiple places within a portal. SVN r35756 |2014-12-29 15:55:12 +0000 --- src/org/scharp/atlas/pepdb/PepDBModule.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBModule.java b/src/org/scharp/atlas/pepdb/PepDBModule.java index dfebdffa..6bb4d860 100644 --- a/src/org/scharp/atlas/pepdb/PepDBModule.java +++ b/src/org/scharp/atlas/pepdb/PepDBModule.java @@ -45,13 +45,7 @@ protected void init() protected Collection createWebPartFactories() { - return new ArrayList(Arrays.asList(new BaseWebPartFactory("PepDB Summary") { - public WebPartView getWebPartView(ViewContext portalCtx, Portal.WebPart webPart) throws IllegalAccessException, InvocationTargetException - { - return new PepDBWebPart(); - } - }, - new BaseWebPartFactory("PepDB Summary", WebPartFactory.LOCATION_RIGHT) { + return new ArrayList(Arrays.asList(new BaseWebPartFactory("PepDB Summary", WebPartFactory.LOCATION_BODY, WebPartFactory.LOCATION_RIGHT) { { addLegacyNames("Narrow PepDB Summary"); } From 368e7d0022da334fa0208460960b2311642b5461 Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Mon, 29 Dec 2014 21:51:46 +0000 Subject: [PATCH 012/587] Fix NPE in ClientAPITest and others where WebPartFactory.getWebPartView SVN r35766 |2014-12-29 21:51:46 +0000 --- src/org/scharp/atlas/pepdb/PepDBModule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBModule.java b/src/org/scharp/atlas/pepdb/PepDBModule.java index 6bb4d860..c866557d 100644 --- a/src/org/scharp/atlas/pepdb/PepDBModule.java +++ b/src/org/scharp/atlas/pepdb/PepDBModule.java @@ -1,6 +1,7 @@ package org.scharp.atlas.pepdb; import org.apache.log4j.Logger; +import org.jetbrains.annotations.NotNull; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; import org.labkey.api.data.DbSchema; @@ -15,7 +16,6 @@ import org.scharp.atlas.pepdb.query.PepDBQuerySchema; import org.scharp.atlas.pepdb.view.PepDBWebPart; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -50,7 +50,7 @@ protected Collection createWebPartFactories() addLegacyNames("Narrow PepDB Summary"); } - public WebPartView getWebPartView(ViewContext portalCtx, Portal.WebPart webPart) throws IllegalAccessException, InvocationTargetException + public WebPartView getWebPartView(@NotNull ViewContext portalCtx, @NotNull Portal.WebPart webPart) { return new PepDBWebPart(); } From b0f4a79cd459de1bcbdd284caa5bb0277da509bc Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Tue, 6 Jan 2015 22:04:30 +0000 Subject: [PATCH 013/587] Migrate SCHARP modules to use Selector framework instead of deprecated Table select/execute methods SVN r35871 |2015-01-06 22:04:30 +0000 --- src/org/scharp/atlas/pepdb/PepDBManager.java | 232 +++++++++--------- .../atlas/pepdb/view/peptideGroupSelect.jsp | 24 +- 2 files changed, 118 insertions(+), 138 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBManager.java b/src/org/scharp/atlas/pepdb/PepDBManager.java index 42a66824..fe04e78a 100644 --- a/src/org/scharp/atlas/pepdb/PepDBManager.java +++ b/src/org/scharp/atlas/pepdb/PepDBManager.java @@ -1,14 +1,29 @@ package org.scharp.atlas.pepdb; -import java.sql.*; -import java.util.Arrays; -import java.util.HashMap; - import org.apache.log4j.Logger; -import org.labkey.api.data.*; +import org.labkey.api.data.Container; +import org.labkey.api.data.SQLFragment; +import org.labkey.api.data.SimpleFilter; +import org.labkey.api.data.Sort; +import org.labkey.api.data.SqlExecutor; +import org.labkey.api.data.SqlSelector; +import org.labkey.api.data.Table; +import org.labkey.api.data.TableInfo; +import org.labkey.api.data.TableSelector; import org.labkey.api.query.FieldKey; import org.labkey.api.security.User; -import org.scharp.atlas.pepdb.model.*; +import org.scharp.atlas.pepdb.model.OptimalEpitopeList; +import org.scharp.atlas.pepdb.model.Parent; +import org.scharp.atlas.pepdb.model.PeptideGroup; +import org.scharp.atlas.pepdb.model.PeptidePool; +import org.scharp.atlas.pepdb.model.PeptidePoolAssignment; +import org.scharp.atlas.pepdb.model.Peptides; +import org.scharp.atlas.pepdb.model.PoolType; +import org.scharp.atlas.pepdb.model.ProteinCategory; +import org.scharp.atlas.pepdb.model.Source; + +import java.sql.SQLException; +import java.util.HashMap; /** * Handles Data Operations. @@ -27,64 +42,49 @@ private PepDBManager() { } - public static PeptideGroup[] getPeptideGroups() throws SQLException + public static PeptideGroup[] getPeptideGroups() { Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID); - return Table.select(schema.getTableInfoPeptideGroups(), - schema.getTableInfoPeptideGroups().getColumns() - , null, sort, PeptideGroup.class); + return new TableSelector(schema.getTableInfoPeptideGroups(), null, sort).getArray(PeptideGroup.class); } - public static PeptidePool[] getPeptidePools() throws SQLException + public static PeptidePool[] getPeptidePools() { Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_POOL_ID); - return Table.select(schema.getTableInfoPeptidePools(), - schema.getTableInfoPeptidePools().getColumns() - , null, sort, PeptidePool.class); + return new TableSelector(schema.getTableInfoPeptidePools(), null, sort).getArray(PeptidePool.class); } - public static PoolType[] getPoolTypes() throws SQLException + + public static PoolType[] getPoolTypes() { Sort sort = new Sort(PepDBSchema.COLUMN_POOL_TYPE_ID); - return Table.select(schema.getTableInfoPoolType(), - schema.getTableInfoPoolType().getColumns() - , null, sort, PoolType.class); + return new TableSelector(schema.getTableInfoPoolType(), null, sort).getArray(PoolType.class); } - public static ProteinCategory[] getProteinCategory() throws SQLException + public static ProteinCategory[] getProteinCategory() { - return Table.select(schema.getTableInfoProteinCat(), - schema.getTableInfoProteinCat().getColumns(), null, - new Sort(PepDBSchema.COLUMN_PROTEIN_CAT_ID), ProteinCategory.class); + return new TableSelector(schema.getTableInfoProteinCat(), null, new Sort(PepDBSchema.COLUMN_PROTEIN_CAT_ID)).getArray(ProteinCategory.class); } - public static OptimalEpitopeList[] getOptimalEpitopeList() throws SQLException + public static OptimalEpitopeList[] getOptimalEpitopeList() { - return Table.select(schema.getTableInfoOptimalEpitopeList(), - schema.getTableInfoOptimalEpitopeList().getColumns(), null, - new Sort(PepDBSchema.COLUMN_OPTIMAL_EPITOPE_LIST_ID), OptimalEpitopeList.class); + return new TableSelector(schema.getTableInfoOptimalEpitopeList(), null, new Sort(PepDBSchema.COLUMN_OPTIMAL_EPITOPE_LIST_ID)).getArray(OptimalEpitopeList.class); } - public static Peptides[] getPeptides() throws SQLException + public static Peptides[] getPeptides() { TableInfo tInfo = schema.getTableInfoPeptides(); - return Table.select(tInfo,tInfo.getColumns(), - null,new Sort("peptide_id"),Peptides.class); + return new TableSelector(tInfo, null, new Sort("peptide_id")).getArray(Peptides.class); } /** * @param peptideGroup * @return the number of peptides in a given group - * @throws SQLException */ - public static Integer getCount(Integer peptideGroup) throws SQLException + public static Integer getCount(Integer peptideGroup) { SimpleFilter containerFilter = new SimpleFilter(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID, peptideGroup); - Integer count = Table.executeSingleton(schema.getSchema(), - "SELECT COUNT(*) FROM " - + PepDBSchema.getInstance().getTableInfoViewGroupPeptides() - + " WHERE peptide_group_id=? ", new Object[] { peptideGroup }, - Integer.class); - return count; + + return (int)new TableSelector(PepDBSchema.getInstance().getTableInfoViewGroupPeptides(), new SimpleFilter("peptide_group_id", peptideGroup), null).getRowCount(); } public static Peptides insertPeptide(User user, Peptides p) throws Exception @@ -99,7 +99,7 @@ public static Peptides insertPeptide(User user, Peptides p) throws Exception public static Peptides updatePeptide(User user, Peptides p) throws Exception { TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPeptides(); - return Table.update(user,tInfo,p,p.getPeptide_id()); + return Table.update(user, tInfo, p, p.getPeptide_id()); } public static PeptidePool updatePeptidePool(User user, PeptidePool p) throws Exception @@ -114,27 +114,24 @@ public static Parent insertParent(User user,Parent parent) throws Exception return Table.insert(user,tInfo,parent); } - public static Parent parentExists(Parent p) throws SQLException + public static Parent parentExists(Parent p) { TableInfo tInfo = PepDBSchema.getInstance().getTableInfoParent(); Parent dbParent = null; SimpleFilter sFilter = new SimpleFilter(FieldKey.fromParts("peptide_id"), p.getPeptide_id()); sFilter.addCondition(FieldKey.fromParts("linked_parent"), p.getLinked_parent()); - Parent[] dbParents = Table.select(tInfo, - tInfo.getColumns("peptide_id,linked_parent") - ,sFilter,null,Parent.class); - if(dbParents != null && dbParents.length>0) + Parent[] dbParents = new TableSelector(tInfo, tInfo.getColumns("peptide_id,linked_parent"), sFilter, null).getArray(Parent.class); + if (dbParents.length > 0) dbParent = dbParents[0]; return dbParent; } - public static Peptides peptideExists(Peptides p,TableInfo tInfo) throws SQLException + public static Peptides peptideExists(Peptides p, TableInfo tInfo) { Peptides dbPeptide = null; SimpleFilter sFilter = new SimpleFilter(FieldKey.fromParts("peptide_sequence"), p.getPeptide_sequence()); - Peptides[] dbPeptides = Table.select(tInfo, - tInfo.getColumns(),sFilter,null,Peptides.class); - if(dbPeptides != null && dbPeptides.length>0) + Peptides[] dbPeptides = new TableSelector(tInfo, sFilter, null).getArray(Peptides.class); + if(dbPeptides.length > 0) dbPeptide = dbPeptides[0]; return dbPeptide; } @@ -145,8 +142,8 @@ public static Source insertSource(User user, Source src) throws Exception SimpleFilter sFilter = new SimpleFilter(FieldKey.fromParts("peptide_group_id"), src.getPeptide_group_id()); sFilter.addCondition(FieldKey.fromParts("peptide_id"), src.getPeptide_id()); Source dbSrc ; - Source[] dbSrcs = Table.select(tInfo,tInfo.getColumns(),sFilter,null,Source.class); - if(dbSrcs != null && dbSrcs.length>0) + Source[] dbSrcs = new TableSelector(tInfo, sFilter, null).getArray(Source.class); + if(dbSrcs.length > 0) dbSrc = dbSrcs[0]; else dbSrc = Table.insert(user,tInfo,src); @@ -154,25 +151,24 @@ public static Source insertSource(User user, Source src) throws Exception java.sql.Timestamp date = new java.sql.Timestamp(System.currentTimeMillis()); String sql = "UPDATE pepdb.peptide_group_assignment SET in_current_file = true,modified = ?,modifiedby = ? WHERE peptide_group_assignment_id=?"; Object[] params = { date,user.getUserId(),dbSrc.getPeptide_group_assignment_id() }; - Table.execute(schema.getSchema(),sql,params); + new SqlExecutor(schema.getSchema()).execute(sql, params); return dbSrc; } public static PeptidePool insertPeptidePool(User u,PeptidePool pPool) throws Exception { TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPeptidePools(); - PeptidePool dbPool = null; - dbPool = Table.insert(u,tInfo,pPool); - return dbPool; + return Table.insert(u,tInfo,pPool); } - public static PeptidePoolAssignment insertPeptidesInPool(User user,PeptidePoolAssignment src) throws Exception{ + public static PeptidePoolAssignment insertPeptidesInPool(User user,PeptidePoolAssignment src) throws Exception + { TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPoolAssignment(); SimpleFilter sFilter = new SimpleFilter(FieldKey.fromParts("peptide_pool_id"), src.getPeptide_pool_id()); sFilter.addCondition(FieldKey.fromParts("peptide_id"), src.getPeptide_id()); PeptidePoolAssignment dbSrc ; - PeptidePoolAssignment[] dbSrcs = Table.select(tInfo,tInfo.getColumns(),sFilter,null,PeptidePoolAssignment.class); - if(dbSrcs != null && dbSrcs.length>0) + PeptidePoolAssignment[] dbSrcs = new TableSelector(tInfo, sFilter, null).getArray(PeptidePoolAssignment.class); + if(dbSrcs.length > 0) return null; else dbSrc = Table.insert(user,tInfo,src); @@ -186,111 +182,106 @@ public static Source[] getSourcesForPeptide(String peptideId) { SimpleFilter sfilter = new SimpleFilter(FieldKey.fromParts("peptide_id"), Integer.parseInt(peptideId)); TableInfo tInfo = PepDBSchema.getInstance().getTableInfoViewGroupPeptides(); - sources = Table.select(tInfo, - tInfo.getColumns("peptide_group_id,peptide_id_in_group,peptide_group_name,frequency_number,frequency_number_date"), - sfilter, - null, Source.class); - if (sources == null || sources.length < 1) + sources = new TableSelector(tInfo, tInfo.getColumns("peptide_group_id,peptide_id_in_group,peptide_group_name,frequency_number,frequency_number_date"), sfilter, null).getArray(Source.class); + if (sources.length < 1) return null; } catch (NumberFormatException e) { log.error(e.getMessage(), e); } - catch (SQLException e) - { - log.error(e.getMessage(), e); - } return sources; } - public static PeptidePool[] getPoolsForPeptide(String peptideId) throws SQLException + public static PeptidePool[] getPoolsForPeptide(String peptideId) { SimpleFilter sfilter = new SimpleFilter(FieldKey.fromParts("peptide_id"), Integer.parseInt(peptideId)); TableInfo tInfo = PepDBSchema.getInstance().getTableInfoViewPoolPeptides(); - PeptidePool[] pools = Table.select(tInfo,Table.ALL_COLUMNS,sfilter,null,PeptidePool.class); - if (pools == null || pools.length < 1) + PeptidePool[] pools = new TableSelector(tInfo, sfilter, null).getArray(PeptidePool.class); + if (pools.length < 1) return null; return pools; } - public static PeptideGroup insertGroup(Container c, User user, PeptideGroup pg) throws SQLException { + public static PeptideGroup insertGroup(Container c, User user, PeptideGroup pg) throws SQLException + { PeptideGroup resultGroup = Table.insert(user, PepDBSchema.getInstance().getTableInfoPeptideGroups(), pg); return resultGroup; } - public static PeptideGroup updatePeptideGroup(User user,PeptideGroup pg) throws SQLException{ + public static PeptideGroup updatePeptideGroup(User user,PeptideGroup pg) throws SQLException + { return Table.update(user, PepDBSchema.getInstance().getTableInfoPeptideGroups(),pg,pg.getPeptide_group_id()); } - public static HashMap getPeptideSequenceMap() throws SQLException + public static HashMap getPeptideSequenceMap() { - HashMap peptideSequenceMap = new HashMap(); - Peptides[] peptides = getPeptides(); + HashMap peptideSequenceMap = new HashMap<>(); + Peptides[] peptides = getPeptides(); for(Peptides peptide : peptides) - peptideSequenceMap.put(peptide.getPeptide_sequence().trim().toUpperCase(),peptide); + peptideSequenceMap.put(peptide.getPeptide_sequence().trim().toUpperCase(), peptide); return peptideSequenceMap; } - public static HashMap getPeptideGroupMap() throws SQLException + public static HashMap getPeptideGroupMap() { - HashMap groupsMap = new HashMap(); + HashMap groupsMap = new HashMap<>(); PeptideGroup[] peptideGroups = getPeptideGroups(); for(PeptideGroup pGroup:peptideGroups) - groupsMap.put(pGroup.getPeptide_group_name().trim().toUpperCase(),pGroup); + groupsMap.put(pGroup.getPeptide_group_name().trim().toUpperCase(), pGroup); return groupsMap; } - public static HashMap getPeptidePoolMap() throws SQLException + public static HashMap getPeptidePoolMap() { - HashMap poolsMap = new HashMap(); + HashMap poolsMap = new HashMap<>(); PeptidePool[] peptidePools = getPeptidePools(); for(PeptidePool pPool:peptidePools) - poolsMap.put(pPool.getPeptide_pool_name().trim().toUpperCase(),pPool); + poolsMap.put(pPool.getPeptide_pool_name().trim().toUpperCase(), pPool); return poolsMap; } - public static HashMap getPoolTypeMap() throws SQLException + public static HashMap getPoolTypeMap() { - HashMap poolTypeMap = new HashMap(); + HashMap poolTypeMap = new HashMap<>(); PoolType[] poolTypes = getPoolTypes(); for(PoolType pType : poolTypes) - poolTypeMap.put(pType.getPool_type_desc().trim().toUpperCase(),pType); + poolTypeMap.put(pType.getPool_type_desc().trim().toUpperCase(), pType); return poolTypeMap; } - public static HashMap getProteinCatMap() throws SQLException + public static HashMap getProteinCatMap() { - HashMap proCatMap = new HashMap(); + HashMap proCatMap = new HashMap<>(); ProteinCategory[] proCats = getProteinCategory(); for(ProteinCategory proCat : proCats) - proCatMap.put(proCat.getProtein_cat_desc().trim().toUpperCase(),proCat); + proCatMap.put(proCat.getProtein_cat_desc().trim().toUpperCase(), proCat); return proCatMap; } - public static HashMap getProteinCatIDMap() throws SQLException + public static HashMap getProteinCatIDMap() { - HashMap proCatMap = new HashMap(); + HashMap proCatMap = new HashMap<>(); ProteinCategory[] proCats = getProteinCategory(); for(ProteinCategory proCat : proCats) - proCatMap.put(proCat.getProtein_cat_id(),proCat); + proCatMap.put(proCat.getProtein_cat_id(), proCat); return proCatMap; } - public static HashMap getOptimalEpitopeListMap() throws SQLException + public static HashMap getOptimalEpitopeListMap() { - HashMap optListMap = new HashMap(); + HashMap optListMap = new HashMap<>(); OptimalEpitopeList[] optLists = getOptimalEpitopeList(); for(OptimalEpitopeList optList : optLists) - optListMap.put(optList.getOptimal_epitope_list_desc().trim().toUpperCase(),optList); + optListMap.put(optList.getOptimal_epitope_list_desc().trim().toUpperCase(), optList); return optListMap; } - public static Peptides getPeptideById(Integer peptideId) throws SQLException + public static Peptides getPeptideById(Integer peptideId) { TableInfo tInfo = schema.getTableInfoPeptides(); - SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PEPTIDE_ID,peptideId); - return Table.selectObject(tInfo,sFilter,null, Peptides.class); + SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PEPTIDE_ID, peptideId); + return new TableSelector(tInfo, sFilter, null).getObject(Peptides.class); } public static Peptides[] getParentPeptides(Peptides p) throws SQLException @@ -298,7 +289,7 @@ public static Peptides[] getParentPeptides(Peptides p) throws SQLException TableInfo tInfo = schema.getTableInfoPeptides(); String sql = "select * from pepdb.peptides where peptides.protein_cat_id = ? and ? between peptides.amino_acid_start_pos-2 and peptides.amino_acid_end_pos\n" + "and ? between peptides.amino_acid_start_pos and peptides.amino_acid_end_pos+2 and child = false"; - Peptides[] peptides = Table.executeQuery(schema.getSchema(),sql,new Object[]{p.getProtein_cat_id(),p.getAmino_acid_start_pos(),p.getAmino_acid_end_pos()},Peptides.class); + Peptides[] peptides = new SqlSelector(schema.getSchema(), sql, new Object[]{p.getProtein_cat_id(),p.getAmino_acid_start_pos(),p.getAmino_acid_end_pos()}).getArray(Peptides.class); return peptides; } @@ -307,25 +298,25 @@ public static Peptides[] getHyphanatedParents(Peptides p) throws SQLException String sql = "select * from "+schema.getTableInfoPeptides()+" where peptides.protein_cat_id = ? " + " and peptides.peptide_sequence LIKE '%"+p.getPeptide_sequence()+"%' " + " and child = false"; - Peptides[] peptides = Table.executeQuery(schema.getSchema(),sql,new Object[]{p.getProtein_cat_id()},Peptides.class); + Peptides[] peptides = new SqlSelector(schema.getSchema(), sql, new Object[]{p.getProtein_cat_id()}).getArray(Peptides.class); return peptides; } - public static PeptideGroup getPeptideGroupByID(Integer groupId) throws SQLException + public static PeptideGroup getPeptideGroupByID(Integer groupId) { TableInfo tInfo = schema.getTableInfoPeptideGroups(); SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID,groupId); - return Table.selectObject(tInfo,sFilter,null, PeptideGroup.class); + return new TableSelector(tInfo, sFilter, null).getObject(PeptideGroup.class); } - public static ProteinCategory getProCatByID(Integer procatId) throws SQLException + public static ProteinCategory getProCatByID(Integer procatId) { TableInfo tInfo = schema.getTableInfoProteinCat(); SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PROTEIN_CAT_ID,procatId); - return Table.selectObject(tInfo,sFilter,null, ProteinCategory.class); + return new TableSelector(tInfo, sFilter, null).getObject(ProteinCategory.class); } - public static PeptideGroup getPeptideGroupByName(PeptideGroup pg) throws SQLException + public static PeptideGroup getPeptideGroupByName(PeptideGroup pg) { PeptideGroup [] groups = null; TableInfo tInfo = schema.getTableInfoPeptideGroups(); @@ -336,59 +327,60 @@ public static PeptideGroup getPeptideGroupByName(PeptideGroup pg) throws SQLExce sql.append("AND peptide_group_id != ?"); sql.add(pg.getPeptide_group_id()); } - groups = Table.executeQuery(schema.getSchema(),sql, PeptideGroup.class); - if(groups != null && groups.length >0) - return groups[0]; - else return null; + groups = new SqlSelector(schema.getSchema(), sql).getArray(PeptideGroup.class); + + if (groups.length > 0) + return groups[0]; + else + return null; } - public static PeptidePool getPeptidePoolByID(Integer poolId) throws SQLException + public static PeptidePool getPeptidePoolByID(Integer poolId) { TableInfo tInfo = schema.getTableInfoPeptidePools(); SimpleFilter sFilter = new SimpleFilter(PepDBSchema.COLUMN_PEPTIDE_POOL_ID,poolId); - return Table.selectObject(tInfo,sFilter,null, PeptidePool.class); + return new TableSelector(tInfo, sFilter, null).getObject(PeptidePool.class); } - public static int updateInCurrentFile(User user) throws SQLException + public static int updateInCurrentFile(User user) { java.sql.Timestamp date = new java.sql.Timestamp(System.currentTimeMillis()); String sql = "UPDATE pepdb.peptide_group_assignment SET in_current_file = false,modified = ?,modifiedby = ?"; Object[] params = { date,user.getUserId() }; - return Table.execute(PepDBSchema.getInstance().getSchema(),sql,params); + return new SqlExecutor(PepDBSchema.getInstance().getSchema()).execute(sql,params); } - public static Source getSource(Integer peptideId, Integer groupId) throws SQLException + public static Source getSource(Integer peptideId, Integer groupId) { TableInfo tInfo = PepDBSchema.getInstance().getTableInfoSource(); SimpleFilter sFilter = new SimpleFilter(FieldKey.fromParts("peptide_group_id"), groupId); sFilter.addCondition(FieldKey.fromParts("peptide_id"), peptideId); Source dbSrc ; - Source[] dbSrcs = Table.select(tInfo,tInfo.getColumns(),sFilter,null,Source.class); - if(dbSrcs != null && dbSrcs.length>0) + Source[] dbSrcs = new TableSelector(tInfo, sFilter, null).getArray(Source.class); + if(dbSrcs.length > 0) dbSrc = dbSrcs[0]; else dbSrc = null; return dbSrc; } - public static PeptidePool[] getChildrenPools(String peptidePoolId) throws SQLException + public static PeptidePool[] getChildrenPools(String peptidePoolId) { SimpleFilter sfilter = new SimpleFilter(FieldKey.fromParts("parent_pool_id"), Integer.parseInt(peptidePoolId)); TableInfo tInfo = PepDBSchema.getInstance().getTableInfoViewPoolDetails(); - PeptidePool[] pools = Table.select(tInfo,Table.ALL_COLUMNS,sfilter,null,PeptidePool.class); - if (pools == null || pools.length < 1) + PeptidePool[] pools = new TableSelector(tInfo, sfilter, null).getArray(PeptidePool.class); + if (pools.length < 1) return null; return pools; } - public static Integer[] getPeptidesInPool(Integer peptidePoolId) throws SQLException + public static Integer[] getPeptidesInPool(Integer peptidePoolId) { SimpleFilter sfilter = new SimpleFilter(FieldKey.fromParts("peptide_pool_id"), peptidePoolId); TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPoolAssignment(); - Integer[] peptideIds = Table.executeArray(tInfo,"peptide_id",sfilter,null,Integer.class); - if (peptideIds == null || peptideIds.length < 1) + Integer[] peptideIds = new TableSelector(tInfo.getColumn("peptide_id"), sfilter, null).getArray(Integer.class); + if (peptideIds.length < 1) return null; return peptideIds; } - } \ No newline at end of file diff --git a/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp index d752ebd9..37f12ba3 100644 --- a/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp +++ b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp @@ -50,12 +50,8 @@ <%} else if(bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)){ - PeptideGroup[] peptideGroups = new PeptideGroup[0]; - try { - peptideGroups = PepDBManager.getPeptideGroups(); - } catch (SQLException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - }%> + PeptideGroup[] peptideGroups = PepDBManager.getPeptideGroups(); + %> <% } else if (bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_POOL_ID)) { - PeptidePool[] peptidePools = new PeptidePool[0]; - try { - peptidePools = PepDBManager.getPeptidePools(); - } catch (SQLException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - } %> + PeptidePool[] peptidePools = PepDBManager.getPeptidePools(); + %> <% } else if (bean.getQueryKey().equals(PepDBSchema.COLUMN_PROTEIN_CAT_ID)) { - ProteinCategory[] proteinCategories = new ProteinCategory[0]; - try { - proteinCategories = PepDBManager.getProteinCategory(); - } catch (SQLException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - } %> + ProteinCategory[] proteinCategories = PepDBManager.getProteinCategory(); + %> From b517f190e74e3432589a583b3b1e2605efb89d52 Mon Sep 17 00:00:00 2001 From: Dave Bradlee Date: Mon, 3 Aug 2015 18:33:21 +0000 Subject: [PATCH 019/587] Merge from modules15.2 thru 39045 SVN r39049 |2015-08-03 18:33:21 +0000 --- src/org/scharp/atlas/pepdb/PepDBController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 80896e8c..9f93bfdd 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -1,6 +1,7 @@ package org.scharp.atlas.pepdb; import org.labkey.api.action.*; +import org.labkey.api.security.RequiresPermission; import org.labkey.api.view.*; import org.labkey.api.data.*; import org.labkey.api.query.FieldKey; @@ -560,7 +561,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(UpdatePermission.class) + @RequiresPermission(UpdatePermission.class) public class InsertPeptideGroupAction extends FormViewAction { public ModelAndView getView(PeptideGroupForm form, boolean reshow, BindException errors) throws Exception @@ -586,6 +587,7 @@ public boolean handlePost(PeptideGroupForm form, BindException errors) throws Ex { PeptideGroup group = form.getBean(); group = PepDBManager.insertGroup(getContainer(), getUser(), group); + group.setContainerId(getContainer().getId()); form.setBean(group); return true; } From 91f0a4ee98e176f031ee5c0d1002b22191fbe1d8 Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Tue, 25 Aug 2015 00:09:50 +0000 Subject: [PATCH 020/587] Remove about 1500 deprecation warnings by migrating usages of RequiresPermissionClass to RequiresPermission. Support for @RequiresPermissionClass remains unchanged. SVN r39347 |2015-08-25 00:09:50 +0000 --- .../scharp/atlas/pepdb/PepDBController.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 9f93bfdd..f26cca09 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -5,7 +5,7 @@ import org.labkey.api.view.*; import org.labkey.api.data.*; import org.labkey.api.query.FieldKey; -import org.labkey.api.security.RequiresPermissionClass; +import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.security.permissions.UpdatePermission; import org.labkey.api.attachments.AttachmentFile; @@ -65,7 +65,7 @@ protected HttpServletResponse getResponse() return getViewContext().getResponse(); } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class BeginAction extends SimpleViewAction { public ModelAndView getView(DisplayPeptideForm form, BindException errors) throws Exception @@ -80,7 +80,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class SearchForPeptidesAction extends FormViewAction { public ModelAndView getView(PeptideQueryForm form, boolean reshow, BindException errors) throws Exception @@ -134,7 +134,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class GetPeptidesAction extends SimpleViewAction { public ModelAndView getView(PeptideQueryForm form, BindException errors) throws Exception @@ -186,7 +186,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class DisplayPeptideAction extends SimpleViewAction { public ModelAndView getView(DisplayPeptideForm form, BindException errors) throws Exception @@ -246,7 +246,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(UpdatePermission.class) + @RequiresPermission(UpdatePermission.class) public class EditPeptideAction extends FormViewAction { public ModelAndView getView(PeptideForm form, boolean reshow, BindException errors) throws Exception @@ -306,7 +306,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(UpdatePermission.class) + @RequiresPermission(UpdatePermission.class) public class EditPeptidePoolAction extends FormViewAction { public ModelAndView getView(PeptidePoolForm form, boolean reshow, BindException errors) throws Exception @@ -354,7 +354,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class ShowAllPeptideGroupsAction extends SimpleViewAction { public ModelAndView getView(PeptideQueryForm form, BindException errors) throws Exception @@ -387,7 +387,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class DisplayPeptideGroupInformationAction extends SimpleViewAction { public ModelAndView getView(PeptideAndGroupForm form, BindException errors) throws Exception @@ -427,7 +427,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class ShowAllPeptidePoolsAction extends SimpleViewAction { public ModelAndView getView(PeptideQueryForm form, BindException errors) throws Exception @@ -456,7 +456,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class DisplayPeptidePoolInformationAction extends SimpleViewAction { public ModelAndView getView(PeptideAndPoolForm form, BindException errors) throws Exception @@ -503,7 +503,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(UpdatePermission.class) + @RequiresPermission(UpdatePermission.class) public class UpdatePeptideGroupAction extends FormViewAction { public ModelAndView getView(PeptideGroupForm form, boolean reshow, BindException errors) throws Exception @@ -618,7 +618,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(UpdatePermission.class) + @RequiresPermission(UpdatePermission.class) public class ImportPeptidesAction extends FormViewAction { private List resultPeptides = new LinkedList(); @@ -678,7 +678,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(UpdatePermission.class) + @RequiresPermission(UpdatePermission.class) public class DisplayResultAction extends SimpleViewAction { public ModelAndView getView(FileForm form, BindException errors) throws Exception @@ -695,7 +695,7 @@ public NavTree appendNavTrail(NavTree root) } } - @RequiresPermissionClass(UpdatePermission.class) + @RequiresPermission(UpdatePermission.class) public class ImportPeptidePoolsAction extends FormViewAction { ActionURL url = null; @@ -754,7 +754,7 @@ public NavTree appendNavTrail(NavTree root) } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public abstract class PeptideExcelExportAction extends ExportAction { public void printExcel(Object bean, HttpServletResponse response, BindException errors, PeptideQueryForm form) throws Exception @@ -788,7 +788,7 @@ public void printExcel(Object bean, HttpServletResponse response, BindException } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class PeptidesInPoolExcelExportAction extends PeptideExcelExportAction { public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception @@ -801,7 +801,7 @@ public void export(Object bean, HttpServletResponse response, BindException erro } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class PoolsInPoolExcelExportAction extends PeptideExcelExportAction { public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception @@ -814,7 +814,7 @@ public void export(Object bean, HttpServletResponse response, BindException erro } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class PeptideDefaultExcelExportAction extends PeptideExcelExportAction { public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception @@ -829,7 +829,7 @@ public void export(Object bean, HttpServletResponse response, BindException erro - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public abstract class PeptideTextExportAction extends ExportAction { public void printText(Object bean, HttpServletResponse response, BindException errors, PeptideQueryForm form) throws Exception @@ -859,7 +859,7 @@ public void printText(Object bean, HttpServletResponse response, BindException e } } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class PeptidesInPoolTextExportAction extends PeptideTextExportAction { public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception @@ -872,7 +872,7 @@ public void export(Object bean, HttpServletResponse response, BindException erro } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class PoolsInPoolTextExportAction extends PeptideTextExportAction { public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception @@ -885,7 +885,7 @@ public void export(Object bean, HttpServletResponse response, BindException erro } } - @RequiresPermissionClass(ReadPermission.class) + @RequiresPermission(ReadPermission.class) public class PeptideDefaultTextExportAction extends PeptideTextExportAction { public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception From 9f037e9ca64b3ea5db73ae2e59660ede58a0f2de Mon Sep 17 00:00:00 2001 From: Scott Langley Date: Fri, 30 Oct 2015 21:54:48 +0000 Subject: [PATCH 021/587] RT#130395: Omit filter where clasue when no peptide sequence is specified SVN r40759 |2015-10-30 21:54:48 +0000 --- .../scharp/atlas/pepdb/PepDBController.java | 29 +++++++++++++++---- src/org/scharp/atlas/pepdb/PepDBModule.java | 2 +- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index f26cca09..f835c6d7 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -1009,27 +1009,44 @@ protected GridView getGridViewBySequence(PeptideQueryForm form, PropertyValues p form.setTInfo(tableInfo); _log.debug("Creating a Filter for peptideSequence." + PepDBSchema.COLUMN_PEPTIDE_SEQUENCE + ": " + form); SimpleFilter sFilter = new SimpleFilter(); - sFilter.addWhereClause(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE + " LIKE ?", new Object[]{"%" + (form.getQueryValue() == null || form.getQueryValue().length() == 0 ? "" : form.getQueryValue().toUpperCase()) + "%"}, - FieldKey.fromString(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE)); + + boolean sequenceIsEmpty = true; + String sequence = form.getQueryValue(); + if((sequence != null) && (!sequence.trim().isEmpty())){ + sequenceIsEmpty = false; + } + if(!sequenceIsEmpty) + { + sequence = sequence.trim().toUpperCase(); + sFilter.addWhereClause(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE + " LIKE ?", new Object[]{"%" + sequence + "%"}, + FieldKey.fromString(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE)); + } Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_ID); form.setFilter(sFilter); form.setCInfo(tableInfo.getColumns("peptide_id,peptide_sequence,protein_cat_id,peptide_group_name,peptide_id_in_group,pathogen_id," + "sequence_length,amino_acid_start_pos,amino_acid_end_pos,child,parent,peptide_flag,peptide_notes,optimal_epitope_list_id,hla_restriction")); form.setSort(sort); - if (form.getQueryValue() != null && form.getQueryValue().length() != 0) - form.setMessage("Peptides_WITH_Sequence_" + form.getQueryValue()); + if(!sequenceIsEmpty) + { + form.setMessage("Peptides_WITH_Sequence_" + sequence); + } else + { form.setMessage("All_Peptides_In_DB"); + } DataRegion rgn = getDataRegion(getContainer(), form); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); gridView.setFilter(sFilter); gridView.setSort(sort); - if (form.getQueryValue() != null && form.getQueryValue().length() != 0) + if(!sequenceIsEmpty) { gridView.setTitle( - "The Peptides Containing the Sequence string '" + form.getQueryValue() + "' are : "); + "The Peptides Containing the Sequence string '" + sequence + "' are : "); + } else + { gridView.setTitle("All the Peptides in Peptide DB : "); + } return gridView; } diff --git a/src/org/scharp/atlas/pepdb/PepDBModule.java b/src/org/scharp/atlas/pepdb/PepDBModule.java index 18e49acb..8a696ddd 100644 --- a/src/org/scharp/atlas/pepdb/PepDBModule.java +++ b/src/org/scharp/atlas/pepdb/PepDBModule.java @@ -34,7 +34,7 @@ public String getName() public double getVersion() { - return 2.23; + return 2.24; } protected void init() From e9dbca7a9476f69643a14c35aa95dccb777bf84b Mon Sep 17 00:00:00 2001 From: Scott Langley Date: Fri, 30 Oct 2015 22:28:38 +0000 Subject: [PATCH 022/587] RT#130395: Another tyr. Omit filter completely when no peptide sequence is specified SVN r40762 |2015-10-30 22:28:38 +0000 --- src/org/scharp/atlas/pepdb/PepDBController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index f835c6d7..037c869a 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -1020,9 +1020,10 @@ protected GridView getGridViewBySequence(PeptideQueryForm form, PropertyValues p sequence = sequence.trim().toUpperCase(); sFilter.addWhereClause(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE + " LIKE ?", new Object[]{"%" + sequence + "%"}, FieldKey.fromString(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE)); + form.setFilter(sFilter); } Sort sort = new Sort(PepDBSchema.COLUMN_PEPTIDE_ID); - form.setFilter(sFilter); + form.setCInfo(tableInfo.getColumns("peptide_id,peptide_sequence,protein_cat_id,peptide_group_name,peptide_id_in_group,pathogen_id," + "sequence_length,amino_acid_start_pos,amino_acid_end_pos,child,parent,peptide_flag,peptide_notes,optimal_epitope_list_id,hla_restriction")); form.setSort(sort); @@ -1037,9 +1038,9 @@ protected GridView getGridViewBySequence(PeptideQueryForm form, PropertyValues p DataRegion rgn = getDataRegion(getContainer(), form); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); - gridView.setFilter(sFilter); gridView.setSort(sort); if(!sequenceIsEmpty) { + gridView.setFilter(sFilter); gridView.setTitle( "The Peptides Containing the Sequence string '" + sequence + "' are : "); } From 8f6b2f7342d28950b25ac4133abba579c5644ed3 Mon Sep 17 00:00:00 2001 From: Dave Bradlee Date: Fri, 13 Nov 2015 22:53:56 +0000 Subject: [PATCH 023/587] Merge from modules15.3 thru 40987 SVN r40988 |2015-11-13 22:53:56 +0000 --- .../scharp/atlas/pepdb/PepDBController.java | 45 ++++++++++--------- src/org/scharp/atlas/pepdb/PepDBModule.java | 2 +- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 037c869a..bb7ab75f 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -5,7 +5,6 @@ import org.labkey.api.view.*; import org.labkey.api.data.*; import org.labkey.api.query.FieldKey; -import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.security.permissions.UpdatePermission; import org.labkey.api.attachments.AttachmentFile; @@ -44,6 +43,9 @@ public class PepDBController extends PepDBBaseController private static final String PAGE_PEPTIDE_GROUP_SELECT = "peptideGroupSelect.jsp"; private static final String PAGE_IMPORT_PEPTIDES = "importPeptides.jsp"; + // Maximum number of rows to display on a web page at once. Specifying Table.ALL_ROWS was causing a JavaScript timeout. + private static final int MAX_ROWS = 1000; + public PepDBController() { setActionResolver(_actionResolver); @@ -366,7 +368,7 @@ public ModelAndView getView(PeptideQueryForm form, BindException errors) throws form.setCInfo(columns); form.setSort(new Sort(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)); form.setMessage("AllPeptideGroups"); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); ActionURL insertUrl = new ActionURL(InsertPeptideGroupAction.class, getContainer()); ActionButton insert = new ActionButton(insertUrl, "Insert New Group"); @@ -439,7 +441,7 @@ public ModelAndView getView(PeptideQueryForm form, BindException errors) throws form.setCInfo(columns); form.setSort(new Sort(PepDBSchema.COLUMN_PEPTIDE_POOL_ID)); form.setMessage("AllPeptidePools"); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); ActionURL importUrl = new ActionURL(ImportPeptidePoolsAction.class, getContainer()); ActionButton importB = new ActionButton(importUrl, "Import New Pools"); @@ -467,7 +469,7 @@ public ModelAndView getView(PeptideAndPoolForm form, BindException errors) throw PropertyValues pv = this.getPropertyValues(); queryform.setTInfo(tableInfo); queryform.setCInfo(tableInfo.getColumns("peptide_pool_id,peptide_pool_name,pool_type_id,parent_pool_id,matrix_peptide_pool_id,comment,archived,createdby,created,modifiedby,modified")); - DataRegion rgn = getDataRegion(getContainer(), queryform); + DataRegion rgn = getDataRegion(getContainer(), queryform, Table.ALL_ROWS); rgn.setShowBorders(true); rgn.setShadeAlternatingRows(true); ButtonBar buttonBar = getButtonBar(); @@ -577,7 +579,7 @@ public ModelAndView getView(PeptideGroupForm form, boolean reshow, BindException TableInfo tInfo = PepDBSchema.getInstance().getTableInfoPeptideGroups(); qForm.setTInfo(tInfo); qForm.setCInfo(tInfo.getColumns()); - DataRegion rgn = getDataRegion(getContainer(), qForm); + DataRegion rgn = getDataRegion(getContainer(), qForm, Table.ALL_ROWS); rgn.setButtonBar(bb); InsertView iView = new InsertView(rgn, form, errors); return iView; @@ -762,7 +764,7 @@ public void printExcel(Object bean, HttpServletResponse response, BindException try { RenderContext context = new RenderContext(getViewContext()); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); context.setBaseFilter(form.getFilter()); context.setBaseSort(form.getSort()); ExcelWriter ew = new ExcelWriter(rgn.getResultSet(context), rgn.getDisplayColumns()); @@ -838,7 +840,7 @@ public void printText(Object bean, HttpServletResponse response, BindException e { ViewContext ctx = getViewContext(); RenderContext context = new RenderContext(getViewContext()); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); context.setBaseFilter(form.getFilter()); context.setBaseSort(form.getSort()); TSVGridWriter tsv = new TSVGridWriter(rgn.getResultSet(context), rgn.getDisplayColumns()); @@ -914,7 +916,7 @@ protected GridView getGridViewByLastImport(PeptideQueryForm form, PropertyValues form.setSort(sort); form.setMessage("Peptides_IN_Last_File"); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); gridView.setFilter(sFilter); @@ -942,7 +944,7 @@ protected GridView getGridViewByGroup(PeptideQueryForm form, PropertyValues pv) "sequence_length,amino_acid_start_pos,amino_acid_end_pos,child,parent,peptide_flag,peptide_notes,frequency_number,frequency_number_date")); form.setSort(sort); form.setMessage("Peptides_IN_Group_" + pg.getPeptide_group_name()); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); gridView.setFilter(sFilter); @@ -968,7 +970,7 @@ protected GridView getGridViewByPool(PeptideQueryForm form, PropertyValues pv) t "peptide_group_id,peptide_id_in_group,sequence_length,amino_acid_start_pos,amino_acid_end_pos,child,parent,peptide_flag,peptide_notes")); form.setSort(sort); form.setMessage("Peptides_IN_Pool_PP" + pp.getPeptide_pool_name()); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); ButtonBar bb = getGridButtonbar(pv); rgn.setButtonBar(bb, DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); @@ -992,7 +994,7 @@ protected GridView getGridViewByParentPool(PeptideQueryForm form, PropertyValues form.setCInfo(tableInfo.getColumns("peptide_pool_id,peptide_pool_name,pool_type_desc,parent_pool_id,parent_pool_name,matrix_peptide_pool_id,comment,archived,createdby,created,modifiedby,modified")); form.setSort(sort); form.setMessage("Peptides_WITH_Parent_Pool_PP" + form.getQueryValue()); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); gridView.setFilter(sFilter); @@ -1035,7 +1037,7 @@ protected GridView getGridViewBySequence(PeptideQueryForm form, PropertyValues p { form.setMessage("All_Peptides_In_DB"); } - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, MAX_ROWS); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); gridView.setSort(sort); @@ -1082,7 +1084,7 @@ protected GridView getGridViewByProtein(PeptideQueryForm form, PropertyValues pv "sequence_length,amino_acid_start_pos,amino_acid_end_pos,child,parent,peptide_flag,peptide_notes,optimal_epitope_list_id,hla_restriction")); form.setSort(sort); form.setMessage("Peptides_IN_Protein_" + form.getQueryValue()); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); gridView.setFilter(sFilter); @@ -1104,7 +1106,7 @@ protected GridView getGridViewByParentId(PeptideQueryForm form, PropertyValues p "child_aastart,child_aaend,child_peptide_flag,child_peptide_notes,child_optimal_epitope_list_id,child_hla_restriction")); form.setSort(sort); form.setMessage("CHILD_Peptides_WITH_Parent_P" + form.getQueryValue()); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); gridView.setFilter(sFilter); @@ -1127,7 +1129,7 @@ protected GridView getGridViewByParent(PeptideQueryForm form, PropertyValues pv) "child_aastart,child_aaend,child_peptide_flag,child_peptide_notes,child_optimal_epitope_list_id,child_hla_restriction")); form.setSort(sort); form.setMessage("CHILD_Peptides_WITH_Parent_Sequence_" + form.getQueryValue()); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); gridView.setFilter(sFilter); @@ -1150,7 +1152,7 @@ protected GridView getGridViewByChildId(PeptideQueryForm form, PropertyValues pv "parent_seq_length,parent_aastart,parent_aaend,parent_peptide_flag,parent_peptide_notes")); form.setSort(sort); form.setMessage("PARENT_Peptides_WITH_Child_P" + form.getQueryValue()); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); gridView.setFilter(sFilter); @@ -1173,7 +1175,7 @@ protected GridView getGridViewByChild(PeptideQueryForm form, PropertyValues pv) "parent_seq_length,parent_aastart,parent_aaend,parent_peptide_flag,parent_peptide_notes")); form.setSort(sort); form.setMessage("PARENT_Peptides_WITH_Child_Sequence_" + form.getQueryValue()); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); rgn.setButtonBar(getGridButtonbar(pv), DataRegion.MODE_GRID); GridView gridView = new GridView(rgn, (BindException) null); gridView.setFilter(sFilter); @@ -1189,7 +1191,7 @@ protected DetailsView getPeptideDetailsView(PeptideQueryForm form, Peptides p) t form.setTInfo(tableInfo); form.setCInfo(tableInfo.getColumns("peptide_id,peptide_sequence,protein_cat_id,sequence_length,amino_acid_start_pos," + "amino_acid_end_pos,child,parent,optimal_epitope_list_id,hla_restriction,storage_location,src_file_name,peptide_flag,peptide_notes,createdby,created,modifiedby,modified")); - DataRegion rgn = getDataRegion(getContainer(), form); + DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); rgn.setShowBorders(true); rgn.setShadeAlternatingRows(true); ButtonBar buttonBar = new ButtonBar(); @@ -1206,7 +1208,7 @@ protected DetailsView getPeptideDetailsView(PeptideQueryForm form, Peptides p) t return dataView; } - private DataRegion getDataRegion(Container c, PeptideQueryForm form) throws Exception + private DataRegion getDataRegion(Container c, PeptideQueryForm form, int maxRows) throws Exception { DataRegion rgn = new DataRegion(); List columnList = new ArrayList(); @@ -1241,7 +1243,7 @@ else if (PepDBSchema.COLUMN_PARENT_POOL_ID.equals(col.getName())) rgn.setDisplayColumns(displayColumnList); rgn.setShowBorders(true); rgn.setShadeAlternatingRows(true); - rgn.setMaxRows(Table.ALL_ROWS); + rgn.setMaxRows(maxRows); ViewContext ctx = getViewContext(); HttpSession session = ctx.getRequest().getSession(true); session.setAttribute("PEPTIDE_QUERY_FORM", form); @@ -1254,6 +1256,9 @@ else if (PepDBSchema.COLUMN_PARENT_POOL_ID.equals(col.getName())) qs.addAggregates(new Aggregate(ci, Aggregate.Type.COUNT)); qs.setMaxRows(Table.ALL_ROWS); rgn.setSettings(qs); + // We want MOST of the query settings into our dataregion settings, but we still want to paginate the rows. + rgn.setMaxRows(maxRows); + } return rgn; } diff --git a/src/org/scharp/atlas/pepdb/PepDBModule.java b/src/org/scharp/atlas/pepdb/PepDBModule.java index 8a696ddd..0c9633ba 100644 --- a/src/org/scharp/atlas/pepdb/PepDBModule.java +++ b/src/org/scharp/atlas/pepdb/PepDBModule.java @@ -34,7 +34,7 @@ public String getName() public double getVersion() { - return 2.24; + return 2.26; } protected void init() From a4b04bc56de6808f15cbb805950e1acb41f6b5a5 Mon Sep 17 00:00:00 2001 From: labkey-klum Date: Thu, 3 Dec 2015 17:43:39 -0800 Subject: [PATCH 024/587] Update .gitignore --- .gitignore | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..32858aad --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* From 6f1c67160debe8cf940acf2deaee4f9bb9c4d6a8 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 10 Dec 2015 11:18:50 -0800 Subject: [PATCH 025/587] Spec 24959 : Implement Data Finder for ITN --- module.properties | 6 + resources/web/study/Finder/data/Facet.js | 51 ++ .../web/study/Finder/data/FacetMember.js | 10 + resources/web/study/Finder/data/StudyCard.js | 11 + resources/web/study/Finder/dataFinder.css | 519 ++++++++++++++++++ .../study/Finder/panel/FacetPanelHeader.js | 100 ++++ .../web/study/Finder/panel/FacetSelection.js | 50 ++ resources/web/study/Finder/panel/Facets.js | 214 ++++++++ resources/web/study/Finder/panel/Finder.js | 65 +++ .../study/Finder/panel/SelectionSummary.js | 53 ++ resources/web/study/Finder/panel/Studies.js | 37 ++ .../web/study/Finder/panel/StudyCards.js | 97 ++++ .../study/Finder/panel/StudyPanelHeader.js | 45 ++ .../TrialShareContainerListener.java | 55 ++ .../trialshare/TrialShareController.java | 223 ++++++++ .../labkey/trialshare/TrialShareManager.java | 32 ++ .../labkey/trialshare/TrialShareModule.java | 85 +++ .../labkey/trialshare/TrialShareSchema.java | 49 ++ src/org/labkey/trialshare/data/StudyBean.java | 121 ++++ .../trialshare/data/StudyFacetBean.java | 109 ++++ .../trialshare/data/StudyFacetMember.java | 52 ++ .../trialshare/data/StudyPersonnelBean.java | 119 ++++ .../trialshare/data/StudyPubmedBean.java | 119 ++++ src/org/labkey/trialshare/view/dataFinder.jsp | 133 +++++ .../labkey/trialshare/view/studyDetail.jsp | 138 +++++ trialShare.iml | 13 + 26 files changed, 2506 insertions(+) create mode 100644 module.properties create mode 100644 resources/web/study/Finder/data/Facet.js create mode 100644 resources/web/study/Finder/data/FacetMember.js create mode 100644 resources/web/study/Finder/data/StudyCard.js create mode 100644 resources/web/study/Finder/dataFinder.css create mode 100644 resources/web/study/Finder/panel/FacetPanelHeader.js create mode 100644 resources/web/study/Finder/panel/FacetSelection.js create mode 100644 resources/web/study/Finder/panel/Facets.js create mode 100644 resources/web/study/Finder/panel/Finder.js create mode 100644 resources/web/study/Finder/panel/SelectionSummary.js create mode 100644 resources/web/study/Finder/panel/Studies.js create mode 100644 resources/web/study/Finder/panel/StudyCards.js create mode 100644 resources/web/study/Finder/panel/StudyPanelHeader.js create mode 100644 src/org/labkey/trialshare/TrialShareContainerListener.java create mode 100644 src/org/labkey/trialshare/TrialShareController.java create mode 100644 src/org/labkey/trialshare/TrialShareManager.java create mode 100644 src/org/labkey/trialshare/TrialShareModule.java create mode 100644 src/org/labkey/trialshare/TrialShareSchema.java create mode 100644 src/org/labkey/trialshare/data/StudyBean.java create mode 100644 src/org/labkey/trialshare/data/StudyFacetBean.java create mode 100644 src/org/labkey/trialshare/data/StudyFacetMember.java create mode 100644 src/org/labkey/trialshare/data/StudyPersonnelBean.java create mode 100644 src/org/labkey/trialshare/data/StudyPubmedBean.java create mode 100644 src/org/labkey/trialshare/view/dataFinder.jsp create mode 100644 src/org/labkey/trialshare/view/studyDetail.jsp create mode 100644 trialShare.iml diff --git a/module.properties b/module.properties new file mode 100644 index 00000000..68aaaf00 --- /dev/null +++ b/module.properties @@ -0,0 +1,6 @@ +ModuleClass: org.labkey.trialshare.TrialShareModule +Label: Data Finder for ITN +Description: Provides a web part for exploring the studies and manuscripts available within ITN. +URL: http://example.com/FIXME +License: Apache 2.0 +LicenseURL: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/resources/web/study/Finder/data/Facet.js b/resources/web/study/Finder/data/Facet.js new file mode 100644 index 00000000..76e6e109 --- /dev/null +++ b/resources/web/study/Finder/data/Facet.js @@ -0,0 +1,51 @@ +Ext4.define('LABKEY.study.data.Facet', { + extend: 'Ext.data.Model', + + fields: [ + {name: 'name'}, + {name: 'caption'}, + {name: 'pluralName'}, + {name: 'filterOptions'}, + {name: 'members'}, + {name: 'currentFilters'} + ], + + associations: [ + { + type: 'hasMany', + model: 'LABKEY.study.data.FacetMember', + name: 'members', + associationKey: 'members' + }, + { + type: 'hasMany', + model: 'LABKEY.study.data.FacetFilter', + name: 'filterOptions', + associationKey: 'filterOptions' + }, + { + type: 'hasMany', + model: 'LABKEY.study.data.FacetFilter', + name: 'currentFilters', + associationKey: 'currentFilters' + } + ] +}); + +Ext4.define('LABKEY.study.data.FacetMember', { + extend: 'Ext.data.Model', + + fields: [ + {name: 'name'}, + {name: 'count'} + ] +}); + + +Ext4.define('LABKEY.study.data.FacetFilter', { + extend: 'Ext.data.Model', + fields :[ + {name: 'type'}, + {name: 'caption'} + ] +}); diff --git a/resources/web/study/Finder/data/FacetMember.js b/resources/web/study/Finder/data/FacetMember.js new file mode 100644 index 00000000..19753f90 --- /dev/null +++ b/resources/web/study/Finder/data/FacetMember.js @@ -0,0 +1,10 @@ +Ext4.define('LABKEY.study.data.FacetMember', { + extend: 'Ext.data.Model', + + fields: [ + {name: 'name'}, + {name: 'count'}, + {name: 'percent'}, + {name: 'currentFilters'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/StudyCard.js b/resources/web/study/Finder/data/StudyCard.js new file mode 100644 index 00000000..53e97998 --- /dev/null +++ b/resources/web/study/Finder/data/StudyCard.js @@ -0,0 +1,11 @@ +Ext4.define('LABKEY.study.data.StudyCard', { + extend: 'Ext.data.Model', + + fields: [ + {name: 'accession'}, + {name: 'title'}, + {name: 'url'}, + {name: 'investigator'}, + {name: 'isLoaded', type: 'boolean'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css new file mode 100644 index 00000000..01721a3c --- /dev/null +++ b/resources/web/study/Finder/dataFinder.css @@ -0,0 +1,519 @@ +.labkey-studies-panel { + background-color: #ffffff +} + +DIV.labkey-data-finder-outer { + min-height:100px; + min-width:400px; +} + +DIV.labkey-study-cards { + background-color: #ffffff +} + +DIV.labkey-study-facets { + background-color: #ffffff +} + +.labkey-data-finder-inner +{ + background-color:#ffffff; + height:100%; + width:100%; +} + +TABLE.labkey-data-finder +{ + height: 450px; + width:100%; + padding:3pt; +} + +.labkey-filter-message +{ + float:left; +} + +/* labkey-study-card */ +.labkey-study-card-highlight +{ + color:black; + font-variant:small-caps; +} + + +.labkey-study-card-summary, +.labkey-study-card-accession +{ + float:left; +} + +.labkey-study-card-goto, +.labkey-study-card-pi +{ + float:right; +} +.labkey-study-card-divider, +.labkey-study-card-description +{ + clear:both; +} +DIV.labkey-study-card +{ + background-color:#F8F8F8; + border:1pt solid #AAAAAA; + padding: 5pt; + margin: 5pt; + float:left; + position:relative; + width:171pt; + height:140pt; + overflow-y:hidden; +} + +DIV.loaded, +SPAN.loaded +{ + background-color:rgba(81, 158, 218, 0.2); +} + + +TD.study-panel +{ + vertical-align: top; +} + +TD.study-panel > DIV +{ + height:100%; + overflow-y:scroll; +} + +SPAN.labkey-study-search +{ + position: relative; +} + +/* search area */ +DIV.search-box +{ + float:left; + padding:10pt; +} +INPUT.search-box +{ + width:200pt; +} + +DIV.studyfinder-header +{ + float:left; + padding-right:10pt; +} + +DIV.search-message +{ + padding-left:10pt; +} + +.selection-panel +{ + vertical-align: text-top; + width:220pt; + height:100%; + background-color: white; +} + +.selection-panel > DIV +{ + height:100%; + overflow-y:auto; +} + +DIV.help-links +{ + float:right; + vertical-align: text-top; + text-align: right; +} + +DIV.summary LI.member +{ + clear:both; + padding:2pt; + margin:1px 0px 1px 0px; + border:1px solid #ffffff; + height:14pt; + position:relative; + width:100%; +} + +/* facets */ + +.facet-selection-header +{ + background-color: white; +} + +.clear-filter +{ + background-color: white; + float:right; +} + +DIV.facet-summary, +DIV.facet +{ + max-width:200pt; + border:solid 2pt rgb(220, 220, 220); +} + +.active +{ + cursor:pointer; +} +DIV.facet-header +{ + padding:4pt; + background-color: rgb(240, 240, 240); +} + +DIV.facet-header .facet-caption +{ + font-weight:400; +} + +DIV.facet-header .labkey-filter-options +{ + font-size: 11px; + padding: 3px 0px 0px 20px; +} + +DIV.facet-summary UL, +DIV.facet UL +{ + list-style-type:none; + padding-left:0; + padding-right:5pt; + margin:0; +} + +DIV.facet-summary LI.member +{ + clear:both; + cursor:default; + padding:2pt; + margin:1px 0px 1px 0px; + border:1px solid #ffffff; + height:14pt; + position:relative; + width:100%; +} + +DIV.facet LI.member +{ + clear:both; + cursor:pointer; + padding:2pt; + margin:1px 0px 1px 0px; + border:1px solid #ffffff; + height:14pt; + position:relative; + width:100%; +} +DIV.facet.collapsed LI.member +{ + display:none; +} +DIV.facet.collapsed .fa-plus-square +{ + float:left; + display:inline; +} +DIV.facet.collapsed .fa-minus-square +{ + float:left; + display:none; +} +DIV.expanded .fa-plus-square +{ + float:left; + display:none; +} +DIV.expanded .fa-minus-square +{ + float:left; + display:inline; +} +DIV.facet.collapsed LI.member.selected-member +{ + display:block; +} +DIV.facet LI.member:hover SPAN.bar +{ + background-color:rgba(81, 158, 218, 0.2); +} +DIV.facet LI.empty-member +{ + color:#888888; +} +LI.member .member-indicator +{ + position:relative; + display:inline-block; + width:16px; + z-index:2; +} +.member-indicator.selected:after +{ + content:"\002713" +} +.member-indicator.selected:hover:after +{ + content:"x"; +} +.member-indicator.not-selected:after +{ + content:"\0025FB" +} +.member-indicator.not-selected:hover:after +{ + content:"\002713" +} +.member-indicator.not-selected.none-selected:after +{ + content:"\002713"; color:lightgray; +} +LI.member .member-name +{ + display:inline-block; + position:relative; + max-width:150pt; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + z-index:2; +} +LI.member .member-count +{ + position:relative; + float:right; + z-index:2; +} + +LI.member SPAN.bar-selected +{ + background-color:rgba(81, 158, 218, 0.2); +} + +LI.member .bar +{ + height:14pt; + background-color:#DEDEDE; + position:absolute; right:0; + z-index:0; +} + +/* filter type popup */ +DIV.labkey-filter-popup +{ + position: absolute; + min-width: 80px; + margin: 2px 0 0; + list-style: none; + background-color: white; + border-color:#b4b4b4; + border-width: 1px; + border-style: solid; + box-shadow: rgb(136, 136, 136) 0px 0px 6px; + z-index: 100; + -webkit-padding-start: 0px +} + + +.labkey-filter-popup > .labkey-dropdown-menu +{ + display: block; + min-width: 130px; +} + +/* menus */ +.nav +{ + margin: 0; + padding-left: 0;; + list-style: none; +} + +ul.nav +{ + display:block; +} + +.navbar-nav > li +{ + float: left; +} + +.navbar-nav +{ + float: left; + margin: 0; +} + +.navbar-nav > li > .labkey-dropdown-menu +{ + margin-top : 0; +} + +.labkey-filter-options > a.inactive +{ + color: black; + cursor: default; +} + +.labkey-dropdown > a.labkey-disabled-text-link +{ + color: #C0C0C0; + cursor: default; +} + +.labkey-dropdown:hover > .labkey-dropdown-menu-active +{ + display: block; +} + +.labkey-dropdown-menu > li { + margin-right : 0px; +} + +.labkey-dropdown-menu > li > a +{ + color: black; +} + +.labkey-dropdown-menu > li > a.inactive +{ + color: #C0C0C0; + cursor: default; +} + +.labkey-dropdown-menu > li.inactive:hover +{ + color: #C0C0C0; + cursor: default; + background-color: white; + border-style: none; + background-image: none; +} + +.labkey-dropdown-menu > li:hover +{ + background-image: none; + background-color: #eaeaea; + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f2f2f2), color-stop(100%, #e0e0e0)); + background-image: -webkit-linear-gradient(top, #f2f2f2, #e0e0e0); + background-image: -moz-linear-gradient(top, #f2f2f2, #e0e0e0); + background-image: -o-linear-gradient(top, #f2f2f2, #e0e0e0); + background-image: linear-gradient(top, #f2f2f2, #e0e0e0); + border-color: #b4b4b4; + border-width: 1px; + border-style: solid; + padding: 0; +} + +.labkey-dropdown-menu { + position: absolute; + display: none; + min-width: 80px; + margin: 2px 0 0; + list-style: none; + background-color: white; + border-color:#b4b4b4; + border-width: 1px; + border-style: solid; + padding: 0; + box-shadow: rgb(136, 136, 136) 0px 0px 6px; + z-index: 100; + -webkit-padding-start: 0px +} + +#manageMenu > a { + color:black; + padding-right: 5px; +} + +.menu-item-link { + line-height: 22px; + display: inline-block; + padding: 0 8px 0 8px; +} + +.labkey-study-detail .x4-window-header { + background-color: #F8F8F8; + padding: 0; + box-shadow: #F8F8F8 0 0 0 0; +} + +.labkey-study-detail { + border-color: #F8F8F8; + padding: 0; +} + +div.study-demographics { + background-color: white; + padding: 10px 20px 20px 20px; + margin-top: 10px; + box-shadow: 0 1px 1px rgba(0,0,0,0.15), -1px 0 0 rgba(0,0,0,0.06), 1px 0 0 rgba(0,0,0,0.06), 0 1px 0 rgba(0,0,0,0.12); +} + +div.study-demographics h2 { + display: inline-block; + text-transform: uppercase; + font-weight: normal; + background-color: #126495; + color: white; + font-size: 13px; + padding: 9px 20px 7px 20px; + margin-top: -20px; + margin-left: -20px; +} + +div.study-demographics h3 { + text-transform: uppercase; + font-size: 14px; + font-weight: normal; + padding: 10px 0px 10px 50px; + border-bottom: 1px solid darkgray; +} + +#demographics-content .detail { + font-size: 15px; + padding-left: 30px; + padding-bottom: 5px; +} + +#demographics-content .detail div { + font-size: 15px; +} + +#demographics-content h3 { + margin-bottom: 0.5em; + margin-top: 0.5em; +} + +#demographics-content div { + padding: 3px; +} + +#demographics-content div.label, a.label { + font-size: 12px; + color: #a9a9a9; + vertical-align: text-top; +} + + +#assays-content .detail div { + font-size: 15px; + padding: 3px; +} \ No newline at end of file diff --git a/resources/web/study/Finder/panel/FacetPanelHeader.js b/resources/web/study/Finder/panel/FacetPanelHeader.js new file mode 100644 index 00000000..4478e79a --- /dev/null +++ b/resources/web/study/Finder/panel/FacetPanelHeader.js @@ -0,0 +1,100 @@ +Ext4.define("LABKEY.study.panel.FacetPanelHeader", { + extend: "Ext.Component", + + padding: "0 0 5 0", + data : { + loadedStudiesShown: true, + isGuest: false, + hasFilters: true, + showParticipantGroups: true, + currentGroup: { + id: 1, + label: "My group" + }, + saveOptions: [ + { + id: "save", + label : "Save", + isActive : true + }, + { + id: "saveAs", + label : "Save As", + isActive : true + } + ], + + groups: [{ + label: "my first group" + }] + }, + tpl: new Ext4.XTemplate( + '
          ', + '', + '
          Saved group: {currentGroup.label}
          ', + '
          ', + ' ', + '
          ' + ) +}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js new file mode 100644 index 00000000..088ea937 --- /dev/null +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -0,0 +1,50 @@ +Ext4.define("LABKEY.study.panel.FacetSelection", { + extend: 'Ext.panel.Panel', + + layout: { type: 'vbox', align: 'stretch' }, + + border: false, + + alias : 'widget.study-facet-selection-panel', + + cls: 'selection-panel', + + padding: "10 8 8 10", + + initComponent : function() { + this.items = [ + this.getFacetPanelHeader(), + this.getFacetSelectionSummary(), + this.getFacets() + ]; + this.callParent(); + }, + + getFacetPanelHeader : function() { + if (!this.facetPanelHeader) { + this.facetPanelHeader = Ext4.create("LABKEY.study.panel.FacetPanelHeader", { + + }); + } + return this.facetPanelHeader; + }, + + getFacetSelectionSummary: function() { + if (!this.facetSelectionSummary) { + this.facetSelectionSummary = Ext4.create("LABKEY.study.panel.SelectionSummary", { + + }); + } + return this.facetSelectionSummary; + }, + + getFacets : function() { + if (!this.facets) { + this.facets = Ext4.create("LABKEY.study.panel.Facets", { + + }); + } + return this.facets; + } + +}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/Facets.js b/resources/web/study/Finder/panel/Facets.js new file mode 100644 index 00000000..0a08c402 --- /dev/null +++ b/resources/web/study/Finder/panel/Facets.js @@ -0,0 +1,214 @@ +Ext4.define("LABKEY.study.panel.Facets", { + + extend : 'Ext.view.View', + + alias: 'widget.labkey-study-facet-panel', + + cls: 'labkey-study-facets', + + itemSelector: 'div.facet', + + autoScroll: true, + + studyData : [], + + store: Ext4.create('Ext.data.Store', { + model: 'LABKEY.study.data.Facet', + autoLoad: true, + proxy : { + type: "ajax", + url: LABKEY.ActionURL.buildURL("trialshare", "getStudyFacets", LABKEY.containerPath), + reader: { + type: 'json', + root: 'data' + } + }, + listeners: { + 'load' : { + fn : function(store, records, options) { + console.log('Facet store loaded'); + this.facetsLoaded = true; + }, + scope: this + } + } + }), + tpl: new Ext4.XTemplate( + + '', + '', + '', + '', + '', + { + formatNumber : Ext4.util.Format.numberRenderer('0,000'), + } + ), + + listeners: { + itemClick: function(view, record, item, index, event, eOpts) { + var targetEl = event.getTarget(); + if (targetEl.className.includes("member")) + this.selectMember(record, item, index, event); + else if (targetEl.className.includes("facet-toggle")) + { + if (item.className.includes("expanded")) + item.className = item.className.replace("expanded", "collapsed"); + else + item.className = item.className.replace("collapsed", "expanded"); + } + }, + mouseover: function(view, record, item, index, event, eOpts) { + console.log("display filter choice"); + this.displayFilterChoice(record, event); + } + }, + + displayFilterChoice : function (record, event) + { + if (record.filters.length < 2) + + var locationElement = event.target; + if (locationElement.className.includes('fa-caret')) + locationElement = event.target.parentElement; + var xy = Ext4.fly(locationElement).getXY(); + this.filterChoice = + { + show: true, + dimName: dimName, + x: xy[0], + y: xy[1], + options: dim.filterOptions + }; + if (event.stopPropagation) + event.stopPropagation(); + }, + + selectMember : function (facet, item, facetIndex, event) { + var shiftClick = event && (event.ctrlKey || event.altKey || event.metaKey); + this._selectMember(facet, item, facetIndex, event, shiftClick); + }, + + toggleMember : function (facet, item, facetIndex, event) { + this._selectMember(facet, item, facetIndex, event, true); + }, + + _selectMember : function (facet, item, facetIndex, event, shiftClick) { + + var filterMembers = facet.get("filters"); + + var name = facet.get("name"); + var element = event.target; + while (element.parentElement && !element.id.includes("member_")) { + element = element.parentElement; + } + if (!element) + { + if (0 == filterMembers.length) // no change + return; + this._clearFilter(name); + } + else if (!shiftClick) + { + this._clearFilter(name); + facet.currentFilters = [element.id]; // TODO add currentFilters to model + member.selected = true; // TODO change classes here: li gets 'selected-member' and span gets 'selected' + } + else + { + var index = -1; + for (var m = 0; m < filterMembers.length; m++) + { + if (member.uniqueName == filterMembers[m].uniqueName) + index = m; + } + if (index == -1) // unselected -> selected + { + filterMembers.push(member); + member.selected = true; + } + else // selected --> unselected + { + filterMembers.splice(index, 1); + member.selected = false; + this.fireEvent("filterSelectionCleared", this.hasFilters()); + } + } + + this.updateCountsAsync(); + if (event.stopPropagation) + event.stopPropagation(); + }, + + clearAllFilters : function (updateCounts) { + for (var i = 0; i < this.getStore().count(); i++) + { + if (this.store.getAt(i).get("id") == "Study") + continue; + this._clearFilter(this.store.getAt(i)); + } + if (updateCounts) + this.updateCountsAsync(); + this.fireEvent("filterSelectionCleared", false); + }, + + _clearFilter : function (facet) { + var filterMembers = facet.get("filters"); + for (var m = 0; m < filterMembers.length; m++) + filterMembers[m].selected = false; + dim.filters = []; + this.fireEvent("filterSelectionCleared", this.hasFilters()); + } +}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js new file mode 100644 index 00000000..e99e0c3d --- /dev/null +++ b/resources/web/study/Finder/panel/Finder.js @@ -0,0 +1,65 @@ +Ext4.define('LABKEY.study.panel.Finder', { + extend: 'Ext.panel.Panel', + + alias: 'widget.labkey-data-finder-panel', + + layout: 'border', + + cls: 'labkey-data-finder-view', + + title: "Data Finder", + + showParticipantFilters: false, + + studyData: [], + + studySubsets: null, + + height: '500px', + + initComponent : function() { + + this.items = [ + this.getFacetsPanel(), + this.getStudiesPanel() + ]; + + this.callParent(); + }, + + getFacetsPanel: function() { + if (!this.facetsPanel) { + + this.facetsPanel = Ext4.create("LABKEY.study.panel.FacetSelection", { + region: 'west', + width: '21%', + maxWidth: '265px', + showParticipantFilters : this.showParticipantFilters + }); + } + FACETS = this.facetsPanel; + return this.facetsPanel; + }, + + getStudiesPanel: function() { + if (!this.studiesPanel) { + this.studiesPanel = Ext4.create("LABKEY.study.panel.Studies", { + studySubsets : this.studySubsets, + showSearch : this.showSearch, + region: 'center', + width: '80%', + id: 'studies-view' + }); + } + STUDIES = this.studiesPanel; + return this.studiesPanel; + }, + + startTutorial : function() { + LABKEY.help.Tour.show("LABKEY.tour.dataFinder"); + return false; + } + +}); + + diff --git a/resources/web/study/Finder/panel/SelectionSummary.js b/resources/web/study/Finder/panel/SelectionSummary.js new file mode 100644 index 00000000..11327c40 --- /dev/null +++ b/resources/web/study/Finder/panel/SelectionSummary.js @@ -0,0 +1,53 @@ +Ext4.define("LABKEY.study.panel.SelectionSummary", { + extend: 'Ext.Component', + + alias : 'widget.facet-selection-summary', + + renderTpl: new Ext4.XTemplate( + '
          ', + '
          ', + '
          Summary
          ', + '
            ', + '
          • ', + ' Studies', + ' {studyCount:this.formatNumber}', + '
          • ', + '
          • ', + ' Subjects', + ' {participantCount:this.formatNumber}', + '
          • ', + '
          ', + '
          ', + '
          ', + { + formatNumber : Ext4.util.Format.numberRenderer('0,000'), + } + ), + + renderSelectors: { + studyCountEl: 'span#memberCount', + participantCountEl: 'span#participantCount' + }, + + renderData : { + studyCount: 4, + participantCount: 10000 + } + //tpl : new Ext4.XTemplate( + // '
          ', + // '
          ', + // '
          Summary
          ', + // '
            ', + // '
          • ', + // ' Studies', + // ' {studyCount}', + // '
          • ', + // '
          • ', + // ' Subjects', + // ' {participantCount}', + // '
          • ', + // '
          ', + // '
          ', + // '
          ' + //) +}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/Studies.js b/resources/web/study/Finder/panel/Studies.js new file mode 100644 index 00000000..86c54490 --- /dev/null +++ b/resources/web/study/Finder/panel/Studies.js @@ -0,0 +1,37 @@ +Ext4.define("LABKEY.study.panel.Studies", { + extend: 'Ext.panel.Panel', + + layout : 'vbox', + border: false, + alias : 'widget.labkey-studies-panel', + cls: 'labkey-studies-panel', + + padding: "5 0 0 0", + + initComponent : function() { + this.items = [ + this.getStudyPanelHeader(), + this.getStudyCards() + ]; + this.callParent(); + }, + + getStudyPanelHeader : function() { + if (!this.studyPanelHeader) { + this.studyPanelHeader = Ext4.create("LABKEY.study.panel.StudyPanelHeader", { + searchTerms: "testing", + padding: 8 + }); + } + return this.studyPanelHeader; + }, + + getStudyCards : function() { + if (!this.studyCards) { + this.studyCards = Ext4.create("LABKEY.study.panel.StudyCards", { + + }); + } + return this.studyCards; + } +}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js new file mode 100644 index 00000000..c850e9d4 --- /dev/null +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -0,0 +1,97 @@ +Ext4.define("LABKEY.study.panel.StudyCards", { + + extend: 'Ext.view.View', + + alias: 'widget.labkey-studyCards-panel', + + cls: 'labkey-study-cards', + + layout: 'hbox', + + width: "100%", + + itemSelector: 'div.labkey-study-card', + + autoScroll: true, + + store : Ext4.create('Ext.data.Store', { + model: 'LABKEY.study.data.StudyCard', + autoLoad: true, + proxy : { + type: "ajax", + url: LABKEY.ActionURL.buildURL("trialshare", "getStudies", LABKEY.containerPath), + reader: { + type: 'json', + root: 'data' + } + } + }), + + tpl: new Ext4.XTemplate( + '
          ', + ' ', + ' ', + '
          ', + ' ', + '
          ', + ' ', + ' {accession}', + ' {investigator}', + '
          ', + '
          ', + ' view summary', + ' ', + ' go to study', + ' ', + '
          ', + + '
          {title}
          ', + '
          ', + '
          ', + '
          ' + ), + + listeners: { + itemClick: function(view, record, item, index, e, eOpts) { + console.log("Show study popup for record " , record); + this.showPopup(record.get("accession")); + } + }, + + showPopup : function(studyId) + { + this.hidePopup(); + + var detailWindow = Ext4.create('Ext.window.Window', { + width: 800, + maxHeight: 600, + resizable: true, + layout: 'fit', + border: false, + cls: 'labkey-study-detail', + autoScroll: true, + loader: { + autoLoad: true, + url: 'trialshare-studyDetail.view?_frame=none&studyId=' + studyId + } + }); + var viewScroll = Ext4.getBody().getScroll(); + var viewSize = Ext4.getBody().getViewSize(); + var region = [viewScroll.left, viewScroll.top, viewScroll.left + viewSize.width, viewScroll.top + viewSize.height]; + var proposedXY = [region[0] + viewSize.width / 2 - 400, region[1] + viewSize.height / 2 - 300]; + proposedXY[1] = Math.max(region[1], Math.min(region[3] - 400, proposedXY[1])); + detailWindow.setPosition(proposedXY); + this.detailShowing = detailWindow; + this.detailShowing.show(); + }, + + hidePopup: function() + { + if (this.detailShowing) + { + this.detailShowing.hide(); + this.detailShowing.destroy(); + this.detailShowing = null; + } + } +}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/StudyPanelHeader.js b/resources/web/study/Finder/panel/StudyPanelHeader.js new file mode 100644 index 00000000..d2232c93 --- /dev/null +++ b/resources/web/study/Finder/panel/StudyPanelHeader.js @@ -0,0 +1,45 @@ +Ext4.define("LABKEY.study.panel.StudyPanelHeader", { + + extend: 'Ext.view.View', + + studySubsets: [], + selectedSubset: null, + searchMessage: "", + searchTerms : "", + + tpl: new Ext4.XTemplate( + '
          ', + ' ', + '  ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' {searchMessage}', + '
          ' + //'' + ), + + listeners : { + change : function(field, newValue, oldValue) { + if (field = "searchTerms") + onSearchTermsChanged(); + else if (field == "studySubsetSelect") + onStudySubsetChanged(); + } + } +}); \ No newline at end of file diff --git a/src/org/labkey/trialshare/TrialShareContainerListener.java b/src/org/labkey/trialshare/TrialShareContainerListener.java new file mode 100644 index 00000000..5b29b94d --- /dev/null +++ b/src/org/labkey/trialshare/TrialShareContainerListener.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.labkey.trialshare; + +import org.jetbrains.annotations.NotNull; +import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerManager.ContainerListener; +import org.labkey.api.security.User; +import java.util.Collections; +import java.util.Collection; + +import java.beans.PropertyChangeEvent; + +public class TrialShareContainerListener implements ContainerListener +{ + @Override + public void containerCreated(Container c, User user) + { + } + + @Override + public void containerDeleted(Container c, User user) + { + } + + @Override + public void propertyChange(PropertyChangeEvent evt) + { + } + + @Override + public void containerMoved(Container c, Container oldParent, User user) + { + } + + @NotNull @Override + public Collection canMove(Container c, Container newParent, User user) + { + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java new file mode 100644 index 00000000..2a78564e --- /dev/null +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.labkey.trialshare; + +import org.labkey.api.action.ApiAction; +import org.labkey.api.action.Marshal; +import org.labkey.api.action.Marshaller; +import org.labkey.api.action.ReturnUrlForm; +import org.labkey.api.action.SimpleViewAction; +import org.labkey.api.action.SpringActionController; +import org.labkey.api.gwt.client.util.StringUtils; +import org.labkey.api.security.RequiresPermission; +import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.util.PageFlowUtil; +import org.labkey.api.view.HtmlView; +import org.labkey.api.view.JspView; +import org.labkey.api.view.NavTree; +import org.labkey.api.view.NotFoundException; +import org.labkey.api.view.VBox; +import org.labkey.trialshare.data.StudyBean; +import org.labkey.trialshare.data.StudyFacetBean; +import org.labkey.trialshare.data.StudyFacetMember; +import org.springframework.validation.BindException; +import org.springframework.web.servlet.ModelAndView; + +import java.util.ArrayList; +import java.util.List; + +@Marshal(Marshaller.Jackson) +public class TrialShareController extends SpringActionController +{ + private static final DefaultActionResolver _actionResolver = new DefaultActionResolver(TrialShareController.class); + public static final String NAME = "trialshare"; + + public TrialShareController() + { + setActionResolver(_actionResolver); + } + + @RequiresPermission(ReadPermission.class) + public class BeginAction extends SimpleViewAction + { + public NavTree appendNavTrail(NavTree root) + { + return root; + } + + @Override + public ModelAndView getView(Object o, BindException errors) throws Exception + { + return new JspView("/org/labkey/trialshare/view/dataFinder.jsp"); + } + } + + @RequiresPermission(ReadPermission.class) + public class GetStudiesAction extends ApiAction + { + + @Override + public Object execute(StudiesForm form, BindException errors) throws Exception + { + List studies = new ArrayList(); + StudyBean study = new StudyBean(); + + studies.add(getStudy("ABC123")); + studies.add(getStudy("XY1YVA")); + + return success(studies); + } + } + + public static class StudiesForm { + + } + + private StudyBean getStudy(String accession) { + StudyBean study = new StudyBean(); + if (accession.equals("ABC123")) { + study.setAccession("ABC123"); + study.setInvestigator("Some One"); + study.setTitle("The title of this study is very descriptive"); + study.setIsLoaded(true); + study.setUrl("/labkey/project/ITN%20TrialShare/ABC123/begin.view?"); + } else if (accession.equals("XY1YVA")) { + study.setAccession("XY1YVA"); + study.setInvestigator("Some One Else"); + study.setTitle("The title of this study is even more descriptive"); + study.setIsLoaded(false); + study.setDescription("Immunosuppression Withdrawal for Pediatric Living-donor Liver Transplant Recipients\n" + + "\n" + + "Protocol Chair: Sandy Feng, MD, PhD\n" + + "\n" + + "This is a prospective multicenter, open-label, single-arm trial in which 20 pediatric recipients of parental living-donor liver allografts will undergo gradual withdrawal of immunosuppression with the goal of complete withdrawal. Patients on stable immunosuppression regimens with good organ function and no evidence of acute or chronic rejection or other forms of allograft dysfunction will be enrolled. Participants will undergo gradual withdrawal of immunosuppression and will be followed for a minimum of 4 years after completion of immunosuppression withdrawal. Immunologic and genetic profiles will be collected at multiple time points and compared between tolerant and nontolerant participants.\n" + + "\n" + + "Cohort\n" + + "Description\n" + + "Immunosuppression Withdrawal\tPediatric recipients of parental living-donor liver allografts\n" + + "ClinTrials.gov #: NCT00320606"); + } + return study; + } + + @RequiresPermission(ReadPermission.class) + public class GetStudyFacetsAction extends ApiAction + { + + @Override + public Object execute(Object o, BindException errors) throws Exception + { + List facets = new ArrayList(); + StudyFacetBean facet = new StudyFacetBean(); + facet.setName("Facet1"); + facet.setCaption("Facet 1"); + + List members = new ArrayList(); + StudyFacetMember member = new StudyFacetMember(); + member.setName("Member 1.1"); + member.setCount(10000); + member.setUniqueName("Member11"); + members.add(member); + + + member = new StudyFacetMember(); + member.setName("Member 1.2"); + members.add(member); + member.setCount(0); + member.setUniqueName("Member12"); + facet.setMembers(members); + + + facets.add(facet); + + StudyFacetBean facet2 = new StudyFacetBean(); + facet2.setName("Facet2"); + facet2.setCaption("Facet 2"); + + members = new ArrayList(); + member = new StudyFacetMember(); + member.setName("Member 2.1"); + member.setCount(12); + member.setUniqueName("Member21"); + members.add(member); + facet2.setMembers(members); + + facets.add(facet2); + + + return success(facets); + } + } + + @RequiresPermission(ReadPermission.class) + public class StudyDetailAction extends SimpleViewAction + { + StudyIdForm _form; + StudyBean _study = new StudyBean(); + + @Override + public ModelAndView getView(StudyIdForm form, BindException errors) throws Exception + { + _form = form; + + String studyId = (null==form) ? null : form.getStudyId(); + if (StringUtils.isEmpty(studyId)) + throw new NotFoundException("study not specified"); + +// _study.study = (new TableSelector(DbSchema.get("immport").getTable("study"))).getObject(studyId, StudyBean.class); +// if (null == _study.study) +// throw new NotFoundException("study not found: " + form.getStudy()); +// SimpleFilter filter = new SimpleFilter(); +// filter.addCondition(new FieldKey(null,"study_accession"),studyId); +// _study.personnel = (new TableSelector(DbSchema.get("immport").getTable("study_personnel"),filter,null)).getArrayList(StudyPersonnelBean.class); +// _study.pubmed = (new TableSelector(DbSchema.get("immport").getTable("study_pubmed"),filter,null)).getArrayList(StudyPubmedBean.class); + + _study = getStudy(studyId); + VBox v = new VBox(); + if (null != _form.getReturnActionURL()) + { + v.addView(new HtmlView(PageFlowUtil.textLink("back",_form.getReturnActionURL()) + "
          ")); + } + v.addView(new JspView("/org/labkey/trialshare/view/studyDetail.jsp", _study)); + return v; + } + + @Override + public NavTree appendNavTrail(NavTree root) + { + return root; + } + } + + public static class StudyIdForm extends ReturnUrlForm + { + private String studyId; + + public StudyIdForm(){} + + public String getStudyId() + { + return studyId; + } + + public void setStudyId(String studyId) + { + this.studyId = studyId; + } + } + + +} \ No newline at end of file diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java new file mode 100644 index 00000000..cd0ea979 --- /dev/null +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.labkey.trialshare; + +public class TrialShareManager +{ + private static final TrialShareManager _instance = new TrialShareManager(); + + private TrialShareManager() + { + // prevent external construction with a private default constructor + } + + public static TrialShareManager get() + { + return _instance; + } +} \ No newline at end of file diff --git a/src/org/labkey/trialshare/TrialShareModule.java b/src/org/labkey/trialshare/TrialShareModule.java new file mode 100644 index 00000000..899f8bd1 --- /dev/null +++ b/src/org/labkey/trialshare/TrialShareModule.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.labkey.trialshare; + +import org.jetbrains.annotations.NotNull; +import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerManager; +import org.labkey.api.module.DefaultModule; +import org.labkey.api.module.ModuleContext; +import org.labkey.api.view.WebPartFactory; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +public class TrialShareModule extends DefaultModule +{ + public static final String NAME = "TrialShare"; + + @Override + public String getName() + { + return NAME; + } + + @Override + public double getVersion() + { + return 15.31; + } + + @Override + public boolean hasScripts() + { + return true; + } + + @Override + @NotNull + protected Collection createWebPartFactories() + { + return Collections.emptyList(); + } + + @Override + protected void init() + { + addController(TrialShareController.NAME, TrialShareController.class); + } + + @Override + public void doStartup(ModuleContext moduleContext) + { + // add a container listener so we'll know when our container is deleted: + ContainerManager.addContainerListener(new TrialShareContainerListener()); + } + + @Override + @NotNull + public Collection getSummary(Container c) + { + return Collections.emptyList(); + } + + @Override + @NotNull + public Set getSchemaNames() + { + return Collections.singleton(TrialShareSchema.NAME); + } +} \ No newline at end of file diff --git a/src/org/labkey/trialshare/TrialShareSchema.java b/src/org/labkey/trialshare/TrialShareSchema.java new file mode 100644 index 00000000..f0539c5f --- /dev/null +++ b/src/org/labkey/trialshare/TrialShareSchema.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.labkey.trialshare; + +import org.labkey.api.data.DbSchema; +import org.labkey.api.data.DbSchemaType; +import org.labkey.api.data.dialect.SqlDialect; + +public class TrialShareSchema +{ + private static final TrialShareSchema _instance = new TrialShareSchema(); + public static final String NAME = "trialShare"; + + public static TrialShareSchema getInstance() + { + return _instance; + } + + private TrialShareSchema() + { + // private constructor to prevent instantiation from + // outside this class: this singleton should only be + // accessed via org.labkey.trialshare.itnDataFinderSchema.getInstance() + } + + public DbSchema getSchema() + { + return DbSchema.get(NAME, DbSchemaType.Module); + } + + public SqlDialect getSqlDialect() + { + return getSchema().getSqlDialect(); + } +} diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java new file mode 100644 index 00000000..bb91ebaa --- /dev/null +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -0,0 +1,121 @@ +package org.labkey.trialshare.data; + +import java.util.List; + +/** + * Created by susanh on 12/7/15. + */ +public class StudyBean +{ + private String accession; + private String title; + private String investigator; + private String url; + private Boolean isLoaded; + private String description; + private String briefDescription; + private List personnel; + private List pubmed; + private String labelPrefix = null; // common prefix used in labeling studies + + public String getAccession() + { + return accession; + } + + public void setAccession(String accession) + { + this.accession = accession; + } + + public String getInvestigator() + { + return investigator; + } + + public void setInvestigator(String investigator) + { + this.investigator = investigator; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getUrl() + { + return url; + } + + public void setUrl(String url) + { + this.url = url; + } + + public Boolean getIsLoaded() + { + return isLoaded; + } + + public void setIsLoaded(Boolean loaded) + { + isLoaded = loaded; + } + + public String getDescription() + { + return description; + } + + public void setDescription(String description) + { + this.description = description; + } + + public String getBriefDescription() + { + return briefDescription; + } + + public void setBriefDescription(String briefDescription) + { + this.briefDescription = briefDescription; + } + + public List getPersonnel() + { + return personnel; + } + + public void setPersonnel(List personnel) + { + this.personnel = personnel; + } + + public List getPubmed() + { + return pubmed; + } + + public void setPubmed(List pubmed) + { + this.pubmed = pubmed; + } + + public String getLabelPrefix() + { + return labelPrefix; + } + + public void setLabelPrefix(String labelPrefix) + { + this.labelPrefix = labelPrefix; + } +} + diff --git a/src/org/labkey/trialshare/data/StudyFacetBean.java b/src/org/labkey/trialshare/data/StudyFacetBean.java new file mode 100644 index 00000000..d37f9b75 --- /dev/null +++ b/src/org/labkey/trialshare/data/StudyFacetBean.java @@ -0,0 +1,109 @@ +package org.labkey.trialshare.data; + +import java.util.List; + +/** + * Created by susanh on 12/8/15. + */ +public class StudyFacetBean +{ + private String name; + private String caption; + private String pluralName; + private String hierarchyName; + private String levelName; + private String allMemberName; + private String filterType; + private List members; + private List filterOptions; + + public String getCaption() + { + return caption; + } + + public void setCaption(String caption) + { + this.caption = caption; + } + + public List getMembers() + { + return members; + } + + public void setMembers(List members) + { + this.members = members; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getAllMemberName() + { + return allMemberName; + } + + public void setAllMemberName(String allMemberName) + { + this.allMemberName = allMemberName; + } + + public List getFilterOptions() + { + return filterOptions; + } + + public void setFilterOptions(List filterOptions) + { + this.filterOptions = filterOptions; + } + + public String getFilterType() + { + return filterType; + } + + public void setFilterType(String filterType) + { + this.filterType = filterType; + } + + public String getHierarchyName() + { + return hierarchyName; + } + + public void setHierarchyName(String hierarchyName) + { + this.hierarchyName = hierarchyName; + } + + public String getLevelName() + { + return levelName; + } + + public void setLevelName(String levelName) + { + this.levelName = levelName; + } + + public String getPluralName() + { + return pluralName; + } + + public void setPluralName(String pluralName) + { + this.pluralName = pluralName; + } +} diff --git a/src/org/labkey/trialshare/data/StudyFacetMember.java b/src/org/labkey/trialshare/data/StudyFacetMember.java new file mode 100644 index 00000000..23b39322 --- /dev/null +++ b/src/org/labkey/trialshare/data/StudyFacetMember.java @@ -0,0 +1,52 @@ +package org.labkey.trialshare.data; + +/** + * Created by susanh on 12/8/15. + */ +public class StudyFacetMember +{ + private String name; + private String uniqueName; + private Integer count; + private Float percent; + + public Integer getCount() + { + return count; + } + + public void setCount(Integer count) + { + this.count = count; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public Float getPercent() + { + return percent; + } + + public void setPercent(Float percent) + { + this.percent = percent; + } + + public String getUniqueName() + { + return uniqueName; + } + + public void setUniqueName(String uniqueName) + { + this.uniqueName = uniqueName; + } +} diff --git a/src/org/labkey/trialshare/data/StudyPersonnelBean.java b/src/org/labkey/trialshare/data/StudyPersonnelBean.java new file mode 100644 index 00000000..3c2f665b --- /dev/null +++ b/src/org/labkey/trialshare/data/StudyPersonnelBean.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.labkey.trialshare.data; + +public class StudyPersonnelBean +{ + String person_accession; + String email; + String first_name; + String last_name; + String honorific; + String organization; + String role_in_study; + String study_accession; + int workspace_id; + + public String getPerson_accession() + { + return person_accession; + } + + public void setPerson_accession(String person_accession) + { + this.person_accession = person_accession; + } + + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + public String getFirst_name() + { + return first_name; + } + + public void setFirst_name(String first_name) + { + this.first_name = first_name; + } + + public String getLast_name() + { + return last_name; + } + + public void setLast_name(String last_name) + { + this.last_name = last_name; + } + + public String getHonorific() + { + return honorific; + } + + public void setHonorific(String honorific) + { + this.honorific = honorific; + } + + public String getOrganization() + { + return organization; + } + + public void setOrganization(String organization) + { + this.organization = organization; + } + + public String getRole_in_study() + { + return role_in_study; + } + + public void setRole_in_study(String role_in_study) + { + this.role_in_study = role_in_study; + } + + public String getStudy_accession() + { + return study_accession; + } + + public void setStudy_accession(String study_accession) + { + this.study_accession = study_accession; + } + + public int getWorkspace_id() + { + return workspace_id; + } + + public void setWorkspace_id(int workspace_id) + { + this.workspace_id = workspace_id; + } +} diff --git a/src/org/labkey/trialshare/data/StudyPubmedBean.java b/src/org/labkey/trialshare/data/StudyPubmedBean.java new file mode 100644 index 00000000..159a482d --- /dev/null +++ b/src/org/labkey/trialshare/data/StudyPubmedBean.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.labkey.trialshare.data; + +public class StudyPubmedBean +{ + String study_accession; + String pubmed_id; + String authors; + String issue; + String journal; + String pages; + String title; + String year; + int worksspace_id; + + public String getStudy_accession() + { + return study_accession; + } + + public void setStudy_accession(String study_accession) + { + this.study_accession = study_accession; + } + + public String getPubmed_id() + { + return pubmed_id; + } + + public void setPubmed_id(String pubmed_id) + { + this.pubmed_id = pubmed_id; + } + + public String getAuthors() + { + return authors; + } + + public void setAuthors(String authors) + { + this.authors = authors; + } + + public String getIssue() + { + return issue; + } + + public void setIssue(String issue) + { + this.issue = issue; + } + + public String getJournal() + { + return journal; + } + + public void setJournal(String journal) + { + this.journal = journal; + } + + public String getPages() + { + return pages; + } + + public void setPages(String pages) + { + this.pages = pages; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getYear() + { + return year; + } + + public void setYear(String year) + { + this.year = year; + } + + public int getWorksspace_id() + { + return worksspace_id; + } + + public void setWorksspace_id(int worksspace_id) + { + this.worksspace_id = worksspace_id; + } +} diff --git a/src/org/labkey/trialshare/view/dataFinder.jsp b/src/org/labkey/trialshare/view/dataFinder.jsp new file mode 100644 index 00000000..c799c19f --- /dev/null +++ b/src/org/labkey/trialshare/view/dataFinder.jsp @@ -0,0 +1,133 @@ +<% + /* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +%> +<%@ page import="org.labkey.api.view.template.ClientDependency" %> +<%@ page import="java.util.LinkedHashSet" %> +<%@ page extends="org.labkey.api.jsp.JspBase"%> +<%! + public LinkedHashSet getClientDependencies() + { + LinkedHashSet resources = new LinkedHashSet<>(); + resources.add(ClientDependency.fromPath("internal/jQuery")); // this is for the Help tour defined below + resources.add(ClientDependency.fromPath("Ext4")); + resources.add(ClientDependency.fromPath("clientapi/ext4")); + resources.add(ClientDependency.fromPath("query/olap.js")); + resources.add(ClientDependency.fromPath("study/Finder/dataFinder.css")); + resources.add(ClientDependency.fromPath("study/Finder/data/Facet.js")); + resources.add(ClientDependency.fromPath("study/Finder/data/FacetMember.js")); + resources.add(ClientDependency.fromPath("study/Finder/data/StudyCard.js")); + + resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeader.js")); + resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeader2.js")); + resources.add(ClientDependency.fromPath("study/Finder/panel/Facets.js")); + resources.add(ClientDependency.fromPath("study/Finder/panel/SelectionSummary.js")); + resources.add(ClientDependency.fromPath("study/Finder/panel/FacetSelection.js")); + + resources.add(ClientDependency.fromPath("study/Finder/panel/StudyCards.js")); + resources.add(ClientDependency.fromPath("study/Finder/panel/StudyPanelHeader.js")); + resources.add(ClientDependency.fromPath("study/Finder/panel/Studies.js")); + resources.add(ClientDependency.fromPath("study/Finder/panel/Finder.js")); + return resources; + } +%> + + + + +
          +
          + + + + diff --git a/src/org/labkey/trialshare/view/studyDetail.jsp b/src/org/labkey/trialshare/view/studyDetail.jsp new file mode 100644 index 00000000..c902cbcf --- /dev/null +++ b/src/org/labkey/trialshare/view/studyDetail.jsp @@ -0,0 +1,138 @@ +<% +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +%> +<%@ page import="org.apache.commons.lang3.StringUtils" %> +<%@ page import="org.labkey.api.data.Container" %> +<%@ page import="org.labkey.api.data.ContainerFilter" %> +<%@ page import="org.labkey.api.data.ContainerFilterable" %> +<%@ page import="org.labkey.api.data.ContainerManager" %> +<%@ page import="org.labkey.api.data.TableInfo" %> +<%@ page import="org.labkey.api.data.TableSelector" %> +<%@ page import="org.labkey.api.query.DefaultSchema" %> +<%@ page import="org.labkey.api.query.QuerySchema" %> +<%@ page import="org.labkey.api.view.ActionURL" %> +<%@ page import="org.labkey.api.view.HttpView" %> +<%@ page import="org.labkey.api.view.JspView" %> +<%@ page import="org.labkey.api.view.ViewContext" %> +<%@ page import="java.util.Collection" %> +<%@ page import="java.util.HashMap" %> +<%@ page import="java.util.Map" %> +<%@ page import="org.labkey.api.view.template.ClientDependency" %> +<%@ page import="java.util.LinkedHashSet" %> +<%@ page import="org.labkey.trialshare.data.StudyBean" %> +<%@ page import="org.labkey.trialshare.data.StudyPersonnelBean" %> +<%@ page import="org.labkey.trialshare.data.StudyPubmedBean" %> +<%@ page extends="org.labkey.api.jsp.JspBase" %> +<%! + public LinkedHashSet getClientDependencies() + { + LinkedHashSet resources = new LinkedHashSet<>(); + + resources.add(ClientDependency.fromPath("dataFinder.css")); + resources.add(ClientDependency.fromPath("immport/hipc.css")); + + return resources; + } +%> +<% + JspView me = (JspView) HttpView.currentView(); + + ViewContext context = HttpView.currentContext(); + Container c = context.getContainer(); + StudyBean study = me.getModelBean(); + String descriptionHTML; + if (!StringUtils.isEmpty(study.getDescription())) + descriptionHTML= study.getDescription(); + else + descriptionHTML = h(study.getBriefDescription()); + + ActionURL studyUrl = null; + if (!c.isRoot()) + { + String comma = "\n"; + Container p = c.getProject(); + QuerySchema s = DefaultSchema.get(context.getUser(), p).getSchema("study"); + TableInfo sp = s.getTable("StudyProperties"); + if (sp.supportsContainerFilter()) + { + ContainerFilter cf = new ContainerFilter.AllInProject(context.getUser()); + ((ContainerFilterable) sp).setContainerFilter(cf); + } + Collection> maps = new TableSelector(sp).getMapCollection(); + for (Map map : maps) + { + Container studyContainer = ContainerManager.getForId((String) map.get("container")); + String studyAccession = (String)map.get("study_accession"); + String name = (String)map.get("Label"); + if (null == studyAccession && study.getLabelPrefix() != null && name.startsWith(study.getLabelPrefix())) + studyAccession = name; + if (null != studyContainer && StringUtils.equalsIgnoreCase(study.getAccession(), studyAccession)) + { + studyUrl = studyContainer.getStartURL(context.getUser()); + break; + } + } + } + + Map linkProps = new HashMap<>(); + linkProps.put("target", "_blank"); +%> + +
          +

          <% if (null!=studyUrl) {%><%}%><%=h(study.getAccession())%><% if (null!=studyUrl) {%><%}%>

          +
          +

          <%=h(study.getTitle())%>

          +
          <% + if (null != study.getPersonnel()) + { + for (StudyPersonnelBean p : study.getPersonnel()) + { + if ("Principal Investigator".equals(p.getRole_in_study())) + { + %>
          + <%=h(p.getHonorific())%> <%=h(p.getFirst_name())%> <%=h(p.getLast_name())%> + <%=h(p.getOrganization())%> +
          <% + } + } + } + %>
          <%=text(descriptionHTML)%>
          +
          <% + if (null != study.getPubmed() && study.getPubmed().size() > 0) + { + %>Papers<% + for (StudyPubmedBean pub : study.getPubmed()) + { + %>

          <%=h(pub.getJournal())%> <%=h(pub.getYear())%>
          <% + %><%=h(pub.getTitle())%><% + if (!StringUtils.isEmpty(pub.getPubmed_id())) + { + %>
          <%=textLink("PubMed","http://www.ncbi.nlm.nih.gov/pubmed/?term=" + pub.getPubmed_id(), null, null, linkProps)%><% + } + %>

          <% + } + } + %>
          +
          + + <% if (null != studyUrl) { %> + <%= textLink("View study " + study.getAccession(), studyUrl.toString(), null, null, linkProps)%>
          + <% } %> +
          +
          + + diff --git a/trialShare.iml b/trialShare.iml new file mode 100644 index 00000000..49810792 --- /dev/null +++ b/trialShare.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file From 01461570b07f11e36a32c3f9485f88ca3c583bdc Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 10 Dec 2015 15:06:08 -0800 Subject: [PATCH 026/587] Spec 24959 : Implement Data Finder for ITN - update study panel header to use ext components for input and get the study subsets from API call --- .../web/study/Finder/data/StudySubset.js | 8 + resources/web/study/Finder/dataFinder.css | 6 + .../web/study/Finder/panel/FacetSelection.js | 6 +- resources/web/study/Finder/panel/Facets.js | 4 +- resources/web/study/Finder/panel/Finder.js | 9 +- .../study/Finder/panel/SelectionSummary.js | 19 +-- resources/web/study/Finder/panel/Studies.js | 6 +- .../web/study/Finder/panel/StudyCards.js | 14 +- .../study/Finder/panel/StudyPanelHeader.js | 139 +++++++++++++----- .../trialshare/TrialShareController.java | 32 +++- .../labkey/trialshare/data/StudySubset.java | 30 ++++ src/org/labkey/trialshare/view/dataFinder.jsp | 4 +- 12 files changed, 205 insertions(+), 72 deletions(-) create mode 100644 resources/web/study/Finder/data/StudySubset.js create mode 100644 src/org/labkey/trialshare/data/StudySubset.java diff --git a/resources/web/study/Finder/data/StudySubset.js b/resources/web/study/Finder/data/StudySubset.js new file mode 100644 index 00000000..fe659a41 --- /dev/null +++ b/resources/web/study/Finder/data/StudySubset.js @@ -0,0 +1,8 @@ +Ext4.define('LABKEY.study.data.StudySubset', { + extend: 'Ext.data.Model', + + fields: [ + {name: 'id'}, + {name: 'name'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 01721a3c..6a20f31e 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -89,12 +89,18 @@ TD.study-panel > DIV overflow-y:scroll; } +TABLE.labkey-study-search +{ + margin: 5px 0px 0px 5px; +} + SPAN.labkey-study-search { position: relative; } /* search area */ +TD.search-box, DIV.search-box { float:left; diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index 088ea937..946fbcf1 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -23,7 +23,7 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { getFacetPanelHeader : function() { if (!this.facetPanelHeader) { this.facetPanelHeader = Ext4.create("LABKEY.study.panel.FacetPanelHeader", { - + dataModuleName: this.dataModuleName }); } return this.facetPanelHeader; @@ -32,7 +32,7 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { getFacetSelectionSummary: function() { if (!this.facetSelectionSummary) { this.facetSelectionSummary = Ext4.create("LABKEY.study.panel.SelectionSummary", { - + dataModuleName: this.dataModuleName }); } return this.facetSelectionSummary; @@ -41,7 +41,7 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { getFacets : function() { if (!this.facets) { this.facets = Ext4.create("LABKEY.study.panel.Facets", { - + dataModuleName: this.dataModuleName }); } return this.facets; diff --git a/resources/web/study/Finder/panel/Facets.js b/resources/web/study/Finder/panel/Facets.js index 0a08c402..aff1f296 100644 --- a/resources/web/study/Finder/panel/Facets.js +++ b/resources/web/study/Finder/panel/Facets.js @@ -8,6 +8,8 @@ Ext4.define("LABKEY.study.panel.Facets", { itemSelector: 'div.facet', + dataModuleName: 'study', + autoScroll: true, studyData : [], @@ -17,7 +19,7 @@ Ext4.define("LABKEY.study.panel.Facets", { autoLoad: true, proxy : { type: "ajax", - url: LABKEY.ActionURL.buildURL("trialshare", "getStudyFacets", LABKEY.containerPath), + url: LABKEY.ActionURL.buildURL('trialshare', "studyFacets.api", LABKEY.containerPath), reader: { type: 'json', root: 'data' diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index e99e0c3d..7c407a92 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -17,6 +17,8 @@ Ext4.define('LABKEY.study.panel.Finder', { height: '500px', + dataModuleName: 'study', + initComponent : function() { this.items = [ @@ -34,6 +36,7 @@ Ext4.define('LABKEY.study.panel.Finder', { region: 'west', width: '21%', maxWidth: '265px', + dataModuleName: this.dataModuleName, showParticipantFilters : this.showParticipantFilters }); } @@ -46,6 +49,7 @@ Ext4.define('LABKEY.study.panel.Finder', { this.studiesPanel = Ext4.create("LABKEY.study.panel.Studies", { studySubsets : this.studySubsets, showSearch : this.showSearch, + dataModuleName: this.dataModuleName, region: 'center', width: '80%', id: 'studies-view' @@ -53,11 +57,6 @@ Ext4.define('LABKEY.study.panel.Finder', { } STUDIES = this.studiesPanel; return this.studiesPanel; - }, - - startTutorial : function() { - LABKEY.help.Tour.show("LABKEY.tour.dataFinder"); - return false; } }); diff --git a/resources/web/study/Finder/panel/SelectionSummary.js b/resources/web/study/Finder/panel/SelectionSummary.js index 11327c40..8a8e7b62 100644 --- a/resources/web/study/Finder/panel/SelectionSummary.js +++ b/resources/web/study/Finder/panel/SelectionSummary.js @@ -20,7 +20,7 @@ Ext4.define("LABKEY.study.panel.SelectionSummary", { '
          ', '', { - formatNumber : Ext4.util.Format.numberRenderer('0,000'), + formatNumber : Ext4.util.Format.numberRenderer('0,000') } ), @@ -33,21 +33,4 @@ Ext4.define("LABKEY.study.panel.SelectionSummary", { studyCount: 4, participantCount: 10000 } - //tpl : new Ext4.XTemplate( - // '
          ', - // '
          ', - // '
          Summary
          ', - // '
            ', - // '
          • ', - // ' Studies', - // ' {studyCount}', - // '
          • ', - // '
          • ', - // ' Subjects', - // ' {participantCount}', - // '
          • ', - // '
          ', - // '
          ', - // '
          ' - //) }); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/Studies.js b/resources/web/study/Finder/panel/Studies.js index 86c54490..69879773 100644 --- a/resources/web/study/Finder/panel/Studies.js +++ b/resources/web/study/Finder/panel/Studies.js @@ -8,6 +8,8 @@ Ext4.define("LABKEY.study.panel.Studies", { padding: "5 0 0 0", + dataModuleName: 'study', + initComponent : function() { this.items = [ this.getStudyPanelHeader(), @@ -19,7 +21,7 @@ Ext4.define("LABKEY.study.panel.Studies", { getStudyPanelHeader : function() { if (!this.studyPanelHeader) { this.studyPanelHeader = Ext4.create("LABKEY.study.panel.StudyPanelHeader", { - searchTerms: "testing", + dataModuleName: this.dataModuleName, padding: 8 }); } @@ -29,7 +31,7 @@ Ext4.define("LABKEY.study.panel.Studies", { getStudyCards : function() { if (!this.studyCards) { this.studyCards = Ext4.create("LABKEY.study.panel.StudyCards", { - + dataModuleName: this.dataModuleName }); } return this.studyCards; diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index c850e9d4..8721a79a 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -14,12 +14,14 @@ Ext4.define("LABKEY.study.panel.StudyCards", { autoScroll: true, + dataModuleName: 'study', + store : Ext4.create('Ext.data.Store', { model: 'LABKEY.study.data.StudyCard', autoLoad: true, proxy : { type: "ajax", - url: LABKEY.ActionURL.buildURL("trialshare", "getStudies", LABKEY.containerPath), + url: LABKEY.ActionURL.buildURL('trialshare', "studies.api", LABKEY.containerPath), reader: { type: 'json', root: 'data' @@ -72,7 +74,7 @@ Ext4.define("LABKEY.study.panel.StudyCards", { autoScroll: true, loader: { autoLoad: true, - url: 'trialshare-studyDetail.view?_frame=none&studyId=' + studyId + url: this.dataModuleName + '-studyDetail.view?_frame=none&studyId=' + studyId } }); var viewScroll = Ext4.getBody().getScroll(); @@ -93,5 +95,13 @@ Ext4.define("LABKEY.study.panel.StudyCards", { this.detailShowing.destroy(); this.detailShowing = null; } + }, + + initComponent: function() + { + console.log("in StudyCards dataModuleName is " + this.dataModuleName); + this.getStore().load(); + this.callParent(); + } }); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/StudyPanelHeader.js b/resources/web/study/Finder/panel/StudyPanelHeader.js index d2232c93..f24aaf55 100644 --- a/resources/web/study/Finder/panel/StudyPanelHeader.js +++ b/resources/web/study/Finder/panel/StudyPanelHeader.js @@ -1,45 +1,108 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { - extend: 'Ext.view.View', + extend: 'Ext.Container', + + layout: { type: 'hbox', align: 'stretch'}, + + showHelpLinks: true, - studySubsets: [], - selectedSubset: null, searchMessage: "", - searchTerms : "", - - tpl: new Ext4.XTemplate( - '
          ', - ' ', - '  ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' {searchMessage}', - '
          ' - //'' - ), - - listeners : { - change : function(field, newValue, oldValue) { - if (field = "searchTerms") - onSearchTermsChanged(); - else if (field == "studySubsetSelect") - onStudySubsetChanged(); + + dataModuleName: 'study', + + studySubsets : Ext4.create("Ext.data.Store", { + autoLoad: true, + id: 'StudySubsetStore', + model: 'LABKEY.study.data.StudySubset', + proxy: { + type: 'ajax', + url: LABKEY.ActionURL.buildURL('trialshare', "studySubsets.api", LABKEY.containerPath), + reader: { + type: 'json', + root: 'data' + } } + }), + + initComponent: function() { + this.items = []; + this.items.push(this.getSearchBox()); + if (this.getStudySubsetMenu()) + this.items.push(this.getStudySubsetMenu()); + if (this.showHelpLinks) + this.items.push(this.getHelpLinks()); + + this.callParent(); + }, + + getSearchBox : function() { + if (!this.searchBox) { + this.searchBox = Ext4.create('Ext.form.field.Text', { + emptyText:'Studies', + cls: 'search-box', + fieldLabel: '', + labelWidth: "10px", + labelSeparator: '', + fieldCls: 'search-box', + id: 'searchTerms', + listeners: { + scope: this, + 'change': function(field,newValue,oldValue,eOpts) { + this.onSearchTermsChanged(newValue) + } + } + }) + } + return this.searchBox; + + }, + + getStudySubsetMenu: function() { + if (!this.studySubsetMenu && this.studySubsets.count() > 0) { + this.studySubsetMenu = Ext4.create('Ext.form.ComboBox', { + store: this.studySubsets, + name : 'studySubsetSelect', + queryMode: 'local', + valueField: 'id', + displayField: 'name', + cls: 'labkey-study-search', + multiSelect: false, + listeners: { + scope: this, + 'select': function(field, newValue, oldValue, eOpts) { + this.onStudySubsetChanged(newValue[0]) + } + } + }) + } + return this.studySubsetMenu; + }, + + getHelpLinks: function() { + if (!this.helpLinks) { + this.helpLinks = Ext4.create("Ext.button.Button", { + text: 'quick help', + cls: 'labkey-text-link', + scope: this, + handler: function() { + this.startTutorial(); + } + }); + } + return this.helpLinks; + }, + + onSearchTermsChanged: function(value) { + console.log("Search terms changed to ", value); + }, + + onStudySubsetChanged: function(value) { + console.log("Subset changed to ", value.data.id); + }, + + startTutorial: function() { + LABKEY.help.Tour.show("LABKEY.tour.dataFinder"); + return false; } + }); \ No newline at end of file diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 2a78564e..b70aa445 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -33,6 +33,7 @@ import org.labkey.trialshare.data.StudyBean; import org.labkey.trialshare.data.StudyFacetBean; import org.labkey.trialshare.data.StudyFacetMember; +import org.labkey.trialshare.data.StudySubset; import org.springframework.validation.BindException; import org.springframework.web.servlet.ModelAndView; @@ -66,7 +67,7 @@ public ModelAndView getView(Object o, BindException errors) throws Exception } @RequiresPermission(ReadPermission.class) - public class GetStudiesAction extends ApiAction + public class StudiesAction extends ApiAction { @Override @@ -114,7 +115,7 @@ private StudyBean getStudy(String accession) { } @RequiresPermission(ReadPermission.class) - public class GetStudyFacetsAction extends ApiAction + public class StudyFacetsAction extends ApiAction { @Override @@ -219,5 +220,32 @@ public void setStudyId(String studyId) } } + @RequiresPermission(ReadPermission.class) + public class StudySubsetsAction extends ApiAction + { + + @Override + public Object execute(Object o, BindException errors) throws Exception + { + List subsets = new ArrayList<>(); + StudySubset subset = new StudySubset(); + subset.setId("all"); + subset.setName("All"); + + subsets.add(subset); + + subset = new StudySubset(); + subset.setId("operational"); + subset.setName("Operational"); + subsets.add(subset); + + subset = new StudySubset(); + subset.setId("public"); + subset.setName("Public"); + subsets.add(subset); + return success(subsets); + + } + } } \ No newline at end of file diff --git a/src/org/labkey/trialshare/data/StudySubset.java b/src/org/labkey/trialshare/data/StudySubset.java new file mode 100644 index 00000000..96f1f3da --- /dev/null +++ b/src/org/labkey/trialshare/data/StudySubset.java @@ -0,0 +1,30 @@ +package org.labkey.trialshare.data; + +/** + * Created by susanh on 12/10/15. + */ +public class StudySubset +{ + private String _id; + private String _name; + + public String getName() + { + return _name; + } + + public void setName(String name) + { + _name = name; + } + + public String getId() + { + return _id; + } + + public void setId(String id) + { + _id = id; + } +} diff --git a/src/org/labkey/trialshare/view/dataFinder.jsp b/src/org/labkey/trialshare/view/dataFinder.jsp index c799c19f..f8bce811 100644 --- a/src/org/labkey/trialshare/view/dataFinder.jsp +++ b/src/org/labkey/trialshare/view/dataFinder.jsp @@ -30,6 +30,7 @@ resources.add(ClientDependency.fromPath("study/Finder/data/Facet.js")); resources.add(ClientDependency.fromPath("study/Finder/data/FacetMember.js")); resources.add(ClientDependency.fromPath("study/Finder/data/StudyCard.js")); + resources.add(ClientDependency.fromPath("study/Finder/data/StudySubset.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeader.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeader2.js")); @@ -51,7 +52,8 @@ Ext4.onReady(function () { DataFinder.finderView = Ext4.create('LABKEY.study.panel.Finder', { - renderTo : 'dataFinderWrapper' + renderTo : 'dataFinderWrapper', + dataModuleName: 'trialshare' }); }); From ec97bd68ba665d4694a3004ddf98f0f1d6a31ec4 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 11 Dec 2015 08:09:06 -0800 Subject: [PATCH 027/587] Spec 24959 : Implement Data Finder for ITN - add data finder web part --- .../trialshare/TrialShareController.java | 60 +++++++++++++++++++ .../labkey/trialshare/TrialShareModule.java | 10 +++- .../trialshare/view/DataFinderWebPart.java | 39 ++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/org/labkey/trialshare/view/DataFinderWebPart.java diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index b70aa445..1e3ffeca 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -34,6 +34,7 @@ import org.labkey.trialshare.data.StudyFacetBean; import org.labkey.trialshare.data.StudyFacetMember; import org.labkey.trialshare.data.StudySubset; +import org.labkey.trialshare.view.DataFinderWebPart; import org.springframework.validation.BindException; import org.springframework.web.servlet.ModelAndView; @@ -66,6 +67,24 @@ public ModelAndView getView(Object o, BindException errors) throws Exception } } + + @RequiresPermission(ReadPermission.class) + public class DataFinderAction extends SimpleViewAction + { + public ModelAndView getView(Object o, BindException errors) throws Exception + { + setTitle("Data Finder"); + DataFinderWebPart wp = new DataFinderWebPart(getContainer()); + wp.setIsAutoResize(true); + return wp; + } + + public NavTree appendNavTrail(NavTree root) + { + return root; + } + } + @RequiresPermission(ReadPermission.class) public class StudiesAction extends ApiAction { @@ -114,6 +133,47 @@ private StudyBean getStudy(String accession) { return study; } + @RequiresPermission(ReadPermission.class) + public class StudyFacetMembersAction extends ApiAction + { + @Override + public Object execute(Object o, BindException errors) throws Exception + { + List members = new ArrayList<>(); + + StudyFacetMember member = new StudyFacetMember(); + member.setName("Member 1.1"); + member.setCount(10000); + member.setUniqueName("Member11"); + StudyFacetBean facet = new StudyFacetBean(); + facet.setName("Facet1"); + facet.setCaption("Facet 1"); + member.setFacet(facet); + members.add(member); + + member = new StudyFacetMember(); + member.setName("Member 1.2"); + members.add(member); + member.setCount(0); + member.setUniqueName("Member12"); + member.setFacet(facet); + members.add(member); + + member = new StudyFacetMember(); + member.setName("Member 2.1"); + member.setCount(12); + member.setUniqueName("Member21"); + + StudyFacetBean facet2 = new StudyFacetBean(); + facet2.setName("Facet2"); + facet2.setCaption("Facet 2"); + member.setFacet(facet2); + members.add(member); + + return success(members); + } + } + @RequiresPermission(ReadPermission.class) public class StudyFacetsAction extends ApiAction { diff --git a/src/org/labkey/trialshare/TrialShareModule.java b/src/org/labkey/trialshare/TrialShareModule.java index 899f8bd1..4a6326e2 100644 --- a/src/org/labkey/trialshare/TrialShareModule.java +++ b/src/org/labkey/trialshare/TrialShareModule.java @@ -21,8 +21,11 @@ import org.labkey.api.data.ContainerManager; import org.labkey.api.module.DefaultModule; import org.labkey.api.module.ModuleContext; +import org.labkey.api.view.SimpleWebPartFactory; import org.labkey.api.view.WebPartFactory; +import org.labkey.trialshare.view.DataFinderWebPart; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Set; @@ -49,11 +52,14 @@ public boolean hasScripts() return true; } - @Override @NotNull + @Override protected Collection createWebPartFactories() { - return Collections.emptyList(); + ArrayList list = new ArrayList<>(); + SimpleWebPartFactory factory = new SimpleWebPartFactory("TrialShare Data Finder", WebPartFactory.LOCATION_BODY, DataFinderWebPart.class, null); + list.add(factory); + return list; } @Override diff --git a/src/org/labkey/trialshare/view/DataFinderWebPart.java b/src/org/labkey/trialshare/view/DataFinderWebPart.java new file mode 100644 index 00000000..8b6806f4 --- /dev/null +++ b/src/org/labkey/trialshare/view/DataFinderWebPart.java @@ -0,0 +1,39 @@ +package org.labkey.trialshare.view; + +import org.labkey.api.data.Container; +import org.labkey.api.view.ActionURL; +import org.labkey.api.view.JspView; +import org.labkey.api.view.ViewContext; +import org.labkey.trialshare.TrialShareController; + +public class DataFinderWebPart extends JspView +{ + boolean isAutoResize = false; + + public boolean isAutoResize() + { + return isAutoResize; + } + + public void setIsAutoResize(boolean isAutoResize) + { + this.isAutoResize = isAutoResize; + } + + public DataFinderWebPart(Container c) + { + super("/org/labkey/trialshare/view/dataFinder.jsp"); + setTitle("Data Finder"); + setTitleHref(new ActionURL(TrialShareController.DataFinderAction.class, c)); + } + public DataFinderWebPart(ViewContext v) + { + this(v.getContainer()); + } + + @Override + public void setIsOnlyWebPartOnPage(boolean b) + { + setIsAutoResize(b); + } +} From 8259a944818958f07f45332145671fe5141bd08a Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 16 Dec 2015 09:27:00 -0800 Subject: [PATCH 028/587] Spec 24959 : Implement Data Finder for ITN - work on facet selection panel --- resources/web/study/Finder/data/Facet.js | 27 +- .../web/study/Finder/data/FacetFilter.js | 7 + .../web/study/Finder/data/FacetMember.js | 19 +- .../web/study/Finder/data/FacetMembers.js | 253 ++++++++++++++ resources/web/study/Finder/data/Facets.js | 41 +++ resources/web/study/Finder/data/StudyCard.js | 8 +- .../web/study/Finder/data/StudySubset.js | 3 +- resources/web/study/Finder/dataFinder.css | 54 ++- .../study/Finder/panel/FacetPanelHeader.js | 18 +- .../web/study/Finder/panel/FacetSelection.js | 19 +- resources/web/study/Finder/panel/Facets.js | 31 +- .../web/study/Finder/panel/FacetsGrid.js | 309 ++++++++++++++++++ resources/web/study/Finder/panel/Finder.js | 35 +- resources/web/study/Finder/panel/Studies.js | 14 + .../web/study/Finder/panel/StudyCards.js | 12 +- .../study/Finder/panel/StudyPanelHeader.js | 11 + .../trialshare/TrialShareController.java | 195 ++++++++--- .../labkey/trialshare/TrialShareManager.java | 8 + .../labkey/trialshare/data/FacetFilter.java | 35 ++ src/org/labkey/trialshare/data/StudyBean.java | 57 +++- .../trialshare/data/StudyFacetBean.java | 13 +- .../trialshare/data/StudyFacetMember.java | 36 ++ .../labkey/trialshare/data/StudySubset.java | 11 + src/org/labkey/trialshare/view/dataFinder.jsp | 6 +- .../labkey/trialshare/view/studyDetail.jsp | 8 +- 25 files changed, 1099 insertions(+), 131 deletions(-) create mode 100644 resources/web/study/Finder/data/FacetFilter.js create mode 100644 resources/web/study/Finder/data/FacetMembers.js create mode 100644 resources/web/study/Finder/data/Facets.js create mode 100644 resources/web/study/Finder/panel/FacetsGrid.js create mode 100644 src/org/labkey/trialshare/data/FacetFilter.java diff --git a/resources/web/study/Finder/data/Facet.js b/resources/web/study/Finder/data/Facet.js index 76e6e109..59eb8977 100644 --- a/resources/web/study/Finder/data/Facet.js +++ b/resources/web/study/Finder/data/Facet.js @@ -1,13 +1,20 @@ Ext4.define('LABKEY.study.data.Facet', { extend: 'Ext.data.Model', + idProperty : 'name', + fields: [ {name: 'name'}, - {name: 'caption'}, {name: 'pluralName'}, - {name: 'filterOptions'}, + {name: 'uniqueName'}, {name: 'members'}, - {name: 'currentFilters'} + {name: 'selectedMembers'}, + {name: 'filterOptions'}, + {name: 'currentFilterType'}, + {name: 'currentFilterCaption'}, + {name: 'summaryCount', type:'int', default: 0}, + {name: 'allMemerCount', type:'int', default: 0} + ], associations: [ @@ -25,9 +32,9 @@ Ext4.define('LABKEY.study.data.Facet', { }, { type: 'hasMany', - model: 'LABKEY.study.data.FacetFilter', - name: 'currentFilters', - associationKey: 'currentFilters' + model: 'LABKEY.study.data.FacetMember', + name: 'selectedMembers', + associationKey: 'selectedMembers' } ] }); @@ -41,11 +48,3 @@ Ext4.define('LABKEY.study.data.FacetMember', { ] }); - -Ext4.define('LABKEY.study.data.FacetFilter', { - extend: 'Ext.data.Model', - fields :[ - {name: 'type'}, - {name: 'caption'} - ] -}); diff --git a/resources/web/study/Finder/data/FacetFilter.js b/resources/web/study/Finder/data/FacetFilter.js new file mode 100644 index 00000000..c304ed00 --- /dev/null +++ b/resources/web/study/Finder/data/FacetFilter.js @@ -0,0 +1,7 @@ +Ext4.define('LABKEY.study.data.FacetFilter', { + extend: 'Ext.data.Model', + fields :[ + {name: 'type'}, + {name: 'caption'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/FacetMember.js b/resources/web/study/Finder/data/FacetMember.js index 19753f90..dc7acbde 100644 --- a/resources/web/study/Finder/data/FacetMember.js +++ b/resources/web/study/Finder/data/FacetMember.js @@ -1,10 +1,23 @@ -Ext4.define('LABKEY.study.data.FacetMember', { +Ext4.define('LABKEY.study.data.FacetMember2', { extend: 'Ext.data.Model', + idProperty : 'uniqueName', + fields: [ {name: 'name'}, + {name: 'uniqueName'}, {name: 'count'}, {name: 'percent'}, - {name: 'currentFilters'} + {name: 'facetName'}, + {name: 'facetUniqueName'} + ], + + associations: [ + { + type: 'hasMany', + model: 'LABKEY.study.data.FacetFilter', + name: 'filterOptions', + associationKey: 'filterOptions' + } ] -}); \ No newline at end of file +}); diff --git a/resources/web/study/Finder/data/FacetMembers.js b/resources/web/study/Finder/data/FacetMembers.js new file mode 100644 index 00000000..8558bbba --- /dev/null +++ b/resources/web/study/Finder/data/FacetMembers.js @@ -0,0 +1,253 @@ +Ext4.define('LABKEY.study.store.FacetMembers', { + extend: 'Ext.data.Store', + model: 'LABKEY.study.data.FacetMember2', + storeId: 'facetMembers', + autoLoad: true, + dataModuleName: 'trialShare', // TODO figure out how to use this config property so we don't have to hard-code 'trialshare' + proxy : { + type: "ajax", + url: LABKEY.ActionURL.buildURL('trialShare', "studyFacetMembers.api", LABKEY.containerPath), + reader: { + type: 'json', + root: 'data' + } + }, + listeners: { + 'load' : { + fn : function(store, records, options) { + console.log('FacetMember store loaded'); + var facetsStore = Ext4.getStore("facets"); + for (var i = 0; i < records.length; i++) { + if (!facetsStore.getById(records[i].data.facetName)) + { + var facet = { + name: records[i].data.facetName, + uniqueName: records[i].data.facetUniqueName, + filterOptions: records[i].filterOptionsStore ? records[i].filterOptionsStore.data.items : [], + selectedMembers : [] + }; + if (facet.filterOptions.length) { + facet.currentFilterCaption = facet.filterOptions[0].get("caption"); + facet.currentFilterType = facet.filterOptions[0].get("type"); + } + facetsStore.add(facet); + } + } + }, + scope: this + } + }, + groupField : 'facetName', + + updateCountsAsync: function (isSavedGroup) + { + var facetStore = Ext4.getStore("facets"); + //var intersectFilters = []; + //var d, i, dim; + //for (d in dataspace.dimensions) + //{ + // if (!dataspace.dimensions.hasOwnProperty(d)) + // continue; + // dim = dataspace.dimensions[d]; + // var filterMembers = dim.filters; + // if (d == 'Study') + // { + // if (!filterMembers || filterMembers.length == dim.members.length) + // continue; + // if (filterMembers.length == 0) + // { + // // in the case of study filter, this means no matches, rather than no filter! + // this.updateCountsZero(); + // return; + // } + // var uniqueNames = filterMembers.map(function(m){return m.uniqueName;}); + // if (this.filterByLevel != "[Study].[Name]") + // intersectFilters.push({ + // level: this.filterByLevel, + // membersQuery: {level: "[Study].[Name]", members: uniqueNames} + // }); + // else + // intersectFilters.push({level: "[Study].[Name]", members: uniqueNames}); + // } + // else + // { + // if (!filterMembers || filterMembers.length == 0) + // continue; + // if (dim.filterType === "OR") + // { + // var names = []; + // filterMembers.forEach(function (m) + // { + // names.push(m.uniqueName) + // }); + // intersectFilters.push({ + // level: this.filterByLevel, + // membersQuery: {level: filterMembers[0].level, members: names} + // }); + // } + // else + // { + // for (i = 0; i < filterMembers.length; i++) + // { + // var filterMember = filterMembers[i]; + // intersectFilters.push({ + // level: this.filterByLevel, + // membersQuery: {level: filterMember.level, members: [filterMember.uniqueName]} + // }); + // } + // } + // } + //} + // + //var filters = intersectFilters; + //if (intersectFilters.length && this.filterByLevel != "[Subject].[Subject]") + //{ + // filters = [{ + // level: "[Subject].[Subject]", + // membersQuery: {operator: "INTERSECT", arguments: intersectFilters} + // }] + //} + // + //// CONSIDER: Don't fetch subject IDs every time a filter is changed. + //var includeSubjectIds = true; + // + //var onRows = { operator: "UNION", arguments: [] }; + //for (d in dataspace.dimensions) + //{ + // if (!dataspace.dimensions.hasOwnProperty(d)) + // continue; + // dim = dataspace.dimensions[d]; + // if (dim.name == "Subject") + // onRows.arguments.push({level: dim.hierarchy.levels[0].uniqueName}); + // else if (dim.name == "Study" && this.filterByLevel == "[Study].[Name]") + // continue; + // else + // onRows.arguments.push({level: dim.level.uniqueName}); + //} + // + //if (includeSubjectIds) + // onRows.arguments.push({level: "[Subject].[Subject]", members: "members"}); + // + //var config = + //{ + // "sql": true, + // configId: 'ImmPort:/StudyCube', + // schemaName: 'ImmPort', + // name: 'StudyCube', + // success: function (cellSet, mdx, config) + // { + // // use angular timeout() for its implicit $scope.$apply() + // // config.scope.timeout(function(){config.scope.updateCounts(config.dim, cellSet);},1); + // config.scope.timeout(function () + // { + // config.scope.updateCountsUnion(cellSet, isSavedGroup); + // $scope.$broadcast("cubeReady"); + // }, 1); + // }, + // scope: this, + // + // // query + // onRows: onRows, + // countFilter: filters, + // countDistinctLevel: '[Subject].[Subject]' + //}; + //this.mdx.query(config); + }, + + updateCountsZero : function () + { + for (d in dataspace.dimensions) + { + if (!dataspace.dimensions.hasOwnProperty(d)) + continue; + var dim = dataspace.dimensions[d]; + dim.summaryCount = 0; + for (var m = 0; m < dim.members.length; m++) + { + dim.members[m].count = 0; + dim.members[m].percent = 0; + } + dim.summaryCount = 0; + } + + this.saveFilterState(); + this.updateContainerFilter(); + this.changeSubjectGroup(); + this.doneRendering(); + }, + + /* handle query response to update all the member counts with all filters applied */ + updateCountsUnion : function (cellSet, isSavedGroup) + { + var dim, member, d, m; + // map from hierarchyName to dataspace dimension + var map = {}; + + // clear old subjects and counts (to be safe) + //this.subjects.length = 0; + for (d = 0; d < this.count(); d++) + { + dim = this.getAt(d); + map[dim.data.hierarchy.uniqueName] = dim; + dim.data.summaryCount = 0; + dim.data.allMemberCount = 0; + } + for (var i = 0; i < this.count(); i++) { + this.getAt(i).data.count = 0; + this.getAt(i).data.percent = 0; + } + + var positions = cellSetHelper.getRowPositionsOneLevel(cellSet); + var data = cellSetHelper.getDataOneColumn(cellSet, 0); + var max = 0; + for (var i = 0; i < positions.length; i++) + { + var resultMember = positions[i]; + if (resultMember.level.uniqueName == "[Subject].[Subject]") + { + this.subjects.push(resultMember.name); + } + else + { + var hierarchyName = resultMember.level.hierarchy.uniqueName; + dim = map[hierarchyName]; + var count = data[i]; + member = dim.memberMap[resultMember.uniqueName]; + if (!member) + { + // might be an all member + if (dim.allMemberName == resultMember.uniqueName) + dim.allMemberCount = count; + else if (-1 == resultMember.uniqueName.indexOf("#") && "(All)" != resultMember.name) + console.log("member not found: " + resultMember.uniqueName); + } + else + { + member.count = count; + if (count) + dim.summaryCount += 1; + if (count > max) + max = count; + } + } + } + + for (d in dataspace.dimensions) + { + dim = dataspace.dimensions[d]; + map[dim.hierarchy.uniqueName] = dim; + for (m = 0; m < dim.members.length; m++) + { + member = dim.members[m]; + member.percent = max == 0 ? 0 : (100.0 * member.count) / max; + } + } + + //this.saveFilterState(); + //this.updateContainerFilter(); + //if (!isSavedGroup) + // this.changeSubjectGroup(); + //this.doneRendering(); + } + +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js new file mode 100644 index 00000000..7f1182d8 --- /dev/null +++ b/resources/web/study/Finder/data/Facets.js @@ -0,0 +1,41 @@ +Ext4.define('LABKEY.study.store.Facets', { + extend: 'Ext.data.Store', + model: 'LABKEY.study.data.Facet', + storeId: 'facets', + autoLoad: false, + dataModuleName: 'study', + //proxy : { + // type: "ajax", + // url: LABKEY.ActionURL.buildURL("trialshare", "studyFacets.api", LABKEY.containerPath), + // reader: { + // type: 'json', + // root: 'data' + // } + //}, + //listeners: { + // 'load' : { + // fn : function(store, records, options) { + // console.log('Facet store loaded'); + // store.computePercents(); + // }, + // scope: this + // } + //}, + + computePercents: function() { + console.log("Computing percents now"); + }, + + clearAllSelectedMembers: function() { + for (var f = 0; f < this.count(); f++) { + this.getAt(f).data.selectedMembers = []; + } + }, + + selectMembers : function(members) { + for (var i = 0; i < members.length; i++) + { + this.getById(members[i].data.facetName).data.selectedMembers.push(members[i]); + } + } +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/StudyCard.js b/resources/web/study/Finder/data/StudyCard.js index 53e97998..b908afd1 100644 --- a/resources/web/study/Finder/data/StudyCard.js +++ b/resources/web/study/Finder/data/StudyCard.js @@ -1,11 +1,15 @@ Ext4.define('LABKEY.study.data.StudyCard', { extend: 'Ext.data.Model', + idProperty : 'studyId', + fields: [ - {name: 'accession'}, + {name: 'studyId'}, {name: 'title'}, {name: 'url'}, {name: 'investigator'}, - {name: 'isLoaded', type: 'boolean'} + {name: 'hasManuscript', type: 'boolean'}, + {name: 'isLoaded', type: 'boolean'}, + {name: 'availability'} ] }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/StudySubset.js b/resources/web/study/Finder/data/StudySubset.js index fe659a41..cb181ff0 100644 --- a/resources/web/study/Finder/data/StudySubset.js +++ b/resources/web/study/Finder/data/StudySubset.js @@ -3,6 +3,7 @@ Ext4.define('LABKEY.study.data.StudySubset', { fields: [ {name: 'id'}, - {name: 'name'} + {name: 'name'}, + {name: 'default', type:'boolean'} ] }); \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 6a20f31e..ae233e1c 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -77,7 +77,6 @@ SPAN.loaded background-color:rgba(81, 158, 218, 0.2); } - TD.study-panel { vertical-align: top; @@ -163,7 +162,6 @@ DIV.summary LI.member .clear-filter { - background-color: white; float:right; } @@ -195,6 +193,51 @@ DIV.facet-header .labkey-filter-options padding: 3px 0px 0px 20px; } +.x4-grid-row { + border-width: 0px; +} + +.x4-grid-cell-special .x4-grid-cell-inner { + background-color: white; +} + +.x4-grid-group-hd { + background-color:rgb(240, 240, 240); +} + +.x4-grid-cell-special { + border-right-width: 0px; +} + +.x4-grid-group-hd .x4-grid-group-title { + background-position: left 28% +} + +.labkey-study-facets .x-grid-cell { + background-color: #ffa; + border-bottom-color: #ffc; + border-top-color: #ff5; + color: #009; +} + +.facet-selection-header .inactive, +.facet-header .inactive +{ + cursor:default; + color:grey; +} + +/*.facet-header .inactive*/ +/*{*/ + /*display:none;*/ + /*cursor:default;*/ +/*}*/ + +.facet-header .active +{ + display:inline-block; +} + DIV.facet-summary UL, DIV.facet UL { @@ -255,6 +298,7 @@ DIV.facet.collapsed LI.member.selected-member { display:block; } +SPAN.member:hover SPAN.bar, DIV.facet LI.member:hover SPAN.bar { background-color:rgba(81, 158, 218, 0.2); @@ -300,6 +344,8 @@ LI.member .member-name white-space: nowrap; z-index:2; } + +SPAN.member .member-count, LI.member .member-count { position:relative; @@ -307,11 +353,13 @@ LI.member .member-count z-index:2; } +SPAN.member SPAN.bar-selected, LI.member SPAN.bar-selected { background-color:rgba(81, 158, 218, 0.2); } +SPAN.member .bar, LI.member .bar { height:14pt; @@ -372,7 +420,7 @@ ul.nav margin-top : 0; } -.labkey-filter-options > a.inactive +.labkey-filter-options > span.inactive { color: black; cursor: default; diff --git a/resources/web/study/Finder/panel/FacetPanelHeader.js b/resources/web/study/Finder/panel/FacetPanelHeader.js index 4478e79a..e9b6ece0 100644 --- a/resources/web/study/Finder/panel/FacetPanelHeader.js +++ b/resources/web/study/Finder/panel/FacetPanelHeader.js @@ -5,8 +5,8 @@ Ext4.define("LABKEY.study.panel.FacetPanelHeader", { data : { loadedStudiesShown: true, isGuest: false, - hasFilters: true, - showParticipantGroups: true, + hasFilters: false, + showParticipantGroups:false, currentGroup: { id: 1, label: "My group" @@ -29,7 +29,7 @@ Ext4.define("LABKEY.study.panel.FacetPanelHeader", { }] }, tpl: new Ext4.XTemplate( - '
          ', + '
           ', '', '
          Saved group: {currentGroup.label}
          ', '
          ', @@ -91,10 +91,14 @@ Ext4.define("LABKEY.study.panel.FacetPanelHeader", { ' ', ' ', ' ', - ' ', - ' [clear all]', - ' ', + //' ', + ' [clear all]', + //' ', + //' [clear all]', + //' ', '
          ', '
          ' - ) + ), + + }); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index 946fbcf1..558079bf 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -18,6 +18,19 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { this.getFacets() ]; this.callParent(); + + this.on( + 'filterSelectionChanged', this.onFilterSelectionChange + ); + }, + + onFilterSelectionChange: function(hasFilters) { + console.log("FacetSelection filterSelectionChanged handler"); + if (hasFilters) + Ext4.get(Ext4.DomQuery.select('.clear-filter', this.id)[0]).replaceCls('inactive', 'active'); + else + Ext4.get(Ext4.DomQuery.select('.clear-filter', this.id)[0]).replaceCls('active', 'inactive') + }, getFacetPanelHeader : function() { @@ -40,10 +53,14 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { getFacets : function() { if (!this.facets) { - this.facets = Ext4.create("LABKEY.study.panel.Facets", { + //this.facets = Ext4.create("LABKEY.study.panel.Facets", { + // dataModuleName: this.dataModuleName + //}); + this.facets = Ext4.create("LABKEY.study.panel.FacetsGrid", { dataModuleName: this.dataModuleName }); } + FG = this.facets; return this.facets; } diff --git a/resources/web/study/Finder/panel/Facets.js b/resources/web/study/Finder/panel/Facets.js index aff1f296..259b8d9a 100644 --- a/resources/web/study/Finder/panel/Facets.js +++ b/resources/web/study/Finder/panel/Facets.js @@ -14,27 +14,10 @@ Ext4.define("LABKEY.study.panel.Facets", { studyData : [], - store: Ext4.create('Ext.data.Store', { - model: 'LABKEY.study.data.Facet', - autoLoad: true, - proxy : { - type: "ajax", - url: LABKEY.ActionURL.buildURL('trialshare', "studyFacets.api", LABKEY.containerPath), - reader: { - type: 'json', - root: 'data' - } - }, - listeners: { - 'load' : { - fn : function(store, records, options) { - console.log('Facet store loaded'); - this.facetsLoaded = true; - }, - scope: this - } - } + store: Ext4.create('LABKEY.study.store.Facets', { + dataModuleName: this.dataModuleName }), + tpl: new Ext4.XTemplate( '', @@ -50,12 +33,12 @@ Ext4.define("LABKEY.study.panel.Facets", { ' ', ' {name}', ' ', - ' ', + ' ', ' [clear]', ' ', ' ', - ' ', + ' ', '
          ', ' ', ' ', @@ -77,7 +60,7 @@ Ext4.define("LABKEY.study.panel.Facets", { ' ', '
        • ', ' ', - ' ', + ' ', ' ', ' ', ' ', @@ -165,7 +148,7 @@ Ext4.define("LABKEY.study.panel.Facets", { else if (!shiftClick) { this._clearFilter(name); - facet.currentFilters = [element.id]; // TODO add currentFilters to model + facet.selectedMembers = [element.id]; member.selected = true; // TODO change classes here: li gets 'selected-member' and span gets 'selected' } else diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js new file mode 100644 index 00000000..643fda68 --- /dev/null +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -0,0 +1,309 @@ +Ext4.define("LABKEY.study.panel.FacetsGrid", { + + extend : 'Ext.grid.Panel', + + alias: 'widget.labkey-study-facet-panel', + + cls: 'labkey-study-facets', + + ui: 'custom', + + itemSelector: 'div.facet', + + dataModuleName: 'study', + + autoScroll: true, + + bubbleEvents : ["filterSelectionChanged"], + + viewConfig : { stripeRows : false }, + + selType: 'checkboxmodel', + selModel: { + mode: 'SIMPLE', + checkSelector: 'td.x-grid-cell-row-checker' + }, + multiSelect: true, + + header: false, + + hideHeaders: true, + + enableColumnHide: false, + enableColumnResize: false, + columns: [ + { + xtype: 'templatecolumn', + flex: 1, + //header: false, + dataIndex: 'name', + sortable: false, + menuDisabled: true, + cls:'facet', + tpl: new Ext4.XTemplate( + ' ', + ' ', + ' ', + ' ', + ' ', + //'{facetName:this.displaySelection}', + ' {name}', + '  ', + ' {count:this.formatNumber}', + ' ', + ' ', + ' ', + { + formatNumber : Ext4.util.Format.numberRenderer('0,000'), + displaySelection : function(facetName) { + var facet = Ext4.getStore("facets").getById(facetName); + if (facet) + { + var selectedMembers = facet.get("selectedMembers"); + if (selectedMembers || selectedMembers.length == 0) + return ''; + else + return ''; + } + } + } + ) + } + ], + + features: { + ftype: 'grouping', + collapsible: true, + id: 'facetMemberGrouping', + + groupHeaderTpl: new Ext4.XTemplate( + '
          ', + '
          ', + ' {name}', + ' [clear]', + '
          ', + '
          ', + '
          ', + { + genId: function(name) { + var id = Ext4.id(); + LABKEY.study.panel.FacetsGrid.headerLookup[name] = id; + + return id; + } + } + ) + //groupHeaderTpl: new Ext4.XTemplate( + // '{name:this.displayFilterHeader}', + // { + // displayFilterHeader : function(name) { + // var facet = Ext4.getStore("facets").getById(name); + // var selectedMembers = facet.get("selectedMembers"); + // var filterOptions = facet.get("filterOptions"); + // var header = + // '
          ' + + // '
          ' + + // ' ' + name + ''; + // if (selectedMembers && selectedMembers.length > 0) + // header += + // ' [clear]'; + // header += + // '
          '; + // //if (selectedMembers && selectedMembers.length > 0 && filterOptions.length > 0) + // if (filterOptions.length > 0) + // { + // header += + // '
          '; + // if (filterOptions.length < 2) + // { + // header += + // ' '; + // } + // else + // { + // header += + // ' '; + // } + // header += + // ' ' + facet.get("currentFilterCaption"); + // if (filterOptions.length > 1) + // header += + // ' '; + // header += + // ' ' + + // '
          '; + // } + // header += + // '
          '; + // + // return header; + // }, + // genId: function(name) { + // var id = Ext4.id(); + // LABKEY.study.panel.FacetsGrid.headerLookup[name] = id; + // + // return id; + // } + // } + // ) + }, + + statics: { + headerLookup: {} + }, + + initComponent: function() { + this.facetStore = Ext4.create("LABKEY.study.store.Facets"); + + this.store = Ext4.create('LABKEY.study.store.FacetMembers', { + dataModuleName: this.dataModuleName + }); + //this.store.proxy.url = LABKEY.ActionURL.buildURL(this.dataModuleName, "studyFacetMembers.api", LABKEY.containerPath); + + this.store.load(); + + this.callParent(); + + this.mon(this.view, { + groupclick: this.onGroupClick, + //groupcollapse: this.onGroupToggle, + groupexpand: this.onGroupToggle, + scope: this + }); + + this.getSelectionModel().on('selectionchange', this.onSelectionChange, this); + }, + + onSelectionChange: function(selModel, records) { + this.facetStore.clearAllSelectedMembers(); + if (records.length > 0) + this.facetStore.selectMembers(records); + for (var f = 0; f < this.facetStore.count(); f++) { + this.updateFacetHeader(this.facetStore.getAt(f).data.name); + } + this.store.updateCountsAsync(); + + this.fireEvent("filterSelectionChanged", this.hasFilters()); + }, + + onGroupToggle: function(view, node, facetName, eOpts) { + this.updateFacetHeader(facetName); + }, + + onGroupClick : function(view, node, facetName, e, eOpts ) { + console.log("Group " + facetName + " clicked"); + if (e.target.className.includes("clear-filter")) { + console.log("Clearing filter for group " + facetName); + this.clearFilter(facetName); + return false; + } + if (e.target.className.includes("labkey-filter-options") || e.target.className.includes("labkey-filter-caption")) + { + console.log("Showing filter options for facet " + facetName); + this.displayFilterChoice(facetName, e); + return false; + } + }, + + updateFacetHeader: function(facetName) { + if (this.hasFilters(facetName)) + { + Ext4.get(Ext4.DomQuery.select('.clear-filter', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).replaceCls('inactive', 'active'); + } + else + { + Ext4.get(Ext4.DomQuery.select('.clear-filter', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).replaceCls('active', 'inactive'); + } + this.updateCurrentFacetOption(facetName); + }, + + updateCurrentFacetOption: function(facetName) { + var facet = this.facetStore.getById(facetName); + var selectedMembers = facet.get("selectedMembers"); + var html = ""; + if (selectedMembers && selectedMembers.length > 1) + { + html += '' + facet.get("currentFilterCaption") + ''; + if (facet.get("filterOptions").length > 1) + html += ' '; + } + Ext4.get(Ext4.DomQuery.select('.labkey-filter-options', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).setHTML(html); + }, + + displayFilterChoice : function (facetName, event) + { + var facet = this.facetStore.getById(facetName); + + if (!facet) + { + console.error("could not find facet " + facetName); + return; + } + var filterOptions = facet.get("filterOptions"); + if (filterOptions.length < 2) + return; + + var filterOptionsMenu = Ext4.create('Ext.menu.Menu', { + //cls: 'basemenu dropdownmenu', + showSeparator: false, + }); + + filterOptionsMenu.on('click', function(menu, item) { + facet.data.currentFilterType = item.value; + facet.data.currentFilterCaption = item.text; + this.updateCurrentFacetOption(facetName); + }, + this + ); + + for (var i = 0; i < filterOptions.length; i++) { + filterOptionsMenu.add({ + value: filterOptions[i].get("type"), + text: filterOptions[i].get("caption") + }); + } + filterOptionsMenu.showAt(event.xy); + + //if (event.stopPropagation) + // event.stopPropagation(); + }, + + clearAllFilters : function (updateCounts) { + for (var i = 0; i < this.getFacetStore().count(); i++) + { + var name = this.facetStore.getAt(i).get("name"); + if (name == "Study") + continue; + this.clearFilter(name); + } + if (updateCounts) + this.store.updateCountsAsync(); + this.fireEvent("filterSelectionCleared", false); + }, + + clearFilter : function (facetName) { + var facet = this.facetStore.getById(facetName); + facet.data.selectedMembers = []; + for (var i = 0; i < this.store.count(); i++) { + var member = this.store.getAt(i); + if (member.get("facetName") == facetName) { + this.getSelectionModel().deselect(member); + } + } + + this.updateFacetHeader(facetName); + this.fireEvent("filterSelectionCleared", this.hasFilters(facetName)); + }, + + hasFilters : function(facetName) { + if (facetName) + return this.facetStore.getById(facetName).data.selectedMembers.length > 0; + else { + for (var i = 0; i < this.facetStore.count(); i++) { + if (this.facetStore.getAt(i).data.selectedMembers.length > 0) + return true; + } + return false; + } + } +}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index 7c407a92..5060bc2b 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -7,7 +7,7 @@ Ext4.define('LABKEY.study.panel.Finder', { cls: 'labkey-data-finder-view', - title: "Data Finder", + border: false, showParticipantFilters: false, @@ -19,6 +19,8 @@ Ext4.define('LABKEY.study.panel.Finder', { dataModuleName: 'study', + autoScroll : true, + initComponent : function() { this.items = [ @@ -27,6 +29,37 @@ Ext4.define('LABKEY.study.panel.Finder', { ]; this.callParent(); + + this._initResize(); + + this.on( + 'filterSelectionChanged', this.onFilterSelectionChange + //'studySubsetChanged', this.onStudySubsetChanged, + //'searchTermsChanged', this.onSearchTermsChanged + ); + }, + + //onStudySubsetChanged : function(value) { + // this.getStudiesPanel().getStudyCards().store.filter('availability', value); + //}, + // + onFilterSelectionChange : function(){ + console.log("Filter selection changed!"); + }, + + _initResize : function() { + var resize = function(w, h) { + LABKEY.ext4.Util.resizeToViewport(this, w, h, 46, 32); + }; + + Ext4.EventManager.onWindowResize(resize, this); + + this.on('afterrender', function() { + Ext4.defer(function() { + var size = Ext4.getBody().getBox(); + resize.call(this, size.width, size.height); + }, 300, this); + }); }, getFacetsPanel: function() { diff --git a/resources/web/study/Finder/panel/Studies.js b/resources/web/study/Finder/panel/Studies.js index 69879773..bd582d85 100644 --- a/resources/web/study/Finder/panel/Studies.js +++ b/resources/web/study/Finder/panel/Studies.js @@ -16,6 +16,20 @@ Ext4.define("LABKEY.study.panel.Studies", { this.getStudyCards() ]; this.callParent(); + + this.on( + {'studySubsetChanged': this.onStudySubsetChanged, + 'searchTermsChanged': this.onSearchTermsChanged} + ); + }, + + onStudySubsetChanged : function(value) { + this.getStudyCards().store.clearFilter(); + this.getStudyCards().store.filter('availability', value); + }, + + onSearchTermsChanged : function(value) { + console.log("search terms changed to " + value) }, getStudyPanelHeader : function() { diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index 8721a79a..ba325284 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -32,22 +32,26 @@ Ext4.define("LABKEY.study.panel.StudyCards", { tpl: new Ext4.XTemplate( '
          ', ' ', - ' ', + ' ', + // TODO this should be labkey-study-card-highlight-1 instead of loaded '
          ', ' ', '
          ', ' ', - ' {accession}', + ' {studyId}', ' {investigator}', '
          ', '
          ', ' view summary', - ' ', + ' ', ' go to study', ' ', '
          ', '
          {title}
          ', + ' ', + '

          Manuscript available', + '
          ', '
          ', ' ', '
          ' @@ -56,7 +60,7 @@ Ext4.define("LABKEY.study.panel.StudyCards", { listeners: { itemClick: function(view, record, item, index, e, eOpts) { console.log("Show study popup for record " , record); - this.showPopup(record.get("accession")); + this.showPopup(record.get("studyId")); } }, diff --git a/resources/web/study/Finder/panel/StudyPanelHeader.js b/resources/web/study/Finder/panel/StudyPanelHeader.js index f24aaf55..f6965828 100644 --- a/resources/web/study/Finder/panel/StudyPanelHeader.js +++ b/resources/web/study/Finder/panel/StudyPanelHeader.js @@ -10,6 +10,11 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { dataModuleName: 'study', + bubbleEvents: [ + "studySubsetChanged", + "searchTermsChanged" + ], + studySubsets : Ext4.create("Ext.data.Store", { autoLoad: true, id: 'StudySubsetStore', @@ -65,12 +70,16 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { queryMode: 'local', valueField: 'id', displayField: 'name', + value: this.studySubsets.getAt(0), cls: 'labkey-study-search', multiSelect: false, listeners: { scope: this, 'select': function(field, newValue, oldValue, eOpts) { this.onStudySubsetChanged(newValue[0]) + }, + 'render': function(eOpts) { + this.onStudySubsetChanged(this.studySubsets.getAt(0)) } } }) @@ -94,10 +103,12 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { onSearchTermsChanged: function(value) { console.log("Search terms changed to ", value); + this.fireEvent("searchTermsChanged", value); }, onStudySubsetChanged: function(value) { console.log("Subset changed to ", value.data.id); + this.fireEvent("studySubsetChanged", value.data.id); }, startTutorial: function() { diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 1e3ffeca..6fac913b 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -21,6 +21,7 @@ import org.labkey.api.action.ReturnUrlForm; import org.labkey.api.action.SimpleViewAction; import org.labkey.api.action.SpringActionController; +import org.labkey.api.data.SqlExecutor; import org.labkey.api.gwt.client.util.StringUtils; import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.ReadPermission; @@ -30,6 +31,7 @@ import org.labkey.api.view.NavTree; import org.labkey.api.view.NotFoundException; import org.labkey.api.view.VBox; +import org.labkey.trialshare.data.FacetFilter; import org.labkey.trialshare.data.StudyBean; import org.labkey.trialshare.data.StudyFacetBean; import org.labkey.trialshare.data.StudyFacetMember; @@ -93,42 +95,59 @@ public class StudiesAction extends ApiAction public Object execute(StudiesForm form, BindException errors) throws Exception { List studies = new ArrayList(); - StudyBean study = new StudyBean(); - - studies.add(getStudy("ABC123")); - studies.add(getStudy("XY1YVA")); + studies.add(getStudy("ITN029ST")); + studies.add(getStudy("ITN021AI")); + studies.add(getStudy("ITN033AI")); return success(studies); } } - public static class StudiesForm { + public static class StudiesForm + {} - } + private StudyBean getStudy(String studyId) + { + SqlExecutor executor = new SqlExecutor(TrialShareManager.getSchema()); - private StudyBean getStudy(String accession) { StudyBean study = new StudyBean(); - if (accession.equals("ABC123")) { - study.setAccession("ABC123"); - study.setInvestigator("Some One"); - study.setTitle("The title of this study is very descriptive"); + if (studyId.equals("ITN029ST")) + { + study.setStudyId("ITN029ST"); + study.setInvestigator("Sandy Feng, MD, PhD"); + study.setTitle("Immunosuppression Withdrawal for Pediatric Living-donor Liver Transplant Recipients"); + study.setDescription("This is a prospective multicenter, open-label, single-arm trial in which 20 pediatric recipients of parental living-donor liver allografts will undergo gradual withdrawal of immunosuppression with the goal of complete withdrawal. Patients on stable immunosuppression regimens with good organ function and no evidence of acute or chronic rejection or other forms of allograft dysfunction will be enrolled. Participants will undergo gradual withdrawal of immunosuppression and will be followed for a minimum of 4 years after completion of immunosuppression withdrawal. Immunologic and genetic profiles will be collected at multiple time points and compared between tolerant and nontolerant participants."); study.setIsLoaded(true); - study.setUrl("/labkey/project/ITN%20TrialShare/ABC123/begin.view?"); - } else if (accession.equals("XY1YVA")) { - study.setAccession("XY1YVA"); - study.setInvestigator("Some One Else"); - study.setTitle("The title of this study is even more descriptive"); + study.setAvailability("operational"); + study.setHasManuscript(true); + study.setUrl("https://www.itntrialshare.org/project/Studies/ITN029STOPR/Study%20Data/begin.view"); + } + else if (studyId.equals("ITN021AI")) + { + study.setStudyId("ITN021AI"); + study.setInvestigator("John H. Stone, MD, MPH"); + study.setTitle("Rituximab for ANCA-Associated Vasculitis"); study.setIsLoaded(false); - study.setDescription("Immunosuppression Withdrawal for Pediatric Living-donor Liver Transplant Recipients\n" + + study.setAvailability("public"); + study.setHasManuscript(false); + study.setDescription("Current conventional therapies for ANCA-associated vasculitis (AAV) are associated with high incidences of treatment failure, disease relapse, substantial toxicity, and patient morbidity and mortality. Rituximab is a monoclonal antibody used to treat non-Hodgkin's lymphoma. This study will evaluate the efficacy of rituximab with glucocorticoids in inducing disease remission in adults with severe forms of AAV (WG and MPA).\n" + "\n" + - "Protocol Chair: Sandy Feng, MD, PhD\n" + + "The study consists of two phases: a 6-month remission induction phase, followed by a 12-month remission maintenance phase. All participants will receive at least 1 g of pulse IV methylprednisolone or a dose-equivalent of another glucocorticoid preparation. Depending on the participant's condition, he or she may receive up to 3 days of IV methylprednisolone for a total of 3 g of methylprednisolone (or a dose-equivalent). During the remission induction phase, all participants will receive oral prednisone daily (1 mg/kg/day, not to exceed 80 mg/day). Prednisone tapering will be completed by the Month 6 study visit.\n" + "\n" + - "This is a prospective multicenter, open-label, single-arm trial in which 20 pediatric recipients of parental living-donor liver allografts will undergo gradual withdrawal of immunosuppression with the goal of complete withdrawal. Patients on stable immunosuppression regimens with good organ function and no evidence of acute or chronic rejection or other forms of allograft dysfunction will be enrolled. Participants will undergo gradual withdrawal of immunosuppression and will be followed for a minimum of 4 years after completion of immunosuppression withdrawal. Immunologic and genetic profiles will be collected at multiple time points and compared between tolerant and nontolerant participants.\n" + + "Next, participants will be randomly assigned to one of two arms. Arm 1 participants will receive rituximab (375 mg/m2) infusions once weekly for 4 weeks and cyclophosphamide (CYC) placebo daily for 3 to 6 months. Arm 2 participants will receive rituximab placebo infusions once weekly for 4 weeks and CYC daily for 3 to 6 months. During the remission maintenance phase, participants in Arm 1 will discontinue CYC placebo and start oral azathioprine (AZA) placebo daily until Month 18. Participants in Arm 2 will discontinue CYC and start AZA daily until Month 18. Participants who fail treatment before Month 6 will be crossed over to the other treatment arm unless there are specific contraindications.\n" + "\n" + - "Cohort\n" + - "Description\n" + - "Immunosuppression Withdrawal\tPediatric recipients of parental living-donor liver allografts\n" + - "ClinTrials.gov #: NCT00320606"); + "All participants will be followed for at least 18 months. Initially, study visits are weekly, progressing to monthly and then quarterly visits as the study proceeds. Blood collection will occur at each study visit."); + } + else if (studyId.equals("ITN033AI")) + { + study.setStudyId("ITN033AI"); + study.setInvestigator("Richard A. Nash, MD"); + study.setTitle("High Dose Immunosuppression and Autologous Transplantation for Multiple Sclerosis"); + study.setIsLoaded(false); + study.setAvailability("operational"); + study.setHasManuscript(false); + study.setDescription("This study is a prospective, multicenter Phase II clinical trial evaluating high-dose immunosuppressive therapy (HDIT) using Carmustine, Etoposide, Cytarabine, and Melphalan (BEAM) plus Thymoglobulin (rATG) with autologous transplantation of CD34+ HCT for the treatment of poor-risk MS. The active treatment period will be approximately 3 months from the time of initiation of mobilization to the day of discharge after transplant. Subjects will be followed up to 60 months (5 years) after transplant. Total study duration will be 60 months after the last subject is transplanted."); + study.setUrl("https://www.itntrialshare.org/project/Studies/ITN033AIOPR/Study%20Data/begin.view"); } return study; } @@ -142,38 +161,115 @@ public Object execute(Object o, BindException errors) throws Exception List members = new ArrayList<>(); StudyFacetMember member = new StudyFacetMember(); - member.setName("Member 1.1"); - member.setCount(10000); - member.setUniqueName("Member11"); - StudyFacetBean facet = new StudyFacetBean(); - facet.setName("Facet1"); - facet.setCaption("Facet 1"); - member.setFacet(facet); + member.setName("Transplant"); + member.setUniqueName("Transplant"); + member.setFacetName("Therapeutic Area"); + member.setFacetUniqueName("TherapeuticArea"); + member.setFilterOptions(getFacetFilters()); + member.setCount(4); members.add(member); member = new StudyFacetMember(); - member.setName("Member 1.2"); + member.setName("Autoimmune"); + member.setUniqueName("Autoimmune"); + member.setFacetName("Therapeutic Area"); + member.setFacetUniqueName("TherapeuticArea"); + member.setFilterOptions(getFacetFilters()); + member.setCount(3); members.add(member); + + member = new StudyFacetMember(); + member.setName("Allergy"); + member.setUniqueName("Allergy"); + member.setFacetName("Therapeutic Area"); + member.setFacetUniqueName("TherapeuticArea"); + member.setFilterOptions(getFacetFilters()); + member.setCount(1); + members.add(member); + + member = new StudyFacetMember(); + member.setName("T1DM"); + member.setUniqueName("T1DM"); + member.setFacetName("Therapeutic Area"); + member.setFacetUniqueName("TherapeuticArea"); + member.setFilterOptions(getFacetFilters()); member.setCount(0); - member.setUniqueName("Member12"); - member.setFacet(facet); members.add(member); member = new StudyFacetMember(); - member.setName("Member 2.1"); - member.setCount(12); - member.setUniqueName("Member21"); + member.setName("Interventional"); + member.setUniqueName("Interventional"); + member.setFacetName("Study Type"); + member.setFacetUniqueName("StudyType"); + member.setFilterOptions(getFacetFilters()); + member.setCount(5); + members.add(member); - StudyFacetBean facet2 = new StudyFacetBean(); - facet2.setName("Facet2"); - facet2.setCaption("Facet 2"); - member.setFacet(facet2); + member = new StudyFacetMember(); + member.setName("Observational"); + member.setUniqueName("Observational"); + member.setFacetName("Study Type"); + member.setFacetUniqueName("StudyType"); + member.setFilterOptions(getFacetFilters()); + member.setCount(2); + members.add(member); + + member = new StudyFacetMember(); + member.setName("Expanded Access"); + member.setUniqueName("Expanded Access"); + member.setFacetName("Study Type"); + member.setFacetUniqueName("StudyType"); + member.setFilterOptions(getFacetFilters()); + member.setCount(3); + members.add(member); + + member = new StudyFacetMember(); + member.setName("Adult"); + member.setUniqueName("Adult"); + member.setFacetName("Age Group"); + member.setFacetUniqueName("AgeGroup"); + member.setFilterOptions(getFacetFilters()); + member.setCount(4); + members.add(member); + + member = new StudyFacetMember(); + member.setName("Child"); + member.setUniqueName("Child"); + member.setFacetName("Age Group"); + member.setFacetUniqueName("AgeGroup"); + member.setFilterOptions(getFacetFilters()); + member.setCount(1); + members.add(member); + + member = new StudyFacetMember(); + member.setName("Senior"); + member.setUniqueName("Senior"); + member.setFacetName("Age Group"); + member.setFacetUniqueName("AgeGroup"); + member.setFilterOptions(getFacetFilters()); + member.setCount(3); members.add(member); return success(members); } } + + private List getFacetFilters() + { + List filterOptions = new ArrayList<>(); + FacetFilter filter = new FacetFilter(); + filter.setType(FacetFilter.Type.OR); + filter.setCaption("is any of"); + filterOptions.add(filter); + filter = new FacetFilter(); + filter.setType(FacetFilter.Type.AND); + filter.setCaption("is all of"); + filterOptions.add(filter); + return filterOptions; + } + + @RequiresPermission(ReadPermission.class) public class StudyFacetsAction extends ApiAction { @@ -181,19 +277,18 @@ public class StudyFacetsAction extends ApiAction @Override public Object execute(Object o, BindException errors) throws Exception { - List facets = new ArrayList(); + List facets = new ArrayList<>(); StudyFacetBean facet = new StudyFacetBean(); facet.setName("Facet1"); facet.setCaption("Facet 1"); - List members = new ArrayList(); + List members = new ArrayList<>(); StudyFacetMember member = new StudyFacetMember(); member.setName("Member 1.1"); member.setCount(10000); member.setUniqueName("Member11"); members.add(member); - member = new StudyFacetMember(); member.setName("Member 1.2"); members.add(member); @@ -201,7 +296,6 @@ public Object execute(Object o, BindException errors) throws Exception member.setUniqueName("Member12"); facet.setMembers(members); - facets.add(facet); StudyFacetBean facet2 = new StudyFacetBean(); @@ -289,22 +383,23 @@ public Object execute(Object o, BindException errors) throws Exception { List subsets = new ArrayList<>(); StudySubset subset = new StudySubset(); - subset.setId("all"); - subset.setName("All"); - - subsets.add(subset); +// subset.setId("all"); +// subset.setName("All"); +// +// subsets.add(subset); - subset = new StudySubset(); +// subset = new StudySubset(); subset.setId("operational"); subset.setName("Operational"); + subset.setDefault(false); subsets.add(subset); subset = new StudySubset(); subset.setId("public"); subset.setName("Public"); + subset.setDefault(true); subsets.add(subset); return success(subsets); - } } diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index cd0ea979..a1c9f8a4 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -16,6 +16,9 @@ package org.labkey.trialshare; +import org.labkey.api.data.DbSchema; +import org.labkey.api.data.DbSchemaType; + public class TrialShareManager { private static final TrialShareManager _instance = new TrialShareManager(); @@ -29,4 +32,9 @@ public static TrialShareManager get() { return _instance; } + + public static DbSchema getSchema() + { + return DbSchema.get("trialShare", DbSchemaType.Module); + } } \ No newline at end of file diff --git a/src/org/labkey/trialshare/data/FacetFilter.java b/src/org/labkey/trialshare/data/FacetFilter.java new file mode 100644 index 00000000..d9f2bd10 --- /dev/null +++ b/src/org/labkey/trialshare/data/FacetFilter.java @@ -0,0 +1,35 @@ +package org.labkey.trialshare.data; + +import org.labkey.api.action.Marshal; +import org.labkey.api.action.Marshaller; + +/** + * Created by susanh on 12/14/15. + */ +public class FacetFilter +{ + public enum Type { OR, AND }; + + private Type _type; + private String _caption; + + public String getCaption() + { + return _caption; + } + + public void setCaption(String caption) + { + _caption = caption; + } + + public Type getType() + { + return _type; + } + + public void setType(Type type) + { + _type = type; + } +} diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index bb91ebaa..debdb9db 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -7,25 +7,30 @@ */ public class StudyBean { - private String accession; + private String shortName; + private String studyId; private String title; + private Boolean hasManuscript; // TODO generalize? private String investigator; private String url; private Boolean isLoaded; private String description; private String briefDescription; - private List personnel; + private String studyIdPrefix = null; // common prefix used in labeling studies + private String availability; + + private List personnel; // TODO remove? private List pubmed; - private String labelPrefix = null; // common prefix used in labeling studies - public String getAccession() + + public String getStudyId() { - return accession; + return studyId; } - public void setAccession(String accession) + public void setStudyId(String studyId) { - this.accession = accession; + this.studyId = studyId; } public String getInvestigator() @@ -108,14 +113,44 @@ public void setPubmed(List pubmed) this.pubmed = pubmed; } - public String getLabelPrefix() + public String getStudyIdPrefix() + { + return studyIdPrefix; + } + + public void setStudyIdPrefix(String studyIdPrefix) + { + this.studyIdPrefix = studyIdPrefix; + } + + public Boolean getHasManuscript() + { + return hasManuscript; + } + + public void setHasManuscript(Boolean hasManuscript) + { + this.hasManuscript = hasManuscript; + } + + public String getShortName() + { + return shortName; + } + + public void setShortName(String shortName) + { + this.shortName = shortName; + } + + public String getAvailability() { - return labelPrefix; + return availability; } - public void setLabelPrefix(String labelPrefix) + public void setAvailability(String availability) { - this.labelPrefix = labelPrefix; + this.availability = availability; } } diff --git a/src/org/labkey/trialshare/data/StudyFacetBean.java b/src/org/labkey/trialshare/data/StudyFacetBean.java index d37f9b75..be91f40e 100644 --- a/src/org/labkey/trialshare/data/StudyFacetBean.java +++ b/src/org/labkey/trialshare/data/StudyFacetBean.java @@ -1,5 +1,7 @@ package org.labkey.trialshare.data; +import com.fasterxml.jackson.annotation.JsonIgnore; + import java.util.List; /** @@ -13,7 +15,7 @@ public class StudyFacetBean private String hierarchyName; private String levelName; private String allMemberName; - private String filterType; + private String defaultFilterType; private List members; private List filterOptions; @@ -27,6 +29,7 @@ public void setCaption(String caption) this.caption = caption; } + @JsonIgnore public List getMembers() { return members; @@ -67,14 +70,14 @@ public void setFilterOptions(List filterOptions) this.filterOptions = filterOptions; } - public String getFilterType() + public String getDefaultFilterType() { - return filterType; + return defaultFilterType; } - public void setFilterType(String filterType) + public void setDefaultFilterType(String defaultFilterType) { - this.filterType = filterType; + this.defaultFilterType = defaultFilterType; } public String getHierarchyName() diff --git a/src/org/labkey/trialshare/data/StudyFacetMember.java b/src/org/labkey/trialshare/data/StudyFacetMember.java index 23b39322..7e3cbeaf 100644 --- a/src/org/labkey/trialshare/data/StudyFacetMember.java +++ b/src/org/labkey/trialshare/data/StudyFacetMember.java @@ -1,5 +1,8 @@ package org.labkey.trialshare.data; +import java.util.List; +import java.util.Map; + /** * Created by susanh on 12/8/15. */ @@ -9,6 +12,9 @@ public class StudyFacetMember private String uniqueName; private Integer count; private Float percent; + private String facetName; + private String facetUniqueName; + private List filterOptions; public Integer getCount() { @@ -49,4 +55,34 @@ public void setUniqueName(String uniqueName) { this.uniqueName = uniqueName; } + + public String getFacetName() + { + return facetName; + } + + public void setFacetName(String facetName) + { + this.facetName = facetName; + } + + public String getFacetUniqueName() + { + return facetUniqueName; + } + + public void setFacetUniqueName(String facetUniqueName) + { + this.facetUniqueName = facetUniqueName; + } + + public List getFilterOptions() + { + return filterOptions; + } + + public void setFilterOptions(List filterOptions) + { + this.filterOptions = filterOptions; + } } diff --git a/src/org/labkey/trialshare/data/StudySubset.java b/src/org/labkey/trialshare/data/StudySubset.java index 96f1f3da..c74f2733 100644 --- a/src/org/labkey/trialshare/data/StudySubset.java +++ b/src/org/labkey/trialshare/data/StudySubset.java @@ -7,6 +7,7 @@ public class StudySubset { private String _id; private String _name; + private Boolean _default; public String getName() { @@ -27,4 +28,14 @@ public void setId(String id) { _id = id; } + + public Boolean getDefault() + { + return _default; + } + + public void setDefault(Boolean aDefault) + { + _default = aDefault; + } } diff --git a/src/org/labkey/trialshare/view/dataFinder.jsp b/src/org/labkey/trialshare/view/dataFinder.jsp index f8bce811..52120538 100644 --- a/src/org/labkey/trialshare/view/dataFinder.jsp +++ b/src/org/labkey/trialshare/view/dataFinder.jsp @@ -28,13 +28,17 @@ resources.add(ClientDependency.fromPath("query/olap.js")); resources.add(ClientDependency.fromPath("study/Finder/dataFinder.css")); resources.add(ClientDependency.fromPath("study/Finder/data/Facet.js")); + resources.add(ClientDependency.fromPath("study/Finder/data/FacetFilter.js")); resources.add(ClientDependency.fromPath("study/Finder/data/FacetMember.js")); resources.add(ClientDependency.fromPath("study/Finder/data/StudyCard.js")); resources.add(ClientDependency.fromPath("study/Finder/data/StudySubset.js")); + resources.add(ClientDependency.fromPath("study/Finder/data/Facets.js")); + resources.add(ClientDependency.fromPath("study/Finder/data/FacetMembers.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeader.js")); - resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeader2.js")); +// resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeader2.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/Facets.js")); + resources.add(ClientDependency.fromPath("study/Finder/panel/FacetsGrid.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/SelectionSummary.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/FacetSelection.js")); diff --git a/src/org/labkey/trialshare/view/studyDetail.jsp b/src/org/labkey/trialshare/view/studyDetail.jsp index c902cbcf..1e5fb4e6 100644 --- a/src/org/labkey/trialshare/view/studyDetail.jsp +++ b/src/org/labkey/trialshare/view/studyDetail.jsp @@ -78,9 +78,9 @@ Container studyContainer = ContainerManager.getForId((String) map.get("container")); String studyAccession = (String)map.get("study_accession"); String name = (String)map.get("Label"); - if (null == studyAccession && study.getLabelPrefix() != null && name.startsWith(study.getLabelPrefix())) + if (null == studyAccession && study.getStudyIdPrefix() != null && name.startsWith(study.getStudyIdPrefix())) studyAccession = name; - if (null != studyContainer && StringUtils.equalsIgnoreCase(study.getAccession(), studyAccession)) + if (null != studyContainer && StringUtils.equalsIgnoreCase(study.getStudyId(), studyAccession)) { studyUrl = studyContainer.getStartURL(context.getUser()); break; @@ -93,7 +93,7 @@ %>
          -

          <% if (null!=studyUrl) {%><%}%><%=h(study.getAccession())%><% if (null!=studyUrl) {%><%}%>

          +

          <% if (null!=studyUrl) {%><%}%><%=h(study.getStudyId())%><% if (null!=studyUrl) {%><%}%>

          <%=h(study.getTitle())%>

          <% @@ -130,7 +130,7 @@
          <% if (null != studyUrl) { %> - <%= textLink("View study " + study.getAccession(), studyUrl.toString(), null, null, linkProps)%>
          + <%= textLink("View study " + study.getStudyId(), studyUrl.toString(), null, null, linkProps)%>
          <% } %>
          From 492b8214317f3b7d3ed0c70d2fb970234f87c2be Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 16 Dec 2015 16:20:03 -0800 Subject: [PATCH 029/587] Spec 24959 : Implement Data Finder for ITN -improve styling in facets display and make clear all link work --- .../web/study/Finder/data/FacetMembers.js | 52 +++- resources/web/study/Finder/dataFinder.css | 57 +++- .../study/Finder/panel/FacetPanelHeader.js | 245 +++++++++++------- .../study/Finder/panel/FacetPanelHeaderTpl.js | 104 ++++++++ .../web/study/Finder/panel/FacetSelection.js | 17 +- .../web/study/Finder/panel/FacetsGrid.js | 118 +++------ resources/web/study/Finder/panel/Finder.js | 12 + .../web/study/Finder/panel/StudyCards.js | 2 +- .../trialshare/TrialShareController.java | 10 + .../trialshare/data/StudyFacetMember.java | 6 +- src/org/labkey/trialshare/view/dataFinder.jsp | 4 +- .../labkey/trialshare/view/studyDetail.jsp | 1 - 12 files changed, 425 insertions(+), 203 deletions(-) create mode 100644 resources/web/study/Finder/panel/FacetPanelHeaderTpl.js diff --git a/resources/web/study/Finder/data/FacetMembers.js b/resources/web/study/Finder/data/FacetMembers.js index 8558bbba..20603436 100644 --- a/resources/web/study/Finder/data/FacetMembers.js +++ b/resources/web/study/Finder/data/FacetMembers.js @@ -192,13 +192,13 @@ Ext4.define('LABKEY.study.store.FacetMembers', { dim.data.summaryCount = 0; dim.data.allMemberCount = 0; } - for (var i = 0; i < this.count(); i++) { - this.getAt(i).data.count = 0; - this.getAt(i).data.percent = 0; + for (d = 0; d < this.count(); d++) { + this.getAt(d).data.count = 0; + this.getAt(d).data.percent = 0; } - var positions = cellSetHelper.getRowPositionsOneLevel(cellSet); - var data = cellSetHelper.getDataOneColumn(cellSet, 0); + var positions = this.getRowPositionsOneLevel(cellSet); + var data = this.getDataOneColumn(cellSet, 0); var max = 0; for (var i = 0; i < positions.length; i++) { @@ -248,6 +248,48 @@ Ext4.define('LABKEY.study.store.FacetMembers', { //if (!isSavedGroup) // this.changeSubjectGroup(); //this.doneRendering(); + }, + + getRowPositions : function(cellSet) + { + return cellSet.axes[1].positions; + }, + + getRowPositionsOneLevel : function(cellSet) + { + var positions = cellSet.axes[1].positions; + if (positions.length > 0 && positions[0].length > 1) + { + console.log("warning: rows have nested members"); + throw "illegal state"; + } + return positions.map(function(inner){return inner[0]}); + }, + + getData : function(cellSet,defaultValue) + { + var cells = cellSet.cells; + var ret = cells.map(function(row) + { + return row.map(function(col){return col.value ? col.value : defaultValue;}); + }); + return ret; + }, + + getDataOneColumn : function(cellSet,defaultValue) + { + var cells = cellSet.cells; + if (cells.length > 0 && cells[0].length > 1) + { + console.log("warning cellSet has more than one column"); + throw "illegal state"; + } + var ret = cells.map(function(row) + { + return row[0].value ? row[0].value : defaultValue; + }); + return ret; } + }); \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index ae233e1c..98028a99 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -160,7 +160,37 @@ DIV.summary LI.member background-color: white; } -.clear-filter +.labkey-clear-all +{ + float: right; + background-color: white; + background-image: none; + color: black; + border: none; +} + +.labkey-clear-all .x4-btn-inner { + text-transform: none; + font-weight: normal; + font-size:100%; +} + +.labkey-clear-all.inactive +{ + cursor: default; +} +.labkey-clear-all.inactive .x4-btn-inner +{ + color: grey; + cursor:default; +} + +.labkey-clear-all.active .x4-btn-inner +{ + color:black; +} + +.labkey-clear-filter { float:right; } @@ -197,6 +227,12 @@ DIV.facet-header .labkey-filter-options border-width: 0px; } +.x4-grid-row-selected .x4-grid-td { + background-color: white; +} + + + .x4-grid-cell-special .x4-grid-cell-inner { background-color: white; } @@ -220,6 +256,12 @@ DIV.facet-header .labkey-filter-options color: #009; } +a.labkey-clear-filter.inactive +{ + cursor:default; + color:grey; +} + .facet-selection-header .inactive, .facet-header .inactive { @@ -294,10 +336,12 @@ DIV.expanded .fa-minus-square float:left; display:inline; } + DIV.facet.collapsed LI.member.selected-member { display:block; } + SPAN.member:hover SPAN.bar, DIV.facet LI.member:hover SPAN.bar { @@ -353,13 +397,22 @@ LI.member .member-count z-index:2; } +.x4-grid-row-selected SPAN.bar, SPAN.member SPAN.bar-selected, LI.member SPAN.bar-selected { background-color:rgba(81, 158, 218, 0.2); } -SPAN.member .bar, +.x4-grid-row-focused .x4-grid-td, +.x4-grid-row-before-focused .x4-grid-td, +.x4-grid-cell +{ + border-bottom-style: hidden; + border-top-style: hidden; +} + +.x4-grid-row SPAN.bar, LI.member .bar { height:14pt; diff --git a/resources/web/study/Finder/panel/FacetPanelHeader.js b/resources/web/study/Finder/panel/FacetPanelHeader.js index e9b6ece0..1f248f97 100644 --- a/resources/web/study/Finder/panel/FacetPanelHeader.js +++ b/resources/web/study/Finder/panel/FacetPanelHeader.js @@ -1,104 +1,157 @@ Ext4.define("LABKEY.study.panel.FacetPanelHeader", { - extend: "Ext.Component", + extend: "Ext.Container", padding: "0 0 5 0", - data : { - loadedStudiesShown: true, - isGuest: false, - hasFilters: false, - showParticipantGroups:false, - currentGroup: { - id: 1, - label: "My group" - }, - saveOptions: [ - { - id: "save", - label : "Save", - isActive : true - }, - { - id: "saveAs", - label : "Save As", - isActive : true - } - ], + bubbleEvents: ["clearAllFilters"], + + // TODO need to hook up the participant group functionality to the menus + showParticipantGroups: false, + + isGuest : false, - groups: [{ - label: "my first group" - }] + currentGroup: { + id: null, + label: "Unsaved group" }, - tpl: new Ext4.XTemplate( - '
           ', - '', - '
          Saved group: {currentGroup.label}
          ', - '
          ', - ' ', - '
          ' - ), + saveOptions: [ + { + id: "save", + label : "Save", + isActive : true + }, + { + id: "saveAs", + label : "Save As", + isActive : false + } + ], + + initComponent: function() { + this.items = []; + if (this.showParticipantGroups) + { + this.items.push( + { + xtype: 'component', + html: (this.currentGroup.id != null ? 'Saved Group: ' : '') + this.currentGroup.label + } + ); + + this.items.push( + { + xtype: 'component', + html: '' + } + ); + + this.items.push( + { + xtype: 'component', + html: 'Load' + } + ); + + this.items.push( + { + xtype: 'component', + html: 'Save' + } + ); + } + this.items.push( + Ext4.create("Ext.button.Button", { + text: '[clear all]', + cls: 'labkey-clear-all inactive', + scope: this, + handler: function() { + this.fireEvent("clearAllFilters", false); + } + }) + ); + this.callParent(); + + } + // TODO to hook up the menus, we'll want to use a mechanism like this + //getLoadMenuTitle : function () { + // if (!this.loadMenuTitle) + // { + // this.loadMenu = Ext.create('Ext.menu.Menu', { + // // NOTE: consider replacing the action class here with a id based-method + // cls: 'labkey-dropdown', + // showSeparator: false + // }); + // + // this.groupingMenuTitle = Ext.create('Ext.Component', { + // data: {}, + // tpl: new Ext.XTemplate( + // '', + // '', + // '{text:htmlEncode}', + // '', + // '', + // '', + // '', + // '{text:htmlEncode}', + // '', + // '' + // ) + // }); + // + // this.groupingMenu.on('afterrender', function() { + // Ext.iterate(this.availableGroupings, function (group) { + // this.groupingMenu.add({value: group, text: this.groupingLookupMap[group].label}); + // }, this); + // }, this); + // + // this.groupingMenu.on('click', function(menu, item) { + // this.selectedGrouping = item.value; + // this.dirty = true; + // this._refresh(); + // }, this); + // } + // + // return this.groupingMenuTitle; + //}, + // + //showGroupingMenu : function () { + // var boxPos = this.getGroupingMenuTitle().getBox(); + // this.groupingMenu.showAt([boxPos.x, boxPos.y + boxPos.height + 6]); + //}, + // + //getGroupingMenuPanel : function () { + // if (!this.groupingMenuPanel) + // { + // this.groupingMenuPanel = Ext.create('Ext.panel.Panel', { + // minWidth : 400, + // border : false, + // layout : {type: 'hbox', align: 'stretch'}, + // items :[ + // this.getChartCategoryBox(), + // { + // xtype : 'container', + // minWidth : 200, + // cls : 'groupingmenupanel', + // items : [this.getGroupingMenuTitle()] + // }] + // }); + // } + // + // return this.groupingMenuPanel; + //}, + // + //updateGroupingMenuTitle : function() { + // var showMenu = this.availableGroupings.length > 1; + // + // this.getGroupingMenuTitle().update({ + // value: this.selectedGrouping, + // text: this.selectedGrouping ? this.groupingLookupMap[this.selectedGrouping].label : null, + // showMenu: showMenu + // }); + // + // if (showMenu) { + // this.getGroupingMenuTitle().getEl().down('.menutitle').on('click', this.showGroupingMenu, this); + // } + //} }); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/FacetPanelHeaderTpl.js b/resources/web/study/Finder/panel/FacetPanelHeaderTpl.js new file mode 100644 index 00000000..7f7dd651 --- /dev/null +++ b/resources/web/study/Finder/panel/FacetPanelHeaderTpl.js @@ -0,0 +1,104 @@ +Ext4.define("LABKEY.study.panel.FacetPanelHeaderTpl", { + extend: "Ext.Component", + + padding: "0 0 5 0", + data : { + loadedStudiesShown: true, + isGuest: false, + hasFilters: false, + showParticipantGroups:false, + currentGroup: { + id: 1, + label: "My group" + }, + saveOptions: [ + { + id: "save", + label : "Save", + isActive : true + }, + { + id: "saveAs", + label : "Save As", + isActive : true + } + ], + + groups: [{ + label: "my first group" + }] + }, + tpl: new Ext4.XTemplate( + '
           ', + '', + '
          Saved group: {currentGroup.label}
          ', + '
          ', + ' ', + '
          ' + ), + + +}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index 558079bf..5ddec3ce 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -19,22 +19,31 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { ]; this.callParent(); - this.on( - 'filterSelectionChanged', this.onFilterSelectionChange + this.on({ + filterSelectionChanged: this.onFilterSelectionChange, + clearAllFilters: this.onClearAllFilters + } ); }, + onClearAllFilters: function() { + this.facets.clearAllFilters(true); + this.onFilterSelectionChange(false); + }, + + onFilterSelectionChange: function(hasFilters) { console.log("FacetSelection filterSelectionChanged handler"); if (hasFilters) - Ext4.get(Ext4.DomQuery.select('.clear-filter', this.id)[0]).replaceCls('inactive', 'active'); + Ext4.get(Ext4.DomQuery.select('.labkey-clear-all', this.id)[0]).replaceCls('inactive', 'active'); else - Ext4.get(Ext4.DomQuery.select('.clear-filter', this.id)[0]).replaceCls('active', 'inactive') + Ext4.get(Ext4.DomQuery.select('.labkey-clear-all', this.id)[0]).replaceCls('active', 'inactive') }, getFacetPanelHeader : function() { if (!this.facetPanelHeader) { + //this.facetPanelHeader = Ext4.create("LABKEY.study.panel.FacetPanelHeaderTpl", { this.facetPanelHeader = Ext4.create("LABKEY.study.panel.FacetPanelHeader", { dataModuleName: this.dataModuleName }); diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 643fda68..cfe4eb83 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -8,7 +8,8 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { ui: 'custom', - itemSelector: 'div.facet', + //itemSelector: 'div.facet', + itemSelector: 'span.labkey-facet-member', dataModuleName: 'study', @@ -35,37 +36,25 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { { xtype: 'templatecolumn', flex: 1, - //header: false, dataIndex: 'name', sortable: false, menuDisabled: true, cls:'facet', tpl: new Ext4.XTemplate( ' ', - ' ', + ' ', ' ', - ' ', + ' ', ' ', - //'{facetName:this.displaySelection}', - ' {name}', + ' {name}', '  ', - ' {count:this.formatNumber}', + ' {count:this.formatNumber}', ' ', - ' ', + ' ', ' ', + ' ', { - formatNumber : Ext4.util.Format.numberRenderer('0,000'), - displaySelection : function(facetName) { - var facet = Ext4.getStore("facets").getById(facetName); - if (facet) - { - var selectedMembers = facet.get("selectedMembers"); - if (selectedMembers || selectedMembers.length == 0) - return ''; - else - return ''; - } - } + formatNumber : Ext4.util.Format.numberRenderer('0,000') } ) } @@ -80,7 +69,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { '
          ', '
          ', ' {name}', - ' [clear]', + ' [clear]', '
          ', '
          ', '
          ', @@ -88,64 +77,11 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { genId: function(name) { var id = Ext4.id(); LABKEY.study.panel.FacetsGrid.headerLookup[name] = id; - return id; } } ) - //groupHeaderTpl: new Ext4.XTemplate( - // '{name:this.displayFilterHeader}', - // { - // displayFilterHeader : function(name) { - // var facet = Ext4.getStore("facets").getById(name); - // var selectedMembers = facet.get("selectedMembers"); - // var filterOptions = facet.get("filterOptions"); - // var header = - // '
          ' + - // '
          ' + - // ' ' + name + ''; - // if (selectedMembers && selectedMembers.length > 0) - // header += - // ' [clear]'; - // header += - // '
          '; - // //if (selectedMembers && selectedMembers.length > 0 && filterOptions.length > 0) - // if (filterOptions.length > 0) - // { - // header += - // '
          '; - // if (filterOptions.length < 2) - // { - // header += - // ' '; - // } - // else - // { - // header += - // ' '; - // } - // header += - // ' ' + facet.get("currentFilterCaption"); - // if (filterOptions.length > 1) - // header += - // ' '; - // header += - // ' ' + - // '
          '; - // } - // header += - // '
          '; - // - // return header; - // }, - // genId: function(name) { - // var id = Ext4.id(); - // LABKEY.study.panel.FacetsGrid.headerLookup[name] = id; - // - // return id; - // } - // } - // ) + }, statics: { @@ -166,8 +102,8 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { this.mon(this.view, { groupclick: this.onGroupClick, - //groupcollapse: this.onGroupToggle, - groupexpand: this.onGroupToggle, + groupcollapse: this.onGroupCollapse, + groupexpand: this.onGroupExpand, scope: this }); @@ -179,20 +115,24 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { if (records.length > 0) this.facetStore.selectMembers(records); for (var f = 0; f < this.facetStore.count(); f++) { - this.updateFacetHeader(this.facetStore.getAt(f).data.name); + this.updateFacetHeader(this.facetStore.getAt(f).data.name, true); } this.store.updateCountsAsync(); this.fireEvent("filterSelectionChanged", this.hasFilters()); }, - onGroupToggle: function(view, node, facetName, eOpts) { - this.updateFacetHeader(facetName); + onGroupCollapse: function(view, node, facetName, eOpts) { + this.updateFacetHeader(facetName, false); + }, + + onGroupExpand: function(view, node, facetName, eOpts) { + this.updateFacetHeader(facetName, true); }, onGroupClick : function(view, node, facetName, e, eOpts ) { console.log("Group " + facetName + " clicked"); - if (e.target.className.includes("clear-filter")) { + if (e.target.className.includes("labkey-clear-filter")) { console.log("Clearing filter for group " + facetName); this.clearFilter(facetName); return false; @@ -205,23 +145,23 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { } }, - updateFacetHeader: function(facetName) { + updateFacetHeader: function(facetName, isExpanded) { if (this.hasFilters(facetName)) { - Ext4.get(Ext4.DomQuery.select('.clear-filter', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).replaceCls('inactive', 'active'); + Ext4.get(Ext4.DomQuery.select('.labkey-clear-filter', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).replaceCls('inactive', 'active'); } else { - Ext4.get(Ext4.DomQuery.select('.clear-filter', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).replaceCls('active', 'inactive'); + Ext4.get(Ext4.DomQuery.select('.labkey-clear-filter', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).replaceCls('active', 'inactive'); } - this.updateCurrentFacetOption(facetName); + this.updateCurrentFacetOption(facetName, isExpanded); }, - updateCurrentFacetOption: function(facetName) { + updateCurrentFacetOption: function(facetName, isExpanded) { var facet = this.facetStore.getById(facetName); var selectedMembers = facet.get("selectedMembers"); var html = ""; - if (selectedMembers && selectedMembers.length > 1) + if (isExpanded && selectedMembers && selectedMembers.length > 1) { html += '' + facet.get("currentFilterCaption") + ''; if (facet.get("filterOptions").length > 1) @@ -245,7 +185,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { var filterOptionsMenu = Ext4.create('Ext.menu.Menu', { //cls: 'basemenu dropdownmenu', - showSeparator: false, + showSeparator: false }); filterOptionsMenu.on('click', function(menu, item) { @@ -269,7 +209,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { }, clearAllFilters : function (updateCounts) { - for (var i = 0; i < this.getFacetStore().count(); i++) + for (var i = 0; i < this.facetStore.count(); i++) { var name = this.facetStore.getAt(i).get("name"); if (name == "Study") diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index 5060bc2b..1e151a76 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -30,6 +30,8 @@ Ext4.define('LABKEY.study.panel.Finder', { this.callParent(); + this.getCubeDefinition(); + this._initResize(); this.on( @@ -39,6 +41,16 @@ Ext4.define('LABKEY.study.panel.Finder', { ); }, + getCubeDefinition: function() { + //this.cube = LABKEY.query.olap.CubeManager.getCube({ + // configId: 'TrialShare:/StudyCube', + // schemaName: 'TrialShare', + // name: 'StudyCube', + // deferLoad: false + //}); + }, + + //onStudySubsetChanged : function(value) { // this.getStudiesPanel().getStudyCards().store.filter('availability', value); //}, diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index ba325284..2163afd4 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -43,7 +43,7 @@ Ext4.define("LABKEY.study.panel.StudyCards", { '
          ', '
          ', ' view summary', - ' ', + ' ', ' go to study', ' ', '
          ', diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 6fac913b..987e74eb 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -167,6 +167,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setFacetUniqueName("TherapeuticArea"); member.setFilterOptions(getFacetFilters()); member.setCount(4); + member.setPercent(50); members.add(member); member = new StudyFacetMember(); @@ -176,6 +177,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setFacetUniqueName("TherapeuticArea"); member.setFilterOptions(getFacetFilters()); member.setCount(3); + member.setPercent(42); members.add(member); member = new StudyFacetMember(); @@ -185,6 +187,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setFacetUniqueName("TherapeuticArea"); member.setFilterOptions(getFacetFilters()); member.setCount(1); + member.setPercent(8); members.add(member); member = new StudyFacetMember(); @@ -194,6 +197,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setFacetUniqueName("TherapeuticArea"); member.setFilterOptions(getFacetFilters()); member.setCount(0); + member.setPercent(0); members.add(member); member = new StudyFacetMember(); @@ -203,6 +207,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setFacetUniqueName("StudyType"); member.setFilterOptions(getFacetFilters()); member.setCount(5); + member.setPercent(50); members.add(member); member = new StudyFacetMember(); @@ -212,6 +217,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setFacetUniqueName("StudyType"); member.setFilterOptions(getFacetFilters()); member.setCount(2); + member.setPercent(20); members.add(member); member = new StudyFacetMember(); @@ -221,6 +227,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setFacetUniqueName("StudyType"); member.setFilterOptions(getFacetFilters()); member.setCount(3); + member.setPercent(30); members.add(member); member = new StudyFacetMember(); @@ -230,6 +237,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setFacetUniqueName("AgeGroup"); member.setFilterOptions(getFacetFilters()); member.setCount(4); + member.setPercent(50); members.add(member); member = new StudyFacetMember(); @@ -239,6 +247,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setFacetUniqueName("AgeGroup"); member.setFilterOptions(getFacetFilters()); member.setCount(1); + member.setPercent(13); members.add(member); member = new StudyFacetMember(); @@ -248,6 +257,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setFacetUniqueName("AgeGroup"); member.setFilterOptions(getFacetFilters()); member.setCount(3); + member.setPercent(37); members.add(member); return success(members); diff --git a/src/org/labkey/trialshare/data/StudyFacetMember.java b/src/org/labkey/trialshare/data/StudyFacetMember.java index 7e3cbeaf..fe5b917a 100644 --- a/src/org/labkey/trialshare/data/StudyFacetMember.java +++ b/src/org/labkey/trialshare/data/StudyFacetMember.java @@ -11,7 +11,7 @@ public class StudyFacetMember private String name; private String uniqueName; private Integer count; - private Float percent; + private Integer percent; private String facetName; private String facetUniqueName; private List filterOptions; @@ -36,12 +36,12 @@ public void setName(String name) this.name = name; } - public Float getPercent() + public Integer getPercent() { return percent; } - public void setPercent(Float percent) + public void setPercent(Integer percent) { this.percent = percent; } diff --git a/src/org/labkey/trialshare/view/dataFinder.jsp b/src/org/labkey/trialshare/view/dataFinder.jsp index 52120538..da8e1e26 100644 --- a/src/org/labkey/trialshare/view/dataFinder.jsp +++ b/src/org/labkey/trialshare/view/dataFinder.jsp @@ -35,9 +35,9 @@ resources.add(ClientDependency.fromPath("study/Finder/data/Facets.js")); resources.add(ClientDependency.fromPath("study/Finder/data/FacetMembers.js")); +// resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeaderTpl.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeader.js")); -// resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeader2.js")); - resources.add(ClientDependency.fromPath("study/Finder/panel/Facets.js")); +// resources.add(ClientDependency.fromPath("study/Finder/panel/Facets.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/FacetsGrid.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/SelectionSummary.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/FacetSelection.js")); diff --git a/src/org/labkey/trialshare/view/studyDetail.jsp b/src/org/labkey/trialshare/view/studyDetail.jsp index 1e5fb4e6..50b72fca 100644 --- a/src/org/labkey/trialshare/view/studyDetail.jsp +++ b/src/org/labkey/trialshare/view/studyDetail.jsp @@ -43,7 +43,6 @@ LinkedHashSet resources = new LinkedHashSet<>(); resources.add(ClientDependency.fromPath("dataFinder.css")); - resources.add(ClientDependency.fromPath("immport/hipc.css")); return resources; } From 549208bd2978f2ef0d78c3ff26a7ba80e36da5f5 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 17 Dec 2015 08:49:44 -0800 Subject: [PATCH 030/587] Spec 24959 : Implement Data Finder for ITN - css cleanup --- resources/web/study/Finder/dataFinder.css | 94 ++++++++++--------- .../web/study/Finder/panel/FacetsGrid.js | 19 ++-- .../study/Finder/panel/StudyPanelHeader.js | 4 +- 3 files changed, 62 insertions(+), 55 deletions(-) diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 98028a99..5450e6ef 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -77,16 +77,17 @@ SPAN.loaded background-color:rgba(81, 158, 218, 0.2); } -TD.study-panel -{ - vertical-align: top; -} +/*TD.labkey-study-panel,*/ +/*TD.study-panel*/ +/*{*/ + /*vertical-align: top;*/ +/*}*/ -TD.study-panel > DIV -{ - height:100%; - overflow-y:scroll; -} +/*TD.study-panel > DIV*/ +/*{*/ + /*height:100%;*/ + /*overflow-y:scroll;*/ +/*}*/ TABLE.labkey-study-search { @@ -99,23 +100,26 @@ SPAN.labkey-study-search } /* search area */ +TD.labkey-search-box, TD.search-box, DIV.search-box { float:left; padding:10pt; } +INPUT.labkey-search-box, INPUT.search-box { width:200pt; } -DIV.studyfinder-header -{ - float:left; - padding-right:10pt; -} +/*DIV.studyfinder-header*/ +/*{*/ + /*float:left;*/ + /*padding-right:10pt;*/ +/*}*/ +DIV.labkey-search-message, DIV.search-message { padding-left:10pt; @@ -135,12 +139,12 @@ DIV.search-message overflow-y:auto; } -DIV.help-links -{ - float:right; - vertical-align: text-top; - text-align: right; -} +/*DIV.help-links*/ +/*{*/ + /*float:right;*/ + /*vertical-align: text-top;*/ + /*text-align: right;*/ +/*}*/ DIV.summary LI.member { @@ -206,46 +210,50 @@ DIV.facet { cursor:pointer; } + +.labkey-facet-header, DIV.facet-header { padding:4pt; background-color: rgb(240, 240, 240); } +.labkey-facet-header .labkey-facet-caption, DIV.facet-header .facet-caption { font-weight:400; } +.labkey-facet-header .labkey-filter-options, DIV.facet-header .labkey-filter-options { font-size: 11px; padding: 3px 0px 0px 20px; } -.x4-grid-row { +.labkey-study-facets .x4-grid-row { border-width: 0px; } -.x4-grid-row-selected .x4-grid-td { +.labkey-study-facets .x4-grid-row-selected .x4-grid-td { background-color: white; } -.x4-grid-cell-special .x4-grid-cell-inner { +.labkey-study-facets .x4-grid-cell-special .x4-grid-cell-inner { background-color: white; } -.x4-grid-group-hd { +.labkey-study-facets .x4-grid-group-hd { background-color:rgb(240, 240, 240); } -.x4-grid-cell-special { +.labkey-study-facets .x4-grid-cell-special { border-right-width: 0px; } -.x4-grid-group-hd .x4-grid-group-title { +.labkey-study-facets .x4-grid-group-hd .x4-grid-group-title { background-position: left 28% } @@ -256,25 +264,21 @@ DIV.facet-header .labkey-filter-options color: #009; } -a.labkey-clear-filter.inactive -{ - cursor:default; - color:grey; -} +/*a.labkey-clear-filter.inactive*/ +/*{*/ + /*cursor:default;*/ + /*color:grey;*/ +/*}*/ .facet-selection-header .inactive, +.labkey-facet-header .inactive, .facet-header .inactive { cursor:default; color:grey; } -/*.facet-header .inactive*/ -/*{*/ - /*display:none;*/ - /*cursor:default;*/ -/*}*/ - +.labkey-facet-header .active, .facet-header .active { display:inline-block; @@ -342,11 +346,13 @@ DIV.facet.collapsed LI.member.selected-member display:block; } -SPAN.member:hover SPAN.bar, +SPAN.labkey-facet-member:hover SPAN.labkey-facet-percent-bar, DIV.facet LI.member:hover SPAN.bar { background-color:rgba(81, 158, 218, 0.2); } + +.labkey-empty-member, DIV.facet LI.empty-member { color:#888888; @@ -378,6 +384,9 @@ LI.member .member-indicator { content:"\002713"; color:lightgray; } + +SPAN.member .labkey-facet-member-name, +SPAN.labkey-facet-member .labkey-facet-member-name, LI.member .member-name { display:inline-block; @@ -389,7 +398,8 @@ LI.member .member-name z-index:2; } -SPAN.member .member-count, +SPAN.member .labkey-facet-member-count, +SPAN.labkey-facet-member .labkey-facet-member-count, LI.member .member-count { position:relative; @@ -397,8 +407,9 @@ LI.member .member-count z-index:2; } -.x4-grid-row-selected SPAN.bar, +.x4-grid-row-selected SPAN.labkey-facet-percent-bar, SPAN.member SPAN.bar-selected, +SPAN.labkey-facet-member SPAN.bar-selected, LI.member SPAN.bar-selected { background-color:rgba(81, 158, 218, 0.2); @@ -412,7 +423,7 @@ LI.member SPAN.bar-selected border-top-style: hidden; } -.x4-grid-row SPAN.bar, +.x4-grid-row SPAN.labkey-facet-percent-bar, LI.member .bar { height:14pt; @@ -619,7 +630,6 @@ div.study-demographics h3 { vertical-align: text-top; } - #assays-content .detail div { font-size: 15px; padding: 3px; diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index cfe4eb83..84bded0d8 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -42,15 +42,15 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { cls:'facet', tpl: new Ext4.XTemplate( ' ', - ' ', + ' ', ' ', - ' ', + ' ', ' ', - ' {name}', + ' {name}', '  ', - ' {count:this.formatNumber}', + ' {count:this.formatNumber}', ' ', - ' ', + ' ', ' ', ' ', { @@ -66,9 +66,9 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { id: 'facetMemberGrouping', groupHeaderTpl: new Ext4.XTemplate( - '
          ', - '
          ', - ' {name}', + '
          ', + '
          ', + ' {name}', ' [clear]', '
          ', '
          ', @@ -203,9 +203,6 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { }); } filterOptionsMenu.showAt(event.xy); - - //if (event.stopPropagation) - // event.stopPropagation(); }, clearAllFilters : function (updateCounts) { diff --git a/resources/web/study/Finder/panel/StudyPanelHeader.js b/resources/web/study/Finder/panel/StudyPanelHeader.js index f6965828..ecd26856 100644 --- a/resources/web/study/Finder/panel/StudyPanelHeader.js +++ b/resources/web/study/Finder/panel/StudyPanelHeader.js @@ -44,11 +44,11 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { if (!this.searchBox) { this.searchBox = Ext4.create('Ext.form.field.Text', { emptyText:'Studies', - cls: 'search-box', + cls: 'labkey-search-box', fieldLabel: '', labelWidth: "10px", labelSeparator: '', - fieldCls: 'search-box', + fieldCls: 'labkey-search-box', id: 'searchTerms', listeners: { scope: this, From 38a5b6d4a5d90a679a13851d22d66a7a18bffb83 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 21 Dec 2015 08:10:28 -0800 Subject: [PATCH 031/587] Spec 24959 : Implement Data Finder for ITN - wire up study cube; add ordinal to facets for UI sorting --- resources/olap/StudyCube.xml | 53 +++ resources/web/study/Finder/data/Facet.js | 18 +- .../web/study/Finder/data/FacetFilter.js | 3 +- .../web/study/Finder/data/FacetMember.js | 15 +- .../web/study/Finder/data/FacetMembers.js | 301 +-------------- resources/web/study/Finder/data/Facets.js | 362 ++++++++++++++++-- resources/web/study/Finder/data/StudyCard.js | 3 +- resources/web/study/Finder/dataFinder.css | 30 +- .../web/study/Finder/panel/FacetSelection.js | 6 + .../web/study/Finder/panel/FacetsGrid.js | 36 +- resources/web/study/Finder/panel/Finder.js | 28 +- .../web/study/Finder/panel/StudyCards.js | 2 +- .../trialshare/TrialShareController.java | 97 +++-- .../labkey/trialshare/data/FacetFilter.java | 11 + .../trialshare/data/StudyFacetBean.java | 52 +-- 15 files changed, 568 insertions(+), 449 deletions(-) create mode 100644 resources/olap/StudyCube.xml diff --git a/resources/olap/StudyCube.xml b/resources/olap/StudyCube.xml new file mode 100644 index 00000000..ad40644c --- /dev/null +++ b/resources/olap/StudyCube.xml @@ -0,0 +1,53 @@ + + + + +
        • +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          + + + + +
          + + + + + + + + + + + + + diff --git a/resources/web/study/Finder/data/Facet.js b/resources/web/study/Finder/data/Facet.js index 59eb8977..955fa603 100644 --- a/resources/web/study/Finder/data/Facet.js +++ b/resources/web/study/Finder/data/Facet.js @@ -6,14 +6,18 @@ Ext4.define('LABKEY.study.data.Facet', { fields: [ {name: 'name'}, {name: 'pluralName'}, - {name: 'uniqueName'}, {name: 'members'}, {name: 'selectedMembers'}, - {name: 'filterOptions'}, + {name: 'memberMap'}, {name: 'currentFilterType'}, {name: 'currentFilterCaption'}, {name: 'summaryCount', type:'int', default: 0}, - {name: 'allMemerCount', type:'int', default: 0} + {name: 'allMemberCount', type:'int', default: 0}, + {name: 'hierarchy'}, + {name: 'hierarchyName'}, + {name: 'levelName'}, + {name: 'allMemberName'}, + {name: 'ordinal'} ], @@ -39,12 +43,4 @@ Ext4.define('LABKEY.study.data.Facet', { ] }); -Ext4.define('LABKEY.study.data.FacetMember', { - extend: 'Ext.data.Model', - - fields: [ - {name: 'name'}, - {name: 'count'} - ] -}); diff --git a/resources/web/study/Finder/data/FacetFilter.js b/resources/web/study/Finder/data/FacetFilter.js index c304ed00..552ecdf2 100644 --- a/resources/web/study/Finder/data/FacetFilter.js +++ b/resources/web/study/Finder/data/FacetFilter.js @@ -2,6 +2,7 @@ Ext4.define('LABKEY.study.data.FacetFilter', { extend: 'Ext.data.Model', fields :[ {name: 'type'}, - {name: 'caption'} + {name: 'caption'}, + {name: 'default'} ] }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/FacetMember.js b/resources/web/study/Finder/data/FacetMember.js index dc7acbde..c4ea4968 100644 --- a/resources/web/study/Finder/data/FacetMember.js +++ b/resources/web/study/Finder/data/FacetMember.js @@ -1,4 +1,4 @@ -Ext4.define('LABKEY.study.data.FacetMember2', { +Ext4.define('LABKEY.study.data.FacetMember', { extend: 'Ext.data.Model', idProperty : 'uniqueName', @@ -8,16 +8,9 @@ Ext4.define('LABKEY.study.data.FacetMember2', { {name: 'uniqueName'}, {name: 'count'}, {name: 'percent'}, + {name: 'facet'}, {name: 'facetName'}, - {name: 'facetUniqueName'} - ], - - associations: [ - { - type: 'hasMany', - model: 'LABKEY.study.data.FacetFilter', - name: 'filterOptions', - associationKey: 'filterOptions' - } + {name: 'level'} ] + }); diff --git a/resources/web/study/Finder/data/FacetMembers.js b/resources/web/study/Finder/data/FacetMembers.js index 20603436..934737d3 100644 --- a/resources/web/study/Finder/data/FacetMembers.js +++ b/resources/web/study/Finder/data/FacetMembers.js @@ -1,295 +1,26 @@ Ext4.define('LABKEY.study.store.FacetMembers', { extend: 'Ext.data.Store', - model: 'LABKEY.study.data.FacetMember2', + model: 'LABKEY.study.data.FacetMember', storeId: 'facetMembers', - autoLoad: true, - dataModuleName: 'trialShare', // TODO figure out how to use this config property so we don't have to hard-code 'trialshare' - proxy : { - type: "ajax", - url: LABKEY.ActionURL.buildURL('trialShare', "studyFacetMembers.api", LABKEY.containerPath), - reader: { - type: 'json', - root: 'data' - } - }, - listeners: { - 'load' : { - fn : function(store, records, options) { - console.log('FacetMember store loaded'); - var facetsStore = Ext4.getStore("facets"); - for (var i = 0; i < records.length; i++) { - if (!facetsStore.getById(records[i].data.facetName)) - { - var facet = { - name: records[i].data.facetName, - uniqueName: records[i].data.facetUniqueName, - filterOptions: records[i].filterOptionsStore ? records[i].filterOptionsStore.data.items : [], - selectedMembers : [] - }; - if (facet.filterOptions.length) { - facet.currentFilterCaption = facet.filterOptions[0].get("caption"); - facet.currentFilterType = facet.filterOptions[0].get("type"); - } - facetsStore.add(facet); - } - } - }, - scope: this - } - }, - groupField : 'facetName', - - updateCountsAsync: function (isSavedGroup) - { - var facetStore = Ext4.getStore("facets"); - //var intersectFilters = []; - //var d, i, dim; - //for (d in dataspace.dimensions) - //{ - // if (!dataspace.dimensions.hasOwnProperty(d)) - // continue; - // dim = dataspace.dimensions[d]; - // var filterMembers = dim.filters; - // if (d == 'Study') - // { - // if (!filterMembers || filterMembers.length == dim.members.length) - // continue; - // if (filterMembers.length == 0) - // { - // // in the case of study filter, this means no matches, rather than no filter! - // this.updateCountsZero(); - // return; - // } - // var uniqueNames = filterMembers.map(function(m){return m.uniqueName;}); - // if (this.filterByLevel != "[Study].[Name]") - // intersectFilters.push({ - // level: this.filterByLevel, - // membersQuery: {level: "[Study].[Name]", members: uniqueNames} - // }); - // else - // intersectFilters.push({level: "[Study].[Name]", members: uniqueNames}); - // } - // else - // { - // if (!filterMembers || filterMembers.length == 0) - // continue; - // if (dim.filterType === "OR") - // { - // var names = []; - // filterMembers.forEach(function (m) - // { - // names.push(m.uniqueName) - // }); - // intersectFilters.push({ - // level: this.filterByLevel, - // membersQuery: {level: filterMembers[0].level, members: names} - // }); - // } - // else - // { - // for (i = 0; i < filterMembers.length; i++) - // { - // var filterMember = filterMembers[i]; - // intersectFilters.push({ - // level: this.filterByLevel, - // membersQuery: {level: filterMember.level, members: [filterMember.uniqueName]} - // }); - // } - // } - // } - //} - // - //var filters = intersectFilters; - //if (intersectFilters.length && this.filterByLevel != "[Subject].[Subject]") - //{ - // filters = [{ - // level: "[Subject].[Subject]", - // membersQuery: {operator: "INTERSECT", arguments: intersectFilters} - // }] - //} - // - //// CONSIDER: Don't fetch subject IDs every time a filter is changed. - //var includeSubjectIds = true; - // - //var onRows = { operator: "UNION", arguments: [] }; - //for (d in dataspace.dimensions) - //{ - // if (!dataspace.dimensions.hasOwnProperty(d)) - // continue; - // dim = dataspace.dimensions[d]; - // if (dim.name == "Subject") - // onRows.arguments.push({level: dim.hierarchy.levels[0].uniqueName}); - // else if (dim.name == "Study" && this.filterByLevel == "[Study].[Name]") - // continue; - // else - // onRows.arguments.push({level: dim.level.uniqueName}); - //} - // - //if (includeSubjectIds) - // onRows.arguments.push({level: "[Subject].[Subject]", members: "members"}); - // - //var config = - //{ - // "sql": true, - // configId: 'ImmPort:/StudyCube', - // schemaName: 'ImmPort', - // name: 'StudyCube', - // success: function (cellSet, mdx, config) - // { - // // use angular timeout() for its implicit $scope.$apply() - // // config.scope.timeout(function(){config.scope.updateCounts(config.dim, cellSet);},1); - // config.scope.timeout(function () - // { - // config.scope.updateCountsUnion(cellSet, isSavedGroup); - // $scope.$broadcast("cubeReady"); - // }, 1); - // }, - // scope: this, - // - // // query - // onRows: onRows, - // countFilter: filters, - // countDistinctLevel: '[Subject].[Subject]' - //}; - //this.mdx.query(config); - }, - - updateCountsZero : function () - { - for (d in dataspace.dimensions) - { - if (!dataspace.dimensions.hasOwnProperty(d)) - continue; - var dim = dataspace.dimensions[d]; - dim.summaryCount = 0; - for (var m = 0; m < dim.members.length; m++) - { - dim.members[m].count = 0; - dim.members[m].percent = 0; - } - dim.summaryCount = 0; - } - - this.saveFilterState(); - this.updateContainerFilter(); - this.changeSubjectGroup(); - this.doneRendering(); - }, - - /* handle query response to update all the member counts with all filters applied */ - updateCountsUnion : function (cellSet, isSavedGroup) - { - var dim, member, d, m; - // map from hierarchyName to dataspace dimension - var map = {}; - - // clear old subjects and counts (to be safe) - //this.subjects.length = 0; - for (d = 0; d < this.count(); d++) - { - dim = this.getAt(d); - map[dim.data.hierarchy.uniqueName] = dim; - dim.data.summaryCount = 0; - dim.data.allMemberCount = 0; - } - for (d = 0; d < this.count(); d++) { - this.getAt(d).data.count = 0; - this.getAt(d).data.percent = 0; - } - - var positions = this.getRowPositionsOneLevel(cellSet); - var data = this.getDataOneColumn(cellSet, 0); - var max = 0; - for (var i = 0; i < positions.length; i++) - { - var resultMember = positions[i]; - if (resultMember.level.uniqueName == "[Subject].[Subject]") - { - this.subjects.push(resultMember.name); - } - else - { - var hierarchyName = resultMember.level.hierarchy.uniqueName; - dim = map[hierarchyName]; - var count = data[i]; - member = dim.memberMap[resultMember.uniqueName]; - if (!member) - { - // might be an all member - if (dim.allMemberName == resultMember.uniqueName) - dim.allMemberCount = count; - else if (-1 == resultMember.uniqueName.indexOf("#") && "(All)" != resultMember.name) - console.log("member not found: " + resultMember.uniqueName); - } + autoLoad: false, + + groupers: [ + { + property: 'facetName', + sorterFn: function(o1, o2){ + rank1 = o1.get('facet').get("ordinal"); + rank2 = o2.get('facet').get("ordinal"); + if (rank1 === rank2) + if (o1.get("name") === o2.get("name")) + return 0; + else + return o1.get("name") < o2.get("name") ? -1 : 1; else - { - member.count = count; - if (count) - dim.summaryCount += 1; - if (count > max) - max = count; - } - } - } + return rank1 < rank2 ? -1 : 1; - for (d in dataspace.dimensions) - { - dim = dataspace.dimensions[d]; - map[dim.hierarchy.uniqueName] = dim; - for (m = 0; m < dim.members.length; m++) - { - member = dim.members[m]; - member.percent = max == 0 ? 0 : (100.0 * member.count) / max; } } - - //this.saveFilterState(); - //this.updateContainerFilter(); - //if (!isSavedGroup) - // this.changeSubjectGroup(); - //this.doneRendering(); - }, - - getRowPositions : function(cellSet) - { - return cellSet.axes[1].positions; - }, - - getRowPositionsOneLevel : function(cellSet) - { - var positions = cellSet.axes[1].positions; - if (positions.length > 0 && positions[0].length > 1) - { - console.log("warning: rows have nested members"); - throw "illegal state"; - } - return positions.map(function(inner){return inner[0]}); - }, - - getData : function(cellSet,defaultValue) - { - var cells = cellSet.cells; - var ret = cells.map(function(row) - { - return row.map(function(col){return col.value ? col.value : defaultValue;}); - }); - return ret; - }, - - getDataOneColumn : function(cellSet,defaultValue) - { - var cells = cellSet.cells; - if (cells.length > 0 && cells[0].length > 1) - { - console.log("warning cellSet has more than one column"); - throw "illegal state"; - } - var ret = cells.map(function(row) - { - return row[0].value ? row[0].value : defaultValue; - }); - return ret; - } + ] }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index 7f1182d8..446a4b89 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -2,33 +2,44 @@ Ext4.define('LABKEY.study.store.Facets', { extend: 'Ext.data.Store', model: 'LABKEY.study.data.Facet', storeId: 'facets', - autoLoad: false, + autoLoad: true, dataModuleName: 'study', - //proxy : { - // type: "ajax", - // url: LABKEY.ActionURL.buildURL("trialshare", "studyFacets.api", LABKEY.containerPath), - // reader: { - // type: 'json', - // root: 'data' - // } - //}, - //listeners: { - // 'load' : { - // fn : function(store, records, options) { - // console.log('Facet store loaded'); - // store.computePercents(); - // }, - // scope: this - // } - //}, - - computePercents: function() { - console.log("Computing percents now"); + filterByLevel : '[Study].[Study]', + countDistinctLevel : '[Study].[Study]', + filterByFacetUniqueName : '[Study]', + olapConfig : { + configId: 'TrialShare:/StudyCube', + schemaName: 'lists', + name: 'StudyCube' + }, + mdx: null, + isLoaded: false, + + proxy : { + type: "ajax", + url: LABKEY.ActionURL.buildURL("trialshare", "studyFacets.api", LABKEY.containerPath), + reader: { + type: 'json', + root: 'data' + } + }, + listeners: { + 'load' : { + fn : function(store, records, options) { + store.isLoaded = true; + store.loadFromCube(); + }, + scope: this + } }, clearAllSelectedMembers: function() { for (var f = 0; f < this.count(); f++) { - this.getAt(f).data.selectedMembers = []; + var facet = this.getAt(f); + if (facet.get("name") != "Study") + { + this.getAt(f).data.selectedMembers = []; + } } }, @@ -37,5 +48,312 @@ Ext4.define('LABKEY.study.store.Facets', { { this.getById(members[i].data.facetName).data.selectedMembers.push(members[i]); } + }, + + loadFromCube : function() { + if (this.isLoaded && this.mdx) + { + var cube = this.mdx._cube; + console.log("cube is ready now!"); + var facetMembersStore = Ext4.getStore("facetMembers"); + for (var f = 0; f < this.count(); f++) + { + var facet = this.getAt(f); + facet.data.hierarchy = cube.hierarchyMap[facet.get("hierarchyName")]; + facet.data.level = facet.data.hierarchy.levelMap[facet.get("levelName")]; + facet.data.members = []; + var defaultFilter = this.getDefaultFilterOption(facet.filterOptionsStore); + facet.data.currentFilterType = defaultFilter.get("type"); + facet.data.currentFilterCaption = defaultFilter.get("caption"); + for (var m = 0; m < facet.data.level.members.length; m++) + { + var src = facet.data.level.members[m]; + if (src.name == "#notnull") + continue; + var member = { + name: src.name, + uniqueName: src.uniqueName, + level: src.level.uniqueName, + count: 0, + percent: 0, + //filteredCount: -1, + //selectedCount: -1, + facetName: facet.get("name"), + facet : facet + }; + if (facet.get("name") != "Study") + facetMembersStore.add(member); + facet.data.members.push(member); + facet.data.memberMap[member.uniqueName] = member; + } + if (facet.get("name") == "Study") { + facet.data.selectedMembers = facet.data.members; + } + } + facetMembersStore.sort(); + this.updateCountsAsync(false); + } + }, + + getDefaultFilterOption : function(optionsStore) { + for (var i = 0; i < optionsStore.count(); i++) { + if (optionsStore.getAt(i).data.default) + return optionsStore.getAt(i); + } + return optionsStore.getAt(0); + }, + + updateCountsAsync: function (isSavedGroup) + { + var facetStore = this; + var intersectFilters = []; + var i, f, facet; + for (f = 0; f < facetStore.count(); f++) + { + facet = facetStore.getAt(f); + var filterMembers = facet.get("selectedMembers"); + if (facet.get("name") == 'Study') + { + if (!filterMembers || filterMembers.length == facet.data.members.length) + continue; + if (filterMembers.length == 0) + { + // in the case of study filter, this means no matches, rather than no filter! + this.updateCountsZero(); + return; + } + var uniqueNames = filterMembers.map(function(m){return m.data.uniqueName;}); + if (this.filterByLevel != "[Study].[Study]") + intersectFilters.push({ + level: this.filterByLevel, + membersQuery: {level: "[Study].[Study]", members: uniqueNames} + }); + else + intersectFilters.push({level: "[Study].[Study]", members: uniqueNames}); + } + else + { + if (!filterMembers || filterMembers.length == 0) + continue; + if (facet.get("currentFilterType") === "OR") + { + var names = []; + filterMembers.forEach(function (m) + { + names.push(m.data.uniqueName) + }); + intersectFilters.push({ + level: this.filterByLevel, + membersQuery: {level: filterMembers[0].data.level, members: names} + }); + } + else + { + for (i = 0; i < filterMembers.length; i++) + { + var filterMember = filterMembers[i]; + intersectFilters.push({ + level: this.filterByLevel, + membersQuery: {level: filterMember.data.level, members: [filterMember.data.uniqueName]} + }); + } + } + } + } + + var filters = intersectFilters; + //if (intersectFilters.length && this.filterByLevel != "[Subject].[Subject]") + //{ + // filters = [{ + // level: "[Subject].[Subject]", + // membersQuery: {operator: "INTERSECT", arguments: intersectFilters} + // }] + //} + // + // CONSIDER: Don't fetch subject IDs every time a filter is changed. + var includeSubjectIds = false; + + var onRows = { operator: "UNION", arguments: [] }; + for (f = 0; f < facetStore.count(); f++) + { + facet = facetStore.getAt(f); + if (facet.get("name") == "Subject") + onRows.arguments.push({level: facet.data.hierarchy.levels[0].uniqueName}); + else if (facet.get("name") == "Study" && this.filterByLevel == "[Study].[Study]") + continue; + else + onRows.arguments.push({level: facet.data.level.uniqueName}); + } + + if (includeSubjectIds) + onRows.arguments.push({level: "[Subject].[Subject]", members: "members"}); + + var config = + { + "sql": true, + configId: this.olapConfig.configId, + schemaName: this.olapConfig.schemaName, + name: this.olapConfig.name, + success: function (cellSet, mdx, config) + { + this.updateCountsUnion(cellSet, isSavedGroup); + //this.fireEvent("cubeReady"); + }, + scope: this, + + // query + onRows: onRows, + countFilter: filters, + countDistinctLevel: this.countDistinctLevel + }; + this.mdx.query(config); + }, + + updateCountsZero : function () + { + var facetStore = Ext4.getStore("facets"); + var facetMembersStore = Ext4.getStore("facetMembers"); + var f, member, facet; + for (f = 0; f < facetStore.count(); f++) + { + facet = facetStore.getAt(f); + facet.data.summaryCount = 0; + facet.data.allMemberCount = 0; + for (var m = 0; m < facetMembersStore.count(); m++) + { + member = facetMembersStore.getAt(m); + member.data.count = 0; + member.data.percent = 0; + } + } + + //this.saveFilterState(); + //this.updateContainerFilter(); + //this.changeSubjectGroup(); + //this.doneRendering(); + }, + + /* handle query response to update all the member counts with all filters applied */ + updateCountsUnion : function (cellSet, isSavedGroup) + { + var facet, member, f, m; + // map from hierarchyName to dataspace dimension + var map = {}; + var facetStore = this; + var facetMembersStore = Ext4.getStore("facetMembers"); + + // clear old subjects and counts (to be safe) + //this.subjects.length = 0; + for (f = 0; f < facetStore.count(); f++) + { + facet = this.getAt(f); + map[facet.data.hierarchy.uniqueName] = facet; + facet.data.summaryCount = 0; + facet.data.allMemberCount = 0; + } + for (m = 0; m < facetMembersStore.count(); m++) { + facetMembersStore.getAt(m).data.count = 0; + facetMembersStore.getAt(m).data.percent = 0; + } + + var positions = this.getRowPositionsOneLevel(cellSet); + var data = this.getDataOneColumn(cellSet, 0); + var max = 0; + for (var i = 0; i < positions.length; i++) + { + var resultMember = positions[i]; + //if (resultMember.data.level.uniqueName == "[Subject].[Subject]") + //{ + // this.subjects.push(resultMember.data.name); + //} + //else + { + var hierarchyName = resultMember.level.hierarchy.uniqueName; + facet = map[hierarchyName]; // todo can't this come from the store if the key is uniqueName? + var count = data[i]; + member = facetMembersStore.getById(resultMember.uniqueName); + if (!member) + { + // might be an all member + if (facet.data.allMemberName == resultMember.uniqueName) + facet.data.allMemberCount = count; + else if (-1 == resultMember.uniqueName.indexOf("#") && "(All)" != resultMember.name) + console.log("member not found: " + resultMember.uniqueName); + } + else + { + member.set("count", count); + //member.data.count = count; + if (count) + facet.data.summaryCount += 1; + if (count > max) + max = count; + } + } + } + + for (f = 0; f < facetStore.count(); f++) + { + facet = facetStore.getAt(f); + map[facet.data.hierarchy.uniqueName] = facet; + if (facet.data.hierarchy.uniqueName !== this.filterByFacetUniqueName) + { + for (m = 0; m < facet.data.members.length; m++) + { + member = facetMembersStore.getById(facet.data.members[m].uniqueName); + member.set("percent", max == 0 ? 0 : (100.0 * member.data.count) / max); + //member.data.percent = max == 0 ? 0 : (100.0 * member.data.count) / max; + } + } + } + + //this.fireEvent("countUpdate"); + //this.saveFilterState(); + //this.updateContainerFilter(); + //if (!isSavedGroup) + // this.changeSubjectGroup(); + //this.doneRendering(); + }, + + getRowPositions : function(cellSet) + { + return cellSet.axes[1].positions; + }, + + getRowPositionsOneLevel : function(cellSet) + { + var positions = cellSet.axes[1].positions; + if (positions.length > 0 && positions[0].length > 1) + { + console.log("warning: rows have nested members"); + throw "illegal state"; + } + return positions.map(function(inner){return inner[0]}); + }, + + getData : function(cellSet,defaultValue) + { + var cells = cellSet.cells; + var ret = cells.map(function(row) + { + return row.map(function(col){return col.value ? col.value : defaultValue;}); + }); + return ret; + }, + + getDataOneColumn : function(cellSet,defaultValue) + { + var cells = cellSet.cells; + if (cells.length > 0 && cells[0].length > 1) + { + console.log("warning cellSet has more than one column"); + throw "illegal state"; + } + var ret = cells.map(function(row) + { + return row[0].value ? row[0].value : defaultValue; + }); + return ret; } + }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/StudyCard.js b/resources/web/study/Finder/data/StudyCard.js index b908afd1..6595dc65 100644 --- a/resources/web/study/Finder/data/StudyCard.js +++ b/resources/web/study/Finder/data/StudyCard.js @@ -7,9 +7,10 @@ Ext4.define('LABKEY.study.data.StudyCard', { {name: 'studyId'}, {name: 'title'}, {name: 'url'}, + {name: 'brand'}, // TODO bring this from the Java side {name: 'investigator'}, {name: 'hasManuscript', type: 'boolean'}, - {name: 'isLoaded', type: 'boolean'}, + {name: 'isLoaded', type: 'boolean'}, // TODO isHighlighted {name: 'availability'} ] }); \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 5450e6ef..3b69206d 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -22,6 +22,10 @@ DIV.labkey-study-facets { width:100%; } +.labkey-data-finder-outer .x4-border-layout-ct +{ + background-color:#ffffff; +} TABLE.labkey-data-finder { height: 450px; @@ -257,19 +261,6 @@ DIV.facet-header .labkey-filter-options background-position: left 28% } -.labkey-study-facets .x-grid-cell { - background-color: #ffa; - border-bottom-color: #ffc; - border-top-color: #ff5; - color: #009; -} - -/*a.labkey-clear-filter.inactive*/ -/*{*/ - /*cursor:default;*/ - /*color:grey;*/ -/*}*/ - .facet-selection-header .inactive, .labkey-facet-header .inactive, .facet-header .inactive @@ -392,7 +383,7 @@ LI.member .member-name display:inline-block; position:relative; max-width:150pt; - overflow-x: hidden; + /*overflow-x: hidden;*/ text-overflow: ellipsis; white-space: nowrap; z-index:2; @@ -415,9 +406,14 @@ LI.member SPAN.bar-selected background-color:rgba(81, 158, 218, 0.2); } -.x4-grid-row-focused .x4-grid-td, -.x4-grid-row-before-focused .x4-grid-td, -.x4-grid-cell +.labkey-study-facets .x4-grid-view +{ + overflow: hidden; +} + +.labkey-study-facets .x4-grid-row-focused .x4-grid-td, +.labkey-study-facets .x4-grid-row-before-focused .x4-grid-td, +.labkey-study-facets .x4-grid-cell { border-bottom-style: hidden; border-top-style: hidden; diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index 5ddec3ce..52023060 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -11,6 +11,8 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { padding: "10 8 8 10", + autoScroll: true, + initComponent : function() { this.items = [ this.getFacetPanelHeader(), @@ -26,6 +28,10 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { ); }, + onCubeReady: function(mdx) { + this.getFacets().onCubeReady(mdx); + }, + onClearAllFilters: function() { this.facets.clearAllFilters(true); this.onFilterSelectionChange(false); diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 84bded0d8..68b630d4 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -13,7 +13,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { dataModuleName: 'study', - autoScroll: true, + autoScroll: false, bubbleEvents : ["filterSelectionChanged"], @@ -46,8 +46,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { ' ', ' ', ' ', - ' {name}', - '  ', + ' {name} ', ' {count:this.formatNumber}', ' ', ' ', @@ -85,7 +84,8 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { }, statics: { - headerLookup: {} + headerLookup: {}, + groupExpansionLookup: {} // TODO }, initComponent: function() { @@ -94,9 +94,6 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { this.store = Ext4.create('LABKEY.study.store.FacetMembers', { dataModuleName: this.dataModuleName }); - //this.store.proxy.url = LABKEY.ActionURL.buildURL(this.dataModuleName, "studyFacetMembers.api", LABKEY.containerPath); - - this.store.load(); this.callParent(); @@ -110,6 +107,11 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { this.getSelectionModel().on('selectionchange', this.onSelectionChange, this); }, + onCubeReady : function(mdx) { + this.facetStore.mdx = mdx; + this.facetStore.loadFromCube(); + }, + onSelectionChange: function(selModel, records) { this.facetStore.clearAllSelectedMembers(); if (records.length > 0) @@ -117,7 +119,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { for (var f = 0; f < this.facetStore.count(); f++) { this.updateFacetHeader(this.facetStore.getAt(f).data.name, true); } - this.store.updateCountsAsync(); + this.facetStore.updateCountsAsync(); this.fireEvent("filterSelectionChanged", this.hasFilters()); }, @@ -164,7 +166,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { if (isExpanded && selectedMembers && selectedMembers.length > 1) { html += '' + facet.get("currentFilterCaption") + ''; - if (facet.get("filterOptions").length > 1) + if (facet.filterOptionsStore.count() > 1) html += ' '; } Ext4.get(Ext4.DomQuery.select('.labkey-filter-options', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).setHTML(html); @@ -179,8 +181,8 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { console.error("could not find facet " + facetName); return; } - var filterOptions = facet.get("filterOptions"); - if (filterOptions.length < 2) + var filterOptions = facet.filterOptionsStore; + if (filterOptions.count() < 2) return; var filterOptionsMenu = Ext4.create('Ext.menu.Menu', { @@ -191,15 +193,15 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { filterOptionsMenu.on('click', function(menu, item) { facet.data.currentFilterType = item.value; facet.data.currentFilterCaption = item.text; - this.updateCurrentFacetOption(facetName); + this.updateCurrentFacetOption(facetName, true); }, this ); - for (var i = 0; i < filterOptions.length; i++) { + for (var i = 0; i < filterOptions.count(); i++) { filterOptionsMenu.add({ - value: filterOptions[i].get("type"), - text: filterOptions[i].get("caption") + value: filterOptions.getAt(i).get("type"), + text: filterOptions.getAt(i).get("caption") }); } filterOptionsMenu.showAt(event.xy); @@ -214,7 +216,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { this.clearFilter(name); } if (updateCounts) - this.store.updateCountsAsync(); + this.facetStore.updateCountsAsync(); this.fireEvent("filterSelectionCleared", false); }, @@ -228,7 +230,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { } } - this.updateFacetHeader(facetName); + this.updateFacetHeader(facetName, true); this.fireEvent("filterSelectionCleared", this.hasFilters(facetName)); }, diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index 1e151a76..efccea7c 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -42,15 +42,31 @@ Ext4.define('LABKEY.study.panel.Finder', { }, getCubeDefinition: function() { - //this.cube = LABKEY.query.olap.CubeManager.getCube({ - // configId: 'TrialShare:/StudyCube', - // schemaName: 'TrialShare', - // name: 'StudyCube', - // deferLoad: false - //}); + var me = this; + this.cube = LABKEY.query.olap.CubeManager.getCube({ + configId: 'TrialShare:/StudyCube', + schemaName: 'lists', + name: 'StudyCube', + deferLoad: false + }); + this.cube.onReady(function (m) + { + me.mdx = m; + me.onCubeReady(); + //this.loadFilterState(); + + //this.onStudySubsetChanged(); + // doShowAllStudiesChanged() has side-effect of calling updateCountsAsync() + //$scope.updateCountsAsync(); + }); }, + onCubeReady: function() { + this.getFacetsPanel().onCubeReady(this.mdx); + //this.getStudiesPanel().onCubeReady(this.cube); + }, + //onStudySubsetChanged : function(value) { // this.getStudiesPanel().getStudyCards().store.filter('availability', value); //}, diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index 2163afd4..8fc97aec 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -33,7 +33,7 @@ Ext4.define("LABKEY.study.panel.StudyCards", { '
          ', ' ', ' ', - // TODO this should be labkey-study-card-highlight-1 instead of loaded + // TODO this should be something like labkey-study-card-highlight-1 instead of loaded '
          ', ' ', '
          ', diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 987e74eb..1f56d65b 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -165,7 +165,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setUniqueName("Transplant"); member.setFacetName("Therapeutic Area"); member.setFacetUniqueName("TherapeuticArea"); - member.setFilterOptions(getFacetFilters()); + member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); member.setCount(4); member.setPercent(50); members.add(member); @@ -175,7 +175,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setUniqueName("Autoimmune"); member.setFacetName("Therapeutic Area"); member.setFacetUniqueName("TherapeuticArea"); - member.setFilterOptions(getFacetFilters()); + member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); member.setCount(3); member.setPercent(42); members.add(member); @@ -185,7 +185,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setUniqueName("Allergy"); member.setFacetName("Therapeutic Area"); member.setFacetUniqueName("TherapeuticArea"); - member.setFilterOptions(getFacetFilters()); + member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); member.setCount(1); member.setPercent(8); members.add(member); @@ -195,7 +195,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setUniqueName("T1DM"); member.setFacetName("Therapeutic Area"); member.setFacetUniqueName("TherapeuticArea"); - member.setFilterOptions(getFacetFilters()); + member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); member.setCount(0); member.setPercent(0); members.add(member); @@ -205,7 +205,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setUniqueName("Interventional"); member.setFacetName("Study Type"); member.setFacetUniqueName("StudyType"); - member.setFilterOptions(getFacetFilters()); + member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); member.setCount(5); member.setPercent(50); members.add(member); @@ -215,7 +215,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setUniqueName("Observational"); member.setFacetName("Study Type"); member.setFacetUniqueName("StudyType"); - member.setFilterOptions(getFacetFilters()); + member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); member.setCount(2); member.setPercent(20); members.add(member); @@ -225,7 +225,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setUniqueName("Expanded Access"); member.setFacetName("Study Type"); member.setFacetUniqueName("StudyType"); - member.setFilterOptions(getFacetFilters()); + member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); member.setCount(3); member.setPercent(30); members.add(member); @@ -235,7 +235,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setUniqueName("Adult"); member.setFacetName("Age Group"); member.setFacetUniqueName("AgeGroup"); - member.setFilterOptions(getFacetFilters()); + member.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); member.setCount(4); member.setPercent(50); members.add(member); @@ -245,7 +245,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setUniqueName("Child"); member.setFacetName("Age Group"); member.setFacetUniqueName("AgeGroup"); - member.setFilterOptions(getFacetFilters()); + member.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); member.setCount(1); member.setPercent(13); members.add(member); @@ -255,7 +255,7 @@ public Object execute(Object o, BindException errors) throws Exception member.setUniqueName("Senior"); member.setFacetName("Age Group"); member.setFacetUniqueName("AgeGroup"); - member.setFilterOptions(getFacetFilters()); + member.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); member.setCount(3); member.setPercent(37); members.add(member); @@ -265,17 +265,26 @@ public Object execute(Object o, BindException errors) throws Exception } - private List getFacetFilters() + private List getFacetFilters(Boolean includeAnd, Boolean includeOr, FacetFilter.Type defaultType) { List filterOptions = new ArrayList<>(); - FacetFilter filter = new FacetFilter(); - filter.setType(FacetFilter.Type.OR); - filter.setCaption("is any of"); - filterOptions.add(filter); - filter = new FacetFilter(); - filter.setType(FacetFilter.Type.AND); - filter.setCaption("is all of"); - filterOptions.add(filter); + FacetFilter filter; + if (includeOr) + { + filter = new FacetFilter(); + filter.setType(FacetFilter.Type.OR); + filter.setCaption("is any of"); + filter.setDefault(FacetFilter.Type.OR == defaultType); + filterOptions.add(filter); + } + if (includeAnd) + { + filter = new FacetFilter(); + filter.setType(FacetFilter.Type.AND); + filter.setCaption("is all of"); + filter.setDefault(FacetFilter.Type.AND == defaultType); + filterOptions.add(filter); + } return filterOptions; } @@ -288,40 +297,26 @@ public class StudyFacetsAction extends ApiAction public Object execute(Object o, BindException errors) throws Exception { List facets = new ArrayList<>(); - StudyFacetBean facet = new StudyFacetBean(); - facet.setName("Facet1"); - facet.setCaption("Facet 1"); - - List members = new ArrayList<>(); - StudyFacetMember member = new StudyFacetMember(); - member.setName("Member 1.1"); - member.setCount(10000); - member.setUniqueName("Member11"); - members.add(member); - - member = new StudyFacetMember(); - member.setName("Member 1.2"); - members.add(member); - member.setCount(0); - member.setUniqueName("Member12"); - facet.setMembers(members); + StudyFacetBean facet; + facet = new StudyFacetBean("Therapeutic Area", "Therapeutic Areas", "Study.Therapeutic Area", "Therapeutic Area", "[Study.Therapeutic Area][(All)]", FacetFilter.Type.OR, 1); + facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); + facets.add(facet); + facet = new StudyFacetBean("Study Type", "Study Types", "Study.Study Type", "StudyType", "[Study.Study Type][(All)]", FacetFilter.Type.OR, 2); + facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); + facets.add(facet); + facet = new StudyFacetBean("Condition", "Conditions", "Study.Condition", "Condition", "[Study.Condition][(All)]", FacetFilter.Type.OR, 5); + facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); + facets.add(facet); + facet = new StudyFacetBean("Age Group", "Age Groups", "Study.AgeGroup", "AgeGroup", "[Study.AgeGroup][(All)]", FacetFilter.Type.OR, 3); + facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); + facets.add(facet); + facet = new StudyFacetBean("Phase", "Phases", "Study.Phase", "Phase", "[Study.Phase][(All)]", FacetFilter.Type.OR, 4); + facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); + facets.add(facet); + facet = new StudyFacetBean("Study", "Studies", "Study", "Study", "[Study].[(All)]", FacetFilter.Type.OR, null); + facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); facets.add(facet); - - StudyFacetBean facet2 = new StudyFacetBean(); - facet2.setName("Facet2"); - facet2.setCaption("Facet 2"); - - members = new ArrayList(); - member = new StudyFacetMember(); - member.setName("Member 2.1"); - member.setCount(12); - member.setUniqueName("Member21"); - members.add(member); - facet2.setMembers(members); - - facets.add(facet2); - return success(facets); } diff --git a/src/org/labkey/trialshare/data/FacetFilter.java b/src/org/labkey/trialshare/data/FacetFilter.java index d9f2bd10..f132fc04 100644 --- a/src/org/labkey/trialshare/data/FacetFilter.java +++ b/src/org/labkey/trialshare/data/FacetFilter.java @@ -12,6 +12,7 @@ public enum Type { OR, AND }; private Type _type; private String _caption; + private Boolean _default; public String getCaption() { @@ -32,4 +33,14 @@ public void setType(Type type) { _type = type; } + + public Boolean geDefault() + { + return _default; + } + + public void setDefault(Boolean aDefault) + { + _default = aDefault; + } } diff --git a/src/org/labkey/trialshare/data/StudyFacetBean.java b/src/org/labkey/trialshare/data/StudyFacetBean.java index be91f40e..6660faf9 100644 --- a/src/org/labkey/trialshare/data/StudyFacetBean.java +++ b/src/org/labkey/trialshare/data/StudyFacetBean.java @@ -10,34 +10,24 @@ public class StudyFacetBean { private String name; - private String caption; private String pluralName; private String hierarchyName; private String levelName; private String allMemberName; - private String defaultFilterType; - private List members; - private List filterOptions; + private FacetFilter.Type defaultFilterType; + private List filterOptions; + private Integer ordinal; - public String getCaption() - { - return caption; - } - - public void setCaption(String caption) - { - this.caption = caption; - } + public StudyFacetBean() {} - @JsonIgnore - public List getMembers() - { - return members; - } - - public void setMembers(List members) - { - this.members = members; + public StudyFacetBean(String name, String pluralName, String hierarchyName, String levelName, String allMembersName, FacetFilter.Type defaultFilterType, Integer ordinal) { + this.name = name; + this.pluralName = pluralName; + this.hierarchyName = hierarchyName; + this.levelName = levelName; + this.allMemberName = allMembersName; + this.defaultFilterType = defaultFilterType; + this.ordinal = ordinal; } public String getName() @@ -60,22 +50,22 @@ public void setAllMemberName(String allMemberName) this.allMemberName = allMemberName; } - public List getFilterOptions() + public List getFilterOptions() { return filterOptions; } - public void setFilterOptions(List filterOptions) + public void setFilterOptions(List filterOptions) { this.filterOptions = filterOptions; } - public String getDefaultFilterType() + public FacetFilter.Type getDefaultFilterType() { return defaultFilterType; } - public void setDefaultFilterType(String defaultFilterType) + public void setDefaultFilterType(FacetFilter.Type defaultFilterType) { this.defaultFilterType = defaultFilterType; } @@ -109,4 +99,14 @@ public void setPluralName(String pluralName) { this.pluralName = pluralName; } + + public Integer getOrdinal() + { + return ordinal; + } + + public void setOrdinal(Integer ordinal) + { + this.ordinal = ordinal; + } } From 9bb411a49eff8b7c500efec7b3066875e13247d8 Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Mon, 21 Dec 2015 23:00:10 +0000 Subject: [PATCH 032/587] JavaDoc, code cleanup, and Nullable/NotNull annotations SVN r41519 |2015-12-21 23:00:10 +0000 --- src/org/scharp/atlas/pepdb/PepDBBaseController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/org/scharp/atlas/pepdb/PepDBBaseController.java b/src/org/scharp/atlas/pepdb/PepDBBaseController.java index cc9d701c..0dc6c9d2 100644 --- a/src/org/scharp/atlas/pepdb/PepDBBaseController.java +++ b/src/org/scharp/atlas/pepdb/PepDBBaseController.java @@ -1,5 +1,6 @@ package org.scharp.atlas.pepdb; +import org.jetbrains.annotations.NotNull; import org.labkey.api.action.SpringActionController; import org.labkey.api.data.*; import org.labkey.api.util.DateUtil; @@ -454,6 +455,7 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep } } + @NotNull public String getFormattedValue(RenderContext ctx) { StringBuilder sb = new StringBuilder("P"); sb.append(super.getFormattedValue(ctx)); @@ -525,6 +527,7 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep } } + @NotNull public String getFormattedValue(RenderContext ctx) { StringBuilder sb = new StringBuilder("PP"); sb.append(super.getFormattedValue(ctx)); From b3e0147fe4965eff737349758dcde6c282b4baf0 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 22 Dec 2015 20:05:08 -0800 Subject: [PATCH 033/587] Spec 24959 : Implement Data Finder for ITN - update study card display; adjust study cards and summary on facet selection; update styling for facet display; add study publications list; add publications to study details --- resources/olap/StudyCube.xml | 12 +- resources/web/study/Finder/data/Facet.js | 3 +- resources/web/study/Finder/data/Facets.js | 73 ++++--- resources/web/study/Finder/data/Studies.js | 39 ++++ resources/web/study/Finder/data/StudyCard.js | 9 +- resources/web/study/Finder/dataFinder.css | 69 ++++-- .../web/study/Finder/panel/FacetSelection.js | 19 +- .../web/study/Finder/panel/FacetsGrid.js | 18 +- resources/web/study/Finder/panel/Finder.js | 93 +++++++- .../study/Finder/panel/SelectionSummary.js | 37 +++- resources/web/study/Finder/panel/Studies.js | 18 +- .../web/study/Finder/panel/StudyCards.js | 112 +++++++--- resources/web/study/Finder/trialShare.css | 4 + .../trialshare/TrialShareController.java | 201 ++++++------------ src/org/labkey/trialshare/data/StudyBean.java | 65 +++++- ...medBean.java => StudyPublicationBean.java} | 33 ++- src/org/labkey/trialshare/view/dataFinder.jsp | 6 +- .../labkey/trialshare/view/studyDetail.jsp | 89 ++++---- .../trialshare/view/studyPublications.jsp | 124 +++++++++++ 19 files changed, 706 insertions(+), 318 deletions(-) create mode 100644 resources/web/study/Finder/data/Studies.js create mode 100644 resources/web/study/Finder/trialShare.css rename src/org/labkey/trialshare/data/{StudyPubmedBean.java => StudyPublicationBean.java} (73%) create mode 100644 src/org/labkey/trialshare/view/studyPublications.jsp diff --git a/resources/olap/StudyCube.xml b/resources/olap/StudyCube.xml index ad40644c..31c7a2f8 100644 --- a/resources/olap/StudyCube.xml +++ b/resources/olap/StudyCube.xml @@ -7,25 +7,18 @@ - + - - - - + - - - -
          @@ -47,6 +40,7 @@ + diff --git a/resources/web/study/Finder/data/Facet.js b/resources/web/study/Finder/data/Facet.js index 955fa603..dcd0a374 100644 --- a/resources/web/study/Finder/data/Facet.js +++ b/resources/web/study/Finder/data/Facet.js @@ -17,7 +17,8 @@ Ext4.define('LABKEY.study.data.Facet', { {name: 'hierarchyName'}, {name: 'levelName'}, {name: 'allMemberName'}, - {name: 'ordinal'} + {name: 'ordinal'}, + {name: 'isExpanded', type:'boolean', default: 'true'} ], diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index 446a4b89..1aabbdb6 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -111,18 +111,18 @@ Ext4.define('LABKEY.study.store.Facets', { for (f = 0; f < facetStore.count(); f++) { facet = facetStore.getAt(f); - var filterMembers = facet.get("selectedMembers"); + var selectedMembers = facet.get("selectedMembers"); if (facet.get("name") == 'Study') { - if (!filterMembers || filterMembers.length == facet.data.members.length) - continue; - if (filterMembers.length == 0) - { - // in the case of study filter, this means no matches, rather than no filter! - this.updateCountsZero(); - return; - } - var uniqueNames = filterMembers.map(function(m){return m.data.uniqueName;}); + //if (!selectedMembers || selectedMembers.length == facet.data.members.length) + // continue; + //if (selectedMembers.length == 0) + //{ + // // in the case of study filter, this means no matches, rather than no filter! + // this.updateCountsZero(); + // return; + //} + var uniqueNames = selectedMembers.map(function(m){return m.uniqueName;}); if (this.filterByLevel != "[Study].[Study]") intersectFilters.push({ level: this.filterByLevel, @@ -133,25 +133,25 @@ Ext4.define('LABKEY.study.store.Facets', { } else { - if (!filterMembers || filterMembers.length == 0) + if (!selectedMembers || selectedMembers.length == 0) continue; if (facet.get("currentFilterType") === "OR") { var names = []; - filterMembers.forEach(function (m) + selectedMembers.forEach(function (m) { names.push(m.data.uniqueName) }); intersectFilters.push({ level: this.filterByLevel, - membersQuery: {level: filterMembers[0].data.level, members: names} + membersQuery: {level: selectedMembers[0].data.level, members: names} }); } else { - for (i = 0; i < filterMembers.length; i++) + for (i = 0; i < selectedMembers.length; i++) { - var filterMember = filterMembers[i]; + var filterMember = selectedMembers[i]; intersectFilters.push({ level: this.filterByLevel, membersQuery: {level: filterMember.data.level, members: [filterMember.data.uniqueName]} @@ -174,6 +174,7 @@ Ext4.define('LABKEY.study.store.Facets', { var includeSubjectIds = false; var onRows = { operator: "UNION", arguments: [] }; + onRows.arguments.push({level: this.filterByLevel}); for (f = 0; f < facetStore.count(); f++) { facet = facetStore.getAt(f); @@ -217,13 +218,11 @@ Ext4.define('LABKEY.study.store.Facets', { for (f = 0; f < facetStore.count(); f++) { facet = facetStore.getAt(f); - facet.data.summaryCount = 0; - facet.data.allMemberCount = 0; for (var m = 0; m < facetMembersStore.count(); m++) { member = facetMembersStore.getAt(m); - member.data.count = 0; - member.data.percent = 0; + member.set("count", 0); + member.set("percent", 0); } } @@ -248,28 +247,30 @@ Ext4.define('LABKEY.study.store.Facets', { { facet = this.getAt(f); map[facet.data.hierarchy.uniqueName] = facet; - facet.data.summaryCount = 0; - facet.data.allMemberCount = 0; + if (facet.get("name") == "Study") { + facet.data.selectedMembers = []; + } } for (m = 0; m < facetMembersStore.count(); m++) { - facetMembersStore.getAt(m).data.count = 0; - facetMembersStore.getAt(m).data.percent = 0; + facetMembersStore.getAt(m).set("count", 0); + facetMembersStore.getAt(m).set("percent", 0); } var positions = this.getRowPositionsOneLevel(cellSet); var data = this.getDataOneColumn(cellSet, 0); var max = 0; + var selectedStudies = {}; for (var i = 0; i < positions.length; i++) { var resultMember = positions[i]; + var hierarchyName = resultMember.level.hierarchy.uniqueName; //if (resultMember.data.level.uniqueName == "[Subject].[Subject]") //{ // this.subjects.push(resultMember.data.name); //} //else { - var hierarchyName = resultMember.level.hierarchy.uniqueName; - facet = map[hierarchyName]; // todo can't this come from the store if the key is uniqueName? + facet = map[hierarchyName]; var count = data[i]; member = facetMembersStore.getById(resultMember.uniqueName); if (!member) @@ -277,15 +278,18 @@ Ext4.define('LABKEY.study.store.Facets', { // might be an all member if (facet.data.allMemberName == resultMember.uniqueName) facet.data.allMemberCount = count; + else if (facet.get("name") == "Study") + { + selectedStudies[resultMember.name] = resultMember; + facet.data.selectedMembers.push(resultMember); + } else if (-1 == resultMember.uniqueName.indexOf("#") && "(All)" != resultMember.name) console.log("member not found: " + resultMember.uniqueName); + } else { member.set("count", count); - //member.data.count = count; - if (count) - facet.data.summaryCount += 1; if (count > max) max = count; } @@ -295,24 +299,29 @@ Ext4.define('LABKEY.study.store.Facets', { for (f = 0; f < facetStore.count(); f++) { facet = facetStore.getAt(f); - map[facet.data.hierarchy.uniqueName] = facet; if (facet.data.hierarchy.uniqueName !== this.filterByFacetUniqueName) { for (m = 0; m < facet.data.members.length; m++) { member = facetMembersStore.getById(facet.data.members[m].uniqueName); member.set("percent", max == 0 ? 0 : (100.0 * member.data.count) / max); - //member.data.percent = max == 0 ? 0 : (100.0 * member.data.count) / max; } } } - //this.fireEvent("countUpdate"); + + this.updateStudyFilter(selectedStudies); + //this.saveFilterState(); //this.updateContainerFilter(); //if (!isSavedGroup) // this.changeSubjectGroup(); - //this.doneRendering(); + }, + + updateStudyFilter : function(selectedStudies) { + var studiesStore = Ext4.getStore("studies"); + studiesStore.selectedStudies = selectedStudies; + studiesStore.updateFilters(selectedStudies); }, getRowPositions : function(cellSet) diff --git a/resources/web/study/Finder/data/Studies.js b/resources/web/study/Finder/data/Studies.js new file mode 100644 index 00000000..f6ad89d9 --- /dev/null +++ b/resources/web/study/Finder/data/Studies.js @@ -0,0 +1,39 @@ +Ext4.define('LABKEY.study.store.Studies', { + extend: 'Ext.data.Store', + storeId: 'studies', + model: 'LABKEY.study.data.StudyCard', + autoLoad: true, + dataModuleName: this.dataModuleName, + selectedStudies : {}, + selectedSubset : 'public', + proxy : { + type: "ajax", + url: LABKEY.ActionURL.buildURL('trialshare', "studies.api", LABKEY.containerPath), + reader: { + type: 'json', + root: 'data' + } + }, + sorters: [{ + property: 'studyId', + direction: 'ASC' + }], + + updateFilters: function(selectedStudies, selectedSubset) { + if (selectedStudies) + this.selectedStudies = selectedStudies; + if (selectedSubset) + this.selectedSubset = selectedSubset; + var study; + for (var i = 0; i < this.count(); i++) { + study = this.getAt(i); + study.set("isSelected", this.selectedStudies[study.get("shortName")] !== undefined); + } + this.clearFilter(true); + this.filter([ + {property: 'isSelected', value: true}, + {property: 'isPublic', value: this.selectedSubset !== "operational"} + ]); + } + +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/StudyCard.js b/resources/web/study/Finder/data/StudyCard.js index 6595dc65..1166f770 100644 --- a/resources/web/study/Finder/data/StudyCard.js +++ b/resources/web/study/Finder/data/StudyCard.js @@ -7,10 +7,15 @@ Ext4.define('LABKEY.study.data.StudyCard', { {name: 'studyId'}, {name: 'title'}, {name: 'url'}, - {name: 'brand'}, // TODO bring this from the Java side + {name: 'shortName'}, + {name: 'iconUrl'}, {name: 'investigator'}, {name: 'hasManuscript', type: 'boolean'}, {name: 'isLoaded', type: 'boolean'}, // TODO isHighlighted - {name: 'availability'} + {name: 'availability'}, + {name: 'isPublic', type: 'boolean'}, + {name: 'manuscriptCount', type: 'int'}, + {name: 'participantCount', type: 'int'}, + {name: 'isSelected', type: 'boolean'} ] }); \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 3b69206d..a6f0ea3a 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -53,15 +53,36 @@ TABLE.labkey-data-finder } .labkey-study-card-goto, -.labkey-study-card-pi +.labkey-study-card-pi, +.labkey-study-card-short-name { float:right; } + +.labkey-study-card-shortName +{ + float:right; +} + +.labkey-study-icon +{ + height: 25px; + float: right; +} + .labkey-study-card-divider, +.labkey-study-card-title, .labkey-study-card-description { clear:both; } + +.labkey-text-link.labkey-study-card-manuscript +{ + float:right; + margin-top: 10px; +} + DIV.labkey-study-card { background-color:#F8F8F8; @@ -75,6 +96,7 @@ DIV.labkey-study-card overflow-y:hidden; } +DIV.manuscript-highlight, DIV.loaded, SPAN.loaded { @@ -243,7 +265,9 @@ DIV.facet-header .labkey-filter-options background-color: white; } - +.labkey-study-facets .x4-grid-row-selected .labkey-facet-percent-bar { + background-color: rgba(81, 158, 218, 0.2); +} .labkey-study-facets .x4-grid-cell-special .x4-grid-cell-inner { background-color: white; @@ -423,7 +447,7 @@ LI.member SPAN.bar-selected LI.member .bar { height:14pt; - background-color:#DEDEDE; + background-color:rgba(222,222,222,0.3); position:absolute; right:0; z-index:0; } @@ -574,14 +598,14 @@ ul.nav padding: 0; } -div.study-demographics { +div.labkey-study-details { background-color: white; padding: 10px 20px 20px 20px; margin-top: 10px; box-shadow: 0 1px 1px rgba(0,0,0,0.15), -1px 0 0 rgba(0,0,0,0.06), 1px 0 0 rgba(0,0,0,0.06), 0 1px 0 rgba(0,0,0,0.12); } -div.study-demographics h2 { +div.labkey-study-details h2.labkey-study-accession { display: inline-block; text-transform: uppercase; font-weight: normal; @@ -593,7 +617,22 @@ div.study-demographics h2 { margin-left: -20px; } -div.study-demographics h3 { +div.labkey-study-details h2.labkey-study-short-name { + display: inline-block; + text-transform: uppercase; + font-weight: normal; + background-color: #126495; + color: white; + font-size: 13px; + padding: 9px 20px 7px 20px; + float: right; + color: black; + margin-right: -20px; + margin-top: -10px; +} + + +div.labkey-study-details h3 { text-transform: uppercase; font-size: 14px; font-weight: normal; @@ -601,32 +640,38 @@ div.study-demographics h3 { border-bottom: 1px solid darkgray; } -#demographics-content .detail { +#labkey-study-details-content .detail { font-size: 15px; padding-left: 30px; padding-bottom: 5px; } -#demographics-content .detail div { +#labkey-study-details-content .detail div { font-size: 15px; } -#demographics-content h3 { +#labkey-study-details-content h3 { margin-bottom: 0.5em; margin-top: 0.5em; } -#demographics-content div { +#labkey-study-details-content div { padding: 3px; } -#demographics-content div.label, a.label { +#labkey-study-details-content div.label, a.label { font-size: 12px; color: #a9a9a9; vertical-align: text-top; } -#assays-content .detail div { +#labkey-study-assays-content .detail div { font-size: 15px; padding: 3px; +} + +.labkey-study-manuscript-header { + color: #cc541f; + font-size: 120%; + font-variant: small-caps; } \ No newline at end of file diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index 52023060..adc44808 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -33,7 +33,7 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { }, onClearAllFilters: function() { - this.facets.clearAllFilters(true); + this.getFacets().clearAllFilters(true); this.onFilterSelectionChange(false); }, @@ -43,8 +43,16 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { if (hasFilters) Ext4.get(Ext4.DomQuery.select('.labkey-clear-all', this.id)[0]).replaceCls('inactive', 'active'); else - Ext4.get(Ext4.DomQuery.select('.labkey-clear-all', this.id)[0]).replaceCls('active', 'inactive') + Ext4.get(Ext4.DomQuery.select('.labkey-clear-all', this.id)[0]).replaceCls('active', 'inactive'); + this.updateSummaryPanel(); // TODO is this necessary? + }, + updateSummaryPanel : function() { + var studiesStore = Ext4.getStore("studies"); + this.getFacetSelectionSummary().update({ + studyCount: studiesStore.count(), + participantCount: studiesStore.sum("participantCount") + }); }, getFacetPanelHeader : function() { @@ -59,8 +67,13 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { getFacetSelectionSummary: function() { if (!this.facetSelectionSummary) { + var studiesStore = Ext4.getStore("studies"); this.facetSelectionSummary = Ext4.create("LABKEY.study.panel.SelectionSummary", { - dataModuleName: this.dataModuleName + dataModuleName: this.dataModuleName, + data: { + studyCount: studiesStore.count(), + participantCount: studiesStore.sum("participantCount") + } }); } return this.facetSelectionSummary; diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 68b630d4..eec7dad2 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -77,6 +77,11 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { var id = Ext4.id(); LABKEY.study.panel.FacetsGrid.headerLookup[name] = id; return id; + }, + makeFilterOptions: function(name) { + var facetStore = Ext4.getStore("facets"); + var facet = facetStore.get(name); + // TODO render menu or not } } ) @@ -84,8 +89,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { }, statics: { - headerLookup: {}, - groupExpansionLookup: {} // TODO + headerLookup: {} }, initComponent: function() { @@ -147,7 +151,14 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { } }, + updateFacetHeaders: function() { + this.facetStore.each(function(facet) { + this.updateFacetHeaders(facet.get("name"), facet.get("isExpanded")); + }, this); + }, + updateFacetHeader: function(facetName, isExpanded) { + if (this.hasFilters(facetName)) { Ext4.get(Ext4.DomQuery.select('.labkey-clear-filter', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).replaceCls('inactive', 'active'); @@ -157,6 +168,9 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { Ext4.get(Ext4.DomQuery.select('.labkey-clear-filter', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).replaceCls('active', 'inactive'); } this.updateCurrentFacetOption(facetName, isExpanded); + var facet = this.facetStore.getById(facetName); + if (facet) + facet.set("isExpanded", isExpanded); }, updateCurrentFacetOption: function(facetName, isExpanded) { diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index efccea7c..fbe241da 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -34,10 +34,11 @@ Ext4.define('LABKEY.study.panel.Finder', { this._initResize(); - this.on( - 'filterSelectionChanged', this.onFilterSelectionChange - //'studySubsetChanged', this.onStudySubsetChanged, - //'searchTermsChanged', this.onSearchTermsChanged + this.on({ + filterSelectionChanged: this.onFilterSelectionChange, + studySubsetChanged: this.onStudySubsetChanged + //searchTermsChanged: this.onSearchTermsChanged + } ); }, @@ -67,14 +68,90 @@ Ext4.define('LABKEY.study.panel.Finder', { //this.getStudiesPanel().onCubeReady(this.cube); }, - //onStudySubsetChanged : function(value) { - // this.getStudiesPanel().getStudyCards().store.filter('availability', value); - //}, - // + onStudySubsetChanged : function(value) { + this.getFacetsPanel().updateSummaryPanel(); + }, + onFilterSelectionChange : function(){ console.log("Filter selection changed!"); + this.getStudiesPanel().onFilterSelectionChanged(); }, +// $scope.onStudySubsetChanged = function () +//{ +// if ($scope.localStorageService.isSupported) +// $scope.localStorageService.add("studySubset", $scope.studySubset); +// // if there are search terms, just act as if the search terms have changed +// if ($scope.searchTerms) +// { +// $scope.onSearchTermsChanged(); +// } +// else +// { +// $scope.clearStudyFilter(); +// } +//}; +// +//$scope.doSearchTermsChanged_promise = null; +// +//$scope.doSearchTermsChanged = function () +//{ +// if ($scope.doSearchTermsChanged_promise) +// { +// // UNDONE:cancel doesn't seem to really be supported for $http +// //$scope.http.cancel($scope.doSearchTermsChanged_promise); +// } +// +// if (!$scope.searchTerms) +// { +// $scope.searchMessage = ""; +// $scope.clearStudyFilter(); +// return; +// } +// +// var scope = $scope; +// var url = LABKEY.ActionURL.buildURL("search", "json", "/home/", { +// "category": "immport_study", +// "scope": "Folder", +// "q": $scope.searchTerms +// }); +// var promise = $scope.http.get(url); +// $scope.doSearchTermsChanged_promise = promise; +// promise.success(function (data) +// { +// // NOOP if we're not current (poor man's cancel) +// if (promise != $scope.doSearchTermsChanged_promise) +// return; +// $scope.doSearchTermsChanged_promise = null; +// var hits = data.hits; +// var searchStudies = []; +// var found = {}; +// for (var h = 0; h < hits.length; h++) +// { +// var id = hits[h].id; +// var accession = id.substring(id.lastIndexOf(':') + 1); +// if (found[accession]) +// continue; +// found[accession] = true; +// searchStudies.push("[Study].[" + accession + "]"); +// } +// if (!searchStudies.length) +// { +// $scope.setStudyFilter(searchStudies); +// $scope.searchMessage = 'No studies match your search criteria'; +// } +// else +// { +// $scope.searchMessage = ''; +// // intersect with study subset list +// var result = $scope.intersect(searchStudies, $scope.getStudySubsetList()); +// if (!result.length) +// $scope.searchMessage = 'No studies match your search criteria'; +// $scope.setStudyFilter(result); +// } +// }); +//}; + _initResize : function() { var resize = function(w, h) { LABKEY.ext4.Util.resizeToViewport(this, w, h, 46, 32); diff --git a/resources/web/study/Finder/panel/SelectionSummary.js b/resources/web/study/Finder/panel/SelectionSummary.js index 8a8e7b62..2eff6c60 100644 --- a/resources/web/study/Finder/panel/SelectionSummary.js +++ b/resources/web/study/Finder/panel/SelectionSummary.js @@ -3,34 +3,51 @@ Ext4.define("LABKEY.study.panel.SelectionSummary", { alias : 'widget.facet-selection-summary', - renderTpl: new Ext4.XTemplate( + tpl: new Ext4.XTemplate( '
          ', '
          ', '
          Summary
          ', '
            ', '
          • ', ' Studies', - ' {studyCount:this.formatNumber}', + ' {studyCount:this.getStudyCount}', '
          • ', '
          • ', ' Subjects', - ' {participantCount:this.formatNumber}', + ' {participantCount:this.getParticipantCount}', '
          • ', '
          ', '
          ', '
          ', { - formatNumber : Ext4.util.Format.numberRenderer('0,000') + formatNumber : Ext4.util.Format.numberRenderer('0,000'), + + getStudyCount : function(defaultValue) { + var studyStore = Ext4.getStore("studies"); + if (!studyStore) + return this.formatNumber(defaultValue); + return this.formatNumber(studyStore.count()); + }, + + getParticipantCount: function(defaultValue) { + var studyStore = Ext4.getStore("studies"); + if (!studyStore) + return this.formatNumber(defaultValue); + return this.formatNumber(studyStore.sum("participantCount")); + } } ), - renderSelectors: { - studyCountEl: 'span#memberCount', - participantCountEl: 'span#participantCount' + data : { + studyCount: 0, + participantCount: 0 }, - renderData : { - studyCount: 4, - participantCount: 10000 + initComponent: function() { + Ext4.getStore('studies').addListener('filterChange',this.onFilterSelectionChanged, this); + }, + onFilterSelectionChanged : function() { + console.log("in SelectionSummary with filterChange"); + this.update(); } }); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/Studies.js b/resources/web/study/Finder/panel/Studies.js index bd582d85..d8e257df 100644 --- a/resources/web/study/Finder/panel/Studies.js +++ b/resources/web/study/Finder/panel/Studies.js @@ -10,6 +10,8 @@ Ext4.define("LABKEY.study.panel.Studies", { dataModuleName: 'study', + autoScroll: true, + initComponent : function() { this.items = [ this.getStudyPanelHeader(), @@ -17,15 +19,23 @@ Ext4.define("LABKEY.study.panel.Studies", { ]; this.callParent(); + this.getStudyCards().store.addListener('filterChange',this.onFilterSelectionChanged, this); this.on( {'studySubsetChanged': this.onStudySubsetChanged, - 'searchTermsChanged': this.onSearchTermsChanged} + 'searchTermsChanged': this.onSearchTermsChanged, + 'filterSelectionChanged': this.onFilterSelectionChanged + } ); }, - onStudySubsetChanged : function(value) { - this.getStudyCards().store.clearFilter(); - this.getStudyCards().store.filter('availability', value); + onFilterSelectionChanged: function() { + console.log('filterChange happened!') + }, + + onStudySubsetChanged : function(selectedSubset) { + if (!selectedSubset) + selectedSubset = this.getStudyPanelHeader().getStudySubsetMenu().getValue(); + this.getStudyCards().store.updateFilters(null, selectedSubset); }, onSearchTermsChanged : function(value) { diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index 8fc97aec..56bef93e 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -14,31 +14,28 @@ Ext4.define("LABKEY.study.panel.StudyCards", { autoScroll: true, - dataModuleName: 'study', - - store : Ext4.create('Ext.data.Store', { - model: 'LABKEY.study.data.StudyCard', - autoLoad: true, - proxy : { - type: "ajax", - url: LABKEY.ActionURL.buildURL('trialshare', "studies.api", LABKEY.containerPath), - reader: { - type: 'json', - root: 'data' - } - } + dataModuleName: 'study', // TODO is there a way to use this below? + + store : Ext4.create('LABKEY.study.store.Studies', { + dataModuleName: this.dataModuleName }), tpl: new Ext4.XTemplate( '
          ', ' ', - ' ', - // TODO this should be something like labkey-study-card-highlight-1 instead of loaded - '
          ', + ' ', + '
          ', ' ', '
          ', ' ', ' {studyId}', + ' ', + ' {shortName}', + ' ', + ' ', + '  
          ', + '
          ', + '
          ', ' {investigator}', '
          ', '
          ', @@ -47,26 +44,46 @@ Ext4.define("LABKEY.study.panel.StudyCards", { ' go to study', ' ', '
          ', - - '
          {title}
          ', - ' ', - '

          Manuscript available', - '
          ', + '
          {title}
          ', + ' {manuscriptCount:this.displayManuscriptCount}', '
          ', '
          ', - '
          ' + '
          ', + { + displayManuscriptCount : function(count) { + if (count == 0) + return ""; + else { + var text = ''; + if (count == 1) + text += "1 manuscript available"; + else + text += count + " manuscripts available"; + text += ''; + return text; + } + } + } ), listeners: { - itemClick: function(view, record, item, index, e, eOpts) { - console.log("Show study popup for record " , record); - this.showPopup(record.get("studyId")); + itemClick: function(view, record, item, index, event, eOpts) { + if (event.target.className.includes("labkey-study-card-summary")) + { + console.log("Show study popup for record " , record); + this.showStudyDetailPopup(record.get("studyId")); + } + else if (event.target.className.includes("labkey-study-card-manuscript")) + { + console.log("Show manuscript popup for record " , record); + this.showStudyManuscriptsPopup(record.get("studyId")); + } } }, - showPopup : function(studyId) + showStudyDetailPopup : function(studyId) { - this.hidePopup(); + this.hidePopup(this.detailShowing); var detailWindow = Ext4.create('Ext.window.Window', { width: 800, @@ -78,7 +95,7 @@ Ext4.define("LABKEY.study.panel.StudyCards", { autoScroll: true, loader: { autoLoad: true, - url: this.dataModuleName + '-studyDetail.view?_frame=none&studyId=' + studyId + url: this.dataModuleName + '-studyDetail.view?_frame=none&detailType=study&studyId=' + studyId } }); var viewScroll = Ext4.getBody().getScroll(); @@ -91,21 +108,46 @@ Ext4.define("LABKEY.study.panel.StudyCards", { this.detailShowing.show(); }, - hidePopup: function() + showStudyManuscriptsPopup : function(studyId) { - if (this.detailShowing) + this.hidePopup(this.manuscriptsShowing); + + var detailWindow = Ext4.create('Ext.window.Window', { + width: 800, + maxHeight: 600, + resizable: true, + layout: 'fit', + border: false, + cls: 'labkey-study-detail-manuscripts', + autoScroll: true, + loader: { + autoLoad: true, + url: this.dataModuleName + '-studyDetail.view?_frame=none&detailType=publications&studyId=' + studyId + } + }); + var viewScroll = Ext4.getBody().getScroll(); + var viewSize = Ext4.getBody().getViewSize(); + var region = [viewScroll.left, viewScroll.top, viewScroll.left + viewSize.width, viewScroll.top + viewSize.height]; + var proposedXY = [region[0] + viewSize.width / 2 - 400, region[1] + viewSize.height / 2 - 300]; + proposedXY[1] = Math.max(region[1], Math.min(region[3] - 400, proposedXY[1])); + detailWindow.setPosition(proposedXY); + this.manuscriptsShowing = detailWindow; + this.manuscriptsShowing.show(); + }, + + hidePopup: function(popup) + { + if (popup) { - this.detailShowing.hide(); - this.detailShowing.destroy(); - this.detailShowing = null; + popup.hide(); + popup.destroy(); + popup = null; } }, initComponent: function() { - console.log("in StudyCards dataModuleName is " + this.dataModuleName); this.getStore().load(); this.callParent(); - } }); \ No newline at end of file diff --git a/resources/web/study/Finder/trialShare.css b/resources/web/study/Finder/trialShare.css new file mode 100644 index 00000000..34ef5db7 --- /dev/null +++ b/resources/web/study/Finder/trialShare.css @@ -0,0 +1,4 @@ +.labkey-study-card-pi +{ + float:left; +} \ No newline at end of file diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 1f56d65b..1a775eb2 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -21,8 +21,13 @@ import org.labkey.api.action.ReturnUrlForm; import org.labkey.api.action.SimpleViewAction; import org.labkey.api.action.SpringActionController; +import org.labkey.api.data.SimpleFilter; import org.labkey.api.data.SqlExecutor; +import org.labkey.api.data.TableSelector; import org.labkey.api.gwt.client.util.StringUtils; +import org.labkey.api.query.DefaultSchema; +import org.labkey.api.query.FieldKey; +import org.labkey.api.query.QuerySchema; import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.util.PageFlowUtil; @@ -34,14 +39,16 @@ import org.labkey.trialshare.data.FacetFilter; import org.labkey.trialshare.data.StudyBean; import org.labkey.trialshare.data.StudyFacetBean; -import org.labkey.trialshare.data.StudyFacetMember; +import org.labkey.trialshare.data.StudyPublicationBean; import org.labkey.trialshare.data.StudySubset; import org.labkey.trialshare.view.DataFinderWebPart; import org.springframework.validation.BindException; import org.springframework.web.servlet.ModelAndView; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Marshal(Marshaller.Jackson) public class TrialShareController extends SpringActionController @@ -49,6 +56,8 @@ public class TrialShareController extends SpringActionController private static final DefaultActionResolver _actionResolver = new DefaultActionResolver(TrialShareController.class); public static final String NAME = "trialshare"; + public enum DetailType { publications, study }; + public TrialShareController() { setActionResolver(_actionResolver); @@ -95,9 +104,25 @@ public class StudiesAction extends ApiAction public Object execute(StudiesForm form, BindException errors) throws Exception { List studies = new ArrayList(); - studies.add(getStudy("ITN029ST")); - studies.add(getStudy("ITN021AI")); - studies.add(getStudy("ITN033AI")); +// studies.add(getStudy("ITN029ST")); +// studies.add(getStudy("ITN021AI")); +// studies.add(getStudy("ITN033AI")); + QuerySchema coreSchema = DefaultSchema.get(getUser(), getContainer()).getSchema("core"); + studies = (new TableSelector(coreSchema.getSchema("lists").getTable("studyProperties"))).getArrayList(StudyBean.class); + List publications = (new TableSelector(coreSchema.getSchema("lists").getTable("studyManuscripts")).getArrayList(StudyPublicationBean.class)); + Map pubCounts = new HashMap<>(); + for (StudyPublicationBean pub : publications) { + if (pubCounts.get(pub.getStudyId()) == null) + pubCounts.put(pub.getStudyId(), 0); + int count = pubCounts.get(pub.getStudyId()); + pubCounts.put(pub.getStudyId(), count + 1); + } + for (StudyBean study : studies) { + if (pubCounts.get(study.getStudyId()) == null) + study.setManuscriptCount(0); + else + study.setManuscriptCount(pubCounts.get(study.getStudyId())); + } return success(studies); } @@ -114,6 +139,7 @@ private StudyBean getStudy(String studyId) if (studyId.equals("ITN029ST")) { study.setStudyId("ITN029ST"); + study.setShortName("WISP-R"); study.setInvestigator("Sandy Feng, MD, PhD"); study.setTitle("Immunosuppression Withdrawal for Pediatric Living-donor Liver Transplant Recipients"); study.setDescription("This is a prospective multicenter, open-label, single-arm trial in which 20 pediatric recipients of parental living-donor liver allografts will undergo gradual withdrawal of immunosuppression with the goal of complete withdrawal. Patients on stable immunosuppression regimens with good organ function and no evidence of acute or chronic rejection or other forms of allograft dysfunction will be enrolled. Participants will undergo gradual withdrawal of immunosuppression and will be followed for a minimum of 4 years after completion of immunosuppression withdrawal. Immunologic and genetic profiles will be collected at multiple time points and compared between tolerant and nontolerant participants."); @@ -125,6 +151,7 @@ private StudyBean getStudy(String studyId) else if (studyId.equals("ITN021AI")) { study.setStudyId("ITN021AI"); + study.setShortName("RAVE"); study.setInvestigator("John H. Stone, MD, MPH"); study.setTitle("Rituximab for ANCA-Associated Vasculitis"); study.setIsLoaded(false); @@ -141,6 +168,9 @@ else if (studyId.equals("ITN021AI")) else if (studyId.equals("ITN033AI")) { study.setStudyId("ITN033AI"); + study.setShortName("Halt-MS"); + study.setIconUrl("https://www.itntrialshare.org/files/Studies/ITN033AIOPR/Study%20Data/@files/studyDocs/ITN033AI%20HALT-MS%20250px.gif"); + study.setManuscriptCount(3); study.setInvestigator("Richard A. Nash, MD"); study.setTitle("High Dose Immunosuppression and Autologous Transplantation for Multiple Sclerosis"); study.setIsLoaded(false); @@ -152,117 +182,6 @@ else if (studyId.equals("ITN033AI")) return study; } - @RequiresPermission(ReadPermission.class) - public class StudyFacetMembersAction extends ApiAction - { - @Override - public Object execute(Object o, BindException errors) throws Exception - { - List members = new ArrayList<>(); - - StudyFacetMember member = new StudyFacetMember(); - member.setName("Transplant"); - member.setUniqueName("Transplant"); - member.setFacetName("Therapeutic Area"); - member.setFacetUniqueName("TherapeuticArea"); - member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); - member.setCount(4); - member.setPercent(50); - members.add(member); - - member = new StudyFacetMember(); - member.setName("Autoimmune"); - member.setUniqueName("Autoimmune"); - member.setFacetName("Therapeutic Area"); - member.setFacetUniqueName("TherapeuticArea"); - member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); - member.setCount(3); - member.setPercent(42); - members.add(member); - - member = new StudyFacetMember(); - member.setName("Allergy"); - member.setUniqueName("Allergy"); - member.setFacetName("Therapeutic Area"); - member.setFacetUniqueName("TherapeuticArea"); - member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); - member.setCount(1); - member.setPercent(8); - members.add(member); - - member = new StudyFacetMember(); - member.setName("T1DM"); - member.setUniqueName("T1DM"); - member.setFacetName("Therapeutic Area"); - member.setFacetUniqueName("TherapeuticArea"); - member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); - member.setCount(0); - member.setPercent(0); - members.add(member); - - member = new StudyFacetMember(); - member.setName("Interventional"); - member.setUniqueName("Interventional"); - member.setFacetName("Study Type"); - member.setFacetUniqueName("StudyType"); - member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); - member.setCount(5); - member.setPercent(50); - members.add(member); - - member = new StudyFacetMember(); - member.setName("Observational"); - member.setUniqueName("Observational"); - member.setFacetName("Study Type"); - member.setFacetUniqueName("StudyType"); - member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); - member.setCount(2); - member.setPercent(20); - members.add(member); - - member = new StudyFacetMember(); - member.setName("Expanded Access"); - member.setUniqueName("Expanded Access"); - member.setFacetName("Study Type"); - member.setFacetUniqueName("StudyType"); - member.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); - member.setCount(3); - member.setPercent(30); - members.add(member); - - member = new StudyFacetMember(); - member.setName("Adult"); - member.setUniqueName("Adult"); - member.setFacetName("Age Group"); - member.setFacetUniqueName("AgeGroup"); - member.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); - member.setCount(4); - member.setPercent(50); - members.add(member); - - member = new StudyFacetMember(); - member.setName("Child"); - member.setUniqueName("Child"); - member.setFacetName("Age Group"); - member.setFacetUniqueName("AgeGroup"); - member.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); - member.setCount(1); - member.setPercent(13); - members.add(member); - - member = new StudyFacetMember(); - member.setName("Senior"); - member.setUniqueName("Senior"); - member.setFacetName("Age Group"); - member.setFacetUniqueName("AgeGroup"); - member.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); - member.setCount(3); - member.setPercent(37); - members.add(member); - - return success(members); - } - } private List getFacetFilters(Boolean includeAnd, Boolean includeOr, FacetFilter.Type defaultType) @@ -322,21 +241,31 @@ public Object execute(Object o, BindException errors) throws Exception } } + @RequiresPermission(ReadPermission.class) public class StudyDetailAction extends SimpleViewAction { - StudyIdForm _form; - StudyBean _study = new StudyBean(); + StudyBean _study; + String _studyId; @Override - public ModelAndView getView(StudyIdForm form, BindException errors) throws Exception + public void validate(StudyIdForm form, BindException errors) { - _form = form; + _studyId = (null==form) ? null : form.getStudyId(); + if (StringUtils.isEmpty(_studyId)) + errors.reject(ERROR_MSG, "Study not specified"); + } - String studyId = (null==form) ? null : form.getStudyId(); - if (StringUtils.isEmpty(studyId)) - throw new NotFoundException("study not specified"); + @Override + public ModelAndView getView(StudyIdForm form, BindException errors) throws Exception + { + QuerySchema coreSchema = DefaultSchema.get(getUser(), getContainer()).getSchema("core"); + QuerySchema listSchema = coreSchema.getSchema("lists"); + _study = (new TableSelector(listSchema.getTable("studyProperties"))).getObject(_studyId, StudyBean.class); + SimpleFilter filter = new SimpleFilter(); + filter.addCondition(FieldKey.fromParts("studyId"), _studyId); + _study.setPublications((new TableSelector(listSchema.getTable("studyManuscripts"), filter, null)).getArrayList(StudyPublicationBean.class)); // _study.study = (new TableSelector(DbSchema.get("immport").getTable("study"))).getObject(studyId, StudyBean.class); // if (null == _study.study) // throw new NotFoundException("study not found: " + form.getStudy()); @@ -345,13 +274,17 @@ public ModelAndView getView(StudyIdForm form, BindException errors) throws Excep // _study.personnel = (new TableSelector(DbSchema.get("immport").getTable("study_personnel"),filter,null)).getArrayList(StudyPersonnelBean.class); // _study.pubmed = (new TableSelector(DbSchema.get("immport").getTable("study_pubmed"),filter,null)).getArrayList(StudyPubmedBean.class); - _study = getStudy(studyId); +// _study = getStudy(studyId); VBox v = new VBox(); - if (null != _form.getReturnActionURL()) + if (null != form.getReturnActionURL()) { - v.addView(new HtmlView(PageFlowUtil.textLink("back",_form.getReturnActionURL()) + "
          ")); + v.addView(new HtmlView(PageFlowUtil.textLink("back", form.getReturnActionURL()) + "
          ")); } - v.addView(new JspView("/org/labkey/trialshare/view/studyDetail.jsp", _study)); + if (form.getDetailType() == DetailType.study) + v.addView(new JspView("/org/labkey/trialshare/view/studyDetail.jsp", _study)); + else if (form.getDetailType() == DetailType.publications) + v.addView(new JspView("/org/labkey/trialshare/view/studyPublications.jsp", _study)); + return v; } @@ -365,6 +298,7 @@ public NavTree appendNavTrail(NavTree root) public static class StudyIdForm extends ReturnUrlForm { private String studyId; + private DetailType detailType; public StudyIdForm(){} @@ -377,6 +311,16 @@ public void setStudyId(String studyId) { this.studyId = studyId; } + + public DetailType getDetailType() + { + return detailType; + } + + public void setDetailType(DetailType detailType) + { + this.detailType = detailType; + } } @RequiresPermission(ReadPermission.class) @@ -388,12 +332,7 @@ public Object execute(Object o, BindException errors) throws Exception { List subsets = new ArrayList<>(); StudySubset subset = new StudySubset(); -// subset.setId("all"); -// subset.setName("All"); -// -// subsets.add(subset); -// subset = new StudySubset(); subset.setId("operational"); subset.setName("Operational"); subset.setDefault(false); diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index debdb9db..ba1abf6e 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -13,14 +13,19 @@ public class StudyBean private Boolean hasManuscript; // TODO generalize? private String investigator; private String url; + private String iconUrl; private Boolean isLoaded; private String description; private String briefDescription; private String studyIdPrefix = null; // common prefix used in labeling studies private String availability; + private Boolean isPublic; + private Integer participantCount; + private Boolean isSelected = true; private List personnel; // TODO remove? - private List pubmed; + private List publications; + private Integer manuscriptCount; public String getStudyId() @@ -103,14 +108,14 @@ public void setPersonnel(List personnel) this.personnel = personnel; } - public List getPubmed() + public List getPublications() { - return pubmed; + return publications; } - public void setPubmed(List pubmed) + public void setPublications(List publications) { - this.pubmed = pubmed; + this.publications = publications; } public String getStudyIdPrefix() @@ -152,5 +157,55 @@ public void setAvailability(String availability) { this.availability = availability; } + + public String getIconUrl() + { + return iconUrl; + } + + public void setIconUrl(String iconUrl) + { + this.iconUrl = iconUrl; + } + + public Integer getManuscriptCount() + { + return manuscriptCount; + } + + public void setManuscriptCount(Integer manuscriptCount) + { + this.manuscriptCount = manuscriptCount; + } + + public Boolean getIsPublic() + { + return isPublic; + } + + public void setIsPublic(Boolean aPublic) + { + isPublic = aPublic; + } + + public Integer getParticipantCount() + { + return participantCount; + } + + public void setParticipantCount(Integer participantCount) + { + this.participantCount = participantCount; + } + + public Boolean getIsSelected() + { + return isSelected; + } + + public void setIsSelected(Boolean selected) + { + isSelected = selected; + } } diff --git a/src/org/labkey/trialshare/data/StudyPubmedBean.java b/src/org/labkey/trialshare/data/StudyPublicationBean.java similarity index 73% rename from src/org/labkey/trialshare/data/StudyPubmedBean.java rename to src/org/labkey/trialshare/data/StudyPublicationBean.java index 159a482d..e85141e0 100644 --- a/src/org/labkey/trialshare/data/StudyPubmedBean.java +++ b/src/org/labkey/trialshare/data/StudyPublicationBean.java @@ -15,36 +15,36 @@ */ package org.labkey.trialshare.data; -public class StudyPubmedBean +public class StudyPublicationBean { - String study_accession; - String pubmed_id; + String studyId; + String url; + String pubmedId; String authors; String issue; String journal; String pages; String title; String year; - int worksspace_id; - public String getStudy_accession() + public String getStudyId() { - return study_accession; + return studyId; } - public void setStudy_accession(String study_accession) + public void setStudyId(String studyId) { - this.study_accession = study_accession; + this.studyId = studyId; } - public String getPubmed_id() + public String getPubmedId() { - return pubmed_id; + return pubmedId; } - public void setPubmed_id(String pubmed_id) + public void setPubmedId(String pubmedId) { - this.pubmed_id = pubmed_id; + this.pubmedId = pubmedId; } public String getAuthors() @@ -107,13 +107,4 @@ public void setYear(String year) this.year = year; } - public int getWorksspace_id() - { - return worksspace_id; - } - - public void setWorksspace_id(int worksspace_id) - { - this.worksspace_id = worksspace_id; - } } diff --git a/src/org/labkey/trialshare/view/dataFinder.jsp b/src/org/labkey/trialshare/view/dataFinder.jsp index da8e1e26..f2ee5825 100644 --- a/src/org/labkey/trialshare/view/dataFinder.jsp +++ b/src/org/labkey/trialshare/view/dataFinder.jsp @@ -27,10 +27,12 @@ resources.add(ClientDependency.fromPath("clientapi/ext4")); resources.add(ClientDependency.fromPath("query/olap.js")); resources.add(ClientDependency.fromPath("study/Finder/dataFinder.css")); + resources.add(ClientDependency.fromPath("study/Finder/trialShare.css")); resources.add(ClientDependency.fromPath("study/Finder/data/Facet.js")); resources.add(ClientDependency.fromPath("study/Finder/data/FacetFilter.js")); resources.add(ClientDependency.fromPath("study/Finder/data/FacetMember.js")); resources.add(ClientDependency.fromPath("study/Finder/data/StudyCard.js")); + resources.add(ClientDependency.fromPath("study/Finder/data/Studies.js")); resources.add(ClientDependency.fromPath("study/Finder/data/StudySubset.js")); resources.add(ClientDependency.fromPath("study/Finder/data/Facets.js")); resources.add(ClientDependency.fromPath("study/Finder/data/FacetMembers.js")); @@ -42,9 +44,9 @@ resources.add(ClientDependency.fromPath("study/Finder/panel/SelectionSummary.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/FacetSelection.js")); - resources.add(ClientDependency.fromPath("study/Finder/panel/StudyCards.js")); - resources.add(ClientDependency.fromPath("study/Finder/panel/StudyPanelHeader.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/Studies.js")); + resources.add(ClientDependency.fromPath("study/Finder/panel/StudyPanelHeader.js")); + resources.add(ClientDependency.fromPath("study/Finder/panel/StudyCards.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/Finder.js")); return resources; } diff --git a/src/org/labkey/trialshare/view/studyDetail.jsp b/src/org/labkey/trialshare/view/studyDetail.jsp index 50b72fca..1ef255f3 100644 --- a/src/org/labkey/trialshare/view/studyDetail.jsp +++ b/src/org/labkey/trialshare/view/studyDetail.jsp @@ -35,7 +35,7 @@ <%@ page import="java.util.LinkedHashSet" %> <%@ page import="org.labkey.trialshare.data.StudyBean" %> <%@ page import="org.labkey.trialshare.data.StudyPersonnelBean" %> -<%@ page import="org.labkey.trialshare.data.StudyPubmedBean" %> +<%@ page import="org.labkey.trialshare.data.StudyPublicationBean" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <%! public LinkedHashSet getClientDependencies() @@ -43,6 +43,7 @@ LinkedHashSet resources = new LinkedHashSet<>(); resources.add(ClientDependency.fromPath("dataFinder.css")); + resources.add(ClientDependency.fromPath("trialShare.css")); return resources; } @@ -60,40 +61,43 @@ descriptionHTML = h(study.getBriefDescription()); ActionURL studyUrl = null; - if (!c.isRoot()) - { - String comma = "\n"; - Container p = c.getProject(); - QuerySchema s = DefaultSchema.get(context.getUser(), p).getSchema("study"); - TableInfo sp = s.getTable("StudyProperties"); - if (sp.supportsContainerFilter()) - { - ContainerFilter cf = new ContainerFilter.AllInProject(context.getUser()); - ((ContainerFilterable) sp).setContainerFilter(cf); - } - Collection> maps = new TableSelector(sp).getMapCollection(); - for (Map map : maps) - { - Container studyContainer = ContainerManager.getForId((String) map.get("container")); - String studyAccession = (String)map.get("study_accession"); - String name = (String)map.get("Label"); - if (null == studyAccession && study.getStudyIdPrefix() != null && name.startsWith(study.getStudyIdPrefix())) - studyAccession = name; - if (null != studyContainer && StringUtils.equalsIgnoreCase(study.getStudyId(), studyAccession)) - { - studyUrl = studyContainer.getStartURL(context.getUser()); - break; - } - } - } +// if (!c.isRoot()) +// { +// String comma = "\n"; +// Container p = c.getProject(); +// QuerySchema s = DefaultSchema.get(context.getUser(), p).getSchema("study"); +// TableInfo sp = s.getTable("StudyProperties"); +// if (sp.supportsContainerFilter()) +// { +// ContainerFilter cf = new ContainerFilter.AllInProject(context.getUser()); +// ((ContainerFilterable) sp).setContainerFilter(cf); +// } +// Collection> maps = new TableSelector(sp).getMapCollection(); +// for (Map map : maps) +// { +// Container studyContainer = ContainerManager.getForId((String) map.get("container")); +// String studyAccession = (String)map.get("study_accession"); +// String name = (String)map.get("Label"); +// if (null == studyAccession && study.getStudyIdPrefix() != null && name.startsWith(study.getStudyIdPrefix())) +// studyAccession = name; +// if (null != studyContainer && StringUtils.equalsIgnoreCase(study.getStudyId(), studyAccession)) +// { +// studyUrl = studyContainer.getStartURL(context.getUser()); +// break; +// } +// } +// } + String publicationsTitle = "Manuscripts and Abstracts"; Map linkProps = new HashMap<>(); linkProps.put("target", "_blank"); %> -
          -

          <% if (null!=studyUrl) {%><%}%><%=h(study.getStudyId())%><% if (null!=studyUrl) {%><%}%>

          -
          +
          +

          <% if (null!=studyUrl) {%><%}%><%=h(study.getStudyId())%><% if (null!=studyUrl) {%><%}%>

          +

          <% if (null!=study.getShortName()) {%><%}%><%=h(study.getShortName())%><% if (null!=study.getShortName()) {%><%}%>

          +
          +<% if (null != study.getIconUrl()) {%><%}%>

          <%=h(study.getTitle())%>

          <% if (null != study.getPersonnel()) @@ -103,26 +107,29 @@ if ("Principal Investigator".equals(p.getRole_in_study())) { %>
          - <%=h(p.getHonorific())%> <%=h(p.getFirst_name())%> <%=h(p.getLast_name())%> - <%=h(p.getOrganization())%> + <%=h(p.getHonorific())%> <%=h(p.getFirst_name())%> <%=h(p.getLast_name())%> + <%=h(p.getOrganization())%>
          <% } } } - %>
          <%=text(descriptionHTML)%>
          -
          <% - if (null != study.getPubmed() && study.getPubmed().size() > 0) + %>
          <%=text(descriptionHTML)%>
          +
          <% + if (null != study.getPublications() && study.getPublications().size() > 0) { - %>Papers<% - for (StudyPubmedBean pub : study.getPubmed()) + %><%=h(publicationsTitle)%><% + for (StudyPublicationBean pub : study.getPublications()) { - %>

          <%=h(pub.getJournal())%> <%=h(pub.getYear())%>
          <% - %><%=h(pub.getTitle())%><% - if (!StringUtils.isEmpty(pub.getPubmed_id())) + if (pub.getTitle() != null) + { + %>

          <%=h(pub.getJournal())%> <%=h(pub.getYear())%>
          <% + %><%=h(pub.getTitle())%><% + if (!StringUtils.isEmpty(pub.getPubmedId())) { - %>
          <%=textLink("PubMed","http://www.ncbi.nlm.nih.gov/pubmed/?term=" + pub.getPubmed_id(), null, null, linkProps)%><% + %>
          <%=textLink("PubMed","http://www.ncbi.nlm.nih.gov/pubmed/?term=" + pub.getPubmedId(), null, null, linkProps)%><% } %>

          <% + } } } %>
          diff --git a/src/org/labkey/trialshare/view/studyPublications.jsp b/src/org/labkey/trialshare/view/studyPublications.jsp new file mode 100644 index 00000000..c1461f83 --- /dev/null +++ b/src/org/labkey/trialshare/view/studyPublications.jsp @@ -0,0 +1,124 @@ +<% +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +%> +<%@ page import="org.apache.commons.lang3.StringUtils" %> +<%@ page import="org.labkey.api.data.Container" %> +<%@ page import="org.labkey.api.data.ContainerFilter" %> +<%@ page import="org.labkey.api.data.ContainerFilterable" %> +<%@ page import="org.labkey.api.data.ContainerManager" %> +<%@ page import="org.labkey.api.data.TableInfo" %> +<%@ page import="org.labkey.api.data.TableSelector" %> +<%@ page import="org.labkey.api.query.DefaultSchema" %> +<%@ page import="org.labkey.api.query.QuerySchema" %> +<%@ page import="org.labkey.api.view.ActionURL" %> +<%@ page import="org.labkey.api.view.HttpView" %> +<%@ page import="org.labkey.api.view.JspView" %> +<%@ page import="org.labkey.api.view.ViewContext" %> +<%@ page import="org.labkey.api.view.template.ClientDependency" %> +<%@ page import="org.labkey.trialshare.data.StudyBean" %> +<%@ page import="org.labkey.trialshare.data.StudyPublicationBean" %> +<%@ page import="java.util.Collection" %> +<%@ page import="java.util.HashMap" %> +<%@ page import="java.util.LinkedHashSet" %> +<%@ page import="java.util.Map" %> +<%@ page extends="org.labkey.api.jsp.JspBase" %> +<%! + public LinkedHashSet getClientDependencies() + { + LinkedHashSet resources = new LinkedHashSet<>(); + + resources.add(ClientDependency.fromPath("dataFinder.css")); + resources.add(ClientDependency.fromPath("trialShare.css")); + + return resources; + } +%> +<% + JspView me = (JspView) HttpView.currentView(); + + ViewContext context = HttpView.currentContext(); + Container c = context.getContainer(); + StudyBean study = me.getModelBean(); + + ActionURL studyUrl = null; + if (!c.isRoot()) + { + String comma = "\n"; + Container p = c.getProject(); + QuerySchema s = DefaultSchema.get(context.getUser(), p).getSchema("study"); + TableInfo sp = s.getTable("StudyProperties"); + if (sp.supportsContainerFilter()) + { + ContainerFilter cf = new ContainerFilter.AllInProject(context.getUser()); + ((ContainerFilterable) sp).setContainerFilter(cf); + } + Collection> maps = new TableSelector(sp).getMapCollection(); + for (Map map : maps) + { + Container studyContainer = ContainerManager.getForId((String) map.get("container")); + String studyAccession = (String)map.get("study_accession"); + String name = (String)map.get("Label"); + if (null == studyAccession && study.getStudyIdPrefix() != null && name.startsWith(study.getStudyIdPrefix())) + studyAccession = name; + if (null != studyContainer && StringUtils.equalsIgnoreCase(study.getStudyId(), studyAccession)) + { + studyUrl = studyContainer.getStartURL(context.getUser()); + break; + } + } + } + + String publicationsTitle = "Manuscripts and Abstracts"; + Map linkProps = new HashMap<>(); + linkProps.put("target", "_blank"); +%> + +
          +

          <% if (null!=studyUrl) {%><%}%><%=h(study.getStudyId())%><% if (null!=studyUrl) {%><%}%>

          +

          <% if (null!=study.getShortName()) {%><%}%><%=h(study.getShortName())%><% if (null!=study.getShortName()) {%><%}%>

          +
          +<% if (null != study.getIconUrl()) {%><%}%> +

          <%=h(study.getTitle())%>

          +
          +
          <% + if (null != study.getPublications() && study.getPublications().size() > 0) + { + %><%=h(publicationsTitle)%><% + for (StudyPublicationBean pub : study.getPublications()) + { + if (pub.getTitle() != null) + { + %>

          <%=h(pub.getJournal())%> <%=h(pub.getYear())%>
          <% + %><%=h(pub.getTitle())%><% + if (!StringUtils.isEmpty(pub.getPubmedId())) + { + %>
          <%=textLink("PubMed","http://www.ncbi.nlm.nih.gov/pubmed/?term=" + pub.getPubmedId(), null, null, linkProps)%><% + } + %>

          <% + } + } + } + %>
          +
          + + <% if (null != studyUrl) { %> + <%= textLink("View study " + study.getStudyId(), studyUrl.toString(), null, null, linkProps)%>
          + <% } %> +
          +
          + + From f7985830b5d8f91c2ab14a736720e80ab6dbd38a Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 23 Dec 2015 11:49:34 -0800 Subject: [PATCH 034/587] Spec 24959 : Implement Data Finder for ITN - remove icon from study card --- resources/web/study/Finder/panel/StudyCards.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index 56bef93e..c5b9c5ca 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -31,9 +31,6 @@ Ext4.define("LABKEY.study.panel.StudyCards", { ' {studyId}', ' ', ' {shortName}', - ' ', - ' ', - '  
          ', '
          ', '
          ', ' {investigator}', From 340d13cb10485b9d0b1b2daf20e5644a044ebf0c Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 23 Dec 2015 11:50:24 -0800 Subject: [PATCH 035/587] Spec 24959 : Implement Data Finder for ITN - update styling to swap placement of short name and study id --- resources/web/study/Finder/dataFinder.css | 5 +++- resources/web/study/Finder/trialShare.css | 35 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index a6f0ea3a..79745d0b 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -626,7 +626,6 @@ div.labkey-study-details h2.labkey-study-short-name { font-size: 13px; padding: 9px 20px 7px 20px; float: right; - color: black; margin-right: -20px; margin-top: -10px; } @@ -640,6 +639,10 @@ div.labkey-study-details h3 { border-bottom: 1px solid darkgray; } +#labkey-study-details-content { + padding-top: 1em; +} + #labkey-study-details-content .detail { font-size: 15px; padding-left: 30px; diff --git a/resources/web/study/Finder/trialShare.css b/resources/web/study/Finder/trialShare.css index 34ef5db7..ebf5645c 100644 --- a/resources/web/study/Finder/trialShare.css +++ b/resources/web/study/Finder/trialShare.css @@ -1,4 +1,39 @@ .labkey-study-card-pi { float:left; +} + +.labkey-study-card-short-name +{ + float:left; +} + +.labkey-study-card-accession +{ + float:right; +} + +div.labkey-study-details h2.labkey-study-short-name { + display: inline-block; + text-transform: uppercase; + font-weight: normal; + background-color: #126495; + color: white; + font-size: 13px; + padding: 9px 20px 7px 20px; + margin-left: -20px; + float: left; +} + +div.labkey-study-details h2.labkey-study-accession { + display: inline-block; + text-transform: uppercase; + font-weight: normal; + background-color: #126495; + color: white; + font-size: 13px; + padding: 9px 20px 7px 20px; + float: right; + margin-right: -20px; + margin-top: -10px; } \ No newline at end of file From da47d8584297a92773216b2045f0dd3e4a4107d9 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 23 Dec 2015 11:51:41 -0800 Subject: [PATCH 036/587] Spec 24959 : Implement Data Finder for ITN - fix facet header display and filtering after choosing items --- resources/olap/StudyCube.xml | 3 + resources/web/study/Finder/data/Facet.js | 10 +- resources/web/study/Finder/data/Facets.js | 36 ++++-- resources/web/study/Finder/data/Studies.js | 12 +- .../web/study/Finder/panel/FacetSelection.js | 17 +-- .../web/study/Finder/panel/FacetsGrid.js | 112 ++++++++++-------- resources/web/study/Finder/panel/Finder.js | 2 +- 7 files changed, 109 insertions(+), 83 deletions(-) diff --git a/resources/olap/StudyCube.xml b/resources/olap/StudyCube.xml index 31c7a2f8..ad387cbb 100644 --- a/resources/olap/StudyCube.xml +++ b/resources/olap/StudyCube.xml @@ -34,6 +34,9 @@ + + + diff --git a/resources/web/study/Finder/data/Facet.js b/resources/web/study/Finder/data/Facet.js index dcd0a374..ff80fadc 100644 --- a/resources/web/study/Finder/data/Facet.js +++ b/resources/web/study/Finder/data/Facet.js @@ -5,20 +5,20 @@ Ext4.define('LABKEY.study.data.Facet', { fields: [ {name: 'name'}, - {name: 'pluralName'}, + {name: 'pluralName'}, // TODO not currently used {name: 'members'}, {name: 'selectedMembers'}, - {name: 'memberMap'}, + {name: 'filterOptions'}, + {name: 'memberMap'}, // TODO is this used? {name: 'currentFilterType'}, {name: 'currentFilterCaption'}, - {name: 'summaryCount', type:'int', default: 0}, - {name: 'allMemberCount', type:'int', default: 0}, + {name: 'allMemberCount', type:'int', defaultValue: 0}, // TODO not currently used {name: 'hierarchy'}, {name: 'hierarchyName'}, {name: 'levelName'}, {name: 'allMemberName'}, {name: 'ordinal'}, - {name: 'isExpanded', type:'boolean', default: 'true'} + {name: 'isExpanded', type:'boolean', defaultValue: true} ], diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index 1aabbdb6..8894fbee 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -54,7 +54,6 @@ Ext4.define('LABKEY.study.store.Facets', { if (this.isLoaded && this.mdx) { var cube = this.mdx._cube; - console.log("cube is ready now!"); var facetMembersStore = Ext4.getStore("facetMembers"); for (var f = 0; f < this.count(); f++) { @@ -76,8 +75,6 @@ Ext4.define('LABKEY.study.store.Facets', { level: src.level.uniqueName, count: 0, percent: 0, - //filteredCount: -1, - //selectedCount: -1, facetName: facet.get("name"), facet : facet }; @@ -103,11 +100,27 @@ Ext4.define('LABKEY.study.store.Facets', { return optionsStore.getAt(0); }, + getStudySubsetFilter: function() { + var studiesStore = Ext4.getStore("studies"); + if (studiesStore.selectedSubset == "operational") + return {level: "[Study.Public].[Public]", members: ["[Study.Public].[false]"]}; + else + return {level: "[Study.Public].[Public]", members: ["[Study.Public].[true]"]}; + }, + updateCountsAsync: function (isSavedGroup) { + if (!this.isLoaded || !this.mdx) + { + console.log("Store not ready for count update. Please try again later."); + return; + } + var facetStore = this; var intersectFilters = []; var i, f, facet; + + intersectFilters.push(this.getStudySubsetFilter()); for (f = 0; f < facetStore.count(); f++) { facet = facetStore.getAt(f); @@ -122,14 +135,15 @@ Ext4.define('LABKEY.study.store.Facets', { // this.updateCountsZero(); // return; //} - var uniqueNames = selectedMembers.map(function(m){return m.uniqueName;}); - if (this.filterByLevel != "[Study].[Study]") - intersectFilters.push({ - level: this.filterByLevel, - membersQuery: {level: "[Study].[Study]", members: uniqueNames} - }); - else - intersectFilters.push({level: "[Study].[Study]", members: uniqueNames}); + // TODO seems unnecessary if we always pass in all of the names. + //var uniqueNames = facet.data.members.map(function(m){return m.uniqueName;}); + //if (this.filterByLevel != "[Study].[Study]") + // intersectFilters.push({ + // level: this.filterByLevel, + // membersQuery: {level: "[Study].[Study]", members: uniqueNames} + // }); + //else + // intersectFilters.push({level: "[Study].[Study]", members: uniqueNames}); } else { diff --git a/resources/web/study/Finder/data/Studies.js b/resources/web/study/Finder/data/Studies.js index f6ad89d9..a01034e9 100644 --- a/resources/web/study/Finder/data/Studies.js +++ b/resources/web/study/Finder/data/Studies.js @@ -25,15 +25,25 @@ Ext4.define('LABKEY.study.store.Studies', { if (selectedSubset) this.selectedSubset = selectedSubset; var study; + + this.clearFilter(); for (var i = 0; i < this.count(); i++) { study = this.getAt(i); study.set("isSelected", this.selectedStudies[study.get("shortName")] !== undefined); } - this.clearFilter(true); + this.filter([ {property: 'isSelected', value: true}, {property: 'isPublic', value: this.selectedSubset !== "operational"} ]); + }, + + selectAll : function() { + for (var i = 0; i < this.count(); i++) { + study = this.getAt(i); + study.set("isSelected", true); + } } + }); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index adc44808..e285fc01 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -37,6 +37,9 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { this.onFilterSelectionChange(false); }, + onStudySubsetChanged: function() { + this.getFacets().onStudySubsetChanged(); + }, onFilterSelectionChange: function(hasFilters) { console.log("FacetSelection filterSelectionChanged handler"); @@ -44,16 +47,8 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { Ext4.get(Ext4.DomQuery.select('.labkey-clear-all', this.id)[0]).replaceCls('inactive', 'active'); else Ext4.get(Ext4.DomQuery.select('.labkey-clear-all', this.id)[0]).replaceCls('active', 'inactive'); - this.updateSummaryPanel(); // TODO is this necessary? }, - updateSummaryPanel : function() { - var studiesStore = Ext4.getStore("studies"); - this.getFacetSelectionSummary().update({ - studyCount: studiesStore.count(), - participantCount: studiesStore.sum("participantCount") - }); - }, getFacetPanelHeader : function() { if (!this.facetPanelHeader) { @@ -69,11 +64,7 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { if (!this.facetSelectionSummary) { var studiesStore = Ext4.getStore("studies"); this.facetSelectionSummary = Ext4.create("LABKEY.study.panel.SelectionSummary", { - dataModuleName: this.dataModuleName, - data: { - studyCount: studiesStore.count(), - participantCount: studiesStore.sum("participantCount") - } + dataModuleName: this.dataModuleName }); } return this.facetSelectionSummary; diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index eec7dad2..9276963f 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -68,9 +68,9 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { '
          ', '
          ', ' {name}', - ' [clear]', + ' {name:this.displayClearLabel}', '
          ', - '
          ', + ' {name:this.makeFilterOptions}', '
          ', { genId: function(name) { @@ -78,10 +78,32 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { LABKEY.study.panel.FacetsGrid.headerLookup[name] = id; return id; }, - makeFilterOptions: function(name) { + displayClearLabel: function(facetName) { var facetStore = Ext4.getStore("facets"); - var facet = facetStore.get(name); - // TODO render menu or not + var facet = facetStore.getById(facetName); + var html = '[clear]'; + if (LABKEY.study.panel.FacetsGrid.hasFilters(facetName)) { + html = '[clear]'; + } + return html; + }, + makeFilterOptions: function(facetName) { + var facetStore = Ext4.getStore("facets"); + var facet = facetStore.getById(facetName); + var selectedMembers = facet.get("selectedMembers"); + var html = '
          '; + + if (facet.get("isExpanded") && selectedMembers && selectedMembers.length > 1) + { + var pointerClass = (facet.filterOptionsStore.count() < 2) ? "inactive" : "active"; + + html += '' + facet.get("currentFilterCaption") + ''; + if (facet.filterOptionsStore.count() > 1) + html += ' '; + } + html += '
          '; + return html; + //Ext4.get(Ext4.DomQuery.select('.labkey-filter-options', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).setHTML(html); } } ) @@ -89,7 +111,20 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { }, statics: { - headerLookup: {} + headerLookup: {}, + + hasFilters : function(facetName) { + var facetStore = Ext4.getStore('facets'); + if (facetName) + return facetStore.getById(facetName).data.selectedMembers.length > 0; + else { + for (var i = 0; i < facetStore.count(); i++) { + if (facetStore.getAt(i).data.selectedMembers.length > 0) + return true; + } + return false; + } + } }, initComponent: function() { @@ -116,16 +151,27 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { this.facetStore.loadFromCube(); }, + onStudySubsetChanged: function() { + var studiesStore = Ext4.getStore("studies"); + studiesStore.selectAll(); + if (this.facetStore.getById("Study")) + { + this.facetStore.updateCountsAsync(); + } + }, + onSelectionChange: function(selModel, records) { this.facetStore.clearAllSelectedMembers(); if (records.length > 0) this.facetStore.selectMembers(records); + var facet; for (var f = 0; f < this.facetStore.count(); f++) { - this.updateFacetHeader(this.facetStore.getAt(f).data.name, true); + facet = this.facetStore.getAt(f); + //this.updateFacetHeader(facet.data.name, facet.get("isExpanded")); } this.facetStore.updateCountsAsync(); - this.fireEvent("filterSelectionChanged", this.hasFilters()); + this.fireEvent("filterSelectionChanged", LABKEY.study.panel.FacetsGrid.hasFilters()); }, onGroupCollapse: function(view, node, facetName, eOpts) { @@ -151,39 +197,14 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { } }, - updateFacetHeaders: function() { - this.facetStore.each(function(facet) { - this.updateFacetHeaders(facet.get("name"), facet.get("isExpanded")); - }, this); - }, - updateFacetHeader: function(facetName, isExpanded) { - - if (this.hasFilters(facetName)) - { - Ext4.get(Ext4.DomQuery.select('.labkey-clear-filter', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).replaceCls('inactive', 'active'); - } - else - { - Ext4.get(Ext4.DomQuery.select('.labkey-clear-filter', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).replaceCls('active', 'inactive'); - } - this.updateCurrentFacetOption(facetName, isExpanded); var facet = this.facetStore.getById(facetName); if (facet) facet.set("isExpanded", isExpanded); }, updateCurrentFacetOption: function(facetName, isExpanded) { - var facet = this.facetStore.getById(facetName); - var selectedMembers = facet.get("selectedMembers"); - var html = ""; - if (isExpanded && selectedMembers && selectedMembers.length > 1) - { - html += '' + facet.get("currentFilterCaption") + ''; - if (facet.filterOptionsStore.count() > 1) - html += ' '; - } - Ext4.get(Ext4.DomQuery.select('.labkey-filter-options', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).setHTML(html); + this.facetStore.updateCountsAsync(); }, displayFilterChoice : function (facetName, event) @@ -200,13 +221,12 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { return; var filterOptionsMenu = Ext4.create('Ext.menu.Menu', { - //cls: 'basemenu dropdownmenu', showSeparator: false }); filterOptionsMenu.on('click', function(menu, item) { - facet.data.currentFilterType = item.value; - facet.data.currentFilterCaption = item.text; + facet.set("currentFilterType", item.value); + facet.set("currentFilterCaption", item.text); this.updateCurrentFacetOption(facetName, true); }, this @@ -225,8 +245,8 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { for (var i = 0; i < this.facetStore.count(); i++) { var name = this.facetStore.getAt(i).get("name"); - if (name == "Study") - continue; + //if (name == "Study") + // continue; this.clearFilter(name); } if (updateCounts) @@ -245,18 +265,6 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { } this.updateFacetHeader(facetName, true); - this.fireEvent("filterSelectionCleared", this.hasFilters(facetName)); - }, - - hasFilters : function(facetName) { - if (facetName) - return this.facetStore.getById(facetName).data.selectedMembers.length > 0; - else { - for (var i = 0; i < this.facetStore.count(); i++) { - if (this.facetStore.getAt(i).data.selectedMembers.length > 0) - return true; - } - return false; - } + this.fireEvent("filterSelectionCleared", LABKEY.study.panel.FacetsGrid.hasFilters(facetName)); } }); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index fbe241da..a26fcb46 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -69,7 +69,7 @@ Ext4.define('LABKEY.study.panel.Finder', { }, onStudySubsetChanged : function(value) { - this.getFacetsPanel().updateSummaryPanel(); + this.getFacetsPanel().onStudySubsetChanged(); }, onFilterSelectionChange : function(){ From 329f1f7f2bb97da12d482ac0c3d4b850c47ea067 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 25 Dec 2015 18:20:20 -0800 Subject: [PATCH 037/587] Spec 24959 : Implement Data Finder for ITN - add more data in publication listings; count manuscripts and abstracts differently; start integration of search --- resources/web/study/Finder/data/StudyCard.js | 6 +- resources/web/study/Finder/dataFinder.css | 55 ++++--- resources/web/study/Finder/panel/Finder.js | 134 ++++++++---------- .../web/study/Finder/panel/StudyCards.js | 24 +++- .../trialshare/TrialShareController.java | 69 ++++++--- src/org/labkey/trialshare/data/StudyBean.java | 90 +++++++++--- .../trialshare/data/StudyPublicationBean.java | 129 +++++++++++++++-- .../labkey/trialshare/view/studyDetail.jsp | 100 +++++++------ 8 files changed, 406 insertions(+), 201 deletions(-) diff --git a/resources/web/study/Finder/data/StudyCard.js b/resources/web/study/Finder/data/StudyCard.js index 1166f770..9b0e8ef0 100644 --- a/resources/web/study/Finder/data/StudyCard.js +++ b/resources/web/study/Finder/data/StudyCard.js @@ -7,13 +7,13 @@ Ext4.define('LABKEY.study.data.StudyCard', { {name: 'studyId'}, {name: 'title'}, {name: 'url'}, + {name: 'externalUrl'}, + {name: 'externalUrlDescription'}, {name: 'shortName'}, {name: 'iconUrl'}, {name: 'investigator'}, - {name: 'hasManuscript', type: 'boolean'}, - {name: 'isLoaded', type: 'boolean'}, // TODO isHighlighted - {name: 'availability'}, {name: 'isPublic', type: 'boolean'}, + {name: 'abstractCount', type: 'int'}, {name: 'manuscriptCount', type: 'int'}, {name: 'participantCount', type: 'int'}, {name: 'isSelected', type: 'boolean'} diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 79745d0b..c8940323 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -77,12 +77,19 @@ TABLE.labkey-data-finder clear:both; } -.labkey-text-link.labkey-study-card-manuscript +.labkey-study-card-publications { - float:right; margin-top: 10px; } +.labkey-text-link.labkey-study-card-pub-count +{ + float:right; + margin-bottom: 1px; + padding-top : 2px; +} + + DIV.labkey-study-card { background-color:#F8F8F8; @@ -96,25 +103,11 @@ DIV.labkey-study-card overflow-y:hidden; } -DIV.manuscript-highlight, -DIV.loaded, -SPAN.loaded +DIV.labkey-publication-highlight { background-color:rgba(81, 158, 218, 0.2); } -/*TD.labkey-study-panel,*/ -/*TD.study-panel*/ -/*{*/ - /*vertical-align: top;*/ -/*}*/ - -/*TD.study-panel > DIV*/ -/*{*/ - /*height:100%;*/ - /*overflow-y:scroll;*/ -/*}*/ - TABLE.labkey-study-search { margin: 5px 0px 0px 5px; @@ -139,11 +132,6 @@ INPUT.search-box width:200pt; } -/*DIV.studyfinder-header*/ -/*{*/ - /*float:left;*/ - /*padding-right:10pt;*/ -/*}*/ DIV.labkey-search-message, DIV.search-message @@ -673,8 +661,29 @@ div.labkey-study-details h3 { padding: 3px; } -.labkey-study-manuscript-header { +.labkey-study-publication-header { color: #cc541f; font-size: 120%; font-variant: small-caps; +} + +.labkey-publication-journal { + font-size:80%; + text-decoration:underline; +} + +.labkey-publication-year { + font-size:80%; +} + +.labkey-publication-title { + font-weight: bold; +} + +.labkey-publication-citation { + +} + +.labkey-publication-author { + font-style: italic; } \ No newline at end of file diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index a26fcb46..fee5bb9a 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -21,6 +21,8 @@ Ext4.define('LABKEY.study.panel.Finder', { autoScroll : true, + searchTerms : '', + initComponent : function() { this.items = [ @@ -36,8 +38,8 @@ Ext4.define('LABKEY.study.panel.Finder', { this.on({ filterSelectionChanged: this.onFilterSelectionChange, - studySubsetChanged: this.onStudySubsetChanged - //searchTermsChanged: this.onSearchTermsChanged + studySubsetChanged: this.onStudySubsetChanged, + searchTermsChanged: this.onSearchTermsChanged } ); }, @@ -77,80 +79,60 @@ Ext4.define('LABKEY.study.panel.Finder', { this.getStudiesPanel().onFilterSelectionChanged(); }, -// $scope.onStudySubsetChanged = function () -//{ -// if ($scope.localStorageService.isSupported) -// $scope.localStorageService.add("studySubset", $scope.studySubset); -// // if there are search terms, just act as if the search terms have changed -// if ($scope.searchTerms) -// { -// $scope.onSearchTermsChanged(); -// } -// else -// { -// $scope.clearStudyFilter(); -// } -//}; -// -//$scope.doSearchTermsChanged_promise = null; -// -//$scope.doSearchTermsChanged = function () -//{ -// if ($scope.doSearchTermsChanged_promise) -// { -// // UNDONE:cancel doesn't seem to really be supported for $http -// //$scope.http.cancel($scope.doSearchTermsChanged_promise); -// } -// -// if (!$scope.searchTerms) -// { -// $scope.searchMessage = ""; -// $scope.clearStudyFilter(); -// return; -// } -// -// var scope = $scope; -// var url = LABKEY.ActionURL.buildURL("search", "json", "/home/", { -// "category": "immport_study", -// "scope": "Folder", -// "q": $scope.searchTerms -// }); -// var promise = $scope.http.get(url); -// $scope.doSearchTermsChanged_promise = promise; -// promise.success(function (data) -// { -// // NOOP if we're not current (poor man's cancel) -// if (promise != $scope.doSearchTermsChanged_promise) -// return; -// $scope.doSearchTermsChanged_promise = null; -// var hits = data.hits; -// var searchStudies = []; -// var found = {}; -// for (var h = 0; h < hits.length; h++) -// { -// var id = hits[h].id; -// var accession = id.substring(id.lastIndexOf(':') + 1); -// if (found[accession]) -// continue; -// found[accession] = true; -// searchStudies.push("[Study].[" + accession + "]"); -// } -// if (!searchStudies.length) -// { -// $scope.setStudyFilter(searchStudies); -// $scope.searchMessage = 'No studies match your search criteria'; -// } -// else -// { -// $scope.searchMessage = ''; -// // intersect with study subset list -// var result = $scope.intersect(searchStudies, $scope.getStudySubsetList()); -// if (!result.length) -// $scope.searchMessage = 'No studies match your search criteria'; -// $scope.setStudyFilter(result); -// } -// }); -//}; + onSearchTermsChanged: function(searchTerms) { + + if (!searchTerms) + { + this.searchMessage = ""; + this.clearStudyFilter(); + return; + } + + var url = LABKEY.ActionURL.buildURL("search", "json", "/home/", { + "category": "List", + "scope": "Folder", + "q": searchTerms + }); + Ext4.Ajax.request({ + url: url, + success: function (response) + { + //// NOOP if we're not current (poor man's cancel) + //if (promise != $scope.doSearchTermsChanged_promise) + // return; + //$scope.doSearchTermsChanged_promise = null; + var data = Ext4.decode(response.responseText); + var hits = data.hits; + var searchStudies = []; + var found = {}; + for (var h = 0; h < hits.length; h++) + { + var id = hits[h].id; + var accession = id.substring(id.lastIndexOf(':') + 1); + if (found[accession]) + continue; + found[accession] = true; + searchStudies.push("[Study].[" + accession + "]"); + } + if (!searchStudies.length) + { + console.log("No studies match your search criteria"); + //$scope.setStudyFilter(searchStudies); + //$scope.searchMessage = 'No studies match your search criteria'; + } + else + { + console.log("found " + searchStudies.length + " studies matching terms " + searchTerms); + //$scope.searchMessage = ''; + //// intersect with study subset list + //var result = $scope.intersect(searchStudies, $scope.getStudySubsetList()); + //if (!result.length) + // $scope.searchMessage = 'No studies match your search criteria'; + //$scope.setStudyFilter(result); + } + } + }); + }, _initResize : function() { var resize = function(w, h) { diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index c5b9c5ca..dbb44337 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -23,8 +23,8 @@ Ext4.define("LABKEY.study.panel.StudyCards", { tpl: new Ext4.XTemplate( '
          ', ' ', - ' ', - '
          ', + ' ', + '
          ', ' ', '
          ', ' ', @@ -42,7 +42,10 @@ Ext4.define("LABKEY.study.panel.StudyCards", { ' ', '
          ', '
          {title}
          ', + '
          ', ' {manuscriptCount:this.displayManuscriptCount}', + ' {abstractCount:this.displayAbstractCount}', + '
          ', '
          ', '
          ', '
          ', @@ -51,7 +54,7 @@ Ext4.define("LABKEY.study.panel.StudyCards", { if (count == 0) return ""; else { - var text = ''; + var text = ''; if (count == 1) text += "1 manuscript available"; else @@ -59,6 +62,19 @@ Ext4.define("LABKEY.study.panel.StudyCards", { text += ''; return text; } + }, + displayAbstractCount : function(count) { + if (count == 0) + return ""; + else { + var text = ''; + if (count == 1) + text += "1 abstract available"; + else + text += count + " abstracts available"; + text += ''; + return text; + } } } ), @@ -70,7 +86,7 @@ Ext4.define("LABKEY.study.panel.StudyCards", { console.log("Show study popup for record " , record); this.showStudyDetailPopup(record.get("studyId")); } - else if (event.target.className.includes("labkey-study-card-manuscript")) + else if (event.target.className.includes("labkey-study-card-pub-count")) { console.log("Show manuscript popup for record " , record); this.showStudyManuscriptsPopup(record.get("studyId")); diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 1a775eb2..2a941018 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -31,10 +31,10 @@ import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.util.PageFlowUtil; +import org.labkey.api.util.Pair; import org.labkey.api.view.HtmlView; import org.labkey.api.view.JspView; import org.labkey.api.view.NavTree; -import org.labkey.api.view.NotFoundException; import org.labkey.api.view.VBox; import org.labkey.trialshare.data.FacetFilter; import org.labkey.trialshare.data.StudyBean; @@ -110,18 +110,28 @@ public Object execute(StudiesForm form, BindException errors) throws Exception QuerySchema coreSchema = DefaultSchema.get(getUser(), getContainer()).getSchema("core"); studies = (new TableSelector(coreSchema.getSchema("lists").getTable("studyProperties"))).getArrayList(StudyBean.class); List publications = (new TableSelector(coreSchema.getSchema("lists").getTable("studyManuscripts")).getArrayList(StudyPublicationBean.class)); - Map pubCounts = new HashMap<>(); + Map> pubCounts = new HashMap<>(); for (StudyPublicationBean pub : publications) { if (pubCounts.get(pub.getStudyId()) == null) - pubCounts.put(pub.getStudyId(), 0); - int count = pubCounts.get(pub.getStudyId()); - pubCounts.put(pub.getStudyId(), count + 1); + pubCounts.put(pub.getStudyId(), new Pair<>(0,0)); + Pair countPair = pubCounts.get(pub.getStudyId()); + if (pub.hasPubmedLink()) + countPair.first += 1; + else + countPair.second += 1; + } for (StudyBean study : studies) { if (pubCounts.get(study.getStudyId()) == null) + { study.setManuscriptCount(0); + study.setAbstractCount(0); + } else - study.setManuscriptCount(pubCounts.get(study.getStudyId())); + { + study.setManuscriptCount(pubCounts.get(study.getStudyId()).first); + study.setAbstractCount(pubCounts.get(study.getStudyId()).second); + } } return success(studies); @@ -145,8 +155,7 @@ private StudyBean getStudy(String studyId) study.setDescription("This is a prospective multicenter, open-label, single-arm trial in which 20 pediatric recipients of parental living-donor liver allografts will undergo gradual withdrawal of immunosuppression with the goal of complete withdrawal. Patients on stable immunosuppression regimens with good organ function and no evidence of acute or chronic rejection or other forms of allograft dysfunction will be enrolled. Participants will undergo gradual withdrawal of immunosuppression and will be followed for a minimum of 4 years after completion of immunosuppression withdrawal. Immunologic and genetic profiles will be collected at multiple time points and compared between tolerant and nontolerant participants."); study.setIsLoaded(true); study.setAvailability("operational"); - study.setHasManuscript(true); - study.setUrl("https://www.itntrialshare.org/project/Studies/ITN029STOPR/Study%20Data/begin.view"); + study.setExternalUrl("https://www.itntrialshare.org/project/Studies/ITN029STOPR/Study%20Data/begin.view"); } else if (studyId.equals("ITN021AI")) { @@ -156,7 +165,6 @@ else if (studyId.equals("ITN021AI")) study.setTitle("Rituximab for ANCA-Associated Vasculitis"); study.setIsLoaded(false); study.setAvailability("public"); - study.setHasManuscript(false); study.setDescription("Current conventional therapies for ANCA-associated vasculitis (AAV) are associated with high incidences of treatment failure, disease relapse, substantial toxicity, and patient morbidity and mortality. Rituximab is a monoclonal antibody used to treat non-Hodgkin's lymphoma. This study will evaluate the efficacy of rituximab with glucocorticoids in inducing disease remission in adults with severe forms of AAV (WG and MPA).\n" + "\n" + "The study consists of two phases: a 6-month remission induction phase, followed by a 12-month remission maintenance phase. All participants will receive at least 1 g of pulse IV methylprednisolone or a dose-equivalent of another glucocorticoid preparation. Depending on the participant's condition, he or she may receive up to 3 days of IV methylprednisolone for a total of 3 g of methylprednisolone (or a dose-equivalent). During the remission induction phase, all participants will receive oral prednisone daily (1 mg/kg/day, not to exceed 80 mg/day). Prednisone tapering will be completed by the Month 6 study visit.\n" + @@ -175,9 +183,8 @@ else if (studyId.equals("ITN033AI")) study.setTitle("High Dose Immunosuppression and Autologous Transplantation for Multiple Sclerosis"); study.setIsLoaded(false); study.setAvailability("operational"); - study.setHasManuscript(false); study.setDescription("This study is a prospective, multicenter Phase II clinical trial evaluating high-dose immunosuppressive therapy (HDIT) using Carmustine, Etoposide, Cytarabine, and Melphalan (BEAM) plus Thymoglobulin (rATG) with autologous transplantation of CD34+ HCT for the treatment of poor-risk MS. The active treatment period will be approximately 3 months from the time of initiation of mobilization to the day of discharge after transplant. Subjects will be followed up to 60 months (5 years) after transplant. Total study duration will be 60 months after the last subject is transplanted."); - study.setUrl("https://www.itntrialshare.org/project/Studies/ITN033AIOPR/Study%20Data/begin.view"); + study.setExternalUrl("https://www.itntrialshare.org/project/Studies/ITN033AIOPR/Study%20Data/begin.view"); } return study; } @@ -241,11 +248,35 @@ public Object execute(Object o, BindException errors) throws Exception } } + public static class StudyDetailBean + { + private StudyBean _study; + private DetailType _detailType; + + public DetailType getDetailType() + { + return _detailType; + } + + public void setDetailType(DetailType detailType) + { + _detailType = detailType; + } + + public StudyBean getStudy() + { + return _study; + } + + public void setStudy(StudyBean study) + { + _study = study; + } + } @RequiresPermission(ReadPermission.class) public class StudyDetailAction extends SimpleViewAction { - StudyBean _study; String _studyId; @Override @@ -259,13 +290,15 @@ public void validate(StudyIdForm form, BindException errors) @Override public ModelAndView getView(StudyIdForm form, BindException errors) throws Exception { + QuerySchema coreSchema = DefaultSchema.get(getUser(), getContainer()).getSchema("core"); QuerySchema listSchema = coreSchema.getSchema("lists"); - _study = (new TableSelector(listSchema.getTable("studyProperties"))).getObject(_studyId, StudyBean.class); + StudyBean study = (new TableSelector(listSchema.getTable("studyProperties"))).getObject(_studyId, StudyBean.class); + SimpleFilter filter = new SimpleFilter(); filter.addCondition(FieldKey.fromParts("studyId"), _studyId); - _study.setPublications((new TableSelector(listSchema.getTable("studyManuscripts"), filter, null)).getArrayList(StudyPublicationBean.class)); + study.setPublications((new TableSelector(listSchema.getTable("studyManuscripts"), filter, null)).getArrayList(StudyPublicationBean.class)); // _study.study = (new TableSelector(DbSchema.get("immport").getTable("study"))).getObject(studyId, StudyBean.class); // if (null == _study.study) // throw new NotFoundException("study not found: " + form.getStudy()); @@ -280,10 +313,10 @@ public ModelAndView getView(StudyIdForm form, BindException errors) throws Excep { v.addView(new HtmlView(PageFlowUtil.textLink("back", form.getReturnActionURL()) + "
          ")); } - if (form.getDetailType() == DetailType.study) - v.addView(new JspView("/org/labkey/trialshare/view/studyDetail.jsp", _study)); - else if (form.getDetailType() == DetailType.publications) - v.addView(new JspView("/org/labkey/trialshare/view/studyPublications.jsp", _study)); + StudyDetailBean bean = new StudyDetailBean(); + bean.setStudy(study); + bean.setDetailType(form.getDetailType()); + v.addView(new JspView("/org/labkey/trialshare/view/studyDetail.jsp", bean)); return v; } diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index ba1abf6e..90e804d0 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -1,6 +1,20 @@ package org.labkey.trialshare.data; +import org.apache.commons.lang3.StringUtils; +import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerFilter; +import org.labkey.api.data.ContainerFilterable; +import org.labkey.api.data.ContainerManager; +import org.labkey.api.data.TableInfo; +import org.labkey.api.data.TableSelector; +import org.labkey.api.query.DefaultSchema; +import org.labkey.api.query.QuerySchema; +import org.labkey.api.security.User; +import org.labkey.api.view.ActionURL; + +import java.util.Collection; import java.util.List; +import java.util.Map; /** * Created by susanh on 12/7/15. @@ -10,9 +24,9 @@ public class StudyBean private String shortName; private String studyId; private String title; - private Boolean hasManuscript; // TODO generalize? private String investigator; - private String url; + private String externalUrl; + private String externalUrlDescription; private String iconUrl; private Boolean isLoaded; private String description; @@ -23,9 +37,10 @@ public class StudyBean private Integer participantCount; private Boolean isSelected = true; - private List personnel; // TODO remove? + private List personnel; private List publications; private Integer manuscriptCount; + private Integer abstractCount; public String getStudyId() @@ -58,14 +73,14 @@ public void setTitle(String title) this.title = title; } - public String getUrl() + public String getExternalUrl() { - return url; + return externalUrl; } - public void setUrl(String url) + public void setExternalUrl(String externalUrl) { - this.url = url; + this.externalUrl = externalUrl; } public Boolean getIsLoaded() @@ -128,16 +143,6 @@ public void setStudyIdPrefix(String studyIdPrefix) this.studyIdPrefix = studyIdPrefix; } - public Boolean getHasManuscript() - { - return hasManuscript; - } - - public void setHasManuscript(Boolean hasManuscript) - { - this.hasManuscript = hasManuscript; - } - public String getShortName() { return shortName; @@ -178,6 +183,16 @@ public void setManuscriptCount(Integer manuscriptCount) this.manuscriptCount = manuscriptCount; } + public Integer getAbstractCount() + { + return abstractCount; + } + + public void setAbstractCount(Integer abstractCount) + { + this.abstractCount = abstractCount; + } + public Boolean getIsPublic() { return isPublic; @@ -207,5 +222,46 @@ public void setIsSelected(Boolean selected) { isSelected = selected; } + + public ActionURL getUrl(Container c, User user) + { + if (!c.isRoot()) + { + String comma = "\n"; + Container p = c.getProject(); + QuerySchema s = DefaultSchema.get(user, p).getSchema("study"); + TableInfo sp = s.getTable("StudyProperties"); + if (sp.supportsContainerFilter()) + { + ContainerFilter cf = new ContainerFilter.AllInProject(user); + ((ContainerFilterable) sp).setContainerFilter(cf); + } + Collection> maps = new TableSelector(sp).getMapCollection(); + for (Map map : maps) + { + Container studyContainer = ContainerManager.getForId((String) map.get("container")); + String studyAccession = (String)map.get("study_accession"); + // TODO study properties does not have the studyId in it... + String name = (String)map.get("Label"); + if (null == studyAccession && getStudyIdPrefix() != null && name.startsWith(getStudyIdPrefix())) + studyAccession = name; + if (null != studyContainer && StringUtils.equalsIgnoreCase(getStudyId(), studyAccession)) + { + return studyContainer.getStartURL(user); + } + } + } + return null; + } + + public String getExternalUrlDescription() + { + return externalUrlDescription; + } + + public void setExternalUrlDescription(String externalUrlDescription) + { + this.externalUrlDescription = externalUrlDescription; + } } diff --git a/src/org/labkey/trialshare/data/StudyPublicationBean.java b/src/org/labkey/trialshare/data/StudyPublicationBean.java index e85141e0..26d7ad38 100644 --- a/src/org/labkey/trialshare/data/StudyPublicationBean.java +++ b/src/org/labkey/trialshare/data/StudyPublicationBean.java @@ -15,17 +15,24 @@ */ package org.labkey.trialshare.data; +import org.labkey.api.util.Pair; + public class StudyPublicationBean { - String studyId; - String url; - String pubmedId; - String authors; - String issue; - String journal; - String pages; - String title; - String year; + // common fields + private String studyId; + private String pubmedId; + private String author; + private String issue; + private String journal; + private String pages; + private String title; + private String year; + // the first item in the pair is the link; the second is the description (link text) + private Pair[] urls = new Pair[5]; + + // TrialShare fields + private String citation; public String getStudyId() { @@ -47,14 +54,14 @@ public void setPubmedId(String pubmedId) this.pubmedId = pubmedId; } - public String getAuthors() + public String getAuthor() { - return authors; + return author; } - public void setAuthors(String authors) + public void setAuthor(String author) { - this.authors = authors; + this.author = author; } public String getIssue() @@ -107,4 +114,100 @@ public void setYear(String year) this.year = year; } + public String getCitation() + { + return citation; + } + + public void setCitation(String citation) + { + this.citation = citation; + } + + public void setDescription1(String description1) + { + setUrlText(0, description1); + } + + public Pair[] getUrls() + { + return urls; + } + + public void setUrls(Pair[] urls) + { + this.urls = urls; + } + + private void setUrlText(int index, String description) + { + if (description.equals(" ")) + description = ""; + Pair urlData = urls[index]; + if (urlData == null) + { + urlData = new Pair<>(null, description); + urls[index] = urlData; + } + else + { + urlData.second = description; + } + } + + private void setUrlLink(int index, String link) + { + Pair urlData = urls[index]; + if (urlData == null) + { + urlData = new Pair<>(link, null); + urls[index] = urlData; + } + else + { + urlData.first = link; + } + } + + public void setDescription2(String description2) + { + setUrlText(1, description2); + } + + public void setDescription3(String description3) + { + setUrlText(2, description3); + } + + + public void setLink1(String link1) + { + setUrlLink(0, link1); + } + + + public void setLink2(String link2) + { + setUrlLink(1, link2); + } + + + public void setLink3(String link3) + { + setUrlLink(2, link3); + } + + public boolean hasPubmedLink() + { + return getPubmedLink() != null; + } + + public String getPubmedLink() + { + for (Pair urlData : urls) { + if (urlData != null && urlData.first != null && urlData.first.contains("pubmed")) + return urlData.first; + } + return null; + } } diff --git a/src/org/labkey/trialshare/view/studyDetail.jsp b/src/org/labkey/trialshare/view/studyDetail.jsp index 1ef255f3..6cde719e 100644 --- a/src/org/labkey/trialshare/view/studyDetail.jsp +++ b/src/org/labkey/trialshare/view/studyDetail.jsp @@ -17,78 +17,48 @@ %> <%@ page import="org.apache.commons.lang3.StringUtils" %> <%@ page import="org.labkey.api.data.Container" %> -<%@ page import="org.labkey.api.data.ContainerFilter" %> -<%@ page import="org.labkey.api.data.ContainerFilterable" %> -<%@ page import="org.labkey.api.data.ContainerManager" %> -<%@ page import="org.labkey.api.data.TableInfo" %> -<%@ page import="org.labkey.api.data.TableSelector" %> -<%@ page import="org.labkey.api.query.DefaultSchema" %> -<%@ page import="org.labkey.api.query.QuerySchema" %> +<%@ page import="org.labkey.api.util.Pair" %> <%@ page import="org.labkey.api.view.ActionURL" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.api.view.ViewContext" %> -<%@ page import="java.util.Collection" %> -<%@ page import="java.util.HashMap" %> -<%@ page import="java.util.Map" %> <%@ page import="org.labkey.api.view.template.ClientDependency" %> -<%@ page import="java.util.LinkedHashSet" %> +<%@ page import="org.labkey.trialshare.TrialShareController" %> <%@ page import="org.labkey.trialshare.data.StudyBean" %> <%@ page import="org.labkey.trialshare.data.StudyPersonnelBean" %> <%@ page import="org.labkey.trialshare.data.StudyPublicationBean" %> +<%@ page import="java.net.URL" %> +<%@ page import="java.util.HashMap" %> +<%@ page import="java.util.LinkedHashSet" %> +<%@ page import="java.util.Map" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <%! public LinkedHashSet getClientDependencies() { LinkedHashSet resources = new LinkedHashSet<>(); - resources.add(ClientDependency.fromPath("dataFinder.css")); - resources.add(ClientDependency.fromPath("trialShare.css")); + resources.add(ClientDependency.fromPath("study/Finder/dataFinder.css")); + resources.add(ClientDependency.fromPath("study/Finder/trialShare.css")); return resources; } %> <% - JspView me = (JspView) HttpView.currentView(); + JspView me = (JspView) HttpView.currentView(); ViewContext context = HttpView.currentContext(); Container c = context.getContainer(); - StudyBean study = me.getModelBean(); + TrialShareController.StudyDetailBean studyDetail = me.getModelBean(); + StudyBean study = studyDetail.getStudy(); String descriptionHTML; if (!StringUtils.isEmpty(study.getDescription())) descriptionHTML= study.getDescription(); else descriptionHTML = h(study.getBriefDescription()); - ActionURL studyUrl = null; -// if (!c.isRoot()) -// { -// String comma = "\n"; -// Container p = c.getProject(); -// QuerySchema s = DefaultSchema.get(context.getUser(), p).getSchema("study"); -// TableInfo sp = s.getTable("StudyProperties"); -// if (sp.supportsContainerFilter()) -// { -// ContainerFilter cf = new ContainerFilter.AllInProject(context.getUser()); -// ((ContainerFilterable) sp).setContainerFilter(cf); -// } -// Collection> maps = new TableSelector(sp).getMapCollection(); -// for (Map map : maps) -// { -// Container studyContainer = ContainerManager.getForId((String) map.get("container")); -// String studyAccession = (String)map.get("study_accession"); -// String name = (String)map.get("Label"); -// if (null == studyAccession && study.getStudyIdPrefix() != null && name.startsWith(study.getStudyIdPrefix())) -// studyAccession = name; -// if (null != studyContainer && StringUtils.equalsIgnoreCase(study.getStudyId(), studyAccession)) -// { -// studyUrl = studyContainer.getStartURL(context.getUser()); -// break; -// } -// } -// } + ActionURL studyUrl = study.getUrl(c, context.getUser()); - String publicationsTitle = "Manuscripts and Abstracts"; + String publicationsSectionTitle = "Manuscripts and Abstracts"; Map linkProps = new HashMap<>(); linkProps.put("target", "_blank"); %> @@ -97,9 +67,10 @@

          <% if (null!=studyUrl) {%><%}%><%=h(study.getStudyId())%><% if (null!=studyUrl) {%><%}%>

          <% if (null!=study.getShortName()) {%><%}%><%=h(study.getShortName())%><% if (null!=study.getShortName()) {%><%}%>

          -<% if (null != study.getIconUrl()) {%><%}%> +<% if (null != study.getIconUrl()) {%><%}%>

          <%=h(study.getTitle())%>

          <% + if (null != study.getPersonnel()) { for (StudyPersonnelBean p : study.getPersonnel()) @@ -113,21 +84,44 @@ } } } + %><% + if (studyDetail.getDetailType() == TrialShareController.DetailType.study) + { %>
          <%=text(descriptionHTML)%>
          + <% + } + %>
          <% if (null != study.getPublications() && study.getPublications().size() > 0) { - %><%=h(publicationsTitle)%><% + %><%=h(publicationsSectionTitle)%><% for (StudyPublicationBean pub : study.getPublications()) { if (pub.getTitle() != null) { - %>

          <%=h(pub.getJournal())%> <%=h(pub.getYear())%>
          <% - %><%=h(pub.getTitle())%><% + %>

          <%=h(pub.getJournal())%> <%=h(pub.getYear())%>
          <% + %><%=h(pub.getTitle())%><% + if (!StringUtils.isEmpty(pub.getCitation())) + { + %>
          <%=h(pub.getCitation())%><% + } + %><% + if (!StringUtils.isEmpty(pub.getAuthor())) + { + %>
          <%=h(pub.getAuthor())%><% + } + %><% if (!StringUtils.isEmpty(pub.getPubmedId())) { %>
          <%=textLink("PubMed","http://www.ncbi.nlm.nih.gov/pubmed/?term=" + pub.getPubmedId(), null, null, linkProps)%><% } + for (Pair urlData : pub.getUrls()) + { + if (urlData != null && !StringUtils.isEmpty(urlData.second)) + { + %>
          <%=textLink(h(urlData.second), urlData.first, null, null, linkProps)%><% + } + } %>

          <% } } @@ -138,6 +132,18 @@ <% if (null != studyUrl) { %> <%= textLink("View study " + study.getStudyId(), studyUrl.toString(), null, null, linkProps)%>
          <% } %> + <% + if (null != study.getExternalUrl()) + { + String text = study.getExternalUrlDescription(); + if (StringUtils.isEmpty(text)) + { + URL url = new URL(study.getExternalUrl()); + text = "View study at " + url.getHost(); + } + %> + <%= textLink(text, study.getExternalUrl(), null, null, linkProps)%>
          + <% } %>
          From fbeaee01f7b13381a7adb871c10db4f9d0aed005 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 27 Dec 2015 14:05:37 -0800 Subject: [PATCH 038/587] Spec 24959 : Implement Data Finder for ITN - add assay dimension; get description and study url from study.StudyProperties tables; add default for study subset and don't show operational option when user is guest. --- resources/olap/StudyCube.xml | 9 ++ resources/web/study/Finder/data/Studies.js | 7 +- .../web/study/Finder/data/StudySubsets.js | 32 +++++++ .../web/study/Finder/panel/FacetSelection.js | 9 +- .../study/Finder/panel/StudyPanelHeader.js | 22 +---- .../trialshare/TrialShareController.java | 79 ++++----------- src/org/labkey/trialshare/data/StudyBean.java | 96 ++++++++++++++----- src/org/labkey/trialshare/view/dataFinder.jsp | 3 +- .../labkey/trialshare/view/studyDetail.jsp | 10 +- 9 files changed, 145 insertions(+), 122 deletions(-) create mode 100644 resources/web/study/Finder/data/StudySubsets.js diff --git a/resources/olap/StudyCube.xml b/resources/olap/StudyCube.xml index ad387cbb..779b8f4f 100644 --- a/resources/olap/StudyCube.xml +++ b/resources/olap/StudyCube.xml @@ -37,6 +37,15 @@ + +
          + + + + + + + diff --git a/resources/web/study/Finder/data/Studies.js b/resources/web/study/Finder/data/Studies.js index a01034e9..dfb0dd5d 100644 --- a/resources/web/study/Finder/data/Studies.js +++ b/resources/web/study/Finder/data/Studies.js @@ -2,7 +2,7 @@ Ext4.define('LABKEY.study.store.Studies', { extend: 'Ext.data.Store', storeId: 'studies', model: 'LABKEY.study.data.StudyCard', - autoLoad: true, + autoLoad: false, dataModuleName: this.dataModuleName, selectedStudies : {}, selectedSubset : 'public', @@ -43,7 +43,10 @@ Ext4.define('LABKEY.study.store.Studies', { study = this.getAt(i); study.set("isSelected", true); } - } + }, + selectSubset : function(subsetId, subset) { + + } }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/StudySubsets.js b/resources/web/study/Finder/data/StudySubsets.js new file mode 100644 index 00000000..8fe00d8d --- /dev/null +++ b/resources/web/study/Finder/data/StudySubsets.js @@ -0,0 +1,32 @@ +Ext4.define('LABKEY.study.store.StudySubsets', { + extend: "Ext.data.Store", + autoLoad: true, + id: 'StudySubsetStore', + model: 'LABKEY.study.data.StudySubset', + isLoaded: false, + defaultValue: null, + proxy: { + type: 'ajax', + url: LABKEY.ActionURL.buildURL('trialshare', "studySubsets.api", LABKEY.containerPath), + reader: { + type: 'json', + root: 'data' + } + }, + listeners: { + 'load' : { + fn : function(store, records, options) { + store.isLoaded = true; + for (var i = 0; i < records.length; i++) + { + if (records[i].data.default) + { + store.defaultValue = records[i]; + break; + } + } + }, + scope: this + } + } +}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index e285fc01..c53ec4a2 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -24,8 +24,7 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { this.on({ filterSelectionChanged: this.onFilterSelectionChange, clearAllFilters: this.onClearAllFilters - } - ); + }); }, onCubeReady: function(mdx) { @@ -52,7 +51,6 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { getFacetPanelHeader : function() { if (!this.facetPanelHeader) { - //this.facetPanelHeader = Ext4.create("LABKEY.study.panel.FacetPanelHeaderTpl", { this.facetPanelHeader = Ext4.create("LABKEY.study.panel.FacetPanelHeader", { dataModuleName: this.dataModuleName }); @@ -62,7 +60,6 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { getFacetSelectionSummary: function() { if (!this.facetSelectionSummary) { - var studiesStore = Ext4.getStore("studies"); this.facetSelectionSummary = Ext4.create("LABKEY.study.panel.SelectionSummary", { dataModuleName: this.dataModuleName }); @@ -72,14 +69,10 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { getFacets : function() { if (!this.facets) { - //this.facets = Ext4.create("LABKEY.study.panel.Facets", { - // dataModuleName: this.dataModuleName - //}); this.facets = Ext4.create("LABKEY.study.panel.FacetsGrid", { dataModuleName: this.dataModuleName }); } - FG = this.facets; return this.facets; } diff --git a/resources/web/study/Finder/panel/StudyPanelHeader.js b/resources/web/study/Finder/panel/StudyPanelHeader.js index ecd26856..d812dc14 100644 --- a/resources/web/study/Finder/panel/StudyPanelHeader.js +++ b/resources/web/study/Finder/panel/StudyPanelHeader.js @@ -15,19 +15,7 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { "searchTermsChanged" ], - studySubsets : Ext4.create("Ext.data.Store", { - autoLoad: true, - id: 'StudySubsetStore', - model: 'LABKEY.study.data.StudySubset', - proxy: { - type: 'ajax', - url: LABKEY.ActionURL.buildURL('trialshare', "studySubsets.api", LABKEY.containerPath), - reader: { - type: 'json', - root: 'data' - } - } - }), + studySubsets : Ext4.create('LABKEY.study.store.StudySubsets'), initComponent: function() { this.items = []; @@ -63,14 +51,14 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { }, getStudySubsetMenu: function() { - if (!this.studySubsetMenu && this.studySubsets.count() > 0) { + if (!this.studySubsetMenu) { this.studySubsetMenu = Ext4.create('Ext.form.ComboBox', { store: this.studySubsets, name : 'studySubsetSelect', queryMode: 'local', valueField: 'id', displayField: 'name', - value: this.studySubsets.getAt(0), + value: this.studySubsets.defaultValue, cls: 'labkey-study-search', multiSelect: false, listeners: { @@ -79,7 +67,8 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { this.onStudySubsetChanged(newValue[0]) }, 'render': function(eOpts) { - this.onStudySubsetChanged(this.studySubsets.getAt(0)) + if (this.studySubsets.defaultValue) + this.onStudySubsetChanged(this.studySubsets.defaultValue) } } }) @@ -107,7 +96,6 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { }, onStudySubsetChanged: function(value) { - console.log("Subset changed to ", value.data.id); this.fireEvent("studySubsetChanged", value.data.id); }, diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 2a941018..13b6fd5a 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -22,7 +22,6 @@ import org.labkey.api.action.SimpleViewAction; import org.labkey.api.action.SpringActionController; import org.labkey.api.data.SimpleFilter; -import org.labkey.api.data.SqlExecutor; import org.labkey.api.data.TableSelector; import org.labkey.api.gwt.client.util.StringUtils; import org.labkey.api.query.DefaultSchema; @@ -103,12 +102,8 @@ public class StudiesAction extends ApiAction @Override public Object execute(StudiesForm form, BindException errors) throws Exception { - List studies = new ArrayList(); -// studies.add(getStudy("ITN029ST")); -// studies.add(getStudy("ITN021AI")); -// studies.add(getStudy("ITN033AI")); QuerySchema coreSchema = DefaultSchema.get(getUser(), getContainer()).getSchema("core"); - studies = (new TableSelector(coreSchema.getSchema("lists").getTable("studyProperties"))).getArrayList(StudyBean.class); + List studies = (new TableSelector(coreSchema.getSchema("lists").getTable("studyProperties"))).getArrayList(StudyBean.class); List publications = (new TableSelector(coreSchema.getSchema("lists").getTable("studyManuscripts")).getArrayList(StudyPublicationBean.class)); Map> pubCounts = new HashMap<>(); for (StudyPublicationBean pub : publications) { @@ -121,7 +116,9 @@ public Object execute(StudiesForm form, BindException errors) throws Exception countPair.second += 1; } + Map studyUrls = StudyBean.getStudyUrls(getContainer(), getUser(), StudyBean.studyIdField); for (StudyBean study : studies) { + study.setUrl(studyUrls.get(study.getStudyId())); if (pubCounts.get(study.getStudyId()) == null) { study.setManuscriptCount(0); @@ -138,58 +135,10 @@ public Object execute(StudiesForm form, BindException errors) throws Exception } } + public static class StudiesForm {} - private StudyBean getStudy(String studyId) - { - SqlExecutor executor = new SqlExecutor(TrialShareManager.getSchema()); - - StudyBean study = new StudyBean(); - if (studyId.equals("ITN029ST")) - { - study.setStudyId("ITN029ST"); - study.setShortName("WISP-R"); - study.setInvestigator("Sandy Feng, MD, PhD"); - study.setTitle("Immunosuppression Withdrawal for Pediatric Living-donor Liver Transplant Recipients"); - study.setDescription("This is a prospective multicenter, open-label, single-arm trial in which 20 pediatric recipients of parental living-donor liver allografts will undergo gradual withdrawal of immunosuppression with the goal of complete withdrawal. Patients on stable immunosuppression regimens with good organ function and no evidence of acute or chronic rejection or other forms of allograft dysfunction will be enrolled. Participants will undergo gradual withdrawal of immunosuppression and will be followed for a minimum of 4 years after completion of immunosuppression withdrawal. Immunologic and genetic profiles will be collected at multiple time points and compared between tolerant and nontolerant participants."); - study.setIsLoaded(true); - study.setAvailability("operational"); - study.setExternalUrl("https://www.itntrialshare.org/project/Studies/ITN029STOPR/Study%20Data/begin.view"); - } - else if (studyId.equals("ITN021AI")) - { - study.setStudyId("ITN021AI"); - study.setShortName("RAVE"); - study.setInvestigator("John H. Stone, MD, MPH"); - study.setTitle("Rituximab for ANCA-Associated Vasculitis"); - study.setIsLoaded(false); - study.setAvailability("public"); - study.setDescription("Current conventional therapies for ANCA-associated vasculitis (AAV) are associated with high incidences of treatment failure, disease relapse, substantial toxicity, and patient morbidity and mortality. Rituximab is a monoclonal antibody used to treat non-Hodgkin's lymphoma. This study will evaluate the efficacy of rituximab with glucocorticoids in inducing disease remission in adults with severe forms of AAV (WG and MPA).\n" + - "\n" + - "The study consists of two phases: a 6-month remission induction phase, followed by a 12-month remission maintenance phase. All participants will receive at least 1 g of pulse IV methylprednisolone or a dose-equivalent of another glucocorticoid preparation. Depending on the participant's condition, he or she may receive up to 3 days of IV methylprednisolone for a total of 3 g of methylprednisolone (or a dose-equivalent). During the remission induction phase, all participants will receive oral prednisone daily (1 mg/kg/day, not to exceed 80 mg/day). Prednisone tapering will be completed by the Month 6 study visit.\n" + - "\n" + - "Next, participants will be randomly assigned to one of two arms. Arm 1 participants will receive rituximab (375 mg/m2) infusions once weekly for 4 weeks and cyclophosphamide (CYC) placebo daily for 3 to 6 months. Arm 2 participants will receive rituximab placebo infusions once weekly for 4 weeks and CYC daily for 3 to 6 months. During the remission maintenance phase, participants in Arm 1 will discontinue CYC placebo and start oral azathioprine (AZA) placebo daily until Month 18. Participants in Arm 2 will discontinue CYC and start AZA daily until Month 18. Participants who fail treatment before Month 6 will be crossed over to the other treatment arm unless there are specific contraindications.\n" + - "\n" + - "All participants will be followed for at least 18 months. Initially, study visits are weekly, progressing to monthly and then quarterly visits as the study proceeds. Blood collection will occur at each study visit."); - } - else if (studyId.equals("ITN033AI")) - { - study.setStudyId("ITN033AI"); - study.setShortName("Halt-MS"); - study.setIconUrl("https://www.itntrialshare.org/files/Studies/ITN033AIOPR/Study%20Data/@files/studyDocs/ITN033AI%20HALT-MS%20250px.gif"); - study.setManuscriptCount(3); - study.setInvestigator("Richard A. Nash, MD"); - study.setTitle("High Dose Immunosuppression and Autologous Transplantation for Multiple Sclerosis"); - study.setIsLoaded(false); - study.setAvailability("operational"); - study.setDescription("This study is a prospective, multicenter Phase II clinical trial evaluating high-dose immunosuppressive therapy (HDIT) using Carmustine, Etoposide, Cytarabine, and Melphalan (BEAM) plus Thymoglobulin (rATG) with autologous transplantation of CD34+ HCT for the treatment of poor-risk MS. The active treatment period will be approximately 3 months from the time of initiation of mobilization to the day of discharge after transplant. Subjects will be followed up to 60 months (5 years) after transplant. Total study duration will be 60 months after the last subject is transplanted."); - study.setExternalUrl("https://www.itntrialshare.org/project/Studies/ITN033AIOPR/Study%20Data/begin.view"); - } - return study; - } - - private List getFacetFilters(Boolean includeAnd, Boolean includeOr, FacetFilter.Type defaultType) { @@ -231,13 +180,16 @@ public Object execute(Object o, BindException errors) throws Exception facet = new StudyFacetBean("Study Type", "Study Types", "Study.Study Type", "StudyType", "[Study.Study Type][(All)]", FacetFilter.Type.OR, 2); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); facets.add(facet); - facet = new StudyFacetBean("Condition", "Conditions", "Study.Condition", "Condition", "[Study.Condition][(All)]", FacetFilter.Type.OR, 5); + facet = new StudyFacetBean("Assay", "Assays", "Study.Assay", "Assay", "[Study.Assay][(All)]", FacetFilter.Type.OR, 3); + facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); + facets.add(facet); + facet = new StudyFacetBean("Condition", "Conditions", "Study.Condition", "Condition", "[Study.Condition][(All)]", FacetFilter.Type.OR, 6); facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); facets.add(facet); - facet = new StudyFacetBean("Age Group", "Age Groups", "Study.AgeGroup", "AgeGroup", "[Study.AgeGroup][(All)]", FacetFilter.Type.OR, 3); + facet = new StudyFacetBean("Age Group", "Age Groups", "Study.AgeGroup", "AgeGroup", "[Study.AgeGroup][(All)]", FacetFilter.Type.OR, 5); facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); facets.add(facet); - facet = new StudyFacetBean("Phase", "Phases", "Study.Phase", "Phase", "[Study.Phase][(All)]", FacetFilter.Type.OR, 4); + facet = new StudyFacetBean("Phase", "Phases", "Study.Phase", "Phase", "[Study.Phase][(All)]", FacetFilter.Type.OR, 5); facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); facets.add(facet); facet = new StudyFacetBean("Study", "Studies", "Study", "Study", "[Study].[(All)]", FacetFilter.Type.OR, null); @@ -366,10 +318,13 @@ public Object execute(Object o, BindException errors) throws Exception List subsets = new ArrayList<>(); StudySubset subset = new StudySubset(); - subset.setId("operational"); - subset.setName("Operational"); - subset.setDefault(false); - subsets.add(subset); + if (!getUser().isGuest()) + { + subset.setId("operational"); + subset.setName("Operational"); + subset.setDefault(false); + subsets.add(subset); + } subset = new StudySubset(); subset.setId("public"); diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index 90e804d0..0bdcfa47 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -10,9 +10,10 @@ import org.labkey.api.query.DefaultSchema; import org.labkey.api.query.QuerySchema; import org.labkey.api.security.User; -import org.labkey.api.view.ActionURL; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -24,6 +25,7 @@ public class StudyBean private String shortName; private String studyId; private String title; + private String url; private String investigator; private String externalUrl; private String externalUrlDescription; @@ -35,13 +37,14 @@ public class StudyBean private String availability; private Boolean isPublic; private Integer participantCount; - private Boolean isSelected = true; private List personnel; private List publications; private Integer manuscriptCount; private Integer abstractCount; + public final static String studyIdField = "StudyId"; + public String getStudyId() { @@ -93,15 +96,6 @@ public void setIsLoaded(Boolean loaded) isLoaded = loaded; } - public String getDescription() - { - return description; - } - - public void setDescription(String description) - { - this.description = description; - } public String getBriefDescription() { @@ -213,21 +207,43 @@ public void setParticipantCount(Integer participantCount) this.participantCount = participantCount; } - public Boolean getIsSelected() + public void setUrl(String url) { - return isSelected; + this.url = url; } - public void setIsSelected(Boolean selected) + public String getUrl() { - isSelected = selected; + return this.url; } - public ActionURL getUrl(Container c, User user) + public String getUrl(Container c, User user) + { + if (url == null) + { + Collection> maps = getStudyProperties(c, user); + for (Map map : maps) + { + Container studyContainer = ContainerManager.getForId((String) map.get("container")); + String studyId = (String) map.get(studyIdField); + String name = (String) map.get("Label"); + if (null == studyId && getStudyIdPrefix() != null && name.startsWith(getStudyIdPrefix())) + studyId = name; + if (null != studyContainer && StringUtils.equalsIgnoreCase(getStudyId(), studyId)) + { + url = studyContainer.getStartURL(user).toString(); + break; + } + } + } + return url; + } + + + public static Collection> getStudyProperties(Container c, User user) { if (!c.isRoot()) { - String comma = "\n"; Container p = c.getProject(); QuerySchema s = DefaultSchema.get(user, p).getSchema("study"); TableInfo sp = s.getTable("StudyProperties"); @@ -236,24 +252,54 @@ public ActionURL getUrl(Container c, User user) ContainerFilter cf = new ContainerFilter.AllInProject(user); ((ContainerFilterable) sp).setContainerFilter(cf); } - Collection> maps = new TableSelector(sp).getMapCollection(); + return new TableSelector(sp).getMapCollection(); + } + return Collections.emptyList(); + } + + + public static Map getStudyUrls(Container c, User user, String idField) + { + Map studyUrls = new HashMap<>(); + Collection> maps = getStudyProperties(c, user); + for (Map map : maps) + { + Container studyContainer = ContainerManager.getForId((String) map.get("container")); + String studyId = (String) map.get(idField); + + if (null != studyContainer && studyId != null) + { + studyUrls.put(studyId, studyContainer.getStartURL(user).toString()); + } + } + return studyUrls; + } + + public String getDescription(Container c, User user) + { + if (description == null) + { + Collection> maps = getStudyProperties(c, user); for (Map map : maps) { Container studyContainer = ContainerManager.getForId((String) map.get("container")); - String studyAccession = (String)map.get("study_accession"); - // TODO study properties does not have the studyId in it... - String name = (String)map.get("Label"); - if (null == studyAccession && getStudyIdPrefix() != null && name.startsWith(getStudyIdPrefix())) - studyAccession = name; + String studyAccession = (String)map.get(studyIdField); if (null != studyContainer && StringUtils.equalsIgnoreCase(getStudyId(), studyAccession)) { - return studyContainer.getStartURL(user); + description = (String) map.get("description"); + break; } } } - return null; + return description; + } + + public void setDescription(String description) + { + this.description = description; } + public String getExternalUrlDescription() { return externalUrlDescription; diff --git a/src/org/labkey/trialshare/view/dataFinder.jsp b/src/org/labkey/trialshare/view/dataFinder.jsp index f2ee5825..66e44427 100644 --- a/src/org/labkey/trialshare/view/dataFinder.jsp +++ b/src/org/labkey/trialshare/view/dataFinder.jsp @@ -34,12 +34,11 @@ resources.add(ClientDependency.fromPath("study/Finder/data/StudyCard.js")); resources.add(ClientDependency.fromPath("study/Finder/data/Studies.js")); resources.add(ClientDependency.fromPath("study/Finder/data/StudySubset.js")); + resources.add(ClientDependency.fromPath("study/Finder/data/StudySubsets.js")); resources.add(ClientDependency.fromPath("study/Finder/data/Facets.js")); resources.add(ClientDependency.fromPath("study/Finder/data/FacetMembers.js")); -// resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeaderTpl.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/FacetPanelHeader.js")); -// resources.add(ClientDependency.fromPath("study/Finder/panel/Facets.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/FacetsGrid.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/SelectionSummary.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/FacetSelection.js")); diff --git a/src/org/labkey/trialshare/view/studyDetail.jsp b/src/org/labkey/trialshare/view/studyDetail.jsp index 6cde719e..e00b23a2 100644 --- a/src/org/labkey/trialshare/view/studyDetail.jsp +++ b/src/org/labkey/trialshare/view/studyDetail.jsp @@ -50,13 +50,11 @@ Container c = context.getContainer(); TrialShareController.StudyDetailBean studyDetail = me.getModelBean(); StudyBean study = studyDetail.getStudy(); - String descriptionHTML; - if (!StringUtils.isEmpty(study.getDescription())) - descriptionHTML= study.getDescription(); - else + String descriptionHTML = study.getDescription(c, context.getUser()); + if (StringUtils.isEmpty(descriptionHTML)) descriptionHTML = h(study.getBriefDescription()); - ActionURL studyUrl = study.getUrl(c, context.getUser()); + String studyUrl = study.getUrl(c, context.getUser()); String publicationsSectionTitle = "Manuscripts and Abstracts"; Map linkProps = new HashMap<>(); @@ -130,7 +128,7 @@ <% if (null != studyUrl) { %> - <%= textLink("View study " + study.getStudyId(), studyUrl.toString(), null, null, linkProps)%>
          + <%= textLink("View study " + study.getShortName(), studyUrl, null, null, linkProps)%>
          <% } %> <% if (null != study.getExternalUrl()) From bc7ce6781ac3d8da95aac1b983098006e3a663b1 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 27 Dec 2015 14:45:08 -0800 Subject: [PATCH 039/587] Spec 24959 : Implement Data Finder for ITN - remove unused code; clean up css classes. --- resources/web/study/Finder/data/Facet.js | 6 +- resources/web/study/Finder/data/Facets.js | 1 - resources/web/study/Finder/data/Studies.js | 2 +- resources/web/study/Finder/dataFinder.css | 44 ++-- .../study/Finder/panel/FacetPanelHeaderTpl.js | 104 --------- .../web/study/Finder/panel/FacetSelection.js | 5 +- resources/web/study/Finder/panel/Facets.js | 199 ------------------ .../web/study/Finder/panel/FacetsGrid.js | 3 - resources/web/study/Finder/panel/Finder.js | 12 +- .../study/Finder/panel/SelectionSummary.js | 20 +- .../web/study/Finder/view/dataFinderTour.js | 67 ++++++ src/org/labkey/trialshare/view/dataFinder.jsp | 81 +------ 12 files changed, 118 insertions(+), 426 deletions(-) delete mode 100644 resources/web/study/Finder/panel/FacetPanelHeaderTpl.js delete mode 100644 resources/web/study/Finder/panel/Facets.js create mode 100644 resources/web/study/Finder/view/dataFinderTour.js diff --git a/resources/web/study/Finder/data/Facet.js b/resources/web/study/Finder/data/Facet.js index ff80fadc..b7736698 100644 --- a/resources/web/study/Finder/data/Facet.js +++ b/resources/web/study/Finder/data/Facet.js @@ -5,21 +5,19 @@ Ext4.define('LABKEY.study.data.Facet', { fields: [ {name: 'name'}, - {name: 'pluralName'}, // TODO not currently used + {name: 'pluralName'}, // not currently used {name: 'members'}, {name: 'selectedMembers'}, {name: 'filterOptions'}, - {name: 'memberMap'}, // TODO is this used? {name: 'currentFilterType'}, {name: 'currentFilterCaption'}, - {name: 'allMemberCount', type:'int', defaultValue: 0}, // TODO not currently used + {name: 'allMemberCount', type:'int', defaultValue: 0}, // set but not currently used {name: 'hierarchy'}, {name: 'hierarchyName'}, {name: 'levelName'}, {name: 'allMemberName'}, {name: 'ordinal'}, {name: 'isExpanded', type:'boolean', defaultValue: true} - ], associations: [ diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index 8894fbee..0513d6b5 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -81,7 +81,6 @@ Ext4.define('LABKEY.study.store.Facets', { if (facet.get("name") != "Study") facetMembersStore.add(member); facet.data.members.push(member); - facet.data.memberMap[member.uniqueName] = member; } if (facet.get("name") == "Study") { facet.data.selectedMembers = facet.data.members; diff --git a/resources/web/study/Finder/data/Studies.js b/resources/web/study/Finder/data/Studies.js index dfb0dd5d..8b1aa38a 100644 --- a/resources/web/study/Finder/data/Studies.js +++ b/resources/web/study/Finder/data/Studies.js @@ -3,7 +3,7 @@ Ext4.define('LABKEY.study.store.Studies', { storeId: 'studies', model: 'LABKEY.study.data.StudyCard', autoLoad: false, - dataModuleName: this.dataModuleName, + dataModuleName: "trialshare", // TODO how can this be used below? selectedStudies : {}, selectedSubset : 'public', proxy : { diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index c8940323..ff13bb80 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -119,27 +119,28 @@ SPAN.labkey-study-search } /* search area */ -TD.labkey-search-box, -TD.search-box, -DIV.search-box +TD.labkey-search-box { float:left; padding:10pt; } -INPUT.labkey-search-box, -INPUT.search-box +INPUT.labkey-search-box { width:200pt; } - -DIV.labkey-search-message, -DIV.search-message +DIV.labkey-search-message { padding-left:10pt; } -.selection-panel +/*.labkey-facet-selection-summary*/ +/*{*/ + /*background-color: white;*/ + /*border:none*/ +/*}*/ + +.labkey-facet-selection-panel { vertical-align: text-top; width:220pt; @@ -147,7 +148,7 @@ DIV.search-message background-color: white; } -.selection-panel > DIV +.labkey-facet-selection-panel > DIV { height:100%; overflow-y:auto; @@ -160,7 +161,7 @@ DIV.search-message /*text-align: right;*/ /*}*/ -DIV.summary LI.member +DIV.labkey-facet-summary LI.labkey-facet-member { clear:both; padding:2pt; @@ -213,7 +214,7 @@ DIV.summary LI.member float:right; } -DIV.facet-summary, +DIV.labkey-facet-summary, DIV.facet { max-width:200pt; @@ -232,6 +233,7 @@ DIV.facet-header background-color: rgb(240, 240, 240); } +.labkey-facet-header .labkey-facet-caption, .labkey-facet-header .labkey-facet-caption, DIV.facet-header .facet-caption { @@ -281,14 +283,12 @@ DIV.facet-header .labkey-filter-options color:grey; } -.labkey-facet-header .active, -.facet-header .active +.labkey-facet-header .active { display:inline-block; } -DIV.facet-summary UL, -DIV.facet UL +DIV.labkey-facet-summary UL { list-style-type:none; padding-left:0; @@ -296,7 +296,7 @@ DIV.facet UL margin:0; } -DIV.facet-summary LI.member +DIV.labkey-facet-summary LI.member { clear:both; cursor:default; @@ -360,7 +360,7 @@ DIV.facet LI.empty-member { color:#888888; } -LI.member .member-indicator +LI.labkey-facet-member .member-indicator { position:relative; display:inline-block; @@ -390,7 +390,7 @@ LI.member .member-indicator SPAN.member .labkey-facet-member-name, SPAN.labkey-facet-member .labkey-facet-member-name, -LI.member .member-name +LI.labkey-facet-member .labkey-facet-member-name { display:inline-block; position:relative; @@ -403,7 +403,7 @@ LI.member .member-name SPAN.member .labkey-facet-member-count, SPAN.labkey-facet-member .labkey-facet-member-count, -LI.member .member-count +LI.labkey-facet-member .labkey-facet-member-count { position:relative; float:right; @@ -413,7 +413,7 @@ LI.member .member-count .x4-grid-row-selected SPAN.labkey-facet-percent-bar, SPAN.member SPAN.bar-selected, SPAN.labkey-facet-member SPAN.bar-selected, -LI.member SPAN.bar-selected +LI.labkey-facet-member SPAN.bar-selected { background-color:rgba(81, 158, 218, 0.2); } @@ -432,7 +432,7 @@ LI.member SPAN.bar-selected } .x4-grid-row SPAN.labkey-facet-percent-bar, -LI.member .bar +LI.labkey-facet-member .bar { height:14pt; background-color:rgba(222,222,222,0.3); diff --git a/resources/web/study/Finder/panel/FacetPanelHeaderTpl.js b/resources/web/study/Finder/panel/FacetPanelHeaderTpl.js deleted file mode 100644 index 7f7dd651..00000000 --- a/resources/web/study/Finder/panel/FacetPanelHeaderTpl.js +++ /dev/null @@ -1,104 +0,0 @@ -Ext4.define("LABKEY.study.panel.FacetPanelHeaderTpl", { - extend: "Ext.Component", - - padding: "0 0 5 0", - data : { - loadedStudiesShown: true, - isGuest: false, - hasFilters: false, - showParticipantGroups:false, - currentGroup: { - id: 1, - label: "My group" - }, - saveOptions: [ - { - id: "save", - label : "Save", - isActive : true - }, - { - id: "saveAs", - label : "Save As", - isActive : true - } - ], - - groups: [{ - label: "my first group" - }] - }, - tpl: new Ext4.XTemplate( - '
           ', - '', - '
          Saved group: {currentGroup.label}
          ', - '
          ', - ' ', - '
          ' - ), - - -}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index c53ec4a2..25031b46 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -7,7 +7,7 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { alias : 'widget.study-facet-selection-panel', - cls: 'selection-panel', + cls: 'labkey-facet-selection-panel', padding: "10 8 8 10", @@ -70,7 +70,8 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { getFacets : function() { if (!this.facets) { this.facets = Ext4.create("LABKEY.study.panel.FacetsGrid", { - dataModuleName: this.dataModuleName + dataModuleName: this.dataModuleName, + olapConfig: this.olapConfig }); } return this.facets; diff --git a/resources/web/study/Finder/panel/Facets.js b/resources/web/study/Finder/panel/Facets.js deleted file mode 100644 index 259b8d9a..00000000 --- a/resources/web/study/Finder/panel/Facets.js +++ /dev/null @@ -1,199 +0,0 @@ -Ext4.define("LABKEY.study.panel.Facets", { - - extend : 'Ext.view.View', - - alias: 'widget.labkey-study-facet-panel', - - cls: 'labkey-study-facets', - - itemSelector: 'div.facet', - - dataModuleName: 'study', - - autoScroll: true, - - studyData : [], - - store: Ext4.create('LABKEY.study.store.Facets', { - dataModuleName: this.dataModuleName - }), - - tpl: new Ext4.XTemplate( - - '', - '', - '', - '', - '', - { - formatNumber : Ext4.util.Format.numberRenderer('0,000'), - } - ), - - listeners: { - itemClick: function(view, record, item, index, event, eOpts) { - var targetEl = event.getTarget(); - if (targetEl.className.includes("member")) - this.selectMember(record, item, index, event); - else if (targetEl.className.includes("facet-toggle")) - { - if (item.className.includes("expanded")) - item.className = item.className.replace("expanded", "collapsed"); - else - item.className = item.className.replace("collapsed", "expanded"); - } - }, - mouseover: function(view, record, item, index, event, eOpts) { - console.log("display filter choice"); - this.displayFilterChoice(record, event); - } - }, - - displayFilterChoice : function (record, event) - { - if (record.filters.length < 2) - - var locationElement = event.target; - if (locationElement.className.includes('fa-caret')) - locationElement = event.target.parentElement; - var xy = Ext4.fly(locationElement).getXY(); - this.filterChoice = - { - show: true, - dimName: dimName, - x: xy[0], - y: xy[1], - options: dim.filterOptions - }; - if (event.stopPropagation) - event.stopPropagation(); - }, - - selectMember : function (facet, item, facetIndex, event) { - var shiftClick = event && (event.ctrlKey || event.altKey || event.metaKey); - this._selectMember(facet, item, facetIndex, event, shiftClick); - }, - - toggleMember : function (facet, item, facetIndex, event) { - this._selectMember(facet, item, facetIndex, event, true); - }, - - _selectMember : function (facet, item, facetIndex, event, shiftClick) { - - var filterMembers = facet.get("filters"); - - var name = facet.get("name"); - var element = event.target; - while (element.parentElement && !element.id.includes("member_")) { - element = element.parentElement; - } - if (!element) - { - if (0 == filterMembers.length) // no change - return; - this._clearFilter(name); - } - else if (!shiftClick) - { - this._clearFilter(name); - facet.selectedMembers = [element.id]; - member.selected = true; // TODO change classes here: li gets 'selected-member' and span gets 'selected' - } - else - { - var index = -1; - for (var m = 0; m < filterMembers.length; m++) - { - if (member.uniqueName == filterMembers[m].uniqueName) - index = m; - } - if (index == -1) // unselected -> selected - { - filterMembers.push(member); - member.selected = true; - } - else // selected --> unselected - { - filterMembers.splice(index, 1); - member.selected = false; - this.fireEvent("filterSelectionCleared", this.hasFilters()); - } - } - - this.updateCountsAsync(); - if (event.stopPropagation) - event.stopPropagation(); - }, - - clearAllFilters : function (updateCounts) { - for (var i = 0; i < this.getStore().count(); i++) - { - if (this.store.getAt(i).get("id") == "Study") - continue; - this._clearFilter(this.store.getAt(i)); - } - if (updateCounts) - this.updateCountsAsync(); - this.fireEvent("filterSelectionCleared", false); - }, - - _clearFilter : function (facet) { - var filterMembers = facet.get("filters"); - for (var m = 0; m < filterMembers.length; m++) - filterMembers[m].selected = false; - dim.filters = []; - this.fireEvent("filterSelectionCleared", this.hasFilters()); - } -}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 9276963f..81287716 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -8,7 +8,6 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { ui: 'custom', - //itemSelector: 'div.facet', itemSelector: 'span.labkey-facet-member', dataModuleName: 'study', @@ -103,7 +102,6 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { } html += ''; return html; - //Ext4.get(Ext4.DomQuery.select('.labkey-filter-options', LABKEY.study.panel.FacetsGrid.headerLookup[facetName])[0]).setHTML(html); } } ) @@ -167,7 +165,6 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { var facet; for (var f = 0; f < this.facetStore.count(); f++) { facet = this.facetStore.getAt(f); - //this.updateFacetHeader(facet.data.name, facet.get("isExpanded")); } this.facetStore.updateCountsAsync(); diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index fee5bb9a..485c013f 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -17,7 +17,7 @@ Ext4.define('LABKEY.study.panel.Finder', { height: '500px', - dataModuleName: 'study', + dataModuleName: 'study', // TODO autoScroll : true, @@ -47,9 +47,9 @@ Ext4.define('LABKEY.study.panel.Finder', { getCubeDefinition: function() { var me = this; this.cube = LABKEY.query.olap.CubeManager.getCube({ - configId: 'TrialShare:/StudyCube', - schemaName: 'lists', - name: 'StudyCube', + configId: this.olapConfig.configId, + schemaName: this.olapConfig.schemaName, + name: this.olapConfig.name, deferLoad: false }); this.cube.onReady(function (m) @@ -67,7 +67,6 @@ Ext4.define('LABKEY.study.panel.Finder', { onCubeReady: function() { this.getFacetsPanel().onCubeReady(this.mdx); - //this.getStudiesPanel().onCubeReady(this.cube); }, onStudySubsetChanged : function(value) { @@ -157,7 +156,8 @@ Ext4.define('LABKEY.study.panel.Finder', { width: '21%', maxWidth: '265px', dataModuleName: this.dataModuleName, - showParticipantFilters : this.showParticipantFilters + showParticipantFilters : this.showParticipantFilters, + olapConfig: this.olapConfig }); } FACETS = this.facetsPanel; diff --git a/resources/web/study/Finder/panel/SelectionSummary.js b/resources/web/study/Finder/panel/SelectionSummary.js index 2eff6c60..2c7936cd 100644 --- a/resources/web/study/Finder/panel/SelectionSummary.js +++ b/resources/web/study/Finder/panel/SelectionSummary.js @@ -4,17 +4,17 @@ Ext4.define("LABKEY.study.panel.SelectionSummary", { alias : 'widget.facet-selection-summary', tpl: new Ext4.XTemplate( - '
          ', - '
          ', - '
          Summary
          ', + '
          ', + '
          ', + '
          Summary
          ', '
            ', - '
          • ', - ' Studies', - ' {studyCount:this.getStudyCount}', + '
          • ', + ' Studies', + ' {studyCount:this.getStudyCount}', '
          • ', - '
          • ', - ' Subjects', - ' {participantCount:this.getParticipantCount}', + '
          • ', + ' Subjects', + ' {participantCount:this.getParticipantCount}', '
          • ', '
          ', '
          ', @@ -46,8 +46,8 @@ Ext4.define("LABKEY.study.panel.SelectionSummary", { initComponent: function() { Ext4.getStore('studies').addListener('filterChange',this.onFilterSelectionChanged, this); }, + onFilterSelectionChanged : function() { - console.log("in SelectionSummary with filterChange"); this.update(); } }); \ No newline at end of file diff --git a/resources/web/study/Finder/view/dataFinderTour.js b/resources/web/study/Finder/view/dataFinderTour.js new file mode 100644 index 00000000..3eea6143 --- /dev/null +++ b/resources/web/study/Finder/view/dataFinderTour.js @@ -0,0 +1,67 @@ +var $=$||jQuery; + +LABKEY.help.Tour.register({ + id: "LABKEY.tour.dataFinder", + steps: [ + { + target: $('.labkey-wp')[0], + title: "Data Finder", + content: "Welcome to the Data Finder. A tool for searching, accessing and combining data across studies.", + placement: "top", + showNextButton: true + },{ + target: "studypanel", + title: "Study Panel", + content: "This area contains short descriptions of the studies/datasets that match the selected criteria.", + placement: "top", + showNextButton: true, + showPrevButton: true + },{ + target: "summaryArea", + title: "Summary", + content: "This summary area indicates how many subjects and studies match the selected criteria.", + placement: "right", + showNextButton: true, + showPrevButton: true + },{ + target: "facetPanel", + title: "Filters", + content: "This is where filters are selected and applied. The numbers (also represented as the length of the gray bars) represent how many subjects will match the search if this filter is added.", + placement: "right", + showNextButton: true, + showPrevButton: true + },{ + target: "searchTerms", + title: "Quick Search", + content: "Enter terms of interest to search study and data descriptions. This will find matches within the selection of filtered studies/datasets.", + placement: "right", + yOffset: -25, + showPrevButton: true + } + //{ + // target: 'group_Condition', + // title: "Study Attributes", + // content: "Select items in this area to find studies of interest. The gray bars show the number of selected participants.

          Try " + (Ext4.isMac ? "Command" : "Ctrl") + "-click to multi-select.", + // placement: "right" + //}, + //{ + // target: 'searchTerms', + // title: "Quick Search", + // content: "Enter terms of interest to search study descriptions.", + // placement: "right" + //}, + //{ + // target: 'summaryArea', + // title: "Summary", + // content: "Here is a summary of the data in the selected studies. Studies represents the number of studies that contain some participants that match the criteria. Subjects is the number of subjects across all selected studies (including subjects that did not match all attributes).", + // placement: "right" + //}, + //{ + // target: 'filterArea', + // title: "Filter Area", + // content: "See and manage your active filters.", + // placement: "right" + //} + ] +}); + diff --git a/src/org/labkey/trialshare/view/dataFinder.jsp b/src/org/labkey/trialshare/view/dataFinder.jsp index 66e44427..d767c4b1 100644 --- a/src/org/labkey/trialshare/view/dataFinder.jsp +++ b/src/org/labkey/trialshare/view/dataFinder.jsp @@ -47,6 +47,7 @@ resources.add(ClientDependency.fromPath("study/Finder/panel/StudyPanelHeader.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/StudyCards.js")); resources.add(ClientDependency.fromPath("study/Finder/panel/Finder.js")); + resources.add(ClientDependency.fromPath("study/Finder/view/DataFinderTour.js")); return resources; } %> @@ -58,83 +59,15 @@ { DataFinder.finderView = Ext4.create('LABKEY.study.panel.Finder', { renderTo : 'dataFinderWrapper', - dataModuleName: 'trialshare' + dataModuleName: 'trialshare', + olapConfig : { + configId: 'TrialShare:/StudyCube', + schemaName: 'lists', + name: 'StudyCube', + } }); });

          - - - - From ef07995ef0ffb257121f788c0351c7a3675fbc4e Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 27 Dec 2015 15:09:58 -0800 Subject: [PATCH 040/587] Spec 24959 : Implement Data Finder for ITN - remove unused classes. --- resources/web/study/Finder/dataFinder.css | 235 +--------------------- 1 file changed, 10 insertions(+), 225 deletions(-) diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index ff13bb80..cf486f0a 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -26,17 +26,7 @@ DIV.labkey-study-facets { { background-color:#ffffff; } -TABLE.labkey-data-finder -{ - height: 450px; - width:100%; - padding:3pt; -} -.labkey-filter-message -{ - float:left; -} /* labkey-study-card */ .labkey-study-card-highlight @@ -45,7 +35,6 @@ TABLE.labkey-data-finder font-variant:small-caps; } - .labkey-study-card-summary, .labkey-study-card-accession { @@ -59,17 +48,6 @@ TABLE.labkey-data-finder float:right; } -.labkey-study-card-shortName -{ - float:right; -} - -.labkey-study-icon -{ - height: 25px; - float: right; -} - .labkey-study-card-divider, .labkey-study-card-title, .labkey-study-card-description @@ -134,12 +112,6 @@ DIV.labkey-search-message padding-left:10pt; } -/*.labkey-facet-selection-summary*/ -/*{*/ - /*background-color: white;*/ - /*border:none*/ -/*}*/ - .labkey-facet-selection-panel { vertical-align: text-top; @@ -154,13 +126,6 @@ DIV.labkey-search-message overflow-y:auto; } -/*DIV.help-links*/ -/*{*/ - /*float:right;*/ - /*vertical-align: text-top;*/ - /*text-align: right;*/ -/*}*/ - DIV.labkey-facet-summary LI.labkey-facet-member { clear:both; @@ -174,11 +139,6 @@ DIV.labkey-facet-summary LI.labkey-facet-member /* facets */ -.facet-selection-header -{ - background-color: white; -} - .labkey-clear-all { float: right; @@ -198,6 +158,7 @@ DIV.labkey-facet-summary LI.labkey-facet-member { cursor: default; } + .labkey-clear-all.inactive .x4-btn-inner { color: grey; @@ -214,8 +175,7 @@ DIV.labkey-facet-summary LI.labkey-facet-member float:right; } -DIV.labkey-facet-summary, -DIV.facet +DIV.labkey-facet-summary { max-width:200pt; border:solid 2pt rgb(220, 220, 220); @@ -226,22 +186,18 @@ DIV.facet cursor:pointer; } -.labkey-facet-header, -DIV.facet-header +.labkey-facet-header { padding:4pt; background-color: rgb(240, 240, 240); } -.labkey-facet-header .labkey-facet-caption, -.labkey-facet-header .labkey-facet-caption, -DIV.facet-header .facet-caption +.labkey-facet-header .labkey-facet-caption { font-weight:400; } -.labkey-facet-header .labkey-filter-options, -DIV.facet-header .labkey-filter-options +.labkey-facet-header .labkey-filter-options { font-size: 11px; padding: 3px 0px 0px 20px; @@ -275,9 +231,7 @@ DIV.facet-header .labkey-filter-options background-position: left 28% } -.facet-selection-header .inactive, -.labkey-facet-header .inactive, -.facet-header .inactive +.labkey-facet-header .inactive { cursor:default; color:grey; @@ -296,7 +250,7 @@ DIV.labkey-facet-summary UL margin:0; } -DIV.labkey-facet-summary LI.member +DIV.labkey-facet-summary LI.labkey-facet-member { clear:both; cursor:default; @@ -308,58 +262,16 @@ DIV.labkey-facet-summary LI.member width:100%; } -DIV.facet LI.member -{ - clear:both; - cursor:pointer; - padding:2pt; - margin:1px 0px 1px 0px; - border:1px solid #ffffff; - height:14pt; - position:relative; - width:100%; -} -DIV.facet.collapsed LI.member -{ - display:none; -} -DIV.facet.collapsed .fa-plus-square -{ - float:left; - display:inline; -} -DIV.facet.collapsed .fa-minus-square -{ - float:left; - display:none; -} -DIV.expanded .fa-plus-square -{ - float:left; - display:none; -} -DIV.expanded .fa-minus-square -{ - float:left; - display:inline; -} - -DIV.facet.collapsed LI.member.selected-member -{ - display:block; -} - -SPAN.labkey-facet-member:hover SPAN.labkey-facet-percent-bar, -DIV.facet LI.member:hover SPAN.bar +SPAN.labkey-facet-member:hover SPAN.labkey-facet-percent-bar { background-color:rgba(81, 158, 218, 0.2); } -.labkey-empty-member, -DIV.facet LI.empty-member +.labkey-empty-member { color:#888888; } + LI.labkey-facet-member .member-indicator { position:relative; @@ -367,28 +279,7 @@ LI.labkey-facet-member .member-indicator width:16px; z-index:2; } -.member-indicator.selected:after -{ - content:"\002713" -} -.member-indicator.selected:hover:after -{ - content:"x"; -} -.member-indicator.not-selected:after -{ - content:"\0025FB" -} -.member-indicator.not-selected:hover:after -{ - content:"\002713" -} -.member-indicator.not-selected.none-selected:after -{ - content:"\002713"; color:lightgray; -} -SPAN.member .labkey-facet-member-name, SPAN.labkey-facet-member .labkey-facet-member-name, LI.labkey-facet-member .labkey-facet-member-name { @@ -401,7 +292,6 @@ LI.labkey-facet-member .labkey-facet-member-name z-index:2; } -SPAN.member .labkey-facet-member-count, SPAN.labkey-facet-member .labkey-facet-member-count, LI.labkey-facet-member .labkey-facet-member-count { @@ -411,7 +301,6 @@ LI.labkey-facet-member .labkey-facet-member-count } .x4-grid-row-selected SPAN.labkey-facet-percent-bar, -SPAN.member SPAN.bar-selected, SPAN.labkey-facet-member SPAN.bar-selected, LI.labkey-facet-member SPAN.bar-selected { @@ -464,33 +353,6 @@ DIV.labkey-filter-popup } /* menus */ -.nav -{ - margin: 0; - padding-left: 0;; - list-style: none; -} - -ul.nav -{ - display:block; -} - -.navbar-nav > li -{ - float: left; -} - -.navbar-nav -{ - float: left; - margin: 0; -} - -.navbar-nav > li > .labkey-dropdown-menu -{ - margin-top : 0; -} .labkey-filter-options > span.inactive { @@ -498,83 +360,6 @@ ul.nav cursor: default; } -.labkey-dropdown > a.labkey-disabled-text-link -{ - color: #C0C0C0; - cursor: default; -} - -.labkey-dropdown:hover > .labkey-dropdown-menu-active -{ - display: block; -} - -.labkey-dropdown-menu > li { - margin-right : 0px; -} - -.labkey-dropdown-menu > li > a -{ - color: black; -} - -.labkey-dropdown-menu > li > a.inactive -{ - color: #C0C0C0; - cursor: default; -} - -.labkey-dropdown-menu > li.inactive:hover -{ - color: #C0C0C0; - cursor: default; - background-color: white; - border-style: none; - background-image: none; -} - -.labkey-dropdown-menu > li:hover -{ - background-image: none; - background-color: #eaeaea; - background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f2f2f2), color-stop(100%, #e0e0e0)); - background-image: -webkit-linear-gradient(top, #f2f2f2, #e0e0e0); - background-image: -moz-linear-gradient(top, #f2f2f2, #e0e0e0); - background-image: -o-linear-gradient(top, #f2f2f2, #e0e0e0); - background-image: linear-gradient(top, #f2f2f2, #e0e0e0); - border-color: #b4b4b4; - border-width: 1px; - border-style: solid; - padding: 0; -} - -.labkey-dropdown-menu { - position: absolute; - display: none; - min-width: 80px; - margin: 2px 0 0; - list-style: none; - background-color: white; - border-color:#b4b4b4; - border-width: 1px; - border-style: solid; - padding: 0; - box-shadow: rgb(136, 136, 136) 0px 0px 6px; - z-index: 100; - -webkit-padding-start: 0px -} - -#manageMenu > a { - color:black; - padding-right: 5px; -} - -.menu-item-link { - line-height: 22px; - display: inline-block; - padding: 0 8px 0 8px; -} - .labkey-study-detail .x4-window-header { background-color: #F8F8F8; padding: 0; From 2772a73a369ff8541c6673201964bdf9e3dc708f Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 27 Dec 2015 15:10:24 -0800 Subject: [PATCH 041/587] Spec 24959 : Implement Data Finder for ITN - remove debugging code --- resources/web/study/Finder/panel/Finder.js | 2 -- resources/web/study/Finder/panel/StudyCards.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index 485c013f..b63636f2 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -160,7 +160,6 @@ Ext4.define('LABKEY.study.panel.Finder', { olapConfig: this.olapConfig }); } - FACETS = this.facetsPanel; return this.facetsPanel; }, @@ -175,7 +174,6 @@ Ext4.define('LABKEY.study.panel.Finder', { id: 'studies-view' }); } - STUDIES = this.studiesPanel; return this.studiesPanel; } diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index dbb44337..5b1d3bea 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -83,12 +83,10 @@ Ext4.define("LABKEY.study.panel.StudyCards", { itemClick: function(view, record, item, index, event, eOpts) { if (event.target.className.includes("labkey-study-card-summary")) { - console.log("Show study popup for record " , record); this.showStudyDetailPopup(record.get("studyId")); } else if (event.target.className.includes("labkey-study-card-pub-count")) { - console.log("Show manuscript popup for record " , record); this.showStudyManuscriptsPopup(record.get("studyId")); } } From 3658aa8d972e07073a89d427a28b0edaaef17467 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 27 Dec 2015 16:28:11 -0800 Subject: [PATCH 042/587] Spec 24959 : Implement Data Finder for ITN - update display of facets with names that overflow; always show facet option menu if multiples are selected --- resources/web/study/Finder/dataFinder.css | 11 +++++++++-- resources/web/study/Finder/panel/FacetsGrid.js | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index cf486f0a..41ba6151 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -209,6 +209,7 @@ DIV.labkey-facet-summary .labkey-study-facets .x4-grid-row-selected .x4-grid-td { background-color: white; + background-image: none; } .labkey-study-facets .x4-grid-row-selected .labkey-facet-percent-bar { @@ -217,10 +218,13 @@ DIV.labkey-facet-summary .labkey-study-facets .x4-grid-cell-special .x4-grid-cell-inner { background-color: white; + background-image: none; } .labkey-study-facets .x4-grid-group-hd { background-color:rgb(240, 240, 240); + border-color: #7c7c7c; + border-bottom-width:1px; } .labkey-study-facets .x4-grid-cell-special { @@ -283,10 +287,10 @@ LI.labkey-facet-member .member-indicator SPAN.labkey-facet-member .labkey-facet-member-name, LI.labkey-facet-member .labkey-facet-member-name { - display:inline-block; + display:inline; position:relative; max-width:150pt; - /*overflow-x: hidden;*/ + overflow-x: hidden; text-overflow: ellipsis; white-space: nowrap; z-index:2; @@ -318,8 +322,11 @@ LI.labkey-facet-member SPAN.bar-selected { border-bottom-style: hidden; border-top-style: hidden; + background-image: none; + background-color: white; } + .x4-grid-row SPAN.labkey-facet-percent-bar, LI.labkey-facet-member .bar { diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 81287716..31cdf070 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -92,7 +92,8 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { var selectedMembers = facet.get("selectedMembers"); var html = '
          '; - if (facet.get("isExpanded") && selectedMembers && selectedMembers.length > 1) + //if (facet.get("isExpanded") && selectedMembers && selectedMembers.length > 1) + if ( selectedMembers && selectedMembers.length > 1) { var pointerClass = (facet.filterOptionsStore.count() < 2) ? "inactive" : "active"; From 91ad8752bd296350546354fe425ad2f206513643 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 28 Dec 2015 08:31:38 -0800 Subject: [PATCH 043/587] Spec 24959 : Implement Data Finder for ITN - fix some styling and make dataModuleName part of the initial configuration --- resources/web/study/Finder/data/Facets.js | 11 ++++++++-- resources/web/study/Finder/data/Studies.js | 5 ++--- .../web/study/Finder/data/StudySubsets.js | 7 ++++++- resources/web/study/Finder/dataFinder.css | 2 +- .../web/study/Finder/panel/FacetSelection.js | 2 +- .../web/study/Finder/panel/FacetsGrid.js | 11 +++++----- resources/web/study/Finder/panel/Finder.js | 3 +-- resources/web/study/Finder/panel/Studies.js | 5 ++++- .../web/study/Finder/panel/StudyCards.js | 11 ++++++++-- .../study/Finder/panel/StudyPanelHeader.js | 21 +++++++++++++++++-- src/org/labkey/trialshare/view/dataFinder.jsp | 5 +++-- 11 files changed, 61 insertions(+), 22 deletions(-) diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index 0513d6b5..71bafd9f 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -8,7 +8,7 @@ Ext4.define('LABKEY.study.store.Facets', { countDistinctLevel : '[Study].[Study]', filterByFacetUniqueName : '[Study]', olapConfig : { - configId: 'TrialShare:/StudyCube', + configId: 'Study:/StudyCube', schemaName: 'lists', name: 'StudyCube' }, @@ -17,7 +17,7 @@ Ext4.define('LABKEY.study.store.Facets', { proxy : { type: "ajax", - url: LABKEY.ActionURL.buildURL("trialshare", "studyFacets.api", LABKEY.containerPath), + //url: // set in constructor reader: { type: 'json', root: 'data' @@ -376,6 +376,13 @@ Ext4.define('LABKEY.study.store.Facets', { return row[0].value ? row[0].value : defaultValue; }); return ret; + }, + + constructor: function(config) + { + this.proxy.url = LABKEY.ActionURL.buildURL(config.dataModuleName, "studyFacets.api", LABKEY.containerPath); + this.olapConfig = config.olapConfig; + this.callParent(config); } }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Studies.js b/resources/web/study/Finder/data/Studies.js index 8b1aa38a..58db4bff 100644 --- a/resources/web/study/Finder/data/Studies.js +++ b/resources/web/study/Finder/data/Studies.js @@ -3,12 +3,12 @@ Ext4.define('LABKEY.study.store.Studies', { storeId: 'studies', model: 'LABKEY.study.data.StudyCard', autoLoad: false, - dataModuleName: "trialshare", // TODO how can this be used below? + dataModuleName: "study", selectedStudies : {}, selectedSubset : 'public', proxy : { type: "ajax", - url: LABKEY.ActionURL.buildURL('trialshare', "studies.api", LABKEY.containerPath), + //url: set before calling "load". reader: { type: 'json', root: 'data' @@ -48,5 +48,4 @@ Ext4.define('LABKEY.study.store.Studies', { selectSubset : function(subsetId, subset) { } - }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/StudySubsets.js b/resources/web/study/Finder/data/StudySubsets.js index 8fe00d8d..7a920cf2 100644 --- a/resources/web/study/Finder/data/StudySubsets.js +++ b/resources/web/study/Finder/data/StudySubsets.js @@ -7,7 +7,7 @@ Ext4.define('LABKEY.study.store.StudySubsets', { defaultValue: null, proxy: { type: 'ajax', - url: LABKEY.ActionURL.buildURL('trialshare', "studySubsets.api", LABKEY.containerPath), + //url: set in constructor below reader: { type: 'json', root: 'data' @@ -28,5 +28,10 @@ Ext4.define('LABKEY.study.store.StudySubsets', { }, scope: this } + }, + + constructor: function(config) { + this.proxy.url = LABKEY.ActionURL.buildURL(config.dataModuleName, "studySubsets.api", LABKEY.containerPath); + this.callParent(config); } }); \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 41ba6151..9f068de3 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -287,7 +287,7 @@ LI.labkey-facet-member .member-indicator SPAN.labkey-facet-member .labkey-facet-member-name, LI.labkey-facet-member .labkey-facet-member-name { - display:inline; + display:inline-block; position:relative; max-width:150pt; overflow-x: hidden; diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index 25031b46..21f0ec4f 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -11,7 +11,7 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { padding: "10 8 8 10", - autoScroll: true, + autoScroll: false, initComponent : function() { this.items = [ diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 31cdf070..87ae0f71 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -12,7 +12,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { dataModuleName: 'study', - autoScroll: false, + autoScroll: true, bubbleEvents : ["filterSelectionChanged"], @@ -127,12 +127,13 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { }, initComponent: function() { - this.facetStore = Ext4.create("LABKEY.study.store.Facets"); - - this.store = Ext4.create('LABKEY.study.store.FacetMembers', { - dataModuleName: this.dataModuleName + this.facetStore = Ext4.create("LABKEY.study.store.Facets", { + dataModuleName: this.dataModuleName, + olapConfig: this.olapConfig }); + this.store = Ext4.create('LABKEY.study.store.FacetMembers'); + this.callParent(); this.mon(this.view, { diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index b63636f2..558d9408 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -17,7 +17,7 @@ Ext4.define('LABKEY.study.panel.Finder', { height: '500px', - dataModuleName: 'study', // TODO + dataModuleName: 'study', autoScroll : true, @@ -89,7 +89,6 @@ Ext4.define('LABKEY.study.panel.Finder', { var url = LABKEY.ActionURL.buildURL("search", "json", "/home/", { "category": "List", - "scope": "Folder", "q": searchTerms }); Ext4.Ajax.request({ diff --git a/resources/web/study/Finder/panel/Studies.js b/resources/web/study/Finder/panel/Studies.js index d8e257df..b2684af6 100644 --- a/resources/web/study/Finder/panel/Studies.js +++ b/resources/web/study/Finder/panel/Studies.js @@ -10,6 +10,8 @@ Ext4.define("LABKEY.study.panel.Studies", { dataModuleName: 'study', + showSearch : true, + autoScroll: true, initComponent : function() { @@ -46,7 +48,8 @@ Ext4.define("LABKEY.study.panel.Studies", { if (!this.studyPanelHeader) { this.studyPanelHeader = Ext4.create("LABKEY.study.panel.StudyPanelHeader", { dataModuleName: this.dataModuleName, - padding: 8 + padding: 8, + showSearch : this.showSearch }); } return this.studyPanelHeader; diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index 5b1d3bea..48774d05 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -14,7 +14,7 @@ Ext4.define("LABKEY.study.panel.StudyCards", { autoScroll: true, - dataModuleName: 'study', // TODO is there a way to use this below? + dataModuleName: 'study', store : Ext4.create('LABKEY.study.store.Studies', { dataModuleName: this.dataModuleName @@ -156,9 +156,16 @@ Ext4.define("LABKEY.study.panel.StudyCards", { } }, - initComponent: function() + initComponent: function(config) { + this.getStore().proxy.url = LABKEY.ActionURL.buildURL(this.dataModuleName, "studies.api", LABKEY.containerPath); this.getStore().load(); this.callParent(); + }, + + constructor: function(config) + { + this.dataModuleName = config.dataModuleName; + this.callParent(config); } }); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/StudyPanelHeader.js b/resources/web/study/Finder/panel/StudyPanelHeader.js index d812dc14..1a5c349c 100644 --- a/resources/web/study/Finder/panel/StudyPanelHeader.js +++ b/resources/web/study/Finder/panel/StudyPanelHeader.js @@ -2,7 +2,12 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { extend: 'Ext.Container', - layout: { type: 'hbox', align: 'stretch'}, + layout: { + type: 'hbox', + align: 'stretch' + }, + + showSearch : true, showHelpLinks: true, @@ -15,9 +20,12 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { "searchTermsChanged" ], - studySubsets : Ext4.create('LABKEY.study.store.StudySubsets'), initComponent: function() { + + this.studySubsets = Ext4.create('LABKEY.study.store.StudySubsets', { + dataModuleName : this.dataModuleName + }); this.items = []; this.items.push(this.getSearchBox()); if (this.getStudySubsetMenu()) @@ -25,6 +33,13 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { if (this.showHelpLinks) this.items.push(this.getHelpLinks()); + this.studySubsets.on( + 'load', function(store) { + this.getStudySubsetMenu().setValue(store.defaultValue); + }, + this + ); + this.callParent(); }, @@ -36,6 +51,8 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { fieldLabel: '', labelWidth: "10px", labelSeparator: '', + disabled: !this.showSearch, + hidden: !this.showSearch, fieldCls: 'labkey-search-box', id: 'searchTerms', listeners: { diff --git a/src/org/labkey/trialshare/view/dataFinder.jsp b/src/org/labkey/trialshare/view/dataFinder.jsp index d767c4b1..fa387f49 100644 --- a/src/org/labkey/trialshare/view/dataFinder.jsp +++ b/src/org/labkey/trialshare/view/dataFinder.jsp @@ -63,8 +63,9 @@ olapConfig : { configId: 'TrialShare:/StudyCube', schemaName: 'lists', - name: 'StudyCube', - } + name: 'StudyCube' + }, + showSearch: false }); }); From 7d1e951ac3d5138a80080cc6052a83166f12ff05 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 28 Dec 2015 10:22:54 -0800 Subject: [PATCH 044/587] Spec 24959 : Implement Data Finder for ITN - use proper renderer to display description --- src/org/labkey/trialshare/data/StudyBean.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index 0bdcfa47..6754281c 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -1,6 +1,7 @@ package org.labkey.trialshare.data; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerFilter; import org.labkey.api.data.ContainerFilterable; @@ -10,6 +11,9 @@ import org.labkey.api.query.DefaultSchema; import org.labkey.api.query.QuerySchema; import org.labkey.api.security.User; +import org.labkey.api.services.ServiceRegistry; +import org.labkey.api.wiki.WikiRendererType; +import org.labkey.api.wiki.WikiService; import java.util.Collection; import java.util.Collections; @@ -287,13 +291,32 @@ public String getDescription(Container c, User user) if (null != studyContainer && StringUtils.equalsIgnoreCase(getStudyId(), studyAccession)) { description = (String) map.get("description"); + if (description != null) { + String rendererType = (String) map.get("descriptionRendererType"); + description = getFormattedHtml(rendererType == null ? null : WikiRendererType.valueOf(rendererType), description); + } break; } } + } return description; } + private String getFormattedHtml(WikiRendererType rendererType, String markup) + { + WikiService wikiService = ServiceRegistry.get().getService(WikiService.class); + + if (null == wikiService) + return null; + + if (rendererType == null) + rendererType = wikiService.getDefaultMessageRendererType(); + + return wikiService.getFormattedHtml(rendererType, markup); + } + + public void setDescription(String description) { this.description = description; From a2b6dddf36fafbf05c33f959bf7ab8438300bfa8 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 28 Dec 2015 11:19:30 -0800 Subject: [PATCH 045/587] Spec 24959 : Implement Data Finder for ITN - remove unused dimension --- resources/olap/StudyCube.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/resources/olap/StudyCube.xml b/resources/olap/StudyCube.xml index 779b8f4f..f3b94868 100644 --- a/resources/olap/StudyCube.xml +++ b/resources/olap/StudyCube.xml @@ -12,9 +12,6 @@ - - - From 992cb10466cb01a33a85076f9e406d9254163b19 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Dec 2015 07:52:54 -0800 Subject: [PATCH 046/587] Issue 25234: Fix selection of facet members in Phase group --- resources/web/study/Finder/panel/FacetSelection.js | 1 - resources/web/study/Finder/panel/FacetsGrid.js | 4 ++-- resources/web/study/Finder/panel/Finder.js | 10 +++++----- resources/web/study/Finder/panel/Studies.js | 12 ++++++------ src/org/labkey/trialshare/TrialShareController.java | 2 +- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index 21f0ec4f..a454f891 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -41,7 +41,6 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { }, onFilterSelectionChange: function(hasFilters) { - console.log("FacetSelection filterSelectionChanged handler"); if (hasFilters) Ext4.get(Ext4.DomQuery.select('.labkey-clear-all', this.id)[0]).replaceCls('inactive', 'active'); else diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 87ae0f71..499e3996 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -8,7 +8,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { ui: 'custom', - itemSelector: 'span.labkey-facet-member', + itemSelector: 'span.x4-grid-data-row', dataModuleName: 'study', @@ -38,7 +38,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { dataIndex: 'name', sortable: false, menuDisabled: true, - cls:'facet', + cls:'labkey-facet', tpl: new Ext4.XTemplate( ' ', ' ', diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index 558d9408..a39c1959 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -37,7 +37,7 @@ Ext4.define('LABKEY.study.panel.Finder', { this._initResize(); this.on({ - filterSelectionChanged: this.onFilterSelectionChange, + //filterSelectionChanged: this.onFilterSelectionChange, studySubsetChanged: this.onStudySubsetChanged, searchTermsChanged: this.onSearchTermsChanged } @@ -73,10 +73,10 @@ Ext4.define('LABKEY.study.panel.Finder', { this.getFacetsPanel().onStudySubsetChanged(); }, - onFilterSelectionChange : function(){ - console.log("Filter selection changed!"); - this.getStudiesPanel().onFilterSelectionChanged(); - }, + //onFilterSelectionChange : function(){ + // console.log("Filter selection changed!"); + // this.getStudiesPanel().onFilterSelectionChanged(); + //}, onSearchTermsChanged: function(searchTerms) { diff --git a/resources/web/study/Finder/panel/Studies.js b/resources/web/study/Finder/panel/Studies.js index b2684af6..8a04ffa3 100644 --- a/resources/web/study/Finder/panel/Studies.js +++ b/resources/web/study/Finder/panel/Studies.js @@ -21,18 +21,18 @@ Ext4.define("LABKEY.study.panel.Studies", { ]; this.callParent(); - this.getStudyCards().store.addListener('filterChange',this.onFilterSelectionChanged, this); + //this.getStudyCards().store.addListener('filterChange',this.onFilterSelectionChanged, this); this.on( {'studySubsetChanged': this.onStudySubsetChanged, - 'searchTermsChanged': this.onSearchTermsChanged, - 'filterSelectionChanged': this.onFilterSelectionChanged + 'searchTermsChanged': this.onSearchTermsChanged + //'filterSelectionChanged': this.onFilterSelectionChanged } ); }, - onFilterSelectionChanged: function() { - console.log('filterChange happened!') - }, + //onFilterSelectionChanged: function() { + // console.log('filterChange happened!') + //}, onStudySubsetChanged : function(selectedSubset) { if (!selectedSubset) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 13b6fd5a..2484c55c 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -186,7 +186,7 @@ public Object execute(Object o, BindException errors) throws Exception facet = new StudyFacetBean("Condition", "Conditions", "Study.Condition", "Condition", "[Study.Condition][(All)]", FacetFilter.Type.OR, 6); facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); facets.add(facet); - facet = new StudyFacetBean("Age Group", "Age Groups", "Study.AgeGroup", "AgeGroup", "[Study.AgeGroup][(All)]", FacetFilter.Type.OR, 5); + facet = new StudyFacetBean("Age Group", "Age Groups", "Study.AgeGroup", "AgeGroup", "[Study.AgeGroup][(All)]", FacetFilter.Type.OR, 4); facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); facets.add(facet); facet = new StudyFacetBean("Phase", "Phases", "Study.Phase", "Phase", "[Study.Phase][(All)]", FacetFilter.Type.OR, 5); From 96df696ac6bd47b5700aef33fdc5daf3afb23289 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Dec 2015 10:19:36 -0800 Subject: [PATCH 047/587] Spec 24959 : Implement Data Finder for ITN - remove unused JSP --- .../trialshare/view/studyPublications.jsp | 124 ------------------ 1 file changed, 124 deletions(-) delete mode 100644 src/org/labkey/trialshare/view/studyPublications.jsp diff --git a/src/org/labkey/trialshare/view/studyPublications.jsp b/src/org/labkey/trialshare/view/studyPublications.jsp deleted file mode 100644 index c1461f83..00000000 --- a/src/org/labkey/trialshare/view/studyPublications.jsp +++ /dev/null @@ -1,124 +0,0 @@ -<% -/* - * Copyright (c) 2015 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -%> -<%@ page import="org.apache.commons.lang3.StringUtils" %> -<%@ page import="org.labkey.api.data.Container" %> -<%@ page import="org.labkey.api.data.ContainerFilter" %> -<%@ page import="org.labkey.api.data.ContainerFilterable" %> -<%@ page import="org.labkey.api.data.ContainerManager" %> -<%@ page import="org.labkey.api.data.TableInfo" %> -<%@ page import="org.labkey.api.data.TableSelector" %> -<%@ page import="org.labkey.api.query.DefaultSchema" %> -<%@ page import="org.labkey.api.query.QuerySchema" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.ViewContext" %> -<%@ page import="org.labkey.api.view.template.ClientDependency" %> -<%@ page import="org.labkey.trialshare.data.StudyBean" %> -<%@ page import="org.labkey.trialshare.data.StudyPublicationBean" %> -<%@ page import="java.util.Collection" %> -<%@ page import="java.util.HashMap" %> -<%@ page import="java.util.LinkedHashSet" %> -<%@ page import="java.util.Map" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%! - public LinkedHashSet getClientDependencies() - { - LinkedHashSet resources = new LinkedHashSet<>(); - - resources.add(ClientDependency.fromPath("dataFinder.css")); - resources.add(ClientDependency.fromPath("trialShare.css")); - - return resources; - } -%> -<% - JspView me = (JspView) HttpView.currentView(); - - ViewContext context = HttpView.currentContext(); - Container c = context.getContainer(); - StudyBean study = me.getModelBean(); - - ActionURL studyUrl = null; - if (!c.isRoot()) - { - String comma = "\n"; - Container p = c.getProject(); - QuerySchema s = DefaultSchema.get(context.getUser(), p).getSchema("study"); - TableInfo sp = s.getTable("StudyProperties"); - if (sp.supportsContainerFilter()) - { - ContainerFilter cf = new ContainerFilter.AllInProject(context.getUser()); - ((ContainerFilterable) sp).setContainerFilter(cf); - } - Collection> maps = new TableSelector(sp).getMapCollection(); - for (Map map : maps) - { - Container studyContainer = ContainerManager.getForId((String) map.get("container")); - String studyAccession = (String)map.get("study_accession"); - String name = (String)map.get("Label"); - if (null == studyAccession && study.getStudyIdPrefix() != null && name.startsWith(study.getStudyIdPrefix())) - studyAccession = name; - if (null != studyContainer && StringUtils.equalsIgnoreCase(study.getStudyId(), studyAccession)) - { - studyUrl = studyContainer.getStartURL(context.getUser()); - break; - } - } - } - - String publicationsTitle = "Manuscripts and Abstracts"; - Map linkProps = new HashMap<>(); - linkProps.put("target", "_blank"); -%> - -
          -

          <% if (null!=studyUrl) {%><%}%><%=h(study.getStudyId())%><% if (null!=studyUrl) {%><%}%>

          -

          <% if (null!=study.getShortName()) {%><%}%><%=h(study.getShortName())%><% if (null!=study.getShortName()) {%><%}%>

          -
          -<% if (null != study.getIconUrl()) {%><%}%> -

          <%=h(study.getTitle())%>

          -
          -
          <% - if (null != study.getPublications() && study.getPublications().size() > 0) - { - %><%=h(publicationsTitle)%><% - for (StudyPublicationBean pub : study.getPublications()) - { - if (pub.getTitle() != null) - { - %>

          <%=h(pub.getJournal())%> <%=h(pub.getYear())%>
          <% - %><%=h(pub.getTitle())%><% - if (!StringUtils.isEmpty(pub.getPubmedId())) - { - %>
          <%=textLink("PubMed","http://www.ncbi.nlm.nih.gov/pubmed/?term=" + pub.getPubmedId(), null, null, linkProps)%><% - } - %>

          <% - } - } - } - %>
          -
          - - <% if (null != studyUrl) { %> - <%= textLink("View study " + study.getStudyId(), studyUrl.toString(), null, null, linkProps)%>
          - <% } %> -
          -
          - - From f141f8b81e2ab6db838e2fdd7c3488cfc23c8fbe Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Dec 2015 12:49:48 -0800 Subject: [PATCH 048/587] Issue 25240: make menu icon for filter options activate menu --- resources/web/study/Finder/panel/FacetsGrid.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 499e3996..77ed87fe 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -97,9 +97,10 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { { var pointerClass = (facet.filterOptionsStore.count() < 2) ? "inactive" : "active"; - html += '' + facet.get("currentFilterCaption") + ''; + html += '' + facet.get("currentFilterCaption"); if (facet.filterOptionsStore.count() > 1) - html += ' '; + html += ' '; + html += ''; } html += '
          '; return html; From 7cabb52219ce44922a7c21be603b09e8ed6446fa Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Dec 2015 12:51:08 -0800 Subject: [PATCH 049/587] Spec 24959 : Implement Data Finder for ITN - update tour so it will not try to show help for the search box if the search box doesn't exit --- .../study/Finder/panel/StudyPanelHeader.js | 6 +++-- .../web/study/Finder/view/dataFinderTour.js | 27 ++----------------- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/resources/web/study/Finder/panel/StudyPanelHeader.js b/resources/web/study/Finder/panel/StudyPanelHeader.js index 1a5c349c..36731ed3 100644 --- a/resources/web/study/Finder/panel/StudyPanelHeader.js +++ b/resources/web/study/Finder/panel/StudyPanelHeader.js @@ -27,7 +27,9 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { dataModuleName : this.dataModuleName }); this.items = []; - this.items.push(this.getSearchBox()); + var searchBox = this.getSearchBox(); + if (searchBox) + this.items.push(searchBox); if (this.getStudySubsetMenu()) this.items.push(this.getStudySubsetMenu()); if (this.showHelpLinks) @@ -44,7 +46,7 @@ Ext4.define("LABKEY.study.panel.StudyPanelHeader", { }, getSearchBox : function() { - if (!this.searchBox) { + if (!this.searchBox && this.showSearch) { this.searchBox = Ext4.create('Ext.form.field.Text', { emptyText:'Studies', cls: 'labkey-search-box', diff --git a/resources/web/study/Finder/view/dataFinderTour.js b/resources/web/study/Finder/view/dataFinderTour.js index 3eea6143..420ef2c2 100644 --- a/resources/web/study/Finder/view/dataFinderTour.js +++ b/resources/web/study/Finder/view/dataFinderTour.js @@ -30,7 +30,8 @@ LABKEY.help.Tour.register({ placement: "right", showNextButton: true, showPrevButton: true - },{ + }, + { target: "searchTerms", title: "Quick Search", content: "Enter terms of interest to search study and data descriptions. This will find matches within the selection of filtered studies/datasets.", @@ -38,30 +39,6 @@ LABKEY.help.Tour.register({ yOffset: -25, showPrevButton: true } - //{ - // target: 'group_Condition', - // title: "Study Attributes", - // content: "Select items in this area to find studies of interest. The gray bars show the number of selected participants.

          Try " + (Ext4.isMac ? "Command" : "Ctrl") + "-click to multi-select.", - // placement: "right" - //}, - //{ - // target: 'searchTerms', - // title: "Quick Search", - // content: "Enter terms of interest to search study descriptions.", - // placement: "right" - //}, - //{ - // target: 'summaryArea', - // title: "Summary", - // content: "Here is a summary of the data in the selected studies. Studies represents the number of studies that contain some participants that match the criteria. Subjects is the number of subjects across all selected studies (including subjects that did not match all attributes).", - // placement: "right" - //}, - //{ - // target: 'filterArea', - // title: "Filter Area", - // content: "See and manage your active filters.", - // placement: "right" - //} ] }); From 39b33abc13a43a46952edd4ba167776124b33ed5 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Dec 2015 13:11:02 -0800 Subject: [PATCH 050/587] Spec 24959 : Implement Data Finder for ITN - make summary section stretch in the same was as the facet grid --- resources/web/study/Finder/dataFinder.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 9f068de3..a756aea7 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -115,7 +115,6 @@ DIV.labkey-search-message .labkey-facet-selection-panel { vertical-align: text-top; - width:220pt; height:100%; background-color: white; } @@ -123,6 +122,7 @@ DIV.labkey-search-message .labkey-facet-selection-panel > DIV { height:100%; + /*max-width:200pt;*/ overflow-y:auto; } @@ -177,7 +177,7 @@ DIV.labkey-facet-summary LI.labkey-facet-member DIV.labkey-facet-summary { - max-width:200pt; + /*max-width:200pt;*/ border:solid 2pt rgb(220, 220, 220); } @@ -189,6 +189,7 @@ DIV.labkey-facet-summary .labkey-facet-header { padding:4pt; + /*max-width: 185pt;*/ background-color: rgb(240, 240, 240); } From 37e3a48ef8e45ff5eff651234e415c9dfe46a990 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Dec 2015 13:32:00 -0800 Subject: [PATCH 051/587] Spec 24959 : Implement Data Finder for ITN - make clear all link deactivate properly --- .../web/study/Finder/panel/FacetsGrid.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 77ed87fe..e672b328 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -10,8 +10,6 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { itemSelector: 'span.x4-grid-data-row', - dataModuleName: 'study', - autoScroll: true, bubbleEvents : ["filterSelectionChanged"], @@ -119,7 +117,8 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { return facetStore.getById(facetName).data.selectedMembers.length > 0; else { for (var i = 0; i < facetStore.count(); i++) { - if (facetStore.getAt(i).data.selectedMembers.length > 0) + var facet = facetStore.getAt(i); + if (facet.get("name") != "Study" && facetStore.getAt(i).data.selectedMembers.length > 0) return true; } return false; @@ -165,10 +164,6 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { this.facetStore.clearAllSelectedMembers(); if (records.length > 0) this.facetStore.selectMembers(records); - var facet; - for (var f = 0; f < this.facetStore.count(); f++) { - facet = this.facetStore.getAt(f); - } this.facetStore.updateCountsAsync(); this.fireEvent("filterSelectionChanged", LABKEY.study.panel.FacetsGrid.hasFilters()); @@ -247,14 +242,14 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { var name = this.facetStore.getAt(i).get("name"); //if (name == "Study") // continue; - this.clearFilter(name); + this.clearFilter(name, true); } if (updateCounts) this.facetStore.updateCountsAsync(); - this.fireEvent("filterSelectionCleared", false); + this.fireEvent("filterSelectionChanged", false); }, - clearFilter : function (facetName) { + clearFilter : function (facetName, suppressEvent) { var facet = this.facetStore.getById(facetName); facet.data.selectedMembers = []; for (var i = 0; i < this.store.count(); i++) { @@ -264,7 +259,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { } } - this.updateFacetHeader(facetName, true); - this.fireEvent("filterSelectionCleared", LABKEY.study.panel.FacetsGrid.hasFilters(facetName)); + if (!suppressEvent) + this.fireEvent("filterSelectionChanged", LABKEY.study.panel.FacetsGrid.hasFilters()); } }); \ No newline at end of file From 552bebafc69789db3b4d437ebfdfaab11de276d4 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Dec 2015 16:20:44 -0800 Subject: [PATCH 052/587] Issue 25246: fix disappearance of studies after clearing multiple filters --- resources/web/study/Finder/panel/FacetSelection.js | 3 +-- resources/web/study/Finder/panel/FacetsGrid.js | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index a454f891..99a4dc4a 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -32,8 +32,7 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { }, onClearAllFilters: function() { - this.getFacets().clearAllFilters(true); - this.onFilterSelectionChange(false); + this.getFacets().clearAllFilters(false); }, onStudySubsetChanged: function() { diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index e672b328..44748a1d 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -239,11 +239,10 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { clearAllFilters : function (updateCounts) { for (var i = 0; i < this.facetStore.count(); i++) { - var name = this.facetStore.getAt(i).get("name"); - //if (name == "Study") - // continue; - this.clearFilter(name, true); + var facet = this.facetStore.getAt(i); + facet.data.selectedMembers = []; } + this.getSelectionModel().deselectAll(false); if (updateCounts) this.facetStore.updateCountsAsync(); this.fireEvent("filterSelectionChanged", false); From f8a3266902e4d40670fe75046a318a56972d4762 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Dec 2015 20:29:45 -0800 Subject: [PATCH 053/587] Spec 24959 : Implement Data Finder for ITN - turn off autoscroll for facets grid and coalesce selection changes --- resources/web/study/Finder/panel/FacetsGrid.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 44748a1d..6dccf435 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -10,7 +10,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { itemSelector: 'span.x4-grid-data-row', - autoScroll: true, + autoScroll: false, bubbleEvents : ["filterSelectionChanged"], @@ -143,7 +143,21 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { scope: this }); - this.getSelectionModel().on('selectionchange', this.onSelectionChange, this); + var facetSelectionChange = function() { + this.facetStore.updateCountsAsync(); + this.fireEvent("filterSelectionChanged", LABKEY.study.panel.FacetsGrid.hasFilters()); + }; + + var facetChangeTask = new Ext4.util.DelayedTask(facetSelectionChange, this); + + this.getSelectionModel().on('selectionchange', function(selModel, records){ + this.facetStore.clearAllSelectedMembers(); + if (records.length > 0) + this.facetStore.selectMembers(records); + facetChangeTask.delay(500); + }, this); + + //this.getSelectionModel().on('selectionchange', this.onSelectionChange, this); }, onCubeReady : function(mdx) { From ed078924a21dcd2d87f2b9b4290ee46e0aaf6cf4 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Dec 2015 20:30:17 -0800 Subject: [PATCH 054/587] Spec 24959 : Implement Data Finder for ITN - after initial load, deselect all studies unless a subset has been selected already --- resources/web/study/Finder/data/Studies.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/resources/web/study/Finder/data/Studies.js b/resources/web/study/Finder/data/Studies.js index 58db4bff..b5dc6864 100644 --- a/resources/web/study/Finder/data/Studies.js +++ b/resources/web/study/Finder/data/Studies.js @@ -4,6 +4,7 @@ Ext4.define('LABKEY.study.store.Studies', { model: 'LABKEY.study.data.StudyCard', autoLoad: false, dataModuleName: "study", + isLoaded: false, selectedStudies : {}, selectedSubset : 'public', proxy : { @@ -19,6 +20,16 @@ Ext4.define('LABKEY.study.store.Studies', { direction: 'ASC' }], + listeners: { + 'load' : { + fn : function(store, records, options) { + store.isLoaded = true; + store.updateFilters(this.selectedSubset ? null : {}); // initial load should have no studies selected + }, + scope: this + } + }, + updateFilters: function(selectedStudies, selectedSubset) { if (selectedStudies) this.selectedStudies = selectedStudies; From 70c430ba10e1c6b941f878a6f743866d439e1a82 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 30 Dec 2015 10:29:17 -0800 Subject: [PATCH 055/587] Spec 24959 : Implement Data Finder for ITN - fix display of facet member text for lower resolutions --- resources/web/study/Finder/dataFinder.css | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index a756aea7..1227d8c7 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -292,6 +292,7 @@ LI.labkey-facet-member .labkey-facet-member-name position:relative; max-width:150pt; overflow-x: hidden; + overflow-y: hidden; text-overflow: ellipsis; white-space: nowrap; z-index:2; From 7858a38173af89aa6d8dce3c0d8382b379251f91 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 30 Dec 2015 10:48:46 -0800 Subject: [PATCH 056/587] Spec 24959 : Implement Data Finder for ITN - speed up updateCountsUnion by suspending events when updating values for facetMembers --- .../web/study/Finder/data/FacetMembers.js | 9 +++- resources/web/study/Finder/data/Facets.js | 50 +++++++------------ resources/web/study/Finder/panel/Finder.js | 2 +- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/resources/web/study/Finder/data/FacetMembers.js b/resources/web/study/Finder/data/FacetMembers.js index 934737d3..cc5619b9 100644 --- a/resources/web/study/Finder/data/FacetMembers.js +++ b/resources/web/study/Finder/data/FacetMembers.js @@ -20,7 +20,14 @@ Ext4.define('LABKEY.study.store.FacetMembers', { } } - ] + ], + + zeroCounts : function() { + this.each(function(record) { + record.set("count", 0); + record.set("percent", 0); + }); + } }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index 71bafd9f..426fa5b6 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -225,20 +225,8 @@ Ext4.define('LABKEY.study.store.Facets', { updateCountsZero : function () { - var facetStore = Ext4.getStore("facets"); var facetMembersStore = Ext4.getStore("facetMembers"); - var f, member, facet; - for (f = 0; f < facetStore.count(); f++) - { - facet = facetStore.getAt(f); - for (var m = 0; m < facetMembersStore.count(); m++) - { - member = facetMembersStore.getAt(m); - member.set("count", 0); - member.set("percent", 0); - } - } - + facetMembersStore.zeroCounts(); //this.saveFilterState(); //this.updateContainerFilter(); //this.changeSubjectGroup(); @@ -249,10 +237,11 @@ Ext4.define('LABKEY.study.store.Facets', { updateCountsUnion : function (cellSet, isSavedGroup) { var facet, member, f, m; - // map from hierarchyName to dataspace dimension + // map from hierarchyName to facet var map = {}; var facetStore = this; var facetMembersStore = Ext4.getStore("facetMembers"); + facetMembersStore.suspendEvents(false); // clear old subjects and counts (to be safe) //this.subjects.length = 0; @@ -264,11 +253,8 @@ Ext4.define('LABKEY.study.store.Facets', { facet.data.selectedMembers = []; } } - for (m = 0; m < facetMembersStore.count(); m++) { - facetMembersStore.getAt(m).set("count", 0); - facetMembersStore.getAt(m).set("percent", 0); - } + this.updateCountsZero(); var positions = this.getRowPositionsOneLevel(cellSet); var data = this.getDataOneColumn(cellSet, 0); var max = 0; @@ -286,19 +272,19 @@ Ext4.define('LABKEY.study.store.Facets', { facet = map[hierarchyName]; var count = data[i]; member = facetMembersStore.getById(resultMember.uniqueName); - if (!member) + if (facet.get("name") == "Study") + { + selectedStudies[resultMember.name] = resultMember; + facet.data.selectedMembers.push(resultMember); + } + else if (!member) { // might be an all member - if (facet.data.allMemberName == resultMember.uniqueName) - facet.data.allMemberCount = count; - else if (facet.get("name") == "Study") - { - selectedStudies[resultMember.name] = resultMember; - facet.data.selectedMembers.push(resultMember); - } - else if (-1 == resultMember.uniqueName.indexOf("#") && "(All)" != resultMember.name) + //if (facet.data.allMemberName == resultMember.uniqueName) + // facet.data.allMemberCount = count; + //else + if (-1 == resultMember.uniqueName.indexOf("#") && "(All)" != resultMember.name) console.log("member not found: " + resultMember.uniqueName); - } else { @@ -322,6 +308,8 @@ Ext4.define('LABKEY.study.store.Facets', { } } + facetMembersStore.resumeEvents(); + facetMembersStore.fireEvent("refresh"); this.updateStudyFilter(selectedStudies); @@ -356,11 +344,10 @@ Ext4.define('LABKEY.study.store.Facets', { getData : function(cellSet,defaultValue) { var cells = cellSet.cells; - var ret = cells.map(function(row) + return cells.map(function(row) { return row.map(function(col){return col.value ? col.value : defaultValue;}); }); - return ret; }, getDataOneColumn : function(cellSet,defaultValue) @@ -371,11 +358,10 @@ Ext4.define('LABKEY.study.store.Facets', { console.log("warning cellSet has more than one column"); throw "illegal state"; } - var ret = cells.map(function(row) + return cells.map(function(row) { return row[0].value ? row[0].value : defaultValue; }); - return ret; }, constructor: function(config) diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index a39c1959..8476cdf9 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -152,7 +152,7 @@ Ext4.define('LABKEY.study.panel.Finder', { this.facetsPanel = Ext4.create("LABKEY.study.panel.FacetSelection", { region: 'west', - width: '21%', + width: '20%', maxWidth: '265px', dataModuleName: this.dataModuleName, showParticipantFilters : this.showParticipantFilters, From 88ee1cb21f6b47a5add2aa1222779177482dc8cd Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 30 Dec 2015 11:15:29 -0800 Subject: [PATCH 057/587] Spec 24959 : Implement Data Finder for ITN - encapsulate javascript and css dependencies in dataFinder.lib.xml --- resources/web/study/Finder/dataFinder.lib.xml | 34 +++++++++++++++++++ src/org/labkey/trialshare/view/dataFinder.jsp | 27 +-------------- 2 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 resources/web/study/Finder/dataFinder.lib.xml diff --git a/resources/web/study/Finder/dataFinder.lib.xml b/resources/web/study/Finder/dataFinder.lib.xml new file mode 100644 index 00000000..b292355b --- /dev/null +++ b/resources/web/study/Finder/dataFinder.lib.xml @@ -0,0 +1,34 @@ + + + + +

          From dbc8e18664e9932b04cd1040a3b92e9fe0382282 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 15 Jan 2016 18:05:19 -0800 Subject: [PATCH 070/587] Spec 25182: put tabbed view switcher in separate panel above the finder card --- .../web/study/Finder/data/Publication.js | 4 +- resources/web/study/Finder/data/Studies.js | 6 +-- resources/web/study/Finder/dataFinder.css | 4 ++ resources/web/study/Finder/dataFinder.lib.xml | 2 + .../study/Finder/panel/FacetPanelHeader.js | 31 ------------- resources/web/study/Finder/panel/Finder.js | 44 ++++++++++++------- .../web/study/Finder/panel/FinderCardDeck.js | 34 ++++++++++++++ .../Finder/panel/FinderObjectSelection.js | 25 +++++++++++ .../study/Finder/panel/StudyPanelHeader.js | 2 +- .../trialshare/TrialShareController.java | 13 ++++++ 10 files changed, 110 insertions(+), 55 deletions(-) create mode 100644 resources/web/study/Finder/panel/FinderCardDeck.js create mode 100644 resources/web/study/Finder/panel/FinderObjectSelection.js diff --git a/resources/web/study/Finder/data/Publication.js b/resources/web/study/Finder/data/Publication.js index 3c646076..a5af5a12 100644 --- a/resources/web/study/Finder/data/Publication.js +++ b/resources/web/study/Finder/data/Publication.js @@ -6,8 +6,8 @@ Ext4.define('LABKEY.study.data.Publication', { fields : [ {name: 'id', type:'int'}, {name: 'studyId'}, - {name: 'pubMedId'}, // PubMed Id (for abstracts) - {name: 'pmcId'}, // PubMed Central reference number (for full-text papers) + {name: 'pmid'}, // PubMed Id + {name: 'pmcid'}, // PubMed Central reference number {name: 'doi'}, {name: 'author'}, {name: 'authorAbbrev'}, diff --git a/resources/web/study/Finder/data/Studies.js b/resources/web/study/Finder/data/Studies.js index 4badc421..b04827bb 100644 --- a/resources/web/study/Finder/data/Studies.js +++ b/resources/web/study/Finder/data/Studies.js @@ -45,7 +45,7 @@ Ext4.define('LABKEY.study.store.Studies', { this.filter([ {property: 'isSelected', value: true}, - {property: 'isPublic', value: this.selectedSubset !== "operational"} + {property: 'isPublic', value: this.selectedSubset !== "operational"} // TODO generalize ]); }, @@ -54,9 +54,5 @@ Ext4.define('LABKEY.study.store.Studies', { study = this.getAt(i); study.set("isSelected", true); } - }, - - selectSubset : function(subsetId, subset) { - } }); \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 497facd4..049f65ee 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -13,6 +13,10 @@ /*float: right;*/ /*}*/ +.labkey-finder-object-selection { + padding-left: 10px; +} + .labkey-studies-panel { background-color: #ffffff } diff --git a/resources/web/study/Finder/dataFinder.lib.xml b/resources/web/study/Finder/dataFinder.lib.xml index 1baf080a..f3a8588b 100644 --- a/resources/web/study/Finder/dataFinder.lib.xml +++ b/resources/web/study/Finder/dataFinder.lib.xml @@ -28,6 +28,8 @@ From 2080b64d62db6eca1df2af4374b316a000793468 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 28 Feb 2016 19:27:48 -0800 Subject: [PATCH 178/587] Spec 25400: rename studyContainer list to studyAccess --- resources/olap/StudyCube.xml | 4 ++-- src/org/labkey/trialshare/TrialShareController.java | 9 ++++----- src/org/labkey/trialshare/data/StudyBean.java | 6 +++--- .../labkey/trialshare/query/TrialShareQuerySchema.java | 3 +-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/resources/olap/StudyCube.xml b/resources/olap/StudyCube.xml index e5609bcb..5afccd3f 100644 --- a/resources/olap/StudyCube.xml +++ b/resources/olap/StudyCube.xml @@ -7,11 +7,11 @@ FALSE -
          +
          - + diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 992ca488..0cd6d376 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -33,7 +33,6 @@ import org.labkey.api.gwt.client.util.StringUtils; import org.labkey.api.module.Module; import org.labkey.api.module.ModuleLoader; -import org.labkey.api.query.DefaultSchema; import org.labkey.api.query.FieldKey; import org.labkey.api.query.QuerySchema; import org.labkey.api.query.QueryService; @@ -537,11 +536,11 @@ public Object execute(Object form, BindException errors) throws Exception } } } - TableInfo studyContainersTable = listsSchema.getTable(TrialShareQuerySchema.STUDY_ACCESS_TABLE); + TableInfo studyAccessTable = listsSchema.getTable(TrialShareQuerySchema.STUDY_ACCESS_TABLE); Map> studyAccessMap = new HashMap<>(); - if (studyContainersTable != null) + if (studyAccessTable != null) { - List studyAccessList = (new TableSelector(studyContainersTable)).getArrayList(StudyAccess.class); + List studyAccessList = (new TableSelector(studyAccessTable)).getArrayList(StudyAccess.class); for (StudyAccess studyAccess : studyAccessList) { @@ -775,7 +774,7 @@ public ModelAndView getView(StudyIdForm form, BindException errors) throws Excep { StudyBean study = (new TableSelector(listSchema.getTable(TrialShareQuerySchema.STUDY_TABLE))).getObject(_studyId, StudyBean.class); - study.setStudyContainers(getUser(), getContainer()); + study.setStudyAccessList(getUser(), getContainer()); study.setUrl(getUser()); study.setPublications(getUser(), getContainer(), form.getDetailType().getDbFieldValue()); diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index 9d8f44e8..def45ff0 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -375,15 +375,15 @@ public void setStudyAccessList(List studyAccessList) this._studyAccessList = studyAccessList; } - public void setStudyContainers(User user, Container currentContainer) + public void setStudyAccessList(User user, Container currentContainer) { QuerySchema listSchema = TrialShareQuerySchema.getSchema(user, currentContainer); SimpleFilter filter = new SimpleFilter(); filter.addCondition(FieldKey.fromParts("studyId"), getStudyId()); - TableInfo studyContainersTable = listSchema.getTable(TrialShareQuerySchema.STUDY_ACCESS_TABLE); - List studyAccessList = (new TableSelector(studyContainersTable, filter, null)).getArrayList(StudyAccess.class); + TableInfo studyAccessTable = listSchema.getTable(TrialShareQuerySchema.STUDY_ACCESS_TABLE); + List studyAccessList = (new TableSelector(studyAccessTable, filter, null)).getArrayList(StudyAccess.class); this._studyAccessList.clear(); for (StudyAccess studyAccess : studyAccessList) { diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index 72a6da21..175c0591 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -10,9 +10,8 @@ */ public class TrialShareQuerySchema { - public static final String NAME = "lists"; public static final String STUDY_TABLE = "studyProperties"; - public static final String STUDY_ACCESS_TABLE = "studyContainer"; + public static final String STUDY_ACCESS_TABLE = "studyAccess"; public static final String PUBLICATION_TABLE = "manuscriptsAndAbstracts"; public static final String STUDY_ASSAY_TABLE = "studyAssay"; From 5faf6df40656cfba98cca23e2c2abe1163ebbfa9 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 28 Feb 2016 19:28:07 -0800 Subject: [PATCH 179/587] Spec 25400: update automated tests --- test/sampledata/DataFinder.lists.zip | Bin 29468 -> 29114 bytes .../trialshare/PublicationPanel.java | 14 ++-- .../test/pages/trialshare/DataFinderPage.java | 2 +- .../trialshare/TrialShareDataFinderTest.java | 72 ++++++++---------- 4 files changed, 43 insertions(+), 45 deletions(-) diff --git a/test/sampledata/DataFinder.lists.zip b/test/sampledata/DataFinder.lists.zip index 196aa3167fba0f09e687f6f8885dfd5c354cdb72..49955008c49df2b1261d51e0113f066e4e502c7d 100644 GIT binary patch delta 20572 zcmV($K;yrh<^j6n0S!<~0|XQR2nYxOsB>Jg4HE(bsB>JiBLckw1E_Ocv*QFv0RyOW zT(d3)Oap%yMx)wjja!w9*{YA~rd5KUMx|B@{wdWsZ#|7ft!do(fpD3eg|W-Vz61D& zXJyxv~#AkQ{nIXIgpEQ=t4{UkL;6_~j zhjih>9E1^@1pGygIg8;DFCz)J7)j5T!JF;1$t0`-mJP6@eESKNNeX@Qr~8;6>MW93*bc;2cNrm7#Nn0*50pd^l!-IAxBU z#K4A}slY9`%FN-mVz+`0QiKCV;7{TYdTM_=4#JeE_%!(?90`KLhp&Ps0o*vTtB?lI z0XK;xe*xqtpMt-PWx}=4A9FZ?itljGVFb@9{vej%-LofdOL!!BsDv!U82mFboZEms zL{Rb!+_qyk1o;JD%aaF10>*$_c#q@2TT$AavRUlBNT3H@>U$hU>BF8T%uTOII5&U( zrVQ#b0Hp!P7tV{zK~)4H%B&aCC2%3>eUF1VLy-o`Li!Wo3A}GWbK8SYff^3{*;G2L zGmm_rcIZl*{xtH(^VRAJB&jB-hrY{0Q~;k*HKl4RmAa>JETSp!A;Pzy*oTL$+l5QWPY&Wwe868X)Ode=1`44D1p15cm@?Ny7EoWZ_e+Uiri-t!C4# zv|5c*Mptgt>(!cNkIa$XtSQ;sda_z(xmq8$xLq1o%GOR=2d{c&mH%8WWnT|J1AgFN zx(&cu=sqF<0(*KGos`ir*Gcwjom47)e=3#EN?WU@dU-su#`TKXERBRB53kjPX_s5g zaZOa7tOi`{Z^&Ad6AzD0G!?85q7!5_AJI8MJB_b82tJdHSzS|Z?cvu(iVV5vP^MzNlMK#p9G)Q3dNs;TueP_61Z7%ORT@{cjsh>p%R7RZp2^TFnIPdjQ!2*y6>r zYbTd}RJ?+}{<+-5`+=Vn2L4=by^ZP=mFv){et#350KKY_gY89-D(lQ`2V}Az&ZgXz zWAV1PkO^B2cgy(N+Fe*c-Y^cHf2DYp_7B$9ah4-7#pYTg{F{)`58*v?cZM)aF}Mn! z8tC@%C))3FDAE(W1ao6&>%W5Hx?Hx*a-~f8E$wLjBU2wA7rcPdW9cw5w^Xk-tX6BI z%q1C{g8ItwU&%-S(<+$by;x?cVV3Lgqgkq%aBQORWm~}v$gdTQ7qU)fe?Azd(lg5p zV8hu+`pTx+EQX8r$n8J>`+xlBfBb*Y%nb?k@iPw`(05R03KD@hB8g2zpPjcEmp8m!s3wm*F#A`3fv}@ek~5 zjfCG-tS?CQ6k$7v@lb}2sPyqr8^Fue@R=H`G*c6;khv3 zTmTzGfhH~R!H;L80Manuc4w;Xg12kqcL1c|>nI4QEM{cD{0s@{3sGcn@(avk!N`q0B|o+=W{BZ-$j(#! zKtAt<1O|oxOSk|y_VY|>tUQFQs5MsUgJt)0NAI+^g?oCoLi}H?mH*#(;Gt)FSJY;@ zs=D!~9=?2gqX~Byf36P#-~rwM?+cYlutA`Z@c=I^m<|AhMYCaDs9+(%bG5f_*OhQ4Np#V* zgU(--1lo`TN;UQn*ofxxU+rx}ZO@D0F?s^s$_eVv5a|@5O>xnApS_jPRyVI!m{yt@h7?sgDHJ&p264?bC7r983Uu> z*h_(Of1=YqIP=%o)|&xvWagKRhXDQ-%-N+kp7|2Ec?AMG4v2}XL*47N8}V|Lo!pa- zA3FRce1@-4Hu}#!P#py5lms0*uBw z1#o=?d*Mz@R0;(e&A>>_P14P0;?>N78wL(Ae~zD4ucH946UMO2JKTKm4MTTH_VBGX z**674s9=&T8%=BC9GamuIZ ze+>=P-^Kxd1&Wfb#rQaOFyRk8W$*}I2u}#IE`W>Al=TkKMV?hkgP~zu3HZ!_faFk0 z99{ApRq%7{0eAsjeKChSf*)mxJ3~ldU{#r4Ol9|9uGLk zO?dUzp9%P=6bv1_DSw7zv%w=DX7_Kjf4_AH*G6{Vs94dQ1s@+JITf{e#62Jc_%oDG zM%s4ohVXQkcika?{cqV_x6}R%lKCr#GX>`sPYajc`!rz6OOqIBri?UmMw;W0n@b!t zbLNqud}P$sj5~ocDP)eo;K`Uh1-?rPDy8cRlxYA1z_5~KD++ye%?3ivlF)JM%M$)hh>6s1nN={mEg{uIh*-#(@S94h_r%;+6GE%p;5rtdRdgA zMuQvJi$1#e`v57^h51`D{J^&Xe??H;_1_xD zelnM4U@>y6U)3!nKqr_#@FQFZo*4~vWuq-hF-*9p$h&;T>-_>I)15y8Zz%HF04w_h zRtW&E+6YRYvg+*S0e=qR8>a9Tlph2p`0TP*u*5ofSn3TJm(Ey{)z zxCTi;_v)KLSM`HH*f|qqXym`fX+?f z5_{%^oWLin4YAYO5O&oWFd`1BG|p_0KXBxn#E1$-glE|>k{I$WfA=^55AKxW!~hlS zkboaQ`!nwFXMoYvgJTbG{S4gx9fQv?{^#6}1CSU&F-n~Uskf0*i;(#B*RKVOhs?x+ z=t_lD8p~{tUy!wFpe!qpLC2Iwkt4v@d!LYUqzeWPw@obnDb;otHG9O5%cYDapEhbE z){jE=Zaz%Kq>ZC>sF}iG|N|3>JYaN1x~H0fa)NvR+FfN0&Id zz*!?>GGVa~EPje%^90XFNima0;6|ml_*2+z8ErO|lPN$t@Z-sJ7K8Qxe)|~10Sf}11xWyiQ0i(n74Vhw8R-;kzA}o3J}o2JboRAFe=qHW9{MXW7Al)VCpdi+ z1$&C&s6$V&iK{Q2?%>10n2tm@bE2Cm(anV7W=?T4CAXPU+eB)cirPO1Cm2um(bcCo zqriAmM!2a2ip+R4RWut(e-?tARIPv^Ad5#ViB*A-V%zXoPqD*Z1@}||;Fv;=9C!{F z1khpf-?x1Le^pkaMHfXrl-V3iw2*;uBeYQETmF@@YfO+gq_=WANZB~K!K8cjMPml8 zjYUPbD+SSwULUIj*1P0Q8jDY3vZ!%X5daWeNzx=K1JG=@2X_~TgXS#fw3&0-EV9{D zY&KIin+b=_oWthNfo8?XPA)=;ZWHd1UPx}rR}paVe{+IVoo6?K=vaiqnkdhE6;UDT zn3=koEPIEh`j-^T$;1?foOOPfIX_H|8D@56Gvfq$UuNm&AgF3|$G|U)VrD{(vvXz( z2dq{(@d)BfOMXy{n$mhYRtlJ!#^7rMXzs-;UIy#avm%OH89cK#F#NNm%8cPf(Wo87 z03HC4e+-nnejW!v(#e#&>>E~h@!&aR->%6k{=vHF_1jatP#=E2M#g(%@x#1AXAlHL z??LySmTT|t7Tl;bBDM;5SMAR53*oL-YM?JgdK2*Wf)%V*x%3JA_e$Ngn$<>9{eFyA zC17#wQIYoXrqex8X?(3@wW{^QmmaTj!H9ftf9sn(uyW0~CZ&G?9H!d2DZqa_mwyqaZI|8XM!Z%2#Ol?O ze_5}-qsNGv?9cLQI8BSqvK)Q91yuBnpoCv@?ibBx%D}K%0i>rqLLqdf(e+oJ#shr9M)WCXqrim1ti2N($) zEW}$fZF7lUNl1>%xdfU5e^~^3AaygxOeDf|%%^l~=T0m_RnSPFwV$yy3yT{+2SRuw z9=oiw(N95obu)Sy-Mg~cZ$GptnqxKFT_gR|8LL~LSh)h83jEXDyHPj2-XqfYKyFH- zY_%HJW;f+sIZC9&#rY=xxKhsX%4RrDXA@|{c=Vl|UIkj~0;H45e>qXMGK@o-@`e+i z>>#aorwhXSW^kJ23l7myEkitqG#iWj{T*{A;DC<>bHE{YLJiUxl5@mrSSunk#bW_e zMX=@PVZDHS6KI)G%nq*}c{W+Y3L`eKWfC3(g|0^8u|=au$;_%*-rAquBsEi}n#fdB zg2!ZPOf?gxnkh%kf5^f%Kw_qu@zIof)EMp~H7f?W(ZD$Q1m=!_ixZ}X%7*R>u63sN zaeNkvL7@1=^KsDDqWsJ{&(iL5uv-Ejv2@{wAknT-EGBMLQRtu%76}Kdd1T%9D5?1+yWyf%xZ#c zBm1lRF3^nV318I%suJk2InFs^MoQlX89UJFB3{7T5?+|meD-rdQ{6>$p|Hxtm++>X zAO@eXG}#oAa6%b$QLgk} z9?o>Z=1`6n8N2JRb`kTDqa7Y{u)l_2^IgONdIs1Hf1eZrvYY!2R#uyCU~Z^OC@i(w z7sd@s{*q)a6oyplqTXzpE%NM3I}Ytk9`5m`-RVB!$InVzjFZ~J(6So!iq)!>4`XN% zJnF`m90L!5$N_SZSTbM+*l{Eh)rI?x$0Zcam%mA)K>Y$w>r=K9l zFf^{7f5$kS^4m96tqLafJ-i0k;UW7S1H?Xvv>wKX{a4QsbJqX7IR9(|fbdcgjsLjC z1g$53u-{G-?@0@7-HQm+#`6KOWHml9t5P+sYH8cRq7EkYH5r!bkJe$JRIfA}wbmP{ zg`J?(C_o9-*a~=@^t;0ktP709(&9_fKv+gHe+2o+;BS;7$9g*i;|rRa2(zv6#7+k7 z{s*v#+*#ziAqtulNh^U?fY&}q%0LN5l^aHak5SL%2wkh_*1@QbR(7%CIStL^6(Kes z3zs7E$z|~7B5--t2!l)&s(5AF1HFgyVTZrIOY=P=EzMkKCP|Hn<@t-9 zxr|zYwc&r=mDTNsX;2Om3`aUO%rKz_rXmt*n5l;6$?YDOKP~E{bd_nKWu6u)h_Og8 zD|nJiRT*fq(s)~~uQj><#9XHF@~KfQe~P+hU{6I%JrDo{IKKY|tXEVBw`leDkj?IF zgx(Q$mx6;5QT6B)&bUv>i;EaO2!`&jGMSor!Kco%V4V%^qie9BS+10TIB7MOnjV%# zfaaR(zLgB4@B_)UNa7I$33c&45aon2IhT7EAn$^Zs+wG(J3z=>0DuXmC3Ahxf43bv z(Mh8r=#^Y7C2gY#rWfp4EzFbHR;&lRVHlPT*10+sj*@fkI9mbx3!n#KA; z;iWn=H$-=^X*D}|wRyYFKzXZ=eht=s{q<{i5mT3Kur1J&i^K?6mk#}TUmdd|M!_*6 z{sH04oPTMf=v>z-nY)2sd1Rnje@1%~s`%IiyCTHB5o5g>IkRR45yxTR9E_bCjuYGO z05EYNK4`*bXC|#`C*Q2+vB4a4$>HigZMvn+EYpcoGysmww6bn!&UBw;rfkMgb5S#| zWgH7EC|=>?l1*CB4x0b5pQ*UYLVSl&xZYAYlx>$VTsQ7C!kQ^z&6Kcae@0jn2&)-^ zN9KJcKd{4ueM#&gTnan1)3C#XCa9AkU#V{!EzZSBv_`>eDa0#uIloF~?_&$r*VuwR z$z2IwdG}7coo+rq&8eN9sJf6|ENHlda;C20jyDMmD; zq!`e@<`aL0hUzyx8F`ESzUHM9;*>~J%WH5b+h6MC6U+MWsnV#|%|;UC-!c@Uh?gfY@tTLKw5&8M zwMu36`hn#0DR9oxAHJ(G@*&+Qq6gUjk(!1v(^~mhs(&okK8{CL#Vl2-wX>BKeyc9F z{ki$mnwHMSrgZ6_f9SN;jD<nw z^uPW!mO)ZYvg#Z%bq~b+V z--~fzEX_=nc)J@~D>j3=jw3Wz1(O7VW1IAIjL4{*)D)r&~ z@FN_ufwMh8e?ea^pi0NbzNlDm;CIM~9?U^>WUTCc@(mN3C-L5}fE62#Oub@}nKJw|`2^>EUnE_|fj<*(HJPTLhA$Ubf0l{!_c0!lZtJr3vySeY8%Ru+cozXy>Lq<M68R??&&P_E$*wIqQ%&p3j)FXVSXjE8Uf<~vW} z*bkm)e>#w~(UGOv^$5P@HKZ!aY(io-1WK#t*uepy+f}}88Sr((vZ0A*tWy2Ns->n` zwYDypUvBlQKGIh_-eeb+(flwe(N?Qs!TlXBC0Z*Po#9=(+MQ_r>k>HJ*oh=ilZCc} zTOYBWqU~XavS&I#R?z3ODV7vk3sY1Ks#>f4EgH3g;+H6eqcEUiZ zPAie(WE^k9`ovmR!)%qeF0wBIpwxU+SAWbddKlI>8?9=!RXrf(~9hJM&&~f-Fl`w zFbUVDOy>64k;AKUqS>5oxKH2rPJAjGZI*IsxX7vD22K^uUV5O`F;Hik&dl_b(QsQ( zwn=OT8!2wy&DCwvh%0$bVMoyQ+@8KafB3j9r;p6%iqT&m>Z9}`sPY#c;-RPV0IjD* zhQ68=63)GG;JZRO7lUDY-suilsAdy=iF?TTJK=rE;m=Oag`?=fNYfqEyZwN|~kdB@pqNUs`g9m;E4bvik~ zyD7j23BcJ2*3G^L=Ks=Az7ga!ePtz*H$!tZ(zg-TOK`O+)fJ;IGeMoAkE}m(){=1I z;VL~|k+CtzVtB^=7W3?e)7rssN)f_q99@>J&FlCcE@O~tic~K(u?MM%f6Y#MYw&&P z0(fXq4NIVo#XX+HvGFP15lo=B;Vp=ITz&KOq<~LNtDxWs{in!Jp$-7@Qbx%c0DlX3 z7J)cTPJ+51hwq^CaLT?o{0TAlB4}~l3og*1BZ1c(2jVq77r5$%M!6=3gBq=Q@q8rC z9A7>pc!n{GksTDcJP&|qf9kN%srKWsc4CkiFK{wb)qsI755Dk%@Onwx{<0JF_W8jQ z2jF;ejPTt1QN#vk$!Xf_C|!1j{VsMBUtsdyLC^Ov5sO!a2UFN!HGpmU6Anqecu3MP zIN}!9458tILCaBqp@Uw(%RpaIGGd=&q(-lyQRL%B2wRMNT#&a(e^~x6pbfT=_j|#x zUdF9z``F(L?dk8r><>CeCg+|i-K!oMunj{+!vLx{BhQzCNM`rZfWMdqfcbF<;#Roo zoq3OV9)Da8iU1Ph0Av#xtbKokyCTtD;?C5EDZf3@7Gu z)^;%2G=MvyX+@s{fBE!;$KJ95@|abTvEhW zI(A)~GK*&txduLdoj18RYwE)(e^q72X}Lx_@Gy&Tf$}y$kw+!!uP9j84?fuh$W?2z6yfq< z&tYoNa}PfxfA-VZO~w8oWxtsob~8KdCe1`0`xrA&oIkSIY8fg2%`_a;U4Q}+360G6 z0CIp*Zxt%lwoug!v)vIfj1&V})c$SUX)w;v1}|SJD-t7HSAmby6KE#;I(+9RAi;4a zF82Z?T2g!J%Gmyaeo*`O_f2epn@^PFv2D6~DSdt%e>nADFt_h511hOh+>DogAA^z_ zM{`7M!_OQPIubI5=Em4t=*nbdije(4Pzlx~dC zP!RxZH?)5S0lcX2TONg{?mt*!k3pyn)t976H9IPDlXla)1JUtq5tO{mDl(X=3r z)*E3bf9>l*2R@E4<4P>85Z&yg-~X_}SWDb%rD&6$3&Y6qmg96+8Ki+-6Gw}fz1<$P z?^B?wg_5?4_I<%~Bv=|dmw75&MbW3+p%h6{hmlXt^%PPxC3<8$Ye|%DUN*3e^_GD2 z7lakKJAoUAfGcszE)k@m9K(;DIr^U?dKuB&e-jRn1n!HSoL{zkAK2f+<>qd-o%eK6 z1$Qng)xY0D5yRc2kdJiBb>_n>z^0_o15am)h}=BXfxJ=-7jc*&HU)Ghj`x$D>z&O` zZ_uHZ=NAmHbiTY_OlE+jklC`Wmh(9)HJ^@UWX2>sY~u2f5m{N-Hk74#sNjF>qJQn_ zf79gyh9m2uML9^DmSz&fOuI+L6Fx4C?9jv%q%khiZpTr=}U>4?YeVf1b130hWOXe>YTRLxG5XV0`g2ePa~=_#tuOCJ5Sij9-3> z(>=mh4#KRjCi4lZRAm=fpDr;*g1Z_Jr~@(K%FPahd=5EvUSo{M4@Q7yTHbjMZ-&Rd zI>$`ui{zfaZU~@O-h^q;XQ0JRe^bdxKI$#ujmmk}Izt>Q$1c5z!*1Xdj9H~tf5YZ` zt7%%5N}>IJ`;vpYS!z9|hP1k5J@DNTi7;B0K!+MIyb}0Hdqg_3FR5d~YC}#lW4^LGfi+s7{L@?1;R~GcJd7h_ zHZKyr56JP_g4`q1%qC+wmw~GpPSSaee0-~-%`i04SQJdUIMjt&Q8ea4 zFsr22#YPH|T<&Tz6m&0KYU(>baJ0~(m?cD}Vlxvkdtq(!*vR6Hp@oUjf2!cA!n9$O zl@D*Cb5mcox;azo>^3!aCq^Ri+FamuqKU=$=(|_zbpv~3#~iTnBVQAP3lS0+s^W^& zqs5n(0c>Un&KE4G=-dj8ya|B1<|~3`6YSbOO)~*Rzx>_3kTe?Liv#|Ks<%uPZQDbc zqEa4#1Nw**JCI=N2Vq3ke<9cl@TKdWMg{+_7(me<4?4LyV&5!`P`h zhEh@sk;uZIhTXei;&Z74gPdR~Q@9Pf@UtpmRef`2@=^hyLRZafsTo;1tgJ?*_AXq> z2ynq&^Eu&K>f_SXzH!R3q{&2QnaOi*;p)!aXXWmMtEOI6mg!flsE;G8G7DpOmReKN zd*ala9%ZT9i>o)M6IwoQEooj610W>e?P9wt(L;jpD}eJnPXAU03`7G=uK^;ggl`aJWg#i=nXl zteQmcF53_5dqkJk#-z>9_%Y?RmfIJpxt>_-7hk$yt(VTMe^Raf5e;phTUD#ttd-bF zxnu#NnvHS8;w@SqQ&Q_=Mt03!ULTX1VdA?>P#gR5<5TQ7A6H&JH|EK_-!{tzmRzk_ z-aL(Mw~f-PQ*A&gysu5Uwt6ewTQnz&K8G4b3RmOp6GmacjOPI@UsJ)l_Q;2KJg7IO z6U|PLZ66pge;1&G?@elD-ok5!938kl9L0>H_j&=uovG@Bx$^&D?E*Mc;O%g)TeUB* zM&;^BZfCS8W>6}%EVJIIw)U+f#Al&#ZU<^ywi?xXZFAGY?rY5(C4+{ok?i{Nq$#VR z+EbWtpo?|1+WQh-bUhXPqjIdyR664TOQVJbx$y>ke+mG&k|CN**L{+2*zyk|?OwuF zv#$4|Jd6bHo1{4Js+hw)u2c+Y-(*(Sig&km+)fG(y>K_YYCUc^4ecPiOfFUqf8c86S1vK~(D6g*O4i1ahcg*ruw&TA3Pb1F ze|j?e@kr8U<;AUA11_n~a-hN>I5{0d8V`Y>AFsLzHv9BvU}UPL@k1z#E(A3l-umof zK(U#KB6XjAkRA8fjfjI5za?1bZpglUV>ba61i#>LPQ2myvk2AA*wc_*TyL2af53OE z7Sz%pO9`+$^fAs@rH;)YVDy{tk5Z+Tos2jlE#(1d0L_7{xO%N#uh$M22yRr3boXP@ zBg<5cJ@r0`KS8Qt)H*A*9y7u{w%wcj;s3A|dse=a?Ek|U=(gm3tLEc3=%~J~&BCl{90^+6va!z;@`+^(uZCfSY zu}^^d6>vp7dWkp`Isl8uM1u=RP8o(ccoyvQH~c6DOMRLJ6aF;@`2UcIyQK4As}vMn zO_^F{K!;VPVL+b$!lS&3e}?89t1UAN=P!3Gxkt7a1#m^OqDPHk*dimoX^00A&9rm= zQBaonXX^YjbN-Rm3C*!uXAI247mjRRHH`iuen0^#{HXa-X`&nB20ohYJ0dJ!Z?_f3 zG!SWUTgFp{Iu(}f?1C_K5J8t?YYPadtC(J0KdgK`?si+GY8F?hf4&P?YfzH}{uRN^ zoMj83au!EV9UfRsgMvl=M0nDcs+4|(^m69zY)ZbzabfkAyvjhd0vJaYUhTnkmsOV6 z8!RnV&HTZ`!6+*5Xq9-<7_Axr`WGix*vBn(p z$^Td`5Q0mgB;wSt9!JK$!*hRAMSObd$w{G|qyL`HN7>&XF(5qEcO; zN*KLP;eHAjSrAH^Bzg(Y7-!{Qa}1g3n{!3FUR)Khks)FYe~`2ak45eHMU!`z8enl| zk?`i2&%?O>5B@Ae*8Z%Ff5{^kXhE}_-yzM;d5Eb790Q6Qu|ax(@1oihXoGs8h-J|( zOWLk5fxymr?1)9ntd;J;tGx%k6GbmOhlZI!!&J~P6I_r+Rk$z{TqI%;adBkyT8qXR z8{^ISvvl+hf9Qw1{)M?gJFZ0=0e+>rr8emkETKrt+&&n_YO`$F2wP$Juhgh-OT<*R z_A>k*RJSfx1c7n|#cGzV_rq(oiDqiNGo4_(X~Df30CeQgYO`#pWTg^)u0)uCtfHgJ z?ZAiEVjA+uUwnxY?7PS!`GCAdFoiPxNTO;f*u8|!f27w`pC?H)e#v+85|<&W)~;eq00 zm1?zGe>u<|V}f4AsIuRJWMiKB%S@%0Fq>=+X4KP$8OWrY7ra9cAArqnODfd(~E?W2DX7AdS+mP#HEj#WRGxM%^4wBKVtT z)fi;*3b(juV|LbwEUhs(C^T4r8F+bkfi^1dA9b8>rTrdmvmRm%!AS=yE?xt5WaGU&*P z6acPQ0#Ugm@Z(nDXi>~7Nrr+;rJ1ryTcB^Ic2%!l2GJt2G%jo4xQ_AH8&pm-1Cx)z zXu4N(i9+`KG4L?p-a@tOLD-#lY^qxQe>IH8naJaGWp!Ts8u25uyKX#yD*y(QhZRVL znwmUi7^Ns!8KzjUYjg|aZ4)Np_*UN8GDrU>L<=&;&T_hC$8?d|E8_=8=4b;r>d&Tg z2Rpj$)Cdmo8N3O~@N83aaYGhnc(I<3>PYRRo)3%RXVUSbv>No{9VVA5bO}SRnnIaIo)VPzBI&B=dlN8r|erR`;BUD>kyQvD4 zcFpwT^H8PcpUb5?5rIR0$c%rZ-mEnaIB~eyG%kcU4kU7x1TOFuOOR+a0CjMIh?^lt z9X5iag=0_yI0nf9Usdz+aPwv8CA*a5+c56kBrA9NQW4><16fRtwqZ zuQ(cUiurB+Ocm;VtV*Kp8Nj+4g$v64%9(pKrt5bhUI+x~y3O{tO*qio8Po?54YiWB zoT@)#l~46%%WTzEC(KT0NG^`v26hM@1_oBS(Ja;K2TaX3TShN)D=8F}e{Q(&pH?vP z48@`?W)YtLCmh31<*w9jLYAJROytU`o(Rp`L4#6TV-G>n{os5}dR$RGgTjF(Jb z`#LE6+NRcM55(zrK^W;Q;^@oYb@1(g^aq?65o064N+YzWR<4CW4fHU`ptdi(7JEcl zqx@-LRm?Kg6_u=A-CFC+e=0quI6HI^h}CR1twYTOL!`Azs|8NDQr2MiL7IclifJ2HzNiZiwS>}?U?Y#%$Z{nbJEo;p{aAmJgza)vW`@!*io)#w1*hx0{chHupqV#u8`vY<1r;3Z7f;7Sq>5S8fhWxT!OKOAr&_>?S$ zv?y!2JWQ({>vGU)fA9?Lg?g|%-{v7k<_BnQ&Va`gtgt@Uipk(d7tNyneX%lavCvBk zvhD|?B{Rjc07un&)wHS&@=L5;b%|v?mgp^8;2a@`Q)6PkLclQ@kYX4f>+pqz`R3QadBe{(WhAX6DxSmnsZO3g@eIBKUa zST0PnTvkWYvkh?}*4#@kR=RRmGiK?0FV)o-Or2Gg@R_*CRGO-F)(e`?_5n>H?Na;j zMh53$Djhz?>1~pqQqNO+7Dz`~!w9PCmbz{Opwz5sbLw!-v|JZiRnkDNE5A8}O{LpKxtX)klv-J)zib1gL;dj^_X5=~ z1NOHn^CF8A79Ubtt#YcUHMV4y>nqgJu2o<3C}xb+F_`}`GbZiV9lIMc8pgfvxpU$8 zeB`KtE;vGCiT6fL3`x}ih3w8(O)~Kx}JxEBdK8_RZZ<>AsYjwZLF@DQF#Fv%Ap8+J72|ub%;s! ze;^s~e%Hna1(TSP%L{8VLoidtERrMXz&9Yc04lyW!#SG&_WxD?qS15d`0VZutLgZ6 z4i+@-+?_u^t2cOR|3n`s{m=jQKm60b`=|fUHy?b0FP5`XH&8GC><}}IbNJ|g|8EiP zW9vG8a*40FQxo2TsFW^^4n_EW2gXb|e}*S-JvpOmsc)W+Md+#@!N$MNKJ^8^ZHik7 z;93OPU%Y3}fAyBfrYx;*JwI<7M*6E-C4fT$0EKVZ-`Qq>TFGi{o>{VgQ-7-k=uqoS zLxFR!2pXqs;>0%2fmO}8sXK-u=P3~2!NHLN31rK0VlwIT!4{1ws(V;5eePK}e<3!y ze3u|A;EQy+u8{0rCzJF-dNwd zAAv37Qja!d#??}-;3CPj0#%AxBr^s)_NZziLSY9&mA&#Aw@J$7{2`LB;uEsufcTaQ z;A*yV4)O9yLqWW;B?lVSX0y>c3dGAse*nY;+XRgXH60`nSWs)jje{&ff5R6S8^iNq zXlI5qN(CxV8i&N3cor*(vm`qnxF>;|Q+b;2)Kc?qX@k{*Xt|5Y2zvW|^Nm3rl*Oq{Z;?Ko8~wJMdG^+srZKTcJRYoKPJ zsav$W0zMkah}wfz3^I93R()a^zsIRKfxr=lzyq)#;TF6ZPzP<`e~cq{yzhs+KqgtQV^?d4!&zfsHqS`jbcyh7ckW=xK8YCcnw8!7 z0OA!r6wQC@`D5W21__E=Uw~xCt|J=cqy&DCHa-RzSir7{JAyVY9Ju!95jvkP4}VY{ z!v63snv+(o!cM9R(d8}4T>uJMx|f3_WY(P&l5Z`7dd$BPyK7z3IUYmP0IC4FyTwX7y!8S9ffPXS$bo<=zheqO5hTp~ z-%$zO`qNXelh98~D2LVo&>`4?f?MSKF9qi#BY`A z<=Rn@TSmv^S$YAu6n=!APE@{1b&jBeRCFovvkBSY&w3(2hmZzfEeKzD1Ne;-Nt5Pr zWvv!@f1(Vp(q&`uByI9vT#4EKp4|aaj`5OLN8--qTbZnDI|luClp3{4qjn4*+^QJY ziS?ycDY+aRg*NN+05cTnLXj-*sZ6w0yX$as;g9DSowWxc|M0QXhW60V=%s)!uv6I1>ob42sJeoiJ+wqI$+L+yo#G zf53axmjR_)7T)1+oU|%A6SlK@t!BA;46E0wW#MWGj@FFIbhI&eS>A#wQ;WUA_8b>J z&9KZ`YzVgg9tS4)7E_1jAIDEGB1C@Ng0(jRvtchfE({kqDc5!+XtUL@s;y%Xw4RBG zX(A#LVy;tlKTQxU!!V&*WlO468jWhBe{mFxqOmG#Lh5QEP)5E&W}$7!l7L|D6dV(j zK`xjbE-E^7h7rReo;Y(`xLCgp5@p6*kXInR^n}W-kN&V4Xn;L-PUL&$Ie(^UVj`Ht zgy+7Hjx$$@mz~4Yt5o2_bRNni*I)FT7oyZBl~P?XvbrTBtM!&udaKKOKSnl-rJ1jM_n1{w1iZ2bO@X_#jZhrQivC1J7!>3=tM19WFJ3Z;G$-yn~HsN?@D*c!i z>^y=Wae{>8_zFaacXAcB&@rO(SMD=HKsI!3iN#j z8q7$t$7UfUCsvaB)$>m?fJy_f0A>P_^%$u zsU0;~hX7PGR4Jb`H>Yz@(0|NFaWp48m&SU6x&eC$1F;Fk@6lm)5!7fPsedm(q>MO4 zxM_A}@|FuOpQjYlihMc{zr|y$2%eztzjG}!3isw>oPQ1m zS93>US1H%)&GIo!*mBwEf2VC1=Xx| zQ8|h{sTg1{K5+~IV4CGWqH2(15z;SV9Aag$7}IZQWQKl$Ytw&aTnA87Tel8@(0do8 zH-&&82ucSDJs6r4DWUfgns6xzDAE*#00IJ16_93vRFU41B2797QY1)G2u0uzzW=@J zy>Fj$=Ipi3nptbUb7t*tX3zPGa!3o!l6Rtx-)Nop3ZISo6u;jfOE`n3)*T-g5)*A? zQ~!EBS!tTO>UHO|6#DJ;0^;>t2Qo>y0+h#gTzxuh*A#iIw4jqW|5WL_RAOqr?fyIB zZy?Lkt+z9r`zMksiD7ADQYG`bTf*APe7m2nNopV+t*e*?9t#&$Z9M)eMk1ZjGP+N zsqhyn;96}9ivD9JGhaJQ4A%O_O*=^UbT)OX9@6J`F&;-ZvtQsw%D6nT4m8xc2t)KdN^J=H8mS3Y-`Cd(Wb`}J9iOD zFQar;F0rl4eyE4OTm2O(#ZYW3}vsP--bVH0* z(ekBalq-4y)h!G9!Uav+RgYPK&?H_Mx+FuYxF7c3PTGq^jtitPy5HTc42BA9zZIp% zYfyy5d5jB~T52(7uF`)E@najgK@HAq> zg6So5^1~w|Tg$}HL0y!wayPBC9x9=po51Dq;j;o{l`oecf4NFZ!|g3=;Zc8%Jo2bj zfQc=Z77fPV(jzC~xM6jx0D`KXU^~MnA)gL{AKN-f?kY+4MGI&V(XF~v!m~fRfp`;p z%nwbZvDNn7wkOVY*-R$oCVf7jtov%M=X5YVGV(*1#{GFs0Dy&y2=EWV5p;`wERrBN zq7C?C&=Kaw0fCMoY7P!g{{9jH{z2KPP>*&=>PJI^NQ$1cuTLG^t~m|USO(QDGXpK| zP5&H5Hq(Q;f-(bp`Bv00dxzuq&W*Y^ENo8lm1DY}#URor%w{&7blZ!X-^}gQJ=qA< zwhb>BKMMdX%Y`EMBR03)I4YjYh`Q)bblP$9;>$@pWEu9oTwz5`@u zV^<@&_voWDo{IKEaJId-KO1Uk)^L(zQ&*phS;q24bqmK@AJ32?ZT8+oH{)7mLBID` z-mW8}6eGp8oGACpJkMA2O|x7E;|)0KET+==&1iotlz%fv5&x}X4A!7A_3X9`%;F}Ia=D~W0~*EDi7Isd99!)s+w32!56nM9)kVB zj*~t8@-CAaSNh~_tps*3gKU%9bu+#Qad4hs?$nK9T0_T=0cF-JtP!FU_!ELs3>kT$ zqts|55de@)0r=;OOrp7rb%E*=|JI7Bfg%1!G)B}&mHM~G%O>^rBQ(7WE1J`u0txw~ z=G`Vq4;l`<31U;lbj#|bRa|v9(G~kb#cTJ@kS~u9>~})Ty*#BnrGm@~q4OUxz>m&m zQHC~htwomwjvO{UdpE~?4<89-4;k>RMj|z^iD$cC~oUgqMt>!Iqs9>GGCF_eq=?7dRuSx{d6%x9tZeI z+k%j(9+U>sl=};5rwDGi4MoFmM3vqJ?!kT@sN@MLr@{rP$F`x6!yN zql82||z zQ-+k|UA7j6EO~}~SMq|e`%CNEc$l3EKckzxevaH;gd@Av<7|2{+3ss?qdaFrRq;V-Dm4?!A|YsI_Q1mUWl)4 z?!%e&dzjBln+d&VT<>Ay2?idoq~fupX(Bp>5WSOwy!+Egt$s3H=sLa3T}*#l#kJHH zwF!wgJJll)KCsz6nB|+jLR_kGkl5=F1~?8%LC?`tYn^l6z=BKxgN!8QS?yI7arWTv%xd@#J)hRta&gKzpp+c0Ga0%Cc|!MaeO<$^?dn_PRyi zsY3~IleXE!Hw}f;tIW;Y__c3F(8=zO0UD{Cjia3Sng8LR_j4kl(=Fc z;Gk3&Pn(7-1O$!VHe;@%w9Wmj7a+4F{j9MFur7UD2`u7+^GkRrwn$C$Hq%oer2j3> zG*`q>KDL#W`#N=~4sBezQAW;g%8X8uC@L_KWM<3KlQ(n)aa7l2fD}sUjMFt6w=R0& zfL%3JzE{!ki)v{)Q#dJqdZ+h*=71?CNB$L}k` z$C-B6>26-j$Vl#Gch3Rm1Bbomi#k?hnxVkup{2a&uWGLD^o}sk#d@l9uryC@47a`N ztUrE=SE8J`!rxBDgKhhs8^=6)DmBObvTfhzyG3{EJ2? z0T5)Xs&l{J2ZS?tFol@pJ;1yVGZAbGLYz1O>@$~nX{yOQAttwO*9d*#S7(8RMpuci zhN{&JamShjJZ9Np^kC^BZSYuY?-V`lU+dT>4Z2%oU}-yNM(6t?LgbC^y+g?RX$7R_ zw&MEMmt(TM35Ci)Xq8~#SaE?S*NvRA74}-xdJ8WuWX$}Rz}z{}MI#S;bMO9h5C9Nm z{;x)kR`3=?MsMSCcuslc(*SnLhE5c@9Cx+m(=3&iwBZI+hkqYRv&3 z3=FsPp}RhP;{%Wu^rCb#=>0f{GBeC^S^4;{`1=-khnj_ zxih-ek4@tu!Jmjz{}BQJeDnkW{G9><+`L`P5{u) z*nR9c++{BS@F(fXQ5-UO9QaiFc@_mSjO1Tq!FW80Me-5maQI62^Iv25IDjV&Sb&sz>ksYb^1?e#nBU@7eGzGe3wT8Tr8(0B3)kT!C@s3H++qfv>ft2${>_ zZFs>r;3J98@C5Qeco9BnESVqJ@|3}ixcm?4!i6~q8a4^|iyU(n!y{fs5^gb)o-N0* z7pdFufPoI2W(VX#ayR^pbv^BU^cwTW(m#dkXumRnLWb@|#Blp@I1|91Fcg3ZAi`Lt zbOC}+&scwt3tMxzkqH*>SIYuf1FEV~8_DRo=M04jM`HMJgaUEO965=B4LMVRTX2<`!)?WG1s$Xa2a3R-#2@t3 zb{vE$QSoW=OE?mQfDc~TlmEWx{HPu!O9NbpbzS%@+CXJk0H0egs`PVJx!RKUXgHa{7o5DVE_sNjwYNJmxCGzLX<}@q$l7)()%6< zbB2H73KW6#C&CkW-+<<}2cH5J8~C%SbXaE|`9ST^l{WopO$ZNtmxn0a zKcm`7RZ=QZPvKZZQ{Y2{Z$Xg`4{d{XXPROuNPk`P==v&EB?Y~;px+9zUMa#zQ);LZtS*4Q`3=)40oJzUeXjJRE(ohl-q_SnSgJ>Ec z=~#a%V)qPe4fYWD6EHWz^_pVgQ>8!N9N-_S1EI2vw5c1Jfus(=9kgiN&phyxiV0yQ_K z27C_I)5A4hcn&%wGJ8Cj_$WKS=J?kQsNbh>WB8(O*%XgKZrKzp8ih+Rv)LC=4J?OR zYKXsW$gKbHCssXWmT5Kj2dM>+#EWOwPA-4_sCWf`{d2jA_X9sE4E(v=dKTF*WpLAR5Rh& zMB&R0Lbp~hI>@S+`Cw2=&nz>54QC_iD_dl<7%tioxBvX_|M8#y@&7^dG$ho=&pcGf z-QU9KfNcptVS^m&3pNFO>;L#qE9`%Y+bNiKW6AE@i{gJD!{VC9;2QkE!;xDQdqDNU z0ngwIJa^;7`tXT~O36x0f%48$w*2^T=MTQzUp^`>90s3Y2eoRA(sA^C-MIWC0>1)a zbOla@|7ylF)cwOi@Cc1}_9~;PW(IuSAemPMU*KBn1@s?<&?8qh3F{7 zvKgc>HkE;4+6yo?gABsb8)uKbuko|>F>DwE5r9JiE=(k(2mXn8a)eF#pzA$3+;!>U zfMDc)o|6CB7c<0gJ7njnejs1GhJ2P_)`yGzP-_e#lJo{hlmVwmLLNVnj!-rnj^zhaS0P} z0dGNEn3>TKnqx%7&x3PQGvzareLKf7!(5I-l1cC`c9OE>15=5EeFcy6Pbh+AFx9dtclO($6+Ck^9O3ZA?%cL562y8@ic`yYNV4#1=zJGn9NEU!&+6CZ{ z=#eRKK-Y%PCP(0yvjjNs4x5u4_<3jtXA{FJg&bjS%&swHbL*E~KsnKAADsDXZ0pScI5P9g#zO#q3+C+78_#?R+`Iw- z9S6k3)uHZn+KqU*%1-V{#}6I;5#Cfp7N7+r~iN9m}h;ah-iw0h~|?bqQ?IG zJQL~6myC3P03_XU8C?NJW9kX=D-aD2N=gstJhHg z*a>4;<{fT6_=cgoBzyQ)o9vr{AyhC)mW`%0aSqMU8gefHLIq=q7S2^5#2CN$=JH=B zI8(-(#CU%*<+_>rK?RQq?Uj_}X3lajYmN-js&V<^J8>Ex8~Yv>ZvZwV$WqL^W2zVn zAxTlzKzXC?XUcj9 z=pxT5rNPiJt^|B$KtOURm5nZWjw<*$_5i$quD*Ym!yUm7cj84xDB*6{Em(n(%N-YS z{yW^F2oOEJEb|5|6=9DD9ONdvdh5>wd{hdC4&IbM!?D@mkq@)`H`?F2gKHzZZ&a*k z&VrARlAH?7JmMY@0{j`u10!v_cSCr(%e(Fn!2Y-FuG?vU2Fd)D!HN4?|m9D z<)wc~j5Je5nmHrQamdXj4w^ah$WT5q>T1TFK$#RWM_}+|%$@?@B?Xnzbp^^afC1pX zePATR5kwm>2HG)UF3Pn8VM{rM1u~U~IiC%21=h4eUi9UHpB3l_#nUdTj zdYd`D&79sFQFCO$)5yT_MFU4lt558bVDf;seMd0Sh}K<#$+!Hre+o2G7_0)xKG1(C z3YEnNRz_#)$DN>@`M|6u#@iO`CUoJhkVP`I-|xmPVJQc7S8aa;rB7LPcJqKghwu$k_zKDo0uy|8*(+FLojk1g z;A0f=R#$Krsvd>2JFOOFLke7jBp~v^>?_l~1(>{P_u8ydYJ6hlX2mSGDk=fKMX+!w z1CEz^M4B8b`CY42TD6VI?_I-!cn-dUU+@u=2wAC*>I@hW2UQwpHpm}1a!z7Ig(AYU zY#2!l`IdVefCqOcO#xw|)k0|Bk`u82@wb#{o!;pctjj zg4EkcsYOWq`s>$%#Y1LdL3DqmLMn}Aw#P5X+B8s>7094t%A?2;;Oo6lNIB941Bcrt zmj9G$yNjAV;>YDuMw3q)wGndoA(JNkf;4$ZrOBO=CKn`47G;0<36zb1hs1*AIR=Zs zm7~vd_5ea5QdzGhk)ul-UEr*dF`2N~2NplYuz7;#qokP0BXFbATl{}1?6!BK@a_v7z>rnp%a`wih@1GaMYov*u>SBPIvI(U`$7%n>o?Vl;~zcaWkj5nUdQ~ zscj;)O-1dWgAK7cB#(V~kYAIfYFCR)hAxDi^Y@-6>L*)=A} z8`4|39i(iW++fnZ`l2xd*T$lv+m(XoMz4=m0_$D!CXL0XFzd z6awe~nz00N1IiMF>?_BnJMPS}*)9+-0`3h#X%Z;mtpYnT(H_eI`byQsNyS{4$0mV4 z&ng|hMzDWf^SKA_yTgJW?mhdj;WIZs1NJVdjt78)piSy{`s*O3gK$-YM-D;lOcbEK zojbn>!M4k8bOYTge`57&$*fo3(LY2@Za2e6MVdo3UzDwyRjzDC;a(!Fjxz-045uDA zAh|k7-d>XXO@!N=O0xo_kgDp?NY>ZDuu)*WA(?-4A(l(2O|mDKf!k=kMb@8W`r-e$ zJ1;dqux%YSyuCTAl$ePnj;x|;9N(q&Jfhnhj$HRxI;>&n&U8DK9BC+nsxc;@iUYDB zGbAKsGH*j+tqRgqSi)UUCu` zB1y0!es?t9w~RLX#iL;si2<84x4mG%e$jvYp$rVG6+n8*GcK-{<;Al;C^9&c8vF2e zGZ%+`;Cm7B5Dw>%nUER{A;CK^HW$~DH(t2izn%c zEKZN2pX5+Fq=<|ILdBo{l->TH{v9z z0Y>=-3$c_;+gzeo5|ZO`E`g>%7Qr4!-OMq|h%g=VDc#z+6N^w4 zD-vk!XKcH|!o|;l5Z;K#F6(UcQ;=TWj9y0fu59+(4{eInSj~3VNdI)k>ehcJR<3}j z0{=AkZo*Bk_lWd8kekvdTdjt**-d#@{t+p0alXkvu9S1UvKfxJ*#z1!9(^aLSAo{L z0O_RiNtB5UoM!oggKbnh5YHja#v*@z$D9c`;A6oYaLAod zgLH=E9I+bKipWgySin>fZ25nAST7*o1X?B(v%{-Ll}*;L!iWuQnS{PTp{o&eY|$uE zGP7!yxAvzuNzIh0CNkBO;4zsRQ_X~_X39}BvarREm}zEwH02)kgZoI$ia~BPFit*! zxg+4>gsGvjq5FbsU8j8Q9AXh~8XsJx+jqd{*I&C`)i<~si(zijz4s^PR7x1=(7iKh{ z{T$F#cM)AEtTOQ>yeWSth`}c;O*VxjoKOZ`lxu%a%J-CwFj?z_m~3(mo&k2cCq;hj z=6-{f)utPm8|o4YORe^Wal?|oBv}cCA(fV>H=AaQJp0m)Lo$d7DMx6{OX(n4GJA_BGXd_aFJS&dK3s#Hy@TG}?SsDnv; zO@^iVqjeZ4)ho?Lt@TD~VJ9dx3Q$5dwgMg}{qFDs>jEROZ1|Ei5SEAxK|V708>PRo zmJY%Af~F?IY->ERlR>-x0W2bS7Wr<7f+j`MN}v_swGWaqP=ZnAhLPZ7)N?sP*DAVo zFsh@KU95j*PD3+!wTI2e!llT3av8k22wYw@o*+|&Dqh+4K=0wq*5R-3(tOWIOEcFQ zN&%wMFbhfpyB$pz_-l%kzw{DE)?!ZoxK+@Vriup9(e+)C!$pg0qxG#&m68znl&Km$ zFZB&a9`>`EQVm9V{$gh?qgG&T_+NKr<@#ZokHdci!;wx6Gfb#~sfdIcW~$+Na=XXn zPm8)FU1b_*nWu&7U@QR4ikak6#RZzIG;&t!SxxRgF_&qadukMmqOKX(QxQ{t0{{Vz z@4o@-6&1oQTD?7FvpXB1cZ8jz;GjfQ89IeC?o;yOB8Crwq5G>$CSqRjsq-vYXG3e~ z8Z3WkmMbM7PFjtnhK6MkptD(nJV)B^OIcOK5`W1$$Nt^CUL;m}I7dlQ_V0#mU`d%KCsn zCB8y@M$Me1&PB9lv35{+sm{y|(H(4B%?^KFZQiakP~Pg1UxT$@fBo8B#MEUQYzy?{ zA~6Eir2~E5SEH_cI@h&I=5F9u9vNts(cXkAK6b&b2ysutSZ_4W zteHW?aX>c*W9NqB6!kj*OdN<0ny}fKNvm4LH|u$99tT}=xSCFzZfP^ibm9~ZfFpl1 zt*jfGGu>yIDVs6WT-3~K8OH*Pg;)5vWRn)OgXVwiXDY6;5Z_@GuD28pW!ohT*Nr=k zux3hFGbOB<5!M94YDVCZd0)v7>@ZSTad>f1((Gj9^DQSe#{ z@d{neuaepO*n;&nwqQ?kSHf4`z0-egr<>1Db84q2sxG7#3mPt=oT;n0V-KOJR*j*U zrRVyD?zVGLVQzc$ffdeph{HfaEx(f4c*_Y%PrRD=r-(%=v_4Kl>y+t%dmsC*{2{QZ zfI{mI{74OGpMVb$AHk8f2{baZvFyBC0SvW zoV~LUo!4@Xpob$ziV+PUDF*bf`NW^0q54fvM&4pCuX*W&H~|qWE!L$t^Lg(*`<{c@ z%+b2lQ<#5IM&_tJyiD2$hJw30q%Y~Cs>-}k$X(*H7#z6vmwNfcvOavOH0pJ;kwp2o z423A-L1Is zkK>V5F-w(d?QCU*->Qpke{TM?rd_kKDP8&}I&C#$VT$qe%k8l857tTB;U@h5119GK z|AkIUNTU$*V@A(2suxR_26LrD}g! zNG9l~AwM*j@OQxwyIf4p_hK9dOY=}A-tLCBiOry{<9N(fdC)~4ZB-uilzj=1r~bR8 z`jp58Rv6bthfV$GsPgB|<)6OWP~q5jN~Mn$S0_Qgy!|Q(pJSXbsh?nJf_zCI zt&=8fG|x2JxoLk-x%7&$3B&jjjId4>%mp4it4Yf@aGIv#JC5JEl{Cc%lxz4yEkNMT zGmcm83;7)of2xSG9<76j4H?gZnOGjQq2kKCY(m zai`y4QyqV+HuqA!oiI?U)4HQL8MWK6KCzb7Fk9uVi|mU4C^a9|)gQBq9)|VJMypzF zRS(E|%XlEBb(3i}sF_z`p*Jv$jKHgzh-Z;nXDHm5a|sW#AOC@e^@hhNkK~35z6~6z z)iL3;BDHm83Z?$gt~6Q9aPo28r@E^=zPfm4OE zmma8f4AhyXGc!G9G`JR&Z4#ToMv9wvb9I|E;!0jq*b#I+x2Nw9K5onDBlEdp^w)>_ zD7^@({Dp^j=&3wF>uHgpujYV+b8j5@u29a!VA!5_x&s!fxkKN0JisL8e z_M%VYi1CWRDgYZQ@P`28JUqG}v3{fEKA4a8vzed}l|ct=h!I!~lEREVNxjXS68PH5KENBk%B~5bnaQ1%=p#695zDS1+27jTh(v)V}h7$Tisa$Gz zlUG5-01xTdy%B!Y(4Sn`NUxQvoio;Ge!@B|%Y=UljT5`{O=Q=5Oc;Hj-id0hRc~(I zU$z_4t43Rg^4eCNP7d&H3h+S!aCU-qv+sfVzciF@1UXGsS&8J$&|Ho5ZG`m_T&;gf zb;TIVOi-ujBkPZxwIrN)xJr*#WNZww7@l#zr8~Rfw01C@QiSjt$C70$@jAYTix#As z5Y_KW`vyr-vXXRAWoB$pe}#N;XCL&oU$(te?rW?2wF<_g3EE}NZ>Wcfp|^N z1+KB7F|Enrphjz6JRgZO$CnQYo?(n)WCsN<&jTQuIxKXm{dlaM7$n9EoXk`;VBpJx zFT5bUUeYGN>;%1iez3#=IG!9MJokPSvB6n#n)W(Mmz`n1i`~Q*n7nt;^F4n|#Nt)q z!4x)F4Pcx8ghNs<9+ET+j=04YLTI>P&~g-D=%Cl{GSF9)jM(QGsj+Hk6#2OA!4@MQ zm)~s?mj4TAgDvF!UNEeeabwy(_V+@2`nxdugU*r3xu;6^sz(NF!%)#MfGW-OEy z>?tHvDij6NhF(!FyqNg@ZcAb#4A>x(rP z!|Gb#MisRn+D-2gHJXAa@9&KMYufJbC??R+`|uv{WMZju|G)JZ>ERc%nrLr^G?S;#tanak1V!Y zM#_IP4F`1>pg=@IBlA6g9H7)&g-W#vR5inFcSH;$#ef#Ie;a>y8jLfv!OK_5ip0p) zRp8_F1e&+L4&V6+NN}8q%e?@JmeiiQGPZx9AJqQ+eG^;Y<`X4(Y@4oLN}nGGPW>0m z?R(3BN-7mMX^G8vg7WIqs8f^`N{2WO&!c8pzo z1*J635e(3)iim$C@MP~3R0IIq4eg(SfGj!CT^xFUl8B*CYb*ahs2R#2hfq8&PBI40 zWM$3Q7uah@6Do3XG%bju^+wo9`+CrUk0Z>u5=$#YH#_O~Kddm;68Bmu+N9^gFmk-* zINenSX<*mH(IRGVw+HR}6zFQ9q^+5KU+^3Wmd4Iyo(g|gQS>QyC`FRgVdPVBJ%toa zi5?lxS`ww3mkn%Vy(J+11z`p5PTY z9A=130iAz|WVURp<$TUc&8K4-nK20uo49;r zL{=6y31w*>D)=9}=wEyKB-nFKM@?osiCkINuCG%*Efj7zfHG1f`g z4hA-^DIPW+Ih=-KBr9!jQ#eW@VZ%GsvLu|w3#@;-pqd(*si|JYEe01Ibp0{C7|WjyV{ilescC-rgO3BmpXcm$fMp=U4OQ7tAfg`_U;Iqp z7{xz+NL;1~f;JxGm*3)akMNa)Fzc(ye1a-f*#*|8ON^1=9tH&JKuoxDvjZWYLr$I7 z7~_BOgAt&a7IU7%o8hsq&M{N^BDv?U8v>}6H(?s|8EA3S-&Asvk9tdZqjH|L&Jf4S zu}g2_up2l9V^*ovu=(C#vsd$?XJ*BYgIsmS#Ma=f-6_sBG}$ym;1;A)1GbY3GL->QFT zGYm~M76p?o4t1ec6pgtM%qppMv5`U~m%Ew_1>Fmmn)=QU94)jcW(kq0*vtgXURc{a zHnKQlXklWsDtM|eZ5Ub)R(Pp&XhX4O^w}&kx0BY7kHg$Vlh7YUe$Wtz#iE# z2WMc`6+xAeVsFX+GfIcF{4kVcRK^T#B2=)Se>3S#m=V7qcu-=722Z;h^ zV1dIXu{S|p#@cIMP z0W?@`{E4-36K5z#pam}fKZ zRWn;^MwSjMt5K=F3s*7%TyTHad``HQ`nWW;Z=A9$X)@7SX7Zd{xR!JGS-Csms;O6% zW%?B>>f;Ej%);26rPh@6o;dZUM_KANd1E?u#khz87LwXW(q)*R^+doqpL(bt@ZR@JIDYbAD4E?Em;^2B$S5H$AX$EVnFKCb*|ZljaczHRah476Iaym`plZcCt7 zXV8EOcwd`RX!TaQYG{8>hI)=53IMK#(I*V9fOXCT+LxwcaP5%~?|4veOb3;nAWJ?l z;29tS-h%IPJ5$x(a^?TQ+6By|c-rA!-+{Q*1KQ4L|I46M zYFTEzQEly89*ECEQ`!#Hk8Cxn_1flMgxxohH%bQ0P$QA_ugs%G}wy={QB-Ooyt7gsYMKKo%ToFm( z+f|{2d)$H;(2B^cS{3hZ?G&999C|%!ip%wJxRvly%Oh5faX?x31#Cgks;4w!{7Q_1 z7#FXU>tLI~6M%n%Ts}?4Kjmt)S$&rh9b>#OGC5D@Htnno_duAMs1sp4)}DhMoCamJ zh$6K-(5YY0rWx{dkpDq+RoQxazlt~MU!GBGq3ATduGb1qfqG8EX-Kw%gJa? zqZh7(gB>!LT`;7j<48)$Cm6k>Q7(ZhJ;Kk2_7r=qFfD)T<1E8(fWHUn;egw@+JCk4 zScbHys@U+S&4x(I75l^erZdn-mA!|Pbm`n8@1wj`{ck}jkA1jQ+EiR_vUWU)!E?cx z27h6oazBx_wdx-S<+4>Vo3&=+#HxH~luGrQnXbi7APaZw&{v$NMUV-& zkAM~S5ugRPc9zI?@X9fjGFoG;2D5eO%HX#IEgg5O@Uc~E=mI+4*%}JIu^plG&-UY# zby-&8OPGhskQ#0kM$)kyxv(wDhOPDD;vo3$+<2ne9igA6{HbG3e9L=Ng(pnUr zIhttnxE#G?Pc4O9EC|4D$gf;tK%wJ@(v_@@lMH7v!WhP|kA;KIv;XvD_T!PHWyp)m zv<6&K`Q$)FH*g?2Mk*cxK|fw~6KwYB&%nr3OXG)77+nbJCA{_7#ehOC5e48r`ye~+ zvB`fB=O=zku+H6(ef!340;=!F238S8tF>Mr1h1l(s^oW5`U1O z8>)BY>qWEW$0OQlkt}z_fWrutjLU<-e_el-07#dTKKKEKtiavB%2}*q=|ZrtGV)bN zI~Y-@+J+rUghvrAo+O6nh+9G`0jpnxvj!Rs@Yx|q@uNZNcR%phoFtTW81ySFMgnY<$Ada&guctRr}x4>PXV6n3V)sRjF0Xx}|@& zLGRE7eO9s_bAffB7U;57uhb4HtLjEa1~$zYPWHB|o$1npCy4^}vNlE`9ijj>CaxpQ zYm?vuahMA(Eenvj5#(K@Lprq>Ao2>Q93XBwAm@ZfAuYHu-?mjg9n1fz4FR{hqnC)Y zo&&IWOfnLkAIbId-gofVzt5)%7#T*W+%t{itSfGwQp5wFWgwJYNyq%vrVoDra$`)Zu~E zG$>f)PlP9JsVd}GNH1sZ&Zgvh92Zt^$*T-RD}Zri;nf~ocUfh5Yr)d4)XX0|9E_p@ zk5-8%jX^6w^b{W%MlIu)Sw4SfA3=2W<=2M_qgK9Erj;_)vjNmHi`L@eU>8H53MSq- zv%+*6ei?l#;i%z96H6<_R)()#1k$aehRduEn*NKDLbf7@8EecjpZq^&6_ePqR4+kn z$wyyjY&kA`nkAwi50qJ8PbFq(LpRwcNK-2Kn7;_6>>T-`EGpI9sf2&g>lE&%fRP2E zq)DQe@QiU*{x!#tnZ7w!r0d010UH@2)&NN>+*s6(Uo?4lsR0&85eaXO`8h71`!uWMz6JKoUt+9oIgt^>VST@ z>tC1~wBuT|5#U#(b-=SqYL$SQw2s@x8IcrB*cjr_%z zD8asqERqk%TLe=m(~l&omV(_&*i2eT^)Zo@lgcK9>qAHl1vPu)(AE*BS!*}kqv=rG{H*E~7ZzdPzQ)u}uSY0W)l_R!s0K7*VS;(qZ`%bg z(wkQCi;7+vbfbSVI22}FkbW(!;Al2XtJG@l9WtvvreFTpz8M}UPFAT_tCa(-Cno4s zj4JyrNEYFlzsyv63A4%OU`DNKn1M_R$0U{_fnLGwu31PnPs0z@toaL)%7b*eq_(Q+ z|GF6T|8JJmf#6olz9=f6#3mfXKqm>9{Srr@8-!sjU}t{`Tc7!>q~h3XKKJ0lxR55p zE6A1a9M?gxVvAIgUE2;fDk=VEU76-3U`<(?F)Ad(ty=wk6|^)+#rKi!f0V;MpF7}nR(%Bvw)_``|^*^$?ch$ z+AuX%Ei1=l&swsVT1H;Vpd%|u0JvV|Llug^k6VSKMG>x~6bdDkLduF`fxekaRqb^d zM2pDMxU7NWI>ujbQ1Q^%NiOW%6`jl%il|2x7rz(XEHK&69-VTX|l~9Q~gV3CI{b z%juRK(?w>lj13%_qYdDwKby`SY{Irv12@EH@Fpn3v*pUgrB;}|#acb85w(+AJ*;1! zNymSW(n`;3--*}*y3jz<2P#H#XH?Od4cYN&&*7avMInBNjjgKeia_jAuT4_M zv~fmGQaJbdq1{nVPHk;nrYb(#HH(kWLsgi6E|=~^1kU;)v-ypBv(`A^kl<$1xDehr zkjPmQxWHE|FQRGw)N}=e@%M4mc~iI zA3Q)iEM%X*;xxl4=C1iO6_xj~;)ohv0PAXcEhzUZXYSEtt>1-s zArPb+GTYxa;Xv;|P#-`v)JoQJ8vcw`KGmBovsG8kD?6bfxj1?f*wl9z7+B>-vs8bp zA299SY#F`GWu!1ux|G6yTEWOO6pOZ)MR@w3aB@DCvQqaH#ynJMr!p##4X z19!9#!o4T1j5U1Y-VWhK&qc4Bg!M6j_A8^P+j12%Q zjnJZ6xfTL7(8C~u+P?5wZ0lr=@~3}+RWZv{GgPv6by=-5tMr)S?9c@sRMbFa5`mML1$dSOPGkk zEg~EsD$(Q1czeZvIN(C?DOrCEX~ETU>6ca)*5#np;2GKrwO)6=%|nd3576A40goqG zJ$%63col)feXuJSyrQ zYR#cluQ%(J1Ddd{iXu&(oIKTvA~Jlbm;s1*)f|hq3N1=xNlrcud&YlWvOx?~hleZ9 z^uYtzC8AQCyOy#>ezKbiL2HjNP}x#iG?hwIO_Ovb^Dz9gb-@`u^KOg{oG-xT*3{$LgXm+z+5jhEA&l0IioArd2QQ-&uMQ<8s>mO8*eLg^&r6`F2N z=42c|rZTdy%8`wgnvvvZ)EZu}T$pCLtd68-OWi`OrC?99VdI(&=+*Caosex~*;kdCs35mePJb=?L)sqxU} z)Zv~B@(MHYu5KBTl-JZ?~NK1l8S!<3fY~n8a#3bC(ay-z)iEa zsmSW(B5PMYl)7O(O@1v+nO&A&i?($xh*WotE`c=Rq-FY2$yR{oDZAIjUkb?g%CK6s z@s)D zSUEGJ(gA-kltU5tcD_9YYYda@K{Av5u8j{0CNU+K7dBglV5W*$BuCPLZ$NMXRD5rS zqc8vM|EvB*qvz7`+1(vh%JJ_UENEPlJAZywZ}8O0h(1vIpa1QD_@{sOPye59KKKM* zELEjWpI-jiA!Zoo@X`PN-y&L})^+^k5?^r*CcJ+IQ7K)T_KEQQ4vd*_3{T#Aaz@uu z-#i_Q&{aQzjenhe>I;6`6c-7=wFt7mc+Z~y>Sgr+>o=~RpSKMo{Z&o>#{mFpzODa9<3`2ik#R5cNyumhn= zTKSCIB;|7c5Xo2Z30ZPLd`kszH4Zt4c=@EEAl}%L1C464*=QXF;$@>h0Ahk|g2sP@ zn*0$6ES9z5#zB^#;fuS7;rTGMGsCf>0u?BYI$};die0}PFdD= zoGO=El}gQeBQ(Dsr>e#^P&3ffE!tfHAB|*0tv@S^urZ6S~bK=x|oq_rE>f$XhD^C;(|W?X9~ z%2t`D8P7BHc zUyKpxUvNeJGbdtB3HjjZ*wcR)i2VDM%WSIX;fTCDh2LuP-jBj{qix%;5e;cl0>4K~8UqY0 zU`NCqK^u1jT>J9~ollpCKd262e|Q(oNvl?2Csl>$@|NVTfc5@XhHrmAayM|YZY0tm z6i+gDsim;m6i0RH`7grBx?{qX)k4|a`(4b(7&~jUVN!h(MSxMu+6kjz7nVxZ?L3|x ziPkjQ$(lnMDvT;R;f9n|X>2kaR}t}v#-N7`EZ&!7zyT#*XKLU&(J>B$zDatWb&mm< zDGxYK4EFYs51Im`O=f?1;C%GJu?X}b*Z4_)+K#+vv?}E{YEbs$MGF9o0nG_EHAMa^ z4vkTwR%I0wy$GLo6pxz1ldoOSum@1T%+Qb~<@zO<@TEZu0NnbH!nNKky)im;KVn%W za0$@yMjfvzdL19Lrd%`b-U8b1sDy6)=_%Mr=%*!=L+b$O5bS?I!7ciMc0cu2t5vDh zk3l@kxI;;)EY=Cfd6Ge#K*^Q)wk-*Q!A@og7MlpCv#+<<9-z4w@USL?I_4Cqhs&xoAr5s8H#iVNS60hCfcfXbGU5q$8(I% z+JlgP_}FPft7B-aQa~Ly9Y3J9*{r!++g!5)dGnEib&G$U46F}odLTw!7m2YSQL11A z;L>E_{g}G~@bO24nwpA4P*sMsLygTPSeh#SpgCNL#ieajqXlaiUJ{-FLhFIhnVO=D zs~SN*xN#6q@W^9nqsMX971ql-G&b>@bf&^_sdvhyg@zvg0Rn?MFd|yK-RHPu=>?pL zQ}$cT(T9HZ$Xu*#XewrjysxWSY|CY1Y3WP0~36UsYA1k@vL)3jjYhT6IEqElSd}#) zb+r&EBVQr2&^BaAKrnX-jtR;j7t9V96&*Umh+z>=oVhJrtltKSGGi{tE0A7#Lgm&+ ze^?DGz#cm%@;&pMKhvZz5lmvjb6-funXAOh&f)1*D)3=C4`q_;FZ#_3QR`- zF0>Z8kWK7ek#E_89o#0lgY7iFT7uXemKs^iLt_oamj+Y#X!txgKl{#DeoQ;@p7GbE$m@R|))70Ymv@wg^-8HyI)*1-E*Y0I3HFbV zatZDi7v3PFgvUoXM@6wp%RO-1;EBA+2WW;UKjx!2nv-WxubuutCZ{YX89Nw>h1>K$#;M!nLg97Uc~46qlUIEDZ)&GH{nHOR3D>6b7Lv9ef<>9;g8 zL%+ba>AzA>1JWx=ctQQuqyJ>n{OLDT-t>b-|E5Y@eSOk_fb>p=`P1vys!D(Jwx@p8 zrts76xQ1@xNFP^z&eQYn^5n^9`_hWM$xbON&)o(?ZNig?Tz|<7;;BG zOX`?ZZ_xG*B%cttwl#KHC;7|(yh6a;efu=qD?qRJeD4Q;kt}(#Y*m4}Zyr8NYy+ol zTn9K^OSC?E^e%c0(V|38IC?#Z-fNV|4GxYR-BzM=L??Rhhah^?=%NK7x}yXkO4N`% zy!*bJd%rh(&$suQy=K;)Su<&a&~j2v|cK()m`gIekaeb zVabi)g7ame;fr0QYl3@z!dGd#F3g$ivG(mm3S~#X26laX`zx}>&Mliye_08LJEb&k zxi>gZmYO~o`;4I)b5@mTUUuO{ z+0_Jh)vlFWH9fFK)X!C`S?1~fM!$BxY)^15kor=^@~ot7leGQ%LEEyKkief!e zgo^iF#(EQ$+5jNbH!-Y@n?pmsCSk8<8Dr5R!1yOT+H%<{eITS7i^$xT@KepAWpU;y z+Z$XQ^&T`KR6v?*$GyXJnsW^b+oBNuXoP98Q_|Z`%oe7;{jc7FhV}$20n=d$WJL*r zOpR@-J7DsTjwpAr@YPMN0o9l#x*qkS6nuvP!Nk0Mm1Us1Oqkp<_RR+6m6S`4AYRrk zt&WDO!ED_&Pu&rc(3y)|JxhQEXAt7RQO21IEQS+zuneZhiS+(HVjDGlH%N$LB@*Aw z!!oyn`5=Za9@Z_lfeDG#q6=RA`cQK*!((QVj>I|cD;2^`(HW}sRY0Xa?>0;D9}MCg zbX-BNSOg|}7LErSOW-1YzE+g2m{*gjc3AT8q)2l=;X1bD?7Sd5Q%PaG>N&;?-jg?( zG=B1i`JwsDTt})5Qy5r>g`_6SP>zK0MGvE1yuczAl9GYRVd_ac&c*69(V%ohQ0CSJ z%7!VqDDKpV65E4HbWys1?)zKs82bGlUIkWN061`1=1sD)_UB&^j+J8nrLF+tlhv&| zJoYyxQi!n*vte1@g!qk$Kb@oDVP0HzVIVyLS?C0A&7Zm|)3^E~j>3wmg*_W*Z7bJ* z{a&wH*psRBC{KRGv%$+k;t=h!9-VwS{>b8NN%@!5qgny~q|Ohg9h`R&^jMOq+3$m1 ziastr_7`WhS<(GC5qageo4x|q)Ox4G)lTWMh=uiAS_^`62aLQ5P*nnf#OcvM|90`A zuMvLh)g$YP1IFzQuCD999(=eUxBi%LF(j%^|)Bkc3thKY7a^kT~N? zl(N{tM^((}>M;i~$|Dn@r z<}9i)XXd=r6(HXRu9~Aww7|CJ=-ZFyw|ZIDa$G3EI9)jqfi02HRCbTzv&NinDkfT0 z$god3t_>hv5o-;OQ-s3ozdb8udPt^)UmLs!Q`le1ps;`#>-(vFz@7X$n~&UqG%SA3 z6zpCeABLu&zHexm3cWJhE|hy1XXKLqkea8-=t19mdVx~!wCk-rt#C0toBY*tYs{o% zX;?^Db;o0LwR2Hfi63s4%qfs_y(yp~sY7q%*Z~OiaoxLHD6brQ*zA-&cwtLkV=d?Z zsr)YLEyyu9^Qhm22JtzHMdYyeO zz4`@si*K~_8oUlGtu5kqlMa z(5=v=)@y{I*UDeLZE0LtRVu!upwgI|UifPk;5VM!$O0e(%O_FAE!f`EYS=d|Kcqas{2 zjRvj@ym4CWk1Z2drv)BZl=Ni_7n35&-;CpyoT$(&V4=`3l4VN)y1xB&CsN#bsdx@R zv$H)?bYyubycg|kv$vP!Ou4t0;IeKmwL2vuCSp|Tso+7=Rty7EjFY;r^HZBu`~~k- zSa3RtrF5z&+;`-DTlmS7aDLXdS7#UX4cENjC*iRYOH-qn%D&E=H4AAW}@kOYbZ}fIt(@)C&Y*I^=t3=Xl zAHIj+GpGqAd!zI!a5L_dw^?}ZVSqpfIH3QGyACTFz}@<}vz;5CI}DzgEc>EMh9qS4 zpdHix8N7Bj|EnTiea??IK`!0`ZDGdCQ$jDf(y{5x7t1z_nr*EgAs#$%)VudiUsk2e zbrLRw7nv#%-PE7PGNWELXIyyB^^Q7QM}G8&d`n4RJ)X=&r6T0N;p2w-?fT8SyKxdm zB(lWw1G5HB;W&rWlRZIbVGF--hAqI3sCi7-8D%mJm3KgPx+B_=TV=ok0bLqLyLmd8 z;dSHs-uDlbh@GNb;|!c<`u8hI4H}9929AHgYsu))9rR47RTQ0*nyfH#)hp@n^HXb{ z;HQ@iVq24)JCbnIj9`D~b49jbGrHFQz$S|L3ecjnMof*9sAeESHFQeB3~i^MY?`E0 z<+&g{l!R3kdq+0?c8dB4g6;~p&9Pzb@8pg4!Q4$9BFc|wkrE3dSV#%;K72EnMG8H# zDF*^>+34SiA4N*6FL2bpI5tzmqeW`NBk+WUR#sSvPqHnkAWP&&NB(FxiW%pJ%}~BT z1A#Je|D&1OE<5F019<(DoDoc`9G;#3A2W$l|^gZ*K4UXgA-H$0#OF zDxSWpy~-9fw1>_Dvm%}3$4t*3y>RE7h(mnxJC%$YBVP&X{nk7MZZHiOkS5|xw`kZ~ z=$y6N$vi6h**!bE3nPQ6qW|bX(a`pQ5o33JnEKH*?iiBXfl#QWnE}QI1puK9OBLTP zWG=G$RE5f*3k5{*KSs+Xo?yRp)vP zNt{lhY(nP+6fstFM@CEhypB&km)FPilHYy#?S%i?v$9b#A5u~kQrp#L{U5x2;pdZh zlid%FUkrH#Yw^X@i~_NqN=Q@GWK>CPA#G|FiRbtr@XcqBRa?5{{gQHnvF_ANY`%N1 zg1^2+{^h8cOLvaG3b*kBVJS1i;Jq}m#dm{`le7?PIy9ukp|3(^KbQSBc(tk+ zTYC?jJvzF1m*xQB)KJ;}U5Q-!p3r5!xASN29-dcWecP-p-+TZs{QG%wkx2cPqr#kW zoWZ7UUP^Fr1cemU4@nk@lOuRjM@o$IV5eK6#`DrInPr9sQPoG{%zkEhRy^}}fs3Y1mP{@-fF_>e4SN z+)YP^1>jG!=_JXT#Rngi8noc9#K${PDGue%+FN7BP!|J?_dV$Krg`1d8=q>L4Jd)^ z`z}6VqRch+*+Q^1rWVcI)K;vAts|7Nt@(O>6|;nA{{`i1I#{SCHudBVxWcNX>118E z~=a%VWF<*H1RK2=_r}5xkG5rw}aNHW8Agfzun`iAm9_{jD}up40eSL z*1Z{$#;gJmIdc~|*aEXSzoT_WTreMgWDjkklF(jwf+Zj|L9(GEdAw#Lzl#6Fhw%Q4 z?MdOj^~0ZZ+_e#R3qsL9nAwy+JWNkjRi_EGBc^2JMkBU;{bIZX!&N>CORtapK zfM!cFPCuWQg%lL3gKMYCeT-~o(}!j3dg-4Ulp{Tv%}i^BlVhzu z=HR=<%pXJl(U?+LdtCB=}zqc+?m!_-*cS@_dTD$RDz+Ses{}5hJwfRm^tMI z12@5!wU1ulcz@->{=nvu{a|(COW+J2s|dU53*&6>kC)WtB`cpWg}lg%9gpqjrt!bw z5!g9!d{*12>XgOzI=W*8$E^ZG8#k-V#=Z^91*Q%io*0vvH+ui}!hCA7%#7uZ2{?M` zU4Ky|b!E(@Eolm_=Gjn|h4*KLzjlvg-xtYw^cvS)3>h}mJodRNBAqS3tU%SIy`?H^ zS+>sto&?{Q4x#-%|IR}cYDG=ruX#ZjO@6_oR*w5i5$o7Nfj!y|%LaYQZy2P%pf7}< zDuOR8TSEa>e=RvcUDLS?*9}UW(=^xh#rmpuAAV&X7 zT~IqCBVWC*Va1`vV>ib#5zT#sLn9>KVj3!dd{94dnW0|QY9v@sA;cET?76;UAr{Z! zy_G>P@kwo;H&Yi-VUv7P_(Z8w)kTh28hI*HR~LVKPaj+nAJ)P zY+Cj$x2vGcgT0vzY4xWOTB2)mA2@1Cd|o7G5eH@~Zt&Nm|Kxu2t0qLb&R;wjn&-+> zhr#^(PP3TyhMO!v&M@9{y>V8%2vuty{{@6idSFnzT@cJIbbJDg`JP*t9o)`@C>cEm zlIw}E0`sP^(9kJB|M>sobe6tZ0VMq8ta^K`ca|r*rd9q~P9)$!A2Z)12~bfIDuO^D z)$2%o9sdcn>IqMw9Jfr4tLjZ$*KjBY4|FZ%&j8j)SckIS3c+)sLod}zxbMPrOR8Rz z3_1z+E-bfXqzfIcl?4c->;BT#D*+0n0dA!KiL;3QEEjZU{Wq^H3})?hgSGxsNtGKY z@P9~v>|c3}?EcOx4|VAg*7>6M94a E4``sam;e9( diff --git a/test/src/org/labkey/test/components/trialshare/PublicationPanel.java b/test/src/org/labkey/test/components/trialshare/PublicationPanel.java index 274ca30d..b0810019 100644 --- a/test/src/org/labkey/test/components/trialshare/PublicationPanel.java +++ b/test/src/org/labkey/test/components/trialshare/PublicationPanel.java @@ -40,7 +40,12 @@ public WebElement getComponentElement() public String getAuthor() { - return getTextOfElement(Locators.allAuthors); + for (WebElement element : Locators.authors.findElements(_test.getDriver()) ) + { + if (element.isDisplayed()) + return element.getText(); + } + return null; } public String getTitle() @@ -55,7 +60,7 @@ public String getCitation() public boolean isPMIDDisplayed() { - return _test.isElementVisible(Locators.PMID); + return _test.isElementPresent(Locators.PMID); } public int getPMIDCount() @@ -71,7 +76,7 @@ public String getPMID() public boolean isPMCIDDisplayed() { - return _test.isElementVisible(Locators.PMCID); + return _test.isElementPresent(Locators.PMCID); } public String getPMCID() { @@ -108,14 +113,13 @@ private String getTextOfElement(Locator el) private static class Locators { private static Locator.CssLocator publicationCard = Locator.css(".labkey-publication-card"); - private static Locator.CssLocator allAuthors = publicationCard.append(Locator.css("#fullAuthorList")); + private static Locator.CssLocator authors = publicationCard.append(Locator.css(".labkey-publication-author")); private static Locator.CssLocator title = publicationCard.append(Locator.css(".labkey-publication-title")); private static Locator.CssLocator citation = publicationCard.append(Locator.css(".labkey-publication-citation")); private static Locator.XPathLocator PMID = Locator.xpath("//div[contains(@class, 'labkey-publication-detail')]//span[contains(@class, 'labkey-publication-identifier')]//a[contains(text(), 'PMID')]"); private static Locator.XPathLocator PMCID = Locator.xpath("//div[contains(@class, 'labkey-publication-detail')]//span[contains(@class, 'labkey-publication-identifier')]//a[contains(text(), 'PMCID')]"); private static Locator.XPathLocator DOI = Locator.xpath("//div[contains(@class, 'labkey-publication-detail')]//span[contains(@class, 'labkey-publication-identifier')]//a[contains(text(), 'DOI')]"); private static Locator.CssLocator studyShortName = Locator.css(".labkey-study-short-name"); - private static Locator.CssLocator closeButton = Locator.css(".x4-tool-close"); } } diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java index 19fb008c..b5a20486 100644 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java @@ -40,7 +40,7 @@ public class DataFinderPage extends LabKeyPage { private static final String CONTROLLER = "trialshare"; private static final String ACTION = "dataFinder"; - private static final String COUNT_SIGNAL = "dataFinderCountsUpdated"; + public static final String COUNT_SIGNAL = "dataFinderCountsUpdated"; private static final String GROUP_UPDATED_SIGNAL = "participantGroupUpdated"; private static final String PUBLICATION_DETAILS_SIGNAL = "publicationDetailsLoaded"; private boolean testingStudies = true; diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 03c41358..c6c16502 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -299,14 +299,15 @@ public void testPublicAccess() impersonate(PUBLIC_READER); DataFinderPage finder = new DataFinderPage(this, true); DataFinderPage.FacetGrid facetGrid = finder.getFacetsGrid(); - Assert.assertFalse("Public user should not see the subset menu", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); + Assert.assertFalse("Public user should not see the visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); List cards = finder.getDataCards(); Assert.assertEquals("Number of studies not as expected", studySubsets.get("Public").size(), cards.size()); stopImpersonating(); - goToProjectHome(); - Assert.assertTrue("Admin user should see subset menu again", finder.hasStudySubsetCombo()); + doAndWaitForPageSignal(() -> goToProjectHome(), DataFinderPage.COUNT_SIGNAL); + + Assert.assertTrue("Admin user should see visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); - impersonate(CASALE_READER); + doAndWaitForPageSignal(() -> impersonate(CASALE_READER), DataFinderPage.COUNT_SIGNAL); Assert.assertFalse("User with access to only Casale study should not see the subset menu", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); cards = finder.getDataCards(); Assert.assertEquals("User with access to only Casale study should see only that study", 1, cards.size()); @@ -320,7 +321,7 @@ public void testOperationalAccess() impersonate(WISPR_READER); DataFinderPage finder = new DataFinderPage(this, true); DataFinderPage.FacetGrid facetGrid = finder.getFacetsGrid(); - Assert.assertTrue("Operational user should see the subset menu", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); + Assert.assertTrue("Operational user should visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); facetGrid.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"); List cards = finder.getDataCards(); Assert.assertEquals("User with access to only WISP-R study should see only that study", 1, cards.size()); @@ -344,7 +345,7 @@ public void testDataFinderRelocation() new PortalHelper(this).addWebPart(WEB_PART_NAME); DataFinderPage finder = new DataFinderPage(this, true); DataFinderPage.FacetGrid facetGrid = finder.getFacetsGrid(); - Assert.assertTrue("Should see the visibility dropdown", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); + Assert.assertTrue("Should see the visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); facetGrid.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Public"); Assert.assertEquals("Should see all the study cards", 12, finder.getDataCards().size()); containerHelper.deleteProject(RELOCATED_DATA_FINDER_PROJECT, false); @@ -390,7 +391,7 @@ public void testSelectingEmptyMeasure() DataFinderPage finder = DataFinderPage.goDirectlyToPage(this, getProjectName(), true); DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"); - facets.toggleFacet(DataFinderPage.Dimension.ASSAY, "ELISA"); + facets.toggleFacet(DataFinderPage.Dimension.ASSAY, "Elispot"); List filteredStudyCards = finder.getDataCards(); @@ -745,7 +746,7 @@ public void testPublicationDetail() finder.navigateToPublications(); finder.clearAllFilters(); - log("Filter for a publication that has DOI, PMDI and PMCID values."); + log("Filter for a publication that has DOI, PMID and PMCID values."); fg = finder.getFacetsGrid(); fg.toggleFacet(DataFinderPage.Dimension.YEAR, "2011"); fg.toggleFacet(DataFinderPage.Dimension.PUBLICATION_JOURNAL, "Arthritis Rheum."); @@ -755,24 +756,27 @@ public void testPublicationDetail() log("Click the 'More Details' and validate that the detail content is as expected."); DataFinderPage.DataCard card = finder.getDataCards().get(0); - PublicationPanel detailPanel = card.viewDetail(); + PublicationPanel publicationPanel = card.viewDetail(); - assertTrue("Author value not as expected on detail page: " + detailPanel.getAuthor(), detailPanel.getAuthor().contains("Monach PA, Tomasson G, Specks U, Stone JH, Cuthbertson D")); - assertTrue("Title value not as expected on detail page:" + detailPanel.getTitle(), detailPanel.getTitle().contains("Circulating markers of vascular injury and angiogenesis in Antineutrophil Cytoplasmic Antibody-Associated Vasculitis.")); - assertTrue("Citation value not as expected on detail page:" + detailPanel.getCitation(), detailPanel.getCitation().contains("Arthritis Rheum 63: 3988-3997, 2011")); - assertTrue("PMID value not as expected on detail page:" + detailPanel.getPMID(), detailPanel.getPMID().contains("21953143")); - assertTrue("PMCID value not as expected on detail page:" + detailPanel.getPMCID(), detailPanel.getPMCID().contains("PMC3227746")); - assertTrue("DOI value not as expected on detail page:" + detailPanel.getDOI(), detailPanel.getDOI().contains("10.1002/ART.30615")); - assertTrue("Studies value not as expected on detail page:" + detailPanel.getStudyShortName(), detailPanel.getStudyShortName().contains("RAVE")); + assertTrue("Author value not as expected on detail page: " + publicationPanel.getAuthor(), publicationPanel.getAuthor().contains("Monach PA, Tomasson G, Specks U, Stone JH, Cuthbertson D")); + assertTrue("Title value not as expected on detail page:" + publicationPanel.getTitle(), publicationPanel.getTitle().contains("Circulating markers of vascular injury and angiogenesis in Antineutrophil Cytoplasmic Antibody-Associated Vasculitis.")); + assertTrue("Citation value not as expected on detail page:" + publicationPanel.getCitation(), publicationPanel.getCitation().contains("Arthritis Rheum 63: 3988-3997, 2011")); + assertTrue("PMID value not as expected on detail page:" + publicationPanel.getPMID(), publicationPanel.getPMID().contains("21953143")); + assertTrue("PMCID value not as expected on detail page:" + publicationPanel.getPMCID(), publicationPanel.getPMCID().contains("PMC3227746")); + assertTrue("DOI value not as expected on detail page:" + publicationPanel.getDOI(), publicationPanel.getDOI().contains("10.1002/ART.30615")); + assertTrue("Studies value not as expected on detail page:" + publicationPanel.getStudyShortName(), publicationPanel.getStudyShortName().contains("RAVE")); + card = finder.getDataCards().get(0); card.hideDetail(); - Assert.assertFalse("Author value not as expected in collapsed view", detailPanel.getAuthor().contains("Cuthbertson")); - Assert.assertFalse("PMID should not be displayed in collapsed view", detailPanel.isPMIDDisplayed()); - Assert.assertFalse("PMCID should not be displayed in collapsed view", detailPanel.isPMCIDDisplayed()); + publicationPanel = new PublicationPanel(this); + Assert.assertFalse("Author value not as expected in collapsed view", publicationPanel.getAuthor().contains("Cuthbertson")); + Assert.assertFalse("PMID should not be displayed in collapsed view", publicationPanel.isPMIDDisplayed()); + Assert.assertFalse("PMCID should not be displayed in collapsed view", publicationPanel.isPMCIDDisplayed()); // open it up again and make sure we have only one copy of the fields + card = finder.getDataCards().get(0); card.viewDetail(); - Assert.assertEquals("Should still have just one PMID", 1, detailPanel.getPMIDCount()); + Assert.assertEquals("Should still have just one PMID", 1, publicationPanel.getPMIDCount()); log("Go to another publication that doesn't have the same type of detail."); @@ -788,30 +792,20 @@ public void testPublicationDetail() log("Show details, this time validate that the missing values are rendered as expected."); card = finder.getDataCards().get(0); - detailPanel = card.viewDetail(); + publicationPanel = card.viewDetail(); - assertTrue("Author value not as expected on detail page:" + detailPanel.getAuthor(), detailPanel.getAuthor().contains("Ytterberg SR, Mueller M, Sejismundo LP, Mieras K, Stone JH.")); - assertTrue("Title value not as expected on detail page.", detailPanel.getTitle().contains("Efficacy of Remission-Induction Regimens for ANCA-Associated Vasculitis")); - assertTrue("Citation value not as expected on detail page.", detailPanel.getCitation().contains("New Eng J Med 2013; 369:417-427")); - assertTrue("PMID value not as expected on detail page.", detailPanel.getPMID().contains("23902481")); - assertTrue("PMCID value not as expected on detail page.", detailPanel.getPMCID().contains("")); - assertTrue("DOI value not as expected on detail page.", detailPanel.getDOI().contains("")); - assertTrue("Studies value not as expected on detail page.", detailPanel.getStudyShortName().contains("RAVE")); + assertTrue("Author value not as expected on detail page:" + publicationPanel.getAuthor(), publicationPanel.getAuthor().contains("Ytterberg SR, Mueller M, Sejismundo LP, Mieras K, Stone JH.")); + assertTrue("Title value not as expected on detail page.", publicationPanel.getTitle().contains("Efficacy of Remission-Induction Regimens for ANCA-Associated Vasculitis")); + assertTrue("Citation value not as expected on detail page.", publicationPanel.getCitation().contains("New Eng J Med 2013; 369:417-427")); + assertTrue("PMID value not as expected on detail page.", publicationPanel.getPMID().contains("23902481")); + assertTrue("PMCID value not as expected on detail page.", publicationPanel.getPMCID().contains("")); + assertTrue("DOI value not as expected on detail page.", publicationPanel.getDOI().contains("")); + assertTrue("Studies value not as expected on detail page.", publicationPanel.getStudyShortName().contains("RAVE")); + card = finder.getDataCards().get(0); card.hideDetail(); } - @Ignore("Not yet implemented well") - @Test - public void testAssayVisibility() - { - goToProjectHome(); - impersonate(PUBLIC_READER); - DataFinderPage finder = new DataFinderPage(this, true); - // TODO - stopImpersonating(); - - } @LogMethod(quiet = true) private void assertCountsSynced(DataFinderPage finder) From 6b06a8995ae072cea05b8a4b578f107c2ac71e7b Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 29 Feb 2016 07:26:54 -0800 Subject: [PATCH 180/587] Spec 25400: update automated test for rename of StudyContainers to StudyAccess --- .../test/pages/trialshare/StudyPropertiesQueryUpdatePage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java b/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java index 065fc623..5be340a9 100644 --- a/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java +++ b/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java @@ -22,7 +22,7 @@ public void setStudyContainers(Set loadedStudies, String publicStudyName _test.log("Setting up study container links"); _test.goToProjectHome(); String projectName = _test.getCurrentProject(); - clickAndWait(Locator.linkWithText("StudyContainer")); + clickAndWait(Locator.linkWithText("StudyAccess")); DataRegionTable table = new DataRegionTable("query", _test); for (int i = 0; i < table.getDataRowCount(); i++) { From 10df4f9fdfe8831e4d8a43ad2aa02535a180feba Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 29 Feb 2016 09:37:28 -0800 Subject: [PATCH 181/587] Spec 25400: add automated tests for go to study menu --- .../web/study/Finder/panel/StudyCards.js | 1 + .../test/pages/trialshare/DataFinderPage.java | 6 +++ .../trialshare/TrialShareDataFinderTest.java | 37 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index ce5bc658..361de149 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -160,6 +160,7 @@ Ext4.define("LABKEY.study.panel.StudyCards", { return; var studyLinksMenu = Ext4.create('Ext.menu.Menu', { + cls: 'labkey-study-goto-menu', showSeparator: false }); diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java index b5a20486..526bc651 100644 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java @@ -608,6 +608,11 @@ public StudySummaryWindow viewStudySummary() return new StudySummaryWindow(_test); } + public void clickGoToStudy(String choice) + { + _ext4Helper.clickExt4MenuButton(false, locators.goToStudyLink, false, choice); + } + public void clickGoToStudy() { _test.clickAndWait(locators.goToStudyLink.findElement(card)); @@ -656,6 +661,7 @@ private class Locators { public Locator viewStudyLink = Locator.linkWithText("view summary"); public Locator goToStudyLink = Locator.linkWithText("go to study"); + public Locator.XPathLocator goToStudyMenu = Locator.tagWithClass("div", "labkey-study-goto-menu"); public Locator studyAccession = Locator.css(".labkey-study-card-accession"); public Locator studyShortName = Locator.css(".labkey-study-card-short-name"); public Locator studyPI = Locator.css(".labkey-study-card-pi"); diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index c6c16502..056c393f 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -507,6 +507,42 @@ public void testStudyCardStudyLinks() assertEquals("Didn't find all studies", loadedStudies, foundNames); } + @Test + public void testGoToStudyMenu() + { + DataFinderPage finder = new DataFinderPage(this, true); + DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); + log("Filtering to show DIAMOND card with two study containers"); + facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Public"); + doAndWaitForPageSignal(() -> facets.toggleFacet(DataFinderPage.Dimension.CONDITION, "Lupus Nephritis"), DataFinderPage.COUNT_SIGNAL); + List dataCards = finder.getDataCards(); + Assert.assertEquals("Should have a single data card at this point", 1, dataCards.size()); + DataFinderPage.DataCard card = dataCards.get(0); + Assert.assertEquals("DIAMOND", card.getStudyShortName()); + log("Go to operational study"); + card.clickGoToStudy("/" + getProjectName() + "/" + OPERATIONAL_STUDY_NAME); + + log("Impersonating public reader who should see only one go to study link"); + goToProjectHome(); + impersonate(PUBLIC_READER); + } + + @Test + public void testGoToStudyNoMenuForPublicReader() + { + log("Impersonating public reader who should see only one go to study link"); + impersonate(PUBLIC_READER); + DataFinderPage finder = new DataFinderPage(this, true); + DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); + log("Filtering to show DIAMOND card"); + doAndWaitForPageSignal(() -> facets.toggleFacet(DataFinderPage.Dimension.CONDITION, "Lupus Nephritis"), DataFinderPage.COUNT_SIGNAL); + List dataCards = finder.getDataCards(); + Assert.assertEquals("Should have a single data card at this point", 1, dataCards.size()); + DataFinderPage.DataCard card = dataCards.get(0); + Assert.assertEquals("DIAMOND", card.getStudyShortName()); + card.clickGoToStudy(); + } + @Test @Ignore("Session storage not yet in use") public void testNavigationDoesNotRemoveFinderFilter() @@ -710,6 +746,7 @@ public void testFilterOnStatus() log("Validate counts for 'Complete' publications."); counts = fg.getMemberCounts(DataFinderPage.Dimension.COMPLETE); + // one is "in progress" and one is set to not show assertEquals("Expected count after filtering for 'Complete' was not as expected.", 126, counts.get("Complete").intValue()); log("Validate that there are no 'In Progress' cards visible."); From de602a758a7d55cc5d53116885f7e07f47c25e36 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 29 Feb 2016 11:57:45 -0800 Subject: [PATCH 182/587] Spec 25400: add tabs for switching between studies and publications --- resources/web/study/Finder/dataFinder.css | 41 +++++++++++++++++-- .../study/Finder/panel/FacetPanelHeader.js | 38 +---------------- .../web/study/Finder/panel/FinderCardDeck.js | 5 ++- 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 26000a6d..ecd43a4d 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -13,10 +13,39 @@ /*float: right;*/ /*}*/ +.labkey-data-finder-view +{ + padding-left: 2px; +} + +.labkey-data-finder-view .x4-tab +{ + border-bottom-width: 0; +} + +.labkey-data-finder-view .x4-tab-bar-strip +{ + height: 0; + border-top-width: 0; +} + +.labkey-data-finder-view .x4-tab-default-top-active +{ + background-image: -webkit-linear-gradient(top,rgb(240, 240, 240),rgb(240, 240, 240)); +} + +.labkey-data-finder-view .x4-tab-bar-body-default-top { + padding-bottom: 0; +} + +.labkey-data-finder-card-deck-view .x4-panel-body-default { + border-top-width: 0; +} + .labkey-studies-panel, .labkey-publications-panel { - padding: 5px 0px 0px 0px; + padding: 0; } .labkey-finder-object-selection { @@ -167,7 +196,7 @@ DIV.labkey-search-message .labkey-facet-selection-panel { - padding: 5px 0px 5px 5px; + padding: 0 0 5px 0px; vertical-align: text-top; height:100%; background-color: white; @@ -242,10 +271,16 @@ DIV.labkey-facet-summary cursor:pointer; } +.labkey-facet-summary-header +{ + border-left-style: solid; + border-left-width: 1px; + border-left-color: rgb(180, 180, 180); +} + .labkey-facet-header { padding:4pt; - /*max-width: 185pt;*/ background-color: rgb(240, 240, 240); } diff --git a/resources/web/study/Finder/panel/FacetPanelHeader.js b/resources/web/study/Finder/panel/FacetPanelHeader.js index 0b3eca6b..335ca5ff 100644 --- a/resources/web/study/Finder/panel/FacetPanelHeader.js +++ b/resources/web/study/Finder/panel/FacetPanelHeader.js @@ -8,7 +8,7 @@ Ext4.define("LABKEY.study.panel.FacetPanelHeader", { layout: 'vbox', - cls: 'labkey-facet-header', + cls: 'labkey-facet-summary-header labkey-facet-header', bubbleEvents: ["clearAllFilters", "finderObjectChanged"], @@ -46,7 +46,6 @@ Ext4.define("LABKEY.study.panel.FacetPanelHeader", { width: "100%", items: [ this.getSummaryLabel(), - this.getCubeConfigMenu(), { // spacer xtype: 'box', @@ -76,45 +75,12 @@ Ext4.define("LABKEY.study.panel.FacetPanelHeader", { { this.summaryLabel = Ext4.create("Ext4.Component", { html: "Summary", - hidden: this.cubeConfigs.count() > 1 + cls: 'labkey-facet-panel-summary', }); } return this.summaryLabel; }, - - getCubeConfigMenu: function() { - if (!this.cubeConfigsMenu) { - this.cubeConfigsMenu = Ext4.create('Ext.form.ComboBox', { - store: this.cubeConfigs, - queryMode: 'local', - name: 'configSelect', - valueField: 'objectName', - displayField: 'objectNamePlural', - hidden: this.cubeConfigs.count() < 2, - fieldLabel: "View", - labelWidth: 30, - padding: '0 0 0 3', - labelSeparator: "", - labelPadding: 1, - value: this.objectName, - multiSelect: false, - listeners: { - scope: this, - 'select': function(field, newValue, oldValue, eOpts) { - this.cubeConfigs.selectedValue = newValue[0].data.objectName; - this.onCubeConfigChanged(newValue[0].data.objectName) - }, - 'render': function(eOpts) { - if (this.cubeConfigs.selectedValue) - this.onCubeConfigChanged(this.cubeConfigs.selectedValue) - } - } - }) - } - return this.cubeConfigsMenu; - }, - onFilterSelectionChange: function(hasFilters) { if (hasFilters) Ext4.get(Ext4.DomQuery.select('.labkey-clear-all', this.id)[0]).replaceCls('inactive', 'active'); diff --git a/resources/web/study/Finder/panel/FinderCardDeck.js b/resources/web/study/Finder/panel/FinderCardDeck.js index b216f69c..4085279a 100644 --- a/resources/web/study/Finder/panel/FinderCardDeck.js +++ b/resources/web/study/Finder/panel/FinderCardDeck.js @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 */ Ext4.define('LABKEY.study.panel.FinderCardDeck', { - extend: 'Ext.panel.Panel', + extend: 'Ext.tab.Panel', alias: 'widget.labkey-data-finder-card-deck', @@ -23,6 +23,7 @@ Ext4.define('LABKEY.study.panel.FinderCardDeck', { for (var i = 0; i < this.cubeConfigs.length; i++) { this.items.push(Ext4.create('LABKEY.study.panel.FinderCard', { + title: Ext4.htmlEncode(this.cubeConfigs[i].objectNamePlural), dataModuleName: this.dataModuleName, cubeConfig: this.cubeConfigs[i], itemId: this.cubeConfigs[i].objectName + '-finder-card' @@ -33,7 +34,7 @@ Ext4.define('LABKEY.study.panel.FinderCardDeck', { this.callParent(); - this.getLayout().setActiveItem(activeIndex); + this.setActiveTab(activeIndex); } }); From 80136c83da0148dda93315af06aac6d7c609aaaf Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 29 Feb 2016 15:04:43 -0800 Subject: [PATCH 183/587] Spec 25400: add formatting for studies count in publications card --- .../web/study/Finder/panel/PublicationSummary.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/resources/web/study/Finder/panel/PublicationSummary.js b/resources/web/study/Finder/panel/PublicationSummary.js index 8338f0cb..90cd65dd 100644 --- a/resources/web/study/Finder/panel/PublicationSummary.js +++ b/resources/web/study/Finder/panel/PublicationSummary.js @@ -28,10 +28,14 @@ Ext4.define("LABKEY.study.panel.PublicationSummary", { { formatNumber : Ext4.util.Format.numberRenderer('0,000'), - getPublicationCount : function(defaultValue) { + getPublicationCount : function(defaultValue) + { var store = Ext4.getStore("Publication"); if (!store) + { + console.log("Publication store not available. Using " + defaultValue); return this.formatNumber(defaultValue); + } return this.formatNumber(store.count()); }, @@ -39,7 +43,10 @@ Ext4.define("LABKEY.study.panel.PublicationSummary", { { var store = Ext4.getStore("PublicationFacetMembers"); if (!store) + { + console.log("PublicationFacetMembers store not available. Using " + defaultValue); return this.formatNumber(defaultValue); + } var count = 0; for (var i = 0; i < store.count(); i++) { @@ -47,7 +54,7 @@ Ext4.define("LABKEY.study.panel.PublicationSummary", { if (member.data.facet.data.name == "Study" && member.data.count > 0) count++; } - return count; + return this.formatNumber(count); } } ), From a71a5218a277d7480520de865c6867fa057b33fc Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 29 Feb 2016 15:05:17 -0800 Subject: [PATCH 184/587] Spec 25400: use cube container for retrieving query schema --- src/org/labkey/trialshare/query/TrialShareQuerySchema.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index 175c0591..e241b4b1 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -1,9 +1,11 @@ package org.labkey.trialshare.query; import org.labkey.api.data.Container; +import org.labkey.api.module.ModuleLoader; import org.labkey.api.query.DefaultSchema; import org.labkey.api.query.QuerySchema; import org.labkey.api.security.User; +import org.labkey.trialshare.TrialShareModule; /** * Created by susanh on 2/23/16. @@ -25,7 +27,8 @@ public class TrialShareQuerySchema public static QuerySchema getSchema(User user, Container container) { - QuerySchema coreSchema = DefaultSchema.get(user, container).getSchema("core"); + Container cubeContainer = ((TrialShareModule) ModuleLoader.getInstance().getModule(TrialShareModule.NAME)).getCubeContainer(container); + QuerySchema coreSchema = DefaultSchema.get(user, cubeContainer).getSchema("core"); return coreSchema.getSchema("lists"); } } From d44ec29ea692b4e6c0a35892e8cfdf2cebc4c07d Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 29 Feb 2016 15:05:57 -0800 Subject: [PATCH 185/587] Spec 25400: add some judicious waiting for our automated tests --- .../test/pages/trialshare/DataFinderPage.java | 26 ++++++------------ .../trialshare/TrialShareDataFinderTest.java | 27 +++++++------------ 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java index 526bc651..130e0106 100644 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java @@ -73,7 +73,7 @@ protected void waitForGroupUpdate() public static DataFinderPage goDirectlyToPage(BaseWebDriverTest test, String containerPath, boolean testingStudies) { - test.beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)); + test.doAndWaitForPageSignal(() -> test.beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), COUNT_SIGNAL); return new DataFinderPage(test, testingStudies); } @@ -151,22 +151,7 @@ public void navigateToPublications() public void selectDataFinderObject(String text) { - _ext4Helper.openComboList(DataFinderPage.Locators.finderObjectCombo); - if (!_test.isElementPresent(Ext4Helper.Locators.comboListItemSelected().withText(text))) - { - _ext4Helper.selectItemFromOpenComboList(text, Ext4Helper.TextMatchTechnique.EXACT); -// _test.doAndWaitForElementToRefresh(() -> _ext4Helper.selectItemFromOpenComboList(text, Ext4Helper.TextMatchTechnique.EXACT), Locators.cardDeck, shortWait()); -// _test.doAndWaitForPageSignal(() ->_ext4Helper.selectItemFromOpenComboList(text, Ext4Helper.TextMatchTechnique.EXACT), COUNT_SIGNAL); - - } - else // FIXME you should be able to just close the combo box at this point, but the close method assumes you've chosen something from the list - { - _ext4Helper.selectItemFromOpenComboList(text, Ext4Helper.TextMatchTechnique.EXACT); - } - } - public void navigate(String buttonText) - { - clickButton(buttonText, 0); + Locators.finderObjectTab(text).findElement(getDriver()).click(); } public Map getSummaryCounts() @@ -296,6 +281,11 @@ public static Locator.CssLocator getActiveClearAll(Locator.CssLocator locator) return locator.append(".active"); } + public static Locator.XPathLocator finderObjectTab(String objectName) + { + return Locator.tagWithText("span", objectName); + } + } public enum Dimension @@ -615,7 +605,7 @@ public void clickGoToStudy(String choice) public void clickGoToStudy() { - _test.clickAndWait(locators.goToStudyLink.findElement(card)); + locators.goToStudyLink.findElement(card).click(); } public String getStudyAccession() diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 056c393f..13a8e965 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -29,7 +29,6 @@ import org.labkey.test.TestFileUtils; import org.labkey.test.TestTimeoutException; import org.labkey.test.WebTestHelper; -import org.labkey.test.categories.Data; import org.labkey.test.categories.Git; import org.labkey.test.components.study.StudyOverviewWebPart; import org.labkey.test.components.trialshare.PublicationPanel; @@ -348,8 +347,6 @@ public void testDataFinderRelocation() Assert.assertTrue("Should see the visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); facetGrid.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Public"); Assert.assertEquals("Should see all the study cards", 12, finder.getDataCards().size()); - containerHelper.deleteProject(RELOCATED_DATA_FINDER_PROJECT, false); - } @Test @@ -393,7 +390,7 @@ public void testSelectingEmptyMeasure() facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"); facets.toggleFacet(DataFinderPage.Dimension.ASSAY, "Elispot"); - + facets = finder.getFacetsGrid(); List filteredStudyCards = finder.getDataCards(); assertEquals("Study cards visible after selection", 0, filteredStudyCards.size()); @@ -447,9 +444,6 @@ public void testStudySummaryWindow() assertEquals("Study card does not match summary (Accession)", studyCard.getStudyAccession().toLowerCase(), summaryWindow.getAccession().toLowerCase()); assertEquals("Study card does not match summary (Short Name)", studyCard.getStudyShortName().toLowerCase(), summaryWindow.getShortName().toLowerCase()); assertEquals("Study card does not match summary (Title)", studyCard.getTitle().toUpperCase(), summaryWindow.getTitle().toUpperCase()); -// String cardPI = studyCard.getStudyPI(); -// String summaryPI = summaryWindow.getStudyPI(); -// assertTrue("Study card does not match summary (studyPI)", summaryPI.contains(cardPI)); summaryWindow.closeWindow(); } @@ -494,12 +488,14 @@ public void testStudyCardStudyLinks() { if (name.contains(studyCard.getStudyShortName())) { + log("Going to study " + name); String shortName = studyCard.getStudyShortName(); foundNames.add(name); studyCard.clickGoToStudy(); + switchToWindow(foundNames.size()); WebElement title = Locator.css(".labkey-folder-title > a").waitForElement(shortWait()); Assert.assertTrue("Study card " + name + " linked to wrong study", title.getText().contains(shortName)); - goBack(); + switchToMainWindow(); break; // we've found it so we don't need to look further } } @@ -521,10 +517,6 @@ public void testGoToStudyMenu() Assert.assertEquals("DIAMOND", card.getStudyShortName()); log("Go to operational study"); card.clickGoToStudy("/" + getProjectName() + "/" + OPERATIONAL_STUDY_NAME); - - log("Impersonating public reader who should see only one go to study link"); - goToProjectHome(); - impersonate(PUBLIC_READER); } @Test @@ -551,7 +543,6 @@ public void testNavigationDoesNotRemoveFinderFilter() DataFinderPage.FacetGrid facetsGrid = finder.getFacetsGrid(); facetsGrid.toggleFacet(DataFinderPage.Dimension.THERAPEUTIC_AREA, "Allergy"); - Map> selections = finder.getFacetsGrid().getSelectedMembers(); clickTab("Manage"); clickTab("Overview"); @@ -699,11 +690,11 @@ public void testSwitchBetweenStudyAndPublication() { DataFinderPage finder = new DataFinderPage(this, true); log("Start at home."); - goToProjectHome(); - assertElementVisible(DataFinderPage.Locators.studyFinder); - log("Click the 'Publications' button."); + doAndWaitForPageSignal(() -> goToProjectHome(), DataFinderPage.COUNT_SIGNAL); + waitForElement(DataFinderPage.Locators.studyFinder); + log("Click the 'Publications' tab"); finder.navigateToPublications(); - log("Go back by clicking the 'Studies' button"); + log("Go back by clicking the 'Studies' tab"); finder.navigateToStudies(); } @@ -723,7 +714,7 @@ public void testFilterOnStatus() log("Filter for 'In Progress' only publications."); DataFinderPage.FacetGrid fg = finder.getFacetsGrid(); - fg.toggleFacet(DataFinderPage.Dimension.STATUS, "In Progress"); + doAndWaitForPageSignal(() -> fg.toggleFacet(DataFinderPage.Dimension.STATUS, "In Progress"), DataFinderPage.COUNT_SIGNAL); log("Validate that the number, content and style of the cards is as expected."); counts = fg.getMemberCounts(DataFinderPage.Dimension.IN_PROGRESS); From 1b7a731350c1e34f0cd1bf7f2be7da11658c5253 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 29 Feb 2016 15:25:03 -0800 Subject: [PATCH 186/587] Spec 25400: use display text for text in dropdown not value --- resources/web/study/Finder/panel/StudyCards.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index 361de149..bcc628db 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -172,8 +172,8 @@ Ext4.define("LABKEY.study.panel.StudyCards", { for (var i = 0; i < studyLinks.length; i++) { studyLinksMenu.add({ - value: studyLinks[i].displayName ? studyLinks[i].displayName : studyLinks[i].studyContainerPath, - text: studyLinks[i].studyContainerPath + text: studyLinks[i].displayName ? studyLinks[i].displayName : studyLinks[i].studyContainerPath, + value: studyLinks[i].studyContainerPath }); } studyLinksMenu.showAt(event.xy); From ddc0ab650e464b450bbb060631f669ce1a658d77 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 29 Feb 2016 16:37:04 -0800 Subject: [PATCH 187/587] Spec 25400: code clean up after code review --- resources/olap/StudyCube.xml | 5 ----- src/org/labkey/trialshare/TrialShareController.java | 4 ---- src/org/labkey/trialshare/TrialShareManager.java | 2 +- src/org/labkey/trialshare/data/FacetFilter.java | 2 +- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/resources/olap/StudyCube.xml b/resources/olap/StudyCube.xml index 5afccd3f..4c1569f1 100644 --- a/resources/olap/StudyCube.xml +++ b/resources/olap/StudyCube.xml @@ -46,11 +46,6 @@ - - - - - diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 0cd6d376..440b7668 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -989,10 +989,6 @@ public Object execute(AccessibleMembersForm accessibleMembersForm, BindException { levelMembers.put("[Publication].[Publication]", TrialShareManager.get().getVisiblePublications(getUser(), getContainer())); } -// else if (_objectName.equalsIgnoreCase("study")) -// { -// levelMembers.put("[Study.AssayVisibility].[Study]", TrialShareManager.get().getVisibleAssays(getUser(), getContainer())); -// } return success(levelMembers); } } diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 1289b1f8..a71cb752 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -160,7 +160,7 @@ public Set getVisiblePublications(User user, Container container) { publicationIds.add(publication.getCubeId()); } - }; + } } return publicationIds; } diff --git a/src/org/labkey/trialshare/data/FacetFilter.java b/src/org/labkey/trialshare/data/FacetFilter.java index 89336e12..d32c6e04 100644 --- a/src/org/labkey/trialshare/data/FacetFilter.java +++ b/src/org/labkey/trialshare/data/FacetFilter.java @@ -23,7 +23,7 @@ */ public class FacetFilter { - public enum Type { OR, AND }; + public enum Type { OR, AND } private Type _type; private String _caption; From b284eca629f0c5b7ef09bf8c6800281e8d11249e Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 29 Feb 2016 20:54:52 -0800 Subject: [PATCH 188/587] Spec 25400: update automated tests --- resources/web/study/Finder/panel/PublicationSummary.js | 2 +- .../test/tests/trialshare/TrialShareDataFinderTest.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/web/study/Finder/panel/PublicationSummary.js b/resources/web/study/Finder/panel/PublicationSummary.js index 90cd65dd..ee808ae2 100644 --- a/resources/web/study/Finder/panel/PublicationSummary.js +++ b/resources/web/study/Finder/panel/PublicationSummary.js @@ -65,8 +65,8 @@ Ext4.define("LABKEY.study.panel.PublicationSummary", { }, initComponent: function() { - this.callParent(); Ext4.getStore(this.objectName).addListener('filterChange',this.onFilterSelectionChanged, this); + this.callParent(); }, onFilterSelectionChanged : function() { diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 13a8e965..71ce67b3 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -324,6 +324,7 @@ public void testOperationalAccess() facetGrid.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"); List cards = finder.getDataCards(); Assert.assertEquals("User with access to only WISP-R study should see only that study", 1, cards.size()); + stopImpersonating(); } @@ -533,6 +534,7 @@ public void testGoToStudyNoMenuForPublicReader() DataFinderPage.DataCard card = dataCards.get(0); Assert.assertEquals("DIAMOND", card.getStudyShortName()); card.clickGoToStudy(); + stopImpersonating(); } @Test @@ -777,7 +779,7 @@ public void testPublicationDetail() log("Filter for a publication that has DOI, PMID and PMCID values."); fg = finder.getFacetsGrid(); fg.toggleFacet(DataFinderPage.Dimension.YEAR, "2011"); - fg.toggleFacet(DataFinderPage.Dimension.PUBLICATION_JOURNAL, "Arthritis Rheum."); + doAndWaitForPageSignal(() -> fg.toggleFacet(DataFinderPage.Dimension.PUBLICATION_JOURNAL, "Arthritis Rheum."), DataFinderPage.COUNT_SIGNAL); summaryCount = finder.getSummaryCounts(); assertTrue("Number of publication cards returned does not match dimension count. Number of cards: " + finder.getDataCards().size() + " Count in dimension: " + summaryCount.get(DataFinderPage.Dimension.PUBLICATIONS), summaryCount.get(DataFinderPage.Dimension.PUBLICATIONS) == finder.getDataCards().size()); From adb6124c937a97311b967cccd402a0ec8ab99419 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 1 Mar 2016 08:58:12 -0800 Subject: [PATCH 189/587] Spec 25400: fix summary panel display issue --- .../web/study/Finder/panel/FacetSelection.js | 4 ++ .../web/study/Finder/panel/FacetsGrid.js | 4 +- .../study/Finder/panel/PublicationSummary.js | 48 +++++++------------ .../web/study/Finder/panel/StudySummary.js | 24 +++------- 4 files changed, 28 insertions(+), 52 deletions(-) diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index 7615c684..d51171b3 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -21,6 +21,10 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { autoScroll: false, initComponent : function() { + Ext4.create('LABKEY.study.store.FacetMembers', { + storeId : this.cubeConfig.objectName + "FacetMembers" + }); + this.items = [ this.getFacetPanelHeader(), this.getFacetSelectionSummary(), diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index f1f84c7d..8c6836cf 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -94,9 +94,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { storeId: this.cubeConfig.objectName + "Facets" }); - this.store = Ext4.create('LABKEY.study.store.FacetMembers', { - storeId : this.cubeConfig.objectName + "FacetMembers" - }); + this.store = Ext4.getStore(this.cubeConfig.objectName + "FacetMembers"); // This makes the objectName available to the header this.features = this.getGroupHeaderFeature(this.cubeConfig.objectName); diff --git a/resources/web/study/Finder/panel/PublicationSummary.js b/resources/web/study/Finder/panel/PublicationSummary.js index ee808ae2..3d87fc00 100644 --- a/resources/web/study/Finder/panel/PublicationSummary.js +++ b/resources/web/study/Finder/panel/PublicationSummary.js @@ -16,46 +16,17 @@ Ext4.define("LABKEY.study.panel.PublicationSummary", { '
            ', '
          • ', ' Publications', - ' {publicationCount:this.getPublicationCount}', + ' {publicationCount:this.formatNumber}', '
          • ', '
          • ', ' Studies', - ' {studyCount:this.getStudyCount}', + ' {studyCount:this.formatNumber}', '
          • ', '
          ', ' ', '', { formatNumber : Ext4.util.Format.numberRenderer('0,000'), - - getPublicationCount : function(defaultValue) - { - var store = Ext4.getStore("Publication"); - if (!store) - { - console.log("Publication store not available. Using " + defaultValue); - return this.formatNumber(defaultValue); - } - return this.formatNumber(store.count()); - }, - - getStudyCount: function(defaultValue) - { - var store = Ext4.getStore("PublicationFacetMembers"); - if (!store) - { - console.log("PublicationFacetMembers store not available. Using " + defaultValue); - return this.formatNumber(defaultValue); - } - var count = 0; - for (var i = 0; i < store.count(); i++) - { - var member = store.getAt(i); - if (member.data.facet.data.name == "Study" && member.data.count > 0) - count++; - } - return this.formatNumber(count); - } } ), @@ -66,10 +37,23 @@ Ext4.define("LABKEY.study.panel.PublicationSummary", { initComponent: function() { Ext4.getStore(this.objectName).addListener('filterChange',this.onFilterSelectionChanged, this); + Ext4.getStore(this.objectName).addListener('load', this.onFilterSelectionChanged, this); + Ext4.getStore("PublicationFacetMembers").addListener('load', this.onFilterSelectionChanged, this); this.callParent(); }, onFilterSelectionChanged : function() { - this.update(); + var store = Ext4.getStore("PublicationFacetMembers"); + var count = 0; + for (var i = 0; i < store.count(); i++) + { + var member = store.getAt(i); + if (member.data.facet.data.name == "Study" && member.data.count > 0) + count++; + } + this.update( { + studyCount : count, + publicationCount : Ext4.getStore("Publication").count() + }); } }); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/StudySummary.js b/resources/web/study/Finder/panel/StudySummary.js index 6c3a76c4..62f182fa 100644 --- a/resources/web/study/Finder/panel/StudySummary.js +++ b/resources/web/study/Finder/panel/StudySummary.js @@ -14,31 +14,17 @@ Ext4.define("LABKEY.study.panel.StudySummary", { '
            ', '
          • ', ' Studies', - ' {studyCount:this.getStudyCount}', + ' {studyCount:this.formatNumber}', '
          • ', '
          • ', ' Subjects', - ' {participantCount:this.getParticipantCount}', + ' {participantCount:this.formatNumber}', '
          • ', '
          ', ' ', '', { formatNumber : Ext4.util.Format.numberRenderer('0,000'), - - getStudyCount : function(defaultValue) { - var studyStore = Ext4.getStore("Study"); - if (!studyStore) - return this.formatNumber(defaultValue); - return this.formatNumber(studyStore.count()); - }, - - getParticipantCount: function(defaultValue) { - var studyStore = Ext4.getStore("Study"); - if (!studyStore) - return this.formatNumber(defaultValue); - return this.formatNumber(studyStore.sum("participantCount")); - } } ), @@ -49,10 +35,14 @@ Ext4.define("LABKEY.study.panel.StudySummary", { initComponent: function() { Ext4.getStore(this.objectName).addListener('filterChange',this.onFilterSelectionChanged, this); + Ext4.getStore(this.objectName).addListener('load', this.onFilterSelectionChanged, this); this.callParent(); }, onFilterSelectionChanged : function() { - this.update(); + this.update( { + studyCount : Ext4.getStore("Study").count(), + participantCount : Ext4.getStore("Study").sum("participantCount") + }); } }); \ No newline at end of file From 066d6ca5fec13429130a591a030772e2d379cf56 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 1 Mar 2016 12:57:54 -0800 Subject: [PATCH 190/587] Spec 25400: more automated test fixes --- .../test/tests/trialshare/TrialShareDataFinderTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 71ce67b3..f9c23cea 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -481,10 +481,11 @@ public void testStudyCardStudyLinks() { DataFinderPage finder = new DataFinderPage(this, true); DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); + finder.clearAllFilters(); if (name.contains("Operational")) - facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"); + doAndWaitForPageSignal(() -> facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"), DataFinderPage.COUNT_SIGNAL); else - facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Public"); + doAndWaitForPageSignal(() -> facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Public"), DataFinderPage.COUNT_SIGNAL); for (DataFinderPage.DataCard studyCard : finder.getDataCards()) { if (name.contains(studyCard.getStudyShortName())) @@ -493,6 +494,7 @@ public void testStudyCardStudyLinks() String shortName = studyCard.getStudyShortName(); foundNames.add(name); studyCard.clickGoToStudy(); + log("Switching to window " + foundNames.size()); switchToWindow(foundNames.size()); WebElement title = Locator.css(".labkey-folder-title > a").waitForElement(shortWait()); Assert.assertTrue("Study card " + name + " linked to wrong study", title.getText().contains(shortName)); @@ -778,9 +780,11 @@ public void testPublicationDetail() log("Filter for a publication that has DOI, PMID and PMCID values."); fg = finder.getFacetsGrid(); + sleep(1000); // HACK to see if TeamCity will be happier fg.toggleFacet(DataFinderPage.Dimension.YEAR, "2011"); doAndWaitForPageSignal(() -> fg.toggleFacet(DataFinderPage.Dimension.PUBLICATION_JOURNAL, "Arthritis Rheum."), DataFinderPage.COUNT_SIGNAL); + summaryCount = finder.getSummaryCounts(); assertTrue("Number of publication cards returned does not match dimension count. Number of cards: " + finder.getDataCards().size() + " Count in dimension: " + summaryCount.get(DataFinderPage.Dimension.PUBLICATIONS), summaryCount.get(DataFinderPage.Dimension.PUBLICATIONS) == finder.getDataCards().size()); From 77d14704bfd554d2a1bc7fcad0544d337df5cf2e Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 2 Mar 2016 07:45:30 -0800 Subject: [PATCH 191/587] Spec 25400: more automated test fixes --- .../test/tests/trialshare/TrialShareDataFinderTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index f9c23cea..dacc1a90 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -125,7 +125,7 @@ public static void initTest() @Override protected BrowserType bestBrowser() { - return BrowserType.CHROME; + return BrowserType.FIREFOX; } @Override @@ -780,9 +780,7 @@ public void testPublicationDetail() log("Filter for a publication that has DOI, PMID and PMCID values."); fg = finder.getFacetsGrid(); - sleep(1000); // HACK to see if TeamCity will be happier - fg.toggleFacet(DataFinderPage.Dimension.YEAR, "2011"); - doAndWaitForPageSignal(() -> fg.toggleFacet(DataFinderPage.Dimension.PUBLICATION_JOURNAL, "Arthritis Rheum."), DataFinderPage.COUNT_SIGNAL); + doAndWaitForPageSignal(() -> fg.toggleFacet(DataFinderPage.Dimension.STATUS, "In Progress"), DataFinderPage.COUNT_SIGNAL); summaryCount = finder.getSummaryCounts(); From ef39fce613fed1d888c559ba949120e0cf36bc1c Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 3 Mar 2016 08:27:13 -0800 Subject: [PATCH 192/587] Automated test setup updates --- .../PublicationsQueryUpdatePage.java | 38 +++++++++++++++++++ .../trialshare/TrialShareDataFinderTest.java | 7 ++++ 2 files changed, 45 insertions(+) create mode 100644 test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java b/test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java new file mode 100644 index 00000000..e4dd0cf7 --- /dev/null +++ b/test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java @@ -0,0 +1,38 @@ +package org.labkey.test.pages.trialshare; + +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.Locator; +import org.labkey.test.pages.LabKeyPage; +import org.labkey.test.util.DataRegionTable; + +/** + * Created by susanh on 2/5/16. + */ +public class PublicationsQueryUpdatePage extends LabKeyPage +{ + public PublicationsQueryUpdatePage(BaseWebDriverTest test) + { + super(test); + } + + public void setPermissionsContainer(String publicStudyName, String operationalStudyName) + { + _test.log("Setting up permissions container"); + _test.goToProjectHome(); + String projectName = _test.getCurrentProject(); + clickAndWait(Locator.linkWithText("ManuscriptsAndAbstracts")); + DataRegionTable table = new DataRegionTable("query", _test); + int rowIndex = table.getRow("Status", "In Progress"); + + clickAndWait(table.updateLink(rowIndex)); + selectOptionByText(Locators.permissionsContainerSelect,operationalStudyName ); + clickButton("Submit"); + } + + private static class Locators + { + public static final Locator.XPathLocator manuscriptContainerSelect = Locator.tagWithName("select", "quf_ManuscriptContainer"); + public static final Locator.XPathLocator permissionsContainerSelect = Locator.tagWithName("select", "quf_PermissionsContainer"); + } + +} diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index dacc1a90..0f92b344 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -36,6 +36,7 @@ import org.labkey.test.pages.PermissionsEditor; import org.labkey.test.pages.study.ManageParticipantGroupsPage; import org.labkey.test.pages.trialshare.DataFinderPage; +import org.labkey.test.pages.trialshare.PublicationsQueryUpdatePage; import org.labkey.test.pages.trialshare.StudyPropertiesQueryUpdatePage; import org.labkey.test.util.APIContainerHelper; import org.labkey.test.util.AbstractContainerHelper; @@ -172,6 +173,9 @@ private void setUpProject() createStudy(OPERATIONAL_STUDY_NAME); StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); queryUpdatePage.setStudyContainers(loadedStudies, "/" + getProjectName() + "/" + PUBLIC_STUDY_NAME, "/" + getProjectName() + "/" + OPERATIONAL_STUDY_NAME); + PublicationsQueryUpdatePage pubUpdatePage = new PublicationsQueryUpdatePage(this); + pubUpdatePage.setPermissionsContainer("/" + getProjectName() + "/" + PUBLIC_STUDY_NAME, "/" + getProjectName() + "/" + OPERATIONAL_STUDY_NAME); + createUsers(); List propList = new ArrayList<>(); @@ -348,6 +352,7 @@ public void testDataFinderRelocation() Assert.assertTrue("Should see the visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); facetGrid.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Public"); Assert.assertEquals("Should see all the study cards", 12, finder.getDataCards().size()); + goToProjectHome(); } @Test @@ -473,9 +478,11 @@ public void testStudyParticipantCounts() assertEquals("Participant counts in study finder don't match LabKey studies", finderParticipantCounts, studyParticipantCounts); } + @Ignore("Flaky test") @Test public void testStudyCardStudyLinks() { + goToProjectHome(); Set foundNames = new HashSet<>(); for (String name : loadedStudies) { From 7ae0bc09754226022b9f601ee7582475b0f85e48 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 3 Mar 2016 08:27:58 -0800 Subject: [PATCH 193/587] New list archives for automated tests --- test/sampledata/DataFinder.lists.zip | Bin 29114 -> 29060 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/sampledata/DataFinder.lists.zip b/test/sampledata/DataFinder.lists.zip index 49955008c49df2b1261d51e0113f066e4e502c7d..6c2dd585b8b5b900d5799ac50c9af27fe9c5df4b 100644 GIT binary patch delta 19070 zcmV(|K+(Ut;{k-@0S!<~0|XQR2nYxOqCR7jo=+UHDfa;bqCR7@BLckw1EM}-v*QFv z0Ry5wW3w&>Fb03#Nu%H=&aCpE%cbnn@H1co{-v(~9EI*9GB>cNhtW3~jdFcsuhuuE z(x+1CthBY-F^s<Y&DK^5@^4}UZ^55>{_`~AKt_o`&CRU=pM!<-Q<#7`~`mKgDB^TR#P>N8u7oarOmN1IwY78tZQxJL^CEiB(UTWm?VsL23ac@#5LF zlS@A;Ucq1gTyEn1z)uPTe=fJ)Mr?|>b;wh{Zv(xmk%R3;kP7O|Z3kqjAI_%Sm1FU? zm7fVo4R?Rb_}bdcUqIe44xXiWmG%$T)^V02F~#Oudwj)y?9~Z3m(qri`^0ZX1Hmp`_qdX<~ znS%Pt@n1=~gGm(3@m?&m)G*6+_|YuYOgJ`C__BY4(5)4W4zem{J{XkJGs_HM!`Vps z$`;uyhKqK@?LYtffBffv{D06q4GHz}GY=JV_qQ-QU|RxE*dWLHf=vP6`ak~D3cKQV z3Z~syvitU;_}|B{xaKjq20!p{~ zgD-#gmye1IhruVt&#A%iuDDl zo+4}qF`mlpB!-iPaNd;J+Hp%DepR&43n71^JQ8%r!J}yFGXKz_A3PT(oC{!MD9~I5 zKKSvB6hIoD+wM%YUGP$E{0@K=d>sYvlr;>F>|_p71lZ0e;AD4!$oKw>aFOs;v!(2t zv$v1lEbyNMfeoOKXC)1nh95(Hk1mKW>3ljN9AT3_=z32McU^ioAQ-uyr{sV3#SAgr4%vCCAIMj|kiftY zUm<`@z2^WfanO!>@Y-_CK& zFqh+yWD>lKoun-Jz*KT!Ujb!RL8o=S=t)R0vpj>9!$Xm7-+KZU!N$F1>l%=0XQUjWC|S6wc)eL5jf^70S>&w<|GGx9@@d# z#IQ;skC+>?YYf@k`lYlZrmcTcow`w#eq#7tB&$+^mfL8Ww@zOYC2w6}a~&QStnGO* zJVsBTTRB1f86uq`v?(q+@3XfO+Un-j3e!q6!;qp%Cxs#hg}pN>w$G>`608kK*c5Qu z0u3v5+aem)fI+jo-^=^~+=Phe(eA};8YtGEvB( z2WS2o+j=tqj?DbB@esh@f;qeN#xq|6H?KfI#{n^Mb*Outb|YS{vXgt#@k58dgwOCb z%0~aWr~IVd>3=xpS)VB)nj#^h`J{-bu|Gf0L^|^&BOM?BNq1aESAfx2UjVL;U@zQ> ziAteBqZt^fxkU9(VcET8zd54=1zG3Jt$sWGdCi|vf2o+3{ zWus|LoI^9ThTIE)P{CNDg>w}MF~%>xx%}4&&Xn;cG2TqMZl-=v!DB*uC1tspvmDHt zBSW-mT)y~DoW{q-zK6vdfDH+<6!Y$wD#k*HlZ!$7gEFI)=@oy*vss{?9=xG}`rA0* zuRu|rwks!3*iYt)&+3!nX=viy2!IiX)rX5D*>Mw5Re>7Wur@;qY8eG zJpeDDt1sqoNASa)c##oGxEpp0R$%0E$3>j~4!0-*L{Bfvya7u^*y8~Qxe2e{`ZEC^ zm4cyzH|5W8Y&L&*>v*6>SB&T9DkGKbf0Dp$^z)0Kf-4LGc z@~%4su>UQ)>vr0oK{9{kaHing;%VX1d!GhOd1(?O&6JU5&Pa0{a&w7;X3jh^l#h(M znsFykCWXuq7(5xXr@(hfL8WwEfiewX0Jv`-7|C!1(FT8vfp$!oi*hYN*b2d>TGk}?z|Z@J zeRjfD-ROFt`LMh%jzC=sq7vM>GiNg&Zh8qU8dObrX)9s-eyj3GpF}P)ErsxG%|2}(ZG??>Jz&p zm^|QZ-w{kSqIH*G@-4sZp8|~(2CG1_4>XEGW$}TP(V6;jCn#q=Fsq62wgtNhUAQY` zkqqtkyKzfc%EZl%pgJ;^ni+{VOAs{YL(D4vHAa8O9c*ZS67Z*pJpn+J!B6JW3@k>D zb*H+81n30w2Y!SL!84AAz$yX2RU1L+ zQ&yebJmAkEe8UvJg7SmF1fN~@3YJ(W4=X3hkQ4ZX zwIOy|8^W$S14hI_mByJ3@&}HblNeEzBe$puN1McE&I0%arMA+cb2j=>^u<>>RAJ%CV%RMu-r`brix}G>CZxtld6R;1Z44uC9x_nQfwO@>nV2FtKgn0031`ukps`+f&hOyO#b_} z51`6wwCJM9hccUki54<2ZiE)9e9OO5c8v-0hV)i$2PqpTH<)ynpYgBZXA0Fr@n z*U#esNIIEvmwm(PE*?CG?AtYY#XndVy?%R&7wW^$*T{HpEPj|*=nR5@=soDZ({kDauMvLNeD1;f?y%^Gd(ZxB_{`1EfX_>+^#O1pu#Ofu91d_v4 zJ2wURZ|Cwa!nEzO8{LSv%AZ)hS~Ba^ck~!hBizmKQIY0Q4H{*uW|b?OQMi|wtK%#J zIm77*4oJ5SlDC)ShZ6xfr}C`;DWobtG?Mi-Fl-cRZ%8j)(B+bBlYGkMNyI>>ivMvv(BhO^f_mY!=Ey2IU0B}W?Wpo)zN zsOx~N%nS)hDb3qZSgWQqHJ5M~lum)?3y?{$v=6(#`GGTjo3%HwGiT*8`{#fB5C8O^ zS($WIss7%4d`et170Vb{2CqX=HMMghQ;AwVwycTTpMrmdrqV1_qd}SlSWhikNpVOb zABLGpGsskk^fn8JjhbTF_$Y|_91@vaSt~yFf`=vztXc!40U8r;h=fA zHTL1{W-fmY{lNDk1n3)+~dWqOd*XO6OzEU#& z)E7_E6Im=DML)@*bl4FY2ZV}0{VBWsKmBRR{Ykw%kfW(wTXuChiu#7&Rad&P@?_>AQ zEMtIeBvl*%krMD3wO6Gj?g|MwvWr#j*Fh0AneYH3frEv3OQvlu(JKkbaXFVjQy`0A52SA9n2AJ~j`@^s z?c9k)s0tbhwDvQ$W?^yT=RgQ=#ABCrHu@<@uWm*!qkC62`|XD|MRTlXyKAI>I%9R~ z6DwE1Q-Ob)dpGK)*Ly_z9>`5;l&x07+U$R(yemhEl(;zG?sP$T-waN(e8J&2s%41hkY;0%zrSP71RU_O zU=BFsPN+dTLvoH-4QoYYrg$u1stC6HJggUxZvrh7irL}SBhMylSYgBlwoJlfpwNHS zC_A=j6e*cmHOpK3)0?Db%2X4XYD(~!OpU2#!c;Tms2N$<21v{_Gd`Mfj~c^$q-Mn+ zHyRixpTOJ^aB;%aP}$IZ!L`oRK90RYF$ff&cs>ruT9ltz=ULi)4t7i6BbF}w5G2|) zip9i@DheGm!Xn{dmCpbJ&fIYkEf0TKN8$U=p$XnmfcJFXqX>-;uF~y0;PdOR-LC2_ zTo-N5a%m=3hGS2JlUrcqgjr3HZDfB{-vycxJ>jc*Kve=gHpe+f%t-0mAY%tQUBnA` zTfz%7n$La?XsWx2E)-Uo_!8cf6U5*XmL{7*5>65k=znb z*s+FfK4jworgX8hjrv_HzuuBr@4(q!nc4%E+HWh6P!456DdiKs-7*!Qeh#*3NZTAo z#o-pd%N_Y6#?Bw)Vi1RExqS*aOgIL7@+wG%j$<2S6t-mq6D%B?Qt@2)2~Pb~j%3P| z?&Q)`W9=gH5!45S$3~KF*f4)?o!G;fF4!E((IR7a{najFJ}R`sLk{-W5Ny7SI6%(; zyWx{UKz4J#!OCjW4a^O735BIr`@*l4IZ@5II0D5=#cm06UIEqPl-j->Y#cn6K|6 zV7g0y=_i2cCV&~hbx0kwQ{?m$a~gV$S-X7w4aC01#d(qVXTMn4tCK5BA$>;yr1ht$PuH+IT)7maN7nW>u=DRV{5B zSk%F!z9z#`{n0uMltNQNLE z8T^e>$oNf{`?sB*(d@G>kOp;QE8Y3rGXuhrVIQvMao}#i6d(YgXrk`uE^n{#kJA;R;Wrz z2z<&^jh>hKhGP-?S>34yqdb4HGnY{-ur~a!yRy3dFb&FKg5gM~h8ZT*z*Iy+4Kvm7 zJh|QD@~1_el&*g=4YbVDLIp7v31$UPa;Yii>D|814nF|0g!L($q@A=7#7FHmzm{uQqSj87Ob{(XYYUufKlnE@JAk z4Ymb(a*=-+0qfGCKkut!R>UYcM#MiLoSE}4Z4{mBS|xKg@GFlDG|OmjLKPpoU{{2= z?_sPrBWKpkAmTX0n}e})!*SC39RMZ{#0O2-?98N9?c|&FJT{nvE;(G?r%kuCnPob0 ziUz=unO4>f&6)1A%#_U-YA$N#wTxqd1;s0TT(W;j3)(^RKlU>fS6PVfFbdaO3Wu`o z5{B!>okmzQC9Ihe*31ZN0%0{H@W{NcWW&2CLd}3K2K2;j^ zy4gsg{9A@X6!G!|CSLPUm6nxerB3jbSosI*r0sAM z{{I1!^MU_DCncnDiTN?3XtBU(jQ-cZ#xh8%NmiXhrq1DT;T)a}t&84={A}n)11odF zsZzDSEF=^3(~ut;O!&KCh+Qrw=X-xK4veLlsSL zRILFWfbCsa+aalXx|CEs1iTIat#Gg8`%ds`7>S<@ehRwo@W)Sd7Bdh2DMf!A(;$Zi zKmwda+$`kRUoF9IKLpE@>2Po*v1yt$y*cTNG4*U)o(StdNj*OMP zPrhM7^CaFI7O-NbU`Z%#W)>MH*oA4CV|iqFG>zPn17ipmdZD$o(ss+bm~*oY-Nq=l zOp9%&Nx48$J6}Xi0W5h+T(^I|bm^nZm4c~PEHYDupC+H+yzh&o%Q*07;;kmr^waR= z0?RUS{ys(o=kJ1$oASiWd19svF;j-*jm|kk%$yr$**tc*L4~W6pkLm8m4weRPMFkB zurxuwq>t7~6E>P>8tvS)r(Al)*o0wx2}W3_3g!Y2p4Bww8#ql<@g09h^xR6C^aIK@ z{Gpa4aOW9EF!zQ04v6vaP2YUyDIEL36HNz_HafCYyB@)}yoOXonN3K{hCpfc96LAw zbi2y8Ed#!8ST;2Aj8&?iShdtNtJc;9^UJM%)kpe@$D8cJGMXPICE99LEV#eJr9^8b zqcgl~SGyCE`S>`?Yh2gnNgd^W|BLTh1)ieY?}6A&0U ziZ?LJ3Rhx~e-cA@O)oCSCoQ&gbSf5zSYM8C^)xI4Hr2z z+`y^A*-H=9ItG90Ow*Z}o-!J43(7W$&0r(N&AYj}O&W0}uPN*Zx}Mw9_Xi)h<@AyH zTrv9VLw%H91XcdRLp=0U9-#HK$k11_Lc+N>4t!TA=VCBy&pX`#3)O6*Z#*7hBMBDU zV()V#6(Yd=C$q2P;~In;YL5lf;41;<2B@GUFu#ye{tAD0<*Lr9J_k*$_AaV*W-o$$ zq0IHG94mX#r;){YMPL9N)12)76tOiM8#-5~J z?e=17L*HrEi-Xy8j^A4xSeguRra2chtND_qJ$pEx2hjdIcVDE#27|v)S7}N!ZHo#0 zp;RulyUBm6pkjcBbnM;;KWgYtE^MUN%GJ&pYcxM$9hPOnKZVALUHT@n>pdonK2YyO zwbrUPH}5#x4e3>*twVWjt4=2ecsB+3AOSc#!MfS^!2Dkt$~S_Xrmw6-@@8nRM*23w zdI_#prMhC&WhSUo^pW*P&RP;qJY1#6D>60)Sqy*AxZh%)-Edkv7)~idc#WgWvbA{~ z-@|1LQcaQSr6%?uHL=-AZwuX)s$mJ#vAD;RI5s}TJAw)HHoOHvtLGD@sP}bBxsJH8hHR z+z4Tdk&g@VHVMoB1+>8y@_sKE*2}n6Z6Eu4p*{UwnEgTL$mHBprF+#Q1GZtPXc#~h zXXN=Z5XtO58t@m>05CreLEH*gy)*9-&*P8FK@mV=9Dr;hgSGFEa91R{OWc|Ia0Pz^ z$F)yQLH#e1Eqkv5^Onw9(+iEbYgJRc~3jfr+E7wlylNwTe}% z9muF$nPJA~dShZ$t!WhYM-4!W1=xQ_;_z9B*#caeOJjgn>1UEzsMF00AD#2!P)F(* zQ1$}tF3e$dEpY3KS|V*HgG-9|O2@8iQ)cl@BG-xbbn*h0LZI&Wj9_%?x4SMe3hs1swyQ$b8r0h4-!)|7W-K3eQV;^G% zit|SnTP-8yznO-Ex(iSsBB7D_9zYII>a9Yh+7_ysVYWLWhLK`Gi`u`9I}OGe+Ti6Y zWkq6S>niYZdIHU4Ux)Af1SEer&cx+jfJ94bPhA<7keEZTOjkLPtWz(A*e%3tgFvOcAml2r9ukgQq?4*4?=)lJjW?YG-6{4G+^!p!H7;A}ptrTt2 zb72@c-g2DoDuXnzYvO1Tv$xxW_I(O;wNTPl(Y`Nujs#0%=Q2-)t0?-EJCq_x>M-)j zxt>CbrbLg7XDx}+&C7oVwz1w4kp6}V?*sdLxZK>$w)37Ys^HE=rTX_+?;c00f_5aEWZY$y=X z4~#E(eF1NN`sJ0(BrJT)EkS zkk28f&TEYE_`wL!Ov^jZ;mz>aSLc{1eUaSr*9`&G%A0>M4f+hUxan^yImt)8CA?8N z&st}QW98VTH*wevoPsf{)N0s#Z#7M;QYp0GZ(njyH%qO@6fuwYmp8ow&2oS{ZdF_V zF!pEB|Ct^JY@0t^DyUvH9>8h>UcCs4um`?7A`wQ*66jC^hF1bVX^%)}_9b;pSZ&B@ zX3ST1C$N7;3zUC)i#mLPGoFWWWbCH$?J4WsEI{)@0C1?{C^=vohQe&%_@Y_dXnwL9 zWwS6vXiH`%_m5*1s*elTC7!oceC9n|ua#?!QoU5<`T;p!TabHXn%QJ5=Q40L!$~@? zk&kaxv>Apb8jFHS7l*n~D~iTk2xgVky4Xk|lFNTxO@@N*g-cC+=Le1!S`@Q{$W&}* z0%k9)Z5|t0oH4X8F~ zy>4KS?3e>Ke&lOna3MkhLseXndbIfRGJwqt!TEv(6`fn5kv9QQ*L+3LY=T|8r)egD z=$C)LyBCs11AKA7-%$0IsiJLrC{t9*BXB?;kzxlDO#L8?$T|dj0lsv-ll=2ASZi4C zLZO310W+|`VUyUKATML@Dx{hjRF90phH(q-100RKX+kLL|3V|MO3|jC?Ju(#Xx|unq`~3ndNd2g z$3-Am36nea?J(pjY=}|xd>A`b$52XYAre{m)3AFtOnfetV2~3mWeT@J7k*YHtg3I$ zOkOGgROqUiEj1%chn3Z+)ZT?F838W1Yd$AjOMP6L+BZ&FmNc2@EHin|EnMBX`>cQ5 zop9CEtI9I{iWT*7gjHr??9NhaN_tP6defsUb(_2~9lK&&!~hFP?IY>J%+Gov;G9oA z)R@<#C&#hlzo08i%!xpJwXbLpQ9PM~gIdgjo%2w|k#Q%LKwaCT#1`;7rcvA&o@YH8 zqU(wufM(F0KYX(C9S%2%c`+1rpH+X8=-p-eVSSJ2(%P7``58Z^yw-C2A~n|&YyIL& z7p(QtnN_OQKcb=SbE|4qo3#==#q%bTaM?Y2>Rb*c?0h4-~7*H&+(dyD2|(dU0qqe$Uu zynVtb44Cmepyg{SSl1r;@Qw%d#&n|D39{`21LgvB@V!Z`%v*TPkfQ^)hohKL^j

          !O$?c35#SBWNmSxr()z-dsg!n8p&h0>r%T}XW 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 delta 19186 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 MiYot}sIRX70_`O;7XSbN From f6418360e905b934663e726978bcf5808fe33c75 Mon Sep 17 00:00:00 2001 From: labkey-ryans Date: Thu, 3 Mar 2016 15:22:37 -0800 Subject: [PATCH 194/587] updates to tests to more closely match new architecture --- .../test/pages/trialshare/DataFinderPage.java | 89 ++++++++++--------- .../PublicationsQueryUpdatePage.java | 5 +- .../StudyPropertiesQueryUpdatePage.java | 6 +- .../trialshare/TrialShareDataFinderTest.java | 32 ++++--- 4 files changed, 73 insertions(+), 59 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java index 130e0106..0bde528c 100644 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java @@ -19,7 +19,6 @@ import org.junit.Assert; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; -import org.labkey.test.WebTestHelper; import org.labkey.test.components.Component; import org.labkey.test.components.trialshare.PublicationPanel; import org.labkey.test.components.trialshare.StudySummaryWindow; @@ -63,32 +62,38 @@ public DataFinderPage(BaseWebDriverTest test, boolean testingStudies) @Override protected void waitForPage() { - _test.waitForElement(LabKeyPage.Locators.pageSignal(COUNT_SIGNAL)); + waitForElement(LabKeyPage.Locators.pageSignal(COUNT_SIGNAL)); } protected void waitForGroupUpdate() { - _test.waitForElement(LabKeyPage.Locators.pageSignal(GROUP_UPDATED_SIGNAL)); + waitForElement(LabKeyPage.Locators.pageSignal(GROUP_UPDATED_SIGNAL)); } - public static DataFinderPage goDirectlyToPage(BaseWebDriverTest test, String containerPath, boolean testingStudies) - { - test.doAndWaitForPageSignal(() -> test.beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), COUNT_SIGNAL); - return new DataFinderPage(test, testingStudies); - } +// public static DataFinderPage goDirectlyToPage(BaseWebDriverTest test, String containerPath, boolean testingStudies) +// { +// test.doAndWaitForPageSignal(() -> test.beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), COUNT_SIGNAL); +// return new DataFinderPage(test, testingStudies); +// } +// +// public static DataFinderPage goDirectlyToPage(WebDriver test, String containerPath, boolean testingStudies) +// { +// doAndWaitForPageSignal(() -> beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), COUNT_SIGNAL); +// return new DataFinderPage(test, testingStudies); +// } public boolean hasStudySubsetCombo() { - return _test.isElementPresent(Locators.studySubsetCombo); + return isElementPresent(Locators.studySubsetCombo); } public void selectStudySubset(String text) { // FIXME isn't there a general method for asking "is this combo list item already selected"? _ext4Helper.openComboList(DataFinderPage.Locators.studySubsetCombo); - if (!_test.isElementPresent(Ext4Helper.Locators.comboListItemSelected().withText(text))) + if (!isElementPresent(Ext4Helper.Locators.comboListItemSelected().withText(text))) { - _test.doAndWaitForPageSignal(() ->_ext4Helper.selectItemFromOpenComboList(text, Ext4Helper.TextMatchTechnique.EXACT), COUNT_SIGNAL); + doAndWaitForPageSignal(() ->_ext4Helper.selectItemFromOpenComboList(text, Ext4Helper.TextMatchTechnique.EXACT), COUNT_SIGNAL); } else // FIXME you should be able to just close the combo box at this point, but the close method assumes you've chosen something from teh lis @@ -100,41 +105,41 @@ public void selectStudySubset(String text) @LogMethod public void studySearch(@LoggedParam final String search) { - _test.doAndWaitForPageSignal(() -> _test.setFormElement(DataFinderPage.Locators.studySearchInput, search), COUNT_SIGNAL); + doAndWaitForPageSignal(() -> setFormElement(DataFinderPage.Locators.studySearchInput, search), COUNT_SIGNAL); } @LogMethod(quiet = true) public void clearSearch() { - if (_test.isElementPresent(DataFinderPage.Locators.studySearchInput) && !_test.getFormElement(DataFinderPage.Locators.studySearchInput).isEmpty()) + if (isElementPresent(DataFinderPage.Locators.studySearchInput) && !getFormElement(DataFinderPage.Locators.studySearchInput).isEmpty()) studySearch(" "); } public void saveGroup(String name) { - _test.setFormElement(DataFinderPage.Locators.groupLabelInput, name); - _test.clickButtonContainingText("Save", BaseWebDriverTest.WAIT_FOR_EXT_MASK_TO_DISSAPEAR); + setFormElement(DataFinderPage.Locators.groupLabelInput, name); + clickButtonContainingText("Save", BaseWebDriverTest.WAIT_FOR_EXT_MASK_TO_DISSAPEAR); waitForGroupUpdate(); } public String getGroupLabel() { - return DataFinderPage.Locators.groupLabel.findElement(_test.getDriver()).getText().trim(); + return DataFinderPage.Locators.groupLabel.findElement(getDriver()).getText().trim(); } public GroupMenu getMenu(Locator locator) { - return new GroupMenu(locator.findElement(_test.getDriver())); + return new GroupMenu(locator.findElement(getDriver())); } public boolean menuIsDisabled(Locator.CssLocator locator) { - return _test.isElementPresent(locator.append(" .labkey-disabled-text-link")); + return isElementPresent(locator.append(" .labkey-disabled-text-link")); } public void openMenu(Locator locator) { - locator.findElement(_test.getDriver()).click(); + locator.findElement(getDriver()).click(); } public void navigateToStudies() @@ -158,9 +163,9 @@ public Map getSummaryCounts() { WebElement summaryElement; if (testingStudies) - summaryElement = DataFinderPage.Locators.studySummaryArea.findElement(_test.getDriver()); + summaryElement = DataFinderPage.Locators.studySummaryArea.findElement(getDriver()); else - summaryElement = DataFinderPage.Locators.pubSummaryArea.findElement(_test.getDriver()); + summaryElement = DataFinderPage.Locators.pubSummaryArea.findElement(getDriver()); SummaryPanel summary = new SummaryPanel(summaryElement); @@ -182,10 +187,10 @@ public List getDataCards() Locator locator = testingStudies? Locators.studyCard : Locators.pubCard; - if (_test.isElementPresent(locator)) + if (isElementPresent(locator)) { - _test.scrollIntoView(locator); - cardEls = locator.findElements(_test.getDriver()); + scrollIntoView(locator); + cardEls = locator.findElements(getDriver()); for (WebElement el : cardEls) { cards.add(new DataCard(el)); @@ -198,37 +203,37 @@ public List getDataCards() public FacetGrid getFacetsGrid() { if(testingStudies) - return new FacetGrid(DataFinderPage.Locators.studyFacetPanel.findElement(_test.getDriver())); + return new FacetGrid(DataFinderPage.Locators.studyFacetPanel.findElement(getDriver())); else - return new FacetGrid(DataFinderPage.Locators.pubFacetPanel.findElement(_test.getDriver())); + return new FacetGrid(DataFinderPage.Locators.pubFacetPanel.findElement(getDriver())); } public void clearAllFilters() { Locator.CssLocator clearAllLocator = DataFinderPage.Locators.getClearAll(finderLocator); - _test.scrollIntoView(clearAllLocator); + scrollIntoView(clearAllLocator); Locator activeClearAllLocator = DataFinderPage.Locators.getActiveClearAll(clearAllLocator); - if (_test.isElementPresent(activeClearAllLocator)) + if (isElementPresent(activeClearAllLocator)) { - final WebElement clearAll = activeClearAllLocator.findElement(_test.getDriver()); + final WebElement clearAll = activeClearAllLocator.findElement(getDriver()); if (clearAll.isDisplayed()) { - _test.doAndWaitForPageSignal(clearAll::click, COUNT_SIGNAL); + doAndWaitForPageSignal(clearAll::click, COUNT_SIGNAL); } } } public void dismissTour() { - _test.shortWait().until(new Predicate() + shortWait().until(new Predicate() { @Override public boolean apply(WebDriver webDriver) { try { - return (Boolean) _test.executeScript("" + + return (Boolean) executeScript("" + "if (window.hopscotch)" + " return !hopscotch.endTour().isActive;" + "else" + @@ -384,7 +389,7 @@ public List getInactiveOptions() public void chooseOption(String optionText, boolean waitForUpdate) { - _test.log("Choosing menu option " + optionText); + log("Choosing menu option " + optionText); List activeOptions = findElements(elements.activeOption); for (WebElement option : activeOptions) { @@ -436,24 +441,24 @@ public WebElement getComponentElement() public boolean facetIsPresent(Dimension dimension) { - return _test.isElementPresent(locators.facetGroup(dimension)); + return isElementPresent(locators.facetGroup(dimension)); } public void toggleFacet(Dimension dimension, String name) { Locator.XPathLocator rowLocator = locators.facetMemberName(dimension, name); scrollIntoView(rowLocator); - WebElement row = rowLocator.findElement(_test.getDriver()); + WebElement row = rowLocator.findElement(getDriver()); - _test.doAndWaitForPageSignal(() -> row.click(), COUNT_SIGNAL); + doAndWaitForPageSignal(() -> row.click(), COUNT_SIGNAL); } public void clearFilter(Dimension dimension) { - WebElement clearEl = locators.facetClear(dimension).findElement(_test.getDriver()); + WebElement clearEl = locators.facetClear(dimension).findElement(getDriver()); scrollIntoView(clearEl); Assert.assertFalse("Attempting to clear filter that is not active", clearEl.getAttribute("class").contains("inactive")); - _test.clickAt(locators.facetClear(dimension), 3, 3, 0); + clickAt(locators.facetClear(dimension), 3, 3, 0); waitForElement(Locator.xpath("//tr[contains(@data-recordid,'" + dimension.getHierarchyName() + "')]//span[contains(@class,'labkey-clear-filter')][contains(@class, 'inactive')]")); } @@ -472,7 +477,7 @@ public Map> getSelectedMembers() public List getSelectedMembers(Dimension dimension) { - List selectedElements = locators.facetMemberSelected(dimension).findElements(_test.getDriver()); + List selectedElements = locators.facetMemberSelected(dimension).findElements(getDriver()); List selectedNames = new ArrayList<>(); for (WebElement element: selectedElements) { @@ -483,7 +488,7 @@ public List getSelectedMembers(Dimension dimension) public Map getMemberCounts(Dimension dimension) { - List memberElements = locators.facetMembers(dimension).findElements(_test.getDriver()); + List memberElements = locators.facetMembers(dimension).findElements(getDriver()); Map countMap = new HashMap<>(); for (WebElement member : memberElements) { @@ -564,7 +569,7 @@ public WebElement getComponentElement() public List getValues() { - return _test.getTexts(findElements(elements.member)); + return getTexts(findElements(elements.member)); } private class Elements @@ -633,7 +638,7 @@ public String getTitle() public PublicationPanel viewDetail() { - _test.doAndWaitForPageSignal(() -> locators.pubMoreDetails.findElement(card).click(), PUBLICATION_DETAILS_SIGNAL); + doAndWaitForPageSignal(() -> locators.pubMoreDetails.findElement(card).click(), PUBLICATION_DETAILS_SIGNAL); return new PublicationPanel(_test); } diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java b/test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java index e4dd0cf7..9707e249 100644 --- a/test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java +++ b/test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java @@ -17,9 +17,8 @@ public PublicationsQueryUpdatePage(BaseWebDriverTest test) public void setPermissionsContainer(String publicStudyName, String operationalStudyName) { - _test.log("Setting up permissions container"); - _test.goToProjectHome(); - String projectName = _test.getCurrentProject(); + log("Setting up permissions container"); + String projectName = getCurrentProject(); clickAndWait(Locator.linkWithText("ManuscriptsAndAbstracts")); DataRegionTable table = new DataRegionTable("query", _test); int rowIndex = table.getRow("Status", "In Progress"); diff --git a/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java b/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java index 5be340a9..423f297f 100644 --- a/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java +++ b/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java @@ -19,9 +19,8 @@ public StudyPropertiesQueryUpdatePage(BaseWebDriverTest test) public void setStudyContainers(Set loadedStudies, String publicStudyName, String operationalStudyName) { - _test.log("Setting up study container links"); - _test.goToProjectHome(); - String projectName = _test.getCurrentProject(); + log("Setting up study container links"); + String projectName = getCurrentProject(); clickAndWait(Locator.linkWithText("StudyAccess")); DataRegionTable table = new DataRegionTable("query", _test); for (int i = 0; i < table.getDataRowCount(); i++) @@ -43,7 +42,6 @@ else if (isPublic) private void unlinkStudy(String studyShortName) { - _test.goToProjectHome(); clickAndWait(Locator.linkWithText("StudyProperties")); DataRegionTable table = new DataRegionTable("query", _test); int row = table.getRow("Short Name", studyShortName); diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 0f92b344..8eff68fa 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -45,6 +45,7 @@ import org.labkey.test.util.LogMethod; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.ReadOnlyTest; +import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; @@ -77,6 +78,9 @@ public class TrialShareDataFinderTest extends BaseWebDriverTest implements ReadO private static final String CASALE_READER = CASALE_READER_DISPLAY_NAME + EMAIL_EXTENSION; private static final String WISPR_READER_DISPLAY_NAME = "wispr_reader"; private static final String WISPR_READER = WISPR_READER_DISPLAY_NAME + EMAIL_EXTENSION; + private static final String CONTROLLER = "trialshare"; + private static final String ACTION = "dataFinder"; + public static final String COUNT_SIGNAL = "dataFinderCountsUpdated"; private static File listArchive = TestFileUtils.getSampleData("DataFinder.lists.zip"); private static final String RELOCATED_DATA_FINDER_PROJECT = "RelocatedDataFinder"; @@ -171,8 +175,10 @@ private void setUpProject() } createStudy(PUBLIC_STUDY_NAME); createStudy(OPERATIONAL_STUDY_NAME); + goToProjectHome(); StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); queryUpdatePage.setStudyContainers(loadedStudies, "/" + getProjectName() + "/" + PUBLIC_STUDY_NAME, "/" + getProjectName() + "/" + OPERATIONAL_STUDY_NAME); + goToProjectHome(); PublicationsQueryUpdatePage pubUpdatePage = new PublicationsQueryUpdatePage(this); pubUpdatePage.setPermissionsContainer("/" + getProjectName() + "/" + PUBLIC_STUDY_NAME, "/" + getProjectName() + "/" + OPERATIONAL_STUDY_NAME); @@ -265,7 +271,7 @@ public void testCounts() @Test public void testStudyCards() { - DataFinderPage finder = DataFinderPage.goDirectlyToPage(this, getProjectName(), true); + DataFinderPage finder = goDirectlyToDataFinderPage(getDriver(), getProjectName(), true); List studyCards = finder.getDataCards(); @@ -275,7 +281,7 @@ public void testStudyCards() @Test public void testStudySubset() { - DataFinderPage finder = DataFinderPage.goDirectlyToPage(this, getProjectName(), true); + DataFinderPage finder = goDirectlyToDataFinderPage(getDriver(), getProjectName(), true); DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); Set linkedStudyNames = new HashSet<>(); @@ -337,7 +343,7 @@ public void testDataFinderRelocation() { log("Test that we can put the data finder in a project other than the one with the cube definition and lists"); AbstractContainerHelper containerHelper = new APIContainerHelper(this); - + containerHelper.deleteProject(RELOCATED_DATA_FINDER_PROJECT, false); containerHelper.createProject(RELOCATED_DATA_FINDER_PROJECT, "Custom"); containerHelper.addCreatedProject(RELOCATED_DATA_FINDER_PROJECT); containerHelper.enableModule(MODULE_NAME); @@ -391,7 +397,7 @@ public void testSelectingEmptyMeasure() expectedCounts.put(DataFinderPage.Dimension.STUDIES, 0); expectedCounts.put(DataFinderPage.Dimension.SUBJECTS, 0); - DataFinderPage finder = DataFinderPage.goDirectlyToPage(this, getProjectName(), true); + DataFinderPage finder = goDirectlyToDataFinderPage(getDriver(), getProjectName(), true); DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"); facets.toggleFacet(DataFinderPage.Dimension.ASSAY, "Elispot"); @@ -420,7 +426,7 @@ public void testSelectingEmptyMeasure() @Ignore("Search not implemented") public void testSearch() { - DataFinderPage finder = DataFinderPage.goDirectlyToPage(this, getProjectName(), true); + DataFinderPage finder = goDirectlyToDataFinderPage(getDriver(), getProjectName(), true); DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"); @@ -441,7 +447,7 @@ public void testSearch() @Test public void testStudySummaryWindow() { - DataFinderPage finder = DataFinderPage.goDirectlyToPage(this, getProjectName(), true); + DataFinderPage finder = goDirectlyToDataFinderPage(getDriver(), getProjectName(), true); DataFinderPage.DataCard studyCard = finder.getDataCards().get(0); @@ -596,7 +602,7 @@ public void testFinderWebPartAndActionShareFilter() facetGrid.toggleFacet(DataFinderPage.Dimension.THERAPEUTIC_AREA, "Allergy"); Map> selections = finder.getFacetsGrid().getSelectedMembers(); - DataFinderPage.goDirectlyToPage(this, getProjectName(), true); + goDirectlyToDataFinderPage(getDriver(), getProjectName(), true); assertEquals("WebPart study finder filter didn't get applied", selections, finder.getFacetsGrid().getSelectedMembers()); } @@ -717,7 +723,7 @@ public void testFilterOnStatus() String cardText; Map counts; - DataFinderPage finder = DataFinderPage.goDirectlyToPage(this, getProjectName(), false); + DataFinderPage finder = goDirectlyToDataFinderPage(getDriver(), getProjectName(), false); log("Go to publications and clear any filters that may have been set."); finder.navigateToPublications(); @@ -760,7 +766,7 @@ public void testFilterOnStatus() public void testPublicationStatusForReader() { impersonate(PUBLIC_READER); - DataFinderPage finder = DataFinderPage.goDirectlyToPage(this, getProjectName(), false); + DataFinderPage finder = goDirectlyToDataFinderPage(getDriver(), getProjectName(), false); log("Go to publications and clear any filters that may have been set."); finder.navigateToPublications(); finder.clearAllFilters(); @@ -779,7 +785,7 @@ public void testPublicationDetail() DataFinderPage.FacetGrid fg; Map summaryCount; - DataFinderPage finder = DataFinderPage.goDirectlyToPage(this, getProjectName(), false); + DataFinderPage finder = goDirectlyToDataFinderPage(getDriver(), getProjectName(), false); log("Go to publications and clear any filters that may have been set."); finder.navigateToPublications(); @@ -855,4 +861,10 @@ private void assertCountsSynced(DataFinderPage finder) assertEquals("Study count mismatch", studyCards.size(), studyCounts.get(DataFinderPage.Dimension.STUDIES).intValue()); } + + private DataFinderPage goDirectlyToDataFinderPage(WebDriver test, String containerPath, boolean testingStudies) + { + doAndWaitForPageSignal(() -> beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), COUNT_SIGNAL); + return new DataFinderPage(this, testingStudies); + } } \ No newline at end of file From 12f6946d845a84980a2573b6c7db2855ad2fe788 Mon Sep 17 00:00:00 2001 From: labkey-adam Date: Sun, 6 Mar 2016 10:33:58 -0800 Subject: [PATCH 195/587] Module doesn't have scripts --- src/org/labkey/trialshare/TrialShareModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/TrialShareModule.java b/src/org/labkey/trialshare/TrialShareModule.java index eae473dc..b28e4512 100644 --- a/src/org/labkey/trialshare/TrialShareModule.java +++ b/src/org/labkey/trialshare/TrialShareModule.java @@ -54,7 +54,7 @@ public double getVersion() @Override public boolean hasScripts() { - return true; + return false; } public TrialShareModule() From 60d5edc50381d5f68c105dce40a016de2867d116 Mon Sep 17 00:00:00 2001 From: labkey-adam Date: Thu, 10 Mar 2016 15:52:19 -0800 Subject: [PATCH 196/587] Update ITN distribution to community edition + timeline + trialShare, per new contract --- distributions/itn.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distributions/itn.xml b/distributions/itn.xml index aa084f3a..8c02cae0 100644 --- a/distributions/itn.xml +++ b/distributions/itn.xml @@ -13,10 +13,10 @@ - + + - From 40ad3adb32f3e3cba231d8ab8777d20ebca8df74 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 17 Mar 2016 13:01:33 -0700 Subject: [PATCH 197/587] Spec 25616: Implement document providers for studies and publications --- .../trialshare/TrialShareController.java | 19 ++- .../TrialShareDocumentResolver.java | 62 +++++++ .../labkey/trialshare/TrialShareModule.java | 15 ++ ...TrialSharePublicationDocumentProvider.java | 156 ++++++++++++++++++ .../TrialShareStudyDocumentProvider.java | 148 +++++++++++++++++ .../query/TrialShareQuerySchema.java | 91 +++++++++- src/org/labkey/trialshare/view/cubeAdmin.jsp | 49 +++++- 7 files changed, 534 insertions(+), 6 deletions(-) create mode 100644 src/org/labkey/trialshare/TrialShareDocumentResolver.java create mode 100644 src/org/labkey/trialshare/TrialSharePublicationDocumentProvider.java create mode 100644 src/org/labkey/trialshare/TrialShareStudyDocumentProvider.java diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 440b7668..98432861 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -15,6 +15,7 @@ */ package org.labkey.trialshare; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.Nullable; import org.labkey.api.action.ApiAction; import org.labkey.api.action.FormViewAction; @@ -30,7 +31,6 @@ import org.labkey.api.data.SimpleFilter; import org.labkey.api.data.TableInfo; import org.labkey.api.data.TableSelector; -import org.labkey.api.gwt.client.util.StringUtils; import org.labkey.api.module.Module; import org.labkey.api.module.ModuleLoader; import org.labkey.api.query.FieldKey; @@ -486,6 +486,18 @@ public void setCubeContainerPath(String cubeContainerPath) } } + @RequiresPermission(AdminPermission.class) + public class ReindexAction extends ApiAction + { + @Override + public Object execute(Object o, BindException errors) throws Exception + { + TrialShareStudyDocumentProvider.reindex(); + TrialSharePublicationDocumentProvider.reindex(); + return success(); + } + } + @RequiresPermission(ReadPermission.class) public class DataFinderAction extends SimpleViewAction { @@ -509,6 +521,7 @@ public class StudiesAction extends ApiAction @Override public Object execute(Object form, BindException errors) throws Exception { + // TODO update to not use static methods QuerySchema listsSchema = TrialShareQuerySchema.getSchema(getUser(), getContainer()); TableInfo studyProperties = listsSchema.getTable(TrialShareQuerySchema.STUDY_TABLE); @@ -611,7 +624,7 @@ public class PublicationsAction extends ApiAction @Override public Object execute(Object o, BindException errors) throws Exception { - TableInfo publicationsList = TrialShareQuerySchema.getSchema(getUser(), getContainer()).getTable(TrialShareQuerySchema.PUBLICATION_TABLE); + TableInfo publicationsList = TrialShareQuerySchema.getPublicationsTableInfo(getUser(), getContainer()); if (publicationsList != null) { List publications = (new TableSelector(publicationsList).getArrayList(StudyPublicationBean.class)); @@ -885,7 +898,7 @@ public ModelAndView getView(PublicationIdForm form, BindException errors) throws TableInfo publicationsList = listSchema.getTable(TrialShareQuerySchema.PUBLICATION_TABLE); if (publicationsList != null) { - StudyPublicationBean publication = (new TableSelector(listSchema.getTable(TrialShareQuerySchema.PUBLICATION_TABLE))).getObject(_id, StudyPublicationBean.class); + StudyPublicationBean publication = (new TableSelector(publicationsList)).getObject(_id, StudyPublicationBean.class); SimpleFilter filter = new SimpleFilter(); filter.addCondition(FieldKey.fromParts("key"), _id); diff --git a/src/org/labkey/trialshare/TrialShareDocumentResolver.java b/src/org/labkey/trialshare/TrialShareDocumentResolver.java new file mode 100644 index 00000000..0f3d6104 --- /dev/null +++ b/src/org/labkey/trialshare/TrialShareDocumentResolver.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.labkey.trialshare; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.labkey.api.search.SearchService; +import org.labkey.api.security.User; +import org.labkey.api.view.HttpView; +import org.labkey.api.webdav.WebdavResource; + +/* + * Resolve resources starting with "trialshare", eg. "trialshare:study:ITN007AI" + */ +public class TrialShareDocumentResolver implements SearchService.ResourceResolver +{ + @Override + public WebdavResource resolve(@NotNull String resourceIdentifier) + { + String[] parts = StringUtils.split(resourceIdentifier,":"); + if (parts.length != 2) + return null; + String type = parts[0]; + String id = parts[1]; + switch (type) + { + case "study": + return createStudyResource(resourceIdentifier, id); + case "publication": + return createPublicationResource(parts[2]); + } + return null; + } + + public HttpView getCustomSearchResult(User user, @NotNull String resourceIdentifier) + { + return null; + } + + public static WebdavResource createStudyResource(String id, String study_accession) + { + return null; + } + + public WebdavResource createPublicationResource(String publicationId) + { + return null; + } +} diff --git a/src/org/labkey/trialshare/TrialShareModule.java b/src/org/labkey/trialshare/TrialShareModule.java index b28e4512..5fe0aa00 100644 --- a/src/org/labkey/trialshare/TrialShareModule.java +++ b/src/org/labkey/trialshare/TrialShareModule.java @@ -25,6 +25,8 @@ import org.labkey.api.module.ModuleContext; import org.labkey.api.module.ModuleProperty; import org.labkey.api.settings.AdminConsole; +import org.labkey.api.search.SearchService; +import org.labkey.api.services.ServiceRegistry; import org.labkey.api.view.SimpleWebPartFactory; import org.labkey.api.view.WebPartFactory; import org.labkey.trialshare.view.DataFinderWebPart; @@ -39,6 +41,10 @@ public class TrialShareModule extends DefaultModule public static final String NAME = "TrialShare"; private final ModuleProperty _cubeContainer; + public static final SearchService.SearchCategory searchCategoryStudy = new SearchService.SearchCategory("trialshare_study", "TrialShare Study", false); + public static final SearchService.SearchCategory searchCategoryPublication = new SearchService.SearchCategory("trialshare_publication", "TrialShare Publication", false); + + @Override public String getName() { @@ -87,6 +93,15 @@ public void doStartup(ModuleContext moduleContext) FolderTypeManager.get().registerFolderType(this, new StudyITNFolderType(this)); AdminConsole.addLink(AdminConsole.SettingsLinkType.Management, "Data Cube", TrialShareController.getCubeAdminURL()); + SearchService ss = ServiceRegistry.get().getService(SearchService.class); + if (null != ss) + { + ss.addDocumentProvider(new TrialShareStudyDocumentProvider()); + ss.addDocumentProvider(new TrialSharePublicationDocumentProvider()); +// ss.addResourceResolver("trialshare", new TrialShareDocumentResolver()); + ss.addSearchCategory(searchCategoryStudy); + ss.addSearchCategory(searchCategoryPublication); + } } @Override diff --git a/src/org/labkey/trialshare/TrialSharePublicationDocumentProvider.java b/src/org/labkey/trialshare/TrialSharePublicationDocumentProvider.java new file mode 100644 index 00000000..29439562 --- /dev/null +++ b/src/org/labkey/trialshare/TrialSharePublicationDocumentProvider.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.labkey.trialshare; + +import org.jetbrains.annotations.NotNull; +import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerManager; +import org.labkey.api.data.SqlExecutor; +import org.labkey.api.data.TableInfo; +import org.labkey.api.module.Module; +import org.labkey.api.module.ModuleLoader; +import org.labkey.api.query.QuerySchema; +import org.labkey.api.query.QueryService; +import org.labkey.api.search.SearchService; +import org.labkey.api.security.User; +import org.labkey.api.services.ServiceRegistry; +import org.labkey.api.util.Path; +import org.labkey.api.util.StringUtilsLabKey; +import org.labkey.api.view.ActionURL; +import org.labkey.api.webdav.SimpleDocumentResource; +import org.labkey.trialshare.query.TrialShareQuerySchema; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class TrialSharePublicationDocumentProvider implements SearchService.DocumentProvider +{ + private static final Logger _logger = LoggerFactory.getLogger(TrialSharePublicationDocumentProvider.class); + + public static Container getDocumentContainer() + { + Module trialShareModule = ModuleLoader.getInstance().getModule(TrialShareModule.NAME); + return ((TrialShareModule) trialShareModule).getCubeContainer(null); + } + + public static void reindex() + { + TrialSharePublicationDocumentProvider dp = new TrialSharePublicationDocumentProvider(); + SearchService ss = ServiceRegistry.get(SearchService.class); + dp.enumerateDocuments(ss.defaultTask(), getDocumentContainer(), null); + } + + + @Override + public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container c, Date since) + { + + QuerySchema listSchema = TrialShareQuerySchema.getSchema(User.getSearchUser(), c); + + String sql = + "SELECT pub.Key as PublicationId," + + "pub.Title, " + + "pub.Author, " + + "pub.DOI, " + + "pub.PMID, " + + "pub.PMCID, " + + "pub.PublicationType, " + + "pub.Year, " + + "pub.Journal, " + + "pub.Status, " + + "pub.Study as PrimaryStudy, " + + "pub.StudyId as PrimaryStudyId, " + + "pub.AbstractText, " + + "pub.Keywords, " + + "pub.PermissionsContainer, " + + "pub.ManuscriptContainer, " + + "pa.Assay, " + + "pc.Condition, " + + "ps.ShortName as StudyShortName, " + + "ps.StudyId, " + + "pta.TherapeuticArea " + + "FROM " + + "ManuscriptsAndAbstracts pub LEFT JOIN PublicationAssay pa ON pub.Key = pa.PublicationId " + + "LEFT JOIN PublicationCondition pc on pa.Key = pc.PublicationId " + + "LEFT JOIN PublicationStudy ps on pa.Key = ps.PublicationId " + + "LEFT JOIN PublicationTherapeuticArea pta on pa.Key = pta.PublicationId " + + "WHERE pub.Show = true"; + + try (ResultSet results = QueryService.get().select(listSchema,sql)) + { + while (results.next()) + { + + StringBuilder body = new StringBuilder(); + + for (String field : new String[]{"AbstractText", "DOI", "PMID", "PMCID", "PublicationType", "Journal", "Year", "Status", "PrimaryStudy", "PrimaryStudyId", "Keywords", "Assay", "Condition", "StudyId", "StudyShortName", "TherapeuticArea"}) + { + if (results.getString(field) != null) + body.append(results.getString(field)).append("\n"); + } + Map properties = new HashMap<>(); + + properties.put(SearchService.PROPERTY.identifiersMed.toString(), results.getString("Author")); + properties.put(SearchService.PROPERTY.keywordsMed.toString(), results.getString("Title")); + properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); + properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryPublication.getName()); + + String containerId; + if (results.getString("PermissionsContainer") != null) + containerId = results.getString("PermissionsContainer"); + else if (results.getString("ManuscriptContainer") != null) + containerId = results.getString("ManuscriptContainer"); + else + containerId = ContainerManager.getHomeContainer().getId(); + + ActionURL url = new ActionURL(TrialShareController.PublicationDetailViewAction.class, c).addParameter("id", results.getString("PublicationId")); + url.setExtraPath(containerId); + + SimpleDocumentResource resource = new SimpleDocumentResource ( + Path.parse("/" + results.getString("PublicationId")), + "trialShare:publication:" + results.getString("PublicationId"), + containerId, + "text/html", + body.toString().getBytes(StringUtilsLabKey.DEFAULT_CHARSET), + url, + properties + ); + task.addResource(resource, SearchService.PRIORITY.item); + } + } + catch (SQLException e) + { + _logger.error("Problem executing query for study indexing", e); + } + } + + @Override + public void indexDeleted() throws SQLException + { + TrialShareQuerySchema querySchema = new TrialShareQuerySchema(User.getSearchUser(), null); + SqlExecutor executor = new SqlExecutor(querySchema.getSchema().getDbSchema()); + + for (TableInfo ti : querySchema.getPublicationTables()) + { + executor.execute("UPDATE " + ti.getFromSQL(ti.getName()) + " SET LastIndexed = NULL"); + } + } +} diff --git a/src/org/labkey/trialshare/TrialShareStudyDocumentProvider.java b/src/org/labkey/trialshare/TrialShareStudyDocumentProvider.java new file mode 100644 index 00000000..ceb95f06 --- /dev/null +++ b/src/org/labkey/trialshare/TrialShareStudyDocumentProvider.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.labkey.trialshare; + +import org.jetbrains.annotations.NotNull; +import org.labkey.api.data.Container; +import org.labkey.api.data.SqlExecutor; +import org.labkey.api.data.TableInfo; +import org.labkey.api.module.Module; +import org.labkey.api.module.ModuleLoader; +import org.labkey.api.query.QuerySchema; +import org.labkey.api.query.QueryService; +import org.labkey.api.search.SearchService; +import org.labkey.api.security.User; +import org.labkey.api.services.ServiceRegistry; +import org.labkey.api.util.Path; +import org.labkey.api.util.StringUtilsLabKey; +import org.labkey.api.view.ActionURL; +import org.labkey.api.webdav.SimpleDocumentResource; +import org.labkey.trialshare.query.TrialShareQuerySchema; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class TrialShareStudyDocumentProvider implements SearchService.DocumentProvider +{ + private static final Logger _logger = LoggerFactory.getLogger(TrialShareStudyDocumentProvider.class); + public static Container getDocumentContainer() + { + Module trialShareModule = ModuleLoader.getInstance().getModule(TrialShareModule.NAME); + return ((TrialShareModule) trialShareModule).getCubeContainer(null); + } + + public static void reindex() + { + TrialShareStudyDocumentProvider dp = new TrialShareStudyDocumentProvider(); + SearchService ss = ServiceRegistry.get(SearchService.class); + dp.enumerateDocuments(ss.defaultTask(), getDocumentContainer(), null); + } + + + @Override + public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container c, Date since) + { + + QuerySchema listSchema = TrialShareQuerySchema.getSchema(User.getSearchUser(), c); + + String sql = + "SELECT sa.StudyId," + + "sa.StudyContainer, " + + "sag.AgeGroup, " + + "sas.Assay, " + + "sc.Condition, " + + "sph.Phase, " + + "sta.TherapeuticArea, " + + "sp.shortName, " + + "sp.Title, " + + "sp.StudyType, " + + "sp.Description, " + + "sp.Investigator " + + "FROM " + + "StudyAccess sa LEFT JOIN StudyProperties sp ON sa.StudyId = sp.StudyId " + + "LEFT JOIN StudyAgeGroup sag on sa.StudyId = sag.StudyId " + + "LEFT JOIN StudyAssay sas on sa.StudyId = sas.StudyId " + + "LEFT JOIN StudyCondition sc on sa.StudyId = sc.StudyId " + + "LEFT JOIN StudyPhase sph on sa.StudyId = sph.StudyId " + + "LEFT JOIN StudyTherapeuticArea sta on sa.StudyId = sta.StudyId"; + + try (ResultSet results = QueryService.get().select(listSchema,sql)) + { + while (results.next()) + { + + StringBuilder body = new StringBuilder(); + + for (String field : new String[]{"Description", "AgeGroup", "Assay", "Condition", "Phase", "TherapeuticArea", "StudyType"}) + { + if (results.getString(field) != null) + body.append(results.getString(field)).append("\n"); + } + + Map properties = new HashMap<>(); + + StringBuilder identifiers = new StringBuilder(); + for (String field : new String[]{"shortName", "StudyId", "Investigator"}) + { + if (results.getString(field) != null) + identifiers.append(results.getString(field)).append(" "); + } + + properties.put(SearchService.PROPERTY.identifiersMed.toString(), identifiers.toString()); + properties.put(SearchService.PROPERTY.keywordsMed.toString(), results.getString("Title")); + properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); + properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryStudy.getName()); + + String containerId = results.getString("StudyContainer") == null ? c.getId() : results.getString("StudyContainer"); + + ActionURL url = new ActionURL(TrialShareController.StudyDetailAction.class, c).addParameter("studyId", (String) results.getString("StudyId")).addParameter("detailType", "study"); + url.setExtraPath(containerId); + + SimpleDocumentResource resource = new SimpleDocumentResource ( + Path.parse("/" + results.getString("StudyId")), + "trialShare:study:" + results.getString("StudyId"), + containerId, + "text/html", + body.toString().getBytes(StringUtilsLabKey.DEFAULT_CHARSET), + url, + properties + ); + task.addResource(resource, SearchService.PRIORITY.item); + } + } + catch (SQLException e) + { + _logger.error("Problem executing query for study indexing", e); + } + } + + @Override + public void indexDeleted() throws SQLException + { + TrialShareQuerySchema querySchema = new TrialShareQuerySchema(User.getSearchUser(), null); + SqlExecutor executor = new SqlExecutor(querySchema.getSchema().getDbSchema()); + + for (TableInfo ti : querySchema.getStudyTables()) + { + executor.execute("UPDATE " + ti.getFromSQL(ti.getName()) + " SET LastIndexed = NULL"); + } + } +} diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index e241b4b1..2d606178 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -1,12 +1,16 @@ package org.labkey.trialshare.query; import org.labkey.api.data.Container; +import org.labkey.api.data.TableInfo; import org.labkey.api.module.ModuleLoader; import org.labkey.api.query.DefaultSchema; import org.labkey.api.query.QuerySchema; import org.labkey.api.security.User; import org.labkey.trialshare.TrialShareModule; +import java.util.HashSet; +import java.util.Set; + /** * Created by susanh on 2/23/16. */ @@ -14,8 +18,17 @@ public class TrialShareQuerySchema { public static final String STUDY_TABLE = "studyProperties"; public static final String STUDY_ACCESS_TABLE = "studyAccess"; - public static final String PUBLICATION_TABLE = "manuscriptsAndAbstracts"; public static final String STUDY_ASSAY_TABLE = "studyAssay"; + public static final String STUDY_CONDITION_TABLE = "studyCondition"; + public static final String STUDY_AGE_GROUP_TABLE = "studyAgeGroup"; + public static final String STUDY_PHASE_TABLE = "studyPhase"; + public static final String STUDY_THERAPEUTIC_AREA_TABLE = "studyTherapeuticArea"; + + public static final String PUBLICATION_TABLE = "manuscriptsAndAbstracts"; + public static final String PUBLICATION_ASSAY_TABLE = "publicationAssay"; + public static final String PUBLICATION_CONDITION_TABLE = "publicationCondition"; + public static final String PUBLICATION_STUDY_TABLE = "publicationStudy"; + public static final String PUBLICATION_THERAPEUTIC_AREA_TABLE = "publicationTherapeuticArea"; // study visibility values public static final String OPERATIONAL_VISIBILITY = "Operational"; @@ -25,10 +38,86 @@ public class TrialShareQuerySchema public static final String IN_PROGRESS_STATUS = "In Progress"; public static final String COMPLETED_STATUS = "Complete"; + private QuerySchema _listsSchema = null; + + public TrialShareQuerySchema(User user, Container container) + { + setSchema(user, container); + } + + public QuerySchema getSchema() + { + return _listsSchema; + } + + public void setSchema(User user, Container container) + { + _listsSchema = getSchema(user, container); + } + public static QuerySchema getSchema(User user, Container container) { Container cubeContainer = ((TrialShareModule) ModuleLoader.getInstance().getModule(TrialShareModule.NAME)).getCubeContainer(container); QuerySchema coreSchema = DefaultSchema.get(user, cubeContainer).getSchema("core"); return coreSchema.getSchema("lists"); } + + public static TableInfo getPublicationsTableInfo(User user, Container container) + { + QuerySchema listSchema = TrialShareQuerySchema.getSchema(user, container); + return listSchema.getTable(PUBLICATION_TABLE); + } + + public TableInfo getStudyPropertiesTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.STUDY_TABLE); + } + + public TableInfo getStudyAccessTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.STUDY_ACCESS_TABLE); + } + + public TableInfo getStudyAssayTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.STUDY_ASSAY_TABLE); + } + + public TableInfo getStudyConditionTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.STUDY_CONDITION_TABLE); + } + + public TableInfo getStudyAgeGroupTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.STUDY_AGE_GROUP_TABLE); + } + + public TableInfo getStudyPhaseTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.STUDY_PHASE_TABLE); + } + + public TableInfo getStudyTherapeuticAreaTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.STUDY_THERAPEUTIC_AREA_TABLE); + } + + public TableInfo[] getStudyTables() { + return new TableInfo[] { +// getStudyPropertiesTableInfo(), +// getStudyAccessTableInfo(), +// getStudyAgeGroupTableInfo(), +// getStudyAssayTableInfo(), +// getStudyConditionTableInfo(), +// getStudyPhaseTableInfo(), +// getStudyTherapeuticAreaTableInfo() + }; + } + + public TableInfo[] getPublicationTables() { + return new TableInfo[] { +// TODO + }; + } } diff --git a/src/org/labkey/trialshare/view/cubeAdmin.jsp b/src/org/labkey/trialshare/view/cubeAdmin.jsp index e7459286..6081e57a 100644 --- a/src/org/labkey/trialshare/view/cubeAdmin.jsp +++ b/src/org/labkey/trialshare/view/cubeAdmin.jsp @@ -31,7 +31,8 @@

          Data cube definitions and contents are cached on the server for efficiency. When data are updated, you will need to -clear the cache in order for the new data to be presented to users. +clear the cache in order for the new data to be presented to users. Also, the search index for the data cube data is refreshed + periodically, but you may want to manually reindex if you are making changes to the cube data.

          Cube definitions are managed per container. The following listing shows the definitions currently registered. @@ -53,7 +54,7 @@ Cube definitions are managed per container. The following listing shows the def

          - + @@ -82,4 +83,48 @@ Cube definitions are managed per container. The following listing shows the def } }); } + + function confirmReindex(path) + { + Ext4.Msg.show({ + title: 'Confirm', + buttons: Ext4.MessageBox.OKCANCEL, + msg: 'Reindex the data in the cube ' + path + '?', + fn: function (btn) + { + if (btn == 'ok') + { + reindexCubeData(path); + } + } + }); + } + + function reindexCubeData(path) + { + Ext4.Ajax.request({ + url: LABKEY.ActionURL.buildURL("trialShare", "reindex.api", path), + success: function (response) + { + var o = Ext4.decode(response.responseText); + if (!o.success) + { + Ext4.Msg.show({ + title: 'Reindex', + buttons: Ext4.MessageBox.OK, + msg: 'Reindex of data failed. Please check the logs.' + }) + } + }, + failure: function () + { + Ext4.Msg.show({ + title: 'Reindex', + buttons: Ext4.MessageBox.OK, + msg: 'Reindex of data failed. Please check the logs.' + }) + } + }) + + } From 613d051a10753464db71e33b88a50bf60030148a Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 18 Mar 2016 08:53:35 -0700 Subject: [PATCH 198/587] Spec 25616 remove unneeded document resolver --- .../TrialShareDocumentResolver.java | 62 ------------------- .../labkey/trialshare/TrialShareModule.java | 1 - 2 files changed, 63 deletions(-) delete mode 100644 src/org/labkey/trialshare/TrialShareDocumentResolver.java diff --git a/src/org/labkey/trialshare/TrialShareDocumentResolver.java b/src/org/labkey/trialshare/TrialShareDocumentResolver.java deleted file mode 100644 index 0f3d6104..00000000 --- a/src/org/labkey/trialshare/TrialShareDocumentResolver.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2015 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.trialshare; - -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; -import org.labkey.api.search.SearchService; -import org.labkey.api.security.User; -import org.labkey.api.view.HttpView; -import org.labkey.api.webdav.WebdavResource; - -/* - * Resolve resources starting with "trialshare", eg. "trialshare:study:ITN007AI" - */ -public class TrialShareDocumentResolver implements SearchService.ResourceResolver -{ - @Override - public WebdavResource resolve(@NotNull String resourceIdentifier) - { - String[] parts = StringUtils.split(resourceIdentifier,":"); - if (parts.length != 2) - return null; - String type = parts[0]; - String id = parts[1]; - switch (type) - { - case "study": - return createStudyResource(resourceIdentifier, id); - case "publication": - return createPublicationResource(parts[2]); - } - return null; - } - - public HttpView getCustomSearchResult(User user, @NotNull String resourceIdentifier) - { - return null; - } - - public static WebdavResource createStudyResource(String id, String study_accession) - { - return null; - } - - public WebdavResource createPublicationResource(String publicationId) - { - return null; - } -} diff --git a/src/org/labkey/trialshare/TrialShareModule.java b/src/org/labkey/trialshare/TrialShareModule.java index 5fe0aa00..3474d1d3 100644 --- a/src/org/labkey/trialshare/TrialShareModule.java +++ b/src/org/labkey/trialshare/TrialShareModule.java @@ -98,7 +98,6 @@ public void doStartup(ModuleContext moduleContext) { ss.addDocumentProvider(new TrialShareStudyDocumentProvider()); ss.addDocumentProvider(new TrialSharePublicationDocumentProvider()); -// ss.addResourceResolver("trialshare", new TrialShareDocumentResolver()); ss.addSearchCategory(searchCategoryStudy); ss.addSearchCategory(searchCategoryPublication); } From 2f413ed490b8a45cbf532b2a623260bc61b38560 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 20 Mar 2016 11:18:16 -0700 Subject: [PATCH 199/587] Spec 25616 add search category and enable search box in finder header --- resources/web/study/Finder/data/CubeConfig.js | 3 ++- resources/web/study/Finder/panel/FinderCard.js | 5 +++-- .../study/Finder/panel/FinderCardPanelHeader.js | 5 ++--- resources/web/study/Finder/panel/Publications.js | 4 +++- resources/web/study/Finder/panel/Studies.js | 4 +++- .../labkey/trialshare/TrialShareController.java | 15 ++++++++++++++- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/resources/web/study/Finder/data/CubeConfig.js b/resources/web/study/Finder/data/CubeConfig.js index acbac353..f37e6479 100644 --- a/resources/web/study/Finder/data/CubeConfig.js +++ b/resources/web/study/Finder/data/CubeConfig.js @@ -16,6 +16,7 @@ Ext4.define('LABKEY.study.data.CubeConfig', { {name: 'filterByFacetUniqueName'}, {name: 'showParticipantFilters'}, {name: 'isDefault'}, - {name: 'subsetLevelName'} + {name: 'subsetLevelName'}, + {name: 'searchCategory'} ] }); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/FinderCard.js b/resources/web/study/Finder/panel/FinderCard.js index 53eb1742..6b7b27ef 100644 --- a/resources/web/study/Finder/panel/FinderCard.js +++ b/resources/web/study/Finder/panel/FinderCard.js @@ -74,6 +74,7 @@ Ext4.define('LABKEY.study.panel.FinderCard', { onSearchTermsChanged: function(searchTerms) { + console.log("Search terms changed to " + searchTerms); if (!searchTerms) { this.searchMessage = ""; @@ -81,8 +82,8 @@ Ext4.define('LABKEY.study.panel.FinderCard', { return; } - var url = LABKEY.ActionURL.buildURL("search", "json", "/home/", { - "category": "List", + var url = LABKEY.ActionURL.buildURL("search", "json", this.dataModuleName, { + "category": this.cubeConfig.searchCategory, "q": searchTerms }); Ext4.Ajax.request({ diff --git a/resources/web/study/Finder/panel/FinderCardPanelHeader.js b/resources/web/study/Finder/panel/FinderCardPanelHeader.js index 0a3e7fcb..83390f9a 100644 --- a/resources/web/study/Finder/panel/FinderCardPanelHeader.js +++ b/resources/web/study/Finder/panel/FinderCardPanelHeader.js @@ -66,7 +66,7 @@ Ext4.define("LABKEY.study.panel.FinderCardPanelHeader", { getSearchBox : function() { if (!this.searchBox && this.showSearch) { this.searchBox = Ext4.create('Ext.form.field.Text', { - emptyText:'Studies', + emptyText: this.objectPluralName, cls: 'labkey-search-box', fieldLabel: '', labelWidth: "10px", @@ -74,7 +74,7 @@ Ext4.define("LABKEY.study.panel.FinderCardPanelHeader", { disabled: !this.showSearch, hidden: !this.showSearch, fieldCls: 'labkey-search-box', - id: 'searchTerms', + name: this.objectName + 'searchTerms', listeners: { scope: this, 'change': function(field,newValue,oldValue,eOpts) { @@ -130,7 +130,6 @@ Ext4.define("LABKEY.study.panel.FinderCardPanelHeader", { }, onSearchTermsChanged: function(value) { - console.log("Search terms changed to ", value); this.fireEvent("searchTermsChanged", value); }, diff --git a/resources/web/study/Finder/panel/Publications.js b/resources/web/study/Finder/panel/Publications.js index 597f5740..be4f0652 100644 --- a/resources/web/study/Finder/panel/Publications.js +++ b/resources/web/study/Finder/panel/Publications.js @@ -16,6 +16,7 @@ Ext4.define("LABKEY.study.panel.Publications", { cls: 'labkey-publications-panel', objectName: 'Publication', + objectPluralName: 'Publications', autoScroll: true, @@ -50,7 +51,8 @@ Ext4.define("LABKEY.study.panel.Publications", { dataModuleName: this.dataModuleName, cubeContainerPath: this.cubeContainerPath, showSearch : this.showSearch, - objectName: this.objectName + objectName: this.objectName, + objectPluralName : this.objectPluralName }); } return this.cardPanelHeader; diff --git a/resources/web/study/Finder/panel/Studies.js b/resources/web/study/Finder/panel/Studies.js index de9c352a..bf51393e 100644 --- a/resources/web/study/Finder/panel/Studies.js +++ b/resources/web/study/Finder/panel/Studies.js @@ -17,6 +17,7 @@ Ext4.define("LABKEY.study.panel.Studies", { cls: 'labkey-studies-panel', objectName: 'Study', + objectPluralName: 'Studies', showSearch : true, @@ -58,7 +59,8 @@ Ext4.define("LABKEY.study.panel.Studies", { dataModuleName: this.dataModuleName, cubeContainerPath: this.cubeContainerPath, showSearch : this.showSearch, - objectName: this.objectName + objectName: this.objectName, + objectPluralName: this.objectPluralName }); } return this.cardPanelHeader; diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 98432861..ca1559ad 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -243,7 +243,7 @@ public static CubeConfigBean getCubeConfigBean(String objectName, Container cont CubeConfigBean bean = new CubeConfigBean(); bean.setSchemaName("lists"); bean.setDataModuleName(TrialShareModule.NAME); - bean.setShowSearch(false); + bean.setShowSearch(true); bean.setShowParticipantFilters(false); Module trialShareModule = ModuleLoader.getInstance().getModule(TrialShareModule.NAME); bean.setCubeContainer(((TrialShareModule) trialShareModule).getCubeContainer(container)); @@ -259,6 +259,7 @@ public static CubeConfigBean getCubeConfigBean(String objectName, Container cont bean.setFilterByFacetUniqueName("[Study]"); bean.setIsDefault(isDefault); bean.setSubsetLevelName("[Study.Public].[Public]"); + bean.setSearchCategory(TrialShareModule.searchCategoryStudy.getName()); } else if (objectName.equalsIgnoreCase("publications")) { @@ -271,6 +272,7 @@ else if (objectName.equalsIgnoreCase("publications")) bean.setFilterByFacetUniqueName("[Publication]"); bean.setIsDefault(isDefault); bean.setSubsetLevelName("[Publication.Status].[Status]"); + bean.setSearchCategory(TrialShareModule.searchCategoryPublication.getName()); } return bean; @@ -324,6 +326,7 @@ public static class CubeConfigBean private String _subsetLevelName; private String _cubeContainerPath; private String _cubeContainerId; + private String _searchCategory; public String getObjectName() { @@ -484,6 +487,16 @@ public void setCubeContainerPath(String cubeContainerPath) { _cubeContainerPath = cubeContainerPath; } + + public String getSearchCategory() + { + return _searchCategory; + } + + public void setSearchCategory(String searchCategory) + { + _searchCategory = searchCategory; + } } @RequiresPermission(AdminPermission.class) From ec4f9adf5957c3c7ccbd7db154fe2f41d2b3cbd1 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 22 Mar 2016 10:43:57 -0700 Subject: [PATCH 200/587] Spec 25616 refactor to create CubeObjects panel that can be shared; create CubeObjects store that should be able to be shared; add search filter in Facets store --- resources/web/study/Finder/data/CubeConfig.js | 3 +- .../web/study/Finder/data/CubeObjects.js | 68 +++++++++ resources/web/study/Finder/data/Facets.js | 26 +++- .../web/study/Finder/data/Publication.js | 9 +- .../web/study/Finder/data/Publications.js | 41 ++++-- resources/web/study/Finder/data/Studies.js | 53 +------ resources/web/study/Finder/data/Study.js | 3 +- resources/web/study/Finder/dataFinder.lib.xml | 4 +- .../web/study/Finder/panel/CubeObjects.js | 136 ++++++++++++++++++ .../web/study/Finder/panel/FacetSelection.js | 1 - .../web/study/Finder/panel/FinderCard.js | 72 ++-------- .../Finder/panel/FinderCardPanelHeader.js | 2 +- .../study/Finder/panel/PublicationCards.js | 22 ++- .../web/study/Finder/panel/Publications.js | 90 ------------ resources/web/study/Finder/panel/Studies.js | 98 ------------- .../web/study/Finder/panel/StudyCards.js | 14 +- .../web/study/Finder/panel/StudySummary.js | 2 +- .../trialshare/TrialShareController.java | 13 ++ 18 files changed, 319 insertions(+), 338 deletions(-) create mode 100644 resources/web/study/Finder/data/CubeObjects.js create mode 100644 resources/web/study/Finder/panel/CubeObjects.js delete mode 100644 resources/web/study/Finder/panel/Publications.js delete mode 100644 resources/web/study/Finder/panel/Studies.js diff --git a/resources/web/study/Finder/data/CubeConfig.js b/resources/web/study/Finder/data/CubeConfig.js index f37e6479..ecca9441 100644 --- a/resources/web/study/Finder/data/CubeConfig.js +++ b/resources/web/study/Finder/data/CubeConfig.js @@ -17,6 +17,7 @@ Ext4.define('LABKEY.study.data.CubeConfig', { {name: 'showParticipantFilters'}, {name: 'isDefault'}, {name: 'subsetLevelName'}, - {name: 'searchCategory'} + {name: 'searchCategory'}, + {name: 'hasContainerFilter', type: 'boolean', defaultValue: false} ] }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/CubeObjects.js b/resources/web/study/Finder/data/CubeObjects.js new file mode 100644 index 00000000..21369b04 --- /dev/null +++ b/resources/web/study/Finder/data/CubeObjects.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2015-2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +Ext4.define('LABKEY.study.store.CubeObjects', { + extend: 'Ext.data.Store', + autoLoad: false, + + proxy : { + type: "ajax", + //url: set before calling "load". + reader: { + type: 'json', + root: 'data' + } + }, + + listeners: { + 'load' : { + fn : function(store, records, options) { + store.updateFacetFilters(this.selectedSubset ? null : {}); // initial load should have no studies selected + }, + scope: this + } + }, + + + updateSearchFilters: function(searchSelectedMembers) { + this.updateFilters(this.facetSelectedMembers, searchSelectedMembers, this.selectedSubset); + }, + + updateFacetFilters: function(selectedMembers, selectedSubset) { + this.updateFilters(selectedMembers, this.searchSelectedMembers, selectedSubset) + }, + + updateFilters: function(facetSelectedMembers, searchSelectedMembers, selectedSubset) + { + if (facetSelectedMembers != undefined) + this.facetSelectedMembers = facetSelectedMembers; + if (searchSelectedMembers == null || searchSelectedMembers) // null is a value we want to retain but undefined is not + this.searchSelectedMembers = searchSelectedMembers; + if (selectedSubset) + this.selectedSubset = selectedSubset; + + var object; + + this.clearFilter(); + for (var i = 0; i < this.count(); i++) { + object = this.getAt(i); + object.set("isSelected", this.facetSelectedMembers[object.get(object.idProperty)] !== undefined); + object.set("isSelectedBySearch", this.searchSelectedMembers == null || this.searchSelectedMembers[object.get(object.idProperty)] !== undefined); + } + + this.filter([ + {property: 'isSelected', value: true}, + {property: 'isSelectedBySearch', value: true} + ]); + }, + + selectAll : function() { + for (var i = 0; i < this.count(); i++) { + var cubeObj = this.getAt(i); + cubeObj.set("isSelected", true); + cubeObj.set("isSelectedBySearch", true); + } + } +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index dc51394e..a8a97ba4 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -113,7 +113,11 @@ Ext4.define('LABKEY.study.store.Facets', { }, getCountDistinctFilters: function(filters) { + // TODO I think we need to check for an existing filter for the filterByLevel var newFilters = this.getStudySubsetFilter(); + if (newFilters != null) + filters.push(newFilters); + newFilters = this.getSearchFilter(); if (newFilters != null) filters.push(newFilters); newFilters = this.getCustomFilters(); @@ -122,6 +126,20 @@ Ext4.define('LABKEY.study.store.Facets', { return filters; }, + getSearchFilter : function() + { + var store = Ext4.getStore(this.cubeConfig.objectName); + if (store.searchSelectedMembers != null) + { + var selection = []; + for (var key in store.searchSelectedMembers) + { + selection.push(store.searchSelectedMembers[key]) + } + return {level: this.cubeConfig.filterByLevel, members: selection}; + } + }, + getCustomFilters: function() { return null; @@ -236,9 +254,6 @@ Ext4.define('LABKEY.study.store.Facets', { var filters = intersectFilters; - // CONSIDER: Don't fetch subject IDs every time a filter is changed. - var includeSubjectIds = false; - var onRows = { operator: "UNION", arguments: [] }; onRows.arguments.push({level: this.cubeConfig.filterByLevel}); for (f = 0; f < this.count(); f++) @@ -252,8 +267,6 @@ Ext4.define('LABKEY.study.store.Facets', { onRows.arguments.push({level: facet.data.level.uniqueName}); } - if (includeSubjectIds) - onRows.arguments.push({level: "[Subject].[Subject]", members: "members"}); var config = { @@ -373,8 +386,7 @@ Ext4.define('LABKEY.study.store.Facets', { updateMemberFilter : function(selectedMembers) { var store = Ext4.getStore(this.cubeConfig.objectName); - store.selectedStudies = selectedMembers; - store.updateFilters(selectedMembers); + store.updateFacetFilters(selectedMembers); }, getRowPositions : function(cellSet) diff --git a/resources/web/study/Finder/data/Publication.js b/resources/web/study/Finder/data/Publication.js index 3fbfd05e..09f68fc4 100644 --- a/resources/web/study/Finder/data/Publication.js +++ b/resources/web/study/Finder/data/Publication.js @@ -27,14 +27,15 @@ Ext4.define('LABKEY.study.data.Publication', { {name: 'dataUrl'}, {name: 'studies'}, {name: 'publicationType'}, - {name: 'isSelected', type: 'boolean', defaultValue: true}, - {name: 'isHighlighted', type: 'boolean', defaultValue: false}, {name: 'abstractText'}, {name: 'keywords'}, - {name: 'viewState', type: 'string', defaultValue: 'collapsed'}, {name: 'thumbnails'}, {name: 'keywords'}, - {name: 'urls'} + {name: 'urls'}, + {name: 'isSelected', type: 'boolean', defaultValue: true}, + {name: 'isSelectedBySearch', type: 'boolean', defaultValue: false}, + {name: 'isHighlighted', type: 'boolean', defaultValue: false}, + {name: 'viewState', type: 'string', defaultValue: 'collapsed'} ] }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Publications.js b/resources/web/study/Finder/data/Publications.js index 205b7c16..efb1e6e2 100644 --- a/resources/web/study/Finder/data/Publications.js +++ b/resources/web/study/Finder/data/Publications.js @@ -8,8 +8,6 @@ Ext4.define('LABKEY.study.store.Publications', { storeId: 'Publication', model: 'LABKEY.study.data.Publication', autoLoad: false, - dataModuleName: "", - selectedSubset : null, proxy : { type: "ajax", //url: set before calling "load". @@ -23,21 +21,45 @@ Ext4.define('LABKEY.study.store.Publications', { direction: 'ASC' }], - updateFilters: function(selectedMembers, selectedSubset) { - if (selectedMembers) - this.selectedMembers = selectedMembers; + updateSearchFilters: function(searchSelectedMembers) { + this.updateFilters(this.facetSelectedMembers, searchSelectedMembers, this.selectedSubset); + }, + + clearSearchFilters: function() { + this.searchSelectedMembers = null; + }, + + updateFacetFilters: function(selectedMembers, selectedSubset) { + this.updateFilters(selectedMembers, this.searchSelectedMembers, selectedSubset) + }, + + clearFacetFilters: function() { + this.facetSelectedMembers = null; + }, + + updateFilters: function(facetSelectedMembers, searchSelectedMembers, selectedSubset) + { + if (facetSelectedMembers != undefined) + this.facetSelectedMembers = facetSelectedMembers; + if (searchSelectedMembers == null || searchSelectedMembers) // null is a value we want to retain but undefined is not + this.searchSelectedMembers = searchSelectedMembers; if (selectedSubset) this.selectedSubset = selectedSubset; + var object; + // this.suspendEvents(false); this.clearFilter(); for (var i = 0; i < this.count(); i++) { object = this.getAt(i); - object.set("isSelected", this.selectedMembers[object.get("id")] !== undefined); + object.set("isSelected", this.facetSelectedMembers[object.get("id")] !== undefined); + object.set("isSelectedBySearch", this.searchSelectedMembers == null || this.searchSelectedMembers[object.get("id")] !== undefined); } + // this.resumeEvents(); this.filter([ - {property: 'isSelected', value: true} + {property: 'isSelected', value: true}, + {property: 'isSelectedBySearch', value: true} ]); }, @@ -46,10 +68,5 @@ Ext4.define('LABKEY.study.store.Publications', { var object = this.getAt(i); object.set("isSelected", true); } - }, - - constructor: function(config) { - config.selectedMembers = {}; - this.callParent(config); } }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Studies.js b/resources/web/study/Finder/data/Studies.js index 3f5fcf67..4a25b480 100644 --- a/resources/web/study/Finder/data/Studies.js +++ b/resources/web/study/Finder/data/Studies.js @@ -3,62 +3,15 @@ * * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 */ +// TODO get rid of this Ext4.define('LABKEY.study.store.Studies', { - extend: 'Ext.data.Store', + extend: 'LABKEY.study.store.CubeObjects', storeId: 'Study', model: 'LABKEY.study.data.Study', autoLoad: false, - selectedSubset : null, - proxy : { - type: "ajax", - //url: set before calling "load". - reader: { - type: 'json', - root: 'data' - } - }, sorters: [{ property: 'shortName', direction: 'ASC' - }], - - listeners: { - 'load' : { - fn : function(store, records, options) { - store.updateFilters(this.selectedSubset ? null : {}); // initial load should have no studies selected - }, - scope: this - } - }, - - updateFilters: function(selectedStudies, selectedSubset) { - if (selectedStudies) - this.selectedStudies = selectedStudies; - if (selectedSubset) - this.selectedSubset = selectedSubset; - var study; - - this.clearFilter(); - for (var i = 0; i < this.count(); i++) { - study = this.getAt(i); - study.set("isSelected", this.selectedStudies[study.get("studyId")] !== undefined); - } - - this.filter([ - {property: 'isSelected', value: true} - ]); - }, - - selectAll : function() { - for (var i = 0; i < this.count(); i++) { - study = this.getAt(i); - study.set("isSelected", true); - } - }, - - constructor: function(config) { - config.selectedStudies = {}; - this.callParent(config); - } + }] }); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Study.js b/resources/web/study/Finder/data/Study.js index 1c53774f..b334e339 100644 --- a/resources/web/study/Finder/data/Study.js +++ b/resources/web/study/Finder/data/Study.js @@ -22,7 +22,8 @@ Ext4.define('LABKEY.study.data.Study', { {name: 'abstractCount', type: 'int'}, {name: 'manuscriptCount', type: 'int'}, {name: 'participantCount', type: 'int'}, - {name: 'isSelected', type: 'boolean'}, + {name: 'isSelected', type: 'boolean', defaultValue: true}, + {name: 'isSelectedBySearch', type: 'boolean', defaultValue: false}, {name: 'isHighlighted', type: 'boolean', defaultValue: false}, {name: 'isBorderHighlighted', type: 'boolean', defaultValue: false}, {name: 'studyAccessList'} diff --git a/resources/web/study/Finder/dataFinder.lib.xml b/resources/web/study/Finder/dataFinder.lib.xml index 384197cf..b628b100 100644 --- a/resources/web/study/Finder/dataFinder.lib.xml +++ b/resources/web/study/Finder/dataFinder.lib.xml @@ -5,6 +5,7 @@ From d41f8708d532698b6dd56ed942e1de89f0b94e90 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 28 Mar 2016 11:37:16 -0700 Subject: [PATCH 216/587] Spec 25616 use keywords instead of identifiers when indexing because the stemming done for identifiers causes non-integer identifiers to not be indexed as expected. --- .../trialshare/PublicationDocumentProvider.java | 11 +++++++---- src/org/labkey/trialshare/StudyDocumentProvider.java | 11 ++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index 5b30ace1..0de1d963 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -111,10 +111,13 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container } Map properties = new HashMap<>(); - // FIXME It seems to be required to add the author to the body in order for it to be in the index, even though supplied as an identifier - body.append("\n" + results.getString("Author")); - properties.put(SearchService.PROPERTY.identifiersMed.toString(), results.getString("Author")); - properties.put(SearchService.PROPERTY.keywordsMed.toString(), results.getString("Title")); + StringBuilder keywords = new StringBuilder(); + for (String field : new String[]{"Author", "Title"}) + { + if (results.getString(field) != null) + keywords.append(results.getString(field)).append(" "); + } + properties.put(SearchService.PROPERTY.keywordsMed.toString(), keywords); properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryPublication.getName()); diff --git a/src/org/labkey/trialshare/StudyDocumentProvider.java b/src/org/labkey/trialshare/StudyDocumentProvider.java index c7374598..db7df258 100644 --- a/src/org/labkey/trialshare/StudyDocumentProvider.java +++ b/src/org/labkey/trialshare/StudyDocumentProvider.java @@ -101,17 +101,14 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container Map properties = new HashMap<>(); - StringBuilder identifiers = new StringBuilder(); - for (String field : new String[]{"shortName", "StudyId", "Investigator"}) + StringBuilder keywords = new StringBuilder(); + for (String field : new String[]{"shortName", "StudyId", "Investigator", "Title"}) { if (results.getString(field) != null) - identifiers.append(results.getString(field)).append(" "); + keywords.append(results.getString(field)).append(" "); } - // FIXME it seems to be required to add the identifiers to the body for them to show up in the index even though also supplied as an identifier property - body.append(" " + identifiers); - properties.put(SearchService.PROPERTY.identifiersHi.toString(), identifiers.toString()); - properties.put(SearchService.PROPERTY.keywordsHi.toString(), results.getString("Title")); + properties.put(SearchService.PROPERTY.keywordsMed.toString(), keywords.toString()); properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryStudy.getName()); From 1abcf1d6b646f2bd3144d61feeae1aa887d77d4b Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 28 Mar 2016 16:22:35 -0700 Subject: [PATCH 217/587] Issue 26054: show alert message when search error occurs --- resources/web/study/Finder/panel/CubeObjects.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/resources/web/study/Finder/panel/CubeObjects.js b/resources/web/study/Finder/panel/CubeObjects.js index 1e923419..f8c14003 100644 --- a/resources/web/study/Finder/panel/CubeObjects.js +++ b/resources/web/study/Finder/panel/CubeObjects.js @@ -83,7 +83,15 @@ Ext4.define("LABKEY.study.panel.CubeObjects", { } // console.log("found " + Object.keys(searchHits).length + " objects matching terms " + searchTerms, searchHits); this.updateSearchFilters(searchHits); - // this.getCardPanelHeader().onSearchTermsChanged(this.getSearchMessage()); + }, + failure: function(response) + { + var data = Ext4.decode(response.responseText); + Ext4.Msg.show({ + title: 'Error', + buttons: Ext4.MessageBox.OK, + msg: data.exception + }); } }); From 2395b18b9da0b5bb291c3bd29e45f6bd25d89982 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 28 Mar 2016 16:46:34 -0700 Subject: [PATCH 218/587] Spec 25616: update indexing split between identifiers and keywords --- .../PublicationDocumentProvider.java | 21 ++++++++++--------- .../trialshare/StudyDocumentProvider.java | 6 ++++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index 0de1d963..86ee3af2 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -104,7 +104,7 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container StringBuilder body = new StringBuilder(); - for (String field : new String[]{"AbstractText", "DOI", "PMID", "PMCID", "PublicationType", "Journal", "Citation", "Year", "Status", "PrimaryStudy", "PrimaryStudyId", "Keywords", "Assay", "Condition", "StudyId", "StudyShortName", "TherapeuticArea"}) + for (String field : new String[]{"AbstractText", "Citation", "Keywords"}) { if (results.getString(field) != null) body.append(results.getString(field).replaceAll(",", " ")).append(" "); @@ -112,12 +112,20 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container Map properties = new HashMap<>(); StringBuilder keywords = new StringBuilder(); - for (String field : new String[]{"Author", "Title"}) + // See #26028: identifiers that have punctuation in them (e.g., DOI) are not indexed well as identifiers, so we use keywords instead + for (String field : new String[]{"Author", "Year", "Status", "PrimaryStudy", "Title", "PublicationType", "Journal", "TherapeuticArea", "StudyShortName", "Assay", "Condition", "DOI"}) { if (results.getString(field) != null) keywords.append(results.getString(field)).append(" "); } - properties.put(SearchService.PROPERTY.keywordsMed.toString(), keywords); + StringBuilder identifiers = new StringBuilder(); + for (String field : new String[]{"PMID", "PMCID", "StudyId", "PrimaryStudyId"}) + { + if (results.getString(field) != null) + identifiers.append(results.getString(field)).append(" " ); + } + properties.put(SearchService.PROPERTY.identifiersMed.toString(), identifiers.toString()); + properties.put(SearchService.PROPERTY.keywordsMed.toString(), keywords.toString()); properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryPublication.getName()); @@ -153,12 +161,5 @@ else if (results.getString("ManuscriptContainer") != null) @Override public void indexDeleted() throws SQLException { - TrialShareQuerySchema querySchema = new TrialShareQuerySchema(User.getSearchUser(), null); - SqlExecutor executor = new SqlExecutor(querySchema.getSchema().getDbSchema()); - - for (TableInfo ti : querySchema.getPublicationTables()) - { - executor.execute("UPDATE " + ti.getFromSQL(ti.getName()) + " SET LastIndexed = NULL"); - } } } diff --git a/src/org/labkey/trialshare/StudyDocumentProvider.java b/src/org/labkey/trialshare/StudyDocumentProvider.java index db7df258..53dd9c41 100644 --- a/src/org/labkey/trialshare/StudyDocumentProvider.java +++ b/src/org/labkey/trialshare/StudyDocumentProvider.java @@ -93,7 +93,7 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container StringBuilder body = new StringBuilder(); - for (String field : new String[]{"Description", "AgeGroup", "Assay", "Condition", "Phase", "TherapeuticArea", "StudyType"}) + for (String field : new String[]{"Description", "Title"}) { if (results.getString(field) != null) body.append(results.getString(field).replaceAll(",", " ")).append(" " ); @@ -102,12 +102,14 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container Map properties = new HashMap<>(); StringBuilder keywords = new StringBuilder(); - for (String field : new String[]{"shortName", "StudyId", "Investigator", "Title"}) + // See #26028: we want to avoid stemming of the following fields, so we use keywords instead + for (String field : new String[]{"shortName", "StudyId", "Investigator", "AgeGroup", "Assay", "Condition", "Phase", "TherapeuticArea", "StudyType"}) { if (results.getString(field) != null) keywords.append(results.getString(field)).append(" "); } + properties.put(SearchService.PROPERTY.identifiersMed.toString(), results.getString("StudyId")); properties.put(SearchService.PROPERTY.keywordsMed.toString(), keywords.toString()); properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryStudy.getName()); From 03e0e6a33f901bbd73350ac78cf0f32c0ef88441 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 28 Mar 2016 16:47:59 -0700 Subject: [PATCH 219/587] Spec 25616: add separate count signals for studies vs. publications --- resources/web/study/Finder/data/Facets.js | 2 +- .../test/pages/trialshare/DataFinderPage.java | 28 ++++++++----------- .../trialshare/TrialShareDataFinderTest.java | 25 +++++++++-------- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index b3cc4c09..57a02326 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -376,7 +376,7 @@ Ext4.define('LABKEY.study.store.Facets', { // this.changeSubjectGroup(); - LABKEY.Utils.signalWebDriverTest('dataFinderCountsUpdated'); + LABKEY.Utils.signalWebDriverTest('dataFinder' + this.cubeConfig.objectName + 'CountsUpdated'); }, updateMemberFilter : function(selectedMembers) { diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java index 5fa2fc9a..0b6a4768 100644 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java @@ -39,7 +39,8 @@ public class DataFinderPage extends LabKeyPage { private static final String CONTROLLER = "trialshare"; private static final String ACTION = "dataFinder"; - public static final String COUNT_SIGNAL = "dataFinderCountsUpdated"; + public static final String STUDY_COUNT_SIGNAL = "dataFinderStudyCountsUpdated"; + public static final String PUBLICATION_COUNT_SIGNAL = "dataFinderPublicationCountsUpdated"; private static final String GROUP_UPDATED_SIGNAL = "participantGroupUpdated"; private static final String PUBLICATION_DETAILS_SIGNAL = "publicationDetailsLoaded"; private boolean testingStudies = true; @@ -59,10 +60,14 @@ public DataFinderPage(BaseWebDriverTest test, boolean testingStudies) } } + public String getCountSignal() + { + return (this.testingStudies ? STUDY_COUNT_SIGNAL : PUBLICATION_COUNT_SIGNAL); + } @Override protected void waitForPage() { - waitForElement(LabKeyPage.Locators.pageSignal(COUNT_SIGNAL)); + waitForElement(LabKeyPage.Locators.pageSignal(getCountSignal())); } protected void waitForGroupUpdate() @@ -70,17 +75,6 @@ protected void waitForGroupUpdate() waitForElement(LabKeyPage.Locators.pageSignal(GROUP_UPDATED_SIGNAL)); } -// public static DataFinderPage goDirectlyToPage(BaseWebDriverTest test, String containerPath, boolean testingStudies) -// { -// test.doAndWaitForPageSignal(() -> test.beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), COUNT_SIGNAL); -// return new DataFinderPage(test, testingStudies); -// } -// -// public static DataFinderPage goDirectlyToPage(WebDriver test, String containerPath, boolean testingStudies) -// { -// doAndWaitForPageSignal(() -> beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), COUNT_SIGNAL); -// return new DataFinderPage(test, testingStudies); -// } public boolean hasStudySubsetCombo() { @@ -93,7 +87,7 @@ public void selectStudySubset(String text) _ext4Helper.openComboList(DataFinderPage.Locators.studySubsetCombo); if (!isElementPresent(Ext4Helper.Locators.comboListItemSelected().withText(text))) { - doAndWaitForPageSignal(() ->_ext4Helper.selectItemFromOpenComboList(text, Ext4Helper.TextMatchTechnique.EXACT), COUNT_SIGNAL); + doAndWaitForPageSignal(() ->_ext4Helper.selectItemFromOpenComboList(text, Ext4Helper.TextMatchTechnique.EXACT), getCountSignal()); } else // FIXME you should be able to just close the combo box at this point, but the close method assumes you've chosen something from teh lis @@ -105,7 +99,7 @@ public void selectStudySubset(String text) @LogMethod public void search(@LoggedParam final String search) { - doAndWaitForPageSignal(() -> setFormElement(Locators.getSearchInput(finderLocator), search), COUNT_SIGNAL); + doAndWaitForPageSignal(() -> setFormElement(Locators.getSearchInput(finderLocator), search), getCountSignal()); } @LogMethod(quiet = true) @@ -226,7 +220,7 @@ public void clearAllFilters() final WebElement clearAll = activeClearAllLocator.findElement(getDriver()); if (clearAll.isDisplayed()) { - doAndWaitForPageSignal(clearAll::click, COUNT_SIGNAL); + doAndWaitForPageSignal(clearAll::click, getCountSignal()); } } } @@ -463,7 +457,7 @@ public void toggleFacet(Dimension dimension, String name) scrollIntoView(rowLocator); WebElement row = rowLocator.findElement(getDriver()); - doAndWaitForPageSignal(() -> row.click(), COUNT_SIGNAL); + doAndWaitForPageSignal(() -> row.click(), getCountSignal()); } public void clearFilter(Dimension dimension) diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 55f2851f..11e227e1 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -79,7 +79,6 @@ public class TrialShareDataFinderTest extends BaseWebDriverTest implements ReadO private static final String WISPR_READER = WISPR_READER_DISPLAY_NAME + EMAIL_EXTENSION; private static final String CONTROLLER = "trialshare"; private static final String ACTION = "dataFinder"; - public static final String COUNT_SIGNAL = "dataFinderCountsUpdated"; private static File listArchive = TestFileUtils.getSampleData("DataFinder.lists.zip"); private static final String RELOCATED_DATA_FINDER_PROJECT = "RelocatedDataFinder"; @@ -192,8 +191,10 @@ private void setUpProject() new PortalHelper(this).addWebPart(WEB_PART_NAME); } + @LogMethod private void reindexForSearch() { + log("Reindexing data for full-text search"); goToAdminConsole(); clickAndWait(Locator.linkWithText("Data Cube")); clickButton("Reindex", 0); @@ -320,11 +321,11 @@ public void testPublicAccess() List cards = finder.getDataCards(); Assert.assertEquals("Number of studies not as expected", studySubsets.get("Public").size(), cards.size()); stopImpersonating(); - doAndWaitForPageSignal(() -> goToProjectHome(), DataFinderPage.COUNT_SIGNAL); + doAndWaitForPageSignal(() -> goToProjectHome(), finder.getCountSignal()); Assert.assertTrue("Admin user should see visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); - doAndWaitForPageSignal(() -> impersonate(CASALE_READER), DataFinderPage.COUNT_SIGNAL); + doAndWaitForPageSignal(() -> impersonate(CASALE_READER), finder.getCountSignal()); Assert.assertFalse("User with access to only Casale study should not see the subset menu", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); cards = finder.getDataCards(); Assert.assertEquals("User with access to only Casale study should see only that study", 1, cards.size()); @@ -567,7 +568,7 @@ public void testGoToStudyMenu() DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); log("Filtering to show DIAMOND card with two study containers"); facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Public"); - doAndWaitForPageSignal(() -> facets.toggleFacet(DataFinderPage.Dimension.CONDITION, "Lupus Nephritis"), DataFinderPage.COUNT_SIGNAL); + doAndWaitForPageSignal(() -> facets.toggleFacet(DataFinderPage.Dimension.CONDITION, "Lupus Nephritis"), finder.getCountSignal()); List dataCards = finder.getDataCards(); Assert.assertEquals("Should have a single data card at this point", 1, dataCards.size()); DataFinderPage.DataCard card = dataCards.get(0); @@ -584,7 +585,7 @@ public void testGoToStudyNoMenuForPublicReader() DataFinderPage finder = new DataFinderPage(this, true); DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); log("Filtering to show DIAMOND card"); - doAndWaitForPageSignal(() -> facets.toggleFacet(DataFinderPage.Dimension.CONDITION, "Lupus Nephritis"), DataFinderPage.COUNT_SIGNAL); + doAndWaitForPageSignal(() -> facets.toggleFacet(DataFinderPage.Dimension.CONDITION, "Lupus Nephritis"), finder.getCountSignal()); List dataCards = finder.getDataCards(); Assert.assertEquals("Should have a single data card at this point", 1, dataCards.size()); DataFinderPage.DataCard card = dataCards.get(0); @@ -748,7 +749,7 @@ public void testSwitchBetweenStudyAndPublication() { DataFinderPage finder = new DataFinderPage(this, true); log("Start at home."); - doAndWaitForPageSignal(() -> goToProjectHome(), DataFinderPage.COUNT_SIGNAL); + doAndWaitForPageSignal(() -> goToProjectHome(), finder.getCountSignal()); waitForElement(DataFinderPage.Locators.studyFinder); log("Click the 'Publications' tab"); finder.navigateToPublications(); @@ -770,7 +771,7 @@ public void testFilterOnStatus() log("Filter for 'In Progress' only publications."); DataFinderPage.FacetGrid fg = finder.getFacetsGrid(); - doAndWaitForPageSignal(() -> fg.toggleFacet(DataFinderPage.Dimension.STATUS, "In Progress"), DataFinderPage.COUNT_SIGNAL); + doAndWaitForPageSignal(() -> fg.toggleFacet(DataFinderPage.Dimension.STATUS, "In Progress"), finder.getCountSignal()); log("Validate that the number, content and style of the cards is as expected."); counts = fg.getMemberCounts(DataFinderPage.Dimension.IN_PROGRESS); @@ -830,7 +831,7 @@ public void testPublicationDetail() log("Filter for a publication that has DOI, PMID and PMCID values."); fg = finder.getFacetsGrid(); - doAndWaitForPageSignal(() -> fg.toggleFacet(DataFinderPage.Dimension.STATUS, "In Progress"), DataFinderPage.COUNT_SIGNAL); + doAndWaitForPageSignal(() -> fg.toggleFacet(DataFinderPage.Dimension.STATUS, "In Progress"), finder.getCountSignal()); summaryCount = finder.getSummaryCounts(); @@ -901,10 +902,10 @@ private void assertCountsSynced(DataFinderPage finder, DataFinderPage.Dimension private DataFinderPage goDirectlyToDataFinderPage(String containerPath, boolean testingStudies) { - doAndWaitForPageSignal(() -> beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), COUNT_SIGNAL, longWait()); - DataFinderPage page = new DataFinderPage(this, testingStudies); + DataFinderPage finder = new DataFinderPage(this, testingStudies); + doAndWaitForPageSignal(() -> beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), finder.getCountSignal(), longWait()); if (!testingStudies) - page.navigateToPublications(); - return page; + finder.navigateToPublications(); + return finder; } } \ No newline at end of file From e4c506740d46bf6fb326512db8bbe3bd19a2f133 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 28 Mar 2016 16:48:30 -0700 Subject: [PATCH 220/587] Spec 25616: fill in getPublicationTables method --- .../query/TrialShareQuerySchema.java | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index 2d606178..668edb17 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -8,9 +8,6 @@ import org.labkey.api.security.User; import org.labkey.trialshare.TrialShareModule; -import java.util.HashSet; -import java.util.Set; - /** * Created by susanh on 2/23/16. */ @@ -103,21 +100,50 @@ public TableInfo getStudyTherapeuticAreaTableInfo() return _listsSchema.getTable(TrialShareQuerySchema.STUDY_THERAPEUTIC_AREA_TABLE); } + public TableInfo getPublicationsTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.PUBLICATION_TABLE); + } + + public TableInfo getPublicationAssayTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.PUBLICATION_ASSAY_TABLE); + } + + public TableInfo getPublicationConditionTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.PUBLICATION_CONDITION_TABLE); + } + + public TableInfo getPublicationStudyTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.PUBLICATION_STUDY_TABLE); + } + + public TableInfo getPublicationTherapeuticAreaTableInfo() + { + return _listsSchema.getTable(TrialShareQuerySchema.PUBLICATION_THERAPEUTIC_AREA_TABLE); + } + public TableInfo[] getStudyTables() { return new TableInfo[] { -// getStudyPropertiesTableInfo(), -// getStudyAccessTableInfo(), -// getStudyAgeGroupTableInfo(), -// getStudyAssayTableInfo(), -// getStudyConditionTableInfo(), -// getStudyPhaseTableInfo(), -// getStudyTherapeuticAreaTableInfo() + getStudyPropertiesTableInfo(), + getStudyAccessTableInfo(), + getStudyAgeGroupTableInfo(), + getStudyAssayTableInfo(), + getStudyConditionTableInfo(), + getStudyPhaseTableInfo(), + getStudyTherapeuticAreaTableInfo() }; } public TableInfo[] getPublicationTables() { return new TableInfo[] { -// TODO + getPublicationsTableInfo(), + getPublicationAssayTableInfo(), + getPublicationConditionTableInfo(), + getPublicationStudyTableInfo(), + getPublicationTherapeuticAreaTableInfo() }; } } From 5e07c7e3e37a62bff8de00e7b55d1721fad71487 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Mar 2016 08:44:25 -0700 Subject: [PATCH 221/587] Spec 25616: remove indexDeleted body (since we don't currently support updating the lastIndex) --- .../trialshare/PublicationDocumentProvider.java | 2 +- .../labkey/trialshare/StudyDocumentProvider.java | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index 86ee3af2..988d4ddb 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -15,7 +15,6 @@ */ package org.labkey.trialshare; -import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; @@ -161,5 +160,6 @@ else if (results.getString("ManuscriptContainer") != null) @Override public void indexDeleted() throws SQLException { + // we currently do not support the LastIndexed setting } } diff --git a/src/org/labkey/trialshare/StudyDocumentProvider.java b/src/org/labkey/trialshare/StudyDocumentProvider.java index 53dd9c41..c957df0f 100644 --- a/src/org/labkey/trialshare/StudyDocumentProvider.java +++ b/src/org/labkey/trialshare/StudyDocumentProvider.java @@ -15,11 +15,8 @@ */ package org.labkey.trialshare; -import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.labkey.api.data.Container; -import org.labkey.api.data.SqlExecutor; -import org.labkey.api.data.TableInfo; import org.labkey.api.module.Module; import org.labkey.api.module.ModuleLoader; import org.labkey.api.query.QuerySchema; @@ -116,7 +113,7 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container String containerId = results.getString("StudyContainer") == null ? c.getId() : results.getString("StudyContainer"); - ActionURL url = new ActionURL(TrialShareController.StudyDetailAction.class, c).addParameter("studyId", (String) results.getString("StudyId")).addParameter("detailType", "study"); + ActionURL url = new ActionURL(TrialShareController.StudyDetailAction.class, c).addParameter("studyId", results.getString("StudyId")).addParameter("detailType", "study"); url.setExtraPath(containerId); SimpleDocumentResource resource = new SimpleDocumentResource ( @@ -140,12 +137,6 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container @Override public void indexDeleted() throws SQLException { - TrialShareQuerySchema querySchema = new TrialShareQuerySchema(User.getSearchUser(), null); - SqlExecutor executor = new SqlExecutor(querySchema.getSchema().getDbSchema()); - - for (TableInfo ti : querySchema.getStudyTables()) - { - executor.execute("UPDATE " + ti.getFromSQL(ti.getName()) + " SET LastIndexed = NULL"); - } + // we currently do not support the LastIndexed setting } } From 67aa8234fa034937fc517892de106a4d711b1253 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Mar 2016 08:45:06 -0700 Subject: [PATCH 222/587] Spec 25616: code cleanup --- .../web/study/Finder/data/CubeObjects.js | 19 +++++++++---------- resources/web/study/Finder/data/Facets.js | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/resources/web/study/Finder/data/CubeObjects.js b/resources/web/study/Finder/data/CubeObjects.js index ea17b037..ff697442 100644 --- a/resources/web/study/Finder/data/CubeObjects.js +++ b/resources/web/study/Finder/data/CubeObjects.js @@ -20,23 +20,22 @@ Ext4.define('LABKEY.study.store.CubeObjects', { }, updateSearchFilters: function(searchSelectedMembers) { - this.updateFilters(this.facetSelectedMembers, searchSelectedMembers, this.selectedSubset); - }, - - updateFacetFilters: function(selectedMembers, selectedSubset) { - // console.log("update facet filters with searchSelectedMembers ", this.searchSelectedMembers); - this.updateFilters(selectedMembers, this.searchSelectedMembers, selectedSubset) + if (searchSelectedMembers == null || searchSelectedMembers) // null is a value we want to retain but undefined is not + this.searchSelectedMembers = searchSelectedMembers; + this.updateFilters(); }, - updateFilters: function(facetSelectedMembers, searchSelectedMembers, selectedSubset) - { + updateFacetFilters: function(facetSelectedMembers, selectedSubset) { if (facetSelectedMembers != undefined) this.facetSelectedMembers = facetSelectedMembers; - if (searchSelectedMembers == null || searchSelectedMembers) // null is a value we want to retain but undefined is not - this.searchSelectedMembers = searchSelectedMembers; if (selectedSubset) this.selectedSubset = selectedSubset; + // console.log("update facet filters with searchSelectedMembers ", this.searchSelectedMembers); + this.updateFilters() + }, + updateFilters: function() + { var object; this.suspendEvents(false); diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index 57a02326..fe759ad5 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -112,7 +112,7 @@ Ext4.define('LABKEY.study.store.Facets', { }, - getCountDistinctFilters: function(filtersMap) { + getFiltersForCountDistinct: function(filtersMap) { this.addFilterMapData(filtersMap, this.getStudySubsetFilter()); this.addFilterMapData(filtersMap, this.getSearchFilter()); this.addFilterMapData(filtersMap, this.getCustomFilters()); @@ -213,7 +213,7 @@ Ext4.define('LABKEY.study.store.Facets', { makeCountDistinctQuery: function(filtersMap) { - var filters = this.getCountDistinctFilters(filtersMap); + var filters = this.getFiltersForCountDistinct(filtersMap); var i, f, facet; for (f = 0; f < this.count(); f++) { From 477f9680d8ed7ad6af66c4997a1fba32b8eefd46 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Mar 2016 08:46:20 -0700 Subject: [PATCH 223/587] Spec 25616: return lists instead of arrays when getting tableInfo collections --- .../query/TrialShareQuerySchema.java | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index 668edb17..fa0ede56 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -8,6 +8,9 @@ import org.labkey.api.security.User; import org.labkey.trialshare.TrialShareModule; +import java.util.ArrayList; +import java.util.List; + /** * Created by susanh on 2/23/16. */ @@ -125,25 +128,25 @@ public TableInfo getPublicationTherapeuticAreaTableInfo() return _listsSchema.getTable(TrialShareQuerySchema.PUBLICATION_THERAPEUTIC_AREA_TABLE); } - public TableInfo[] getStudyTables() { - return new TableInfo[] { - getStudyPropertiesTableInfo(), - getStudyAccessTableInfo(), - getStudyAgeGroupTableInfo(), - getStudyAssayTableInfo(), - getStudyConditionTableInfo(), - getStudyPhaseTableInfo(), - getStudyTherapeuticAreaTableInfo() - }; - } - - public TableInfo[] getPublicationTables() { - return new TableInfo[] { - getPublicationsTableInfo(), - getPublicationAssayTableInfo(), - getPublicationConditionTableInfo(), - getPublicationStudyTableInfo(), - getPublicationTherapeuticAreaTableInfo() - }; + public List getStudyTables() { + List list = new ArrayList<>(); + list.add(getStudyPropertiesTableInfo()); + list.add(getStudyAccessTableInfo()); + list.add(getStudyAgeGroupTableInfo()); + list.add(getStudyAssayTableInfo()); + list.add(getStudyConditionTableInfo()); + list.add(getStudyPhaseTableInfo()); + list.add(getStudyTherapeuticAreaTableInfo()); + return list; + } + + public List getPublicationTables() { + List list = new ArrayList<>(); + list.add(getPublicationsTableInfo()); + list.add(getPublicationAssayTableInfo()); + list.add(getPublicationConditionTableInfo()); + list.add(getPublicationStudyTableInfo()); + list.add(getPublicationTherapeuticAreaTableInfo()); + return list; } } From 5796d3c4811b0ab37d4019ea63dce1f5ef01ab01 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Mar 2016 08:46:58 -0700 Subject: [PATCH 224/587] Spec 25616: update site-wide module property for proper access in admin console. --- .../labkey/test/tests/trialshare/TrialShareDataFinderTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 11e227e1..9da475b3 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -182,7 +182,8 @@ private void setUpProject() createUsers(); List propList = new ArrayList<>(); - propList.add(new ModulePropertyValue("TrialShare", "/" + getProjectName(), "DataFinderCubeContainer", getProjectName())); + // set the site-default value for this so it will work as expected from the Admin Console. + propList.add(new ModulePropertyValue("TrialShare", "/", "DataFinderCubeContainer", getProjectName())); setModuleProperties(propList); reindexForSearch(); From f40e57ba21bcea5a06e628755d14a70736157f1f Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 29 Mar 2016 09:23:36 -0700 Subject: [PATCH 225/587] Spec 25616: for publications, use scope of "All" for searching since most public publications will not have permissions container set (or it may be set to the home container) --- src/org/labkey/trialshare/TrialShareController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 50610d2a..30d08127 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -294,7 +294,7 @@ else if (objectName.equalsIgnoreCase("publications")) bean.setIsDefault(isDefault); bean.setSubsetLevelName("[Publication.Status].[Status]"); bean.setSearchCategory(TrialShareModule.searchCategoryPublication.getName()); - bean.setSearchScope("Project"); + bean.setSearchScope("All"); bean.setHasContainerFilter(false); } From 46fb29520c9d75efc6941555f395d26259e463ee Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 1 Apr 2016 08:49:56 -0700 Subject: [PATCH 226/587] Spec 25952: objectName from plural to singular --- src/org/labkey/trialshare/TrialShareController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 30d08127..71df2fba 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -249,15 +249,15 @@ public static FinderBean getFinderBean(Container container, String objectName) { FinderBean bean = new FinderBean(); bean.setDataModuleName(TrialShareModule.NAME); - bean.addCubeConfig(getCubeConfigBean("studies", container, "studies".equalsIgnoreCase(objectName))); - bean.addCubeConfig(getCubeConfigBean("publications", container, "publications".equalsIgnoreCase(objectName))); + bean.addCubeConfig(getCubeConfigBean("study", container, "study".equalsIgnoreCase(objectName))); + bean.addCubeConfig(getCubeConfigBean("publication", container, "publication".equalsIgnoreCase(objectName))); return bean; } public static CubeConfigBean getCubeConfigBean(String objectName, Container container, Boolean isDefault) { if (objectName == null) - objectName = "studies"; + objectName = "study"; CubeConfigBean bean = new CubeConfigBean(); bean.setSchemaName("lists"); @@ -267,7 +267,7 @@ public static CubeConfigBean getCubeConfigBean(String objectName, Container cont Module trialShareModule = ModuleLoader.getInstance().getModule(TrialShareModule.NAME); bean.setCubeContainer(((TrialShareModule) trialShareModule).getCubeContainer(container)); - if (objectName.equalsIgnoreCase("studies")) + if (objectName.equalsIgnoreCase("study")) { bean.setObjectName("Study"); bean.setObjectNamePlural("Studies"); @@ -282,7 +282,7 @@ public static CubeConfigBean getCubeConfigBean(String objectName, Container cont bean.setSearchScope("Project"); bean.setHasContainerFilter(true); } - else if (objectName.equalsIgnoreCase("publications")) + else if (objectName.equalsIgnoreCase("publication")) { bean.setObjectName("Publication"); bean.setObjectNamePlural("Publications"); From c0c400c68207981c17c2caf1ed32960ee7b74fdc Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 1 Apr 2016 09:13:17 -0700 Subject: [PATCH 227/587] Spec 25952: add loading mask and hide summary and facets until counts are updated --- resources/web/study/Finder/data/Facets.js | 5 ++--- .../web/study/Finder/panel/FacetSelection.js | 19 +++++++++++++++---- .../web/study/Finder/panel/FacetsGrid.js | 9 ++++++++- resources/web/study/Finder/panel/Finder.js | 18 ++++++++++++++++-- .../web/study/Finder/panel/FinderCard.js | 2 +- .../web/study/Finder/panel/FinderCardDeck.js | 2 +- .../study/Finder/panel/PublicationCards.js | 2 ++ .../study/Finder/panel/PublicationSummary.js | 2 +- .../web/study/Finder/panel/StudyCards.js | 2 ++ 9 files changed, 48 insertions(+), 13 deletions(-) diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index fe759ad5..ebe5d9b2 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -273,7 +273,6 @@ Ext4.define('LABKEY.study.store.Facets', { success: function (cellSet, mdx, config) { this.updateCountsUnion(cellSet); - //this.fireEvent("cubeReady"); }, scope: this, @@ -374,8 +373,8 @@ Ext4.define('LABKEY.study.store.Facets', { //this.updateContainerFilter(); //if (!isSavedGroup) // this.changeSubjectGroup(); - - + + this.fireEvent("countsUpdated"); LABKEY.Utils.signalWebDriverTest('dataFinder' + this.cubeConfig.objectName + 'CountsUpdated'); }, diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index 2ed64934..96c34e5b 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -19,7 +19,8 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { cls: 'labkey-facet-selection-panel', bubbleEvents: [ - "clearAllFilters" + "clearAllFilters", + "countsUpdated" ], autoScroll: false, @@ -39,8 +40,10 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { this.on({ filterSelectionChanged: this.onFilterSelectionChange, searchTermsChanged: this.onFilterSelectionChange, - clearAllFilters: this.onClearAllFilters + clearAllFilters: this.onClearAllFilters, + // countsUpdated: this.onCountsUpdated }); + this.on("countsUpdated", this.onCountsUpdated, this, {single: true}); }, onCubeReady: function(mdx) { @@ -55,6 +58,11 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { this.getFacets().onSubsetChanged(); }, + onCountsUpdated: function() { + this.getFacetSelectionSummary().show(); + this.getFacetsContainer().show(); + }, + onSearchTermsChanged: function(terms) { if (this.hasFilters) @@ -90,14 +98,16 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { { this.facetSelectionSummary = Ext4.create("LABKEY.study.panel.StudySummary", { dataModuleName: this.dataModuleName, - objectName: this.cubeConfig.objectName + objectName: this.cubeConfig.objectName, + hidden: true }); } else { this.facetSelectionSummary = Ext4.create("LABKEY.study.panel.PublicationSummary", { dataModuleName: this.dataModuleName, - objectName: this.cubeConfig.objectName + objectName: this.cubeConfig.objectName, + hidden: true }); } } @@ -121,6 +131,7 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { itemId: 'facetsContainer', flex: 10, autoScroll: true, + hidden: true, layout: { type: 'vbox', align: 'stretch', diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 8c6836cf..f6cde960 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -17,7 +17,7 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { autoScroll: false, - bubbleEvents : ["filterSelectionChanged"], + bubbleEvents : ["filterSelectionChanged", "countsUpdated"], viewConfig : { stripeRows : false }, @@ -121,8 +121,15 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { this.facetStore.selectMembers(records); facetChangeTask.delay(500); }, this); + + this.facetStore.on( + 'countsUpdated' , this.onCountsUpdated, this + ) }, + onCountsUpdated : function() { + this.fireEvent("countsUpdated"); + }, getGroupHeaderFeature: function(objectName) { return Ext4.create('Ext.grid.feature.Grouping', diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index 5c856315..43256558 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -29,10 +29,13 @@ Ext4.define('LABKEY.study.panel.Finder', { this.callParent(); this._initResize(); - + this.on({ - finderObjectChanged: this.updateFinderObject + finderObjectChanged: this.updateFinderObject, + render : this.mask, }); + + this.on("countsUpdated", this.unmask, this, {single: true}); }, createCubeConfigStore : function(cubeConfigs) { @@ -85,6 +88,17 @@ Ext4.define('LABKEY.study.panel.Finder', { updateFinderObject : function(objectName) { this.getFinderCardDeck().getLayout().setActiveItem(objectName + '-finder-card'); + }, + + mask : function() { + this.getEl().mask("Loading study and publication data ..."); + }, + + unmask : function() { + if (this.getEl().isMasked()) + { + this.getEl().unmask() + } } }); diff --git a/resources/web/study/Finder/panel/FinderCard.js b/resources/web/study/Finder/panel/FinderCard.js index ce67cee1..266a9f5e 100644 --- a/resources/web/study/Finder/panel/FinderCard.js +++ b/resources/web/study/Finder/panel/FinderCard.js @@ -22,7 +22,7 @@ Ext4.define('LABKEY.study.panel.FinderCard', { searchTerms : '', - bubbleEvents : ['detailsChange'], + bubbleEvents : ['detailsChange','countsUpdated'], initComponent : function() { diff --git a/resources/web/study/Finder/panel/FinderCardDeck.js b/resources/web/study/Finder/panel/FinderCardDeck.js index 4085279a..9bb4fefe 100644 --- a/resources/web/study/Finder/panel/FinderCardDeck.js +++ b/resources/web/study/Finder/panel/FinderCardDeck.js @@ -14,7 +14,7 @@ Ext4.define('LABKEY.study.panel.FinderCardDeck', { border: false, - bubbleEvents: ['detailsChange'], + bubbleEvents: ['detailsChange', 'countsUpdated'], initComponent : function() { diff --git a/resources/web/study/Finder/panel/PublicationCards.js b/resources/web/study/Finder/panel/PublicationCards.js index d6a3c68d..d4979206 100644 --- a/resources/web/study/Finder/panel/PublicationCards.js +++ b/resources/web/study/Finder/panel/PublicationCards.js @@ -19,6 +19,8 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { autoScroll: true, + loadMask: false, + bubbleEvents: ["detailsChange"], store: Ext4.create('LABKEY.study.store.CubeObjects', { diff --git a/resources/web/study/Finder/panel/PublicationSummary.js b/resources/web/study/Finder/panel/PublicationSummary.js index 3d87fc00..6f824a9f 100644 --- a/resources/web/study/Finder/panel/PublicationSummary.js +++ b/resources/web/study/Finder/panel/PublicationSummary.js @@ -9,7 +9,7 @@ Ext4.define("LABKEY.study.panel.PublicationSummary", { alias : 'widget.facet-publication-summary', objectName: null, - + tpl: new Ext4.XTemplate( '
          ', '
          ', diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index af4c7fc6..72bf7221 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -18,6 +18,8 @@ Ext4.define("LABKEY.study.panel.StudyCards", { itemSelector: 'div.labkey-study-card', autoScroll: true, + + loadMask: false, dataModuleName: 'study', From ab0440ba07b1971b12ccb24063a9ae4b6a6fb8f3 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 4 Apr 2016 07:37:35 -0700 Subject: [PATCH 228/587] Incorporate enabling of search in data finder --- .../trialshare/PublicationDocumentProvider.java | 2 +- src/org/labkey/trialshare/StudyDocumentProvider.java | 2 +- src/org/labkey/trialshare/view/dataFinder.jsp | 9 ++++++--- src/org/labkey/trialshare/view/publicationDetail.jsp | 11 +++++++---- src/org/labkey/trialshare/view/studyDetail.jsp | 11 +++++++---- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index 988d4ddb..f8726753 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -123,7 +123,7 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container if (results.getString(field) != null) identifiers.append(results.getString(field)).append(" " ); } - properties.put(SearchService.PROPERTY.identifiersMed.toString(), identifiers.toString()); + properties.put(SearchService.PROPERTY.indentifiersMed.toString(), identifiers.toString()); properties.put(SearchService.PROPERTY.keywordsMed.toString(), keywords.toString()); properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryPublication.getName()); diff --git a/src/org/labkey/trialshare/StudyDocumentProvider.java b/src/org/labkey/trialshare/StudyDocumentProvider.java index c957df0f..bd7a4c5e 100644 --- a/src/org/labkey/trialshare/StudyDocumentProvider.java +++ b/src/org/labkey/trialshare/StudyDocumentProvider.java @@ -106,7 +106,7 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container keywords.append(results.getString(field)).append(" "); } - properties.put(SearchService.PROPERTY.identifiersMed.toString(), results.getString("StudyId")); + properties.put(SearchService.PROPERTY.indentifiersMed.toString(), results.getString("StudyId")); properties.put(SearchService.PROPERTY.keywordsMed.toString(), keywords.toString()); properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryStudy.getName()); diff --git a/src/org/labkey/trialshare/view/dataFinder.jsp b/src/org/labkey/trialshare/view/dataFinder.jsp index de748123..a245e3c9 100644 --- a/src/org/labkey/trialshare/view/dataFinder.jsp +++ b/src/org/labkey/trialshare/view/dataFinder.jsp @@ -18,14 +18,17 @@ <%@ page import="com.fasterxml.jackson.databind.ObjectMapper" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> <%@ page import="org.labkey.trialshare.TrialShareController" %> +<%@ page import="org.labkey.api.view.template.ClientDependency" %> +<%@ page import="java.util.LinkedHashSet" %> <%@ page extends="org.labkey.api.jsp.JspBase"%> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%! - public void addClientDependencies(ClientDependencies dependencies) + public LinkedHashSet getClientDependencies() { - dependencies.add("study/Finder/datafinder"); + LinkedHashSet resources = new LinkedHashSet<>(); + resources.add(ClientDependency.fromPath("study/Finder/datafinder")); + return resources; } %> <% diff --git a/src/org/labkey/trialshare/view/publicationDetail.jsp b/src/org/labkey/trialshare/view/publicationDetail.jsp index 707d1877..ca628ffc 100644 --- a/src/org/labkey/trialshare/view/publicationDetail.jsp +++ b/src/org/labkey/trialshare/view/publicationDetail.jsp @@ -20,18 +20,21 @@ <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.api.view.ViewContext" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> <%@ page import="org.labkey.trialshare.data.StudyBean" %> <%@ page import="org.labkey.trialshare.data.StudyPublicationBean" %> <%@ page import="org.labkey.trialshare.data.URLData" %> <%@ page import="java.util.HashMap" %> <%@ page import="java.util.Map" %> +<%@ page import="org.labkey.api.view.template.ClientDependency" %> +<%@ page import="java.util.LinkedHashSet" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <%! - public void addClientDependencies(ClientDependencies dependencies) + public LinkedHashSet getClientDependencies() { - dependencies.add("study/Finder/dataFinder.css"); - dependencies.add("study/Finder/trialShare.css"); + LinkedHashSet resources = new LinkedHashSet<>(); + resources.add(ClientDependency.fromPath("study/Finder/dataFinder.css")); + resources.add(ClientDependency.fromPath("study/Finder/trialShare.css")); + return resources; } %> <% diff --git a/src/org/labkey/trialshare/view/studyDetail.jsp b/src/org/labkey/trialshare/view/studyDetail.jsp index 8645c481..9e24ca6e 100644 --- a/src/org/labkey/trialshare/view/studyDetail.jsp +++ b/src/org/labkey/trialshare/view/studyDetail.jsp @@ -20,7 +20,6 @@ <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.api.view.ViewContext" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> <%@ page import="org.labkey.trialshare.TrialShareController" %> <%@ page import="org.labkey.trialshare.data.StudyBean" %> <%@ page import="org.labkey.trialshare.data.StudyPersonnelBean" %> @@ -29,12 +28,16 @@ <%@ page import="java.net.URL" %> <%@ page import="java.util.HashMap" %> <%@ page import="java.util.Map" %> +<%@ page import="org.labkey.api.view.template.ClientDependency" %> +<%@ page import="java.util.LinkedHashSet" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <%! - public void addClientDependencies(ClientDependencies dependencies) + public LinkedHashSet getClientDependencies() { - dependencies.add("study/Finder/dataFinder.css"); - dependencies.add("study/Finder/trialShare.css"); + LinkedHashSet resources = new LinkedHashSet<>(); + resources.add(ClientDependency.fromPath("study/Finder/dataFinder.css")); + resources.add(ClientDependency.fromPath("study/Finder/trialShare.css")); + return resources; } %> <% From a0d378b3e237b74fa454c4b92fde63c00e0268f9 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 4 Apr 2016 13:28:34 -0700 Subject: [PATCH 229/587] Spec 25952:prevent NPE if cube configuration seting is invalid --- src/org/labkey/trialshare/query/TrialShareQuerySchema.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index fa0ede56..9b89eabd 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -58,6 +58,8 @@ public void setSchema(User user, Container container) public static QuerySchema getSchema(User user, Container container) { Container cubeContainer = ((TrialShareModule) ModuleLoader.getInstance().getModule(TrialShareModule.NAME)).getCubeContainer(container); + if (cubeContainer == null) + cubeContainer = container; QuerySchema coreSchema = DefaultSchema.get(user, cubeContainer).getSchema("core"); return coreSchema.getSchema("lists"); } From ef2efb59fa6ffe7fdb04610c918ac38b789b0865 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 4 Apr 2016 15:26:08 -0700 Subject: [PATCH 230/587] Spec 25952: show total count in facet grid and do not adjust size of percentage bars; remove extraneous scroll bars; --- .../web/study/Finder/data/CubeObjects.js | 1 + resources/web/study/Finder/data/Facet.js | 4 +-- .../web/study/Finder/data/FacetMember.js | 4 ++- resources/web/study/Finder/data/Facets.js | 27 ++++++++++++++----- resources/web/study/Finder/dataFinder.css | 20 ++++++++++---- .../web/study/Finder/panel/FacetsGrid.js | 5 +++- resources/web/study/Finder/panel/Finder.js | 2 -- .../web/study/Finder/panel/FinderCard.js | 2 -- .../study/Finder/panel/PublicationCards.js | 1 - .../test/pages/trialshare/DataFinderPage.java | 6 ++--- .../trialshare/TrialShareDataFinderTest.java | 12 ++++----- 11 files changed, 54 insertions(+), 30 deletions(-) diff --git a/resources/web/study/Finder/data/CubeObjects.js b/resources/web/study/Finder/data/CubeObjects.js index ff697442..02b44884 100644 --- a/resources/web/study/Finder/data/CubeObjects.js +++ b/resources/web/study/Finder/data/CubeObjects.js @@ -40,6 +40,7 @@ Ext4.define('LABKEY.study.store.CubeObjects', { this.suspendEvents(false); this.clearFilter(); + this.unfilteredCount = this.count(); for (var i = 0; i < this.count(); i++) { object = this.getAt(i); object.set({ diff --git a/resources/web/study/Finder/data/Facet.js b/resources/web/study/Finder/data/Facet.js index 101aedf9..f8704624 100644 --- a/resources/web/study/Finder/data/Facet.js +++ b/resources/web/study/Finder/data/Facet.js @@ -16,11 +16,11 @@ Ext4.define('LABKEY.study.data.Facet', { {name: 'filterOptions'}, {name: 'currentFilterType'}, {name: 'currentFilterCaption'}, - {name: 'allMemberCount', type:'int', defaultValue: 0}, // set but not currently used + {name: 'allMemberCount', type:'int'}, // not currently used {name: 'hierarchy'}, {name: 'hierarchyName'}, {name: 'levelName'}, - {name: 'allMemberName'}, + {name: 'allMemberName'}, // not currently used {name: 'ordinal'}, {name: 'isExpanded', type:'boolean', defaultValue: true}, {name: 'displayFacet', type:'boolean', defaultValue: true} diff --git a/resources/web/study/Finder/data/FacetMember.js b/resources/web/study/Finder/data/FacetMember.js index a8fab44c..8199988a 100644 --- a/resources/web/study/Finder/data/FacetMember.js +++ b/resources/web/study/Finder/data/FacetMember.js @@ -13,9 +13,11 @@ Ext4.define('LABKEY.study.data.FacetMember', { {name: 'uniqueName'}, {name: 'count'}, {name: 'percent'}, + {name: 'unfilteredPercent'}, {name: 'facet'}, {name: 'facetName'}, - {name: 'level'} + {name: 'level'}, + {name: 'unfilteredCount', type: 'int', defaultValue: 0} ] }); diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index ebe5d9b2..3979f90c 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -304,6 +304,7 @@ Ext4.define('LABKEY.study.store.Facets', { var map = {}; var facetStore = this; var facetMembersStore = Ext4.getStore(this.cubeConfig.objectName + "FacetMembers"); + var objectStore = Ext4.getStore(this.cubeConfig.objectName); facetMembersStore.suspendEvents(false); for (f = 0; f < facetStore.count(); f++) @@ -335,10 +336,6 @@ Ext4.define('LABKEY.study.store.Facets', { } else if (!member) { - // might be an all member - //if (facet.data.allMemberName == resultMember.uniqueName) - // facet.data.allMemberCount = count; - //else if (-1 == resultMember.uniqueName.indexOf("#") && "(All)" != resultMember.name) console.log("member not found: " + resultMember.uniqueName); } @@ -347,8 +344,9 @@ Ext4.define('LABKEY.study.store.Facets', { member.set("count", count); if (count > max) max = count; + if (!member.data.unfilteredCount) + member.set("unfilteredCount", count); } - } for (f = 0; f < facetStore.count(); f++) @@ -356,12 +354,27 @@ Ext4.define('LABKEY.study.store.Facets', { facet = facetStore.getAt(f); if (facet.data.hierarchy.uniqueName !== this.cubeConfig.filterByFacetUniqueName) { + var facetTotal = 0; for (m = 0; m < facet.data.members.length; m++) { member = facetMembersStore.getById(facet.data.members[m].uniqueName); - member.set("percent", max == 0 ? 0 : (100.0 * member.data.count) / max); + if (facet.get("displayFacet")) + { + if (objectStore) + { + member.set("unfilteredPercent", objectStore.unfilteredCount == 0 ? 0 : 100 * member.data.unfilteredCount / objectStore.unfilteredCount); + member.set("percent", objectStore.unfilteredCount == 0 ? 0 : (100.0 * member.data.count) / objectStore.unfilteredCount); + } + else // not sure this is necessary + { + member.set("unfilteredPercent", 100 * member.data.unfilteredCount / max); + member.set("percent", max == 0 ? 0 : (100.0 * member.data.count) / max); + } + } } } + if (!facet.data.allMemberCount) + facet.set("allMemberCount", facetTotal); } facetMembersStore.resumeEvents(); @@ -373,7 +386,7 @@ Ext4.define('LABKEY.study.store.Facets', { //this.updateContainerFilter(); //if (!isSavedGroup) // this.changeSubjectGroup(); - + this.fireEvent("countsUpdated"); LABKEY.Utils.signalWebDriverTest('dataFinder' + this.cubeConfig.objectName + 'CountsUpdated'); }, diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index ecd43a4d..b110f168 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -327,6 +327,7 @@ DIV.labkey-facet-summary background-image: none; } +.labkey-finder-facets .x4-grid-row-selected .labkey-facet-unfilteredPercent-bar, .labkey-finder-facets .x4-grid-row-selected .labkey-facet-percent-bar { background-color: rgba(81, 158, 218, 0.2); } @@ -386,6 +387,7 @@ DIV.labkey-facet-summary LI.labkey-facet-member padding-left: 8px; } +SPAN.labkey-facet-member:hover SPAN.labkey-facet-unfilteredPercent-bar, SPAN.labkey-facet-member:hover SPAN.labkey-facet-percent-bar { background-color:rgba(81, 158, 218, 0.2); @@ -428,9 +430,8 @@ LI.labkey-facet-member .labkey-facet-member-count padding-right: 8px; } -.x4-grid-row-selected SPAN.labkey-facet-percent-bar, -SPAN.labkey-facet-member SPAN.bar-selected, -LI.labkey-facet-member SPAN.bar-selected +.x4-grid-row-selected SPAN.labkey-facet-unfilteredPercent-bar, +.x4-grid-row-selected SPAN.labkey-facet-percent-bar { background-color:rgba(81, 158, 218, 0.2); } @@ -452,15 +453,24 @@ LI.labkey-facet-member SPAN.bar-selected .x4-grid-row SPAN.labkey-facet-percent-bar, -LI.labkey-facet-member .bar +.x4-grid-row SPAN.labkey-facet-unfilteredPercent-bar { height:14pt; - background-color:rgba(222,222,222,0.3); position:absolute; right:0; z-index:0; padding-right: 8px; } +.x4-grid-row SPAN.labkey-facet-percent-bar +{ + background-color:rgba(200,200,200,0.3); +} + +.x4-grid-row SPAN.labkey-facet-unfilteredPercent-bar +{ + background-color:rgba(222,222,222,0.3); +} + /* filter type popup */ DIV.labkey-filter-popup { diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index f6cde960..834b85d3 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -49,7 +49,10 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { ' ', ' ', ' {name}', - ' {count:this.formatNumber}', + ' {count:this.formatNumber} of {unfilteredCount:this.formatNumber}', + ' ', + ' ', + ' ', ' ', ' ', ' ', diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index 43256558..65d6631a 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -16,8 +16,6 @@ Ext4.define('LABKEY.study.panel.Finder', { border: false, - autoScroll : true, - searchTerms : '', initComponent : function() diff --git a/resources/web/study/Finder/panel/FinderCard.js b/resources/web/study/Finder/panel/FinderCard.js index 266a9f5e..a523fadf 100644 --- a/resources/web/study/Finder/panel/FinderCard.js +++ b/resources/web/study/Finder/panel/FinderCard.js @@ -18,8 +18,6 @@ Ext4.define('LABKEY.study.panel.FinderCard', { dataModuleName: null, // the module responsible for serving up the cube data - autoScroll : true, - searchTerms : '', bubbleEvents : ['detailsChange','countsUpdated'], diff --git a/resources/web/study/Finder/panel/PublicationCards.js b/resources/web/study/Finder/panel/PublicationCards.js index d4979206..72b5ecf8 100644 --- a/resources/web/study/Finder/panel/PublicationCards.js +++ b/resources/web/study/Finder/panel/PublicationCards.js @@ -159,7 +159,6 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { if (o.success) { item.className = item.className.replace("collapsed", "expanded"); // change the +/- icon - console.log(o.data); publication.set(o.data); publication.set("viewState", "expanded"); } diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java index 0b6a4768..2c5f61c6 100644 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java @@ -493,14 +493,14 @@ public List getSelectedMembers(Dimension dimension) return selectedNames; } - public Map getMemberCounts(Dimension dimension) + public Map getMemberCounts(Dimension dimension) { List memberElements = locators.facetMembers(dimension).findElements(getDriver()); - Map countMap = new HashMap<>(); + Map countMap = new HashMap<>(); for (WebElement member : memberElements) { String name = locators.memberName.findElement(member).getText(); - Integer count = Integer.valueOf(locators.memberCount.findElement(member).getText()); + String count = locators.memberCount.findElement(member).getText(); countMap.put(name, count); } return countMap; diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 9da475b3..e5143077 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -423,10 +423,10 @@ public void testSelectingEmptyMeasure() { if (dimension.getHierarchyName() != null) { - Map memberCounts = facets.getMemberCounts(dimension); - for (Map.Entry memberCount : memberCounts.entrySet()) + Map memberCounts = facets.getMemberCounts(dimension); + for (Map.Entry memberCount : memberCounts.entrySet()) { - assertEquals("Wrong counts for member " + memberCount.getKey() + " of dimension " + dimension + " after selecting empty measure", 0, memberCount.getValue().intValue()); + assertTrue("Wrong counts for member " + memberCount.getKey() + " of dimension " + dimension + " after selecting empty measure: " + memberCount.getValue(), memberCount.getValue().matches("0 of \\d+")); } } } @@ -764,7 +764,7 @@ public void testFilterOnStatus() String cardTitle = "Circulating markers of vascular injury"; String cardAuthors = "Monach PA, Tomasson G, Specks U, et al."; String cardText; - Map counts; + Map counts; log("Go to publications and clear any filters that may have been set."); DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); @@ -776,7 +776,7 @@ public void testFilterOnStatus() log("Validate that the number, content and style of the cards is as expected."); counts = fg.getMemberCounts(DataFinderPage.Dimension.IN_PROGRESS); - assertEquals("Expected count after filtering for 'In Progress' was not as expected.", 1, counts.get("In Progress").intValue()); + assertEquals("Expected count after filtering for 'In Progress' was not as expected.", "1 of 1", counts.get("In Progress")); // I have no idea why assertTextPresent returned false for these strings. The below tests appear to be more reliable. scrollIntoView(DataFinderPage.Locators.pubCardHighlight); @@ -796,7 +796,7 @@ public void testFilterOnStatus() log("Validate counts for 'Complete' publications."); counts = fg.getMemberCounts(DataFinderPage.Dimension.COMPLETE); // one is "in progress" and one is set to not show - assertEquals("Expected count after filtering for 'Complete' was not as expected.", 15, counts.get("Complete").intValue()); + assertEquals("Expected count after filtering for 'Complete' was not as expected.", "15 of 15", counts.get("Complete")); log("Validate that there are no 'In Progress' cards visible."); assertElementNotPresent("There is a card with the 'In Progress' style, there should not be.", DataFinderPage.Locators.pubCardHighlight); From 4aedc479de79ec953351103e137b8453b94aade0 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 7 Apr 2016 09:17:31 -0700 Subject: [PATCH 231/587] Don't resize webpart when publication details are expanded --- .../web/study/Finder/panel/CubeObjects.js | 21 +++++++++---------- resources/web/study/Finder/panel/Finder.js | 9 +------- .../study/Finder/panel/PublicationCards.js | 4 +--- .../web/study/Finder/panel/StudyCards.js | 3 --- 4 files changed, 12 insertions(+), 25 deletions(-) diff --git a/resources/web/study/Finder/panel/CubeObjects.js b/resources/web/study/Finder/panel/CubeObjects.js index f8c14003..0a871c14 100644 --- a/resources/web/study/Finder/panel/CubeObjects.js +++ b/resources/web/study/Finder/panel/CubeObjects.js @@ -13,8 +13,6 @@ Ext4.define("LABKEY.study.panel.CubeObjects", { border: false, - autoScroll: true, - initComponent : function() { this.items = [ this.getCardPanelHeader(), @@ -25,6 +23,7 @@ Ext4.define("LABKEY.study.panel.CubeObjects", { var searchTermsChangeTask = new Ext4.util.DelayedTask(); this.on({ + 'resize' : this.updateCardContainerHeight, 'subsetChanged': this.onSubsetChanged, 'searchTermsChanged': function(searchTerms){ searchTermsChangeTask.delay(350, this.onSearchTermsChanged, this, [searchTerms]); @@ -118,22 +117,22 @@ Ext4.define("LABKEY.study.panel.CubeObjects", { return this.cardPanelHeader; }, + updateCardContainerHeight: function() { + this.getCardsContainer().setHeight(this.getHeight() - this.getCardPanelHeader().getHeight()); + }, + getCardsContainer : function() { if (!this.facetsContainer) { - this.facetsContainer = { - xtype: 'container', + this.facetsContainer = Ext4.create('Ext.panel.Panel', { itemId: this.cubeConfig.objectName.toLowerCase() + 'CardsContainer', - flex: 10, autoScroll: true, - layout: { - type: 'vbox', - align: 'stretch', - pack: 'start' - }, + border: false, items: [ this.getCards() ] - }; + }); + this.facetsContainer.on("afterlayout", this.updateCardContainerHeight, this, {single: true}); + } return this.facetsContainer; }, diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index 65d6631a..0ae3ff09 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -30,7 +30,7 @@ Ext4.define('LABKEY.study.panel.Finder', { this.on({ finderObjectChanged: this.updateFinderObject, - render : this.mask, + render : this.mask }); this.on("countsUpdated", this.unmask, this, {single: true}); @@ -74,13 +74,6 @@ Ext4.define('LABKEY.study.panel.Finder', { resize.call(this, size.width, size.height); }, 300, this); }); - - this.on('detailsChange', function() { - Ext4.defer(function() { - var size = Ext4.getBody().getBox(); - resize.call(this, size.width, size.height); - }, 300, this); - }) }, updateFinderObject : function(objectName) diff --git a/resources/web/study/Finder/panel/PublicationCards.js b/resources/web/study/Finder/panel/PublicationCards.js index 72b5ecf8..58e31b86 100644 --- a/resources/web/study/Finder/panel/PublicationCards.js +++ b/resources/web/study/Finder/panel/PublicationCards.js @@ -17,8 +17,6 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { itemSelector: 'div.labkey-publication-card', - autoScroll: true, - loadMask: false, bubbleEvents: ["detailsChange"], @@ -145,7 +143,7 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { { var publication = this.store.getById(publicationId); - + if (expand) { var url = LABKEY.ActionURL.buildURL(this.dataModuleName, "publicationDetails.api", null, { diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index 72bf7221..34271119 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -16,8 +16,6 @@ Ext4.define("LABKEY.study.panel.StudyCards", { width: "100%", itemSelector: 'div.labkey-study-card', - - autoScroll: true, loadMask: false, @@ -36,7 +34,6 @@ Ext4.define("LABKEY.study.panel.StudyCards", { }] }), - tpl: new Ext4.XTemplate( '
          ', ' ', From eca26c2bb18bd012775a4f8861e276a688d72964 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 7 Apr 2016 14:19:13 -0700 Subject: [PATCH 232/587] Fix bug for publications searching that did not filter facets properly --- resources/web/study/Finder/data/Facets.js | 62 ++++++++++--------- .../web/study/Finder/panel/CubeObjects.js | 5 +- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index 3979f90c..0b66f0df 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -102,7 +102,7 @@ Ext4.define('LABKEY.study.store.Facets', { return optionsStore.getAt(0); }, - getStudySubsetFilter: function() { + getObjectSubsetFilter: function() { var store = Ext4.getStore(this.cubeConfig.objectName); if (!store || !store.selectedSubset) @@ -113,7 +113,7 @@ Ext4.define('LABKEY.study.store.Facets', { }, getFiltersForCountDistinct: function(filtersMap) { - this.addFilterMapData(filtersMap, this.getStudySubsetFilter()); + this.addFilterMapData(filtersMap, this.getObjectSubsetFilter()); this.addFilterMapData(filtersMap, this.getSearchFilter()); this.addFilterMapData(filtersMap, this.getCustomFilters()); var filters = []; @@ -177,38 +177,44 @@ Ext4.define('LABKEY.study.store.Facets', { return; } - // console.log("updateCountsAsync called"); - var url = LABKEY.ActionURL.buildURL(this.dataModuleName, "accessibleMembers.api", null, { - "objectName": this.cubeConfig.objectName - }); - Ext4.Ajax.request({ - url: url, - success: function (response) - { - var o = Ext4.decode(response.responseText); - if (o.success) + var store = Ext4.getStore(this.cubeConfig.objectName); + if (store.searchSelectedMembers != null) + this.makeCountDistinctQuery({}); + else + { + // console.log("updateCountsAsync called"); + var url = LABKEY.ActionURL.buildURL(this.dataModuleName, "accessibleMembers.api", null, { + "objectName": this.cubeConfig.objectName + }); + Ext4.Ajax.request({ + url: url, + success: function (response) { - var filters = {}; - for (var level in o.data) + var o = Ext4.decode(response.responseText); + if (o.success) { - if (o.data[level].length) + var filters = {}; + for (var level in o.data) { - if (!filters[level]) - filters[level] = o.data[level]; - else - filters[level] = filters[level].concat(o.data[level]); + if (o.data[level].length) + { + if (!filters[level]) + filters[level] = o.data[level]; + else + filters[level] = filters[level].concat(o.data[level]); + } } + this.makeCountDistinctQuery(filters); + } + else + { + console.log("Problem making request for accessible members", o); } - this.makeCountDistinctQuery(filters); - } - else - { - console.log("Problem making request for accessible members", o); - } - }, - scope: this - }); + }, + scope: this + }); + } }, makeCountDistinctQuery: function(filtersMap) diff --git a/resources/web/study/Finder/panel/CubeObjects.js b/resources/web/study/Finder/panel/CubeObjects.js index 0a871c14..856ae596 100644 --- a/resources/web/study/Finder/panel/CubeObjects.js +++ b/resources/web/study/Finder/panel/CubeObjects.js @@ -43,16 +43,17 @@ Ext4.define("LABKEY.study.panel.CubeObjects", { onSearchTermsChanged : function(searchTerms) { // console.log("search terms changed to " + searchTerms); - this.searchTerms = searchTerms; if (!searchTerms) { + this.searchTerms = null; this.updateSearchFilters(null); return; } + this.searchTerms = searchTerms.trim(); var url = LABKEY.ActionURL.buildURL("search", "json", this.cubeConfig.cubeContainerPath, { "category": this.cubeConfig.searchCategory, - "q": searchTerms, + "q": this.searchTerms, "scope" : this.cubeConfig.searchScope }); Ext4.Ajax.request({ From a481427b8843c69ce5da562e67d9396eefdb3ffc Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 11 Apr 2016 08:01:44 -0700 Subject: [PATCH 233/587] Featured facet should not allow for "And"; add constants for publication and study --- .../web/study/Finder/panel/FacetSelection.js | 3 +-- .../trialshare/TrialShareController.java | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/resources/web/study/Finder/panel/FacetSelection.js b/resources/web/study/Finder/panel/FacetSelection.js index 96c34e5b..5d64df17 100644 --- a/resources/web/study/Finder/panel/FacetSelection.js +++ b/resources/web/study/Finder/panel/FacetSelection.js @@ -40,8 +40,7 @@ Ext4.define("LABKEY.study.panel.FacetSelection", { this.on({ filterSelectionChanged: this.onFilterSelectionChange, searchTermsChanged: this.onFilterSelectionChange, - clearAllFilters: this.onClearAllFilters, - // countsUpdated: this.onCountsUpdated + clearAllFilters: this.onClearAllFilters }); this.on("countsUpdated", this.onCountsUpdated, this, {single: true}); }, diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 71df2fba..86468627 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -68,6 +68,8 @@ public class TrialShareController extends SpringActionController { public static final String OBJECT_NAME_PARAM = "object"; + public static final String STUDY_OBJECT = "Study"; + public static final String PUBLICATION_OBJECT = "Publication"; private static final DefaultActionResolver _actionResolver = new DefaultActionResolver(TrialShareController.class); public static final String NAME = "trialshare"; @@ -249,15 +251,15 @@ public static FinderBean getFinderBean(Container container, String objectName) { FinderBean bean = new FinderBean(); bean.setDataModuleName(TrialShareModule.NAME); - bean.addCubeConfig(getCubeConfigBean("study", container, "study".equalsIgnoreCase(objectName))); - bean.addCubeConfig(getCubeConfigBean("publication", container, "publication".equalsIgnoreCase(objectName))); + bean.addCubeConfig(getCubeConfigBean(STUDY_OBJECT, container, STUDY_OBJECT.equalsIgnoreCase(objectName))); + bean.addCubeConfig(getCubeConfigBean(PUBLICATION_OBJECT, container, PUBLICATION_OBJECT.equalsIgnoreCase(objectName))); return bean; } public static CubeConfigBean getCubeConfigBean(String objectName, Container container, Boolean isDefault) { if (objectName == null) - objectName = "study"; + objectName = STUDY_OBJECT; CubeConfigBean bean = new CubeConfigBean(); bean.setSchemaName("lists"); @@ -267,9 +269,9 @@ public static CubeConfigBean getCubeConfigBean(String objectName, Container cont Module trialShareModule = ModuleLoader.getInstance().getModule(TrialShareModule.NAME); bean.setCubeContainer(((TrialShareModule) trialShareModule).getCubeContainer(container)); - if (objectName.equalsIgnoreCase("study")) + if (objectName.equalsIgnoreCase(STUDY_OBJECT)) { - bean.setObjectName("Study"); + bean.setObjectName(STUDY_OBJECT); bean.setObjectNamePlural("Studies"); bean.setCubeName("StudyCube"); bean.setConfigId("TrialShare:/StudyCube"); @@ -282,9 +284,9 @@ public static CubeConfigBean getCubeConfigBean(String objectName, Container cont bean.setSearchScope("Project"); bean.setHasContainerFilter(true); } - else if (objectName.equalsIgnoreCase("publication")) + else if (objectName.equalsIgnoreCase(PUBLICATION_OBJECT)) { - bean.setObjectName("Publication"); + bean.setObjectName(PUBLICATION_OBJECT); bean.setObjectNamePlural("Publications"); bean.setCubeName("PublicationCube"); bean.setConfigId("TrialShare:/PublicationCube"); @@ -715,7 +717,7 @@ public void validateForm(CubeObjectTypeForm form, Errors errors) @Override public Object execute(CubeObjectTypeForm form, BindException errors) throws Exception { - if (form.getObjectName().equalsIgnoreCase("publication")) + if (form.getObjectName().equalsIgnoreCase(PUBLICATION_OBJECT)) return success(getPublicationFacets()); else return success(getStudyFacets()); @@ -769,7 +771,7 @@ private List getPublicationFacets() facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); facets.add(facet); facet = new StudyFacetBean("Featured", "Featured", "Publication.Featured", "Featured", "[Publication.Featured][(All)]", FacetFilter.Type.OR, 3); - facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); + facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); facets.add(facet); facet = new StudyFacetBean("Therapeutic Area", "Therapeutic Areas", "Publication.Therapeutic Area", "Therapeutic Area", "[Publication.Therapeutic Area][(All)]", FacetFilter.Type.OR, 4); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); @@ -1056,7 +1058,7 @@ public Object execute(AccessibleMembersForm accessibleMembersForm, BindException { Map levelMembers = new HashMap<>(); - if (_objectName.equalsIgnoreCase("publication") ) + if (_objectName.equalsIgnoreCase(PUBLICATION_OBJECT) ) { levelMembers.put("[Publication].[Publication]", TrialShareManager.get().getVisiblePublications(getUser(), getContainer())); } From ba339c714e5cfb1e47f063f2c769d999e0e1848e Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 11 Apr 2016 12:58:24 -0700 Subject: [PATCH 234/587] Issue 26148: override getRecord and indexInStore for Ext.view.Table to get past bug in ExtJs 4.2.1 --- resources/web/study/Finder/panel/Finder.js | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index 0ae3ff09..ef483625 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -3,6 +3,28 @@ * * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 */ +/** + * @Override + * Sencha Issue: https://www.sencha.com/forum/showthread.php?265078-Broken-contracts-of-getAt-indexOf-methods-of-the-Ext.grid.feature.Grouping Version: 4.2.1 + */ +Ext4.override(Ext4.view.Table, +{ + getRecord: function (node) { + node = this.getNode(node); + if (node) { + return this.dataSource.data.get(node.getAttribute('data-recordId')); + } + }, + + indexInStore: function (node) { + node = this.getNode(node, true); + if (!node && node !== 0) { + return -1; + } + return this.dataSource.indexOf(this.getRecord(node)); + } +}); + Ext4.define('LABKEY.study.panel.Finder', { extend: 'Ext.panel.Panel', @@ -95,3 +117,4 @@ Ext4.define('LABKEY.study.panel.Finder', { }); + From 60f2258bc9e17e87c57228ad950c6b1692947988 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 12 Apr 2016 14:48:42 -0700 Subject: [PATCH 235/587] Issue 26028: Identifiers not indexed for search; Issue 26055: Study identifiers and author names are stemmed --- .../trialshare/PublicationDocumentProvider.java | 8 ++++---- src/org/labkey/trialshare/StudyDocumentProvider.java | 11 +++++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index f8726753..b6cff1d3 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -112,18 +112,18 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container StringBuilder keywords = new StringBuilder(); // See #26028: identifiers that have punctuation in them (e.g., DOI) are not indexed well as identifiers, so we use keywords instead - for (String field : new String[]{"Author", "Year", "Status", "PrimaryStudy", "Title", "PublicationType", "Journal", "TherapeuticArea", "StudyShortName", "Assay", "Condition", "DOI"}) + for (String field : new String[]{"Year", "Status", "PrimaryStudy", "Title", "PublicationType", "Journal", "TherapeuticArea", "StudyShortName", "Assay", "Condition"}) { if (results.getString(field) != null) keywords.append(results.getString(field)).append(" "); } StringBuilder identifiers = new StringBuilder(); - for (String field : new String[]{"PMID", "PMCID", "StudyId", "PrimaryStudyId"}) + for (String field : new String[]{"PMID", "PMCID", "StudyId", "PrimaryStudyId", "DOI", "Author"}) { if (results.getString(field) != null) - identifiers.append(results.getString(field)).append(" " ); + identifiers.append(results.getString(field).replaceAll(",", " ")).append(" " ); } - properties.put(SearchService.PROPERTY.indentifiersMed.toString(), identifiers.toString()); + properties.put(SearchService.PROPERTY.identifiersMed.toString(), identifiers.toString()); properties.put(SearchService.PROPERTY.keywordsMed.toString(), keywords.toString()); properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryPublication.getName()); diff --git a/src/org/labkey/trialshare/StudyDocumentProvider.java b/src/org/labkey/trialshare/StudyDocumentProvider.java index bd7a4c5e..824e6a9a 100644 --- a/src/org/labkey/trialshare/StudyDocumentProvider.java +++ b/src/org/labkey/trialshare/StudyDocumentProvider.java @@ -100,13 +100,20 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container StringBuilder keywords = new StringBuilder(); // See #26028: we want to avoid stemming of the following fields, so we use keywords instead - for (String field : new String[]{"shortName", "StudyId", "Investigator", "AgeGroup", "Assay", "Condition", "Phase", "TherapeuticArea", "StudyType"}) + for (String field : new String[]{"AgeGroup", "Assay", "Condition", "Phase", "TherapeuticArea", "StudyType"}) { if (results.getString(field) != null) keywords.append(results.getString(field)).append(" "); } - properties.put(SearchService.PROPERTY.indentifiersMed.toString(), results.getString("StudyId")); + StringBuilder identifiers = new StringBuilder(); + for (String field : new String[]{"shortName", "StudyId", "Investigator"}) + { + if (results.getString(field) != null) + identifiers.append(results.getString(field)).append(" "); + } + + properties.put(SearchService.PROPERTY.identifiersMed.toString(), identifiers.toString()); properties.put(SearchService.PROPERTY.keywordsMed.toString(), keywords.toString()); properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryStudy.getName()); From ae3278b347d6f06fb71573a7d842c80a1ed69ab0 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 12 Apr 2016 14:59:21 -0700 Subject: [PATCH 236/587] Issue 26055: make study short names index as identifiers --- src/org/labkey/trialshare/PublicationDocumentProvider.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index b6cff1d3..878ec15b 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -111,14 +111,13 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container Map properties = new HashMap<>(); StringBuilder keywords = new StringBuilder(); - // See #26028: identifiers that have punctuation in them (e.g., DOI) are not indexed well as identifiers, so we use keywords instead - for (String field : new String[]{"Year", "Status", "PrimaryStudy", "Title", "PublicationType", "Journal", "TherapeuticArea", "StudyShortName", "Assay", "Condition"}) + for (String field : new String[]{"Year", "Status", "Title", "PublicationType", "Journal", "TherapeuticArea" , "Assay", "Condition"}) { if (results.getString(field) != null) keywords.append(results.getString(field)).append(" "); } StringBuilder identifiers = new StringBuilder(); - for (String field : new String[]{"PMID", "PMCID", "StudyId", "PrimaryStudyId", "DOI", "Author"}) + for (String field : new String[]{"PMID", "PMCID", "StudyId", "PrimaryStudy", "StudyShortName", "PrimaryStudyId", "DOI", "Author"}) { if (results.getString(field) != null) identifiers.append(results.getString(field).replaceAll(",", " ")).append(" " ); From 8fa29f2e0a319bf1e5a0abc80b310a66f3ece4d0 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 12 Apr 2016 15:09:25 -0700 Subject: [PATCH 237/587] Issue 26148: remove special override in Finder.js since it is now included in ext_patches.js --- resources/web/study/Finder/panel/Finder.js | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/resources/web/study/Finder/panel/Finder.js b/resources/web/study/Finder/panel/Finder.js index ef483625..446aa55c 100644 --- a/resources/web/study/Finder/panel/Finder.js +++ b/resources/web/study/Finder/panel/Finder.js @@ -3,28 +3,6 @@ * * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 */ -/** - * @Override - * Sencha Issue: https://www.sencha.com/forum/showthread.php?265078-Broken-contracts-of-getAt-indexOf-methods-of-the-Ext.grid.feature.Grouping Version: 4.2.1 - */ -Ext4.override(Ext4.view.Table, -{ - getRecord: function (node) { - node = this.getNode(node); - if (node) { - return this.dataSource.data.get(node.getAttribute('data-recordId')); - } - }, - - indexInStore: function (node) { - node = this.getNode(node, true); - if (!node && node !== 0) { - return -1; - } - return this.dataSource.indexOf(this.getRecord(node)); - } -}); - Ext4.define('LABKEY.study.panel.Finder', { extend: 'Ext.panel.Panel', From 3f40989d90953d4d829c28dc046587e8fc32748b Mon Sep 17 00:00:00 2001 From: labkey-danield Date: Wed, 13 Apr 2016 06:59:51 -0700 Subject: [PATCH 238/587] Fixing build break in trialshare-PublicationDocumentProvider.java --- src/org/labkey/trialshare/PublicationDocumentProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index f8726753..988d4ddb 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -123,7 +123,7 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container if (results.getString(field) != null) identifiers.append(results.getString(field)).append(" " ); } - properties.put(SearchService.PROPERTY.indentifiersMed.toString(), identifiers.toString()); + properties.put(SearchService.PROPERTY.identifiersMed.toString(), identifiers.toString()); properties.put(SearchService.PROPERTY.keywordsMed.toString(), keywords.toString()); properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryPublication.getName()); From 14f1553f9a123e4f0cce2aaeec3a210c5d732ab5 Mon Sep 17 00:00:00 2001 From: labkey-danield Date: Wed, 13 Apr 2016 07:13:22 -0700 Subject: [PATCH 239/587] Fixing build break in trialshare-PublicationDocumentProvider.java --- src/org/labkey/trialshare/StudyDocumentProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/StudyDocumentProvider.java b/src/org/labkey/trialshare/StudyDocumentProvider.java index bd7a4c5e..c957df0f 100644 --- a/src/org/labkey/trialshare/StudyDocumentProvider.java +++ b/src/org/labkey/trialshare/StudyDocumentProvider.java @@ -106,7 +106,7 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container keywords.append(results.getString(field)).append(" "); } - properties.put(SearchService.PROPERTY.indentifiersMed.toString(), results.getString("StudyId")); + properties.put(SearchService.PROPERTY.identifiersMed.toString(), results.getString("StudyId")); properties.put(SearchService.PROPERTY.keywordsMed.toString(), keywords.toString()); properties.put(SearchService.PROPERTY.title.toString(), results.getString("Title")); properties.put(SearchService.PROPERTY.categories.toString(), TrialShareModule.searchCategoryStudy.getName()); From 3cf0b02d764d95c601f8b94adb312e90d7d5a0e3 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 13 Apr 2016 11:11:33 -0700 Subject: [PATCH 240/587] put override of problematic Ext.view.Table functions in our own view table and use that in the FacetsGrid --- resources/web/study/Finder/dataFinder.lib.xml | 2 + .../web/study/Finder/panel/FacetsGrid.js | 54 +++++++++++++++++++ resources/web/study/Finder/view/GridTable.js | 29 ++++++++++ 3 files changed, 85 insertions(+) create mode 100644 resources/web/study/Finder/view/GridTable.js diff --git a/resources/web/study/Finder/dataFinder.lib.xml b/resources/web/study/Finder/dataFinder.lib.xml index 0cd06c07..23d010bf 100644 --- a/resources/web/study/Finder/dataFinder.lib.xml +++ b/resources/web/study/Finder/dataFinder.lib.xml @@ -16,6 +16,8 @@ \ No newline at end of file diff --git a/src/org/labkey/trialshare/view/manageData.jsp b/src/org/labkey/trialshare/view/manageData.jsp index e3a380c2..a1d92bd8 100644 --- a/src/org/labkey/trialshare/view/manageData.jsp +++ b/src/org/labkey/trialshare/view/manageData.jsp @@ -24,8 +24,8 @@ <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%@ page extends="org.labkey.api.jsp.JspBase"%> <% - JspView me = (JspView) HttpView.currentView(); - TrialShareController.CubeObjectTypeForm bean = me.getModelBean(); + JspView me = (JspView) HttpView.currentView(); + TrialShareController.CubeObjectNameForm bean = me.getModelBean(); TrialShareController.ObjectName thisObjectName = bean.getObjectName(); ModelAndView manageObjectsView = me.getView("manageObjectsView"); From 449c6786fc77f908c6699720c65fbbf9d251cbe4 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 21 Jun 2016 14:18:02 -0700 Subject: [PATCH 286/587] Issue 26554: remove HTML encoding of abstract text to allow for markup --- resources/web/study/Finder/panel/PublicationCards.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/web/study/Finder/panel/PublicationCards.js b/resources/web/study/Finder/panel/PublicationCards.js index e54fac1c..f1453c94 100644 --- a/resources/web/study/Finder/panel/PublicationCards.js +++ b/resources/web/study/Finder/panel/PublicationCards.js @@ -112,7 +112,7 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { ' ', '
          ', ' Abstract', - ' {abstractText:htmlEncode}', + ' {abstractText}', '
          ', '
          ', ' ', From f3a8e529d95c922e1cb1289a4ecee795feab6515 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 21 Jun 2016 14:28:46 -0700 Subject: [PATCH 287/587] Spec 26558: update form for inserting publications to include most fields; add form validation --- .../lists/ManuscriptsAndAbstracts.query.xml | 14 +- .../dataFinderDetails.qview.xml | 31 ++- resources/web/study/Finder/dataFinder.css | 5 + .../Finder/panel/JunctionEditFormPanel.js | 207 ++++++++++++++++++ .../trialshare/TrialShareController.java | 90 ++++---- .../labkey/trialshare/TrialShareManager.java | 118 ++++++++-- .../trialshare/data/PublicationEditBean.java | 44 ++++ .../trialshare/data/StudyPublicationBean.java | 93 ++++---- .../query/ManageCubeObjectQueryView.java | 2 +- .../trialshare/view/insertPublication.jsp | 99 +++++++-- 10 files changed, 547 insertions(+), 156 deletions(-) create mode 100644 resources/web/study/Finder/panel/JunctionEditFormPanel.js create mode 100644 src/org/labkey/trialshare/data/PublicationEditBean.java diff --git a/resources/queries/lists/ManuscriptsAndAbstracts.query.xml b/resources/queries/lists/ManuscriptsAndAbstracts.query.xml index 7f969ff9..59c9b88c 100644 --- a/resources/queries/lists/ManuscriptsAndAbstracts.query.xml +++ b/resources/queries/lists/ManuscriptsAndAbstracts.query.xml @@ -3,9 +3,8 @@
          Cube Definitions
          <%= button("Clear Cache").onClick("confirmClear(" + qh(path) + ")") %><%= button("Clear Cache").onClick("confirmClear(" + qh(path) + ")") %> <%= button("Reindex").onClick("confirmReindex(" + qh(path) + ")")%> <%=textLink(path, ContainerManager.getForPath(path).getStartURL(getUser()))%> <%= h(StringUtils.join(definitionIds, ", ")) %>
          - - true - true + + Studies lists PublicationStudy @@ -15,6 +14,15 @@ false + + + lists + PublicationCondition + publicationId + junction + Condition + +
          diff --git a/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml b/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml index 9f1addc2..f63795ab 100644 --- a/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml +++ b/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml @@ -1,16 +1,29 @@ + - + + + + + + + + + + - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 51f737ff..dda23349 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -28,6 +28,11 @@ border-top-width: 0; } +.labkey-data-finder-editor-message +{ + padding-bottom: 20px; +} + .labkey-studies-panel, .labkey-publications-panel { diff --git a/resources/web/study/Finder/panel/JunctionEditFormPanel.js b/resources/web/study/Finder/panel/JunctionEditFormPanel.js new file mode 100644 index 00000000..c6564694 --- /dev/null +++ b/resources/web/study/Finder/panel/JunctionEditFormPanel.js @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { + extend: 'LABKEY.ext4.FormPanel', + + cls: 'labkey-data-finder-junction-editor', + + defaultFieldWidth: 350, + defaultFieldLabelWidth: 200, + + initComponent : function() { + + this.returnUrl = LABKEY.ActionURL.buildURL('trialShare', 'manageData.view', null, {objectName : this.objectName, 'query.viewName': 'manageData'}); + + this.dockedItems = [{ + xtype: 'toolbar', + dock: 'bottom', + ui: 'footer', + style: 'background-color: transparent;', + items: [ + LABKEY.ext4.FORMBUTTONS.getButton('SUBMIT'), + LABKEY.ext4.FORMBUTTONS.getButton('CANCEL', {returnURL : this.returnUrl}) + ] + }]; + this.callParent(); + }, + + shouldShowInInsertView: function(metadata) { + return this.isJoinTableField(metadata.name) || LABKEY.ext4.Util.shouldShowInUpdateView(metadata); + }, + + shouldShowInUpdateView: function(metadata) { + return this.shouldShowInInsertView(metadata); + }, + + configureJoinFields : function(store) { + var fields = LABKEY.ext4.Util.getStoreFields(this.store); + for (var i = 0; i < this.joinTableFields.length; i++) + { + var field = fields.get(this.joinTableFields[i]); + if (field) + { + field.editable = true; + field.facetingBehaviorType = "AUTOMATIC"; + field.multiSelect = true; + } + } + }, + + isJoinTableField : function(fieldName) + { + return this.joinTableFields.indexOf(fieldName) >= 0; + }, + + uncapitalize : function(name) + { + return name && name[0].toLowerCase() + name.slice(1) + }, + + /** Override **/ + configureForm: function(store){ + this.configureJoinFields(store); + var toAdd = []; + toAdd.push({ + tag: 'div', + itemId: 'messageEl', + html:'Items marked with * are required', + border: false, + cls: 'labkey-data-finder-editor-message' + }); + + LABKEY.ext4.Util.getStoreFields(store).each(function(field){ + var config = { + queryName: store.queryName, + schemaName: store.schemaName + }; + + if (this.metadataDefaults) { + Ext4.Object.merge(config, this.metadataDefaults); + } + if (this.metadata && this.metadata[field.name]) { + Ext4.Object.merge(config, this.metadata[field.name]); + } + + if (this.shouldShowInUpdateView(field)){ + + var fieldEditor = LABKEY.ext4.Util.getFormEditorConfig(field, config); + if (fieldEditor.isRequired) + fieldEditor.fieldLabel = fieldEditor.fieldLabel + " *"; + if (!fieldEditor.width) + fieldEditor.width = this.defaultFieldWidth; + if (!fieldEditor.labelWidth) + fieldEditor.labelWidth = this.defaultFieldLabelWidth; + + if (field.inputType == 'textarea' && fieldEditor.xtype == 'textarea' && !fieldEditor.height){ + Ext4.apply(fieldEditor, {width: this.defaultFieldWidth, height: 100}); + } + + if (field.inputType == "checkbox" && field.jsonType == "boolean") + { + fieldEditor.inputValue = true; + } + + if (fieldEditor.xtype == 'combo' || fieldEditor.xtype == 'labkey-combo'){ + fieldEditor.store.containerFilter = fieldEditor.containerFilter; + fieldEditor.multiSelect = field.multiSelect; + fieldEditor.store.autoLoad = true; + fieldEditor.delimiter = '; ' + } + + if (field.isAutoIncrement){ + fieldEditor.xtype = 'displayfield'; + } + + fieldEditor.name = this.uncapitalize(fieldEditor.name); // this is necessary to match the bean properties, somehow... + if (!field.compositeField) + toAdd.push(fieldEditor); + else + console.warn("Composite field encountered", field); + } + }, this); + + return toAdd; + }, + + + /** Override **/ + doSubmit: function(btn){ + btn.setDisabled(true); + + // force record to refresh based on most recent form values. this happens reliably in modern browsers, + // but IE8 sometimes wont apply changes when the cursor is still on a field + var plugin = this.getPlugin('labkey-databind'); + plugin.updateRecordFromForm(); + + if (!this.store.getNewRecords().length && !this.store.getUpdatedRecords().length && !this.store.getRemovedRecords().length){ + Ext4.Msg.alert('No changes', 'There are no changes. Nothing to do.'); + btn.setDisabled(false); + return; + } + + function onSuccess(response, options){ + this.mun(this.store, onError); + btn.setDisabled(false); + + if (!this.supressSuccessAlert) { + Ext4.Msg.alert("Success", "Your upload was successful!", function(){ + window.location = btn.successURL || LABKEY.ActionURL.buildURL('query', 'executeQuery', null, {schemaName: this.store.schemaName, 'query.queryName': this.store.queryName}) + }, this); + } + } + + function onError(response, options){ + this.mun(this.store, onSuccess); + btn.setDisabled(false); + + obj = Ext4.JSON.decode(response.responseText); + for (var i = 0; i < obj.errors.length; i++) + { + this.getForm().findField(obj.errors[i].field).markInvalid([obj.errors[i].message]); + } + } + + Ext4.Ajax.request({ + url: LABKEY.ActionURL.buildURL('trialShare', 'insert' + this.objectName + ".api"), + method: 'POST', + jsonData: this.getFieldValues(), + success: onSuccess, + failure: onError, + scope: this + }); + + }, + + getFieldValues : function() + { + var fieldValues = {}; + var metadata = this.metadata; + // getValues returns the empty string for fields that are empty, which is not what we want. + this.getForm().getFields().each(function(item) + { + var value = item.value; + if (value) + { + if (metadata[item.dataIndex] && metadata[item.dataIndex].stripNewLines) + value = value.replace(/\n/g, " "); + fieldValues[item.name] = value; + } + }); + return fieldValues; + // + // // We have to convert these to something acceptable as an arrayList + // var values = this.getValues(); + // for (var i = 0; i < this.joinTableFields.length; i++) + // { + // var value = values[this.uncapitalize(this.joinTableFields[i])]; + // if (!Ext4.isArray(value)) + // values[this.uncapitalize(this.joinTableFields[i])] = null; + // } + // + // return values; + + } +}); \ No newline at end of file diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 8cc2f2f7..c637935e 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -41,6 +41,7 @@ import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.util.PageFlowUtil; import org.labkey.api.util.Pair; +import org.labkey.api.util.ReturnURLString; import org.labkey.api.util.URLHelper; import org.labkey.api.view.ActionURL; import org.labkey.api.view.HtmlView; @@ -52,6 +53,7 @@ import org.labkey.api.view.WebPartView; import org.labkey.trialshare.data.CubeConfigBean; import org.labkey.trialshare.data.FacetFilter; +import org.labkey.trialshare.data.PublicationEditBean; import org.labkey.trialshare.data.StudyAccess; import org.labkey.trialshare.data.StudyBean; import org.labkey.trialshare.data.StudyFacetBean; @@ -299,7 +301,7 @@ public boolean doReindex() public boolean doClearCache() { - return getMethod().toLowerCase().contains("clearCache"); + return getMethod().toLowerCase().contains("clearcache"); } public void validate(Errors errors) @@ -461,10 +463,9 @@ public Object execute(Object form, BindException errors) throws Exception for (StudyPublicationBean pub : publications) { - if (pub.getShow() && pub.hasPermission(getUser())) + if (pub.getShow() != null && pub.getShow() && pub.hasPermission(getUser())) { - if (pubCounts.get(pub.getStudyId()) == null) - pubCounts.put(pub.getStudyId(), new Pair<>(0, 0)); + pubCounts.putIfAbsent(pub.getStudyId(), new Pair<>(0, 0)); Pair countPair = pubCounts.get(pub.getStudyId()); if (pub.getPublicationType() != null) if (pub.getPublicationType().equalsIgnoreCase("Manuscript")) @@ -485,10 +486,7 @@ public Object execute(Object form, BindException errors) throws Exception Container container = ContainerManager.getForId(studyAccess.getStudyContainer()); if (container != null && container.hasPermission(getUser(), ReadPermission.class)) { - if (studyAccessMap.get(studyAccess.getStudyId()) == null) - { - studyAccessMap.put(studyAccess.getStudyId(), new ArrayList<>()); - } + studyAccessMap.putIfAbsent(studyAccess.getStudyId(), new ArrayList<>()); studyAccessMap.get(studyAccess.getStudyId()).add(studyAccess); } } @@ -854,7 +852,7 @@ public Object execute(CubeObjectNameForm form, BindException errors) throws Exce } } - public static class CubeObjectNameForm + public static class CubeObjectNameForm extends ReturnUrlForm { private ObjectName _objectName = null; @@ -975,7 +973,7 @@ public NavTree appendNavTrail(NavTree root) private ActionURL getManageDataUrl(ObjectName name) { - return new ActionURL(ManageDataAction.class, getContainer()).addParameter("objectName", name.toString()); + return new ActionURL(ManageDataAction.class, getContainer()).addParameter("objectName", name.toString()).addParameter("query.viewName", "manageData"); } @RequiresPermission(ReadPermission.class) @@ -990,72 +988,61 @@ public Object execute(Object o, BindException errors) throws Exception } @RequiresPermission(InsertPermission.class) - public class InsertDataAction extends FormViewAction + public class InsertDataFormAction extends SimpleViewAction { private ObjectName _objectName = null; @Override - public void validateCommand(CubeObjectForm target, Errors errors) + public void validate(CubeObjectNameForm form, BindException errors) { - _objectName = target.getObjectName(); + _objectName = form.getObjectName(); if (_objectName == null) - errors.rejectValue(ERROR_REQUIRED, "Object name is required"); - - } - - public ModelAndView getView(CubeObjectForm tableForm, boolean reshow, BindException errors) throws Exception - { - setTitle("Update or Insert Publication"); - return new JspView("/org/labkey/trialshare/view/insertPublication.jsp", tableForm); - - } - - public boolean handlePost(CubeObjectForm tableForm, BindException errors) throws Exception - { - if (_objectName == ObjectName.publication) - TrialShareManager.get().insertPublication(getUser(), getContainer(), (StudyPublicationBean) tableForm.getCubeObject()); - - return 0 == errors.getErrorCount(); + errors.reject("Object name is required"); } @Override - public URLHelper getSuccessURL(CubeObjectForm form) + public ModelAndView getView(CubeObjectNameForm form, BindException errors) throws Exception { - ActionURL returnURL = getActionURLParam(ActionURL.Param.returnUrl); - if (returnURL == null) - { - returnURL = getManageDataUrl(form.getObjectName()); - } - return returnURL; + setTitle("Update or Insert " + form.getObjectName().getDisplayName()); + if (form.getReturnUrl() == null) + form.setReturnUrl(new ReturnURLString(getManageDataUrl(form.getObjectName()).toString())); + return new JspView("/org/labkey/trialshare/view/insertPublication.jsp", form); } + @Override public NavTree appendNavTrail(NavTree root) { String name = getViewContext().getActionURL().getParameter("objectName"); if (name != null) { ObjectName objectName = ObjectName.valueOf(name); - root.addChild("Manage " + objectName.getPluralName(), getSuccessURL(null)); + root.addChild("Manage " + objectName.getPluralName(), getManageDataUrl(objectName)); root.addChild("Insert " + objectName.getDisplayName()); } return root; } + } - private ActionURL getActionURLParam(ActionURL.Param param) + @RequiresPermission(InsertPermission.class) + public class InsertPublicationAction extends ApiAction { - String url = getViewContext().getActionURL().getParameter(param); - if (url != null) + @Override + public void validateForm(PublicationEditBean form, Errors errors) { - try - { - return new ActionURL(url); - } - catch (IllegalArgumentException ignored) {} + if (form == null) + errors.reject(ERROR_MSG, "Invalid form. Please check your syntax."); + else + form.validate(errors); } - return null; - } + @Override + public Object execute(PublicationEditBean form, BindException errors) throws Exception + { + TrialShareManager.get().insertPublication(getUser(), getContainer(), form, errors); + return success(); + } + } @RequiresPermission(InsertPermission.class) public class EditDataAction extends UserSchemaAction @@ -1081,8 +1068,11 @@ public NavTree appendNavTrail(NavTree root) if (_table != null) { ObjectName objectName = ObjectName.getFromTableName(_table.getName()); - root.addChild("Manage " + objectName.getPluralName(), getSuccessURL(null)); - root.addChild("Edit " + objectName.getDisplayName()); + if (objectName != null) + { + root.addChild("Manage " + objectName.getPluralName(), getManageDataUrl(objectName)); + root.addChild("Edit " + objectName.getDisplayName()); + } } return root; } diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 2ac5a1b8..4f97c963 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.labkey.api.collections.CaseInsensitiveHashMap; import org.labkey.api.data.CompareType; import org.labkey.api.data.Container; import org.labkey.api.data.DbScope; @@ -26,12 +27,17 @@ import org.labkey.api.data.TableInfo; import org.labkey.api.data.TableSelector; import org.labkey.api.module.ModuleLoader; +import org.labkey.api.query.BatchValidationException; import org.labkey.api.query.FieldKey; import org.labkey.api.security.User; +import org.labkey.trialshare.data.PublicationEditBean; import org.labkey.trialshare.data.StudyAccess; import org.labkey.trialshare.data.StudyPublicationBean; import org.labkey.trialshare.query.TrialShareQuerySchema; +import org.springframework.validation.BindException; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -187,7 +193,7 @@ public Set getVisiblePublications(User user, Container container) List publications = (new TableSelector(publicationsList, null, null)).getArrayList(StudyPublicationBean.class); for (StudyPublicationBean publication : publications) { - if (publication.getShow() && publication.hasPermission(user)) + if (publication.getShow() != null && publication.getShow() && publication.hasPermission(user)) { publicationIds.add(publication.getCubeId()); } @@ -196,53 +202,91 @@ public Set getVisiblePublications(User user, Container container) return publicationIds; } - public void insertPublication(User user, Container container, StudyPublicationBean publication) + public void insertPublication(User user, Container container, PublicationEditBean publication, BindException errors) { + if (publication == null) + return; + try (DbScope.Transaction transaction = TrialShareQuerySchema.getSchema(user, container).getDbSchema().getScope().ensureTransaction()) { TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); // insert the primary fields - Map pubData = Table.insert(user, TrialShareQuerySchema.getPublicationsTableInfo(user, container), publication.getPrimaryFields()); - Map dataMap = new HashMap<>(); - dataMap.put(TrialShareQuerySchema.PUBLICATION_ID_FIELD, pubData.get(TrialShareQuerySchema.PUBLICATION_ID_FIELD)); + + BatchValidationException batchValidationErrors = new BatchValidationException(); + List> pubData = schema.getPublicationsTableInfo().getUpdateService().insertRows(user, container, Collections.singletonList(publication.getPrimaryFields()), batchValidationErrors, null, null); + if (batchValidationErrors.hasErrors()) + throw batchValidationErrors; + List> dataList = new ArrayList<>(); + + Integer publicationKey = (Integer) pubData.get(0).get(TrialShareQuerySchema.PUBLICATION_KEY_FIELD); // insert the one-to-many data // conditions + for (String condition : publication.getConditions()) { + Map dataMap = new CaseInsensitiveHashMap<>(); + dataMap.put(TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey); dataMap.put(TrialShareQuerySchema.CONDITION_FIELD, condition); - Table.insert(user, schema.getPublicationConditionTableInfo(), dataMap); + dataList.add(dataMap); + } + if (!dataList.isEmpty()) + { + schema.getPublicationConditionTableInfo().getUpdateService().insertRows(user, container, dataList, batchValidationErrors, null, null); + if (batchValidationErrors.hasErrors()) + throw batchValidationErrors; + dataList.clear(); } - dataMap.remove(TrialShareQuerySchema.CONDITION_FIELD); // studies - for (Map.Entry entry : publication.getStudyIds().entrySet()) + for (String studyId : publication.getStudyIds()) { - dataMap.put(TrialShareQuerySchema.STUDY_ID_FIELD, entry.getKey()); - dataMap.put(TrialShareQuerySchema.STUDY_SHORT_NAME_FIELD, entry.getValue()); - Table.insert(user, schema.getPublicationStudyTableInfo(), dataMap); + Map dataMap = new CaseInsensitiveHashMap<>(); + dataMap.put(TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey); + dataMap.put(TrialShareQuerySchema.STUDY_ID_FIELD, studyId); + dataList.add(dataMap); + } + if (!dataList.isEmpty()) + { + schema.getPublicationStudyTableInfo().getUpdateService().insertRows(user, container, dataList, batchValidationErrors, null, null); + if (batchValidationErrors.hasErrors()) + throw batchValidationErrors; + dataList.clear(); } - dataMap.remove(TrialShareQuerySchema.STUDY_ID_FIELD); - dataMap.remove(TrialShareQuerySchema.STUDY_SHORT_NAME_FIELD); // Therapeutic Areas for (String area : publication.getTherapeuticAreas()) { + Map dataMap = new CaseInsensitiveHashMap<>(); + dataMap.put(TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey); dataMap.put(TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, area); - Table.insert(user, schema.getPublicationTherapeuticAreaTableInfo(), dataMap); + dataList.add(dataMap); + } + if (!dataList.isEmpty()) + { + schema.getPublicationTherapeuticAreaTableInfo().getUpdateService().insertRows(user, container, dataList, batchValidationErrors, null, null); + if (batchValidationErrors.hasErrors()) + throw batchValidationErrors; } transaction.commit(); } + catch (Exception e) + { + errors.reject("Publication insert failed", e.getMessage()); + } } - public void updatePublication(User user, Container container, StudyPublicationBean publication) + public void updatePublication(User user, Container container, PublicationEditBean publication) { + if (publication == null) + return; + try (DbScope.Transaction transaction = TrialShareQuerySchema.getSchema(user, container).getDbSchema().getScope().ensureTransaction()) { TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); // insert the primary fields - Table.update(user, TrialShareQuerySchema.getPublicationsTableInfo(user, container), publication.getPrimaryFields(), publication.getId()); + Table.update(user, schema.getPublicationsTableInfo(), publication.getPrimaryFields(), publication.getId()); Map dataMap = new HashMap<>(); dataMap.put(TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId()); @@ -263,10 +307,9 @@ public void updatePublication(User user, Container container, StudyPublicationBe // studies // get rid of the current values for this publication Table.delete(schema.getPublicationStudyTableInfo(), filter); - for (Map.Entry entry : publication.getStudyIds().entrySet()) + for (String studyId : publication.getStudyIds()) { - dataMap.put(TrialShareQuerySchema.STUDY_ID_FIELD, entry.getKey()); - dataMap.put(TrialShareQuerySchema.STUDY_SHORT_NAME_FIELD, entry.getValue()); + dataMap.put(TrialShareQuerySchema.STUDY_ID_FIELD, studyId); Table.insert(user, schema.getPublicationStudyTableInfo(), dataMap); } dataMap.remove(TrialShareQuerySchema.STUDY_ID_FIELD); @@ -284,8 +327,11 @@ public void updatePublication(User user, Container container, StudyPublicationBe } } - public void deletePublication(User user, @NotNull Container container, @NotNull Integer publicationId) + public void deletePublication(@NotNull User user, @NotNull Container container, @NotNull Integer publicationId) { + if (publicationId == null) + return; + try (DbScope.Transaction transaction = TrialShareQuerySchema.getSchema(user, container).getDbSchema().getScope().ensureTransaction()) { TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); @@ -301,4 +347,36 @@ public void deletePublication(User user, @NotNull Container container, @NotNull } } + +// public void insertStudy(@NotNull User user, @NotNull Container container, StudyBean study) +// { +// try (DbScope.Transaction transaction = TrialShareQuerySchema.getSchema(user, container).getDbSchema().getScope().ensureTransaction()) +// { +// TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); +// // insert the primary fields +// Map studyData = Table.insert(user, schema.getStudyPropertiesTableInfo(), study.getPrimaryFields()); +// Map dataMap = new HashMap<>(); +// dataMap.put(TrialShareQuerySchema.STUDY_ID_FIELD, studyData.get(TrialShareQuerySchema.STUDY_ID_FIELD)); +// +// // insert the one-to-many data +// // conditions +// for (String condition : study.getConditions()) +// { +// dataMap.put(TrialShareQuerySchema.CONDITION_FIELD, condition); +// Table.insert(user, schema.getPublicationConditionTableInfo(), dataMap); +// } +// dataMap.remove(TrialShareQuerySchema.CONDITION_FIELD); +// +// +// // Therapeutic Areas +// for (String area : study.getTherapeuticAreas()) +// { +// dataMap.put(TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, area); +// Table.insert(user, schema.getPublicationTherapeuticAreaTableInfo(), dataMap); +// } +// +// transaction.commit(); +// } +// +// } } \ No newline at end of file diff --git a/src/org/labkey/trialshare/data/PublicationEditBean.java b/src/org/labkey/trialshare/data/PublicationEditBean.java new file mode 100644 index 00000000..8b43ced4 --- /dev/null +++ b/src/org/labkey/trialshare/data/PublicationEditBean.java @@ -0,0 +1,44 @@ +package org.labkey.trialshare.data; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by susanh on 6/20/16. + */ +public class PublicationEditBean extends StudyPublicationBean +{ + private List _studyIds = new ArrayList<>(); + private List _conditions = new ArrayList<>(); + private List _therapeuticAreas = new ArrayList<>(); + + public List getConditions() + { + return _conditions; + } + + public void setConditions(List conditions) + { + this._conditions = conditions; + } + + public List getStudyIds() + { + return _studyIds; + } + + public void setStudyIds(List studyIds) + { + this._studyIds = studyIds; + } + + public List getTherapeuticAreas() + { + return _therapeuticAreas; + } + + public void setTherapeuticAreas(List therapeuticAreas) + { + this._therapeuticAreas = therapeuticAreas; + } +} diff --git a/src/org/labkey/trialshare/data/StudyPublicationBean.java b/src/org/labkey/trialshare/data/StudyPublicationBean.java index 73784f1a..524c50c4 100644 --- a/src/org/labkey/trialshare/data/StudyPublicationBean.java +++ b/src/org/labkey/trialshare/data/StudyPublicationBean.java @@ -16,9 +16,10 @@ package org.labkey.trialshare.data; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.validator.routines.UrlValidator; +import org.labkey.api.collections.CaseInsensitiveHashMap; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; -import org.labkey.api.data.DbScope; import org.labkey.api.reports.Report; import org.labkey.api.reports.ReportService; import org.labkey.api.reports.model.ViewCategory; @@ -30,13 +31,16 @@ import org.labkey.api.view.ActionURL; import org.labkey.api.view.ViewContext; import org.labkey.trialshare.query.TrialShareQuerySchema; -import org.springframework.validation.BindException; +import org.springframework.validation.Errors; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; + +import static org.labkey.api.action.SpringActionController.ERROR_CONVERSION; +import static org.labkey.api.action.SpringActionController.ERROR_REQUIRED; public class StudyPublicationBean { @@ -49,32 +53,30 @@ public class StudyPublicationBean private static final String PMCID_FIELD = "PMCID"; private static final String ISSUE_NUMBER_FIELD = "IssueNo"; private static final String PAGES_FIELD = "Pages"; - private static final String PUBLICATION_TYPE_FIELD = "PublicationType"; // TODO convert to lookup + private static final String PUBLICATION_TYPE_FIELD = "PublicationType"; private static final String YEAR_FIELD = "Year"; private static final String JOURNAL_FIELD = "Journal"; private static final String STATUS_FIELD = "Status"; + private static final String SUBMISSION_STATUS_FIELD = "SubmissionStatus"; private static final String CITATION_FIELD = "Citation"; private static final String ABSTRACT_FIELD = "Abstract"; private static final String DATA_URL_FIELD = "DataUrl"; private static final String IS_HIGHLIGHTED_FIELD = "IsHighlighted"; - private static final String MANUSCRIPT_CONTAINER_FIELD = "ManuscriptContaienr"; + private static final String MANUSCRIPT_CONTAINER_FIELD = "ManuscriptContainer"; private static final String KEYWORDS_FIELD = "Keywords"; private static final String PERMISSIONS_CONTAINER_FIELD = "PermissionsContainer"; private static final String IS_SHOWN_FIELD = "Show"; - public static final String FIGURES_CATEGORY_TEXT = "Manuscript Figures"; + private static final Pattern PMCID_PATTERN = Pattern.compile("[Pp][Mm][Cc]\\d+"); + + private static final String FIGURES_CATEGORY_TEXT = "Manuscript Figures"; private static final int AUTHORS_PER_ABBREV = 3; - private Map _primaryFields = new HashMap<>(); + private Map _primaryFields = new CaseInsensitiveHashMap<>(); private List studies; private List thumbnails; private List urls = new ArrayList<>(); - private Map studyIds = new HashMap<>(); - private List conditions = new ArrayList<>(); - private List therapeuticAreas = new ArrayList<>(); - - public Integer getId() { @@ -332,6 +334,16 @@ public void setStatus(String status) _primaryFields.put(STATUS_FIELD, status); } + public String getSubmissionStatus() + { + return (String) _primaryFields.get(SUBMISSION_STATUS_FIELD); + } + + public void setSubmissionStatus(String status) + { + _primaryFields.put(SUBMISSION_STATUS_FIELD, status); + } + public List getStudies() { return studies; @@ -478,36 +490,6 @@ public void setShow(Boolean show) _primaryFields.put(IS_SHOWN_FIELD, show); } - public List getConditions() - { - return conditions; - } - - public void setConditions(List conditions) - { - this.conditions = conditions; - } - - public Map getStudyIds() - { - return studyIds; - } - - public void setStudyIds(Map studyIds) - { - this.studyIds = studyIds; - } - - public List getTherapeuticAreas() - { - return therapeuticAreas; - } - - public void setTherapeuticAreas(List therapeuticAreas) - { - this.therapeuticAreas = therapeuticAreas; - } - public Boolean inProgress() { return getStatus().equalsIgnoreCase(TrialShareQuerySchema.IN_PROGRESS_STATUS); @@ -545,18 +527,31 @@ public Map getPrimaryFields() } - private void save(User user, Container container, BindException errors) + public void validate(Errors errors) { - - try (DbScope.Transaction transaction = TrialShareQuerySchema.getSchema(user, container).getDbSchema().getScope().ensureTransaction()) + if (getTitle() == null) + errors.rejectValue("title", ERROR_REQUIRED, "Title field is required"); + if (getStatus() == null) + errors.rejectValue("status", ERROR_REQUIRED, "Status field is required"); + if (getPublicationType() == null) + errors.rejectValue("publicationType", ERROR_REQUIRED, "Publication Type field is required"); + if (getPmid() != null && !StringUtils.isNumeric(getPmid())) { - - transaction.commit(); + errors.rejectValue("pmid", ERROR_CONVERSION, "PMID must be an integer"); + } + if (getPmcid() != null && !PMCID_PATTERN.matcher(getPmcid()).matches()) + { + errors.rejectValue("pmcid", ERROR_CONVERSION, "Incorrect format for PMCID. Expected PMC#"); } - catch (Exception e) + if (getYear() != null && !StringUtils.isNumeric(getPmid())) { - errors.reject("Error saving data", "Error saving publication data: " + e.getMessage()); + errors.rejectValue("year", ERROR_CONVERSION, "Year must be an integer"); } } + private boolean isValidUrl(String url) + { + return new UrlValidator(new String[]{"http","https"}).isValid(url); + } + } diff --git a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java index d7f8b8ed..6abd6791 100644 --- a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java +++ b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java @@ -123,7 +123,7 @@ protected ActionURL urlFor(QueryAction action) if (action.equals(QueryAction.insertQueryRow)) { ActionURL url = super.urlFor(action); - url.setAction(TrialShareController.InsertDataAction.class).addParameter("objectName", getCubeObjectName().toString()); + url.setAction(TrialShareController.InsertDataFormAction.class).addParameter("objectName", getCubeObjectName().toString()); return url; } return super.urlFor(action); diff --git a/src/org/labkey/trialshare/view/insertPublication.jsp b/src/org/labkey/trialshare/view/insertPublication.jsp index b6adc34a..69bd0a8a 100644 --- a/src/org/labkey/trialshare/view/insertPublication.jsp +++ b/src/org/labkey/trialshare/view/insertPublication.jsp @@ -28,18 +28,12 @@ resources.add(ClientDependency.fromPath("study/Finder/datafinder")); resources.add(ClientDependency.fromPath("clientapi")); resources.add(ClientDependency.fromPath("Ext4ClientApi")); - resources.add(ClientDependency.fromPath("study/Finder/panel/PublicationEdit.js")); + resources.add(ClientDependency.fromPath("study/Finder/panel/JunctionEditFormPanel.js")); return resources; } %> <% - -// JspView me = (JspView) HttpView.currentView(); -// TrialShareController.CubeObjectTypeForm bean = me.getModelBean(); -// TrialShareController.ObjectName thisObjectName = bean.getObjectName(); -// ModelAndView manageObjectsView = me.getView("manageObjectsView"); - String renderId = "insert-publication-" + UniqueID.getRequestScopedUID(HttpView.currentRequest()); %> @@ -49,41 +43,98 @@ Ext4.onReady(function(){ //create a formpanel using a store config object - Ext4.create('LABKEY.study.panel.PublicationEdit', { + Ext4.create('LABKEY.study.panel.JunctionEditFormPanel', { + objectName : 'Publication', + joinTableFields : ["StudyIds", "Conditions"], store: { schemaName: 'lists', queryName: 'manuscriptsAndAbstracts', viewName: 'dataFinderDetails', - autoLoad: true, + autoLoad: true }, -// store: Ext4.create('LABKEY.study.store.CubeObject', { -// -// url : LABKEY.ActionURL.buildURL(this.dataModuleName, "publication.api", this.cubeContainerPath), -// storeId: 'Publication', -// model: 'LABKEY.study.data.Publication', -// autoLoad: true, -// facetSelectedMembers : {}, // initially we indicate that none of the members is selected by facets -// searchSelectedMembers : null, // initially we have no search terms so everything is selected -// selectedSubset : null -// -// }), renderTo: <%=q(renderId)%>, bindConfig: { autoCreateRecordOnChange: true, - autoBindFirstRecord: true + autoBindFirstRecord: false }, //this config will be applied to the Ext fields created in this FormPanel only. metadata: { Title: { - width: 1000 + width: 1000, + isRequired: true }, Author: { width: 1000, height: 50, - xtype: 'textarea' + xtype: 'textarea', + stripNewLines : true + }, + Journal: { + width: 800 + }, + Status : { + isRequired: true + }, + PublicationType : { + isRequired: true + }, + ManuscriptContainer : { + containerFilter: "AllFolders", + width: 500 }, PermissionsContainer : { - containerFilter: "AllFolders" + containerFilter: "AllFolders", + width: 500 + }, + Citation : { + width: 1000, + height: 30, + xtype: 'textarea' + }, + StudyIds : { + width: 800 + }, + Conditions : { + width: 800 + }, + DOI : { + name: 'doi' + }, + PMID : { + name: 'pmid', + xtype: 'textfield' + }, + PMCID : { + name: 'pmcid' + }, + AbstractText : { + width: 1000, + height: 100, + xtype: 'htmleditor' + }, + Link1 : { + width: 1000 + }, + Description1 : { + width: 1000 + }, + Link2 : { + width: 1000 + }, + Description2 : { + width: 1000 + }, + Link3 : { + width: 1000 + }, + Description3 : { + width: 1000 + }, + Keywords: { + width: 800, + height: 50, + xtype: 'textarea', + stripNewLines : true } } }); From 48dff377c4a20f6a325c53589a8417283453cc06 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 21 Jun 2016 14:33:20 -0700 Subject: [PATCH 288/587] remove renamed file --- .../web/study/Finder/panel/PublicationEdit.js | 194 ------------------ 1 file changed, 194 deletions(-) delete mode 100644 resources/web/study/Finder/panel/PublicationEdit.js diff --git a/resources/web/study/Finder/panel/PublicationEdit.js b/resources/web/study/Finder/panel/PublicationEdit.js deleted file mode 100644 index 2b79374f..00000000 --- a/resources/web/study/Finder/panel/PublicationEdit.js +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.panel.PublicationEdit', { - extend: 'LABKEY.ext4.FormPanel', - // extend: 'Ext.form.Panel', - - alias: 'widget.labkey-data-finder-publication-editor', - - itemId : 'labkey-data-finder-publication-editor', - - cls: 'labkey-data-finder-publication-editor', - - defaultFieldWidth: 800, - defaultFieldLabelWidth: 200, - joinTableFields : ["StudiesList"], - - shouldShowInInsertView: function(metadata) { - return this.isJoinTableField || this.callParent(); - }, - - shouldShowInUpdateView: function(metadata) { - return this.shouldShowInInsertView(metadata); - }, - - configureJoinFields : function(store) { - var fields = LABKEY.ext4.Util.getStoreFields(this.store); - for (var i = 0; i < this.joinTableFields.length; i++) - { - var field = fields.get(this.joinTableFields[i]); - field.editable = true; - field.facetingBehaviorType = "AUTOMATIC"; - field.multiSelect = true; - field.isJoinField = true; - } - }, - - isJoinTableField : function(fieldName) - { - return this.joinTableFields.indexOf(fieldName) >= 0; - }, - - configureForm: function(store){ - this.configureJoinFields(store); - var toAdd = []; - var compositeFields = {}; - var fields = LABKEY.ext4.Util.getStoreFields(this.store); - - LABKEY.ext4.Util.getStoreFields(store).each(function(c){ - var config = { - queryName: store.queryName, - schemaName: store.schemaName - }; - - if (this.metadataDefaults) { - Ext4.Object.merge(config, this.metadataDefaults); - } - if (this.metadata && this.metadata[c.name]) { - Ext4.Object.merge(config, this.metadata[c.name]); - } - - if (this.shouldShowInUpdateView(c)){ - var theField = fields.get(c.name); - var fieldEditor = LABKEY.ext4.Util.getFormEditorConfig(fields.get(c.name), config); - - if (!fieldEditor.width) - fieldEditor.width = this.defaultFieldWidth; - if (!fieldEditor.labelWidth) - fieldEditor.labelWidth = this.defaultFieldLabelWidth; - - if (c.inputType == 'textarea' && fieldEditor.xtype == 'textarea' && !c.height){ - Ext4.apply(fieldEditor, {width: this.defaultFieldWidth, height: 100}); - - } - - if (fieldEditor.xtype == 'combo' || fieldEditor.xtype == 'labkey-combo'){ - fieldEditor.store.containerFilter = fieldEditor.containerFilter; - fieldEditor.multiSelect = theField.multiSelect; - fieldEditor.store.autoLoad = true; - } - - if (c.isAutoIncrement){ - fieldEditor.xtype = 'displayfield'; - } - - if (!c.compositeField) - toAdd.push(fieldEditor); - else { - fieldEditor.fieldLabel = undefined; - if(!compositeFields[c.compositeField]){ - compositeFields[c.compositeField] = { - xtype: 'panel', - autoHeight: true, - layout: 'hbox', - border: false, - fieldLabel: c.compositeField, - defaults: { - border: false, - margins: '0px 4px 0px 0px ' - }, - width: this.defaultFieldWidth, - items: [fieldEditor] - }; - toAdd.push(compositeFields[c.compositeField]); - - if(compositeFields[c.compositeField].msgTarget == 'below'){ - //create a div to hold error messages - compositeFields[c.compositeField].msgTargetId = Ext4.id(); - toAdd.push({ - tag: 'div', - fieldLabel: null, - border: false, - id: compositeFields[c.compositeField].msgTargetId - }); - } - else { - fieldEditor.msgTarget = 'qtip'; - } - } - else { - compositeFields[c.compositeField].items.push(fieldEditor); - } - } - } - }, this); - - //distribute width for compositeFields - for (var i in compositeFields){ - var compositeField = compositeFields[i]; - var toResize = []; - //this leaves a 2px buffer between each field - var availableWidth = this.defaultFieldWidth - 4*(compositeFields[i].items.length-1); - for (var j=0;j Date: Tue, 21 Jun 2016 16:57:53 -0700 Subject: [PATCH 289/587] Spec 26558: implement deletion for publications --- .../trialshare/TrialShareController.java | 83 ++++++++++++++++--- .../labkey/trialshare/TrialShareManager.java | 71 ++++++++++++++-- .../query/ManageCubeObjectQueryView.java | 9 +- 3 files changed, 138 insertions(+), 25 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index c637935e..61bce3e4 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -17,6 +17,7 @@ import org.apache.commons.lang3.StringUtils; import org.labkey.api.action.ApiAction; +import org.labkey.api.action.FormHandlerAction; import org.labkey.api.action.FormViewAction; import org.labkey.api.action.Marshal; import org.labkey.api.action.Marshaller; @@ -27,16 +28,19 @@ import org.labkey.api.data.ButtonBar; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; +import org.labkey.api.data.DataRegionSelection; import org.labkey.api.data.SimpleFilter; import org.labkey.api.data.TableInfo; import org.labkey.api.data.TableSelector; import org.labkey.api.query.FieldKey; +import org.labkey.api.query.QueryForm; import org.labkey.api.query.QuerySchema; import org.labkey.api.query.QueryService; import org.labkey.api.query.QueryUpdateForm; import org.labkey.api.query.UserSchemaAction; import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.AdminPermission; +import org.labkey.api.security.permissions.DeletePermission; import org.labkey.api.security.permissions.InsertPermission; import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.util.PageFlowUtil; @@ -70,6 +74,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; @Marshal(Marshaller.Jackson) public class TrialShareController extends SpringActionController @@ -976,16 +981,6 @@ private ActionURL getManageDataUrl(ObjectName name) return new ActionURL(ManageDataAction.class, getContainer()).addParameter("objectName", name.toString()).addParameter("query.viewName", "manageData"); } - @RequiresPermission(ReadPermission.class) - public class ExportDataAction extends ApiAction - { - - @Override - public Object execute(Object o, BindException errors) throws Exception - { - return null; - } - } @RequiresPermission(InsertPermission.class) public class InsertDataFormAction extends SimpleViewAction @@ -1044,6 +1039,72 @@ public Object execute(PublicationEditBean form, BindException errors) throws Exc } } + @RequiresPermission(DeletePermission.class) + public class DeleteCubeObjectsAction extends FormHandlerAction + { + + @Override + public void validateCommand(CubeObjectQueryForm form, Errors errors) + { + if (form == null) + errors.reject(ERROR_MSG, "Invalid form. Please check your syntax."); + else + form.validate(errors); + } + + @Override + public boolean handlePost(CubeObjectQueryForm form, BindException errors) throws Exception + { + Set ids = DataRegionSelection.getSelected(form.getViewContext(), null, true, true); + if (form.getObjectName() == ObjectName.publication) + TrialShareManager.get().deletePublications(getUser(), getContainer(), ids, errors); + else if (form.getObjectName() == ObjectName.study) + TrialShareManager.get().deleteStudies(getUser(), getContainer(), ids, errors); + else + errors.reject(ERROR_MSG, "Invalid object name: " + form.getObjectName()); + return !errors.hasErrors(); + } + + @Override + public URLHelper getSuccessURL(CubeObjectQueryForm queryForm) + { + return getManageDataUrl(ObjectName.publication); + } + } + + public static class CubeObjectQueryForm extends QueryForm + { + private ObjectName _objectName = null; + + public ObjectName getObjectName() + { + return _objectName; + } + + public void setObjectName(String name) + { + if (name == null) + this._objectName = null; + else + { + try + { + this._objectName = ObjectName.valueOf(name.toLowerCase()); + } + catch (IllegalArgumentException e) + { + + } + } + } + + public void validate(Errors errors) + { + if (getObjectName() == null) + errors.rejectValue("objectName", ERROR_REQUIRED, "Object name not recognized or not supplied"); + } + } + @RequiresPermission(InsertPermission.class) public class EditDataAction extends UserSchemaAction { @@ -1076,8 +1137,6 @@ public NavTree appendNavTrail(NavTree root) } return root; } - - } @RequiresPermission(ReadPermission.class) diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 4f97c963..817f5f61 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -29,6 +29,8 @@ import org.labkey.api.module.ModuleLoader; import org.labkey.api.query.BatchValidationException; import org.labkey.api.query.FieldKey; +import org.labkey.api.query.InvalidKeyException; +import org.labkey.api.query.QueryUpdateServiceException; import org.labkey.api.security.User; import org.labkey.trialshare.data.PublicationEditBean; import org.labkey.trialshare.data.StudyAccess; @@ -36,6 +38,7 @@ import org.labkey.trialshare.query.TrialShareQuerySchema; import org.springframework.validation.BindException; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -44,6 +47,9 @@ import java.util.Map; import java.util.Set; +import static org.labkey.api.action.SpringActionController.ERROR_CONVERSION; +import static org.labkey.api.action.SpringActionController.ERROR_MSG; + public class TrialShareManager { @@ -327,24 +333,64 @@ public void updatePublication(User user, Container container, PublicationEditBea } } - public void deletePublication(@NotNull User user, @NotNull Container container, @NotNull Integer publicationId) + private void deleteJoinTableData(@NotNull TableInfo tableInfo, @NotNull String keyName, @NotNull User user, @NotNull Container container, SimpleFilter objectIdFilter) throws SQLException, QueryUpdateServiceException, BatchValidationException, InvalidKeyException + { + // select the keys of the rows that have the object ids selected by the object filter + List keys = new TableSelector(tableInfo, Collections.singleton(keyName), objectIdFilter, null).getArrayList(Integer.class); + + List> pkMaps = new ArrayList<>(); + for (Integer key : keys) + { + Map keyMap = new HashMap<>(); + keyMap.put(keyName, key); + pkMaps.add(keyMap); + } + tableInfo.getUpdateService().deleteRows(user, container, pkMaps, null, null); + } + + public void deletePublications(@NotNull User user, @NotNull Container container, Set publicationIds, BindException errors) { - if (publicationId == null) + Set integerIds = new HashSet<>(); + for (String id : publicationIds) + { + try + { + integerIds.add(Integer.valueOf(id)); + } + catch (NumberFormatException e) + { + errors.reject(ERROR_CONVERSION, "Invalid id (expecting integer): " + id); + } + } + + if (errors.hasErrors()) return; + SimpleFilter idFilter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.PUBLICATION_ID_FIELD), integerIds, CompareType.IN); try (DbScope.Transaction transaction = TrialShareQuerySchema.getSchema(user, container).getDbSchema().getScope().ensureTransaction()) { TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); - SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.PUBLICATION_ID_FIELD), publicationId); - Table.delete(schema.getPublicationConditionTableInfo(), filter); - Table.delete(schema.getPublicationStudyTableInfo(), filter); - Table.delete(schema.getPublicationTherapeuticAreaTableInfo(), filter); + deleteJoinTableData(schema.getPublicationStudyTableInfo(), "Key", user, container, idFilter); + deleteJoinTableData(schema.getPublicationConditionTableInfo(), "Key", user, container, idFilter); + deleteJoinTableData(schema.getPublicationTherapeuticAreaTableInfo(), "Key", user, container, idFilter); + + List> pkMaps = new ArrayList<>(); + for (Integer id : integerIds) + { + Map keyMap = new HashMap<>(); + keyMap.put(TrialShareQuerySchema.PUBLICATION_KEY_FIELD, id); + pkMaps.add(keyMap); + } + + schema.getPublicationsTableInfo().getUpdateService().deleteRows(user, container, pkMaps, null, null); - filter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.PUBLICATION_KEY_FIELD), publicationId); - Table.delete(schema.getPublicationsTableInfo(), filter); transaction.commit(); } + catch (Exception e) + { + errors.reject(ERROR_MSG, e.getMessage()); + } } @@ -379,4 +425,13 @@ public void deletePublication(@NotNull User user, @NotNull Container container, // } // // } + + public void deleteStudies(@NotNull User user, @NotNull Container container, Set ids, BindException errors) + { +// for (String id : ids) +// { +// deleteStudy(user, container, id); +// } + } + } \ No newline at end of file diff --git a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java index 6abd6791..c10b9f12 100644 --- a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java +++ b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java @@ -38,6 +38,7 @@ abstract class ManageCubeObjectQueryView extends QueryView _cubeContainer = TrialShareManager.get().getCubeContainer(getContainer()); setShowInsertNewButton(true); setShowImportDataButton(false); + setShowExportButtons(false); setShowDetailsColumn(true); setShowUpdateColumn(_cubeContainer.hasPermission(getUser(), InsertPermission.class)); } @@ -112,15 +113,13 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep @Override protected ActionURL urlFor(QueryAction action) { - if (action.equals(QueryAction.exportRowsTsv) || action.equals(QueryAction.exportScript)) - return null; - if (action.equals(QueryAction.exportRowsExcel) || action.equals(QueryAction.exportRowsXLSX)) + if (action.equals(QueryAction.deleteQueryRows)) { ActionURL url = super.urlFor(action); - url.setAction(TrialShareController.ExportDataAction.class).addParameter("objectName", getCubeObjectName().toString()); + url.setAction(TrialShareController.DeleteCubeObjectsAction.class).addParameter("objectName", getCubeObjectName().toString()); return url; } - if (action.equals(QueryAction.insertQueryRow)) + else if (action.equals(QueryAction.insertQueryRow)) { ActionURL url = super.urlFor(action); url.setAction(TrialShareController.InsertDataFormAction.class).addParameter("objectName", getCubeObjectName().toString()); From d61b54f552fc71065e7798eb235205eabea82e9d Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 21 Jun 2016 17:19:15 -0700 Subject: [PATCH 290/587] Spec 26558: add TherapeuticArea for publications --- .../queries/lists/ManuscriptsAndAbstracts.query.xml | 9 +++++++++ .../ManuscriptsAndAbstracts/dataFinderDetails.qview.xml | 1 + .../web/study/Finder/panel/JunctionEditFormPanel.js | 1 + src/org/labkey/trialshare/view/insertPublication.jsp | 5 ++++- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/resources/queries/lists/ManuscriptsAndAbstracts.query.xml b/resources/queries/lists/ManuscriptsAndAbstracts.query.xml index 59c9b88c..fca07a77 100644 --- a/resources/queries/lists/ManuscriptsAndAbstracts.query.xml +++ b/resources/queries/lists/ManuscriptsAndAbstracts.query.xml @@ -23,6 +23,15 @@ Condition + + + lists + PublicationTherapeuticArea + publicationId + junction + TherapeuticArea + +
diff --git a/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml b/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml index f63795ab..8e2276c4 100644 --- a/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml +++ b/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml @@ -19,6 +19,7 @@ + diff --git a/resources/web/study/Finder/panel/JunctionEditFormPanel.js b/resources/web/study/Finder/panel/JunctionEditFormPanel.js index c6564694..b6667bab 100644 --- a/resources/web/study/Finder/panel/JunctionEditFormPanel.js +++ b/resources/web/study/Finder/panel/JunctionEditFormPanel.js @@ -162,6 +162,7 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { { this.getForm().findField(obj.errors[i].field).markInvalid([obj.errors[i].message]); } + Ext4.Msg.alert("Error", "There were problems submitting your data."); } Ext4.Ajax.request({ diff --git a/src/org/labkey/trialshare/view/insertPublication.jsp b/src/org/labkey/trialshare/view/insertPublication.jsp index 69bd0a8a..0f9e0806 100644 --- a/src/org/labkey/trialshare/view/insertPublication.jsp +++ b/src/org/labkey/trialshare/view/insertPublication.jsp @@ -45,7 +45,7 @@ //create a formpanel using a store config object Ext4.create('LABKEY.study.panel.JunctionEditFormPanel', { objectName : 'Publication', - joinTableFields : ["StudyIds", "Conditions"], + joinTableFields : ["StudyIds", "Conditions", "TherapeuticAreas"], store: { schemaName: 'lists', queryName: 'manuscriptsAndAbstracts', @@ -97,6 +97,9 @@ Conditions : { width: 800 }, + TherapeuticAreas : { + width: 800 + }, DOI : { name: 'doi' }, From b50588fb82e3362a2cd1d70ba9ca535e81d74e68 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 22 Jun 2016 12:18:45 -0700 Subject: [PATCH 291/587] Spec 26558: avoid NPE for missing status --- src/org/labkey/trialshare/data/StudyPublicationBean.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/data/StudyPublicationBean.java b/src/org/labkey/trialshare/data/StudyPublicationBean.java index 524c50c4..9ab9bee2 100644 --- a/src/org/labkey/trialshare/data/StudyPublicationBean.java +++ b/src/org/labkey/trialshare/data/StudyPublicationBean.java @@ -492,7 +492,7 @@ public void setShow(Boolean show) public Boolean inProgress() { - return getStatus().equalsIgnoreCase(TrialShareQuerySchema.IN_PROGRESS_STATUS); + return getStatus() != null && getStatus().equalsIgnoreCase(TrialShareQuerySchema.IN_PROGRESS_STATUS); } public String getCubeId() From 6e56c370b8eff3de1784dd0fd774e0ed059d4e66 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 14:54:41 -0700 Subject: [PATCH 292/587] Spec 26558: update canUpdate and canInsert to not require a query update service; add objectName parameter --- .../query/ManageCubeObjectQueryView.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java index c10b9f12..20871d53 100644 --- a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java +++ b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java @@ -11,6 +11,8 @@ import org.labkey.api.query.FieldKey; import org.labkey.api.query.QueryAction; import org.labkey.api.query.QueryView; +import org.labkey.api.security.User; +import org.labkey.api.security.permissions.DeletePermission; import org.labkey.api.security.permissions.InsertPermission; import org.labkey.api.util.PageFlowUtil; import org.labkey.api.view.ActionURL; @@ -45,6 +47,22 @@ abstract class ManageCubeObjectQueryView extends QueryView protected abstract TrialShareController.ObjectName getCubeObjectName(); + protected abstract TableInfo getTable(User user, Container container); + + @Override + protected boolean canInsert() + { + TableInfo table = getTable(getUser(), getContainer()); + return table != null && table.hasPermission(getUser(), InsertPermission.class); + } + + @Override + protected boolean canDelete() + { + TableInfo table = getTable(getUser(), getContainer()); + return table != null && table.hasPermission(getUser(), DeletePermission.class); + } + @Override protected void populateButtonBar(DataView view, ButtonBar bar, boolean exportAsWebPage) { @@ -82,6 +100,7 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep String id = String.valueOf(ctx.get(keyFieldKey)); ActionURL actionUrl = new ActionURL(TrialShareController.ViewDataAction.class, cubeContainer); actionUrl.addParameter("id", id); + actionUrl.addParameter("objectName", getCubeObjectName().toString()); out.write(PageFlowUtil.textLink("Details", actionUrl)); } }; @@ -99,6 +118,7 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep FieldKey keyFieldKey = FieldKey.fromParts(getKeyField()); String id = String.valueOf(ctx.get(keyFieldKey)); ActionURL actionUrl = new ActionURL(TrialShareController.EditDataAction.class, cubeContainer).addParameter("id", id); + actionUrl.addParameter("objectName", getCubeObjectName().toString()); out.write(PageFlowUtil.textLink("Edit", actionUrl)); } }; From 6210a3d1ab5bf3ad020202f019350e759f71f6b9 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 19:13:46 -0700 Subject: [PATCH 293/587] Spec 26558: rearrange columns in studyDetails view --- .../lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml b/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml index 8e2276c4..df7e8426 100644 --- a/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml +++ b/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml @@ -5,6 +5,7 @@ + @@ -13,7 +14,6 @@ - From 48d15c5e606b9f1f43fb4393583884fc2d4b985b Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 19:15:30 -0700 Subject: [PATCH 294/587] Spec 26558: change wording at top of manage data display --- src/org/labkey/trialshare/view/manageData.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/view/manageData.jsp b/src/org/labkey/trialshare/view/manageData.jsp index a1d92bd8..cb1b7b24 100644 --- a/src/org/labkey/trialshare/view/manageData.jsp +++ b/src/org/labkey/trialshare/view/manageData.jsp @@ -32,7 +32,7 @@ %>

- You can import, edit, or delete <%=h(thisObjectName.getPluralName().toLowerCase())%> from this page. + You can insert, edit, or delete <%=h(thisObjectName.getPluralName().toLowerCase())%> from this page. Remember to refresh the cube when you are ready for your changes to show on the data finder.

From f5b00ebe5fff475c1574f086b902911d2bb12bc0 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 19:17:58 -0700 Subject: [PATCH 295/587] Spec 26558: add visibility column to query --- resources/queries/lists/studies.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/queries/lists/studies.sql b/resources/queries/lists/studies.sql index ef754426..ee30c110 100644 --- a/resources/queries/lists/studies.sql +++ b/resources/queries/lists/studies.sql @@ -1,5 +1,6 @@ SELECT sa.StudyId, + sa.Visibility, sa.StudyContainer, sc.condition, sas.Assay, From 3e24ea4ef40296721ea62e47f623d372bb3064c2 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 19:18:29 -0700 Subject: [PATCH 296/587] Spec 26558: update fields show in manage publications view --- .../queries/lists/ManuscriptsAndAbstracts/manageData.qview.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/queries/lists/ManuscriptsAndAbstracts/manageData.qview.xml b/resources/queries/lists/ManuscriptsAndAbstracts/manageData.qview.xml index 801c8480..835edc5d 100644 --- a/resources/queries/lists/ManuscriptsAndAbstracts/manageData.qview.xml +++ b/resources/queries/lists/ManuscriptsAndAbstracts/manageData.qview.xml @@ -2,6 +2,7 @@ - + + \ No newline at end of file From 41726cd65d2471f3af21b5295e85b9c7e8d20618 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 19:27:38 -0700 Subject: [PATCH 297/587] Spec 26558: updates for inserting, updating and deleting studies; updates to form panel for editing an existing record; eliminate need for short name field in PublicationStudy list --- .../lists/ManuscriptsAndAbstracts.query.xml | 2 + .../queries/lists/StudyProperties.query.xml | 49 +++ .../dataFinderDetails.qview.xml | 18 + .../Finder/panel/JunctionEditFormPanel.js | 47 +-- .../trialshare/TrialShareController.java | 237 +++++++++---- .../labkey/trialshare/TrialShareManager.java | 312 +++++++++++------- src/org/labkey/trialshare/data/StudyBean.java | 77 +++-- .../labkey/trialshare/data/StudyEditBean.java | 57 ++++ .../query/ManagePublicationsQueryView.java | 8 + .../query/ManageStudiesQueryView.java | 8 + .../query/TrialShareQuerySchema.java | 19 +- ...Publication.jsp => publicationDetails.jsp} | 21 +- .../labkey/trialshare/view/studyDetail.jsp | 2 +- .../labkey/trialshare/view/studyDetails.jsp | 115 +++++++ 14 files changed, 721 insertions(+), 251 deletions(-) create mode 100644 resources/queries/lists/StudyProperties.query.xml create mode 100644 resources/queries/lists/StudyProperties/dataFinderDetails.qview.xml create mode 100644 src/org/labkey/trialshare/data/StudyEditBean.java rename src/org/labkey/trialshare/view/{insertPublication.jsp => publicationDetails.jsp} (85%) create mode 100644 src/org/labkey/trialshare/view/studyDetails.jsp diff --git a/resources/queries/lists/ManuscriptsAndAbstracts.query.xml b/resources/queries/lists/ManuscriptsAndAbstracts.query.xml index fca07a77..17f29fb0 100644 --- a/resources/queries/lists/ManuscriptsAndAbstracts.query.xml +++ b/resources/queries/lists/ManuscriptsAndAbstracts.query.xml @@ -22,6 +22,7 @@ junction Condition + false @@ -31,6 +32,7 @@ junction TherapeuticArea + false

diff --git a/resources/queries/lists/StudyProperties.query.xml b/resources/queries/lists/StudyProperties.query.xml new file mode 100644 index 00000000..c37f3101 --- /dev/null +++ b/resources/queries/lists/StudyProperties.query.xml @@ -0,0 +1,49 @@ + + + + + + + + lists + StudyAgeGroup + StudyId + junction + AgeGroup + + false + + + + lists + StudyPhase + StudyId + junction + Phase + + false + + + + lists + StudyCondition + StudyId + junction + Condition + + false + + + + + + + + + + + +
+
+
+
diff --git a/resources/queries/lists/StudyProperties/dataFinderDetails.qview.xml b/resources/queries/lists/StudyProperties/dataFinderDetails.qview.xml new file mode 100644 index 00000000..987dc95a --- /dev/null +++ b/resources/queries/lists/StudyProperties/dataFinderDetails.qview.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/web/study/Finder/panel/JunctionEditFormPanel.js b/resources/web/study/Finder/panel/JunctionEditFormPanel.js index b6667bab..5bc3088e 100644 --- a/resources/web/study/Finder/panel/JunctionEditFormPanel.js +++ b/resources/web/study/Finder/panel/JunctionEditFormPanel.js @@ -10,6 +10,7 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { defaultFieldWidth: 350, defaultFieldLabelWidth: 200, + mode: "view", initComponent : function() { @@ -21,7 +22,7 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { ui: 'footer', style: 'background-color: transparent;', items: [ - LABKEY.ext4.FORMBUTTONS.getButton('SUBMIT'), + LABKEY.ext4.FORMBUTTONS.getButton('SUBMIT', {successURL : this.returnUrl}), LABKEY.ext4.FORMBUTTONS.getButton('CANCEL', {returnURL : this.returnUrl}) ] }]; @@ -32,8 +33,21 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { return this.isJoinTableField(metadata.name) || LABKEY.ext4.Util.shouldShowInUpdateView(metadata); }, - shouldShowInUpdateView: function(metadata) { - return this.shouldShowInInsertView(metadata); + shouldShowInDisplayView: function(metadata) { + var record = this.store.getAt(0); // there will only be a single item in the store when in view mode + if (record) + { + var field = record.get(metadata.name); + return field !== undefined && field !== "" + } + return false; + }, + + shouldShowInView: function(metadata) { + if (this.mode == "edit") + return this.shouldShowInInsertView(metadata); + else + return this.shouldShowInDisplayView(metadata); }, configureJoinFields : function(store) { @@ -85,10 +99,10 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { Ext4.Object.merge(config, this.metadata[field.name]); } - if (this.shouldShowInUpdateView(field)){ + if (this.shouldShowInView(field)){ var fieldEditor = LABKEY.ext4.Util.getFormEditorConfig(field, config); - if (fieldEditor.isRequired) + if (fieldEditor.isRequired && this.mode != "view") fieldEditor.fieldLabel = fieldEditor.fieldLabel + " *"; if (!fieldEditor.width) fieldEditor.width = this.defaultFieldWidth; @@ -115,7 +129,9 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { fieldEditor.xtype = 'displayfield'; } - fieldEditor.name = this.uncapitalize(fieldEditor.name); // this is necessary to match the bean properties, somehow... + if (this.mode == "view") + fieldEditor.xtype = 'displayfield'; + if (!field.compositeField) toAdd.push(fieldEditor); else @@ -162,11 +178,11 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { { this.getForm().findField(obj.errors[i].field).markInvalid([obj.errors[i].message]); } - Ext4.Msg.alert("Error", "There were problems submitting your data."); + Ext4.Msg.alert("Error", "There were problems submitting your data. Please check the form for errors."); } Ext4.Ajax.request({ - url: LABKEY.ActionURL.buildURL('trialShare', 'insert' + this.objectName + ".api"), + url: LABKEY.ActionURL.buildURL('trialShare', this.mode + this.objectName + ".api"), method: 'POST', jsonData: this.getFieldValues(), success: onSuccess, @@ -180,7 +196,8 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { { var fieldValues = {}; var metadata = this.metadata; - // getValues returns the empty string for fields that are empty, which is not what we want. + // getValues returns the empty string for fields that are empty, which is not what we want, + // so we'll walk through the fields ourselves. this.getForm().getFields().each(function(item) { var value = item.value; @@ -192,17 +209,5 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { } }); return fieldValues; - // - // // We have to convert these to something acceptable as an arrayList - // var values = this.getValues(); - // for (var i = 0; i < this.joinTableFields.length; i++) - // { - // var value = values[this.uncapitalize(this.joinTableFields[i])]; - // if (!Ext4.isArray(value)) - // values[this.uncapitalize(this.joinTableFields[i])] = null; - // } - // - // return values; - } }); \ No newline at end of file diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 61bce3e4..db77ffde 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -15,44 +15,43 @@ */ package org.labkey.trialshare; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import org.labkey.api.action.ApiAction; import org.labkey.api.action.FormHandlerAction; import org.labkey.api.action.FormViewAction; +import org.labkey.api.action.HasValidator; import org.labkey.api.action.Marshal; import org.labkey.api.action.Marshaller; import org.labkey.api.action.ReturnUrlForm; import org.labkey.api.action.SimpleResponse; import org.labkey.api.action.SimpleViewAction; import org.labkey.api.action.SpringActionController; -import org.labkey.api.data.ButtonBar; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; import org.labkey.api.data.DataRegionSelection; -import org.labkey.api.data.SimpleFilter; import org.labkey.api.data.TableInfo; import org.labkey.api.data.TableSelector; -import org.labkey.api.query.FieldKey; import org.labkey.api.query.QueryForm; import org.labkey.api.query.QuerySchema; import org.labkey.api.query.QueryService; -import org.labkey.api.query.QueryUpdateForm; -import org.labkey.api.query.UserSchemaAction; import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.AdminPermission; import org.labkey.api.security.permissions.DeletePermission; import org.labkey.api.security.permissions.InsertPermission; import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.security.permissions.UpdatePermission; import org.labkey.api.util.PageFlowUtil; import org.labkey.api.util.Pair; -import org.labkey.api.util.ReturnURLString; import org.labkey.api.util.URLHelper; import org.labkey.api.view.ActionURL; import org.labkey.api.view.HtmlView; import org.labkey.api.view.JspView; import org.labkey.api.view.NavTree; import org.labkey.api.view.UnauthorizedException; -import org.labkey.api.view.UpdateView; import org.labkey.api.view.VBox; import org.labkey.api.view.WebPartView; import org.labkey.trialshare.data.CubeConfigBean; @@ -60,6 +59,7 @@ import org.labkey.trialshare.data.PublicationEditBean; import org.labkey.trialshare.data.StudyAccess; import org.labkey.trialshare.data.StudyBean; +import org.labkey.trialshare.data.StudyEditBean; import org.labkey.trialshare.data.StudyFacetBean; import org.labkey.trialshare.data.StudyPublicationBean; import org.labkey.trialshare.query.ManagePublicationsQueryView; @@ -80,6 +80,7 @@ public class TrialShareController extends SpringActionController { public static final String OBJECT_NAME_PARAM = "object"; + public enum ObjectName { study("Study", "Studies"), @@ -152,7 +153,9 @@ public void setSectionHeader(String sectionHeader) { _sectionHeader = sectionHeader; } - }; + } + + ; public TrialShareController() { @@ -202,7 +205,7 @@ public ModelAndView getView(CubeAdminForm form, boolean reshow, BindException er bean.addCubeDefinition(cubeContainer.getPath(), "TrialShare:/StudyCube"); bean.addCubeDefinition(cubeContainer.getPath(), "TrialShare:/PublicationCube"); } - JspView view = new JspView<>("/org/labkey/trialshare/view/cubeAdmin.jsp", bean, errors); + JspView view = new JspView<>("/org/labkey/trialshare/view/cubeAdmin.jsp", bean, errors); view.setFrame(WebPartView.FrameType.PORTAL); view.setTitle("Data Cube Definitions"); @@ -498,8 +501,8 @@ public Object execute(Object form, BindException errors) throws Exception } studies.removeIf(studyBean -> { - List accessList = studyAccessMap.get(studyBean.getStudyId()); - return accessList == null || accessList.isEmpty(); + List accessList = studyAccessMap.get(studyBean.getStudyId()); + return accessList == null || accessList.isEmpty(); }); for (StudyBean study : studies) { @@ -718,7 +721,7 @@ public class StudyDetailAction extends SimpleViewAction @Override public void validate(StudyIdForm form, BindException errors) { - _studyId = (null==form) ? null : form.getStudyId(); + _studyId = (null == form) ? null : form.getStudyId(); if (StringUtils.isEmpty(_studyId)) errors.reject(ERROR_MSG, "Study not specified"); } @@ -797,7 +800,7 @@ public class PublicationDetailsAction extends ApiAction @Override public void validateForm(PublicationIdForm form, Errors errors) { - _id = (null==form) ? null : form.getId(); + _id = (null == form) ? null : form.getId(); if (_id == null) errors.reject(ERROR_MSG, "Publication not specified"); } @@ -805,7 +808,8 @@ public void validateForm(PublicationIdForm form, Errors errors) @Override public Object execute(PublicationIdForm form, BindException errors) throws Exception { - QuerySchema listSchema = TrialShareQuerySchema.getSchema(getUser(), getContainer()); + TrialShareQuerySchema schema = new TrialShareQuerySchema(getUser(), getContainer()); + QuerySchema listSchema = schema.getSchema(getUser(), getContainer()); StudyPublicationBean publication = (new TableSelector(listSchema.getTable(TrialShareQuerySchema.PUBLICATION_TABLE))).getObject(_id, StudyPublicationBean.class); String containerId = publication.getManuscriptContainer(); if (containerId != null) @@ -815,10 +819,10 @@ public Object execute(PublicationIdForm form, BindException errors) throws Excep publication.setDataUrl(new ActionURL("project" + PageFlowUtil.encodeURI(container.getPath() + "/begin.view?pageId=study.DATA_ANALYSIS")).toString()); } publication.setThumbnails(getUser(), getViewContext().getActionURL()); - SimpleFilter filter = new SimpleFilter(); - filter.addCondition(FieldKey.fromParts("key"), _id); - publication.setStudies((new TableSelector(listSchema.getTable("publicationStudy"), filter, null)).getArrayList(StudyBean.class)); - for (StudyBean study : publication.getStudies()) + + + publication.setStudies(schema.getPublicationStudies(_id)); + for (StudyBean study : publication.getStudies()) { study.setStudyAccessList(getUser(), getContainer()); study.setUrl(getUser(), false); @@ -849,7 +853,6 @@ public void setId(Integer id) @RequiresPermission(ReadPermission.class) public class SubsetsAction extends ApiAction { - @Override public Object execute(CubeObjectNameForm form, BindException errors) throws Exception { @@ -857,7 +860,7 @@ public Object execute(CubeObjectNameForm form, BindException errors) throws Exce } } - public static class CubeObjectNameForm extends ReturnUrlForm + public static class CubeObjectNameForm { private ObjectName _objectName = null; @@ -983,47 +986,52 @@ private ActionURL getManageDataUrl(ObjectName name) } @RequiresPermission(InsertPermission.class) - public class InsertDataFormAction extends SimpleViewAction + public class InsertPublicationAction extends CaseInsensitiveApiAction { - private ObjectName _objectName = null; @Override - public void validate(CubeObjectNameForm form, BindException errors) + public void validateForm(PublicationEditBean form, Errors errors) { - _objectName = form.getObjectName(); - if (_objectName == null) - errors.reject("Object name is required"); + if (form == null) + errors.reject(ERROR_MSG, "Invalid form. Please check your syntax."); + else + form.validate(errors); } @Override - public ModelAndView getView(CubeObjectNameForm form, BindException errors) throws Exception + public Object execute(PublicationEditBean form, BindException errors) throws Exception { - setTitle("Update or Insert " + form.getObjectName().getDisplayName()); - if (form.getReturnUrl() == null) - form.setReturnUrl(new ReturnURLString(getManageDataUrl(form.getObjectName()).toString())); - return new JspView("/org/labkey/trialshare/view/insertPublication.jsp", form); + TrialShareManager.get().insertPublication(getUser(), getContainer(), form, errors); + return success(); } + } + @RequiresPermission(UpdatePermission.class) + public class EditPublicationAction extends CaseInsensitiveApiAction + { @Override - public NavTree appendNavTrail(NavTree root) + public void validateForm(PublicationEditBean form, Errors errors) { - String name = getViewContext().getActionURL().getParameter("objectName"); - if (name != null) - { - ObjectName objectName = ObjectName.valueOf(name); - root.addChild("Manage " + objectName.getPluralName(), getManageDataUrl(objectName)); - root.addChild("Insert " + objectName.getDisplayName()); - } - return root; + if (form == null) + errors.reject(ERROR_MSG, "Invalid form. Please check your syntax."); + else + form.validate(errors); } + @Override + public Object execute(PublicationEditBean form, BindException errors) throws Exception + { + TrialShareManager.get().updatePublication(getUser(), getContainer(), form, errors); + return success(); + } } @RequiresPermission(InsertPermission.class) - public class InsertPublicationAction extends ApiAction + public class InsertStudyAction extends CaseInsensitiveApiAction { + @Override - public void validateForm(PublicationEditBean form, Errors errors) + public void validateForm(StudyEditBean form, Errors errors) { if (form == null) errors.reject(ERROR_MSG, "Invalid form. Please check your syntax."); @@ -1032,13 +1040,34 @@ public void validateForm(PublicationEditBean form, Errors errors) } @Override - public Object execute(PublicationEditBean form, BindException errors) throws Exception + public Object execute(StudyEditBean form, BindException errors) throws Exception { - TrialShareManager.get().insertPublication(getUser(), getContainer(), form, errors); + TrialShareManager.get().insertStudy(getUser(), getContainer(), form, errors); return success(); } } + @RequiresPermission(UpdatePermission.class) + public class EditStudyAction extends CaseInsensitiveApiAction + { + @Override + public void validateForm(StudyEditBean form, Errors errors) + { + if (form == null) + errors.reject(ERROR_MSG, "Invalid form. Please check your syntax."); + else + form.validate(errors); + } + + @Override + public Object execute(StudyEditBean form, BindException errors) throws Exception + { + TrialShareManager.get().updateStudy(getUser(), getContainer(), form, errors); + return success(); + } + } + + @RequiresPermission(DeletePermission.class) public class DeleteCubeObjectsAction extends FormHandlerAction { @@ -1072,7 +1101,7 @@ public URLHelper getSuccessURL(CubeObjectQueryForm queryForm) } } - public static class CubeObjectQueryForm extends QueryForm + public static class CubeObjectQueryForm extends QueryForm implements HasValidator { private ObjectName _objectName = null; @@ -1091,7 +1120,7 @@ public void setObjectName(String name) { this._objectName = ObjectName.valueOf(name.toLowerCase()); } - catch (IllegalArgumentException e) + catch (IllegalArgumentException ignore) { } @@ -1106,53 +1135,123 @@ public void validate(Errors errors) } @RequiresPermission(InsertPermission.class) - public class EditDataAction extends UserSchemaAction + public class InsertDataFormAction extends CubeObjectDetailFormAction { + protected String getMode() + { + return "insert"; + } + } - public ModelAndView getView(QueryUpdateForm tableForm, boolean reshow, BindException errors) throws Exception + + @RequiresPermission(UpdatePermission.class) + public class EditDataAction extends CubeObjectDetailFormAction + { + protected String getMode() { - ButtonBar bb = createSubmitCancelButtonBar(tableForm); - UpdateView view = new UpdateView(tableForm, errors); + return "edit"; + } + } - view.getDataRegion().setButtonBar(bb); - return view; + @RequiresPermission(ReadPermission.class) + public class ViewDataAction extends CubeObjectDetailFormAction + { + protected String getMode() + { + return "view"; + } + } + + @RequiresPermission(ReadPermission.class) + private abstract class CubeObjectDetailFormAction extends SimpleViewAction + { + @Override + public void validate(CubeObjectDetailBean form, BindException errors) + { + if (form.getObjectName() == null) + errors.reject("Object name is required"); } - public boolean handlePost(QueryUpdateForm tableForm, BindException errors) throws Exception + protected abstract String getMode(); + + @Override + public ModelAndView getView(CubeObjectDetailBean bean, BindException errors) throws Exception { - doInsertUpdate(tableForm, errors, false); - return 0 == errors.getErrorCount(); + setTitle(StringUtils.capitalize(getMode()) + bean.getObjectName().getDisplayName()); + bean.setMode(getMode()); + if (bean.getObjectName() == ObjectName.publication) + return new JspView("/org/labkey/trialshare/view/publicationDetails.jsp", bean); + else + return new JspView("/org/labkey/trialshare/view/studyDetails.jsp", bean); } + @Override public NavTree appendNavTrail(NavTree root) { - if (_table != null) + String name = getViewContext().getActionURL().getParameter("objectName"); + if (name != null) { - ObjectName objectName = ObjectName.getFromTableName(_table.getName()); - if (objectName != null) - { - root.addChild("Manage " + objectName.getPluralName(), getManageDataUrl(objectName)); - root.addChild("Edit " + objectName.getDisplayName()); - } + ObjectName objectName = ObjectName.valueOf(name); + root.addChild("Manage " + objectName.getPluralName(), getManageDataUrl(objectName)); + root.addChild(StringUtils.capitalize(getMode()) + " " + objectName.getDisplayName()); } return root; } } - @RequiresPermission(ReadPermission.class) - public class ViewDataAction extends SimpleViewAction + public static class CubeObjectDetailBean extends CubeObjectNameForm { + private String _mode; + private Object _id; - @Override - public ModelAndView getView(Object o, BindException errors) throws Exception + public CubeObjectDetailBean() { - return null; } + public CubeObjectDetailBean(@NotNull String mode) + { + _mode = mode; + } + + public Object getId() + { + return _id; + } + + public void setId(Object id) + { + _id = id; + } + + public String getMode() + { + return _mode; + } + + public void setMode(String mode) + { + _mode = mode; + } + + public String getIdField() + { + if (getObjectName() == ObjectName.publication) + return "Key"; + else + return "StudyId"; + } + + } + + + private abstract class CaseInsensitiveApiAction
extends ApiAction + { @Override - public NavTree appendNavTrail(NavTree root) + protected ObjectReader getObjectReader(Class c) { - return null; + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); + return objectMapper.reader(c); } } } \ No newline at end of file diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 817f5f61..aee14d04 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -23,17 +23,18 @@ import org.labkey.api.data.Container; import org.labkey.api.data.DbScope; import org.labkey.api.data.SimpleFilter; -import org.labkey.api.data.Table; import org.labkey.api.data.TableInfo; import org.labkey.api.data.TableSelector; import org.labkey.api.module.ModuleLoader; import org.labkey.api.query.BatchValidationException; +import org.labkey.api.query.DuplicateKeyException; import org.labkey.api.query.FieldKey; import org.labkey.api.query.InvalidKeyException; import org.labkey.api.query.QueryUpdateServiceException; import org.labkey.api.security.User; import org.labkey.trialshare.data.PublicationEditBean; import org.labkey.trialshare.data.StudyAccess; +import org.labkey.trialshare.data.StudyEditBean; import org.labkey.trialshare.data.StudyPublicationBean; import org.labkey.trialshare.query.TrialShareQuerySchema; import org.springframework.validation.BindException; @@ -228,52 +229,9 @@ public void insertPublication(User user, Container container, PublicationEditBea // insert the one-to-many data // conditions - - for (String condition : publication.getConditions()) - { - Map dataMap = new CaseInsensitiveHashMap<>(); - dataMap.put(TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey); - dataMap.put(TrialShareQuerySchema.CONDITION_FIELD, condition); - dataList.add(dataMap); - } - if (!dataList.isEmpty()) - { - schema.getPublicationConditionTableInfo().getUpdateService().insertRows(user, container, dataList, batchValidationErrors, null, null); - if (batchValidationErrors.hasErrors()) - throw batchValidationErrors; - dataList.clear(); - } - - // studies - for (String studyId : publication.getStudyIds()) - { - Map dataMap = new CaseInsensitiveHashMap<>(); - dataMap.put(TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey); - dataMap.put(TrialShareQuerySchema.STUDY_ID_FIELD, studyId); - dataList.add(dataMap); - } - if (!dataList.isEmpty()) - { - schema.getPublicationStudyTableInfo().getUpdateService().insertRows(user, container, dataList, batchValidationErrors, null, null); - if (batchValidationErrors.hasErrors()) - throw batchValidationErrors; - dataList.clear(); - } - - // Therapeutic Areas - for (String area : publication.getTherapeuticAreas()) - { - Map dataMap = new CaseInsensitiveHashMap<>(); - dataMap.put(TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey); - dataMap.put(TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, area); - dataList.add(dataMap); - } - if (!dataList.isEmpty()) - { - schema.getPublicationTherapeuticAreaTableInfo().getUpdateService().insertRows(user, container, dataList, batchValidationErrors, null, null); - if (batchValidationErrors.hasErrors()) - throw batchValidationErrors; - } + addJoinTableData(schema.getPublicationConditionTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey, TrialShareQuerySchema.CONDITION_FIELD, publication.getConditions(), user, container); + addJoinTableData(schema.getPublicationStudyTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey, TrialShareQuerySchema.STUDY_ID_FIELD, publication.getStudyIds(), user, container); + addJoinTableData(schema.getPublicationTherapeuticAreaTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey, TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, publication.getTherapeuticAreas(), user, container); transaction.commit(); } @@ -283,71 +241,50 @@ public void insertPublication(User user, Container container, PublicationEditBea } } - public void updatePublication(User user, Container container, PublicationEditBean publication) + public void updatePublication(User user, Container container, PublicationEditBean publication, BindException errors) { if (publication == null) + { + errors.reject(ERROR_MSG, "No publication data provided to update"); + return; + } + if (publication.getId() == null) + { + errors.reject(ERROR_MSG, "Publication id is null"); return; + } try (DbScope.Transaction transaction = TrialShareQuerySchema.getSchema(user, container).getDbSchema().getScope().ensureTransaction()) { TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); - // insert the primary fields - Table.update(user, schema.getPublicationsTableInfo(), publication.getPrimaryFields(), publication.getId()); - Map dataMap = new HashMap<>(); - dataMap.put(TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId()); + + // update the primary fields + schema.getPublicationsTableInfo().getUpdateService().updateRows(user, container, Collections.singletonList(publication.getPrimaryFields()), null, null, null); // update the many-to-one data SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.PUBLICATION_ID_FIELD), publication.getId()); + // update the many-to-one data // conditions // first get rid of the current values for this publication - Table.delete(schema.getPublicationConditionTableInfo(), filter); - // now add the new values - for (String condition : publication.getConditions()) - { - dataMap.put(TrialShareQuerySchema.CONDITION_FIELD, condition); - Table.insert(user, schema.getPublicationConditionTableInfo(), dataMap); - } - dataMap.remove(TrialShareQuerySchema.CONDITION_FIELD); - - // studies - // get rid of the current values for this publication - Table.delete(schema.getPublicationStudyTableInfo(), filter); - for (String studyId : publication.getStudyIds()) - { - dataMap.put(TrialShareQuerySchema.STUDY_ID_FIELD, studyId); - Table.insert(user, schema.getPublicationStudyTableInfo(), dataMap); - } - dataMap.remove(TrialShareQuerySchema.STUDY_ID_FIELD); - dataMap.remove(TrialShareQuerySchema.STUDY_SHORT_NAME_FIELD); + schema.getPublicationConditionTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationConditionTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, filter), null, null); + addJoinTableData(schema.getPublicationConditionTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId(), TrialShareQuerySchema.CONDITION_FIELD, publication.getConditions(), user, container); - // Therapeutic Areas - Table.delete(schema.getPublicationTherapeuticAreaTableInfo(), filter); - for (String area : publication.getTherapeuticAreas()) - { - dataMap.put(TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, area); - Table.insert(user, schema.getPublicationTherapeuticAreaTableInfo(), dataMap); - } + schema.getPublicationStudyTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationStudyTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, filter), null, null); + addJoinTableData(schema.getPublicationStudyTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId(), TrialShareQuerySchema.STUDY_ID_FIELD, publication.getStudyIds(), user, container); + schema.getPublicationTherapeuticAreaTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationTherapeuticAreaTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, filter), null, null); + addJoinTableData(schema.getPublicationTherapeuticAreaTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId(), TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, publication.getTherapeuticAreas(), user, container); transaction.commit(); } - } - - private void deleteJoinTableData(@NotNull TableInfo tableInfo, @NotNull String keyName, @NotNull User user, @NotNull Container container, SimpleFilter objectIdFilter) throws SQLException, QueryUpdateServiceException, BatchValidationException, InvalidKeyException - { - // select the keys of the rows that have the object ids selected by the object filter - List keys = new TableSelector(tableInfo, Collections.singleton(keyName), objectIdFilter, null).getArrayList(Integer.class); - - List> pkMaps = new ArrayList<>(); - for (Integer key : keys) + catch (Exception e) { - Map keyMap = new HashMap<>(); - keyMap.put(keyName, key); - pkMaps.add(keyMap); + errors.reject("Publication update failed", e.getMessage()); } - tableInfo.getUpdateService().deleteRows(user, container, pkMaps, null, null); + } + public void deletePublications(@NotNull User user, @NotNull Container container, Set publicationIds, BindException errors) { Set integerIds = new HashSet<>(); @@ -394,44 +331,167 @@ public void deletePublications(@NotNull User user, @NotNull Container container, } -// public void insertStudy(@NotNull User user, @NotNull Container container, StudyBean study) -// { -// try (DbScope.Transaction transaction = TrialShareQuerySchema.getSchema(user, container).getDbSchema().getScope().ensureTransaction()) -// { -// TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); -// // insert the primary fields -// Map studyData = Table.insert(user, schema.getStudyPropertiesTableInfo(), study.getPrimaryFields()); -// Map dataMap = new HashMap<>(); -// dataMap.put(TrialShareQuerySchema.STUDY_ID_FIELD, studyData.get(TrialShareQuerySchema.STUDY_ID_FIELD)); -// -// // insert the one-to-many data -// // conditions -// for (String condition : study.getConditions()) -// { -// dataMap.put(TrialShareQuerySchema.CONDITION_FIELD, condition); -// Table.insert(user, schema.getPublicationConditionTableInfo(), dataMap); -// } -// dataMap.remove(TrialShareQuerySchema.CONDITION_FIELD); -// -// -// // Therapeutic Areas -// for (String area : study.getTherapeuticAreas()) -// { -// dataMap.put(TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, area); -// Table.insert(user, schema.getPublicationTherapeuticAreaTableInfo(), dataMap); -// } -// -// transaction.commit(); -// } -// -// } + public void updateStudy(@NotNull User user, @NotNull Container container, StudyEditBean study, BindException errors) + { + if (study == null) + { + errors.reject(ERROR_MSG, "No study data provided to update"); + return; + } + if (study.getStudyId() == null) + { + errors.reject(ERROR_MSG, "No study id provided"); + return; + } + + try (DbScope.Transaction transaction = TrialShareQuerySchema.getSchema(user, container).getDbSchema().getScope().ensureTransaction()) + { + TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); + + // update the primary fields + schema.getStudyPropertiesTableInfo().getUpdateService().updateRows(user, container, Collections.singletonList(study.getPrimaryFields()), null, null, null); + + String studyId = study.getStudyId(); + SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.STUDY_ID_FIELD), studyId); + + // update the many-to-one data. First get rid of the current values for the study + schema.getPublicationConditionTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyConditionTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, filter), null, null); + addJoinTableData(schema.getStudyConditionTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.CONDITION_FIELD, study.getConditions(), user, container); + + schema.getStudyAgeGroupTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyAgeGroupTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, filter), null, null); + addJoinTableData(schema.getStudyAgeGroupTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.AGE_GROUP_FIELD, study.getAgeGroups(), user, container); + + schema.getStudyPhaseTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyPhaseTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, filter), null, null); + addJoinTableData(schema.getStudyPhaseTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.PHASE_FIELD, study.getPhases(), user, container); + + schema.getStudyTherapeuticAreaTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyTherapeuticAreaTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, filter), null, null); + addJoinTableData(schema.getStudyTherapeuticAreaTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, study.getTherapeuticAreas(), user, container); + + transaction.commit(); + } + catch (Exception e) + { + errors.reject(ERROR_MSG, "Problem inserting study " + e.getMessage()); + } + + } + + public void insertStudy(@NotNull User user, @NotNull Container container, StudyEditBean study, BindException errors) + { + if (study == null) + { + errors.reject(ERROR_MSG, "No study data provided to update"); + return; + } + if (study.getStudyId() == null) + { + errors.reject(ERROR_MSG, "No study id provided"); + return; + } + + try (DbScope.Transaction transaction = TrialShareQuerySchema.getSchema(user, container).getDbSchema().getScope().ensureTransaction()) + { + TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); + // insert the primary fields + + BatchValidationException batchValidationErrors = new BatchValidationException(); + List> studyData = schema.getStudyPropertiesTableInfo().getUpdateService().insertRows(user, container, Collections.singletonList(study.getPrimaryFields()), batchValidationErrors, null, null); + if (batchValidationErrors.hasErrors()) + throw batchValidationErrors; + + String studyId = study.getStudyId(); + // insert the one-to-many data + // conditions + addJoinTableData(schema.getStudyConditionTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.CONDITION_FIELD, study.getConditions(), user, container); + addJoinTableData(schema.getStudyAgeGroupTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.AGE_GROUP_FIELD, study.getAgeGroups(), user, container); + addJoinTableData(schema.getStudyPhaseTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.PHASE_FIELD, study.getPhases(), user, container); + addJoinTableData(schema.getStudyTherapeuticAreaTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, study.getTherapeuticAreas(), user, container); + + transaction.commit(); + } + catch (Exception e) + { + errors.reject(ERROR_MSG, "Problem inserting study " + e.getMessage()); + } + } + public void deleteStudies(@NotNull User user, @NotNull Container container, Set ids, BindException errors) { -// for (String id : ids) -// { -// deleteStudy(user, container, id); -// } + + if (errors.hasErrors()) + return; + SimpleFilter idFilter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.STUDY_ID_FIELD), ids, CompareType.IN); + + try (DbScope.Transaction transaction = TrialShareQuerySchema.getSchema(user, container).getDbSchema().getScope().ensureTransaction()) + { + TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); + + deleteJoinTableData(schema.getStudyPhaseTableInfo(), "Key", user, container, idFilter); + deleteJoinTableData(schema.getStudyAgeGroupTableInfo(), "Key", user, container, idFilter); + deleteJoinTableData(schema.getStudyConditionTableInfo(), "Key", user, container, idFilter); + deleteJoinTableData(schema.getStudyTherapeuticAreaTableInfo(), "Key", user, container, idFilter); + deleteJoinTableData(schema.getStudyAccessTableInfo(), "Key", user, container, idFilter); + + List> pkMaps = new ArrayList<>(); + for (String id : ids) + { + Map keyMap = new HashMap<>(); + keyMap.put(TrialShareQuerySchema.STUDY_ID_FIELD, id); + pkMaps.add(keyMap); + } + + schema.getStudyPropertiesTableInfo().getUpdateService().deleteRows(user, container, pkMaps, null, null); + + transaction.commit(); + } + catch (Exception e) + { + errors.reject(ERROR_MSG, e.getMessage()); + } + } + + + private List> getJoinTableIds(@NotNull TableInfo tableInfo, @NotNull String keyName, SimpleFilter objectIdFilter) + { + // select the keys of the rows that have the object ids selected by the object filter + List keys = new TableSelector(tableInfo, Collections.singleton(keyName), objectIdFilter, null).getArrayList(Integer.class); + + List> pkMaps = new ArrayList<>(); + for (Integer key : keys) + { + Map keyMap = new CaseInsensitiveHashMap<>(); + keyMap.put(keyName, key); + pkMaps.add(keyMap); + } + return pkMaps; + } + + + private void deleteJoinTableData(@NotNull TableInfo tableInfo, @NotNull String keyName, @NotNull User user, @NotNull Container container, SimpleFilter objectIdFilter) throws SQLException, QueryUpdateServiceException, BatchValidationException, InvalidKeyException + { + tableInfo.getUpdateService().deleteRows(user, container, getJoinTableIds(tableInfo, keyName, objectIdFilter), null, null); + } + + private void addJoinTableData(TableInfo tableInfo, String idField, Object id, String dataField, List dataValues, User user, Container container) throws SQLException, QueryUpdateServiceException, BatchValidationException, DuplicateKeyException + { + BatchValidationException batchValidationErrors = new BatchValidationException(); + List> dataList = new ArrayList<>(); + for (String value : dataValues) + { + Map dataMap = new CaseInsensitiveHashMap<>(); + dataMap.put(idField, id); + dataMap.put(dataField, value); + dataList.add(dataMap); + } + if (!dataList.isEmpty()) + { + tableInfo.getUpdateService().insertRows(user, container, dataList, batchValidationErrors, null, null); + if (batchValidationErrors.hasErrors()) + throw batchValidationErrors; + dataList.clear(); + } } + } \ No newline at end of file diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index 2c8c42b2..0b1159bd 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -16,6 +16,7 @@ package org.labkey.trialshare.data; import org.jetbrains.annotations.Nullable; +import org.labkey.api.collections.CaseInsensitiveHashMap; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerFilter; import org.labkey.api.data.ContainerFilterable; @@ -32,6 +33,7 @@ import org.labkey.api.wiki.WikiRendererType; import org.labkey.api.wiki.WikiService; import org.labkey.trialshare.query.TrialShareQuerySchema; +import org.springframework.validation.Errors; import java.util.ArrayList; import java.util.Collection; @@ -44,23 +46,28 @@ */ public class StudyBean { - private String shortName; - private String studyId; - private String title; + private static final String STUDY_TYPE_KEY = "studyType"; + private static final String SHORT_NAME_FIELD = "shortName"; + private static final String STUDY_ID_FIELD = "studyId"; + private static final String TITLE_FIELD = "title"; + private static final String INVESTIGATOR_FIELD = "investigator"; + private static final String EXTERNAL_URL_FIELD = "externalUrl"; + private static final String EXTERNAL_URL_DESCRIPTION_FIELD = "externalUrlDescription"; + private static final String ICON_URL_FIELD = "iconUrl"; + private static final String DESCRIPTION_FIELD = "description"; + private static final String PARTICIPANT_COUNT_FIELD = "participantCount"; + + private Map _primaryFields = new CaseInsensitiveHashMap<>(); + + private String url; - private String investigator; - private String externalUrl; - private String externalUrlDescription; - private String iconUrl; private Boolean isLoaded; - private String description; private String briefDescription; private String studyIdPrefix = null; // common prefix used in labeling studies private String availability; private Boolean isHighlighted = false; private String visibility; private Boolean isPublic = false; - private Integer participantCount; private List _studyAccessList = new ArrayList<>(); private List personnel; @@ -71,42 +78,46 @@ public class StudyBean public String getStudyId() { - return studyId; + return (String) _primaryFields.get(STUDY_ID_FIELD); } public void setStudyId(String studyId) { - this.studyId = studyId; + _primaryFields.put(STUDY_ID_FIELD, studyId); } + public String getStudyType() { return (String) _primaryFields.get(STUDY_TYPE_KEY); } + + public void setStudyType(String studyType) { _primaryFields.put(STUDY_TYPE_KEY, studyType); } + public String getInvestigator() { - return investigator; + return (String) _primaryFields.get(INVESTIGATOR_FIELD); } public void setInvestigator(String investigator) { - this.investigator = investigator; + _primaryFields.put(INVESTIGATOR_FIELD, investigator); } public String getTitle() { - return title; + return (String) _primaryFields.get(TITLE_FIELD); } public void setTitle(String title) { - this.title = title; + _primaryFields.put(TITLE_FIELD, title); } public String getExternalUrl() { - return externalUrl; + return (String) _primaryFields.get(EXTERNAL_URL_FIELD); } public void setExternalUrl(String externalUrl) { - this.externalUrl = externalUrl; + _primaryFields.put(EXTERNAL_URL_FIELD, externalUrl); } public Boolean getIsLoaded() @@ -194,12 +205,12 @@ public void setStudyIdPrefix(String studyIdPrefix) public String getShortName() { - return shortName; + return (String) _primaryFields.get(SHORT_NAME_FIELD); } public void setShortName(String shortName) { - this.shortName = shortName; + _primaryFields.put(SHORT_NAME_FIELD, shortName); } public String getAvailability() @@ -214,12 +225,12 @@ public void setAvailability(String availability) public String getIconUrl() { - return iconUrl; + return (String) _primaryFields.get(ICON_URL_FIELD); } public void setIconUrl(String iconUrl) { - this.iconUrl = iconUrl; + _primaryFields.put(ICON_URL_FIELD, iconUrl); } public Integer getManuscriptCount() @@ -269,12 +280,12 @@ public void setIsPublic(Boolean aPublic) public Integer getParticipantCount() { - return participantCount; + return (Integer) _primaryFields.get(PARTICIPANT_COUNT_FIELD); } public void setParticipantCount(Integer participantCount) { - this.participantCount = participantCount; + _primaryFields.put(PARTICIPANT_COUNT_FIELD, participantCount); } public void setUrl(String url) @@ -333,9 +344,9 @@ public static Collection> getStudyProperties(Container c, Us return Collections.emptyList(); } - public String getDescription(Container c, User user) + public String getDescription() { - return description; + return (String) _primaryFields.get(DESCRIPTION_FIELD); } private String getFormattedHtml(WikiRendererType rendererType, String markup) @@ -354,18 +365,18 @@ private String getFormattedHtml(WikiRendererType rendererType, String markup) public void setDescription(String description) { - this.description = description; + _primaryFields.put(DESCRIPTION_FIELD, description); } public String getExternalUrlDescription() { - return externalUrlDescription; + return (String) _primaryFields.get(EXTERNAL_URL_DESCRIPTION_FIELD); } public void setExternalUrlDescription(String externalUrlDescription) { - this.externalUrlDescription = externalUrlDescription; + _primaryFields.put(EXTERNAL_URL_DESCRIPTION_FIELD, externalUrlDescription); } public List getStudyAccessList() @@ -397,5 +408,15 @@ public void setStudyAccessList(User user, Container currentContainer) } } } + + public Map getPrimaryFields() + { + return _primaryFields; + } + + public void validate(Errors errors) + { +// TODO + } } diff --git a/src/org/labkey/trialshare/data/StudyEditBean.java b/src/org/labkey/trialshare/data/StudyEditBean.java new file mode 100644 index 00000000..f9977da8 --- /dev/null +++ b/src/org/labkey/trialshare/data/StudyEditBean.java @@ -0,0 +1,57 @@ +package org.labkey.trialshare.data; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by susanh on 6/23/16. + */ +public class StudyEditBean extends StudyBean +{ + private List _ageGroups = new ArrayList<>(); + private List _phases = new ArrayList<>(); + private List _conditions = new ArrayList<>(); + private List _therapeuticAreas = new ArrayList<>(); + + public List getAgeGroups() + { + return _ageGroups; + } + + public void setAgeGroups(List ageGroups) + { + _ageGroups = ageGroups; + } + + public List getConditions() + { + return _conditions; + } + + public void setConditions(List conditions) + { + this._conditions = conditions; + } + + public List getTherapeuticAreas() + { + return _therapeuticAreas; + } + + public void setTherapeuticAreas(List therapeuticAreas) + { + this._therapeuticAreas = therapeuticAreas; + } + + public List getPhases() + { + return _phases; + } + + public void setPhases(List phases) + { + _phases = phases; + } + + +} diff --git a/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java b/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java index cea61c95..15b15d59 100644 --- a/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java +++ b/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java @@ -1,6 +1,9 @@ package org.labkey.trialshare.query; +import org.labkey.api.data.Container; +import org.labkey.api.data.TableInfo; import org.labkey.api.query.QueryView; +import org.labkey.api.security.User; import org.labkey.api.view.ViewContext; import org.labkey.trialshare.TrialShareController; import org.springframework.validation.BindException; @@ -27,6 +30,11 @@ public ManagePublicationsQueryView(ViewContext context, BindException errors) setSettings(getSchema().getSettings(context, QueryView.DATAREGIONNAME_DEFAULT, TrialShareQuerySchema.PUBLICATION_TABLE)); } + protected TableInfo getTable(User user, Container container) + { + return new TrialShareQuerySchema(user, container).getPublicationsTableInfo(); + } + @Override protected TrialShareController.ObjectName getCubeObjectName() { return TrialShareController.ObjectName.publication; } diff --git a/src/org/labkey/trialshare/query/ManageStudiesQueryView.java b/src/org/labkey/trialshare/query/ManageStudiesQueryView.java index 922cda75..e7caf3eb 100644 --- a/src/org/labkey/trialshare/query/ManageStudiesQueryView.java +++ b/src/org/labkey/trialshare/query/ManageStudiesQueryView.java @@ -1,6 +1,9 @@ package org.labkey.trialshare.query; +import org.labkey.api.data.Container; +import org.labkey.api.data.TableInfo; import org.labkey.api.query.QueryView; +import org.labkey.api.security.User; import org.labkey.api.view.ViewContext; import org.labkey.trialshare.TrialShareController; import org.springframework.validation.BindException; @@ -27,6 +30,11 @@ public ManageStudiesQueryView(ViewContext context, BindException errors) setSettings(getSchema().getSettings(context, QueryView.DATAREGIONNAME_DEFAULT, TrialShareQuerySchema.STUDY_TABLE)); } + protected TableInfo getTable(User user, Container container) + { + return new TrialShareQuerySchema(user, container).getStudyPropertiesTableInfo(); + } + @Override protected TrialShareController.ObjectName getCubeObjectName() { diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index 9480f9ab..7c7c3e5d 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -1,6 +1,8 @@ package org.labkey.trialshare.query; import org.labkey.api.data.Container; +import org.labkey.api.data.SQLFragment; +import org.labkey.api.data.SqlSelector; import org.labkey.api.data.TableInfo; import org.labkey.api.query.DefaultSchema; import org.labkey.api.query.QuerySchema; @@ -8,6 +10,7 @@ import org.labkey.api.query.UserSchema; import org.labkey.api.security.User; import org.labkey.trialshare.TrialShareManager; +import org.labkey.trialshare.data.StudyBean; import java.util.ArrayList; import java.util.HashSet; @@ -37,7 +40,8 @@ public class TrialShareQuerySchema public static final String PUBLICATION_ID_FIELD = "PublicationId"; public static final String CONDITION_FIELD = "Condition"; public static final String STUDY_ID_FIELD = "StudyId"; - public static final String STUDY_SHORT_NAME_FIELD = "ShortName"; + public static final String AGE_GROUP_FIELD = "AgeGroup"; + public static final String PHASE_FIELD = "Phase"; public static final String THERAPEUTIC_AREA_FIELD = "TherapeuticArea"; // study visibility values @@ -178,6 +182,19 @@ public TableInfo getPublicationTherapeuticAreaTableInfo() return _listsSchema.getTable(TrialShareQuerySchema.PUBLICATION_THERAPEUTIC_AREA_TABLE); } + public List getPublicationStudies(Integer id) + { + SQLFragment sql = new SQLFragment("SELECT * FROM "); + sql.append(getPublicationStudyTableInfo(), "ps"); + sql.append(" LEFT JOIN "); + sql.append(getStudyPropertiesTableInfo(), "sp"); + sql.append(" ON ps.StudyId = sp.StudyId "); + sql.append( "WHERE ps.PublicationId = ? "); + sql.add(id); + + return new SqlSelector(getSchema().getDbSchema(), sql).getArrayList(StudyBean.class); + } + public List getStudyTables() { List list = new ArrayList<>(); list.add(getStudyPropertiesTableInfo()); diff --git a/src/org/labkey/trialshare/view/insertPublication.jsp b/src/org/labkey/trialshare/view/publicationDetails.jsp similarity index 85% rename from src/org/labkey/trialshare/view/insertPublication.jsp rename to src/org/labkey/trialshare/view/publicationDetails.jsp index 0f9e0806..eb7b917d 100644 --- a/src/org/labkey/trialshare/view/insertPublication.jsp +++ b/src/org/labkey/trialshare/view/publicationDetails.jsp @@ -17,7 +17,9 @@ %> <%@ page import="org.labkey.api.util.UniqueID" %> <%@ page import="org.labkey.api.view.HttpView" %> +<%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.api.view.template.ClientDependency" %> +<%@ page import="org.labkey.trialshare.TrialShareController" %> <%@ page import="java.util.LinkedHashSet" %> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%@ page extends="org.labkey.api.jsp.JspBase"%> @@ -34,7 +36,9 @@ } %> <% - String renderId = "insert-publication-" + UniqueID.getRequestScopedUID(HttpView.currentRequest()); + TrialShareController.CubeObjectDetailBean bean = ((JspView) HttpView.currentView()).getModelBean(); + + String renderId = "publication-details-" + UniqueID.getRequestScopedUID(HttpView.currentRequest()); %>
@@ -42,22 +46,29 @@ \ No newline at end of file From 789cefd6356209c129f87c2f0731075c7fcb5ecc Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 19:57:24 -0700 Subject: [PATCH 298/587] Spec 26558: add validation for study bean; update validation error response for field matching --- .../queries/lists/StudyProperties.query.xml | 18 +++++++++--------- .../Finder/panel/JunctionEditFormPanel.js | 8 ++++++-- src/org/labkey/trialshare/data/StudyBean.java | 9 ++++++++- .../trialshare/data/StudyPublicationBean.java | 12 ++++++------ .../trialshare/view/publicationDetails.jsp | 7 ------- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/resources/queries/lists/StudyProperties.query.xml b/resources/queries/lists/StudyProperties.query.xml index c37f3101..7e4c7ee1 100644 --- a/resources/queries/lists/StudyProperties.query.xml +++ b/resources/queries/lists/StudyProperties.query.xml @@ -33,15 +33,15 @@ false - - - - - - - - - + + + lists + StudyTherapeuticArea + studyId + junction + TherapeuticArea + + diff --git a/resources/web/study/Finder/panel/JunctionEditFormPanel.js b/resources/web/study/Finder/panel/JunctionEditFormPanel.js index 5bc3088e..ecdd1bd3 100644 --- a/resources/web/study/Finder/panel/JunctionEditFormPanel.js +++ b/resources/web/study/Finder/panel/JunctionEditFormPanel.js @@ -44,7 +44,7 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { }, shouldShowInView: function(metadata) { - if (this.mode == "edit") + if (this.mode == "edit" || this.mode == "insert") return this.shouldShowInInsertView(metadata); else return this.shouldShowInDisplayView(metadata); @@ -176,7 +176,11 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { obj = Ext4.JSON.decode(response.responseText); for (var i = 0; i < obj.errors.length; i++) { - this.getForm().findField(obj.errors[i].field).markInvalid([obj.errors[i].message]); + var field = this.getForm().findField(obj.errors[i].field); + if (field) + field.markInvalid([obj.errors[i].message]); + else + console.log("Unable to find field for invalidation", obj.errors[i]); } Ext4.Msg.alert("Error", "There were problems submitting your data. Please check the form for errors."); } diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index 0b1159bd..020cfa69 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -41,6 +41,8 @@ import java.util.List; import java.util.Map; +import static org.labkey.api.action.SpringActionController.ERROR_REQUIRED; + /** * Created by susanh on 12/7/15. */ @@ -416,7 +418,12 @@ public Map getPrimaryFields() public void validate(Errors errors) { -// TODO + if (getShortName() == null) + errors.rejectValue("ShortName", ERROR_REQUIRED, "Short Name is required"); + if (getStudyId() == null) + errors.rejectValue("StudyId", ERROR_REQUIRED, "Study Id is required"); + if (getTitle() == null) + errors.rejectValue("Title", ERROR_REQUIRED, "Title is required"); } } diff --git a/src/org/labkey/trialshare/data/StudyPublicationBean.java b/src/org/labkey/trialshare/data/StudyPublicationBean.java index 9ab9bee2..3e040646 100644 --- a/src/org/labkey/trialshare/data/StudyPublicationBean.java +++ b/src/org/labkey/trialshare/data/StudyPublicationBean.java @@ -530,22 +530,22 @@ public Map getPrimaryFields() public void validate(Errors errors) { if (getTitle() == null) - errors.rejectValue("title", ERROR_REQUIRED, "Title field is required"); + errors.rejectValue("Title", ERROR_REQUIRED, "Title is required"); if (getStatus() == null) - errors.rejectValue("status", ERROR_REQUIRED, "Status field is required"); + errors.rejectValue("Status", ERROR_REQUIRED, "Status is required"); if (getPublicationType() == null) - errors.rejectValue("publicationType", ERROR_REQUIRED, "Publication Type field is required"); + errors.rejectValue("PublicationType", ERROR_REQUIRED, "Publication type is required"); if (getPmid() != null && !StringUtils.isNumeric(getPmid())) { - errors.rejectValue("pmid", ERROR_CONVERSION, "PMID must be an integer"); + errors.rejectValue("PMID", ERROR_CONVERSION, "PMID must be an integer"); } if (getPmcid() != null && !PMCID_PATTERN.matcher(getPmcid()).matches()) { - errors.rejectValue("pmcid", ERROR_CONVERSION, "Incorrect format for PMCID. Expected PMC#"); + errors.rejectValue("PMCID", ERROR_CONVERSION, "Incorrect format for PMCID. Expected PMC#"); } if (getYear() != null && !StringUtils.isNumeric(getPmid())) { - errors.rejectValue("year", ERROR_CONVERSION, "Year must be an integer"); + errors.rejectValue("Year", ERROR_CONVERSION, "Year must be an integer"); } } diff --git a/src/org/labkey/trialshare/view/publicationDetails.jsp b/src/org/labkey/trialshare/view/publicationDetails.jsp index eb7b917d..b01cce27 100644 --- a/src/org/labkey/trialshare/view/publicationDetails.jsp +++ b/src/org/labkey/trialshare/view/publicationDetails.jsp @@ -111,16 +111,9 @@ TherapeuticAreas : { width: 800 }, - DOI : { - name: 'doi' - }, PMID : { - name: 'pmid', xtype: 'textfield' }, - PMCID : { - name: 'pmcid' - }, AbstractText : { width: 1000, height: 100, From 5b50d96d135bb780951ad204aa0ae57d5a093a62 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 20:06:07 -0700 Subject: [PATCH 299/587] Spec 26558: adjust display for therapeutic areas selection --- resources/queries/lists/StudyProperties.query.xml | 3 ++- src/org/labkey/trialshare/view/publicationDetails.jsp | 2 +- src/org/labkey/trialshare/view/studyDetails.jsp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/queries/lists/StudyProperties.query.xml b/resources/queries/lists/StudyProperties.query.xml index 7e4c7ee1..c4177c35 100644 --- a/resources/queries/lists/StudyProperties.query.xml +++ b/resources/queries/lists/StudyProperties.query.xml @@ -37,10 +37,11 @@ lists StudyTherapeuticArea - studyId + StudyId junction TherapeuticArea + false diff --git a/src/org/labkey/trialshare/view/publicationDetails.jsp b/src/org/labkey/trialshare/view/publicationDetails.jsp index b01cce27..d6024b44 100644 --- a/src/org/labkey/trialshare/view/publicationDetails.jsp +++ b/src/org/labkey/trialshare/view/publicationDetails.jsp @@ -109,7 +109,7 @@ width: 800 }, TherapeuticAreas : { - width: 800 + width: 500 }, PMID : { xtype: 'textfield' diff --git a/src/org/labkey/trialshare/view/studyDetails.jsp b/src/org/labkey/trialshare/view/studyDetails.jsp index 18f13b75..5a55c4f7 100644 --- a/src/org/labkey/trialshare/view/studyDetails.jsp +++ b/src/org/labkey/trialshare/view/studyDetails.jsp @@ -96,7 +96,7 @@ width: 800 }, TherapeuticAreas : { - width: 800 + width: 500 }, Phases : { width: 500 From 488971f3c53ae50e748e0ed0e2deaee85b703604 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 20:07:25 -0700 Subject: [PATCH 300/587] Spec 26558: add experimental store with junctions --- .../study/Finder/data/StoreWithJuncitons.js | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 resources/web/study/Finder/data/StoreWithJuncitons.js diff --git a/resources/web/study/Finder/data/StoreWithJuncitons.js b/resources/web/study/Finder/data/StoreWithJuncitons.js new file mode 100644 index 00000000..08c430d6 --- /dev/null +++ b/resources/web/study/Finder/data/StoreWithJuncitons.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +Ext4.define('LABKEY.study.store.CubeObjects', { + extend: 'LABKEY.ext4.data.Store', + //private + setModel: function (model) + { + // NOTE: if the query lacks a PK, which can happen with queries that dont represent physical tables, + // Ext adds a column to hold an Id. In order to differentiate this from other fields we set defaults + this.model.prototype.fields.each(function (field) + { + if (field.name == '_internalId') + { + Ext4.apply(field, { + hidden: true, + calculatedField: true, + shownInInsertView: false, + shownInUpdateView: false, + userEditable: false + }); + } + if (field.lookup && field.lookup.multiValued !== undefined) + { + Ext4.apply(field, { + editable: true, + facetingBehaviorType: "AUTOMATIC", + multiSelect : true + }) + } + }); + this.model = model; + this.implicitModel = false; + }, +}); \ No newline at end of file From 3eb6c18131868decd9f56174953e399e4ddb485c Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 21:29:11 -0700 Subject: [PATCH 301/587] Issue 26708: filter out accessible members that are not currently in the cube before making countDistinct query --- resources/web/study/Finder/data/Facets.js | 41 +++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index a2d03989..c193c6c2 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -208,12 +208,15 @@ Ext4.define('LABKEY.study.store.Facets', { var filters = {}; for (var level in o.data) { + if (!o.data.hasOwnProperty(level)) + continue; if (Ext4.isObject(o.data[level]) || (Ext4.isArray(o.data[level]) && o.data[level].length)) { + var includedMembers = this.removeMembersNotInCube(level, o.data[level]); if (!filters[level]) - filters[level] = o.data[level]; + filters[level] = includedMembers; else - filters[level] = filters[level].concat(o.data[level]); + filters[level] = filters[level].concat(includedMembers); } } this.makeCountDistinctQueries(filters); @@ -229,6 +232,40 @@ Ext4.define('LABKEY.study.store.Facets', { } }, + removeMembersNotInCube : function(level, filterData) + { + if (!filterData) + return filterData; + if (Ext4.isArray(filterData)) + { + // split the level name into its parts to use for accessing the cube members + var levelParts = level.replace(/[\[\]]/g, "").split("."); + if (levelParts && levelParts.length > 1) + { + // find the uniqueNames of the current members of the cube for the given level + var uniqueNames = this.mdx._cube.hierarchyMap[levelParts[0]].levelMap[levelParts[1]].members.map(function (m) + { + return m.uniqueName + }); + // filter out the accessible members that are not currently part of the cube + var includedMembers = filterData.filter(function (m) + { + return uniqueNames.indexOf(m) >= 0; + }); + } + + } + else if (Ext4.isObject(filterData)) + { + for (var nextLevel in filterData) + { + if (filterData.hasOwnProperty(nextLevel)) + filterData[nextLevel] = this.removeMembersNotInCube(nextLevel, filterData[nextLevel]); + } + return filterData; + } + }, + makeCountDistinctQueries : function(filtersMap) { this.cellSetPositions = null; From 62713f62a1a1d4d48c2622c22817f9e97141cd02 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 21:46:44 -0700 Subject: [PATCH 302/587] Issue 26794: Don't index publications that are in progress and don't have a permissions container. --- src/org/labkey/trialshare/PublicationDocumentProvider.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index 60848e2f..18cb7e6c 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -39,6 +39,8 @@ import java.util.HashMap; import java.util.Map; +import static org.labkey.trialshare.query.TrialShareQuerySchema.IN_PROGRESS_STATUS; + public class PublicationDocumentProvider implements SearchService.DocumentProvider { private static final Logger _logger = LoggerFactory.getLogger(PublicationDocumentProvider.class); @@ -132,7 +134,12 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container else if (results.getString("ManuscriptContainer") != null) containerId = results.getString("ManuscriptContainer"); else + { + // Issue 26794: don't index publications that are in progress and do not have a permissions container + if (IN_PROGRESS_STATUS.equals(results.getString("Status"))) + continue; containerId = ContainerManager.getHomeContainer().getId(); + } ActionURL url = new ActionURL(TrialShareController.PublicationDetailsAction.class, c).addParameter("id", results.getString("PublicationId")); url.setExtraPath(containerId); From cf1c4b5dbd267d4bf77f74ed68dc079490af9a86 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 22:04:41 -0700 Subject: [PATCH 303/587] Issue 26433: Add and href for the go to study links in the dropdown. --- resources/web/study/Finder/panel/StudyCards.js | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index b99362ac..f11a63c2 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -182,6 +182,7 @@ Ext4.define("LABKEY.study.panel.StudyCards", { for (var i = 0; i < studyLinks.length; i++) { studyLinksMenu.add({ text: studyLinks[i].displayName ? studyLinks[i].displayName : studyLinks[i].studyContainerPath, + href: LABKEY.ActionURL.getContextPath() + studyLinks[i].studyContainerPath, value: studyLinks[i].studyContainerPath }); } From 001567140a67b9e65ab7312c346cbccec6674c64 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 23 Jun 2016 22:17:24 -0700 Subject: [PATCH 304/587] Issue 26554: remove HTML encoding for abstract text --- resources/web/study/Finder/panel/PublicationCards.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/web/study/Finder/panel/PublicationCards.js b/resources/web/study/Finder/panel/PublicationCards.js index e54fac1c..f1453c94 100644 --- a/resources/web/study/Finder/panel/PublicationCards.js +++ b/resources/web/study/Finder/panel/PublicationCards.js @@ -112,7 +112,7 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { ' ', '
', ' Abstract', - ' {abstractText:htmlEncode}', + ' {abstractText}', '
', '
', ' ', From 03203875d6fccbfe50c93b3b3dfc75870122a4f4 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 24 Jun 2016 09:50:00 -0700 Subject: [PATCH 305/587] Issue 26708: helps if you return the value you calculated --- resources/web/study/Finder/data/Facets.js | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js index c193c6c2..c4f47464 100644 --- a/resources/web/study/Finder/data/Facets.js +++ b/resources/web/study/Finder/data/Facets.js @@ -252,6 +252,7 @@ Ext4.define('LABKEY.study.store.Facets', { { return uniqueNames.indexOf(m) >= 0; }); + return includedMembers; } } From 934a126d5b587c1938211e29ff5cb1b078c3cbbe Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 24 Jun 2016 14:09:51 -0700 Subject: [PATCH 306/587] Spec 26558: add styling for form labels. --- resources/web/study/Finder/dataFinder.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index dda23349..b2abb1a7 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -1,4 +1,3 @@ - .labkey-data-finder-view { padding-left: 2px; @@ -33,6 +32,11 @@ padding-bottom: 20px; } +.labkey-field-editor .x4-field-label-cell +{ + background-color: #E7EFF4; +} + .labkey-studies-panel, .labkey-publications-panel { From b7ae19be8298b97eb6ea26f10f32049c1daee9dc Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 24 Jun 2016 14:28:27 -0700 Subject: [PATCH 307/587] Issue 26554: prevent data finder window from following go to study link --- resources/web/study/Finder/panel/StudyCards.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/resources/web/study/Finder/panel/StudyCards.js b/resources/web/study/Finder/panel/StudyCards.js index f11a63c2..aac9bb13 100644 --- a/resources/web/study/Finder/panel/StudyCards.js +++ b/resources/web/study/Finder/panel/StudyCards.js @@ -173,16 +173,11 @@ Ext4.define("LABKEY.study.panel.StudyCards", { showSeparator: false }); - studyLinksMenu.on('click', function(menu, item, e, eOpts) { - window.open(LABKEY.ActionURL.buildURL("project", 'begin.view', item.value)); - }, - this - ); - for (var i = 0; i < studyLinks.length; i++) { studyLinksMenu.add({ text: studyLinks[i].displayName ? studyLinks[i].displayName : studyLinks[i].studyContainerPath, href: LABKEY.ActionURL.getContextPath() + studyLinks[i].studyContainerPath, + hrefTarget: "_blank", value: studyLinks[i].studyContainerPath }); } From db4b3d7dcdbba66a1777e33fa11bf411a0027837 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 26 Jun 2016 16:24:22 -0700 Subject: [PATCH 308/587] Issue 26554: use proper key for getting values to delete from join tables. --- src/org/labkey/trialshare/TrialShareManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index aee14d04..a0ab6155 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -267,13 +267,13 @@ public void updatePublication(User user, Container container, PublicationEditBea // update the many-to-one data // conditions // first get rid of the current values for this publication - schema.getPublicationConditionTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationConditionTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, filter), null, null); + schema.getPublicationConditionTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationConditionTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); addJoinTableData(schema.getPublicationConditionTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId(), TrialShareQuerySchema.CONDITION_FIELD, publication.getConditions(), user, container); - schema.getPublicationStudyTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationStudyTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, filter), null, null); + schema.getPublicationStudyTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationStudyTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); addJoinTableData(schema.getPublicationStudyTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId(), TrialShareQuerySchema.STUDY_ID_FIELD, publication.getStudyIds(), user, container); - schema.getPublicationTherapeuticAreaTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationTherapeuticAreaTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, filter), null, null); + schema.getPublicationTherapeuticAreaTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationTherapeuticAreaTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); addJoinTableData(schema.getPublicationTherapeuticAreaTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId(), TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, publication.getTherapeuticAreas(), user, container); transaction.commit(); } From 003ae3e69eb0f51a7ae6f7f67a8ad3dc0a03aa51 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 26 Jun 2016 16:25:09 -0700 Subject: [PATCH 309/587] Spec 26558: use proper key when getting values to delete from join tables --- src/org/labkey/trialshare/query/TrialShareQuerySchema.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index 7c7c3e5d..1c9fbc89 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -36,7 +36,8 @@ public class TrialShareQuerySchema public static final String PUBLICATION_STUDY_TABLE = "PublicationStudy"; public static final String PUBLICATION_THERAPEUTIC_AREA_TABLE = "PublicationTherapeuticArea"; - public static final String PUBLICATION_KEY_FIELD = "Key"; // this is the name of the key field in the publicaiton table itself + public static final String PUBLICATION_KEY_FIELD = "Key"; // this is the name of the key field in the publication table itself + public static final String KEY_FIELD = "Key"; public static final String PUBLICATION_ID_FIELD = "PublicationId"; public static final String CONDITION_FIELD = "Condition"; public static final String STUDY_ID_FIELD = "StudyId"; From 9ccdafb6f09d824473e1b707d691b2d45eab2c1c Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 26 Jun 2016 18:35:11 -0700 Subject: [PATCH 310/587] Spec 26558: don't inlcude redundant _primaryKeys field in Json serialization --- src/org/labkey/trialshare/data/StudyPublicationBean.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/org/labkey/trialshare/data/StudyPublicationBean.java b/src/org/labkey/trialshare/data/StudyPublicationBean.java index 3e040646..1623d179 100644 --- a/src/org/labkey/trialshare/data/StudyPublicationBean.java +++ b/src/org/labkey/trialshare/data/StudyPublicationBean.java @@ -15,6 +15,7 @@ */ package org.labkey.trialshare.data; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.apache.commons.lang3.StringUtils; import org.apache.commons.validator.routines.UrlValidator; import org.labkey.api.collections.CaseInsensitiveHashMap; @@ -521,6 +522,7 @@ else if (permissionsContainer.hasPermission(user, ReadPermission.class)) return false; } + @JsonIgnore public Map getPrimaryFields() { return _primaryFields; From 1196f1371ae9c855da841b456c9c8eb0f0232b72 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 26 Jun 2016 18:36:47 -0700 Subject: [PATCH 311/587] Spec 26558: don't show buttons in view mode; attempt to convert multi-value string values --- .../Finder/panel/JunctionEditFormPanel.js | 69 ++++++++++++++----- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/resources/web/study/Finder/panel/JunctionEditFormPanel.js b/resources/web/study/Finder/panel/JunctionEditFormPanel.js index ecdd1bd3..40de5b27 100644 --- a/resources/web/study/Finder/panel/JunctionEditFormPanel.js +++ b/resources/web/study/Finder/panel/JunctionEditFormPanel.js @@ -14,21 +14,49 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { initComponent : function() { - this.returnUrl = LABKEY.ActionURL.buildURL('trialShare', 'manageData.view', null, {objectName : this.objectName, 'query.viewName': 'manageData'}); - - this.dockedItems = [{ - xtype: 'toolbar', - dock: 'bottom', - ui: 'footer', - style: 'background-color: transparent;', - items: [ - LABKEY.ext4.FORMBUTTONS.getButton('SUBMIT', {successURL : this.returnUrl}), - LABKEY.ext4.FORMBUTTONS.getButton('CANCEL', {returnURL : this.returnUrl}) - ] - }]; + this.manageDataUrl = LABKEY.ActionURL.buildURL('trialShare', 'manageData.view', null, {objectName : this.objectName, 'query.viewName': 'manageData'}); + + if (this.mode == "view") + this.dockedItems = []; + else + this.dockedItems = [this.getToolBar()]; this.callParent(); }, + getToolBar : function() + { + if (!this.toolbar) + { + this.toolBar = { + xtype: 'toolbar', + dock: 'bottom', + ui: 'footer', + style: 'background-color: transparent;' + }; + this.toolBar.items = [ + { + text: 'Submit', + formBind: true, + successURL: LABKEY.ActionURL.getParameter('returnUrl') || this.manageDataUrl, + handler: function (btn) + { + var panel = btn.up('form'); + panel.doSubmit(btn); + } + }, + { + text: 'Cancel', + returnUrl: LABKEY.ActionURL.getParameter('returnUrl') || this.manageDataUrl, + handler: function (btn, key) + { + window.location = btn.returnUrl; + } + } + ]; + } + return this.toolBar; + }, + shouldShowInInsertView: function(metadata) { return this.isJoinTableField(metadata.name) || LABKEY.ext4.Util.shouldShowInUpdateView(metadata); }, @@ -60,6 +88,14 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { field.editable = true; field.facetingBehaviorType = "AUTOMATIC"; field.multiSelect = true; + field.convert = function(v, record) + { + console.log(v, record); + if (Ext4.isArray(v)) + return v; + else if (Ext4.isString(v)) + return v.split(","); + } } } }, @@ -69,11 +105,6 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { return this.joinTableFields.indexOf(fieldName) >= 0; }, - uncapitalize : function(name) - { - return name && name[0].toLowerCase() + name.slice(1) - }, - /** Override **/ configureForm: function(store){ this.configureJoinFields(store); @@ -102,6 +133,8 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { if (this.shouldShowInView(field)){ var fieldEditor = LABKEY.ext4.Util.getFormEditorConfig(field, config); + fieldEditor.cls = 'labkey-field-editor'; + fieldEditor.labelCls ='labkey-field-editor-label'; if (fieldEditor.isRequired && this.mode != "view") fieldEditor.fieldLabel = fieldEditor.fieldLabel + " *"; if (!fieldEditor.width) @@ -122,7 +155,7 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { fieldEditor.store.containerFilter = fieldEditor.containerFilter; fieldEditor.multiSelect = field.multiSelect; fieldEditor.store.autoLoad = true; - fieldEditor.delimiter = '; ' + fieldEditor.delimiter = '; '; } if (field.isAutoIncrement){ From 988b3228ae15332c4c936b6bee32af464a91cea0 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 26 Jun 2016 21:09:29 -0700 Subject: [PATCH 312/587] Spec 26558: remove use of StudyId and Study column from ManuscriptsAndAbstracts list --- .../PublicationDocumentProvider.java | 23 +- .../trialshare/TrialShareController.java | 223 +++++++++++++++++- .../query/TrialShareQuerySchema.java | 25 ++ 3 files changed, 256 insertions(+), 15 deletions(-) diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index 305c3d27..0dbee187 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -78,19 +78,22 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container "pub.Journal, " + "pub.Status, " + "pub.SubmissionStatus, " + - "pub.Study as PrimaryStudy, " + - "pub.StudyId as PrimaryStudyId, " + "pub.AbstractText, " + "pub.Keywords, " + "pub.PermissionsContainer, " + "pub.ManuscriptContainer, " + "pc.Condition, " + - "ps.ShortName as StudyShortName, " + - "ps.StudyId, " + - "pta.TherapeuticArea " + - "FROM ManuscriptsAndAbstracts pub " + - " LEFT JOIN (SELECT PublicationId, group_concat(Condition) AS Condition FROM PublicationCondition GROUP BY PublicationId) pc on pub.Key = pc.PublicationId " + - " LEFT JOIN (SELECT PublicationId, ShortName, group_concat(StudyId) AS StudyId FROM PublicationStudy GROUP BY ShortName, PublicationId) ps on pub.Key = ps.PublicationId " + + "psid.StudyId, " + + "psn.StudyShortName, " + + "pta.TherapeuticArea\n " + + "FROM ManuscriptsAndAbstracts pub\n " + + " LEFT JOIN (SELECT PublicationId, group_concat(Condition) AS Condition FROM PublicationCondition GROUP BY PublicationId) pc on pub.Key = pc.PublicationId\n " + + " LEFT JOIN (SELECT PublicationId, group_concat(StudyId) AS StudyId FROM\n " + + " (SELECT sp1.StudyId AS StudyId, ps1.PublicationId FROM PublicationStudy ps1 LEFT JOIN StudyProperties sp1 on ps1.StudyId = sp1.StudyId) " + + " GROUP BY PublicationId) psid on pub.Key = psid.PublicationId\n " + + " LEFT JOIN (SELECT PublicationId, group_concat(StudyShortName) as StudyShortName FROM\n " + + " (SELECT sp2.ShortName AS StudyShortName, ps2.PublicationId FROM PublicationStudy ps2 LEFT JOIN StudyProperties sp2 on ps2.StudyId = sp2.StudyId) " + + " GROUP BY StudyShortName, PublicationId) psn on pub.Key = psn.PublicationId\n " + " LEFT JOIN (SELECT PublicationId, group_concat(TherapeuticArea) AS TherapeuticArea FROM PublicationTherapeuticArea GROUP BY PublicationId) pta on pub.Key = pta.PublicationId " + "WHERE pub.Show = true "; @@ -110,13 +113,13 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container StringBuilder keywords = new StringBuilder(); // See #26028: identifiers that have punctuation in them (e.g., DOI) are not indexed well as identifiers, so we use keywords instead - for (String field : new String[]{"Author", "Year", "Status", "PrimaryStudy", "Title", "SubmissionStatus", "PublicationType", "Journal", "TherapeuticArea", "StudyShortName", "Condition", "DOI"}) + for (String field : new String[]{"Author", "Year", "Status", "Title", "SubmissionStatus", "PublicationType", "Journal", "TherapeuticArea", "StudyShortName", "Condition", "DOI"}) { if (results.getString(field) != null) keywords.append(results.getString(field)).append(" "); } StringBuilder identifiers = new StringBuilder(); - for (String field : new String[]{"PMID", "PMCID", "StudyId", "PrimaryStudyId"}) + for (String field : new String[]{"PMID", "PMCID", "StudyId"}) { if (results.getString(field) != null) identifiers.append(results.getString(field)).append(" " ); diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index db77ffde..8ff5da94 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -33,11 +33,16 @@ import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; import org.labkey.api.data.DataRegionSelection; +import org.labkey.api.data.SimpleFilter; import org.labkey.api.data.TableInfo; import org.labkey.api.data.TableSelector; +import org.labkey.api.query.FieldKey; import org.labkey.api.query.QueryForm; import org.labkey.api.query.QuerySchema; import org.labkey.api.query.QueryService; +import org.labkey.api.query.QuerySettings; +import org.labkey.api.query.QueryView; +import org.labkey.api.query.UserSchema; import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.AdminPermission; import org.labkey.api.security.permissions.DeletePermission; @@ -456,8 +461,8 @@ public class StudiesAction extends ApiAction @Override public Object execute(Object form, BindException errors) throws Exception { - // TODO update to not use static methods - QuerySchema listsSchema = TrialShareQuerySchema.getSchema(getUser(), getContainer()); + TrialShareQuerySchema schema = new TrialShareQuerySchema(getUser(), getContainer()); + QuerySchema listsSchema =schema.getSchema(); TableInfo studyProperties = listsSchema.getTable(TrialShareQuerySchema.STUDY_TABLE); if (studyProperties != null) @@ -467,7 +472,7 @@ public Object execute(Object form, BindException errors) throws Exception Map> pubCounts = new HashMap<>(); if (publicationsList != null) { - List publications = (new TableSelector(publicationsList).getArrayList(StudyPublicationBean.class)); + List publications = schema.getStudyPublications(); for (StudyPublicationBean pub : publications) { @@ -792,6 +797,202 @@ public void setDetailType(DetailType detailType) } } + @RequiresPermission(ReadPermission.class) + public class StudyDetailsAction extends ApiAction + { + String _id; + + @Override + public void validateForm(StudyIdForm form, Errors errors) + { + _id = (null == form) ? null : form.getStudyId(); + if (_id == null) + errors.reject(ERROR_MSG, "Study not specified"); + } + + @Override + public Object execute(StudyIdForm sform, BindException errors) throws Exception + { + TrialShareQuerySchema schema = new TrialShareQuerySchema(getUser(), getContainer()); + QuerySchema listSchema = schema.getSchema(); + StudyEditBean study = (new TableSelector(listSchema.getTable(TrialShareQuerySchema.STUDY_TABLE))).getObject(_id, StudyEditBean.class); + study.setStudyAccessList(getUser(), getContainer()); + study.setUrl(getUser(), true); + study.setPublications(getUser(), getContainer(), null); + return success(study); + } + + } + +// @ActionNames("selectRows, getQuery") +// @RequiresPermission(ReadPermission.class) +// @ApiVersion(9.1) +// @Action(ActionType.SelectData.class) +// public class SelectRowsAction extends ApiAction +// { +// public ApiResponse execute(APIQueryForm form, BindException errors) throws Exception +// { +//// ensureQueryExists(form); +// +// // Issue 12233: add implicit maxRows=100k when using client API +// if (null == form.getLimit() +// && null == getViewContext().getRequest().getParameter(form.getDataRegionName() + "." + QueryParam.maxRows) +// && null == getViewContext().getRequest().getParameter(form.getDataRegionName() + "." + QueryParam.showRows)) +// { +// form.getQuerySettings().setShowRows(ShowRows.PAGINATED); +// form.getQuerySettings().setMaxRows(100); +// } +// +// if (form.getLimit() != null) +// { +// form.getQuerySettings().setShowRows(ShowRows.PAGINATED); +// form.getQuerySettings().setMaxRows(form.getLimit()); +// } +// if (form.getStart() != null) +// form.getQuerySettings().setOffset(form.getStart()); +// if (form.getContainerFilter() != null) +// { +// // If the user specified an incorrect filter, throw an IllegalArgumentException +// ContainerFilter.Type containerFilterType = +// ContainerFilter.Type.valueOf(form.getContainerFilter()); +// form.getQuerySettings().setContainerFilterName(containerFilterType.name()); +// } +// +// QueryView view = QueryView.create(form, errors); +// +// view.setShowPagination(form.isIncludeTotalCount()); +// +// //if viewName was specified, ensure that it was actually found and used +// //QueryView.create() will happily ignore an invalid view name and just return the default view +// if (null != StringUtils.trimToNull(form.getViewName()) && +// null == view.getQueryDef().getCustomView(getUser(), getViewContext().getRequest(), form.getViewName())) +// { +// throw new NotFoundException("The view named '" + form.getViewName() + "' does not exist for this user!"); +// } +// +// boolean isEditable = false; +// boolean metaDataOnly = form.getQuerySettings().getMaxRows() == 0; +// +// // 13.2 introduced the getData API action, a condensed response wire format, and a js wrapper to consume the wire format. Support this as an option for legacy API's. +// if (getRequestedApiVersion() >= 13.2) +// { +// ReportingApiQueryResponse response = new ReportingApiQueryResponse(view, isEditable, true, view.getQueryDef().getName(), form.getQuerySettings().getOffset(), null, +// metaDataOnly, form.isIncludeDetailsColumn(), form.isIncludeUpdateColumn()); +// response.includeStyle(form.isIncludeStyle()); +// return response; +// } +// //if requested version is >= 9.1, use the extended api query response +// else if (getRequestedApiVersion() >= 9.1) +// { +// ExtendedApiQueryResponse response = new ExtendedApiQueryResponse(view, isEditable, true, +// form.getSchemaName(), form.getQueryName(), form.getQuerySettings().getOffset(), null, +// metaDataOnly, form.isIncludeDetailsColumn(), form.isIncludeUpdateColumn()); +// response.includeStyle(form.isIncludeStyle()); +// return response; +// } +// else +// { +// return new ApiQueryResponse(view, isEditable, true, +// form.getSchemaName(), form.getQueryName(), form.getQuerySettings().getOffset(), null, +// metaDataOnly, form.isIncludeDetailsColumn(), form.isIncludeUpdateColumn(), +// form.isIncludeDisplayValues()); +// } +// } +// } +// +// public static class APIQueryForm extends QueryForm +// { +// private Integer _start; +// private Integer _limit; +// private boolean _includeDetailsColumn = false; +// private boolean _includeUpdateColumn = false; +// private String _containerFilter; +// private boolean _includeTotalCount = true; +// private boolean _includeStyle = false; +// private boolean _includeDisplayValues = false; +// +// public Integer getStart() +// { +// return _start; +// } +// +// public void setStart(Integer start) +// { +// _start = start; +// } +// +// public Integer getLimit() +// { +// return _limit; +// } +// +// public void setLimit(Integer limit) +// { +// _limit = limit; +// } +// +// public String getContainerFilter() +// { +// return _containerFilter; +// } +// +// public void setContainerFilter(String containerFilter) +// { +// _containerFilter = containerFilter; +// } +// +// public boolean isIncludeTotalCount() +// { +// return _includeTotalCount; +// } +// +// public void setIncludeTotalCount(boolean includeTotalCount) +// { +// _includeTotalCount = includeTotalCount; +// } +// +// public boolean isIncludeStyle() +// { +// return _includeStyle; +// } +// +// public void setIncludeStyle(boolean includeStyle) +// { +// _includeStyle = includeStyle; +// } +// +// public boolean isIncludeDetailsColumn() +// { +// return _includeDetailsColumn; +// } +// +// public void setIncludeDetailsColumn(boolean includeDetailsColumn) +// { +// _includeDetailsColumn = includeDetailsColumn; +// } +// +// public boolean isIncludeUpdateColumn() +// { +// return _includeUpdateColumn; +// } +// +// public void setIncludeUpdateColumn(boolean includeUpdateColumn) +// { +// _includeUpdateColumn = includeUpdateColumn; +// } +// +// public boolean isIncludeDisplayValues() +// { +// return _includeDisplayValues; +// } +// +// public void setIncludeDisplayValues(boolean includeDisplayValues) +// { +// _includeDisplayValues = includeDisplayValues; +// } +// } + + @RequiresPermission(ReadPermission.class) public class PublicationDetailsAction extends ApiAction { @@ -822,7 +1023,7 @@ public Object execute(PublicationIdForm form, BindException errors) throws Excep publication.setStudies(schema.getPublicationStudies(_id)); - for (StudyBean study : publication.getStudies()) + for (StudyBean study : publication.getStudies()) { study.setStudyAccessList(getUser(), getContainer()); study.setUrl(getUser(), false); @@ -1182,7 +1383,19 @@ public ModelAndView getView(CubeObjectDetailBean bean, BindException errors) thr if (bean.getObjectName() == ObjectName.publication) return new JspView("/org/labkey/trialshare/view/publicationDetails.jsp", bean); else - return new JspView("/org/labkey/trialshare/view/studyDetails.jsp", bean); + { + JspView view = new JspView("/org/labkey/trialshare/view/studyDetails.jsp", bean); + + UserSchema userSchema = TrialShareQuerySchema.getUserSchema(getUser(), getContainer()); + + QuerySettings settings = userSchema.getSettings(getViewContext(), QueryView.DATAREGIONNAME_DEFAULT, TrialShareQuerySchema.STUDY_ACCESS_TABLE); + SimpleFilter filter = settings.getBaseFilter(); + filter.addAllClauses(new SimpleFilter(FieldKey.fromParts("StudyId"), (String) bean.getId())); + QueryView queryView = userSchema.createView(getViewContext(), settings, errors); + view.setTitle("Study Access"); + view.setView("studiesAccessView", queryView); + return view; + } } @Override diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index 1c9fbc89..ec1926c8 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -11,6 +11,7 @@ import org.labkey.api.security.User; import org.labkey.trialshare.TrialShareManager; import org.labkey.trialshare.data.StudyBean; +import org.labkey.trialshare.data.StudyPublicationBean; import java.util.ArrayList; import java.util.HashSet; @@ -196,6 +197,30 @@ public List getPublicationStudies(Integer id) return new SqlSelector(getSchema().getDbSchema(), sql).getArrayList(StudyBean.class); } + public List getStudyPublications() + { + SQLFragment sql = new SQLFragment("SELECT pub.*, ps.StudyId FROM " ); + sql.append(getPublicationStudyTableInfo(), "ps"); + sql.append(" LEFT JOIN "); + sql.append(getPublicationsTableInfo(), "pub"); + sql.append(" ON ps.PublicationId = pub.Key "); + + return new SqlSelector(getSchema().getDbSchema(), sql).getArrayList(StudyPublicationBean.class); + } + + public List getStudyPublications(String studyId) + { + SQLFragment sql = new SQLFragment("SELECT pub.*, ps.StudyId FROM " ); + sql.append(getPublicationStudyTableInfo(), "ps"); + sql.append(" LEFT JOIN "); + sql.append(getPublicationsTableInfo(), "pub"); + sql.append(" ON ps.PublicationId = pub.Key "); + sql.append( "WHERE ps.StudyId = ? "); + sql.add(studyId); + + return new SqlSelector(getSchema().getDbSchema(), sql).getArrayList(StudyPublicationBean.class); + } + public List getStudyTables() { List list = new ArrayList<>(); list.add(getStudyPropertiesTableInfo()); From 8669c30a2ffae1368b70575322b660b920162098 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 27 Jun 2016 10:41:53 -0700 Subject: [PATCH 313/587] Spec 26558: convert to own form panel, not Ext4FormPanel --- resources/web/study/Finder/data/AgeGroup.js | 22 + resources/web/study/Finder/data/Condition.js | 22 + resources/web/study/Finder/data/Container.js | 30 ++ resources/web/study/Finder/data/Phase.js | 22 + .../web/study/Finder/data/PublicationType.js | 22 + resources/web/study/Finder/data/Status.js | 22 + resources/web/study/Finder/data/StudyType.js | 22 + .../web/study/Finder/data/SubmissionStatus.js | 22 + .../web/study/Finder/data/TherapeuticArea.js | 22 + resources/web/study/Finder/dataFinder.lib.xml | 1 - .../web/study/Finder/dataFinderEditor.lib.xml | 27 ++ .../panel/CubeObjectDetailsFormPanel.js | 144 +++++++ .../panel/PublicationDetailsFormPanel.js | 404 ++++++++++++++++++ .../Finder/panel/StudyDetailsFormPanel.js | 210 +++++++++ .../trialshare/data/StudyPublicationBean.java | 42 +- .../trialshare/view/publicationDetails.jsp | 105 +---- .../labkey/trialshare/view/studyDetails.jsp | 87 +--- 17 files changed, 1041 insertions(+), 185 deletions(-) create mode 100644 resources/web/study/Finder/data/AgeGroup.js create mode 100644 resources/web/study/Finder/data/Condition.js create mode 100644 resources/web/study/Finder/data/Container.js create mode 100644 resources/web/study/Finder/data/Phase.js create mode 100644 resources/web/study/Finder/data/PublicationType.js create mode 100644 resources/web/study/Finder/data/Status.js create mode 100644 resources/web/study/Finder/data/StudyType.js create mode 100644 resources/web/study/Finder/data/SubmissionStatus.js create mode 100644 resources/web/study/Finder/data/TherapeuticArea.js create mode 100644 resources/web/study/Finder/dataFinderEditor.lib.xml create mode 100644 resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js create mode 100644 resources/web/study/Finder/panel/PublicationDetailsFormPanel.js create mode 100644 resources/web/study/Finder/panel/StudyDetailsFormPanel.js diff --git a/resources/web/study/Finder/data/AgeGroup.js b/resources/web/study/Finder/data/AgeGroup.js new file mode 100644 index 00000000..09b727c7 --- /dev/null +++ b/resources/web/study/Finder/data/AgeGroup.js @@ -0,0 +1,22 @@ +Ext4.define('LABKEY.study.data.AgeGroup', { + extend: 'Ext.data.Model', + + idProperty: 'ageGroup', + + proxy : { + type : 'ajax', + url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), + extraParams : { + schemaName : 'lists', + queryName : 'ageGroup' + }, + reader : { + type : 'json', + root : 'rows' + } + }, + + fields: [ + {name: 'ageGroup'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Condition.js b/resources/web/study/Finder/data/Condition.js new file mode 100644 index 00000000..aa5a871c --- /dev/null +++ b/resources/web/study/Finder/data/Condition.js @@ -0,0 +1,22 @@ +Ext4.define('LABKEY.study.data.Condition', { + extend: 'Ext.data.Model', + + idProperty: 'condition', + + proxy : { + type : 'ajax', + url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), + extraParams : { + schemaName : 'lists', + queryName : 'condition' + }, + reader : { + type : 'json', + root : 'rows' + } + }, + + fields: [ + {name: 'condition'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Container.js b/resources/web/study/Finder/data/Container.js new file mode 100644 index 00000000..7091538e --- /dev/null +++ b/resources/web/study/Finder/data/Container.js @@ -0,0 +1,30 @@ +Ext4.define('LABKEY.study.data.Container', { + extend: 'Ext.data.Model', + + idProperty: 'entityId', + + containerFilter: "AllFolders", + + proxy : { + type : 'ajax', + url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), + extraParams : { + schemaName : 'core', + queryName : 'containers', + containerFilter : 'AllFolders', + 'query.columns' : "EntityId,DisplayName,Path", + 'query.queryName': "Containers", + 'query.sort': "DisplayName" + }, + reader : { + type : 'json', + root : 'rows' + } + }, + + fields: [ + {name: 'EntityId'}, + {name: 'DisplayName'}, + {name: 'Path'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Phase.js b/resources/web/study/Finder/data/Phase.js new file mode 100644 index 00000000..1485c1de --- /dev/null +++ b/resources/web/study/Finder/data/Phase.js @@ -0,0 +1,22 @@ +Ext4.define('LABKEY.study.data.Phase', { + extend: 'Ext.data.Model', + + idProperty: 'phase', + + proxy : { + type : 'ajax', + url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), + extraParams : { + schemaName : 'lists', + queryName : 'Phase' + }, + reader : { + type : 'json', + root : 'rows' + } + }, + + fields: [ + {name: 'phase'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/PublicationType.js b/resources/web/study/Finder/data/PublicationType.js new file mode 100644 index 00000000..7fe2e802 --- /dev/null +++ b/resources/web/study/Finder/data/PublicationType.js @@ -0,0 +1,22 @@ +Ext4.define('LABKEY.study.data.PublicationType', { + extend: 'Ext.data.Model', + + idProperty: 'PublicationType', + + proxy : { + type : 'ajax', + url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), + extraParams : { + schemaName : 'lists', + queryName : 'publicationType' + }, + reader : { + type : 'json', + root : 'rows' + } + }, + + fields: [ + {name: 'PublicationType'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Status.js b/resources/web/study/Finder/data/Status.js new file mode 100644 index 00000000..54fcc57f --- /dev/null +++ b/resources/web/study/Finder/data/Status.js @@ -0,0 +1,22 @@ +Ext4.define('LABKEY.study.data.Status', { + extend: 'Ext.data.Model', + + idProperty: 'status', + + proxy : { + type : 'ajax', + url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), + extraParams : { + schemaName : 'lists', + queryName : 'status' + }, + reader : { + type : 'json', + root : 'rows' + } + }, + + fields: [ + {name: 'status'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/StudyType.js b/resources/web/study/Finder/data/StudyType.js new file mode 100644 index 00000000..fcf26405 --- /dev/null +++ b/resources/web/study/Finder/data/StudyType.js @@ -0,0 +1,22 @@ +Ext4.define('LABKEY.study.data.StudyType', { + extend: 'Ext.data.Model', + + idProperty: 'studyType', + + proxy : { + type : 'ajax', + url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), + extraParams : { + schemaName : 'lists', + queryName : 'studyType' + }, + reader : { + type : 'json', + root : 'rows' + } + }, + + fields: [ + {name: 'studyType'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/SubmissionStatus.js b/resources/web/study/Finder/data/SubmissionStatus.js new file mode 100644 index 00000000..de0bc0aa --- /dev/null +++ b/resources/web/study/Finder/data/SubmissionStatus.js @@ -0,0 +1,22 @@ +Ext4.define('LABKEY.study.data.SubmissionStatus', { + extend: 'Ext.data.Model', + + idProperty: 'status', + + proxy : { + type : 'ajax', + url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), + extraParams : { + schemaName : 'lists', + queryName : 'submissionStatus' + }, + reader : { + type : 'json', + root : 'rows' + } + }, + + fields: [ + {name: 'Status'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/TherapeuticArea.js b/resources/web/study/Finder/data/TherapeuticArea.js new file mode 100644 index 00000000..696e4fbd --- /dev/null +++ b/resources/web/study/Finder/data/TherapeuticArea.js @@ -0,0 +1,22 @@ +Ext4.define('LABKEY.study.data.TherapeuticArea', { + extend: 'Ext.data.Model', + + idProperty: 'therapeuticArea', + + proxy : { + type : 'ajax', + url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), + extraParams : { + schemaName : 'lists', + queryName : 'therapeuticArea' + }, + reader : { + type : 'json', + root : 'rows' + } + }, + + fields: [ + {name: 'therapeuticArea'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.lib.xml b/resources/web/study/Finder/dataFinder.lib.xml index 23d010bf..41b1a0a0 100644 --- a/resources/web/study/Finder/dataFinder.lib.xml +++ b/resources/web/study/Finder/dataFinder.lib.xml @@ -1,5 +1,4 @@ - \ No newline at end of file diff --git a/src/org/labkey/trialshare/view/studyDetails.jsp b/src/org/labkey/trialshare/view/studyDetails.jsp index 5a55c4f7..9e2db0b9 100644 --- a/src/org/labkey/trialshare/view/studyDetails.jsp +++ b/src/org/labkey/trialshare/view/studyDetails.jsp @@ -20,6 +20,7 @@ <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.api.view.template.ClientDependency" %> <%@ page import="org.labkey.trialshare.TrialShareController" %> +<%@ page import="org.springframework.web.servlet.ModelAndView" %> <%@ page import="java.util.LinkedHashSet" %> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%@ page extends="org.labkey.api.jsp.JspBase"%> @@ -27,17 +28,16 @@ public LinkedHashSet getClientDependencies() { LinkedHashSet resources = new LinkedHashSet<>(); - resources.add(ClientDependency.fromPath("study/Finder/datafinder")); - resources.add(ClientDependency.fromPath("clientapi")); - resources.add(ClientDependency.fromPath("Ext4ClientApi")); - resources.add(ClientDependency.fromPath("study/Finder/panel/JunctionEditFormPanel.js")); + resources.add(ClientDependency.fromPath("study/Finder/dataFinderEditor")); return resources; } %> <% - TrialShareController.CubeObjectDetailBean bean = ((JspView) HttpView.currentView()).getModelBean(); + JspView me = (JspView) HttpView.currentView(); + TrialShareController.CubeObjectDetailBean bean = me.getModelBean(); + ModelAndView studyAccessView = me.getView("studiesAccessView"); String renderId = "study-details-" + UniqueID.getRequestScopedUID(HttpView.currentRequest()); %> @@ -46,70 +46,17 @@ \ No newline at end of file + + +<% + if (studyAccessView != null) + { + me.include(studyAccessView, out); + } +%> \ No newline at end of file From fab0ab7f8e7d294fb312e276ad1a54f43d4d0395 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 28 Jun 2016 10:12:09 -0700 Subject: [PATCH 314/587] Spec 26558: add edit and view modes for study and publication objects; add link to edit the study access data. --- resources/queries/lists/publications.sql | 2 - resources/web/study/Finder/dataFinder.css | 15 ++ .../panel/CubeObjectDetailsFormPanel.js | 86 ++++--- .../Finder/panel/JunctionEditFormPanel.js | 2 +- .../panel/PublicationDetailsFormPanel.js | 137 ++++++----- .../Finder/panel/StudyDetailsFormPanel.js | 97 +++++--- .../trialshare/TrialShareController.java | 224 +++--------------- .../labkey/trialshare/TrialShareManager.java | 26 ++ .../trialshare/data/PublicationEditBean.java | 7 + src/org/labkey/trialshare/data/StudyBean.java | 12 +- .../labkey/trialshare/data/StudyEditBean.java | 7 + .../trialshare/data/StudyPublicationBean.java | 35 +-- .../query/ManageCubeObjectQueryView.java | 2 +- .../trialshare/view/publicationDetails.jsp | 9 +- .../labkey/trialshare/view/studyDetails.jsp | 20 +- 15 files changed, 323 insertions(+), 358 deletions(-) diff --git a/resources/queries/lists/publications.sql b/resources/queries/lists/publications.sql index ccc69391..ed1421be 100644 --- a/resources/queries/lists/publications.sql +++ b/resources/queries/lists/publications.sql @@ -11,8 +11,6 @@ SELECT pub.Journal, pub.Status, pub.SubmissionStatus, - pub.Study as PrimaryStudy, - pub.StudyId as PrimaryStudyId, pub.AbstractText, pub.Keywords, pub.PermissionsContainer, diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index b2abb1a7..f67f6ed8 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -27,6 +27,21 @@ border-top-width: 0; } +.labkey-data-finder-editor +{ + padding: 5px; +} + +.labkey-combo-disabled .x4-form-item-body +{ + opacity: 0.7 +} + +.labkey-combo-disabled .x4-trigger-cell +{ + display: none; +} + .labkey-data-finder-editor-message { padding-bottom: 20px; diff --git a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js index 9ee10933..83403d63 100644 --- a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js @@ -8,6 +8,8 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { cls: 'labkey-data-finder-editor', + bodyPadding: 5, + fieldClsName: 'labkey-field-editor', fieldLabelClsName : 'labkey-field-editor-label', @@ -20,38 +22,45 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { mode: "view", multiSelectDelimiter: '; ', + cubeObject : null, stripNewLinesFields: [], + - initComponent : function() { + initComponent : function() + { this.manageDataUrl = LABKEY.ActionURL.buildURL('trialShare', 'manageData.view', null, {objectName : this.objectName, 'query.viewName': 'manageData'}); - this.dockedItems = [{ - xtype: 'toolbar', - dock: 'bottom', - ui: 'footer', - style: 'background-color: transparent;', - items: [ - { - text: 'Submit', - formBind: true, - successURL: LABKEY.ActionURL.getParameter('returnUrl') || this.manageDataUrl, - handler: function(btn) + if (this.mode != "view") + { + this.dockedItems = [{ + xtype: 'toolbar', + dock: 'bottom', + ui: 'footer', + style: 'background-color: transparent;', + items: [ { - var panel = btn.up('form'); - panel.doSubmit(btn); - } - }, - { - text: 'Cancel', - returnUrl: LABKEY.ActionURL.getParameter('returnUrl') || this.manageDataUrl, - handler: function(btn, key){ - window.location = btn.returnUrl; + text: 'Submit', + formBind: true, + successURL: LABKEY.ActionURL.getParameter('returnUrl') || this.nextStepUrl || this.manageDataUrl, + handler: function (btn) + { + var panel = btn.up('form'); + panel.doSubmit(btn); + } + }, + { + text: 'Cancel', + returnUrl: LABKEY.ActionURL.getParameter('returnUrl') || this.manageDataUrl, + handler: function (btn, key) + { + window.location = btn.returnUrl; + } } - } - ] - }]; + ] + }]; + } this.callParent(); this.add({ @@ -62,27 +71,34 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { cls: 'labkey-data-finder-editor-message' }); this.add(this.getFormFields()); - }, - - - shouldShowInDisplayView: function(metadata) { - var record = this.store.getAt(0); // there will only be a single item in the store when in view mode - if (record) + if (this.cubeObject) { - var field = record.get(metadata.name); - return field !== undefined && field !== "" + this.bindFormFields(); } - return false; }, // override this to set up the form for the cube object getFormFields: function() { - var items = []; - return items; + return []; }, + bindFormFields : function() + { + if (this.cubeObject) + { + for (var fieldName in this.cubeObject) + { + if (this.cubeObject.hasOwnProperty(fieldName)) + { + var field = this.getForm().findField(fieldName); + if (field) + field.setValue(this.cubeObject[fieldName]); + } + } + } + }, doSubmit: function(btn){ btn.setDisabled(true); diff --git a/resources/web/study/Finder/panel/JunctionEditFormPanel.js b/resources/web/study/Finder/panel/JunctionEditFormPanel.js index 40de5b27..bb0bca41 100644 --- a/resources/web/study/Finder/panel/JunctionEditFormPanel.js +++ b/resources/web/study/Finder/panel/JunctionEditFormPanel.js @@ -72,7 +72,7 @@ Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { }, shouldShowInView: function(metadata) { - if (this.mode == "edit" || this.mode == "insert") + if (this.mode == "update" || this.mode == "insert") return this.shouldShowInInsertView(metadata); else return this.shouldShowInDisplayView(metadata); diff --git a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js index b8efb191..64677ba8 100644 --- a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js @@ -5,46 +5,48 @@ */ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { extend: 'LABKEY.study.panel.CubeObjectDetailsFormPanel', - - publicationId: null, - + dataModuleName: 'TrialShare', cubeContainerPath: 'TrialShare', stripNewLinesFields: ['Keywords','Author','Citation'], + getFormFields: function() { var items = []; items.push( { xtype : 'hidden', - name: 'Key' + name: 'id' }); items.push( { xtype : 'checkbox', + disabled : this.mode == "view", cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Show on Dashboard', - name : 'Show', + name : 'show', labelWidth : this.defaultFieldLabelWidth }); items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Title *', - name : 'Title', + name : 'title', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth }); items.push( { xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Publication Type *', - name : 'PublicationType', + name : 'publicationType', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth, valueField : 'PublicationType', @@ -58,10 +60,12 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Status *', - name : 'Status', + name : 'status', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth, valueField : 'Status', @@ -75,10 +79,12 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Submission Status', - name : 'SubmissionStatus', + name : 'submissionStatus', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth, valueField : 'Status', @@ -91,12 +97,12 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { }); items.push( { - xtype : 'textarea', + xtype : this.mode == "view" ? 'displayfield' : 'textarea', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, stripNewLines : true, fieldLabel : 'Author', - name : 'Author', + name : 'author', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth, height : 50 @@ -104,12 +110,12 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { ); items.push( { - xtype : 'textarea', + xtype : this.mode == "view" ? 'displayfield' : 'textarea', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, stripNewLines : true, fieldLabel : 'Citation', - name : 'Citation', + name : 'citation', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth, height : 50 @@ -117,26 +123,24 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { ); items.push( { - xtype : 'numberfield', + xtype : this.mode == "view" ? 'displayfield' : 'numberfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - // spinUpEnabled : false, - // spinDownEnabled : false, minValue : 1440, // can't have published anything before the printing press was created maxValue : new Date().getFullYear() + 9, fieldLabel : 'Year', - name : 'Year', + name : 'year', labelWidth : this.defaultFieldLabelWidth } ); items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Journal', - name : 'Journal', + name : 'journal', labelWidth : this.defaultFieldLabelWidth, width : this.mediumLargeFieldWidth }); @@ -144,10 +148,11 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { xtype : 'htmleditor', + disabled : this.mode == "view", cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Abstract', - name : 'AbstractText', + name : 'abstractText', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth, height : 150 @@ -156,7 +161,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'DOI', @@ -168,7 +173,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { - xtype : 'numberfield', + xtype : this.mode == "view" ? 'displayfield' : 'numberfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, spinUpEnabled : false, @@ -181,7 +186,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'PMCID', @@ -193,9 +198,11 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - name : 'ManuscriptContainer', + name : 'manuscriptContainer', store : { model : 'LABKEY.study.data.Container', autoLoad: true @@ -212,9 +219,11 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - name : 'PermissionsContainer', + name : 'permissionsContainer', store : { model : 'LABKEY.study.data.Container', autoLoad: true @@ -230,33 +239,35 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { - xtype : 'combo', - multiSelect : true, + xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', + multiSelect : true, cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - name : 'TherapeuticAreas', - store : { - model : 'LABKEY.study.data.TherapeuticArea', - autoLoad: true - }, - fieldLabel : 'Therapeutic Areas', - labelWidth : this.defaultFieldLabelWidth, - valueField : 'TherapeuticArea', - displayField : 'TherapeuticArea', - editable : false, - delimiter : this.multiSelectDelimiter, - width : 500 + name : 'therapeuticAreas', + store : { + model : 'LABKEY.study.data.TherapeuticArea', + autoLoad: true + }, + fieldLabel : 'Therapeutic Areas', + labelWidth : this.defaultFieldLabelWidth, + valueField : 'TherapeuticArea', + displayField : 'TherapeuticArea', + editable : false, + delimiter : this.multiSelectDelimiter, + width : 500 } ); items.push( { - xtype : 'textarea', + xtype : this.mode == "view" ? 'displayfield' : 'textarea', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, stripNewLines : true, fieldLabel : 'Keywords', - name : 'Keywords', + name : 'keywords', labelWidth : this.defaultFieldLabelWidth, width : this.mediumFieldWidth, height : 50 @@ -266,10 +277,12 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', multiSelect : true, cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - name : 'StudyIds', + name : 'studyIds', store : { model : 'LABKEY.study.data.Study', proxy : { @@ -280,6 +293,12 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { root: 'data' } }, + sorters : [ + { + property: 'shortName', + direction: 'ASC' + } + ], autoLoad : true }, fieldLabel : 'Studies', @@ -295,10 +314,12 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', multiSelect : true, cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - name : 'Conditions', + name : 'conditions', store : { model : 'LABKEY.study.data.Condition', autoLoad: true @@ -316,10 +337,12 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', multiSelect : true, cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - name : 'TherapeuticAreas', + name : 'therapeuticAreas', store : { model : 'LABKEY.study.data.TherapeuticArea', autoLoad: true @@ -336,11 +359,11 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Link 1', - name : 'Link1', + name : 'link1', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth } @@ -348,11 +371,11 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Description 1', - name : 'Description1', + name : 'description1', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth } @@ -360,42 +383,42 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Link 2', - name : 'Link2', + name : 'link2', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth }); items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Description 2', - name : 'Description2', + name : 'description2', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth }); items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Link 3', - name : 'Link3', + name : 'link3', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth }); items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Description 3', - name : 'Description3', + name : 'description3', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth }); diff --git a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js index 232c7f00..2f7ea9fe 100644 --- a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js @@ -8,47 +8,62 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { stripNewLinesFields : ['ExternalUrlDescription'], + study : null, + + initComponent: function() + { + if (this.mode == "insert") + { + this.nextStepUrl = LABKEY.ActionURL.buildURL("list", "grid", LABKEY.ActionURL.getContainer(), + { + listId: this.accessListId, + returnUrl: window.location + }); + } + this.callParent(); + }, + getFormFields: function() { var items = []; items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Short Name *', - name : 'ShortName', + name : 'shortName', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth }); items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Study Id *', - name : 'StudyId', + name : 'studyId', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth }); items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Title *', - name : 'Title', + name : 'title', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth }); items.push( { - xtype : 'numberfield', + xtype : this.mode == "view" ? 'displayfield' : 'numberfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, minValue : 0, fieldLabel : 'Participant Count', - name : 'ParticipantCount', + name : 'participantCount', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth } @@ -58,8 +73,10 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { xtype : 'combo', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', fieldLabel : 'Study Type', - name : 'StudyType', + name : 'studyType', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth, valueField : 'StudyType', @@ -72,53 +89,55 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { }); items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Icon Url', - name : 'IconUrl', + name : 'iconUrl', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth }); items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'External Url', - name : 'ExternalURL', + name : 'externalURL', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth }); items.push( { - xtype : 'textarea', + xtype : 'htmleditor', + disabled : this.mode == "view", cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'External Url Description', - name : 'ExternalUrlDescription', + name : 'externalUrlDescription', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth, - height : 50 + height : 100 }); items.push( { - xtype : 'textarea', + xtype : 'htmleditor', + disabled : this.mode == "view", cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Description', - name : 'Description', + name : 'description', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth, - height : 100 + height : 200 }); items.push( { - xtype : 'textfield', + xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Investigator', - name : 'Investigator', + name : 'investigator', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth }); @@ -128,8 +147,10 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { xtype : 'combo', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', fieldLabel : 'Age Groups', - name : 'AgeGroups', + name : 'ageGroups', multiSelect : true, delimiter : this.multiSelectDelimiter, labelWidth : this.defaultFieldLabelWidth, @@ -147,10 +168,12 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { xtype : 'combo', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', multiSelect : true, delimiter : this.multiSelectDelimiter, fieldLabel : 'Phases', - name : 'Phases', + name : 'phases', labelWidth : this.defaultFieldLabelWidth, width : this.mediumFieldWidth, valueField : 'Phase', @@ -164,11 +187,13 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { items.push( { xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', multiSelect : true, delimiter : this.multiSelectDelimiter, cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - name : 'Conditions', + name : 'conditions', store : { model : 'LABKEY.study.data.Condition', autoLoad: true @@ -178,7 +203,6 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { valueField : 'Condition', displayField : 'Condition', editable : false, - delimiter : '; ', width : this.mediumLargeFieldWidth } ); @@ -186,11 +210,13 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { items.push( { xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', multiSelect : true, delimiter : this.multiSelectDelimiter, cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - name : 'TherapeuticAreas', + name : 'therapeuticAreas', store : { model : 'LABKEY.study.data.TherapeuticArea', autoLoad: true @@ -200,10 +226,27 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { valueField : 'TherapeuticArea', displayField : 'TherapeuticArea', editable : false, - delimiter : '; ', width : this.mediumFieldWidth } ); + + if (this.mode != "insert") + { + items.push( + { + xtype : 'displayfield', + hideLabel: true, + width: 175, + value: LABKEY.Utils.textLink({ + href: LABKEY.ActionURL.buildURL("list", "grid", LABKEY.ActionURL.getContainer(), + { + listId: this.accessListId, + 'query.StudyId/ShortName~eq' : this.cubeObject.shortName, + returnUrl: window.location}), + text: this.mode + ' Study Access Data'}) + } + ); + } return items; } diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 8ff5da94..931b432a 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -33,16 +33,13 @@ import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; import org.labkey.api.data.DataRegionSelection; -import org.labkey.api.data.SimpleFilter; import org.labkey.api.data.TableInfo; import org.labkey.api.data.TableSelector; -import org.labkey.api.query.FieldKey; +import org.labkey.api.exp.list.ListDefinition; +import org.labkey.api.exp.list.ListService; import org.labkey.api.query.QueryForm; import org.labkey.api.query.QuerySchema; import org.labkey.api.query.QueryService; -import org.labkey.api.query.QuerySettings; -import org.labkey.api.query.QueryView; -import org.labkey.api.query.UserSchema; import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.AdminPermission; import org.labkey.api.security.permissions.DeletePermission; @@ -824,175 +821,6 @@ public Object execute(StudyIdForm sform, BindException errors) throws Exception } -// @ActionNames("selectRows, getQuery") -// @RequiresPermission(ReadPermission.class) -// @ApiVersion(9.1) -// @Action(ActionType.SelectData.class) -// public class SelectRowsAction extends ApiAction -// { -// public ApiResponse execute(APIQueryForm form, BindException errors) throws Exception -// { -//// ensureQueryExists(form); -// -// // Issue 12233: add implicit maxRows=100k when using client API -// if (null == form.getLimit() -// && null == getViewContext().getRequest().getParameter(form.getDataRegionName() + "." + QueryParam.maxRows) -// && null == getViewContext().getRequest().getParameter(form.getDataRegionName() + "." + QueryParam.showRows)) -// { -// form.getQuerySettings().setShowRows(ShowRows.PAGINATED); -// form.getQuerySettings().setMaxRows(100); -// } -// -// if (form.getLimit() != null) -// { -// form.getQuerySettings().setShowRows(ShowRows.PAGINATED); -// form.getQuerySettings().setMaxRows(form.getLimit()); -// } -// if (form.getStart() != null) -// form.getQuerySettings().setOffset(form.getStart()); -// if (form.getContainerFilter() != null) -// { -// // If the user specified an incorrect filter, throw an IllegalArgumentException -// ContainerFilter.Type containerFilterType = -// ContainerFilter.Type.valueOf(form.getContainerFilter()); -// form.getQuerySettings().setContainerFilterName(containerFilterType.name()); -// } -// -// QueryView view = QueryView.create(form, errors); -// -// view.setShowPagination(form.isIncludeTotalCount()); -// -// //if viewName was specified, ensure that it was actually found and used -// //QueryView.create() will happily ignore an invalid view name and just return the default view -// if (null != StringUtils.trimToNull(form.getViewName()) && -// null == view.getQueryDef().getCustomView(getUser(), getViewContext().getRequest(), form.getViewName())) -// { -// throw new NotFoundException("The view named '" + form.getViewName() + "' does not exist for this user!"); -// } -// -// boolean isEditable = false; -// boolean metaDataOnly = form.getQuerySettings().getMaxRows() == 0; -// -// // 13.2 introduced the getData API action, a condensed response wire format, and a js wrapper to consume the wire format. Support this as an option for legacy API's. -// if (getRequestedApiVersion() >= 13.2) -// { -// ReportingApiQueryResponse response = new ReportingApiQueryResponse(view, isEditable, true, view.getQueryDef().getName(), form.getQuerySettings().getOffset(), null, -// metaDataOnly, form.isIncludeDetailsColumn(), form.isIncludeUpdateColumn()); -// response.includeStyle(form.isIncludeStyle()); -// return response; -// } -// //if requested version is >= 9.1, use the extended api query response -// else if (getRequestedApiVersion() >= 9.1) -// { -// ExtendedApiQueryResponse response = new ExtendedApiQueryResponse(view, isEditable, true, -// form.getSchemaName(), form.getQueryName(), form.getQuerySettings().getOffset(), null, -// metaDataOnly, form.isIncludeDetailsColumn(), form.isIncludeUpdateColumn()); -// response.includeStyle(form.isIncludeStyle()); -// return response; -// } -// else -// { -// return new ApiQueryResponse(view, isEditable, true, -// form.getSchemaName(), form.getQueryName(), form.getQuerySettings().getOffset(), null, -// metaDataOnly, form.isIncludeDetailsColumn(), form.isIncludeUpdateColumn(), -// form.isIncludeDisplayValues()); -// } -// } -// } -// -// public static class APIQueryForm extends QueryForm -// { -// private Integer _start; -// private Integer _limit; -// private boolean _includeDetailsColumn = false; -// private boolean _includeUpdateColumn = false; -// private String _containerFilter; -// private boolean _includeTotalCount = true; -// private boolean _includeStyle = false; -// private boolean _includeDisplayValues = false; -// -// public Integer getStart() -// { -// return _start; -// } -// -// public void setStart(Integer start) -// { -// _start = start; -// } -// -// public Integer getLimit() -// { -// return _limit; -// } -// -// public void setLimit(Integer limit) -// { -// _limit = limit; -// } -// -// public String getContainerFilter() -// { -// return _containerFilter; -// } -// -// public void setContainerFilter(String containerFilter) -// { -// _containerFilter = containerFilter; -// } -// -// public boolean isIncludeTotalCount() -// { -// return _includeTotalCount; -// } -// -// public void setIncludeTotalCount(boolean includeTotalCount) -// { -// _includeTotalCount = includeTotalCount; -// } -// -// public boolean isIncludeStyle() -// { -// return _includeStyle; -// } -// -// public void setIncludeStyle(boolean includeStyle) -// { -// _includeStyle = includeStyle; -// } -// -// public boolean isIncludeDetailsColumn() -// { -// return _includeDetailsColumn; -// } -// -// public void setIncludeDetailsColumn(boolean includeDetailsColumn) -// { -// _includeDetailsColumn = includeDetailsColumn; -// } -// -// public boolean isIncludeUpdateColumn() -// { -// return _includeUpdateColumn; -// } -// -// public void setIncludeUpdateColumn(boolean includeUpdateColumn) -// { -// _includeUpdateColumn = includeUpdateColumn; -// } -// -// public boolean isIncludeDisplayValues() -// { -// return _includeDisplayValues; -// } -// -// public void setIncludeDisplayValues(boolean includeDisplayValues) -// { -// _includeDisplayValues = includeDisplayValues; -// } -// } - - @RequiresPermission(ReadPermission.class) public class PublicationDetailsAction extends ApiAction { @@ -1144,7 +972,6 @@ else if (form.getObjectName() == ObjectName.publication) @RequiresPermission(ReadPermission.class) public class ManageDataAction extends SimpleViewAction { - @Override public void validate(CubeObjectNameForm form, BindException errors) { @@ -1208,7 +1035,7 @@ public Object execute(PublicationEditBean form, BindException errors) throws Exc } @RequiresPermission(UpdatePermission.class) - public class EditPublicationAction extends CaseInsensitiveApiAction + public class UpdatePublicationAction extends CaseInsensitiveApiAction { @Override public void validateForm(PublicationEditBean form, Errors errors) @@ -1249,7 +1076,7 @@ public Object execute(StudyEditBean form, BindException errors) throws Exception } @RequiresPermission(UpdatePermission.class) - public class EditStudyAction extends CaseInsensitiveApiAction + public class UpdateStudyAction extends CaseInsensitiveApiAction { @Override public void validateForm(StudyEditBean form, Errors errors) @@ -1346,11 +1173,11 @@ protected String getMode() @RequiresPermission(UpdatePermission.class) - public class EditDataAction extends CubeObjectDetailFormAction + public class UpdateDataAction extends CubeObjectDetailFormAction { protected String getMode() { - return "edit"; + return "update"; } } @@ -1364,10 +1191,10 @@ protected String getMode() } @RequiresPermission(ReadPermission.class) - private abstract class CubeObjectDetailFormAction extends SimpleViewAction + private abstract class CubeObjectDetailFormAction extends SimpleViewAction { @Override - public void validate(CubeObjectDetailBean form, BindException errors) + public void validate(CubeObjectDetailForm form, BindException errors) { if (form.getObjectName() == null) errors.reject("Object name is required"); @@ -1376,24 +1203,24 @@ public void validate(CubeObjectDetailBean form, BindException errors) protected abstract String getMode(); @Override - public ModelAndView getView(CubeObjectDetailBean bean, BindException errors) throws Exception + public ModelAndView getView(CubeObjectDetailForm bean, BindException errors) throws Exception { setTitle(StringUtils.capitalize(getMode()) + bean.getObjectName().getDisplayName()); bean.setMode(getMode()); if (bean.getObjectName() == ObjectName.publication) + { + if (bean.getId() != null) + bean.setCubeObject(TrialShareManager.get().getPublication(Integer.valueOf((String) bean.getId()), getUser(), getContainer())); return new JspView("/org/labkey/trialshare/view/publicationDetails.jsp", bean); + } else { + ListDefinition def = ListService.get().getList(getContainer(), TrialShareQuerySchema.STUDY_ACCESS_TABLE); + bean.setAccessListId(def.getListId()); JspView view = new JspView("/org/labkey/trialshare/view/studyDetails.jsp", bean); - UserSchema userSchema = TrialShareQuerySchema.getUserSchema(getUser(), getContainer()); - - QuerySettings settings = userSchema.getSettings(getViewContext(), QueryView.DATAREGIONNAME_DEFAULT, TrialShareQuerySchema.STUDY_ACCESS_TABLE); - SimpleFilter filter = settings.getBaseFilter(); - filter.addAllClauses(new SimpleFilter(FieldKey.fromParts("StudyId"), (String) bean.getId())); - QueryView queryView = userSchema.createView(getViewContext(), settings, errors); - view.setTitle("Study Access"); - view.setView("studiesAccessView", queryView); + if (bean.getId() != null) + bean.setCubeObject(TrialShareManager.get().getStudy((String) bean.getId(), getUser(), getContainer())); return view; } } @@ -1412,20 +1239,31 @@ public NavTree appendNavTrail(NavTree root) } } - public static class CubeObjectDetailBean extends CubeObjectNameForm + public static class CubeObjectDetailForm extends CubeObjectForm { + private Integer _accessListId; // identifier of the list that containes access parameters (used only for studies currently) private String _mode; private Object _id; - public CubeObjectDetailBean() + public CubeObjectDetailForm() { } - public CubeObjectDetailBean(@NotNull String mode) + public CubeObjectDetailForm(@NotNull String mode) { _mode = mode; } + public Integer getAccessListId() + { + return _accessListId; + } + + public void setAccessListId(Integer accessListId) + { + _accessListId = accessListId; + } + public Object getId() { return _id; diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index a0ab6155..bc1e3515 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -34,6 +34,7 @@ import org.labkey.api.security.User; import org.labkey.trialshare.data.PublicationEditBean; import org.labkey.trialshare.data.StudyAccess; +import org.labkey.trialshare.data.StudyBean; import org.labkey.trialshare.data.StudyEditBean; import org.labkey.trialshare.data.StudyPublicationBean; import org.labkey.trialshare.query.TrialShareQuerySchema; @@ -493,5 +494,30 @@ private void addJoinTableData(TableInfo tableInfo, String idField, Object id, St } } + public PublicationEditBean getPublication(Integer id, User user, Container container) + { + TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); + + StudyPublicationBean studyPublication = new TableSelector(schema.getPublicationsTableInfo()).getObject(id, StudyPublicationBean.class); + PublicationEditBean publication = new PublicationEditBean(studyPublication); + SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.PUBLICATION_ID_FIELD), id); + publication.setStudyIds(new TableSelector(schema.getPublicationStudyTableInfo(), Collections.singleton(TrialShareQuerySchema.STUDY_ID_FIELD), filter, null).getArrayList(String.class)); + publication.setConditions(new TableSelector(schema.getPublicationConditionTableInfo(), Collections.singleton(TrialShareQuerySchema.CONDITION_FIELD), filter, null).getArrayList(String.class)); + publication.setTherapeuticAreas(new TableSelector(schema.getPublicationTherapeuticAreaTableInfo(), Collections.singleton(TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD), filter, null).getArrayList(String.class)); + return publication; + } + + public StudyEditBean getStudy(String id, User user, Container container) + { + TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); + StudyBean study = new TableSelector(schema.getStudyPropertiesTableInfo()).getObject(id, StudyBean.class); + StudyEditBean editStudy = new StudyEditBean(study); + SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.STUDY_ID_FIELD), id); + editStudy.setAgeGroups(new TableSelector(schema.getStudyAgeGroupTableInfo(), Collections.singleton(TrialShareQuerySchema.AGE_GROUP_FIELD), filter, null).getArrayList(String.class)); + editStudy.setConditions(new TableSelector(schema.getStudyConditionTableInfo(), Collections.singleton(TrialShareQuerySchema.CONDITION_FIELD), filter, null).getArrayList(String.class)); + editStudy.setPhases(new TableSelector(schema.getStudyPhaseTableInfo(), Collections.singleton(TrialShareQuerySchema.PHASE_FIELD), filter, null).getArrayList(String.class)); + editStudy.setTherapeuticAreas(new TableSelector(schema.getStudyTherapeuticAreaTableInfo(), Collections.singleton(TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD), filter, null).getArrayList(String.class)); + return editStudy; + } } \ No newline at end of file diff --git a/src/org/labkey/trialshare/data/PublicationEditBean.java b/src/org/labkey/trialshare/data/PublicationEditBean.java index 8b43ced4..783fa444 100644 --- a/src/org/labkey/trialshare/data/PublicationEditBean.java +++ b/src/org/labkey/trialshare/data/PublicationEditBean.java @@ -12,6 +12,13 @@ public class PublicationEditBean extends StudyPublicationBean private List _conditions = new ArrayList<>(); private List _therapeuticAreas = new ArrayList<>(); + public PublicationEditBean() {} + + public PublicationEditBean(StudyPublicationBean base) + { + setPrimaryFields(base.getPrimaryFields()); + } + public List getConditions() { return _conditions; diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index 020cfa69..65ced836 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -180,6 +180,7 @@ public void setPublications(User user, Container container, @Nullable String pub if (listSchema != null) { SimpleFilter filter = new SimpleFilter(); + // TODO fix this to not use the StudyId field from the PUBLICATION_TABLE filter.addCondition(FieldKey.fromParts("studyId"), getStudyId()); if (publicationType != null) { @@ -416,14 +417,19 @@ public Map getPrimaryFields() return _primaryFields; } + protected void setPrimaryFields(Map primaryFields) + { + _primaryFields = primaryFields; + } + public void validate(Errors errors) { if (getShortName() == null) - errors.rejectValue("ShortName", ERROR_REQUIRED, "Short Name is required"); + errors.rejectValue("shortName", ERROR_REQUIRED, "Short Name is required"); if (getStudyId() == null) - errors.rejectValue("StudyId", ERROR_REQUIRED, "Study Id is required"); + errors.rejectValue("studyId", ERROR_REQUIRED, "Study Id is required"); if (getTitle() == null) - errors.rejectValue("Title", ERROR_REQUIRED, "Title is required"); + errors.rejectValue("title", ERROR_REQUIRED, "Title is required"); } } diff --git a/src/org/labkey/trialshare/data/StudyEditBean.java b/src/org/labkey/trialshare/data/StudyEditBean.java index f9977da8..d11c51d4 100644 --- a/src/org/labkey/trialshare/data/StudyEditBean.java +++ b/src/org/labkey/trialshare/data/StudyEditBean.java @@ -13,6 +13,13 @@ public class StudyEditBean extends StudyBean private List _conditions = new ArrayList<>(); private List _therapeuticAreas = new ArrayList<>(); + public StudyEditBean() {} + + public StudyEditBean(StudyBean study) + { + setPrimaryFields(study.getPrimaryFields()); + } + public List getAgeGroups() { return _ageGroups; diff --git a/src/org/labkey/trialshare/data/StudyPublicationBean.java b/src/org/labkey/trialshare/data/StudyPublicationBean.java index b7915b73..db051ce5 100644 --- a/src/org/labkey/trialshare/data/StudyPublicationBean.java +++ b/src/org/labkey/trialshare/data/StudyPublicationBean.java @@ -106,21 +106,11 @@ public void setStudyId(String studyId) _primaryFields.put(STUDY_ID_FIELD, studyId); } -// public String getPmid() -// { -// return (String) _primaryFields.get(PMID_FIELD); -// } - public String getPMID() { return (String) _primaryFields.get(PMID_FIELD); } -// public void setPmid(String pmid) -// { -// _primaryFields.put(PMID_FIELD, pmid); -// } - public void setPMID(String pmid) { _primaryFields.put(PMID_FIELD, pmid); @@ -136,16 +126,6 @@ public void setPmcid(String pmcid) _primaryFields.put(PMCID_FIELD, pmcid); } -// public String getPMCID() -// { -// return getPmcid(); -// } -// -// public void setPMCID(String pmcid) -// { -// setPmcid(pmcid); -// } - public String getDoi() { return (String) _primaryFields.get(DOI_FIELD); @@ -548,22 +528,27 @@ public Map getPrimaryFields() return _primaryFields; } + protected void setPrimaryFields(Map fields) + { + _primaryFields = fields; + } + public void validate(Errors errors) { if (getTitle() == null) - errors.rejectValue("Title", ERROR_REQUIRED, "Title is required"); + errors.rejectValue("title", ERROR_REQUIRED, "Title is required"); if (getStatus() == null) - errors.rejectValue("Status", ERROR_REQUIRED, "Status is required"); + errors.rejectValue("status", ERROR_REQUIRED, "Status is required"); if (getPublicationType() == null) - errors.rejectValue("PublicationType", ERROR_REQUIRED, "Publication type is required"); + errors.rejectValue("publicationType", ERROR_REQUIRED, "Publication type is required"); if (getPMID() != null && !StringUtils.isNumeric(getPMID())) { - errors.rejectValue("PMID", ERROR_MSG, "PMID must be an integer"); + errors.rejectValue("pmid", ERROR_MSG, "PMID must be an integer"); } if (getPmcid() != null && !PMCID_PATTERN.matcher(getPmcid()).matches()) { - errors.rejectValue("PMCID", ERROR_MSG, "Incorrect format for PMCID. Expected PMC#"); + errors.rejectValue("pmcid", ERROR_MSG, "Incorrect format for PMCID. Expected PMC#"); } } diff --git a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java index 20871d53..a9270752 100644 --- a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java +++ b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java @@ -117,7 +117,7 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep FieldKey keyFieldKey = FieldKey.fromParts(getKeyField()); String id = String.valueOf(ctx.get(keyFieldKey)); - ActionURL actionUrl = new ActionURL(TrialShareController.EditDataAction.class, cubeContainer).addParameter("id", id); + ActionURL actionUrl = new ActionURL(TrialShareController.UpdateDataAction.class, cubeContainer).addParameter("id", id); actionUrl.addParameter("objectName", getCubeObjectName().toString()); out.write(PageFlowUtil.textLink("Edit", actionUrl)); } diff --git a/src/org/labkey/trialshare/view/publicationDetails.jsp b/src/org/labkey/trialshare/view/publicationDetails.jsp index aa7336b1..fbd213c6 100644 --- a/src/org/labkey/trialshare/view/publicationDetails.jsp +++ b/src/org/labkey/trialshare/view/publicationDetails.jsp @@ -15,6 +15,7 @@ * limitations under the License. */ %> +<%@ page import="org.json.JSONObject" %> <%@ page import="org.labkey.api.util.UniqueID" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> @@ -33,10 +34,13 @@ } %> <% - TrialShareController.CubeObjectDetailBean bean = ((JspView) HttpView.currentView()).getModelBean(); + TrialShareController.CubeObjectDetailForm bean = ((JspView) HttpView.currentView()).getModelBean(); String renderId = "publication-details-" + UniqueID.getRequestScopedUID(HttpView.currentRequest()); + + String cubeObjectJson = bean.getCubeObject() == null ? "null" : new JSONObject(bean.getCubeObject()).toString(2); %> +
@@ -46,7 +50,8 @@ Ext4.create('LABKEY.study.panel.PublicationDetailsFormPanel', { mode: "<%=h(bean.getMode())%>", objectName : 'Publication', - renderTo: <%=q(renderId)%> + renderTo: <%=q(renderId)%>, + cubeObject : <%= text( cubeObjectJson )%> }); }); \ No newline at end of file diff --git a/src/org/labkey/trialshare/view/studyDetails.jsp b/src/org/labkey/trialshare/view/studyDetails.jsp index 9e2db0b9..a170c3c7 100644 --- a/src/org/labkey/trialshare/view/studyDetails.jsp +++ b/src/org/labkey/trialshare/view/studyDetails.jsp @@ -15,6 +15,7 @@ * limitations under the License. */ %> +<%@ page import="org.json.JSONObject" %> <%@ page import="org.labkey.api.util.UniqueID" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> @@ -34,11 +35,11 @@ } %> <% - JspView me = (JspView) HttpView.currentView(); - TrialShareController.CubeObjectDetailBean bean = me.getModelBean(); + JspView me = (JspView) HttpView.currentView(); + TrialShareController.CubeObjectDetailForm bean = me.getModelBean(); - ModelAndView studyAccessView = me.getView("studiesAccessView"); String renderId = "study-details-" + UniqueID.getRequestScopedUID(HttpView.currentRequest()); + String cubeObjectJson = bean.getCubeObject() == null ? "null" : new JSONObject(bean.getCubeObject()).toString(2); %>
@@ -49,14 +50,9 @@ Ext4.create('LABKEY.study.panel.StudyDetailsFormPanel', { mode: "<%=h(bean.getMode())%>", objectName : 'Study', - renderTo: <%=q(renderId)%> + renderTo: <%=q(renderId)%>, + accessListId : <%= bean.getAccessListId() %>, + cubeObject : <%= text( cubeObjectJson )%> }); }); - - -<% - if (studyAccessView != null) - { - me.include(studyAccessView, out); - } -%> \ No newline at end of file + \ No newline at end of file From df4b0e71a01e387e2709289334acc8f71c09cabc Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 28 Jun 2016 10:15:01 -0700 Subject: [PATCH 315/587] Spec 26558: update method name for pmid getter --- src/org/labkey/trialshare/view/studyDetail.jsp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/trialshare/view/studyDetail.jsp b/src/org/labkey/trialshare/view/studyDetail.jsp index 39d7a8fa..c4a38eb7 100644 --- a/src/org/labkey/trialshare/view/studyDetail.jsp +++ b/src/org/labkey/trialshare/view/studyDetail.jsp @@ -130,9 +130,9 @@ %>
<%=h(pub.getAuthor())%><% } %><% - if (!StringUtils.isEmpty(pub.getPmid())) + if (!StringUtils.isEmpty(pub.getPMID())) { - %>
<%=textLink("PubMed","http://www.ncbi.nlm.nih.gov/pubmed/?term=" + pub.getPmid(), null, null, linkProps)%><% + %>
<%=textLink("PubMed","http://www.ncbi.nlm.nih.gov/pubmed/?term=" + pub.getPMID(), null, null, linkProps)%><% } for (URLData urlData : pub.getUrls()) { From 078ee241d6b70a5167c852a5973092309169861d Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 28 Jun 2016 11:26:19 -0700 Subject: [PATCH 316/587] Spec 26558: remove reference to StudyId in publication table when retreiving for study details view. --- src/org/labkey/trialshare/data/StudyBean.java | 11 ++--------- .../trialshare/query/TrialShareQuerySchema.java | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index 65ced836..083ac06c 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -179,15 +179,8 @@ public void setPublications(User user, Container container, @Nullable String pub if (listSchema != null) { - SimpleFilter filter = new SimpleFilter(); - // TODO fix this to not use the StudyId field from the PUBLICATION_TABLE - filter.addCondition(FieldKey.fromParts("studyId"), getStudyId()); - if (publicationType != null) - { - filter.addCondition(FieldKey.fromParts("PublicationType"), publicationType); - } - List allPublications = (new TableSelector(listSchema.getTable(TrialShareQuerySchema.PUBLICATION_TABLE), filter, null)).getArrayList(StudyPublicationBean.class); - this.publications.clear(); + TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); + List allPublications = schema.getStudyPublications(getStudyId(), publicationType); for (StudyPublicationBean publication : allPublications) { if (publication.getShow() && publication.hasPermission(user)) diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index ec1926c8..2f6f6dce 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -1,5 +1,6 @@ package org.labkey.trialshare.query; +import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; import org.labkey.api.data.SQLFragment; import org.labkey.api.data.SqlSelector; @@ -208,15 +209,21 @@ public List getStudyPublications() return new SqlSelector(getSchema().getDbSchema(), sql).getArrayList(StudyPublicationBean.class); } - public List getStudyPublications(String studyId) + public List getStudyPublications(String studyId, @Nullable String publicationType) { - SQLFragment sql = new SQLFragment("SELECT pub.*, ps.StudyId FROM " ); - sql.append(getPublicationStudyTableInfo(), "ps"); - sql.append(" LEFT JOIN "); + SQLFragment sql = new SQLFragment("SELECT pub.*, ps.StudyId FROM "); sql.append(getPublicationsTableInfo(), "pub"); + sql.append(" LEFT JOIN "); + sql.append(getPublicationStudyTableInfo(), "ps"); sql.append(" ON ps.PublicationId = pub.Key "); - sql.append( "WHERE ps.StudyId = ? "); + sql.append("WHERE ps.StudyId = ? "); sql.add(studyId); + if (publicationType != null) + { + sql.append(" AND pub.publicationType = ?"); + sql.add(publicationType); + } + return new SqlSelector(getSchema().getDbSchema(), sql).getArrayList(StudyPublicationBean.class); } From 942ab55a31329a136416aad16859166749493faa Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 08:30:52 -0700 Subject: [PATCH 317/587] Spec 26558: remove duplicate TherapeuticAreas field --- .../panel/PublicationDetailsFormPanel.js | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js index 64677ba8..a7ab6640 100644 --- a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js @@ -237,29 +237,6 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { } ); - items.push( - { - xtype : 'combo', - disabled : this.mode == "view", - disabledCls : 'labkey-combo-disabled', - multiSelect : true, - cls : this.fieldClsName, - labelCls : this.fieldLabelClsName, - name : 'therapeuticAreas', - store : { - model : 'LABKEY.study.data.TherapeuticArea', - autoLoad: true - }, - fieldLabel : 'Therapeutic Areas', - labelWidth : this.defaultFieldLabelWidth, - valueField : 'TherapeuticArea', - displayField : 'TherapeuticArea', - editable : false, - delimiter : this.multiSelectDelimiter, - width : 500 - } - ); - items.push( { xtype : this.mode == "view" ? 'displayfield' : 'textarea', From 9ecce8b2dc51e221bced863d38024fbab7f7038a Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 08:32:00 -0700 Subject: [PATCH 318/587] white space clean up --- src/org/labkey/trialshare/query/TrialShareQuerySchema.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index 2f6f6dce..e44199cf 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -224,7 +224,6 @@ public List getStudyPublications(String studyId, @Nullable sql.add(publicationType); } - return new SqlSelector(getSchema().getDbSchema(), sql).getArrayList(StudyPublicationBean.class); } From fbbb4d4a1cc65989090cb6b1e82ac549c5005a05 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 09:24:38 -0700 Subject: [PATCH 319/587] Spec 26558: make link for study access not a form field --- .../Finder/panel/StudyDetailsFormPanel.js | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js index 2f7ea9fe..0e9b13ba 100644 --- a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js @@ -232,20 +232,23 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { if (this.mode != "insert") { - items.push( + items.push ( { - xtype : 'displayfield', - hideLabel: true, - width: 175, - value: LABKEY.Utils.textLink({ - href: LABKEY.ActionURL.buildURL("list", "grid", LABKEY.ActionURL.getContainer(), + tag: 'div', + itemId: 'nextStepEl', + html:LABKEY.Utils.textLink({ + href: LABKEY.ActionURL.buildURL("list", "grid", LABKEY.ActionURL.getContainer(), { listId: this.accessListId, 'query.StudyId/ShortName~eq' : this.cubeObject.shortName, - returnUrl: window.location}), - text: this.mode + ' Study Access Data'}) + returnUrl: window.location + }), + text: this.mode + ' Study Access Data' + }), + border: false, + cls: 'labkey-data-finder-editor-message' } - ); + ) } return items; From 0696d34e12a48089f2c903abe696951e97709203 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 09:25:02 -0700 Subject: [PATCH 320/587] Spec 26558: use nextStepUrl properly --- .../Finder/panel/CubeObjectDetailsFormPanel.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js index 83403d63..58d86116 100644 --- a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js @@ -43,7 +43,7 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { { text: 'Submit', formBind: true, - successURL: LABKEY.ActionURL.getParameter('returnUrl') || this.nextStepUrl || this.manageDataUrl, + successURL: this.nextStepUrl || LABKEY.ActionURL.getParameter('returnUrl') || this.manageDataUrl, handler: function (btn) { var panel = btn.up('form'); @@ -105,12 +105,15 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { function onSuccess(response, options){ btn.setDisabled(false); - - if (!this.supressSuccessAlert) { - Ext4.Msg.alert("Success", "Your upload was successful!", function(){ - window.location = btn.successURL || LABKEY.ActionURL.buildURL('query', 'executeQuery', null, {schemaName: this.store.schemaName, 'query.queryName': this.store.queryName}) - }, this); + + var msg = "Your " + this.objectName.toLowerCase() + " was saved."; + if (this.mode == "insert" && this.objectName == "Study") + { + msg += " Enter study access data to make the study visible in the finder."; } + Ext4.Msg.alert("Success", msg, function(){ + window.location = btn.successURL; + }, this); } function onError(response, options){ From 53e91e388490fcfc25fcf24d56e08497dc0c6b9a Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 10:20:53 -0700 Subject: [PATCH 321/587] Spec 26558: set minimum value for PMID to disallow negative numbers --- resources/web/study/Finder/panel/PublicationDetailsFormPanel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js index a7ab6640..675dd513 100644 --- a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js @@ -177,6 +177,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { cls : this.fieldClsName, labelCls : this.fieldLabelClsName, spinUpEnabled : false, + minValue : 1, spinDownEnabled : false, fieldLabel : 'PMID', name : 'pmid', From fd4fbfc8c74105c40023c09fae8e23ba2147a615 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 10:47:55 -0700 Subject: [PATCH 322/587] Spec 26558: get study short name for publication from join with studyProperties --- resources/olap/PublicationCube.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/olap/PublicationCube.xml b/resources/olap/PublicationCube.xml index 37d13f6c..8786088b 100644 --- a/resources/olap/PublicationCube.xml +++ b/resources/olap/PublicationCube.xml @@ -29,9 +29,12 @@ - - - + + +
+
+ + From 7b2115ece857a53679f6bec98c4dbc647ea17439 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 13:43:31 -0700 Subject: [PATCH 323/587] Spec 26558: improve check for whether to show the Studies section in the details view --- resources/web/study/Finder/panel/PublicationCards.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/web/study/Finder/panel/PublicationCards.js b/resources/web/study/Finder/panel/PublicationCards.js index f1453c94..cd175126 100644 --- a/resources/web/study/Finder/panel/PublicationCards.js +++ b/resources/web/study/Finder/panel/PublicationCards.js @@ -91,7 +91,7 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { ' DOI {doi:htmlEncode}', ' ', ' ', - ' ', + ' ', '
', ' Studies', ' ', From f893512634969573c2e281cd29ab2214b4a5daa2 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 13:45:54 -0700 Subject: [PATCH 324/587] Spec 26558: add getters for the links and descriptions for form entry; change casing of PMID, PMCID, DOI --- .../panel/PublicationDetailsFormPanel.js | 6 ++-- .../labkey/trialshare/TrialShareManager.java | 3 +- .../trialshare/data/PublicationEditBean.java | 1 + .../trialshare/data/StudyPublicationBean.java | 31 ++++++++++++------- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js index 675dd513..878f94dc 100644 --- a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js @@ -165,7 +165,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'DOI', - name : 'doi', + name : 'DOI', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth }); @@ -180,7 +180,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { minValue : 1, spinDownEnabled : false, fieldLabel : 'PMID', - name : 'pmid', + name : 'PMID', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth }); @@ -191,7 +191,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'PMCID', - name : 'pmcid', + name : 'PMCID', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth }); diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index bc1e3515..274825cb 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -266,8 +266,7 @@ public void updatePublication(User user, Container container, PublicationEditBea SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.PUBLICATION_ID_FIELD), publication.getId()); // update the many-to-one data - // conditions - // first get rid of the current values for this publication + // first get rid of the current values for this publication. Then add the new data schema.getPublicationConditionTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationConditionTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); addJoinTableData(schema.getPublicationConditionTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId(), TrialShareQuerySchema.CONDITION_FIELD, publication.getConditions(), user, container); diff --git a/src/org/labkey/trialshare/data/PublicationEditBean.java b/src/org/labkey/trialshare/data/PublicationEditBean.java index 783fa444..be163704 100644 --- a/src/org/labkey/trialshare/data/PublicationEditBean.java +++ b/src/org/labkey/trialshare/data/PublicationEditBean.java @@ -17,6 +17,7 @@ public PublicationEditBean() {} public PublicationEditBean(StudyPublicationBean base) { setPrimaryFields(base.getPrimaryFields()); + setUrls(base.getUrls()); } public List getConditions() diff --git a/src/org/labkey/trialshare/data/StudyPublicationBean.java b/src/org/labkey/trialshare/data/StudyPublicationBean.java index db051ce5..912710da 100644 --- a/src/org/labkey/trialshare/data/StudyPublicationBean.java +++ b/src/org/labkey/trialshare/data/StudyPublicationBean.java @@ -116,22 +116,22 @@ public void setPMID(String pmid) _primaryFields.put(PMID_FIELD, pmid); } - public String getPmcid() + public String getPMCID() { return (String) _primaryFields.get(PMCID_FIELD); } - public void setPmcid(String pmcid) + public void setPMCID(String pmcid) { _primaryFields.put(PMCID_FIELD, pmcid); } - public String getDoi() + public String getDOI() { return (String) _primaryFields.get(DOI_FIELD); } - public void setDoi(String doi) + public void setDOI(String doi) { _primaryFields.put(DOI_FIELD, doi); } @@ -232,12 +232,6 @@ public void setAbstractText(String abstractText) _primaryFields.put(ABSTRACT_FIELD, abstractText); } - public void setDescription1(String description1) - { - setUrlText(0, description1); - } - - public String getUrl() { for (URLData urlData : urls) { @@ -297,34 +291,49 @@ private URLData getUrlData(int index) return null; } + + public void setDescription1(String description1) + { + setUrlText(0, description1); + } + + public String getDescription1() { return getUrlData(0).getLinkText(); } + public void setDescription2(String description2) { setUrlText(1, description2); } + public String getDescription2() { return getUrlData(1).getLinkText(); } + public void setDescription3(String description3) { setUrlText(2, description3); } + public String getDescription3() { return getUrlData(2).getLinkText(); } public void setLink1(String link1) { setUrlLink(0, link1); } + public String getLink1() { return getUrlData(0).getLink(); } public void setLink2(String link2) { setUrlLink(1, link2); } + public String getLink2() { return getUrlData(1).getLink(); } public void setLink3(String link3) { setUrlLink(2, link3); } + public String getLink3() { return getUrlData(2).getLink(); } + public String getStatus() { return (String) _primaryFields.get(STATUS_FIELD); @@ -546,7 +555,7 @@ public void validate(Errors errors) { errors.rejectValue("pmid", ERROR_MSG, "PMID must be an integer"); } - if (getPmcid() != null && !PMCID_PATTERN.matcher(getPmcid()).matches()) + if (getPMCID() != null && !PMCID_PATTERN.matcher(getPMCID()).matches()) { errors.rejectValue("pmcid", ERROR_MSG, "Incorrect format for PMCID. Expected PMC#"); } From e053f581fbf875a622614638f97baab9963a179c Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 14:00:28 -0700 Subject: [PATCH 325/587] Spec 26558: add front end validator for PMCID --- .../web/study/Finder/panel/PublicationDetailsFormPanel.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js index 878f94dc..f6fde581 100644 --- a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js @@ -193,7 +193,13 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { fieldLabel : 'PMCID', name : 'PMCID', labelWidth : this.defaultFieldLabelWidth, - width : this.smallFieldWidth + width : this.smallFieldWidth, + validator : function(value) { + if (!value || value == "" || value.match(/PMC\d+/)) + return true; + else + return "Invalid format for PMCID. Expected PMC####." + } }); items.push( From 217863480631b13f813b95d39ffe8ea01071e58d Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 14:07:44 -0700 Subject: [PATCH 326/587] Spec 26558: styling change for field labels --- resources/web/study/Finder/dataFinder.css | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index f67f6ed8..75a281a0 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -49,6 +49,7 @@ .labkey-field-editor .x4-field-label-cell { + padding-left: 5px; background-color: #E7EFF4; } From 0612e2cd8cb0dd6975f33ffebb309f04b8fb81a1 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 14:50:05 -0700 Subject: [PATCH 327/587] Spec 26558: add some more form validation configuration --- .../study/Finder/panel/PublicationDetailsFormPanel.js | 10 ++++------ .../web/study/Finder/panel/StudyDetailsFormPanel.js | 3 +++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js index f6fde581..a1fe2423 100644 --- a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js @@ -31,6 +31,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { xtype : this.mode == "view" ? 'displayfield' : 'textfield', + allowBlank : false, cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Title *', @@ -41,6 +42,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { xtype : 'combo', + allowBlank : false, disabled : this.mode == "view", disabledCls : 'labkey-combo-disabled', cls : this.fieldClsName, @@ -60,6 +62,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { items.push( { xtype : 'combo', + allowBlank : false, disabled : this.mode == "view", disabledCls : 'labkey-combo-disabled', cls : this.fieldClsName, @@ -194,12 +197,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { name : 'PMCID', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth, - validator : function(value) { - if (!value || value == "" || value.match(/PMC\d+/)) - return true; - else - return "Invalid format for PMCID. Expected PMC####." - } + regex : /PMC\d+/ }); items.push( diff --git a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js index 0e9b13ba..4bf2eec5 100644 --- a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js @@ -31,6 +31,7 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, + allowBlank : false, fieldLabel : 'Short Name *', name : 'shortName', labelWidth : this.defaultFieldLabelWidth, @@ -41,6 +42,7 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, + allowBlank : false, fieldLabel : 'Study Id *', name : 'studyId', labelWidth : this.defaultFieldLabelWidth, @@ -52,6 +54,7 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { cls : this.fieldClsName, labelCls : this.fieldLabelClsName, fieldLabel : 'Title *', + allowBlank : false, name : 'title', labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth From 5f57914e5a3d53b8971b24753f28cc99f723a32a Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 14:50:34 -0700 Subject: [PATCH 328/587] Spec 26558: remove unused query view and queries --- .../dataFinderDetails.qview.xml | 30 ------------------- .../dataFinderDetails.qview.xml | 18 ----------- resources/queries/lists/publications.sql | 24 --------------- resources/queries/lists/studies.sql | 21 ------------- 4 files changed, 93 deletions(-) delete mode 100644 resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml delete mode 100644 resources/queries/lists/StudyProperties/dataFinderDetails.qview.xml delete mode 100644 resources/queries/lists/publications.sql delete mode 100644 resources/queries/lists/studies.sql diff --git a/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml b/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml deleted file mode 100644 index df7e8426..00000000 --- a/resources/queries/lists/ManuscriptsAndAbstracts/dataFinderDetails.qview.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/queries/lists/StudyProperties/dataFinderDetails.qview.xml b/resources/queries/lists/StudyProperties/dataFinderDetails.qview.xml deleted file mode 100644 index 987dc95a..00000000 --- a/resources/queries/lists/StudyProperties/dataFinderDetails.qview.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/queries/lists/publications.sql b/resources/queries/lists/publications.sql deleted file mode 100644 index ed1421be..00000000 --- a/resources/queries/lists/publications.sql +++ /dev/null @@ -1,24 +0,0 @@ -SELECT - pub.Key as PublicationId, - pub.Title, - pub.Author, - pub.Citation, - pub.DOI, - pub.PMID, - pub.PMCID, - pub.PublicationType, - pub.Year, - pub.Journal, - pub.Status, - pub.SubmissionStatus, - pub.AbstractText, - pub.Keywords, - pub.PermissionsContainer, - pub.ManuscriptContainer, - pc.Condition, - ps.StudyId, - pta.TherapeuticArea -FROM ManuscriptsAndAbstracts pub -LEFT JOIN (SELECT PublicationId, group_concat(Condition) AS Condition FROM PublicationCondition GROUP BY PublicationId) pc on pub.Key = pc.PublicationId -LEFT JOIN (SELECT PublicationId, group_concat(StudyId) AS StudyId FROM PublicationStudy GROUP BY PublicationId) ps on pub.Key = ps.PublicationId -LEFT JOIN (SELECT PublicationId, group_concat(TherapeuticArea) AS TherapeuticArea FROM PublicationTherapeuticArea GROUP BY PublicationId) pta on pub.Key = pta.PublicationId \ No newline at end of file diff --git a/resources/queries/lists/studies.sql b/resources/queries/lists/studies.sql deleted file mode 100644 index ee30c110..00000000 --- a/resources/queries/lists/studies.sql +++ /dev/null @@ -1,21 +0,0 @@ -SELECT - sa.StudyId, - sa.Visibility, - sa.StudyContainer, - sc.condition, - sas.Assay, - sph.Phase, - sag.AgeGroup, - sta.TherapeuticArea, - sp.shortName, - sp.Title, - sp.StudyType, - sp.Description, - sp.Investigator -FROM StudyAccess sa - LEFT JOIN StudyProperties sp ON sa.StudyId = sp.StudyId - LEFT JOIN (SELECT StudyId, group_concat(Assay) AS Assay FROM StudyAssay GROUP BY StudyId) sas on sa.StudyId = sas.StudyId - LEFT JOIN (SELECT StudyId, group_concat(Condition) As Condition FROM StudyCondition GROUP BY StudyId) sc on sa.StudyId = sc.StudyId - LEFT JOIN (SELECT StudyId, group_concat(AgeGroup) AS AgeGroup FROM StudyAgeGroup GROUP BY StudyId) sag on sa.StudyId = sag.StudyId - LEFT JOIN (SELECT StudyId, group_concat(Phase) AS Phase FROM StudyPhase GROUP BY StudyId) sph on sa.StudyId = sph.StudyId - LEFT JOIN (SELECT StudyId, group_concat(TherapeuticArea) AS TherapeuticArea FROM StudyTherapeuticArea GROUP BY StudyId) sta on sa.StudyId = sta.StudyId \ No newline at end of file From dc645f6e53bcd588307b595cfe96c295a9917a50 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 14:57:52 -0700 Subject: [PATCH 329/587] Spec 26558: remove unused javascript classes --- .../study/Finder/data/StoreWithJuncitons.js | 37 --- .../Finder/panel/JunctionEditFormPanel.js | 250 ------------------ 2 files changed, 287 deletions(-) delete mode 100644 resources/web/study/Finder/data/StoreWithJuncitons.js delete mode 100644 resources/web/study/Finder/panel/JunctionEditFormPanel.js diff --git a/resources/web/study/Finder/data/StoreWithJuncitons.js b/resources/web/study/Finder/data/StoreWithJuncitons.js deleted file mode 100644 index 08c430d6..00000000 --- a/resources/web/study/Finder/data/StoreWithJuncitons.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.store.CubeObjects', { - extend: 'LABKEY.ext4.data.Store', - //private - setModel: function (model) - { - // NOTE: if the query lacks a PK, which can happen with queries that dont represent physical tables, - // Ext adds a column to hold an Id. In order to differentiate this from other fields we set defaults - this.model.prototype.fields.each(function (field) - { - if (field.name == '_internalId') - { - Ext4.apply(field, { - hidden: true, - calculatedField: true, - shownInInsertView: false, - shownInUpdateView: false, - userEditable: false - }); - } - if (field.lookup && field.lookup.multiValued !== undefined) - { - Ext4.apply(field, { - editable: true, - facetingBehaviorType: "AUTOMATIC", - multiSelect : true - }) - } - }); - this.model = model; - this.implicitModel = false; - }, -}); \ No newline at end of file diff --git a/resources/web/study/Finder/panel/JunctionEditFormPanel.js b/resources/web/study/Finder/panel/JunctionEditFormPanel.js deleted file mode 100644 index bb0bca41..00000000 --- a/resources/web/study/Finder/panel/JunctionEditFormPanel.js +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.panel.JunctionEditFormPanel', { - extend: 'LABKEY.ext4.FormPanel', - - cls: 'labkey-data-finder-junction-editor', - - defaultFieldWidth: 350, - defaultFieldLabelWidth: 200, - mode: "view", - - initComponent : function() { - - this.manageDataUrl = LABKEY.ActionURL.buildURL('trialShare', 'manageData.view', null, {objectName : this.objectName, 'query.viewName': 'manageData'}); - - if (this.mode == "view") - this.dockedItems = []; - else - this.dockedItems = [this.getToolBar()]; - this.callParent(); - }, - - getToolBar : function() - { - if (!this.toolbar) - { - this.toolBar = { - xtype: 'toolbar', - dock: 'bottom', - ui: 'footer', - style: 'background-color: transparent;' - }; - this.toolBar.items = [ - { - text: 'Submit', - formBind: true, - successURL: LABKEY.ActionURL.getParameter('returnUrl') || this.manageDataUrl, - handler: function (btn) - { - var panel = btn.up('form'); - panel.doSubmit(btn); - } - }, - { - text: 'Cancel', - returnUrl: LABKEY.ActionURL.getParameter('returnUrl') || this.manageDataUrl, - handler: function (btn, key) - { - window.location = btn.returnUrl; - } - } - ]; - } - return this.toolBar; - }, - - shouldShowInInsertView: function(metadata) { - return this.isJoinTableField(metadata.name) || LABKEY.ext4.Util.shouldShowInUpdateView(metadata); - }, - - shouldShowInDisplayView: function(metadata) { - var record = this.store.getAt(0); // there will only be a single item in the store when in view mode - if (record) - { - var field = record.get(metadata.name); - return field !== undefined && field !== "" - } - return false; - }, - - shouldShowInView: function(metadata) { - if (this.mode == "update" || this.mode == "insert") - return this.shouldShowInInsertView(metadata); - else - return this.shouldShowInDisplayView(metadata); - }, - - configureJoinFields : function(store) { - var fields = LABKEY.ext4.Util.getStoreFields(this.store); - for (var i = 0; i < this.joinTableFields.length; i++) - { - var field = fields.get(this.joinTableFields[i]); - if (field) - { - field.editable = true; - field.facetingBehaviorType = "AUTOMATIC"; - field.multiSelect = true; - field.convert = function(v, record) - { - console.log(v, record); - if (Ext4.isArray(v)) - return v; - else if (Ext4.isString(v)) - return v.split(","); - } - } - } - }, - - isJoinTableField : function(fieldName) - { - return this.joinTableFields.indexOf(fieldName) >= 0; - }, - - /** Override **/ - configureForm: function(store){ - this.configureJoinFields(store); - var toAdd = []; - toAdd.push({ - tag: 'div', - itemId: 'messageEl', - html:'Items marked with * are required', - border: false, - cls: 'labkey-data-finder-editor-message' - }); - - LABKEY.ext4.Util.getStoreFields(store).each(function(field){ - var config = { - queryName: store.queryName, - schemaName: store.schemaName - }; - - if (this.metadataDefaults) { - Ext4.Object.merge(config, this.metadataDefaults); - } - if (this.metadata && this.metadata[field.name]) { - Ext4.Object.merge(config, this.metadata[field.name]); - } - - if (this.shouldShowInView(field)){ - - var fieldEditor = LABKEY.ext4.Util.getFormEditorConfig(field, config); - fieldEditor.cls = 'labkey-field-editor'; - fieldEditor.labelCls ='labkey-field-editor-label'; - if (fieldEditor.isRequired && this.mode != "view") - fieldEditor.fieldLabel = fieldEditor.fieldLabel + " *"; - if (!fieldEditor.width) - fieldEditor.width = this.defaultFieldWidth; - if (!fieldEditor.labelWidth) - fieldEditor.labelWidth = this.defaultFieldLabelWidth; - - if (field.inputType == 'textarea' && fieldEditor.xtype == 'textarea' && !fieldEditor.height){ - Ext4.apply(fieldEditor, {width: this.defaultFieldWidth, height: 100}); - } - - if (field.inputType == "checkbox" && field.jsonType == "boolean") - { - fieldEditor.inputValue = true; - } - - if (fieldEditor.xtype == 'combo' || fieldEditor.xtype == 'labkey-combo'){ - fieldEditor.store.containerFilter = fieldEditor.containerFilter; - fieldEditor.multiSelect = field.multiSelect; - fieldEditor.store.autoLoad = true; - fieldEditor.delimiter = '; '; - } - - if (field.isAutoIncrement){ - fieldEditor.xtype = 'displayfield'; - } - - if (this.mode == "view") - fieldEditor.xtype = 'displayfield'; - - if (!field.compositeField) - toAdd.push(fieldEditor); - else - console.warn("Composite field encountered", field); - } - }, this); - - return toAdd; - }, - - - /** Override **/ - doSubmit: function(btn){ - btn.setDisabled(true); - - // force record to refresh based on most recent form values. this happens reliably in modern browsers, - // but IE8 sometimes wont apply changes when the cursor is still on a field - var plugin = this.getPlugin('labkey-databind'); - plugin.updateRecordFromForm(); - - if (!this.store.getNewRecords().length && !this.store.getUpdatedRecords().length && !this.store.getRemovedRecords().length){ - Ext4.Msg.alert('No changes', 'There are no changes. Nothing to do.'); - btn.setDisabled(false); - return; - } - - function onSuccess(response, options){ - this.mun(this.store, onError); - btn.setDisabled(false); - - if (!this.supressSuccessAlert) { - Ext4.Msg.alert("Success", "Your upload was successful!", function(){ - window.location = btn.successURL || LABKEY.ActionURL.buildURL('query', 'executeQuery', null, {schemaName: this.store.schemaName, 'query.queryName': this.store.queryName}) - }, this); - } - } - - function onError(response, options){ - this.mun(this.store, onSuccess); - btn.setDisabled(false); - - obj = Ext4.JSON.decode(response.responseText); - for (var i = 0; i < obj.errors.length; i++) - { - var field = this.getForm().findField(obj.errors[i].field); - if (field) - field.markInvalid([obj.errors[i].message]); - else - console.log("Unable to find field for invalidation", obj.errors[i]); - } - Ext4.Msg.alert("Error", "There were problems submitting your data. Please check the form for errors."); - } - - Ext4.Ajax.request({ - url: LABKEY.ActionURL.buildURL('trialShare', this.mode + this.objectName + ".api"), - method: 'POST', - jsonData: this.getFieldValues(), - success: onSuccess, - failure: onError, - scope: this - }); - - }, - - getFieldValues : function() - { - var fieldValues = {}; - var metadata = this.metadata; - // getValues returns the empty string for fields that are empty, which is not what we want, - // so we'll walk through the fields ourselves. - this.getForm().getFields().each(function(item) - { - var value = item.value; - if (value) - { - if (metadata[item.dataIndex] && metadata[item.dataIndex].stripNewLines) - value = value.replace(/\n/g, " "); - fieldValues[item.name] = value; - } - }); - return fieldValues; - } -}); \ No newline at end of file From e9ef8335be7bea50294ee515f93583e0b25e05a1 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 16:40:11 -0700 Subject: [PATCH 330/587] Spec 26558: set default fields to match .qview.xml --- .../labkey/trialshare/query/ManagePublicationsQueryView.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java b/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java index 15b15d59..1441ddd4 100644 --- a/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java +++ b/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java @@ -19,9 +19,10 @@ public class ManagePublicationsQueryView extends ManageCubeObjectQueryView private static final Set _defaultColumns = new HashSet<>(); static { - _defaultColumns.add("title"); - _defaultColumns.add("show"); _defaultColumns.add("key"); + _defaultColumns.add("title"); + _defaultColumns.add("status"); + _defaultColumns.add("publicationType"); } public ManagePublicationsQueryView(ViewContext context, BindException errors) { From 7ec9962aa7dc228114fe9b951e85263898cdfb84 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 29 Jun 2016 16:41:28 -0700 Subject: [PATCH 331/587] Spec 26558: update test data lists to remove unused fields. --- test/sampledata/DataFinder.lists.zip | Bin 15024 -> 14655 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/sampledata/DataFinder.lists.zip b/test/sampledata/DataFinder.lists.zip index 589b4f43c2110e41888b60540bea49743df27ba9..0693038d18b5109e2f812bb3818ad4ed3b83bfbe 100644 GIT binary patch delta 10393 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 delta 10789 zcmZ8{1y~(B*Y3vM-QC^2P~5F}ad&syxI4w24YatsZYa{??(W4YuD3n+-v9s3muIpv zlUZvfSrrGOp#GGpIuIPdwv^YgN1Si=XVpTQ z#Z{3OpL?2{r4(AImR-A2Xxp=@S@0yqF)35F0EHy`ix)OYa|2h3x%HJPkA*I>T^-L` zH#R}qre{i}@!?)nRBR(>>Nd6Sj$7*qumLYOM|=f?oO@ao?i~|hUKDNP%~4&6A$CsR zT=TA%a&&}wHryG2+>?@y7WmfSEB?fV!3*+h-6q?9dgdq?C#Y@MQkrV*!qReA+p5XKCK}>ZJ$RP$l?p?Rz#Y9v`4S_ zvhLb>Qx-AJzfnE4-!bM%0v^*Dmk`1!sP>{+K$Dj_$SCsyekd4V+s-Z*Ac}1$5S0Z9 zmw0R~i_=o_*w=6)Ir;{Z;e*7ZrKCIvmk)Zd5q4lm?zVJ<&hmONBW{(E znM5+dlBbrLm7q#^_~-hcL0Wx|6V`$OY%^@mp0z`);dKhi>os9|%mkM0lzgS9FH~`w z3}_h>OP?vU#Xs|w&c}w6&rZFAq{#D3u=tI?#AY%BDX<3VY*K{djiJiOk`s|pMC%!G z>>pareX2cMrv-&=e1*2It0ux1ZMj!B;=PRzk0}pqzRRP3BA1~|q3A!q)kqnHd;r&| za)QnKiQq0KyTLbQ)3&gow$QvK%5ku-cy?qSDrh;wZzd;8c0L<3D`UB&#KOYDbFmy% zAqc`i6e)w_!Qf=A%uoSm8&pK>we?&y882=fk=TO`7T#1#=Lf9r^`reQ8~)T=ZslL) z`U`l!-pB_~v8KU82up)HG~K2-21l<{d0D4M8@a>M-Vc*Lhe7&~h35VgsOBL?Zb^)t z-$74b?YDh?`!ud&m@GPt5hiciH=(`io;aTaWxD1O2ecZw3L-a}XDCfLxqSL5am?WQ zw1%)u?2uO{qNWh1;CGY9N)Qk-AyGeoU#cyWXf|yu`+$!u-7^o5xT*p((8}d|5ujw+eMmJQAg-cNS4cldVIcWQI3SMsrb~;T8Je&p{@ahh zC4Jz#8&ka`)2ynrk8P|)3A}5Z2wAAe&-qJ1q7tFAHLfzsa$9_>!vaNgoc87@;?YSZ zT676BXlE!o$%bnr;*P3D47?)QZM4G^psI~4Har%tOlh}pdJUB6zJJ#-CDk>hX??T| z#s-b$ocOLu@lIxL4%ex9EQ69h-Ouek_=YKg-R>J)?9&7_rMjX!?|L>0oAz;c(?xd*QA}4ZX2`aN0^s5ewBt0fiRq znNP6`3}mfu?I$DSW*<9kxs|>0pr~Y`5F4R$-WEd5^i8-x_}5^<%GauO5rM1K^>df__T<&gj8ETGx|g3O zwamEq-(UqVPiP@h6kTPu=eMstI46b_R^zZf1*^uDgWtqIofaSkF9L2KV}_nl#5%RO!k~x(#fc<> zdZS1nU(Bl3?o0+gKdl-^(3Ck-BH5QMJ&xEkFP%B?{&HyjSuI|-G!Kkw%8J|i;2HXL zR=<|N6SYMzQT(y@% z)uRIhJ|E$!h3e9(oOSdPszvJxRjgD&XNJ?@6@w(!I46f%8{oOaO2dcavVLm{WE&j# zZH_s$a{ERnqgQY2Kn=7vOF=aB`Nc+zGii3)Cw|FOWR9|s%YGM_xe&&dES8ASd< zXzj|p_FH~CaPSjqVLu@x`NO!nT!<)}=8#zraKfhC)!|JqSClC{>G+*( zsFIBsc?BcrB4n!f{WZU+&DC7ia;4O`##F)Jn{hw1fNUR`1|M*GmAr$FJjv{vMC~^Z43fUDg_1NahJ(=*&<^lUF^;q7QmQd1#3}zD(FU#FrnTR zAkW})z?NkZsc!!8^u?8el9AAw3td#>4*N4qw5x$SVj9rty~}%%NHH)#BrfUXwff}s z=WXDz7x2WZ*R{jwagDCvIeZexsnaYhtA(ASDt;dl?MHe@gvX{9Of}-E>+MCWY1y|p z_=7wqmqwoK@)Na5+>ZhDyUE{~BJlZ`akAdwQB3k~j95WxZDSRx5TW#>+sVRAu%%JW zOM1?80;)jEAms;U8#qd4$t*NpH#v8v9mL=kO~21-hd$5p$Y!(UbMvX(d}|`YOh=dd z_F6c`=E`YrF$416-Q*`d+#y8buIP$2&$Ur|_C!v=KZod2AclP}VvEtx^5)R;`;Vlx zJKis1BW`zn*!P9{-HgAi)6#^+T`TlBf{!_TTY%SrXdl?dV}v>aWfm^j_Z4Oju)T|m zlTgyWuiSeq)-V$3tNchf0b^Ib!gZ3~geWjyipX%F2C2J@TRd@gImn``U3r0aoIpDl zVc$37PMvnPsYnBN>7exS2p(JKNYwJ{Rlg@Sxh8kK_IoDiMAa^Jl8h)-^7y&8DZbW( zC;>es4b#p`%sa}DWdXH^h4jGhx8l>~tA4_x5X?aeru4kx*psd(Ds1V(CS-k>`nAZ< zm-g@m(A~#Ea7D_y4>>>WDw^c0E9yxl#lPEh@YwOan&dY~Fya!?u|X;QA`VrQKfks5 z(7wo*=Ety{ejY$5;3rG)VqMZiw2@PvISSOUulQiEGfa|Olax|V3pXao}{su2Q z?-;X)k%x_{$w=F`0J70}BZ5cmeJ7 zZCl{ae9m}I^ncBWTIpzmo>Pe^Lk%~fMG}`;N-oY@^LkK4*gMJ)-sZ}H@o-EpjQ}>B z&$N7N#JRqde9{#78G*nb(u}^Gae{p2WnJ zamTN>aT3+qGPB9|j!0RbSz5e*#j4^Fa>KjHUe#}%cg@uy(QV4@CL$Px)?V_kUh&Xh z@qqagc0&-YcQLK=TcJW4b6FcdkOvr0I6}hraf!tf-96(N&`31LZKyx_$eID`Eq4`s z+w}BMy+(IJwp6a9C3L+G{iPUL(u|gd_1g1V1+v!Rp9^n(H5V-kf3Q&cUg)#ryd?Cb ze2VkizR=5k(Vt|Gq**0FXdOfr)CIc6N{DA?QRW@p^CqP!VV_ygR^v*M_N1FlJIQ7C zp=~yWKxa=cnn^A}L)xG`0f2z+huC`*xcEb1g{|)D$5KtABv*-6G@OBV?I?>OdkwAUV)ud`dSh zX>u;@t1i|vZno4m9<4`57^%se4*ZA|!eiO=n8B7}fKE{%T>PV!9f}W#CagQlTmlI9 zEa1JV4{_6oJB~xOUn|Yi>C1ADE)3TvICsq5ief6YYHvQjK0}T-xf@*GXr)wfC~D-# zD{FfJjke?Xa}WcEVI&C1Ta^g&Hr#(3NI&XIb_`rK(s?2b6OUoft< zu`wG)=hp9?o80$Or7Z*BD@ytta&yS)ZBV;EcW@;vcLTbof;Bilx!<2CE9Txo>=mrh z4NCP(6XE&>->txSXUDyQ#AWE~8$2YViXX1JX7yR|nhcpkNY7NC%53;u|1jlDl5s?vR=p8MP=R z`S1pgI^8J);tOT>?Kj9HB-hT)OK6KYm;hdr(s2im3Tf)}AP-r@+y($Rye=Qic3AMk zWc7E2-7O#hA5$lZgJZliuE$)gIRZkWD?3)4!5e!jZ!e}H6K|8HP30DH>Mgs^4~1{%0w=QrtDi69di%{L z-ZOWo%Q6OxQtz!}2CSvn(!iAIkC|l((U{IDxWJ*~&JrMtR!f17AayRTm+psvc*?`` z9lPzG~c1K_>Wx`pPIT)-FVU71t6-1VHbogRIrn2584?WH!y0o$1_2 z9ro*}`iMSb#^$w0OmH$;tvUuPizf`n2A%TG<8*1n@4Z?0DnY!EdKBy#%{@Lu^AJ;` zm_bzJoE~{5lPM3ph&<5Us4@R)+kip1h>Ow_h#V)VxxxZl=AbY>PW?H-uQdqsDdQbZ zRRIou%mmb+*D)I6 z_5_qOV3$*`1}FZWRC4;JtJSQDC~{^Wes+2-Vh|a)-Li8%|ggta95Bp>N_+WJrB%}OtNVOJZs+Iuq z!mmE%O1Sq;?K-&|v&LUN(Xh@_gkUv0NV;l*+~z;X_lr4qUfy|NgMO5u$*!l(fj8a- zd6*{yZDM|H;4vGnZTs>Yx3~sen_hA8xNSKnt;`g#aXJn^-_xooT?RJRgJn~ zlcvD4Q9<2lcb=`xsuzTYNae4Qxs}?FT!4f7;PRRI;mp6)j}uIZ+}WUZdEo3T(wij) zUy_EQQz?PpE6>z%mffn^$pfZ(KpYLlU?j#c4ae&DKF+;7v|Kmv$C!X`?}q^vStsC5 zYh|LB_s5Okct#DdI_P*@C!N0$PtswD6xTY7A>*XCa%k~WB*+}?-?^W4aB)y2eBL|w z1+}m^KJ-0ACq0unMN?LGw=pI1{dWsjbh1v71fl59H-pvlbF@iXAL?3ma+q)o8)1Ij zE`O2tl^kleK=sVzhW-$Z^h_3DgQo0ANmBB#-vP8!{@r~fUi+tIiq335bEDru#I~f# zX5(1bZD4Y~$JsL7EM_SOf3|Yb!GYte#~X9kkF43TQuR2rBRV7riO;{U9x;p(fcI!+ z?B?_yNkwxK5iyrKBQmP~7V8*Hl-ZS5LcR@YZLfNi-l=Exz7xS!Hy^8uV8y1hHiR)VunDOh^3tG#^C*_bo zg7vRJ{VNWF|A?NyBK@y${3|~E5hWr=hfV4J!lT0hU%5u<#sTpt!Dz0uBnCWoCeZ@! ziqIFjGpHYhLZ;MmlSLaCy@ES?BEmzaibB@{r|m~By{=bt}aP8 zerBT>)3c| zM9Q@9ueW&=G{C`DK5ewvc$OI9TmddO%Pv{Ta#EY2s_=@Paq5=-u(T%Qv>i27PTO$( z(y{Dm%$_Zn@p&DMcFxfOC2*A-^>u-T?RpQB)&cjcK*9MNkad=b7Ly8qO@4$w7xwksJYaEh(Xz-z8Co98=oLls+W7;YCfYzH4|^;C~f z9WAE?BJQ+4E(3o2j)^&qdd1{h=)a;%wKC+KLWQ zIv8ds{lBSFMKsEHq<)T2%Ih*vXQa>CAvp72uJ^!10R#J#Z4_y-plsm_o06NXxRw@j z^MuzU&$BHFnkPfQcy1fPVfed63!ko;bgySWbV<7U@NdvU^34cJb8PI`t4vnXrHB9c=o z`zZ&oz^2*Ev%9piGz#pklFy9ER)k<4{FT$>?ouNEhKX=I@XMkTLlPbV90pPCi5GDDXx@^RwST z@}bRKYWZv)Tw`A$DbG+Y1dr}5BwZ2FVN)SKkRjVsCiqvY#FoH@bqPjXc}e{I%hnL0 zI(+@@knd0HpYNY7+~p>Kcntd%GNI)KsZx~W!o3pbVu9x&u(`d)8kFh2EO423#&atg zEf=~A%N}D!2#N15p0tK0L=zhw3K&ik6Fn&>A(9MfnE5zOC3eo1keiVR8RqG#KgdVq zfXo#ZKRXoonJuqiz-a(BniF-6K^QRn&UzGutatWI6pIvIMHHk_iI`S&b9kLsHbjou zbvk>Sw_K2vK{N2#RQn6H&j5CV)lZyLnvJd7{fIAVicu56;`7MD1uFd$gW!rbND1D_ z#8xJC_y-V+F&rdbMv|t&S-s+yxVm{n;PG>XoCbcnnQL-Rl`3WG15f68e0>`dZUtqR z%dU*=3w|;ugf>G$I7_A%JU?V*?h-^XLrE?YC&IW9^pRT5wJX{EMQ#F2w&raZrT9#Jpv<2wQYpUQ4g z*yQRUYj-{IBF~w0zXNou_(JE>)KFhWY)I?RNF&>vk+`4ol{{70l-JQmYtJ&#GBRU> zYR9b_%lA3d_aBk*r~4sw6KsvuqKqMhSnYhsgY9 zY0=6k3kInAQ{o1p5$~Zxhfr7m8IyRtJ?H2lVX~^{V8cejpulI`^8EO;JzYJFbV+G8{c*NHnX16r)(e5`?W6(6Ia`{)U^UrT6(=39sY312hRDH@AFW+$u4kW|S&9KH zLnU&twL$5(Zy1p~NiFSn$k3IRl)3PC6xP-B9=ujJl?Pa$h8ONL%d_LQsbJ68!4NsTelMw-su1{G*BkOL>3b z%$3wGei75t<$_6nyvJB&McR8-wR#zA740tb!@El~4k+ z$8WuIdIh?PDvstw#{~_79VMGSBOqmWv&DMDytSpH&ja24k@HZ2#72>eRc}vEh?|eT z0aCsBA3-q8>3gd*9Q|7%!ulKffx2Pf-XSb1jl7MbK(xNb+9bDnGRnhGhRzSgjhdQd zbN-($z(27NenQ4~E$z8Pmmd==9~%=c#^9Mx-#3QLbH$#W$>(a$6I5^ z#C?{0awFp%YLyS_*nxdb7JDBGf>WhJnkO|GZ5cX4v!9d(hHaY@61u-ixOZ3>dsM36 zDxCnLxQp)3V((BHF3H356)P(ksCTa=X1k@%n#v*$+erI+a$wA$R##^)(ATr3Fxl3$ z^cCGHN4Fjdm2C;P(Y~=5kT~szeRcU!SlXp9h?!!07Tr;aR3gtAQ_GF?Lm+>HLJ*{5 zJk-MK9H5i}G$si1Fm@!mc5ruXK6b?NHfH=W6vx)0y*GllxBL<0v&Iv@mM-n(Kg{iz zgnr1Q+hUL-S7}YRq)C&bC`mNf#K5#PPrg1$JB8*Y#p*&Edf~_7M?HDxORMmb&1|{e z9=uQHil0T8af6bJNJqv4rfsogKF~UMu|;2H~JPofsP&6xu)_&(OL*X35%2? zGkK0$c}qlzvoN&SQ99Ae7(35RC}!z4;OzrNAN25GbP%tJ>7>8HhLBaV3JmX#3=~Iu z9)@xNi5{GsoHF!39>yJE=`|>*)u=>~e18$B4`nj>7+~@+dgVL#yq{hB!HUa@n~*=k zfA{LFm{<2*c7!>HFbtN^saOL$I!OxBL3wQC4ZR$L&zh0zA;4TpJ=^x8Me%sR1h^-_ zGh)N;!%p8#^#vd)mMZ3PSeMsyo-%od+t-N&+_9Nrg|%NLy~!(@ro2e{6g)z36U@m) z5BHVUvF=o5IC-eBAg8;z`U;dU;v9e3IXGtU)p1djv|W7olk|^#jsYRAkky~!2F9); zD<9fEu40s(tx#<)gumR~|1dX6y7gPldX041hblRiEwG7zYkZA{>9ezZ+w*s#-mLds z0GY&K|G@X` z&fIB`yHv>V48?+Xe(;fGG{B3EL`ACSJYq^F_4B&vNrI2s_Ni0+KFrU!FG|RtJuf8I zUn`@LRo?X}2h!@qpd#NRKbY538Ubf;YJgUq9aEvlq2=vui{7Rwi=y~rxL)>#O@SvlHWbx-W4sN8MVcT)Ic-DI7z9Qn}B8k z*28S?py`wF?UMWF;vHN-OiQJ&3)mpR-3Aw+{PDASOl4ryaO#pn&JFLXWnqlBY8u)39YX^-c2!Dm}oodl9kN z3hlH=E8u03dd^_=$lLpOJ(y?dWK+wRR4OCsr|6b>`?RW|N)av($FY|?pY;D>aqGtOPOD}lihZ>q9 z1`a&H72TP%KvxUjhaH)6Xj1v?(<ll?P~ zHk_}$om||*dq)r;n#^i|0zNfF6ufgNw7(*J-E-gD+3D+Z;~PR|oc>g+AKiM*5nUv( z5uJlv7Q=JnM+Yb$h&|oDA4~Unry` zS)DQAQ9>`TPwMNefG&DP+_2K>E|eJ~h7$ZzM5XudiTm09K3DkUWVRnMU9c zH3Au>s>>kC_>S%_K$2J)iz1nC6tg%{8$UddHPwh-0R@XRJ~{)*;>`YXf!PbCY-Vz4 zlfMux$+y4FkXSVwVyNfs8>e!3jy>^G=W}sUHb~bu70YyJxsTDnhw-T5N*kDmZHPhj zlSBRb{L>))cC5dB?(LN9kD2*m8Lf5&C|Y7{f!>uCk-C{)pcv95iKGk>4Kq3Tt|Bd_ zs&QeZ7S6>-GEH=7a>(2GN>OMNE{#YQ^FWQ*jhFh(nN|>9WMZYwR9Rn*s?ciq`YXav zXV#Wg6(0BC>p=<@A{u4IAR928J!9u+R42BeZByShHRno}DP1n;6{Zm;gt^Fvc{E#8 z^3oJ(pyKHhJggmiR`^n=J~f6b?<`2q7y`kP^@It!y)JA#*#lRKB4kghgCOd|_)GMp zkJk1zT1XC+wID`h_gtt^`r)1?^dgLKV>6m~cNlMh{G+%wH~W^c-(k(uk}ta`XO`{JeyeKVHH zgRk(Y*uhNN%8}hkqZ5!7oI-fxogO?^%GB&qRXu6uhLAp2INXSA&bnT&qpSM8O>3PT z%4fF&4DZ_Qnh4FVt?BwzFC!mpQ+i6$)}QT1CJ@su`SoMdoGTv!pa-2jgxT9H_$6+LQ-;r>gtSJ-ES>U50TTgC~3D1<=W-_vL~hLQP^ zI(};dg}bB<^*(lr0fVN$jWV|hP^I7()g>X!!~x4D&bNxktEdtBVKo>1Iu}osk~wOmOSA z2>UD3YDL^Uyk$(!0#BjRa%-^CZjifRZ)?!vUAxuMw~s-9@R_*>3fz$oT89oRfEm~% z?YteN+1c$ZP^OqW8L1!wxkR;B%f_NzX>U znS#YNA>95FnK5))dY6Vr#%(5z)r!^1LWou(s7ayjND^hs(O^(yZ^7a>x0k4-R2W4_ znn1CQrv?<~Ey$=LzC>a#oKk!nk$rj{Njt!Eo`EMf8Jn;7x7bVn6`(BD?MW#%X5sTV zlKiqWdbViCQbvG^E(X_^9#M+NU`MGep$f_4h5pMl5Z!!u4}v4%Fv%`Zv=-SMay{>4 z+aGi)n)^^;qViR#h^yRFw2k5!e1@AC_W2#0>jeA@5pf82X^SubfR@33LBzk1;SYuY z+$`MPZ5*xs0f%ahQQHA_^sYmuu=9AkuZc}dsAW^?))YxMQ06xEQVz@3+BA>PD-B8% zXXQr~2A0xXojK>q4g)=X#pjAcySF@6#^zsvP34?#F8)0$*9JZEMDbzvM?9HV+24S1 z#nN92oDIENVuoC~HGFzZ$N0FY^hO?na|Oxl>WblH^l)*Zt@JE}+exxyKYJ6c7{!Nt z8u}slaO)4CWTV@jrg`gRM=hNfF)`6lqtvB9u!>QFM|=+#m+vF+Y(#c^?U=mjJ!5wE}$vURb@kaw#d+9&C z;j5l6#t}q-c+s?SZL$uFp)@G5A5F&YcVDq=UM59UUf26yi2gbj-NSs71Ofnn`Quzw z1qeuN!2j$4lx0xgv4(+2b>$I8{foi>yQ+}d#Uq69x9T4jK7|(tk`5ykkC%k}Z{2?q ziP--G+J8@IFjF@%a8qr0k^i9ueRzo=lW0Sx)Bf|+|C|hA{Y$BnBGrnY;vd(=@)IGvvHX9p w6!PI{|Mkj$4wAo<{9 From 809a392398ebde2ee6cfe7f66b2a99a24d0b02a4 Mon Sep 17 00:00:00 2001 From: labkey-adam Date: Thu, 30 Jun 2016 11:57:31 -0700 Subject: [PATCH 332/587] Bump most "managed" modules to 16.20 in preparation for release. (Script consolidations and remaining modules coming shortly.) Introduce DefaultModule subclass CodeOnlyModule, for the ~55 modules that provide code & resources, but don't manage schemas. This documents intent, avoids mistakes, and simplifies the future release process. Eliminate some unused schemas/dbscripts directories --- src/org/labkey/trialshare/TrialShareModule.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareModule.java b/src/org/labkey/trialshare/TrialShareModule.java index d6122cc5..3c0a9522 100644 --- a/src/org/labkey/trialshare/TrialShareModule.java +++ b/src/org/labkey/trialshare/TrialShareModule.java @@ -21,7 +21,7 @@ import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; -import org.labkey.api.module.DefaultModule; +import org.labkey.api.module.CodeOnlyModule; import org.labkey.api.module.FolderTypeManager; import org.labkey.api.module.ModuleContext; import org.labkey.api.module.ModuleProperty; @@ -36,7 +36,7 @@ import java.util.Collection; import java.util.Collections; -public class TrialShareModule extends DefaultModule +public class TrialShareModule extends CodeOnlyModule { public static final String NAME = "TrialShare"; private final ModuleProperty _cubeContainer; @@ -51,18 +51,6 @@ public String getName() return NAME; } - @Override - public double getVersion() - { - return 16.10; - } - - @Override - public boolean hasScripts() - { - return false; - } - public TrialShareModule() { _cubeContainer = new ModuleProperty(this, "DataFinderCubeContainer"); From 42243a70a60f522de634a31de11a35c4d6ace7cb Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 4 Jul 2016 20:42:24 -0700 Subject: [PATCH 333/587] Spec 26558: fix ordering of cancel links; handle form errors specifically --- .../panel/CubeObjectDetailsFormPanel.js | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js index 58d86116..6deceedc 100644 --- a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js @@ -52,7 +52,7 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { }, { text: 'Cancel', - returnUrl: LABKEY.ActionURL.getParameter('returnUrl') || this.manageDataUrl, + returnUrl: this.manageDataUrl || LABKEY.ActionURL.getParameter('returnUrl'), handler: function (btn, key) { window.location = btn.returnUrl; @@ -120,15 +120,22 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { btn.setDisabled(false); obj = Ext4.JSON.decode(response.responseText); - for (var i = 0; i < obj.errors.length; i++) + if (obj.errors[0].field == "form") { - var field = this.getForm().findField(obj.errors[i].field); - if (field) - field.markInvalid([obj.errors[i].message]); - else - console.log("Unable to find field for invalidation", obj.errors[i]); + Ext4.Msg.alert("Error", "There were problems submitting your data. " + obj.errors[0].message); + } + else + { + for (var i = 0; i < obj.errors.length; i++) + { + var field = this.getForm().findField(obj.errors[i].field); + if (field) + field.markInvalid([obj.errors[i].message]); + else + console.log("Unable to find field for invalidation", obj.errors[i]); + } + Ext4.Msg.alert("Error", "There were problems submitting your data. Please check the form for errors."); } - Ext4.Msg.alert("Error", "There were problems submitting your data. Please check the form for errors."); } Ext4.Ajax.request({ From 1a7be4ec0339bda092b70f024b82483eb7746b50 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 4 Jul 2016 20:49:09 -0700 Subject: [PATCH 334/587] Spec 26558: add tests for managing publications --- .../trialshare/TrialShareController.java | 5 +- .../labkey/trialshare/TrialShareManager.java | 15 +- .../trialshare/data/StudyPublicationBean.java | 62 ++- test/sampledata/DataFinder.lists.zip | Bin 14655 -> 13515 bytes test/sampledata/Lookups.lists.zip | Bin 0 -> 2702 bytes .../test/pages/trialshare/DataFinderPage.java | 13 +- .../test/pages/trialshare/ManageDataPage.java | 129 +++++ .../pages/trialshare/PublicationEditPage.java | 241 +++++++++ .../trialshare/PublicationsListHelper.java | 118 +++++ .../PublicationsQueryUpdatePage.java | 43 -- .../StudyPropertiesQueryUpdatePage.java | 16 +- .../tests/trialshare/DataFinderTestBase.java | 242 +++++++++ .../trialshare/ManagePublicationTest.java | 494 ++++++++++++++++++ .../trialshare/TrialShareDataFinderTest.java | 150 +----- 14 files changed, 1329 insertions(+), 199 deletions(-) create mode 100644 test/sampledata/Lookups.lists.zip create mode 100644 test/src/org/labkey/test/pages/trialshare/ManageDataPage.java create mode 100644 test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java create mode 100644 test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java delete mode 100644 test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java create mode 100644 test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java create mode 100644 test/src/org/labkey/test/tests/trialshare/ManagePublicationTest.java diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 931b432a..9824b0eb 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -1030,7 +1030,10 @@ public void validateForm(PublicationEditBean form, Errors errors) public Object execute(PublicationEditBean form, BindException errors) throws Exception { TrialShareManager.get().insertPublication(getUser(), getContainer(), form, errors); - return success(); + if (errors.hasErrors()) + return errors; + else + return success(); } } diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 274825cb..97986b46 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -49,7 +49,6 @@ import java.util.Map; import java.util.Set; -import static org.labkey.api.action.SpringActionController.ERROR_CONVERSION; import static org.labkey.api.action.SpringActionController.ERROR_MSG; public class TrialShareManager @@ -221,7 +220,9 @@ public void insertPublication(User user, Container container, PublicationEditBea // insert the primary fields BatchValidationException batchValidationErrors = new BatchValidationException(); - List> pubData = schema.getPublicationsTableInfo().getUpdateService().insertRows(user, container, Collections.singletonList(publication.getPrimaryFields()), batchValidationErrors, null, null); + Map fields = publication.getPrimaryFields(); + fields.putAll(publication.getUrlFields()); + List> pubData = schema.getPublicationsTableInfo().getUpdateService().insertRows(user, container, Collections.singletonList(fields), batchValidationErrors, null, null); if (batchValidationErrors.hasErrors()) throw batchValidationErrors; List> dataList = new ArrayList<>(); @@ -238,7 +239,7 @@ public void insertPublication(User user, Container container, PublicationEditBea } catch (Exception e) { - errors.reject("Publication insert failed", e.getMessage()); + errors.reject(ERROR_MSG, "Publication insert failed: " + e.getMessage()); } } @@ -260,7 +261,9 @@ public void updatePublication(User user, Container container, PublicationEditBea TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); // update the primary fields - schema.getPublicationsTableInfo().getUpdateService().updateRows(user, container, Collections.singletonList(publication.getPrimaryFields()), null, null, null); + Map fields = publication.getPrimaryFields(); + fields.putAll(publication.getUrlFields()); + schema.getPublicationsTableInfo().getUpdateService().updateRows(user, container, Collections.singletonList(fields), null, null, null); // update the many-to-one data SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.PUBLICATION_ID_FIELD), publication.getId()); @@ -279,7 +282,7 @@ public void updatePublication(User user, Container container, PublicationEditBea } catch (Exception e) { - errors.reject("Publication update failed", e.getMessage()); + errors.reject(ERROR_MSG, "Publication update failed: " + e.getMessage()); } } @@ -296,7 +299,7 @@ public void deletePublications(@NotNull User user, @NotNull Container container, } catch (NumberFormatException e) { - errors.reject(ERROR_CONVERSION, "Invalid id (expecting integer): " + id); + errors.reject(ERROR_MSG, "Invalid id (expecting integer): " + id); } } diff --git a/src/org/labkey/trialshare/data/StudyPublicationBean.java b/src/org/labkey/trialshare/data/StudyPublicationBean.java index 912710da..a05be666 100644 --- a/src/org/labkey/trialshare/data/StudyPublicationBean.java +++ b/src/org/labkey/trialshare/data/StudyPublicationBean.java @@ -67,6 +67,12 @@ public class StudyPublicationBean private static final String KEYWORDS_FIELD = "Keywords"; private static final String PERMISSIONS_CONTAINER_FIELD = "PermissionsContainer"; private static final String IS_SHOWN_FIELD = "Show"; + private static final String LINK1_FIELD = "Link1"; + private static final String DESCRIPTION1_FIELD = "Description1"; + private static final String LINK2_FIELD = "Link2"; + private static final String DESCRIPTION2_FIELD = "Description2"; + private static final String LINK3_FIELD = "Link3"; + private static final String DESCRIPTION3_FIELD = "Description3"; private static final Pattern PMCID_PATTERN = Pattern.compile("[Pp][Mm][Cc]\\d+"); @@ -146,7 +152,6 @@ public String getAuthorAbbrev() if (getAuthor() == null) return null; String[] authors = getAuthor().split(","); - StringBuilder authorAbbrev = new StringBuilder(); int endVal = AUTHORS_PER_ABBREV; String suffix = ", et al."; if (authors.length <= AUTHORS_PER_ABBREV) @@ -297,42 +302,66 @@ public void setDescription1(String description1) setUrlText(0, description1); } - public String getDescription1() { return getUrlData(0).getLinkText(); } + public String getDescription1() + { + URLData url = getUrlData(0); + return url == null ? null : url.getLinkText(); + } public void setDescription2(String description2) { setUrlText(1, description2); } - public String getDescription2() { return getUrlData(1).getLinkText(); } + public String getDescription2() + { + URLData url = getUrlData(1); + return url == null ? null : url.getLinkText(); + } public void setDescription3(String description3) { setUrlText(2, description3); } - public String getDescription3() { return getUrlData(2).getLinkText(); } + public String getDescription3() + { + URLData url = getUrlData(2); + return url == null ? null : url.getLinkText(); + } public void setLink1(String link1) { setUrlLink(0, link1); } - public String getLink1() { return getUrlData(0).getLink(); } + public String getLink1() + { + URLData url = getUrlData(0); + return url == null ? null : url.getLink(); + } public void setLink2(String link2) { setUrlLink(1, link2); } - public String getLink2() { return getUrlData(1).getLink(); } + public String getLink2() + { + URLData url = getUrlData(1); + return url == null ? null : url.getLink(); + } public void setLink3(String link3) { setUrlLink(2, link3); } - public String getLink3() { return getUrlData(2).getLink(); } + public String getLink3() + { + URLData url = getUrlData(2); + return url == null ? null : url.getLink(); + } public String getStatus() { @@ -537,6 +566,25 @@ public Map getPrimaryFields() return _primaryFields; } + public Map getUrlFields() + { + Map urlFields = new CaseInsensitiveHashMap<>(); + + if (getLink1() != null) + urlFields.put(LINK1_FIELD, getLink1()); + if (getDescription1() != null) + urlFields.put(DESCRIPTION1_FIELD, getDescription1()); + if (getLink2() != null) + urlFields.put(LINK2_FIELD, getLink2()); + if (getDescription2() != null) + urlFields.put(DESCRIPTION2_FIELD, getDescription2()); + if (getLink3() != null) + urlFields.put(LINK3_FIELD, getLink3()); + if (getDescription3() != null) + urlFields.put(DESCRIPTION3_FIELD, getDescription3()); + return urlFields; + } + protected void setPrimaryFields(Map fields) { _primaryFields = fields; diff --git a/test/sampledata/DataFinder.lists.zip b/test/sampledata/DataFinder.lists.zip index 0693038d18b5109e2f812bb3818ad4ed3b83bfbe..66e4743d0bc19320f12c31d19858400d5c2acdd6 100644 GIT binary patch delta 7067 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 delta 8280 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 KiHvxE|NB3mM6M_R diff --git a/test/sampledata/Lookups.lists.zip b/test/sampledata/Lookups.lists.zip new file mode 100644 index 0000000000000000000000000000000000000000..3a12538067923425b51d679657a925738507d421 GIT binary patch literal 2702 zcmai03piAH8y|`pLyPdGnbNjoXU1I2cHuLla@jC5ZmU9Sb7?|mT;^h2>Z|bak;D_- z*p-r+LMaqqh?+8-F)NA^!k{pO7E#%Av`>cJ&-b3^JkL4L|GdBV{lEX;`+JWIec5td z7y^NS-8t574^yxe;ImCg@PR;HU`Q~L#|>w@wFRTdT1O_`0WUW-gK4#NdLl|oxp^z4 zLp*Hz*e{ZGNzx+l_O$ltq)u`hlCQ48)jg_a>=h%bgviz!ZUjlVS!aa&Oy-?nwMpI- zF7&?}Z<9BQqC2fYkD0PJ+J*F9)Vtbh;x3Q)t|o=`82Q;FOcb<^tRt3GUvLmJ5^U5E z8&^8%8I!kpN-`W1w|Qr?wB)NfW>JSWzVC4!VWLZNZ3MZmF06jx4q$Fs-@KeejoLwT z*0s}@n)+TdmF47Cl*X?+IK0(0tzmH5BBFZlzLQtjKF94C2NPYbaFlZ$m3F4=m2gg< z($ILK1wE0RQ=7Q|RQ^C(wP8+u%1+bbn2NGnGJ3gLRs8;slQEU4)>RV&`8TuRa8{$M zk&3Zq5&hxH6A!2*Wv=bFZ@Uo$1qYh%+_P=b&*W9c>Xkj}HXjhPTJLUYZdOT>>RQ$` z2pjOR#E3oeO3f)dWK$bPcVKjPe_uZ7&f`;Lc=cgV`LW`ljO!G;BWtmqmWRA--KJJp zR8};gi*q#Ayxt)_H>5TKQ`Ns%BD&}oys1jSD-T8^{h8BG5Yr>V>46E48^F=bC_4eM zB5c@`&I%0(-rBQ2pX@zt{EqG?8jlP?O~GC0aKye~?Cf(T7|dWf4CeBc5XFlKfdmQR z#`EFvxey!Fi4J%yp^wubCDb|)9D2t9Hw326nM5NH`;w)6 z8sNia&Vkn2&=8gL8jDoYDtBCN2pFvDS^pKM=VyGV}{1 z5$h<_5aR87IRzMdfbQVv8p?2T0<#>Fx?F$i*qZIy^>K;6zlzit zc_rlPWqkT($1Z-Dmm~+?o5eafM#{&;wOAPH1LNUD-*I3i`TlW}$QWuUoI>#Q~uj7D>(% z|DEyvQG{Aobp(0A_HLUzAo}MG7NhscA4ZvFNp8R!FLwIo{?oy>n%XM;%))A&UeuAN zB{N#=5}$Vs^ap`Sy6s4ehHvHzbo7PfhGI^WSz!^eoJHEn8?>@ zI}YDPomft|l>ByV<)_*BG0k?D8eKg}ktzOXOzyIRwNK;UwE0=7QP!rxcYTa)?6ZPb z%{BQ;Q3lFL83ud&%~ApdH942b;{^u$&#|lEspQZ@Qw{jL=k(>Y;_FN!6c>XV<1)0hZDSMKS4vj5EOo-08j`1JM$Tx+4(x4-RfNG;I2uXoBl z+Pv>Z{*?z-$rW2L0q167(+h~5VIGwI^+Nm;oXli>-$5UK0@5IB^P|fvVv=Lp!@JK@ zE0Zmy&tLzjH1nUMze;PV{`XWrpe^t8>Hj0_to30w?}5sFXC3l|9Q|n$FI(C2*_3SG z(5i8wSXRUmK5a^>6jeBm!Hb44jD+9)d@^;z8!xt4?#LUExsu*XX5X+XJ5!G;L*nhx zq=xARcfMj7R2wB_UD*6zg5tP<^md^w_-9abh31LkOn~OeylI06F)u-j{S?I?>h*c^ z2mQXzkHv0^GQKd=(z$R7b7HZpqEbT?m^VYPB#NrP*i%t_AvK)0XsDQf)%C>yib4x% z^t^S00Dq_0ixCt>b72Z*;E4Yld0q^l*cuD#T>t`nRocaFimX{!Gz5HM7rD??RH1z# N!D}aY7Dfo@?GHQM!IA&~ literal 0 HcmV?d00001 diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java index b57dfd34..a1ad028d 100644 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java @@ -37,8 +37,6 @@ public class DataFinderPage extends LabKeyPage { - private static final String CONTROLLER = "trialshare"; - private static final String ACTION = "dataFinder"; public static final String STUDY_COUNT_SIGNAL = "dataFinderStudyCountsUpdated"; public static final String PUBLICATION_COUNT_SIGNAL = "dataFinderPublicationCountsUpdated"; public static final String STUDY_FACET_SIGNAL = "dataFinderStudyFacetsReady"; @@ -62,6 +60,16 @@ public DataFinderPage(BaseWebDriverTest test, boolean testingStudies) } } + public boolean canManageData() + { + return isElementPresent(Locators.manageData); + } + + public void goToManageData() + { + Locators.manageData.findElement(this.getDriver()).click(); + } + public String getCountSignal() { return (this.testingStudies ? STUDY_COUNT_SIGNAL : PUBLICATION_COUNT_SIGNAL); @@ -286,6 +294,7 @@ public static class Locators public static final Locator.CssLocator saveMenu = Locator.css("#saveMenu"); public static final Locator.CssLocator loadMenu = Locator.css("#loadMenu"); public static final Locator.IdLocator manageMenu = Locator.id("manageMenu"); + public static final Locator.CssLocator manageData = Locator.css(".labkey-publications-panel .labkey-finder-manage-data"); public static final Locator.CssLocator getSearchInput(Locator.CssLocator locator) { diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java new file mode 100644 index 00000000..1d7b5d17 --- /dev/null +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -0,0 +1,129 @@ +package org.labkey.test.pages.trialshare; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Assert; +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.Locator; +import org.labkey.test.pages.LabKeyPage; +import org.labkey.test.tests.trialshare.DataFinderTestBase; +import org.labkey.test.util.DataRegionTable; + +/** + * Created by susanh on 6/30/16. + */ +public class ManageDataPage extends LabKeyPage +{ + private DataRegionTable _table; + private DataFinderTestBase.CubeObjectType _objectType; + + + public ManageDataPage(BaseWebDriverTest test, DataFinderTestBase.CubeObjectType objectType) + { + super(test.getDriver()); + + _table = new DataRegionTable("query", test); + _objectType = objectType; + } + + public int getCount() + { + return _table.getDataRowCount(); + } + + public boolean isManageDataView() + { + return isTextPresent(_objectType.getManageDataTitle()) && hasExpectedColumns(); + } + + public boolean hasExpectedColumns() + { + for (String header : _table.getColumnHeaders()) + { + if (!StringUtils.isEmpty(header) && !header.trim().isEmpty() && !_objectType.getManageDataHeaders().contains(header)) + return false; + } + return true; + } + + public boolean hasManageStudiesLink() + { + return isElementPresent(Locators.manageStudiesLink); + } + + public void goToManageStudies() + { + doAndWaitForPageToLoad(() -> Locators.manageStudiesLink.findElement(getDriver()).click()); + } + + public boolean hasManagePublicationsLink() + { + return isElementPresent(Locators.managePublicationsLink); + } + + public void goToManagePublications() + { + doAndWaitForPageToLoad(() -> Locators.managePublicationsLink.findElement(getDriver()).click()); + } + + public boolean itemIsInList(String keyValue) + { + return _table.getRow(_objectType.getKeyField(), keyValue) >= 0; + } + + public void goToInsertNew() + { + log("Going to insert new " + _objectType); + _table.clickHeaderButtonByText("Insert New"); + } + + public int getRowIndex(String value) + { + Integer rowIndex = _table.getRow(_objectType.getKeyField(), value); + Assert.assertTrue("Did not find row with " + _objectType.getKeyField() + " '" + value + "'", rowIndex >= 0); + return rowIndex; + } + + public void goToEditRecord(String keyValue) + { + Integer rowIndex = getRowIndex(keyValue); + editLink(rowIndex).findElement(getDriver()).click(); + } + + public void deleteRecord(String keyValue) + { + log("Deleting record with key value '" + keyValue + "'"); + Integer rowIndex = getRowIndex(keyValue); + _table.checkCheckbox(rowIndex); + _table.clickHeaderButtonByText("Delete"); + acceptAlert(); + } + + public void showDetails(String keyValue) + { + Integer rowIndex = getRowIndex(keyValue); + detailsLink(rowIndex).findElement(getDriver()).click(); + } + + public Locator.XPathLocator detailsLink(int row) + { + return Locator.xpath("//table[@id=" + Locator.xq(_table.getHtmlName()) + "]/tbody/tr[" + (row + 5) + "]/td[3]/a"); + } + + public Locator.XPathLocator editLink(int row) + { + return Locator.xpath("//table[@id=" + Locator.xq(_table.getHtmlName()) + "]/tbody/tr[" + (row + 5) + "]/td[2]/a"); + } + + public void refreshCube() + { + log("Refreshing cube"); + _table.clickHeaderButtonByText("Refresh Cube"); + } + + private static class Locators + { + public static Locator manageStudiesLink = Locator.linkWithText("Manage Studies"); + public static Locator managePublicationsLink = Locator.linkWithText("Manage Publications"); + } + +} diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java b/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java new file mode 100644 index 00000000..37ab79a1 --- /dev/null +++ b/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java @@ -0,0 +1,241 @@ +package org.labkey.test.pages.trialshare; + +import org.apache.commons.lang3.StringUtils; +import org.labkey.test.Locator; +import org.labkey.test.pages.LabKeyPage; +import org.labkey.test.util.Ext4Helper; +import org.labkey.test.util.LoggedParam; +import org.openqa.selenium.StaleElementReferenceException; +import org.openqa.selenium.WebDriver; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Created by susanh on 6/30/16. + */ +public class PublicationEditPage extends LabKeyPage +{ + public static final String NOT_EMPTY_VALUE = "NOT EMPTY VALUE"; + public static final String EMPTY_VALUE = "EMPTY VALUE"; + + public static final String TITLE = "title"; + public static final String PUBLICATION_TYPE ="publicationType"; + public static final String STATUS ="status"; + public static final String SUBMISSION_STATUS ="submissionStatus"; + public static final String AUTHOR ="author"; + public static final String CITATION ="citation"; + public static final String YEAR ="year"; + public static final String JOURNAL ="journal"; + public static final String ABSTRACT ="abstractText"; + public static final String DOI ="DOI"; + public static final String PMID ="PMID"; + public static final String PMCID ="PMCID"; + public static final String MANUSCRIPT_CONTAINER ="manuscriptContainer"; + public static final String PERMISSIONS_CONTAINER ="permissionsContainer"; + public static final String KEYWORDS ="keywords"; + public static final String STUDIES ="studyIds"; + public static final String CONDITIONS ="conditions"; + public static final String THERAPEUTIC_AREAS ="therapeuticAreas"; + public static final String LINK1 ="link1"; + public static final String DESCRIPTION1 ="description1"; + public static final String LINK2 ="link2"; + public static final String DESCRIPTION2 ="description2"; + public static final String LINK3 ="link3"; + public static final String DESCRIPTION3="description3"; + + public static final Map DROPDOWN_FIELD_NAMES = new HashMap<>(); + static + { + DROPDOWN_FIELD_NAMES.put(PUBLICATION_TYPE, "Publication Type *:"); + DROPDOWN_FIELD_NAMES.put(STATUS, "Status *:"); + DROPDOWN_FIELD_NAMES.put(SUBMISSION_STATUS, "Submission Status:"); + DROPDOWN_FIELD_NAMES.put(MANUSCRIPT_CONTAINER, "Manuscript Container:"); + DROPDOWN_FIELD_NAMES.put(PERMISSIONS_CONTAINER, "Permissions Container:"); + } + + public static final Map MULTI_SELECT_FIELD_NAMES = new HashMap<>(); + static + { + MULTI_SELECT_FIELD_NAMES.put(STUDIES, "Studies:"); + MULTI_SELECT_FIELD_NAMES.put(CONDITIONS, "Conditions:"); + MULTI_SELECT_FIELD_NAMES.put(THERAPEUTIC_AREAS, "Therapeutic Areas:"); + } + + public static final Set FIELD_NAMES = new HashSet<>(); + static + { + FIELD_NAMES.add(TITLE); + FIELD_NAMES.add(PUBLICATION_TYPE); + FIELD_NAMES.add(STATUS); + FIELD_NAMES.add(SUBMISSION_STATUS); + FIELD_NAMES.add(AUTHOR); + FIELD_NAMES.add(CITATION); + FIELD_NAMES.add(YEAR); + FIELD_NAMES.add(JOURNAL); + FIELD_NAMES.add(ABSTRACT); + FIELD_NAMES.add(DOI); + FIELD_NAMES.add(PMID); + FIELD_NAMES.add(PMCID); + FIELD_NAMES.add(MANUSCRIPT_CONTAINER); + FIELD_NAMES.add(PERMISSIONS_CONTAINER); + FIELD_NAMES.add(KEYWORDS); + FIELD_NAMES.add(STUDIES); + FIELD_NAMES.add(CONDITIONS); + FIELD_NAMES.add(THERAPEUTIC_AREAS); + FIELD_NAMES.add(LINK1); + FIELD_NAMES.add(DESCRIPTION1); + FIELD_NAMES.add(LINK2); + FIELD_NAMES.add(DESCRIPTION2); + FIELD_NAMES.add(LINK3); + FIELD_NAMES.add(DESCRIPTION3); + } + + public PublicationEditPage(WebDriver driver) + { + super(driver); + } + + public void setTextFormValue(String key, String value) + { + Locator fieldLocator = Locator.name(key); + setFormElement(fieldLocator, value); + } + + public void selectMenuItem(String label, String value) + { + _ext4Helper.selectComboBoxItem(label, value); + } + + public void setFormField(String key, Object value) + { + log("Setting field " + key + " to " + value); + if (DROPDOWN_FIELD_NAMES.keySet().contains(key)) + _ext4Helper.selectComboBoxItem(DROPDOWN_FIELD_NAMES.get(key), (String) value); + else if (MULTI_SELECT_FIELD_NAMES.keySet().contains(key)) + multiSelectComboBoxItem(MULTI_SELECT_FIELD_NAMES.get(key), (String[]) value); + else + setTextFormValue(key, (String) value); + } + + // the similar method in ext4Helper is looking for a property that does not exist to + // decide if this is a multi-select box or not. + public void multiSelectComboBoxItem(String label, @LoggedParam String... selections) + { + Locator.XPathLocator comboBox = Ext4Helper.Locators.formItemWithLabel(label); + _ext4Helper.openComboList(comboBox); + + try + { + for (String selection : selections) + { + _ext4Helper.selectItemFromOpenComboList(selection, Ext4Helper.TextMatchTechnique.EXACT); + } + } + catch (StaleElementReferenceException retry) // Combo-box might still be loading previous selection (no good way to detect) + { + for (String selection : selections) + { + _ext4Helper.selectItemFromOpenComboList(selection, Ext4Helper.TextMatchTechnique.EXACT); + } + } + + Locator arrowTrigger = comboBox.append("//div[contains(@class,'arrow')]"); + arrowTrigger.findElement(this.getDriver()).click(); + } + + public void setFormFields(Map fieldMap, Boolean showOnDashboard) + { + log("Setting form fields"); + for (String key : fieldMap.keySet()) + { + setFormField(key, fieldMap.get(key)); + } + if (showOnDashboard) + { + checkShowOnDashboard(); + } + } + + public void checkShowOnDashboard() + { + _ext4Helper.checkCheckbox("Show on Dashboard:"); + } + + public Map getFormValues() + { + Map formValues = new HashMap<>(); + for (String field : FIELD_NAMES) + { + Locator fieldLocator = Locator.name(field); + formValues.put(field, getFormElement(fieldLocator)); + } + return formValues; + } + + public Map compareFormValues(Map expectedValues) + { + Map formValues = getFormValues(); + Map unexpectedValues = new HashMap<>(); + for (String key : expectedValues.keySet()) + { + String expectedValue; + if (expectedValues.get(key) instanceof String) + { + expectedValue = (String) expectedValues.get(key); + } + else // should be an array of Strings + { + expectedValue = StringUtils.join((String[]) expectedValues.get(key), "; "); + } + String formValue = formValues.get(key); + log("Comparing field values for " + key + " expecting " + expectedValue + " actual " + formValue); + if (expectedValue.equals(EMPTY_VALUE)) + { + if (formValue != null && !formValue.trim().isEmpty()) + unexpectedValues.put(key, "expected: " + expectedValue + " actual: " + formValue); + } + else if (expectedValue.equals(NOT_EMPTY_VALUE)) + { + if (formValue == null || formValue.trim().isEmpty()) + unexpectedValues.put(key, "expected: " + expectedValue + " actual: " + formValue); + } + else if (!expectedValue.equals(formValue)) + { + unexpectedValues.put(key, "expected: " + expectedValue + " actual: " + formValue); + } + } + return unexpectedValues; + } + + public boolean isSubmitEnabled() + { + return !isElementPresent(Locators.disabledSubmitButton); + } + + public void cancel() + { + log("Cancelling publication edit"); + Locators.cancelButton.findElement(getDriver()).click(); + } + + public void submit() + { + log("Submitting publication edit form"); + click(Locators.submitButton); + sleep(1000); + clickAndWait(Locators.ackSubmit, WAIT_FOR_PAGE); + } + + private static class Locators + { + static final Locator showOnDashField = Locator.css(".labkey-field-editor input.x4-form-checkbox"); + static final Locator disabledSubmitButton = Locator.css("a.x4-disabled").withText("SUBMIT"); // why do we need to have the all caps text here? + static final Locator submitButton = Locator.linkWithText("Submit"); + static final Locator cancelButton = Locator.linkWithText("Cancel"); + static final Locator ackSubmit = Locator.linkWithText("OK"); + } + +} diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java b/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java new file mode 100644 index 00000000..9457515c --- /dev/null +++ b/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java @@ -0,0 +1,118 @@ +package org.labkey.test.pages.trialshare; + +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.Locator; +import org.labkey.test.pages.LabKeyPage; +import org.labkey.test.util.DataRegionTable; + +/** + * Created by susanh on 2/5/16. + */ +public class PublicationsListHelper extends LabKeyPage +{ + public PublicationsListHelper(BaseWebDriverTest test) + { + super(test); + } + + public void setPermissionsContainers(String publicStudyName, String operationalStudyName) + { + log("Setting up permissions container for publications"); + clickAndWait(Locator.linkWithText("ManuscriptsAndAbstracts")); + DataRegionTable table = new DataRegionTable("query", _test); + + for (int i = 0; i < table.getDataRowCount(); i++) + { + clickAndWait(table.updateLink(i)); + String status = Locators.statusValue.findElement(getDriver()).getAttribute("value"); + if ("Complete".equalsIgnoreCase(status)) + selectOptionByText(Locators.permissionsContainerSelect,publicStudyName ); + else + selectOptionByText(Locators.permissionsContainerSelect,operationalStudyName ); + clickButton("Submit"); + } + } + + public void setPermissionsContainer(String title, String containerName, Boolean navigateToList) + { + log("Setting up permission container for publication with title: '" + title + "'"); + if (navigateToList) + { + clickAndWait(Locator.linkWithText("ManuscriptsAndAbstracts")); + } + DataRegionTable table = new DataRegionTable("query", _test); + + int rowIndex = table.getRow("Title", title); + clickAndWait(table.updateLink(rowIndex)); + selectOptionByText(Locators.permissionsContainerSelect, containerName); + clickButton("Submit"); + } + + public void setManuscriptContainer(String title, String containerName, Boolean navigateToList) + { + log("Setting up manuscript container for publication with title: '" + title + "'"); + if (navigateToList) + { + clickAndWait(Locator.linkWithText("ManuscriptsAndAbstracts")); + } + DataRegionTable table = new DataRegionTable("query", _test); + + int rowIndex = table.getRow("Title", title); + clickAndWait(table.updateLink(rowIndex)); + selectOptionByText(Locators.manuscriptContainerSelect, containerName); + clickButton("Submit"); + } + + public int getPublicationCount(String title, Boolean navigateToList) + { + if (navigateToList) + { + clickAndWait(Locator.linkWithText("ManuscriptsAndAbstracts")); + } + DataRegionTable table = new DataRegionTable("query", _test); + table.setFilter("Title", "Equals", title); + return table.getDataRowCount(); + } + + public int getPublicationConditionCount(String title, Boolean navigateToList) + { + if (navigateToList) + { + clickAndWait(Locator.linkWithText("PublicationCondition")); + } + DataRegionTable table = new DataRegionTable("query", _test); + table.setFilter("PublicationId", "Equals", title); + return table.getDataRowCount(); + } + + public int getPublicationTherapeuticAreaCount(String title, Boolean navigateToList) + { + if (navigateToList) + { + clickAndWait(Locator.linkWithText("PublicationTherapeuticArea")); + } + DataRegionTable table = new DataRegionTable("query", _test); + table.setFilter("PublicationId", "Equals", title); + return table.getDataRowCount(); + } + + + public int getPublicationStudyCount(String title, Boolean navigateToList) + { + if (navigateToList) + { + clickAndWait(Locator.linkWithText("PublicationStudy")); + } + DataRegionTable table = new DataRegionTable("query", _test); + table.setFilter("PublicationId", "Equals", title); + return table.getDataRowCount(); + } + + private static class Locators + { + static final Locator.XPathLocator manuscriptContainerSelect = Locator.tagWithName("select", "quf_ManuscriptContainer"); + static final Locator.XPathLocator statusValue = Locator.input("quf_Status"); + static final Locator.XPathLocator permissionsContainerSelect = Locator.tagWithName("select", "quf_PermissionsContainer"); + } + +} diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java b/test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java deleted file mode 100644 index 04e1c7e1..00000000 --- a/test/src/org/labkey/test/pages/trialshare/PublicationsQueryUpdatePage.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.labkey.test.pages.trialshare; - -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.Locator; -import org.labkey.test.pages.LabKeyPage; -import org.labkey.test.util.DataRegionTable; - -/** - * Created by susanh on 2/5/16. - */ -public class PublicationsQueryUpdatePage extends LabKeyPage -{ - public PublicationsQueryUpdatePage(BaseWebDriverTest test) - { - super(test); - } - - public void setPermissionsContainer(String publicStudyName, String operationalStudyName) - { - log("Setting up permissions container for publications"); - clickAndWait(Locator.linkWithText("ManuscriptsAndAbstracts")); - DataRegionTable table = new DataRegionTable("query", _test); - - for (int i = 0; i < table.getDataRowCount(); i++) - { - clickAndWait(table.updateLink(i)); - String status = Locators.statusValue.findElement(_test.getDriver()).getAttribute("value"); - if ("Complete".equalsIgnoreCase(status)) - selectOptionByText(Locators.permissionsContainerSelect,publicStudyName ); - else - selectOptionByText(Locators.permissionsContainerSelect,operationalStudyName ); - clickButton("Submit"); - } - } - - private static class Locators - { - public static final Locator.XPathLocator manuscriptContainerSelect = Locator.tagWithName("select", "quf_ManuscriptContainer"); - public static final Locator.XPathLocator statusValue = Locator.input("quf_Status"); - public static final Locator.XPathLocator permissionsContainerSelect = Locator.tagWithName("select", "quf_PermissionsContainer"); - } - -} diff --git a/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java b/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java index b8d89c3f..58f7341f 100644 --- a/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java +++ b/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java @@ -5,8 +5,6 @@ import org.labkey.test.pages.LabKeyPage; import org.labkey.test.util.DataRegionTable; -import java.util.Set; - /** * Created by susanh on 2/5/16. */ @@ -33,6 +31,20 @@ public void setStudyContainers() } } + public void setStudyContainer(String studyId, String containerName, Boolean navigateToList) + { + log("Setting up study container link for studyId " + studyId); + if (navigateToList) + { + clickAndWait(Locator.linkWithText("StudyAccess")); + } + DataRegionTable table = new DataRegionTable("query", _test); + int rowIndex = table.getRow("StudyId", studyId); + clickAndWait(table.updateLink(rowIndex)); + selectOptionByText(Locators.studyContainerSelect, containerName); + clickButton("Submit"); + } + private void unlinkStudy(String studyShortName) { clickAndWait(Locator.linkWithText("StudyProperties")); diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java new file mode 100644 index 00000000..8422990b --- /dev/null +++ b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.labkey.test.tests.trialshare; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.experimental.categories.Category; +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.Locator; +import org.labkey.test.ModulePropertyValue; +import org.labkey.test.TestFileUtils; +import org.labkey.test.TestTimeoutException; +import org.labkey.test.WebTestHelper; +import org.labkey.test.categories.Git; +import org.labkey.test.pages.trialshare.DataFinderPage; +import org.labkey.test.util.APIContainerHelper; +import org.labkey.test.util.AbstractContainerHelper; +import org.labkey.test.util.LogMethod; +import org.labkey.test.util.PortalHelper; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +@Category({Git.class}) +public abstract class DataFinderTestBase extends BaseWebDriverTest +{ + static final String MODULE_NAME = "TrialShare"; + static final String WEB_PART_NAME = "TrialShare Data Finder"; + static final String OPERATIONAL_STUDY_NAME = "DataFinderTestOperationalStudy"; + static final String PUBLIC_STUDY_NAME = "DataFinderTestPublicStudy"; + static final String EMAIL_EXTENSION = "@datafinder.test"; + static final String PUBLIC_READER_DISPLAY_NAME = "public_reader"; + static final String PUBLIC_READER = PUBLIC_READER_DISPLAY_NAME + EMAIL_EXTENSION; + static final String CASALE_READER_DISPLAY_NAME = "casale_reader"; + static final String CASALE_READER = CASALE_READER_DISPLAY_NAME + EMAIL_EXTENSION; + static final String WISPR_READER_DISPLAY_NAME = "wispr_reader"; + static final String WISPR_READER = WISPR_READER_DISPLAY_NAME + EMAIL_EXTENSION; + static final String CONTROLLER = "trialshare"; + static final String ACTION = "dataFinder"; + static File dataListArchive = TestFileUtils.getSampleData("DataFinder.lists.zip"); + static File lookupListArchive = TestFileUtils.getSampleData("Lookups.lists.zip"); + + public enum CubeObjectType { + + study("StudyId", "Manage Studies", new String[]{"Study Id", "Short Name", "Title"}), + publication("Title", "Manage Publications", new String[]{"Key", "Title", "Status", "Publication Type"}); + + + private String _keyField; + private String _manageDataTitle; + private List _manageDataHeaders; + + CubeObjectType(String keyField, String manageDataTitle, String[] manageDataHeaders) + { + _keyField = keyField; + _manageDataHeaders = Arrays.asList(manageDataHeaders); + _manageDataTitle = manageDataTitle; + } + + public String getKeyField() + { + return _keyField; + } + + public List getManageDataHeaders() + { + return _manageDataHeaders; + } + + public String getManageDataTitle() + { + return _manageDataTitle; + } + } + + static final Map> studySubsets = new HashMap<>(); + static { + + Set operationalSet = new HashSet<>(); + studySubsets.put("Operational", operationalSet); + operationalSet.add("TILT"); + operationalSet.add("WISP-R"); + operationalSet.add("ACCEPTOR"); + operationalSet.add("FACTOR"); + operationalSet.add("DIAMOND"); + + Set publicSet = new HashSet<>(); + studySubsets.put("Public", publicSet); + publicSet.add("DIAMOND"); + publicSet.add("Shapiro"); + publicSet.add("Casale"); + publicSet.add("Vincenti"); + }; + + protected static Set loadedStudies = new HashSet<>(); + static { + loadedStudies.add("DataFinderTestPublicCasale"); + loadedStudies.add("DataFinderTestOperationalWISP-R"); + } + + @Override + protected void doCleanup(boolean afterTest) throws TestTimeoutException + { + _containerHelper.deleteProject(getProjectName(), afterTest); + } + + @BeforeClass + public static void initTest() + { + DataFinderTestBase init = (DataFinderTestBase)getCurrentTest(); + + init.setUpProject(); + } + + + @Override + protected BrowserType bestBrowser() + { + return BrowserType.CHROME; + } + + @Override + public List getAssociatedModules() + { + return Collections.singletonList("TrialShare"); + } + + + protected void setUpProject() + { + AbstractContainerHelper containerHelper = new APIContainerHelper(this); + + containerHelper.createProject(getProjectName(), "Custom"); + containerHelper.enableModule(MODULE_NAME); + goToProjectHome(); + importLists(); + createStudies(); + createUsers(); + + List propList = new ArrayList<>(); + // set the site-default value for this so it will work as expected from the Admin Console. + propList.add(new ModulePropertyValue("TrialShare", "/", "DataFinderCubeContainer", getProjectName())); + setModuleProperties(propList); + + reindexForSearch(); + + goToProjectHome(); + new PortalHelper(this).addWebPart(WEB_PART_NAME); + } + + protected abstract void importLists(); + + protected abstract void createStudies(); + + protected abstract void createUsers(); + + @LogMethod + protected void reindexForSearch() + { + log("Reindexing data for full-text search"); + goToAdminConsole(); + clickAndWait(Locator.linkWithText("Data Cube")); + clickButton("Reindex", 0); + clickButton("OK"); + } + + void createStudy(String studyName, Boolean operational) + { + log("creating study " + studyName); + AbstractContainerHelper containerHelper = new APIContainerHelper(this); + File studyArchive = operational ? TestFileUtils.getSampleData(OPERATIONAL_STUDY_NAME + ".folder.zip") : TestFileUtils.getSampleData(PUBLIC_STUDY_NAME + ".folder.zip"); + containerHelper.createSubfolder(getProjectName(), studyName, "Study"); + importStudyFromZip(studyArchive, true, true); + } + + void createStudy(String name) + { + AbstractContainerHelper containerHelper = new APIContainerHelper(this); + + File studyArchive = TestFileUtils.getSampleData(name + ".folder.zip"); + containerHelper.createSubfolder(getProjectName(), name, "Study"); + importStudyFromZip(studyArchive, true, true); + } + + @Before + public void preTest() + { + goToProjectHome(); + DataFinderPage finder = new DataFinderPage(this, true); + finder.clearSearch(); + try + { + finder.clearAllFilters(); + } + catch (NoSuchElementException ignore) {} + finder.dismissTour(); + } + + DataFinderPage goDirectlyToDataFinderPage(String containerPath, boolean testingStudies) + { + log("Going directly to data finder page"); + DataFinderPage finder = new DataFinderPage(this, testingStudies); + doAndWaitForPageSignal(() -> beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), finder.getCountSignal(), longWait()); + sleep(1000); // HACK! + if (!testingStudies) + { + finder.navigateToPublications(); + } + return finder; + } + + void goDirectlyToManageDataPage(String containerPath, CubeObjectType objectType) + { + log("Going directly to manage data page for " + objectType.toString()); + Map params = new HashMap<>(); + params.put("objectName", objectType.toString()); + params.put("query.viewName", "manageData"); + beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, "manageData", params)); + } +} \ No newline at end of file diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationTest.java new file mode 100644 index 00000000..24bb239b --- /dev/null +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationTest.java @@ -0,0 +1,494 @@ +package org.labkey.test.tests.trialshare; + +import org.jetbrains.annotations.Nullable; +import org.junit.Assert; +import org.junit.Test; +import org.labkey.test.pages.PermissionsEditor; +import org.labkey.test.pages.trialshare.DataFinderPage; +import org.labkey.test.pages.trialshare.ManageDataPage; +import org.labkey.test.pages.trialshare.PublicationEditPage; +import org.labkey.test.pages.trialshare.PublicationsListHelper; +import org.labkey.test.pages.trialshare.StudyPropertiesQueryUpdatePage; +import org.labkey.test.util.ListHelper; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.labkey.test.pages.trialshare.PublicationEditPage.EMPTY_VALUE; +import static org.labkey.test.pages.trialshare.PublicationEditPage.NOT_EMPTY_VALUE; +import static org.labkey.test.pages.trialshare.PublicationEditPage.TITLE; + +/** + * Created by susanh on 6/29/16. + */ +public class ManagePublicationTest extends DataFinderTestBase +{ + CubeObjectType _objectType = CubeObjectType.publication; + + private static final String PUBLIC_STUDY_ID = "Casale"; + private static final String OPERATIONAL_STUDY_ID = "WISP-R"; + private static final String PROJECT_NAME = "ManagePublicationTest Project"; + private static final String OPERATIONAL_STUDY_SUBFOLDER_NAME = "/" + PROJECT_NAME + "/" + OPERATIONAL_STUDY_NAME; + private static final String PUBLIC_STUDY_SUBFOLDER_NAME = "/" + PROJECT_NAME + "/" + PUBLIC_STUDY_NAME; + + private static final Map EXISTING_PUB_FIELDS = new HashMap<>(); + static + { + EXISTING_PUB_FIELDS.put(TITLE,"Efficacy of Remission-Induction Regimens for ANCA-Associated Vasculitis" ); + EXISTING_PUB_FIELDS.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.STATUS, "Complete"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.SUBMISSION_STATUS, "Submitted"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.AUTHOR, NOT_EMPTY_VALUE); + EXISTING_PUB_FIELDS.put(PublicationEditPage.CITATION, "New Eng J Med 2013; 369:417-427"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.YEAR, "2013"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.JOURNAL, "New England Journal of Medicine"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.ABSTRACT, NOT_EMPTY_VALUE); + EXISTING_PUB_FIELDS.put(PublicationEditPage.DOI, EMPTY_VALUE); + EXISTING_PUB_FIELDS.put(PublicationEditPage.PMID, "23902481"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.PMCID, EMPTY_VALUE); + EXISTING_PUB_FIELDS.put(PublicationEditPage.MANUSCRIPT_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); + EXISTING_PUB_FIELDS.put(PublicationEditPage.PERMISSIONS_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); + EXISTING_PUB_FIELDS.put(PublicationEditPage.KEYWORDS, EMPTY_VALUE); + EXISTING_PUB_FIELDS.put(PublicationEditPage.STUDIES, EMPTY_VALUE); + EXISTING_PUB_FIELDS.put(PublicationEditPage.CONDITIONS, "Microscopic Polyangiitis"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.THERAPEUTIC_AREAS, "Autoimmune"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.LINK1, "https://www.immunetolerance.org/sites/files/Specks_NEJM_2013.pdf"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.DESCRIPTION1, "Paper on immunetolerance.org"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.LINK2, "http://www.ncbi.nlm.nih.gov/pubmed/23902481"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.DESCRIPTION2, "PubMed.gov Citation"); + EXISTING_PUB_FIELDS.put(PublicationEditPage.LINK3, EMPTY_VALUE); + EXISTING_PUB_FIELDS.put(PublicationEditPage.DESCRIPTION3, EMPTY_VALUE); + } + + @Nullable + @Override + protected String getProjectName() + { + return "ManagePublicationTest Project"; + } + + + @Override + protected void importLists() + { + ListHelper listHelper = new ListHelper(this); + listHelper.importListArchive(dataListArchive); + goToProjectHome(); + listHelper.importListArchive(lookupListArchive); + } + + @Override + protected void createStudies() + { + createStudy(PUBLIC_STUDY_NAME); + createStudy(OPERATIONAL_STUDY_NAME); + goToProjectHome(); +// StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); +// queryUpdatePage.setStudyContainers(); + } + + @Override + protected void createUsers() + { + _userHelper.createUser(PUBLIC_READER); + + PermissionsEditor permissionsEditor = new PermissionsEditor(this); + + goToProjectHome(); + clickAdminMenuItem("Folder", "Permissions"); + permissionsEditor.setSiteGroupPermissions("All Site Users", "Reader"); + + permissionsEditor.selectFolder(PUBLIC_STUDY_NAME); + } + + @Test + public void testManageDataLinkPermissions() + { + log("Checking for manage data link"); + DataFinderPage dataFinder = goDirectlyToDataFinderPage(getCurrentContainerPath(), false); + Assert.assertTrue("Manage Data link is not available", dataFinder.canManageData()); + dataFinder.goToManageData(); + switchToWindow(1); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + Assert.assertTrue("No data shown for publication", manageData.getCount() > 0); + + log("Impersonating user without insert permission"); + goToProjectHome(); + impersonate(PUBLIC_READER); + goDirectlyToDataFinderPage(getCurrentContainerPath(), false); + Assert.assertFalse("Manage Data link should not be available", dataFinder.canManageData()); + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + assertTextPresent("User does not have permission"); + } + + @Test + public void testSwitchToStudies() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + Assert.assertTrue("Should see a link to manage studies", manageData.hasManageStudiesLink()); + manageData.goToManageStudies(); + ManageDataPage manageStudiesData = new ManageDataPage(this, CubeObjectType.study); + Assert.assertTrue("Should be manage studies view", manageStudiesData.isManageDataView()); + } + + @Test + public void testGoToInsertNewAndCancel() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + manageData.goToInsertNew(); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + Assert.assertFalse("Submit button should not be enabled", editPage.isSubmitEnabled()); + doAndWaitForPageToLoad(() -> editPage.cancel()); + Assert.assertTrue("Should be manage publications view", manageData.isManageDataView()); + } + + @Test + public void testViewDetails() + { + goToProjectHome(); + PublicationsListHelper pubUpdatePage = new PublicationsListHelper(this); + pubUpdatePage.setPermissionsContainer((String) EXISTING_PUB_FIELDS.get(TITLE), "/" + getProjectName() + "/" + OPERATIONAL_STUDY_NAME, true); + pubUpdatePage.setManuscriptContainer((String) EXISTING_PUB_FIELDS.get(TITLE), "/" + getProjectName() + "/" + PUBLIC_STUDY_NAME, false); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + // FIXME on the details page, many fields are not form fields, so finding them is more difficult + manageData.goToEditRecord((String) EXISTING_PUB_FIELDS.get(TITLE)); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + Map unexpectedValues = editPage.compareFormValues(EXISTING_PUB_FIELDS); + Assert.assertTrue("Found unexpected values: " + unexpectedValues, unexpectedValues.isEmpty()); + } + + @Test + public void testInvalidPMCID() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + manageData.goToEditRecord("Quality assessments of un-gated flow cytometry FCS files in a clinical trial setting"); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + editPage.setTextFormValue("PMCID", "invalid"); + Assert.assertFalse("Submit should be disabled when invalid PMCID is input", editPage.isSubmitEnabled()); + } + + @Test + public void testRequiredFields() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + manageData.goToInsertNew(); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + Assert.assertFalse("Submit button should not be enabled", editPage.isSubmitEnabled()); + editPage.setTextFormValue("title", "testRequiredFields"); + Assert.assertFalse("Submit button should not be enabled with only title", editPage.isSubmitEnabled()); + doAndWaitForPageToLoad(editPage::cancel); + + manageData.goToInsertNew(); + editPage.selectMenuItem("Publication Type *:", "Manuscript"); + Assert.assertFalse("Submit button should not be enabled with only publication type", editPage.isSubmitEnabled()); + doAndWaitForPageToLoad(editPage::cancel); + + manageData.goToInsertNew(); + editPage.selectMenuItem("Status *:", "Complete"); + Assert.assertFalse("Submit button should not be enabled with only status", editPage.isSubmitEnabled()); + doAndWaitForPageToLoad(editPage::cancel); + + manageData.goToInsertNew(); + editPage.setTextFormValue("title", "testRequiredFields"); + editPage.selectMenuItem("Publication Type *:", "Manuscript"); + Assert.assertFalse("Submit button should not be enabled with title, publication type but no status", editPage.isSubmitEnabled()); + editPage.selectMenuItem("Status *:", "Complete"); + Assert.assertTrue("Submit button should be enabled with all required fields", editPage.isSubmitEnabled()); + } + + @Test + public void testInsertWithAllFields() + { + goToProjectHome(); + StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); + queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + Map newFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + newFields.put(PublicationEditPage.TITLE, "testInsertWithAllFields_" + manageData.getCount()); + newFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); + newFields.put(PublicationEditPage.STATUS, "In Progress"); + newFields.put(PublicationEditPage.SUBMISSION_STATUS, "Submitted"); + newFields.put(PublicationEditPage.AUTHOR, "test1, test2"); + newFields.put(PublicationEditPage.CITATION, "test publications: v24, no 15"); + newFields.put(PublicationEditPage.YEAR, "2016"); + newFields.put(PublicationEditPage.JOURNAL, "Test Publications"); + // TODO this field is not editable like a text field. +// newFields.put(PublicationEditPage.ABSTRACT, "We are concrete."); + newFields.put(PublicationEditPage.DOI, "doi:123/445"); + newFields.put(PublicationEditPage.PMID, "1212"); + newFields.put(PublicationEditPage.PMCID, "PMC1411"); + newFields.put(PublicationEditPage.MANUSCRIPT_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); + newFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); + newFields.put(PublicationEditPage.KEYWORDS, "key words keywords"); + newFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); + newFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy"}); + newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); + newFields.put(PublicationEditPage.LINK1, "http://link/to.this"); + newFields.put(PublicationEditPage.DESCRIPTION1, "Link 1 Description"); + newFields.put(PublicationEditPage.LINK2, "http://also/link/to.this"); + newFields.put(PublicationEditPage.DESCRIPTION2, "Link 2 Description"); + newFields.put(PublicationEditPage.LINK3, "http://finally/link/to.this"); + newFields.put(PublicationEditPage.DESCRIPTION3, "Link 3 Description"); + + manageData.goToInsertNew(); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + + editPage.setFormFields(newFields, false); + editPage.submit(); + + manageData.goToEditRecord((String) newFields.get(TITLE)); + Map unexpectedValues = editPage.compareFormValues(newFields); + Assert.assertTrue("Found unexpected values in edit page of newly inserted publication: " + unexpectedValues, unexpectedValues.isEmpty()); + } + + @Test + public void testInsertMultiValuedFields() + { + goToProjectHome(); + StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); + queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + Map newFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + newFields.put(PublicationEditPage.TITLE, "testInsertMultiValuedFields_" + manageData.getCount()); + newFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); + newFields.put(PublicationEditPage.STATUS, "In Progress"); + + newFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID, OPERATIONAL_STUDY_ID}); + newFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy", "Asthma", "Autoimmune Disorders"}); + newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune", "Allergy"}); + + manageData.goToInsertNew(); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + + editPage.setFormFields(newFields, false); + editPage.submit(); + + manageData.goToEditRecord((String) newFields.get(TITLE)); + Map unexpectedValues = editPage.compareFormValues(newFields); + Assert.assertTrue("Found unexpected values in edit page of newly inserted publication: " + unexpectedValues, unexpectedValues.isEmpty()); + } + + @Test + public void testEditMultiValuedFields() + { + goToProjectHome(); + StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); + queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + Map initialFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + initialFields.put(PublicationEditPage.TITLE, "testEditMultiValuedFields_" + manageData.getCount()); + initialFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); + initialFields.put(PublicationEditPage.STATUS, "In Progress"); + + initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); + initialFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy", "Asthma"}); + initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune", "Allergy"}); + + manageData.goToInsertNew(); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + + editPage.setFormFields(initialFields, false); + editPage.submit(); + + manageData.goToEditRecord((String) initialFields.get(TITLE)); + + Map newFields = new HashMap<>(); + newFields.put(PublicationEditPage.STUDIES, new String[]{OPERATIONAL_STUDY_ID}); + // this removes "Allergy" and adds "Cat Allergy" + newFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy", "Cat Allergy"}); + newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); + editPage.setFormFields(newFields, false); + editPage.submit(); + + manageData.goToEditRecord((String) initialFields.get(TITLE)); + initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID, OPERATIONAL_STUDY_ID}); + initialFields.put(PublicationEditPage.CONDITIONS, new String[]{"Asthma", "Cat Allergy"}); + initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune", "Allergy", "T1DM"}); + + Map unexpectedValues = editPage.compareFormValues(initialFields); + Assert.assertTrue("Found unexpected values in edit page of updated publication: " + unexpectedValues, unexpectedValues.isEmpty()); + } + + @Test + public void testUpdatePublication() + { + goToProjectHome(); + StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); + queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + Map initialFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + initialFields.put(PublicationEditPage.TITLE, "testUpdatePublication_" + manageData.getCount()); + initialFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); + initialFields.put(PublicationEditPage.STATUS, "In Progress"); + initialFields.put(PublicationEditPage.SUBMISSION_STATUS, "Submitted"); + initialFields.put(PublicationEditPage.AUTHOR, "test1, test2"); + initialFields.put(PublicationEditPage.CITATION, "test publications: v24, no 15"); + initialFields.put(PublicationEditPage.YEAR, "2016"); + initialFields.put(PublicationEditPage.JOURNAL, "Test Publications"); +// newFields.put(PublicationEditPage.ABSTRACT, "We are concrete."); + initialFields.put(PublicationEditPage.DOI, "doi:123/445"); + initialFields.put(PublicationEditPage.PMID, "1212"); + initialFields.put(PublicationEditPage.PMCID, "PMC1411"); + initialFields.put(PublicationEditPage.MANUSCRIPT_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); + initialFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); + initialFields.put(PublicationEditPage.KEYWORDS, "key words keywords"); + initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); + initialFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy"}); + initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); + initialFields.put(PublicationEditPage.LINK1, "http://link/to.this"); + initialFields.put(PublicationEditPage.DESCRIPTION1, "Link 1 Description"); + initialFields.put(PublicationEditPage.LINK2, "http://also/link/to.this"); + initialFields.put(PublicationEditPage.DESCRIPTION2, "Link 2 Description"); + initialFields.put(PublicationEditPage.LINK3, "http://finally/link/to.this"); + initialFields.put(PublicationEditPage.DESCRIPTION3, "Link 3 Description"); + + manageData.goToInsertNew(); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + + editPage.setFormFields(initialFields, false); + editPage.submit(); + + Map updatedFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + updatedFields.put(PublicationEditPage.TITLE, "testUpdatePublication_" + manageData.getCount() + "_updated"); + updatedFields.put(PublicationEditPage.PUBLICATION_TYPE, "Abstract"); + updatedFields.put(PublicationEditPage.STATUS, "Complete"); + updatedFields.put(PublicationEditPage.SUBMISSION_STATUS, "Submitted"); + updatedFields.put(PublicationEditPage.AUTHOR, "test1, test2 updated"); + updatedFields.put(PublicationEditPage.CITATION, "test publications: v24, no 15 updated"); + updatedFields.put(PublicationEditPage.YEAR, "2015"); + updatedFields.put(PublicationEditPage.JOURNAL, "Test Publications updated"); + updatedFields.put(PublicationEditPage.DOI, "doi:123/445-u"); + updatedFields.put(PublicationEditPage.PMID, "1213"); + updatedFields.put(PublicationEditPage.PMCID, "PMC1412"); + updatedFields.put(PublicationEditPage.MANUSCRIPT_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); + updatedFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); + updatedFields.put(PublicationEditPage.KEYWORDS, "key words keywords updated"); + + // multi-value fields are tested separately + updatedFields.put(PublicationEditPage.LINK1, "http://link/to.this updated"); + updatedFields.put(PublicationEditPage.DESCRIPTION1, "Link 1 Description updated"); + updatedFields.put(PublicationEditPage.LINK2, "http://also/link/to.this updated"); + updatedFields.put(PublicationEditPage.DESCRIPTION2, "Link 2 Description updated"); + updatedFields.put(PublicationEditPage.LINK3, "http://finally/link/to.this updated"); + updatedFields.put(PublicationEditPage.DESCRIPTION3, "Link 3 Description updated"); + + manageData.goToEditRecord((String) initialFields.get(TITLE)); + editPage.setFormFields(updatedFields, false); + editPage.submit(); + + manageData.goToEditRecord((String) updatedFields.get(TITLE)); + Map unexpectedValues = editPage.compareFormValues(updatedFields); + Assert.assertTrue("Found unexpected values in edit page of updated publication: " + unexpectedValues, unexpectedValues.isEmpty()); + } + + @Test + public void testInsertAndDelete() + { + goToProjectHome(); + StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); + queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + Map initialFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + initialFields.put(PublicationEditPage.TITLE, "testInsertAndDelete_" + manageData.getCount()); + initialFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); + initialFields.put(PublicationEditPage.STATUS, "In Progress"); + initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); + initialFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy"}); + initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); + manageData.goToInsertNew(); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + + editPage.setFormFields(initialFields, false); + editPage.submit(); + + manageData.deleteRecord((String) initialFields.get(PublicationEditPage.TITLE)); + + PublicationsListHelper listHelper = new PublicationsListHelper(this); + + goToProjectHome(); + Assert.assertEquals("Found deleted publication", 0, listHelper.getPublicationCount((String) initialFields.get(PublicationEditPage.TITLE), true)); + goToProjectHome(); + Assert.assertEquals("Found studies for deleted publication", 0, listHelper.getPublicationStudyCount((String) initialFields.get(PublicationEditPage.TITLE), true)); + goToProjectHome(); + Assert.assertEquals("Found conditions for deleted publication", 0, listHelper.getPublicationConditionCount((String) initialFields.get(PublicationEditPage.TITLE), true)); + goToProjectHome(); + Assert.assertEquals("Found therapeutic areas for deleted publication", 0, listHelper.getPublicationTherapeuticAreaCount((String) initialFields.get(PublicationEditPage.TITLE), true)); + } + + @Test + public void testInsertAndRefresh() + { + goToProjectHome(); + StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); + queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + Map initialFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + initialFields.put(PublicationEditPage.TITLE, "testInsertAndDelete_" + manageData.getCount()); + initialFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); + initialFields.put(PublicationEditPage.STATUS, "Complete"); + initialFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); + manageData.goToInsertNew(); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + + editPage.setFormFields(initialFields, true); + editPage.submit(); + + goToProjectHome(); // there should be no error alert after inserting but before refreshing + DataFinderPage finder = new DataFinderPage(this, false); + finder.navigateToPublications(); + finder.search((String) initialFields.get(PublicationEditPage.TITLE)); + List dataCards = finder.getDataCards(); + + assertEquals("Should not find newly inserted publication without reindex", 0, dataCards.size()); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + manageData.refreshCube(); + + goToProjectHome(); + finder.navigateToPublications(); + finder.search((String) initialFields.get(PublicationEditPage.TITLE)); + dataCards = finder.getDataCards(); + + assertEquals("Should find newly inserted publication after reindex", 1, dataCards.size()); + + } +} \ No newline at end of file diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 5c85d739..d36ec605 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -19,15 +19,12 @@ import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.junit.Assert; -import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; -import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; import org.labkey.test.ModulePropertyValue; -import org.labkey.test.TestFileUtils; import org.labkey.test.TestTimeoutException; import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Git; @@ -37,7 +34,7 @@ import org.labkey.test.pages.PermissionsEditor; import org.labkey.test.pages.study.ManageParticipantGroupsPage; import org.labkey.test.pages.trialshare.DataFinderPage; -import org.labkey.test.pages.trialshare.PublicationsQueryUpdatePage; +import org.labkey.test.pages.trialshare.PublicationsListHelper; import org.labkey.test.pages.trialshare.StudyPropertiesQueryUpdatePage; import org.labkey.test.util.APIContainerHelper; import org.labkey.test.util.AbstractContainerHelper; @@ -48,7 +45,6 @@ import org.labkey.test.util.ReadOnlyTest; import org.openqa.selenium.support.ui.ExpectedConditions; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -56,7 +52,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Set; import static org.junit.Assert.assertEquals; @@ -64,54 +59,14 @@ import static org.junit.Assert.assertTrue; @Category({Git.class}) -public class TrialShareDataFinderTest extends BaseWebDriverTest implements ReadOnlyTest +public class TrialShareDataFinderTest extends DataFinderTestBase implements ReadOnlyTest { - private static final String MODULE_NAME = "TrialShare"; - private static final String WEB_PART_NAME = "TrialShare Data Finder"; - private static final String OPERATIONAL_STUDY_NAME = "DataFinderTestOperationalStudy"; - private static final String PUBLIC_STUDY_NAME = "DataFinderTestPublicStudy"; - private static final String EMAIL_EXTENSION = "@datafinder.test"; - private static final String PUBLIC_READER_DISPLAY_NAME = "public_reader"; - private static final String PUBLIC_READER = PUBLIC_READER_DISPLAY_NAME + EMAIL_EXTENSION; - private static final String CASALE_READER_DISPLAY_NAME = "casale_reader"; - private static final String CASALE_READER = CASALE_READER_DISPLAY_NAME + EMAIL_EXTENSION; - private static final String WISPR_READER_DISPLAY_NAME = "wispr_reader"; - private static final String WISPR_READER = WISPR_READER_DISPLAY_NAME + EMAIL_EXTENSION; - private static final String CONTROLLER = "trialshare"; - private static final String ACTION = "dataFinder"; - private static File listArchive = TestFileUtils.getSampleData("DataFinder.lists.zip"); - private static final String RELOCATED_DATA_FINDER_PROJECT = "RelocatedDataFinder"; - private static final Map> studySubsets = new HashMap<>(); - static { - - Set operationalSet = new HashSet<>(); - studySubsets.put("Operational", operationalSet); - operationalSet.add("TILT"); - operationalSet.add("WISP-R"); - operationalSet.add("ACCEPTOR"); - operationalSet.add("FACTOR"); - operationalSet.add("DIAMOND"); - - Set publicSet = new HashSet<>(); - studySubsets.put("Public", publicSet); - publicSet.add("DIAMOND"); - publicSet.add("Shapiro"); - publicSet.add("Casale"); - publicSet.add("Vincenti"); - }; - - private static Set loadedStudies = new HashSet<>(); - static { - loadedStudies.add("DataFinderTestPublicCasale"); - loadedStudies.add("DataFinderTestOperationalWISP-R"); - } - @Override protected void doCleanup(boolean afterTest) throws TestTimeoutException { - _containerHelper.deleteProject(getProjectName(), afterTest); + super.doCleanup(afterTest); _containerHelper.deleteProject(RELOCATED_DATA_FINDER_PROJECT, afterTest); } @@ -124,24 +79,12 @@ public static void initTest() init.setUpProject(); } - @Override - protected BrowserType bestBrowser() - { - return BrowserType.CHROME; - } - @Override protected String getProjectName() { return "TrialShareDataFinderTest Project"; } - @Override - public List getAssociatedModules() - { - return Collections.singletonList("TrialShare"); - } - @Override public boolean needsSetup() { @@ -156,16 +99,14 @@ public boolean needsSetup() } } - private void setUpProject() + protected void importLists() { - AbstractContainerHelper containerHelper = new APIContainerHelper(this); - - containerHelper.createProject(getProjectName(), "Custom"); - containerHelper.enableModule(MODULE_NAME); - goToProjectHome(); ListHelper listHelper = new ListHelper(this); - listHelper.importListArchive(listArchive); + listHelper.importListArchive(dataListArchive); + } + protected void createStudies() + { log("Creating a study container for each study"); for (String subset : studySubsets.keySet()) { @@ -181,53 +122,13 @@ private void setUpProject() StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); queryUpdatePage.setStudyContainers(); goToProjectHome(); - PublicationsQueryUpdatePage pubUpdatePage = new PublicationsQueryUpdatePage(this); - pubUpdatePage.setPermissionsContainer("/" + getProjectName() + "/" + PUBLIC_STUDY_NAME, "/" + getProjectName() + "/" + OPERATIONAL_STUDY_NAME); - - createUsers(); - - List propList = new ArrayList<>(); - // set the site-default value for this so it will work as expected from the Admin Console. - propList.add(new ModulePropertyValue("TrialShare", "/", "DataFinderCubeContainer", getProjectName())); - setModuleProperties(propList); - - reindexForSearch(); - - goToProjectHome(); - new PortalHelper(this).addWebPart(WEB_PART_NAME); + PublicationsListHelper pubUpdatePage = new PublicationsListHelper(this); + pubUpdatePage.setPermissionsContainers("/" + getProjectName() + "/" + PUBLIC_STUDY_NAME, "/" + getProjectName() + "/" + OPERATIONAL_STUDY_NAME); } - @LogMethod - private void reindexForSearch() + protected void createUsers() { - log("Reindexing data for full-text search"); - goToAdminConsole(); - clickAndWait(Locator.linkWithText("Data Cube")); - clickButton("Reindex", 0); - clickButton("OK"); - } - - private void createStudy(String studyName, Boolean operational) - { - log("creating study " + studyName); - AbstractContainerHelper containerHelper = new APIContainerHelper(this); - File studyArchive = operational ? TestFileUtils.getSampleData(OPERATIONAL_STUDY_NAME + ".folder.zip") : TestFileUtils.getSampleData(PUBLIC_STUDY_NAME + ".folder.zip"); - containerHelper.createSubfolder(getProjectName(), studyName, "Study"); - importStudyFromZip(studyArchive, true, true); - } - - private void createStudy(String name) - { - AbstractContainerHelper containerHelper = new APIContainerHelper(this); - - File studyArchive = TestFileUtils.getSampleData(name + ".folder.zip"); - containerHelper.createSubfolder(getProjectName(), name, "Study"); - importStudyFromZip(studyArchive, true, true); - } - - private void createUsers() - { - log("Creating users and setting permisisons"); + log("Creating users and setting permissions"); goToProjectHome(); _userHelper.createUser(PUBLIC_READER); @@ -267,20 +168,6 @@ else if (accession.equalsIgnoreCase("WISP-R")) permissionsEditor.setUserPermissions(WISPR_READER, "Reader"); } - @Before - public void preTest() - { - goToProjectHome(); - DataFinderPage finder = new DataFinderPage(this, true); - finder.clearSearch(); - try - { - finder.clearAllFilters(); - } - catch (NoSuchElementException ignore) {} - finder.dismissTour(); - } - @Test public void testCounts() { @@ -921,17 +808,4 @@ private void assertCountsSynced(DataFinderPage finder, DataFinderPage.Dimension assertEquals("Summary count mismatch", dataCards.size(), summaryCounts.get(dimension).intValue()); } - - - private DataFinderPage goDirectlyToDataFinderPage(String containerPath, boolean testingStudies) - { - DataFinderPage finder = new DataFinderPage(this, testingStudies); - doAndWaitForPageSignal(() -> beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), finder.getCountSignal(), longWait()); - sleep(1000); // HACK! - if (!testingStudies) - { - finder.navigateToPublications(); - } - return finder; - } } \ No newline at end of file From 431ccbcde8d8c95c2768cade98f252a3832b5c7c Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 5 Jul 2016 09:24:26 -0700 Subject: [PATCH 335/587] Spec 26558: add tests for managing studies --- .../labkey/trialshare/TrialShareManager.java | 16 +- src/org/labkey/trialshare/data/StudyBean.java | 4 +- .../labkey/trialshare/view/studyDetail.jsp | 8 +- .../pages/trialshare/CubeObjectEditPage.java | 177 ++++++++ .../test/pages/trialshare/DataFinderPage.java | 10 +- .../pages/trialshare/PublicationEditPage.java | 148 +------ .../pages/trialshare/StudiesListHelper.java | 124 ++++++ .../test/pages/trialshare/StudyEditPage.java | 60 +++ .../StudyPropertiesQueryUpdatePage.java | 66 --- .../tests/trialshare/DataFinderTestBase.java | 12 +- ...nTest.java => ManagePublicationsTest.java} | 60 ++- .../tests/trialshare/ManageStudiesTest.java | 395 ++++++++++++++++++ .../trialshare/TrialShareDataFinderTest.java | 12 +- 13 files changed, 821 insertions(+), 271 deletions(-) create mode 100644 test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java create mode 100644 test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java create mode 100644 test/src/org/labkey/test/pages/trialshare/StudyEditPage.java delete mode 100644 test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java rename test/src/org/labkey/test/tests/trialshare/{ManagePublicationTest.java => ManagePublicationsTest.java} (91%) create mode 100644 test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 97986b46..9f26252a 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -16,6 +16,7 @@ package org.labkey.trialshare; +import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.api.collections.CaseInsensitiveHashMap; @@ -54,6 +55,7 @@ public class TrialShareManager { + private static final Logger _logger = Logger.getLogger(TrialShareManager.class); private static final TrialShareManager _instance = new TrialShareManager(); private TrialShareManager() @@ -239,6 +241,7 @@ public void insertPublication(User user, Container container, PublicationEditBea } catch (Exception e) { + _logger.error(e); errors.reject(ERROR_MSG, "Publication insert failed: " + e.getMessage()); } } @@ -282,6 +285,7 @@ public void updatePublication(User user, Container container, PublicationEditBea } catch (Exception e) { + _logger.error(e); errors.reject(ERROR_MSG, "Publication update failed: " + e.getMessage()); } @@ -329,6 +333,7 @@ public void deletePublications(@NotNull User user, @NotNull Container container, } catch (Exception e) { + _logger.error(e); errors.reject(ERROR_MSG, e.getMessage()); } @@ -358,22 +363,23 @@ public void updateStudy(@NotNull User user, @NotNull Container container, StudyE SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.STUDY_ID_FIELD), studyId); // update the many-to-one data. First get rid of the current values for the study - schema.getPublicationConditionTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyConditionTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, filter), null, null); + schema.getStudyConditionTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyConditionTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); addJoinTableData(schema.getStudyConditionTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.CONDITION_FIELD, study.getConditions(), user, container); - schema.getStudyAgeGroupTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyAgeGroupTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, filter), null, null); + schema.getStudyAgeGroupTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyAgeGroupTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); addJoinTableData(schema.getStudyAgeGroupTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.AGE_GROUP_FIELD, study.getAgeGroups(), user, container); - schema.getStudyPhaseTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyPhaseTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, filter), null, null); + schema.getStudyPhaseTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyPhaseTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); addJoinTableData(schema.getStudyPhaseTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.PHASE_FIELD, study.getPhases(), user, container); - schema.getStudyTherapeuticAreaTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyTherapeuticAreaTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, filter), null, null); + schema.getStudyTherapeuticAreaTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyTherapeuticAreaTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); addJoinTableData(schema.getStudyTherapeuticAreaTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, study.getTherapeuticAreas(), user, container); transaction.commit(); } catch (Exception e) { + _logger.error(e); errors.reject(ERROR_MSG, "Problem inserting study " + e.getMessage()); } @@ -414,6 +420,7 @@ public void insertStudy(@NotNull User user, @NotNull Container container, StudyE } catch (Exception e) { + _logger.error(e); errors.reject(ERROR_MSG, "Problem inserting study " + e.getMessage()); } } @@ -450,6 +457,7 @@ public void deleteStudies(@NotNull User user, @NotNull Container container, Set< } catch (Exception e) { + _logger.error(e); errors.reject(ERROR_MSG, e.getMessage()); } } diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index 083ac06c..7ef88246 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -112,12 +112,12 @@ public void setTitle(String title) _primaryFields.put(TITLE_FIELD, title); } - public String getExternalUrl() + public String getExternalURL() { return (String) _primaryFields.get(EXTERNAL_URL_FIELD); } - public void setExternalUrl(String externalUrl) + public void setExternalURL(String externalUrl) { _primaryFields.put(EXTERNAL_URL_FIELD, externalUrl); } diff --git a/src/org/labkey/trialshare/view/studyDetail.jsp b/src/org/labkey/trialshare/view/studyDetail.jsp index c4a38eb7..627569c6 100644 --- a/src/org/labkey/trialshare/view/studyDetail.jsp +++ b/src/org/labkey/trialshare/view/studyDetail.jsp @@ -86,23 +86,23 @@ } %> - <% if (null != studyUrl || null != study.getExternalUrl()) + <% if (null != studyUrl || null != study.getExternalURL()) { %> diff --git a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java new file mode 100644 index 00000000..553a8fca --- /dev/null +++ b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java @@ -0,0 +1,177 @@ +package org.labkey.test.pages.trialshare; + +import org.apache.commons.lang3.StringUtils; +import org.labkey.test.Locator; +import org.labkey.test.pages.LabKeyPage; +import org.labkey.test.util.Ext4Helper; +import org.labkey.test.util.LoggedParam; +import org.openqa.selenium.StaleElementReferenceException; +import org.openqa.selenium.WebDriver; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Created by susanh on 6/30/16. + */ +public class CubeObjectEditPage extends LabKeyPage +{ + public static final String NOT_EMPTY_VALUE = "NOT EMPTY VALUE"; + public static final String EMPTY_VALUE = "EMPTY VALUE"; + + public static final Map DROPDOWN_FIELD_NAMES = new HashMap<>(); + + public static final Map MULTI_SELECT_FIELD_NAMES = new HashMap<>(); + + + public static final Set FIELD_NAMES = new HashSet<>(); + + public CubeObjectEditPage(WebDriver driver) + { + super(driver); + } + + public void setTextFormValue(String key, String value) + { + Locator fieldLocator = Locator.name(key); + setFormElement(fieldLocator, value); + } + + public void setTextFormValue(String key, String value, Boolean waitForSubmit) + { + Locator fieldLocator = Locator.name(key); + setFormElement(fieldLocator, value); + if (waitForSubmit) + waitForElement(Locators.submitButton); + } + + public void selectMenuItem(String label, String value) + { + _ext4Helper.selectComboBoxItem(label, value); + } + + public void setFormField(String key, Object value) + { + log("Setting field " + key + " to " + (value instanceof String[] ? StringUtils.join((String []) value, "; ") : value)); + if (DROPDOWN_FIELD_NAMES.keySet().contains(key)) + _ext4Helper.selectComboBoxItem(DROPDOWN_FIELD_NAMES.get(key), (String) value); + else if (MULTI_SELECT_FIELD_NAMES.keySet().contains(key)) + multiSelectComboBoxItem(MULTI_SELECT_FIELD_NAMES.get(key), (String[]) value); + else + setTextFormValue(key, (String) value); + } + + // the similar method in ext4Helper is looking for a property that does not exist to + // decide if this is a multi-select box or not. + public void multiSelectComboBoxItem(String label, @LoggedParam String... selections) + { + Locator.XPathLocator comboBox = Ext4Helper.Locators.formItemWithLabel(label); + _ext4Helper.openComboList(comboBox); + + try + { + for (String selection : selections) + { + _ext4Helper.selectItemFromOpenComboList(selection, Ext4Helper.TextMatchTechnique.EXACT); + } + } + catch (StaleElementReferenceException retry) // Combo-box might still be loading previous selection (no good way to detect) + { + for (String selection : selections) + { + _ext4Helper.selectItemFromOpenComboList(selection, Ext4Helper.TextMatchTechnique.EXACT); + } + } + + Locator arrowTrigger = comboBox.append("//div[contains(@class,'arrow')]"); + arrowTrigger.findElement(this.getDriver()).click(); + } + + + + public void setFormFields(Map fieldMap) + { + log("Setting form fields"); + for (String key : fieldMap.keySet()) + { + setFormField(key, fieldMap.get(key)); + } + } + + public Map getFormValues() + { + Map formValues = new HashMap<>(); + for (String field : FIELD_NAMES) + { + Locator fieldLocator = Locator.name(field); + formValues.put(field, getFormElement(fieldLocator)); + } + return formValues; + } + + public Map compareFormValues(Map expectedValues) + { + Map formValues = getFormValues(); + Map unexpectedValues = new HashMap<>(); + for (String key : expectedValues.keySet()) + { + String expectedValue; + if (expectedValues.get(key) instanceof String) + { + expectedValue = (String) expectedValues.get(key); + } + else // should be an array of Strings + { + expectedValue = StringUtils.join((String[]) expectedValues.get(key), "; "); + } + String formValue = formValues.get(key); + log("Comparing field values for " + key + " expecting " + expectedValue + " actual " + formValue); + if (expectedValue.equals(EMPTY_VALUE)) + { + if (formValue != null && !formValue.trim().isEmpty()) + unexpectedValues.put(key, "expected: " + expectedValue + " actual: " + formValue); + } + else if (expectedValue.equals(NOT_EMPTY_VALUE)) + { + if (formValue == null || formValue.trim().isEmpty()) + unexpectedValues.put(key, "expected: " + expectedValue + " actual: " + formValue); + } + else if (!expectedValue.equals(formValue)) + { + unexpectedValues.put(key, "expected: " + expectedValue + " actual: " + formValue); + } + } + return unexpectedValues; + } + + public boolean isSubmitEnabled() + { + return !isElementPresent(Locators.disabledSubmitButton); + } + + public void cancel() + { + log("Cancelling publication edit"); + Locators.cancelButton.findElement(getDriver()).click(); + } + + public void submit() + { + log("Submitting publication edit form"); + click(Locators.submitButton); + waitForElement(Locators.ackSubmit); + clickAndWait(Locators.ackSubmit, WAIT_FOR_PAGE); + } + + private static class Locators + { + static final Locator showOnDashField = Locator.css(".labkey-field-editor input.x4-form-checkbox"); + static final Locator disabledSubmitButton = Locator.css("a.x4-disabled").withText("SUBMIT"); // why do we need to have the all caps text here? + static final Locator submitButton = Locator.linkWithText("Submit"); + static final Locator cancelButton = Locator.linkWithText("Cancel"); + static final Locator ackSubmit = Locator.linkWithText("OK"); + } + +} diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java index a1ad028d..6a15c2d5 100644 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java @@ -62,12 +62,15 @@ public DataFinderPage(BaseWebDriverTest test, boolean testingStudies) public boolean canManageData() { - return isElementPresent(Locators.manageData); + return this.testingStudies ? isElementPresent(Locators.manageStudyData) : isElementPresent(Locators.managePublicationData); } public void goToManageData() { - Locators.manageData.findElement(this.getDriver()).click(); + if (this.testingStudies) + Locators.manageStudyData.findElement(this.getDriver()).click(); + else + Locators.managePublicationData.findElement(this.getDriver()).click(); } public String getCountSignal() @@ -294,7 +297,8 @@ public static class Locators public static final Locator.CssLocator saveMenu = Locator.css("#saveMenu"); public static final Locator.CssLocator loadMenu = Locator.css("#loadMenu"); public static final Locator.IdLocator manageMenu = Locator.id("manageMenu"); - public static final Locator.CssLocator manageData = Locator.css(".labkey-publications-panel .labkey-finder-manage-data"); + public static final Locator.CssLocator managePublicationData = Locator.css(".labkey-publications-panel .labkey-finder-manage-data"); + public static final Locator.CssLocator manageStudyData = Locator.css(".labkey-studies-panel .labkey-finder-manage-data"); public static final Locator.CssLocator getSearchInput(Locator.CssLocator locator) { diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java b/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java index 37ab79a1..2b6a7ec0 100644 --- a/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java @@ -1,26 +1,14 @@ package org.labkey.test.pages.trialshare; -import org.apache.commons.lang3.StringUtils; -import org.labkey.test.Locator; -import org.labkey.test.pages.LabKeyPage; -import org.labkey.test.util.Ext4Helper; -import org.labkey.test.util.LoggedParam; -import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.WebDriver; -import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; /** * Created by susanh on 6/30/16. */ -public class PublicationEditPage extends LabKeyPage +public class PublicationEditPage extends CubeObjectEditPage { - public static final String NOT_EMPTY_VALUE = "NOT EMPTY VALUE"; - public static final String EMPTY_VALUE = "EMPTY VALUE"; - public static final String TITLE = "title"; public static final String PUBLICATION_TYPE ="publicationType"; public static final String STATUS ="status"; @@ -46,7 +34,6 @@ public class PublicationEditPage extends LabKeyPage public static final String LINK3 ="link3"; public static final String DESCRIPTION3="description3"; - public static final Map DROPDOWN_FIELD_NAMES = new HashMap<>(); static { DROPDOWN_FIELD_NAMES.put(PUBLICATION_TYPE, "Publication Type *:"); @@ -56,7 +43,6 @@ public class PublicationEditPage extends LabKeyPage DROPDOWN_FIELD_NAMES.put(PERMISSIONS_CONTAINER, "Permissions Container:"); } - public static final Map MULTI_SELECT_FIELD_NAMES = new HashMap<>(); static { MULTI_SELECT_FIELD_NAMES.put(STUDIES, "Studies:"); @@ -64,7 +50,6 @@ public class PublicationEditPage extends LabKeyPage MULTI_SELECT_FIELD_NAMES.put(THERAPEUTIC_AREAS, "Therapeutic Areas:"); } - public static final Set FIELD_NAMES = new HashSet<>(); static { FIELD_NAMES.add(TITLE); @@ -98,144 +83,17 @@ public PublicationEditPage(WebDriver driver) super(driver); } - public void setTextFormValue(String key, String value) - { - Locator fieldLocator = Locator.name(key); - setFormElement(fieldLocator, value); - } - - public void selectMenuItem(String label, String value) - { - _ext4Helper.selectComboBoxItem(label, value); - } - - public void setFormField(String key, Object value) - { - log("Setting field " + key + " to " + value); - if (DROPDOWN_FIELD_NAMES.keySet().contains(key)) - _ext4Helper.selectComboBoxItem(DROPDOWN_FIELD_NAMES.get(key), (String) value); - else if (MULTI_SELECT_FIELD_NAMES.keySet().contains(key)) - multiSelectComboBoxItem(MULTI_SELECT_FIELD_NAMES.get(key), (String[]) value); - else - setTextFormValue(key, (String) value); - } - - // the similar method in ext4Helper is looking for a property that does not exist to - // decide if this is a multi-select box or not. - public void multiSelectComboBoxItem(String label, @LoggedParam String... selections) - { - Locator.XPathLocator comboBox = Ext4Helper.Locators.formItemWithLabel(label); - _ext4Helper.openComboList(comboBox); - - try - { - for (String selection : selections) - { - _ext4Helper.selectItemFromOpenComboList(selection, Ext4Helper.TextMatchTechnique.EXACT); - } - } - catch (StaleElementReferenceException retry) // Combo-box might still be loading previous selection (no good way to detect) - { - for (String selection : selections) - { - _ext4Helper.selectItemFromOpenComboList(selection, Ext4Helper.TextMatchTechnique.EXACT); - } - } - - Locator arrowTrigger = comboBox.append("//div[contains(@class,'arrow')]"); - arrowTrigger.findElement(this.getDriver()).click(); - } - public void setFormFields(Map fieldMap, Boolean showOnDashboard) { - log("Setting form fields"); - for (String key : fieldMap.keySet()) - { - setFormField(key, fieldMap.get(key)); - } + setFormFields(fieldMap); if (showOnDashboard) { checkShowOnDashboard(); } } - public void checkShowOnDashboard() + private void checkShowOnDashboard() { _ext4Helper.checkCheckbox("Show on Dashboard:"); } - - public Map getFormValues() - { - Map formValues = new HashMap<>(); - for (String field : FIELD_NAMES) - { - Locator fieldLocator = Locator.name(field); - formValues.put(field, getFormElement(fieldLocator)); - } - return formValues; - } - - public Map compareFormValues(Map expectedValues) - { - Map formValues = getFormValues(); - Map unexpectedValues = new HashMap<>(); - for (String key : expectedValues.keySet()) - { - String expectedValue; - if (expectedValues.get(key) instanceof String) - { - expectedValue = (String) expectedValues.get(key); - } - else // should be an array of Strings - { - expectedValue = StringUtils.join((String[]) expectedValues.get(key), "; "); - } - String formValue = formValues.get(key); - log("Comparing field values for " + key + " expecting " + expectedValue + " actual " + formValue); - if (expectedValue.equals(EMPTY_VALUE)) - { - if (formValue != null && !formValue.trim().isEmpty()) - unexpectedValues.put(key, "expected: " + expectedValue + " actual: " + formValue); - } - else if (expectedValue.equals(NOT_EMPTY_VALUE)) - { - if (formValue == null || formValue.trim().isEmpty()) - unexpectedValues.put(key, "expected: " + expectedValue + " actual: " + formValue); - } - else if (!expectedValue.equals(formValue)) - { - unexpectedValues.put(key, "expected: " + expectedValue + " actual: " + formValue); - } - } - return unexpectedValues; - } - - public boolean isSubmitEnabled() - { - return !isElementPresent(Locators.disabledSubmitButton); - } - - public void cancel() - { - log("Cancelling publication edit"); - Locators.cancelButton.findElement(getDriver()).click(); - } - - public void submit() - { - log("Submitting publication edit form"); - click(Locators.submitButton); - sleep(1000); - clickAndWait(Locators.ackSubmit, WAIT_FOR_PAGE); - } - - private static class Locators - { - static final Locator showOnDashField = Locator.css(".labkey-field-editor input.x4-form-checkbox"); - static final Locator disabledSubmitButton = Locator.css("a.x4-disabled").withText("SUBMIT"); // why do we need to have the all caps text here? - static final Locator submitButton = Locator.linkWithText("Submit"); - static final Locator cancelButton = Locator.linkWithText("Cancel"); - static final Locator ackSubmit = Locator.linkWithText("OK"); - } - } diff --git a/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java b/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java new file mode 100644 index 00000000..40fd7e1e --- /dev/null +++ b/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java @@ -0,0 +1,124 @@ +package org.labkey.test.pages.trialshare; + +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.Locator; +import org.labkey.test.pages.LabKeyPage; +import org.labkey.test.util.DataRegionTable; + +/** + * Created by susanh on 2/5/16. + */ +public class StudiesListHelper extends LabKeyPage +{ + public StudiesListHelper(BaseWebDriverTest test) + { + super(test); + } + + public void setStudyContainers() + { + log("Setting up study container links"); + String projectName = getCurrentProject(); + clickAndWait(Locator.linkWithText("StudyAccess")); + DataRegionTable table = new DataRegionTable("query", _test); + for (int i = 0; i < table.getDataRowCount(); i++) + { + String name = table.getDataAsText(i, "StudyId"); + Boolean isPublic = "Public".equalsIgnoreCase(table.getDataAsText(i, "Visibility")); + clickAndWait(table.updateLink(i)); + selectOptionByText(Locators.studyContainerSelect, "/" + projectName + "/DataFinderTest" + (isPublic ? "Public" : "Operational") + name); + clickButton("Submit"); + } + } + + public void addStudyAccessEntry(String shortName, String containerName, String visibility, Boolean navigateToList) + { + log("Adding study access entry for study name " + shortName + " and container " + containerName + " to " + visibility); + if (navigateToList) + { + clickAndWait(Locator.linkWithText("StudyAccess")); + } + DataRegionTable table = new DataRegionTable("query", _test); + table.clickHeaderButtonByText("Insert New"); + selectOptionByText(Locators.studyContainerSelect, containerName); + setFormElement(Locators.studyVisibility, visibility); + selectOptionByText(Locators.studyIdSelect, shortName); + clickButton("Submit"); + } + + public void setStudyContainer(String studyId, String containerName, Boolean navigateToList) + { + log("Setting up study container link for studyId " + studyId); + if (navigateToList) + { + clickAndWait(Locator.linkWithText("StudyAccess")); + } + DataRegionTable table = new DataRegionTable("query", _test); + int rowIndex = table.getRow("StudyId", studyId); + clickAndWait(table.updateLink(rowIndex)); + selectOptionByText(Locators.studyContainerSelect, containerName); + clickButton("Submit"); + } + + private void unlinkStudy(String studyShortName) + { + clickAndWait(Locator.linkWithText("StudyProperties")); + DataRegionTable table = new DataRegionTable("query", _test); + int row = table.getRow("Short Name", studyShortName); + + clickAndWait(table.updateLink(row)); + + selectOptionByText(Locators.studyContainerSelect, ""); + + clickButton("Submit"); + } + + public int getStudyCount(String studyId, Boolean navigateToList) + { + return getStudyListCount("StudyProperties", studyId, navigateToList); + } + + public int getStudyAgeGroupCount(String studyId, Boolean navigateToList) + { + return getStudyListCount("StudyAgeGroup", studyId, navigateToList); + } + + public int getStudyConditionCount(String studyId, Boolean navigateToList) + { + return getStudyListCount("StudyCondition", studyId, navigateToList); + } + + public int getStudyTherapeuticAreaCount(String studyId, Boolean navigateToList) + { + return getStudyListCount("StudyTherapeuticArea", studyId, navigateToList); + } + + public int getStudyPhaseCount(String studyId, Boolean navigateToList) + { + return getStudyListCount("StudyPhase", studyId, navigateToList); + } + + public int getStudyAccessCount(String studyId, Boolean navigateToList) + { + return getStudyListCount("StudyAccess", studyId, navigateToList); + } + + public int getStudyListCount(String listName, String studyId, Boolean navigateToList) + { + if (navigateToList) + { + clickAndWait(Locator.linkWithText(listName)); + } + DataRegionTable table = new DataRegionTable("query", _test); + table.setFilter("StudyId", "Equals", studyId); + return table.getDataRowCount(); + } + + private static class Locators + { + public static final Locator.XPathLocator studyContainerSelect = Locator.tagWithName("select", "quf_StudyContainer"); + public static final Locator.XPathLocator studyIdSelect = Locator.tagWithName("select", "quf_StudyId"); + public static final Locator studyVisibility = Locator.name("quf_Visibility"); + } + +} diff --git a/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java b/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java new file mode 100644 index 00000000..9409659d --- /dev/null +++ b/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java @@ -0,0 +1,60 @@ +package org.labkey.test.pages.trialshare; + +import org.openqa.selenium.WebDriver; + +/** + * Created by susanh on 6/30/16. + */ +public class StudyEditPage extends CubeObjectEditPage +{ + public static final String SHORT_NAME = "shortName"; + public static final String STUDY_ID = "studyId"; + public static final String TITLE = "title"; + public static final String PARTICIPANT_COUNT = "participantCount"; + public static final String STUDY_TYPE ="studyType"; + public static final String ICON_URL = "iconUrl"; + public static final String EXTERNAL_URL = "externalURL"; + public static final String EXTERNAL_URL_DESCRIPTION = "externalUrlDescription"; + public static final String DESCRIPTION = "description"; + public static final String INVESTIGATOR = "investigator"; + public static final String AGE_GROUPS = "ageGroups"; + public static final String PHASES = "phases"; + public static final String CONDITIONS = "conditions"; + public static final String THERAPEUTIC_AREAS = "therapeuticAreas"; + + static + { + DROPDOWN_FIELD_NAMES.put(STUDY_TYPE, "Study Type:"); + } + + static + { + MULTI_SELECT_FIELD_NAMES.put(AGE_GROUPS, "Age Groups:"); + MULTI_SELECT_FIELD_NAMES.put(PHASES, "Phases:"); + MULTI_SELECT_FIELD_NAMES.put(CONDITIONS, "Conditions:"); + MULTI_SELECT_FIELD_NAMES.put(THERAPEUTIC_AREAS, "Therapeutic Areas:"); + } + + static + { + FIELD_NAMES.add(SHORT_NAME); + FIELD_NAMES.add(STUDY_ID); + FIELD_NAMES.add(TITLE); + FIELD_NAMES.add(PARTICIPANT_COUNT); + FIELD_NAMES.add(STUDY_TYPE); + FIELD_NAMES.add(ICON_URL); + FIELD_NAMES.add(EXTERNAL_URL); + FIELD_NAMES.add(EXTERNAL_URL_DESCRIPTION); + FIELD_NAMES.add(DESCRIPTION); + FIELD_NAMES.add(INVESTIGATOR); + FIELD_NAMES.add(AGE_GROUPS); + FIELD_NAMES.add(PHASES); + FIELD_NAMES.add(CONDITIONS); + FIELD_NAMES.add(THERAPEUTIC_AREAS); + } + + public StudyEditPage(WebDriver driver) + { + super(driver); + } +} diff --git a/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java b/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java deleted file mode 100644 index 58f7341f..00000000 --- a/test/src/org/labkey/test/pages/trialshare/StudyPropertiesQueryUpdatePage.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.labkey.test.pages.trialshare; - -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.Locator; -import org.labkey.test.pages.LabKeyPage; -import org.labkey.test.util.DataRegionTable; - -/** - * Created by susanh on 2/5/16. - */ -public class StudyPropertiesQueryUpdatePage extends LabKeyPage -{ - public StudyPropertiesQueryUpdatePage(BaseWebDriverTest test) - { - super(test); - } - - public void setStudyContainers() - { - log("Setting up study container links"); - String projectName = getCurrentProject(); - clickAndWait(Locator.linkWithText("StudyAccess")); - DataRegionTable table = new DataRegionTable("query", _test); - for (int i = 0; i < table.getDataRowCount(); i++) - { - String name = table.getDataAsText(i, "StudyId"); - Boolean isPublic = "Public".equalsIgnoreCase(table.getDataAsText(i, "Visibility")); - clickAndWait(table.updateLink(i)); - selectOptionByText(Locators.studyContainerSelect, "/" + projectName + "/DataFinderTest" + (isPublic ? "Public" : "Operational") + name); - clickButton("Submit"); - } - } - - public void setStudyContainer(String studyId, String containerName, Boolean navigateToList) - { - log("Setting up study container link for studyId " + studyId); - if (navigateToList) - { - clickAndWait(Locator.linkWithText("StudyAccess")); - } - DataRegionTable table = new DataRegionTable("query", _test); - int rowIndex = table.getRow("StudyId", studyId); - clickAndWait(table.updateLink(rowIndex)); - selectOptionByText(Locators.studyContainerSelect, containerName); - clickButton("Submit"); - } - - private void unlinkStudy(String studyShortName) - { - clickAndWait(Locator.linkWithText("StudyProperties")); - DataRegionTable table = new DataRegionTable("query", _test); - int row = table.getRow("Short Name", studyShortName); - - clickAndWait(table.updateLink(row)); - - selectOptionByText(Locators.studyContainerSelect, ""); - - clickButton("Submit"); - } - - private static class Locators - { - public static final Locator.XPathLocator studyContainerSelect = Locator.tagWithName("select", "quf_StudyContainer"); - } - -} diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java index 8422990b..6ed7c62d 100644 --- a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java +++ b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java @@ -18,17 +18,16 @@ import org.junit.Before; import org.junit.BeforeClass; -import org.junit.experimental.categories.Category; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; import org.labkey.test.ModulePropertyValue; import org.labkey.test.TestFileUtils; import org.labkey.test.TestTimeoutException; import org.labkey.test.WebTestHelper; -import org.labkey.test.categories.Git; import org.labkey.test.pages.trialshare.DataFinderPage; import org.labkey.test.util.APIContainerHelper; import org.labkey.test.util.AbstractContainerHelper; +import org.labkey.test.util.ListHelper; import org.labkey.test.util.LogMethod; import org.labkey.test.util.PortalHelper; @@ -43,7 +42,6 @@ import java.util.NoSuchElementException; import java.util.Set; -@Category({Git.class}) public abstract class DataFinderTestBase extends BaseWebDriverTest { static final String MODULE_NAME = "TrialShare"; @@ -170,7 +168,13 @@ protected void setUpProject() new PortalHelper(this).addWebPart(WEB_PART_NAME); } - protected abstract void importLists(); + protected void importLists() + { + ListHelper listHelper = new ListHelper(this); + listHelper.importListArchive(dataListArchive); + goToProjectHome(); + listHelper.importListArchive(lookupListArchive); + } protected abstract void createStudies(); diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java similarity index 91% rename from test/src/org/labkey/test/tests/trialshare/ManagePublicationTest.java rename to test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index 24bb239b..c249b13c 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -2,14 +2,16 @@ import org.jetbrains.annotations.Nullable; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.labkey.test.categories.Git; import org.labkey.test.pages.PermissionsEditor; import org.labkey.test.pages.trialshare.DataFinderPage; import org.labkey.test.pages.trialshare.ManageDataPage; import org.labkey.test.pages.trialshare.PublicationEditPage; import org.labkey.test.pages.trialshare.PublicationsListHelper; -import org.labkey.test.pages.trialshare.StudyPropertiesQueryUpdatePage; -import org.labkey.test.util.ListHelper; +import org.labkey.test.pages.trialshare.StudiesListHelper; import java.util.HashMap; import java.util.List; @@ -23,9 +25,10 @@ /** * Created by susanh on 6/29/16. */ -public class ManagePublicationTest extends DataFinderTestBase +@Category({Git.class}) +public class ManagePublicationsTest extends DataFinderTestBase { - CubeObjectType _objectType = CubeObjectType.publication; + private CubeObjectType _objectType = CubeObjectType.publication; private static final String PUBLIC_STUDY_ID = "Casale"; private static final String OPERATIONAL_STUDY_ID = "WISP-R"; @@ -70,23 +73,12 @@ protected String getProjectName() } - @Override - protected void importLists() - { - ListHelper listHelper = new ListHelper(this); - listHelper.importListArchive(dataListArchive); - goToProjectHome(); - listHelper.importListArchive(lookupListArchive); - } - @Override protected void createStudies() { createStudy(PUBLIC_STUDY_NAME); createStudy(OPERATIONAL_STUDY_NAME); goToProjectHome(); -// StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); -// queryUpdatePage.setStudyContainers(); } @Override @@ -146,7 +138,7 @@ public void testGoToInsertNewAndCancel() Assert.assertTrue("Should be manage publications view", manageData.isManageDataView()); } - @Test + @Test @Ignore("Finding the fields that are display/disabled fields is not yet implemented") public void testViewDetails() { goToProjectHome(); @@ -208,9 +200,9 @@ public void testRequiredFields() public void testInsertWithAllFields() { goToProjectHome(); - StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); - queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + StudiesListHelper studiesListHelper = new StudiesListHelper(this); + studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); @@ -258,9 +250,9 @@ public void testInsertWithAllFields() public void testInsertMultiValuedFields() { goToProjectHome(); - StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); - queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + StudiesListHelper studiesListHelper = new StudiesListHelper(this); + studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); @@ -290,9 +282,9 @@ public void testInsertMultiValuedFields() public void testEditMultiValuedFields() { goToProjectHome(); - StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); - queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + StudiesListHelper studiesListHelper = new StudiesListHelper(this); + studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); @@ -336,9 +328,9 @@ public void testEditMultiValuedFields() public void testUpdatePublication() { goToProjectHome(); - StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); - queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + StudiesListHelper studiesListHelper = new StudiesListHelper(this); + studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); @@ -414,9 +406,9 @@ public void testUpdatePublication() public void testInsertAndDelete() { goToProjectHome(); - StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); - queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + StudiesListHelper studiesListHelper = new StudiesListHelper(this); + studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); @@ -453,9 +445,9 @@ public void testInsertAndDelete() public void testInsertAndRefresh() { goToProjectHome(); - StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); - queryUpdatePage.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - queryUpdatePage.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + StudiesListHelper studiesListHelper = new StudiesListHelper(this); + studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); diff --git a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java new file mode 100644 index 00000000..56b77476 --- /dev/null +++ b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java @@ -0,0 +1,395 @@ +package org.labkey.test.tests.trialshare; + +import org.jetbrains.annotations.Nullable; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.labkey.test.categories.Git; +import org.labkey.test.pages.PermissionsEditor; +import org.labkey.test.pages.trialshare.DataFinderPage; +import org.labkey.test.pages.trialshare.ManageDataPage; +import org.labkey.test.pages.trialshare.StudiesListHelper; +import org.labkey.test.pages.trialshare.StudyEditPage; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +/** + * Created by susanh on 6/29/16. + */ +@Category({Git.class}) +public class ManageStudiesTest extends DataFinderTestBase +{ + CubeObjectType _objectType = CubeObjectType.study; + + private static final String PROJECT_NAME = "ManageStudiesTest Project"; + + @Nullable + @Override + protected String getProjectName() + { + return PROJECT_NAME; + } + + @Override + protected void createStudies() + { + } + + @Override + protected void createUsers() + { + _userHelper.createUser(PUBLIC_READER); + + PermissionsEditor permissionsEditor = new PermissionsEditor(this); + + goToProjectHome(); + clickAdminMenuItem("Folder", "Permissions"); + permissionsEditor.setSiteGroupPermissions("All Site Users", "Reader"); + } + + @Test + public void testManageDataLinkPermissions() + { + log("Checking for manage data link"); + DataFinderPage dataFinder = goDirectlyToDataFinderPage(getCurrentContainerPath(), true); + Assert.assertTrue("Manage Data link is not available", dataFinder.canManageData()); + dataFinder.goToManageData(); + switchToWindow(1); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + Assert.assertTrue("No data shown for studies", manageData.getCount() > 0); + + log("Impersonating user without insert permission"); + goToProjectHome(); + impersonate(PUBLIC_READER); + goDirectlyToDataFinderPage(getCurrentContainerPath(), true); + Assert.assertFalse("Manage Data link should not be available", dataFinder.canManageData()); + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + assertTextPresent("User does not have permission"); + } + + @Test + public void testSwitchToPublications() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + Assert.assertTrue("Should see a link to manage publications", manageData.hasManagePublicationsLink()); + manageData.goToManagePublications(); + ManageDataPage managePublicationsData = new ManageDataPage(this, CubeObjectType.publication); + Assert.assertTrue("Should be manage publications view", managePublicationsData.isManageDataView()); + } + + @Test + public void testGoToInsertNewAndCancel() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + manageData.goToInsertNew(); + StudyEditPage editPage = new StudyEditPage(this.getDriver()); + Assert.assertFalse("Submit button should not be enabled", editPage.isSubmitEnabled()); + doAndWaitForPageToLoad(() -> editPage.cancel()); + Assert.assertTrue("Should be manage studies view", manageData.isManageDataView()); + } + + @Test + public void testRequiredFields() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + manageData.goToInsertNew(); + StudyEditPage editPage = new StudyEditPage(this.getDriver()); + Assert.assertFalse("Submit button should not be enabled", editPage.isSubmitEnabled()); + editPage.setTextFormValue("title", "testRequiredFields"); + Assert.assertFalse("Submit button should not be enabled with only title", editPage.isSubmitEnabled()); + doAndWaitForPageToLoad(editPage::cancel); + + manageData.goToInsertNew(); + editPage.setTextFormValue("shortName", "ShortName"); + Assert.assertFalse("Submit button should not be enabled with only study type", editPage.isSubmitEnabled()); + doAndWaitForPageToLoad(editPage::cancel); + + manageData.goToInsertNew(); + editPage.setTextFormValue("studyId", "StudyId"); + Assert.assertFalse("Submit button should not be enabled with only study id", editPage.isSubmitEnabled()); + doAndWaitForPageToLoad(editPage::cancel); + + manageData.goToInsertNew(); + editPage.setTextFormValue("title", "testRequiredFields"); + editPage.setTextFormValue("shortName", "ShortName"); + Assert.assertFalse("Submit button should not be enabled with title, short name but no studyId", editPage.isSubmitEnabled()); + editPage.setTextFormValue("studyId", "StudyId", true); + Assert.assertTrue("Submit button should be enabled with all required fields", editPage.isSubmitEnabled()); + } + + @Test + public void testInsertWithAllFields() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + int count = manageData.getCount(); + Map newFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + newFields.put(StudyEditPage.SHORT_NAME, "TIWAF" + count); + newFields.put(StudyEditPage.STUDY_ID, "TIWAF_ID" + count); + newFields.put(StudyEditPage.TITLE, "testInsertWithAllFields_" + count); + newFields.put(StudyEditPage.PARTICIPANT_COUNT, String.valueOf(count)); + newFields.put(StudyEditPage.STUDY_TYPE, "Interventional"); + newFields.put(StudyEditPage.ICON_URL, "not your regular url"); + newFields.put(StudyEditPage.EXTERNAL_URL, "external url"); + // N.B. leaving out external URL description and Description fields because + // not sure how to attach to the iframe + newFields.put(StudyEditPage.INVESTIGATOR, "investigate"); + newFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child"}); + newFields.put(StudyEditPage.PHASES, new String[]{"Phase 0"}); + newFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema"}); + newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); + + manageData.goToInsertNew(); + StudyEditPage editPage = new StudyEditPage(this.getDriver()); + + editPage.setFormFields(newFields); + editPage.submit(); + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + + manageData.goToEditRecord((String) newFields.get(StudyEditPage.STUDY_ID)); + Map unexpectedValues = editPage.compareFormValues(newFields); + Assert.assertTrue("Found unexpected values in edit page of newly inserted study: " + unexpectedValues, unexpectedValues.isEmpty()); + } + + @Test + public void testInsertMultiValuedFields() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + int count = manageData.getCount(); + Map newFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + newFields.put(StudyEditPage.SHORT_NAME, "TIMVF" + count); + newFields.put(StudyEditPage.STUDY_ID, "TIMVF_ID" + count); + newFields.put(StudyEditPage.TITLE, "testInsertMultiValuedFields_" + count); + + // N.B. leaving out external URL description and Description fields because + // not sure how to attach to the iframe + newFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child", "Adult"}); + newFields.put(StudyEditPage.PHASES, new String[]{"Phase 0", "Phase 4"}); + newFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema", "Bone Marrow Transplantation", "Hay Fever"}); + newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM", "Allergy"}); + + manageData.goToInsertNew(); + StudyEditPage editPage = new StudyEditPage(this.getDriver()); + + editPage.setFormFields(newFields); + editPage.submit(); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + + manageData.goToEditRecord((String) newFields.get(StudyEditPage.STUDY_ID)); + Map unexpectedValues = editPage.compareFormValues(newFields); + Assert.assertTrue("Found unexpected values in edit page of newly inserted publication: " + unexpectedValues, unexpectedValues.isEmpty()); + } + + @Test + public void testEditMultiValuedFields() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + int count = manageData.getCount(); + Map initialFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + initialFields.put(StudyEditPage.SHORT_NAME, "TEMVF" + count); + initialFields.put(StudyEditPage.STUDY_ID, "TEMVF_ID" + count); + initialFields.put(StudyEditPage.TITLE, "testEditMultiValuedFields_" + count); + + // N.B. leaving out external URL description and Description fields because + // not sure how to attach to the iframe + initialFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child", "Adult"}); + initialFields.put(StudyEditPage.PHASES, new String[]{"Phase 0", "Phase 4"}); + initialFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema", "Bone Marrow Transplantation", "Hay Fever"}); + initialFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM", "Allergy"}); + + manageData.goToInsertNew(); + StudyEditPage editPage = new StudyEditPage(this.getDriver()); + + editPage.setFormFields(initialFields); + editPage.submit(); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + + manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); + + Map newFields = new HashMap<>(); + // this removes "Child" and adds "Senior" + newFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child", "Senior"}); + // this removes both Phase 0 and Phase 4 + newFields.put(StudyEditPage.PHASES, new String[]{"Phase 0", "Phase 4"}); + // this adds "Allergy" and "Cat Allergy" + newFields.put(StudyEditPage.CONDITIONS, new String[]{"Allergy", "Cat Allergy"}); + // this removes "T1DM" + newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); + editPage.setFormFields(newFields); + editPage.submit(); + + + manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); + initialFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Adult", "Senior"}); + initialFields.put(StudyEditPage.PHASES, new String[]{}); + initialFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema", "Bone Marrow Transplantation", "Hay Fever", "Allergy", "Cat Allergy"}); + initialFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"Allergy"}); + + Map unexpectedValues = editPage.compareFormValues(initialFields); + Assert.assertTrue("Found unexpected values in edit page of updated publication: " + unexpectedValues, unexpectedValues.isEmpty()); + } + + @Test + public void testUpdateStudy() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + int count = manageData.getCount(); + Map initialFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + initialFields.put(StudyEditPage.SHORT_NAME, "TUS" + count); + initialFields.put(StudyEditPage.STUDY_ID, "TUS_ID" + count); + initialFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count); + initialFields.put(StudyEditPage.PARTICIPANT_COUNT, String.valueOf(count)); + initialFields.put(StudyEditPage.STUDY_TYPE, "Interventional"); + initialFields.put(StudyEditPage.ICON_URL, "not your regular url"); + initialFields.put(StudyEditPage.EXTERNAL_URL, "external url"); + // N.B. leaving out external URL description and Description fields because + // not sure how to attach to the iframe + initialFields.put(StudyEditPage.INVESTIGATOR, "investigate"); + initialFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child"}); + initialFields.put(StudyEditPage.PHASES, new String[]{"Phase 0"}); + initialFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema"}); + initialFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); + + manageData.goToInsertNew(); + StudyEditPage editPage = new StudyEditPage(this.getDriver()); + + editPage.setFormFields(initialFields); + editPage.submit(); + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + + + Map updatedFields = new HashMap<>(); + updatedFields.put(StudyEditPage.SHORT_NAME, "TUS" + count + "_U"); + updatedFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count + "_updated"); + updatedFields.put(StudyEditPage.PARTICIPANT_COUNT, String.valueOf(count+1)); + updatedFields.put(StudyEditPage.STUDY_TYPE, "Observational"); + updatedFields.put(StudyEditPage.ICON_URL, "not your regular url updated"); + updatedFields.put(StudyEditPage.EXTERNAL_URL, "external url updated"); + // N.B. leaving out external URL description and Description fields because + // not sure how to attach to the iframe + updatedFields.put(StudyEditPage.INVESTIGATOR, "investigate updated"); + updatedFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Adult"}); + updatedFields.put(StudyEditPage.PHASES, new String[]{"Phase 2"}); + updatedFields.put(StudyEditPage.CONDITIONS, new String[]{"Asthma"}); + updatedFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"Allergy"}); + + + manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); + editPage.setFormFields(updatedFields); + editPage.submit(); + + manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); + Map unexpectedValues = editPage.compareFormValues(updatedFields); + Assert.assertTrue("Found unexpected values in edit page of updated study: " + unexpectedValues, unexpectedValues.isEmpty()); + } + + @Test + public void testInsertAndDelete() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + int count = manageData.getCount(); + Map initialFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + initialFields.put(StudyEditPage.SHORT_NAME, "TUS" + count); + initialFields.put(StudyEditPage.STUDY_ID, "TUS_ID" + count); + initialFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count); + initialFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Adult"}); + initialFields.put(StudyEditPage.PHASES, new String[]{"Phase 2"}); + initialFields.put(StudyEditPage.CONDITIONS, new String[]{"Asthma"}); + initialFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"Allergy"}); + + manageData.goToInsertNew(); + StudyEditPage editPage = new StudyEditPage(this.getDriver()); + + editPage.setFormFields(initialFields); + editPage.submit(); + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + + manageData.deleteRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); + + StudiesListHelper listHelper = new StudiesListHelper(this); + + goToProjectHome(); + Assert.assertEquals("Found deleted study", 0, listHelper.getStudyCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); + goToProjectHome(); + Assert.assertEquals("Found age group(s) for deleted study", 0, listHelper.getStudyAgeGroupCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); + goToProjectHome(); + Assert.assertEquals("Found condition(s) for deleted study", 0, listHelper.getStudyConditionCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); + goToProjectHome(); + Assert.assertEquals("Found therapeutic area(s) for deleted study", 0, listHelper.getStudyTherapeuticAreaCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); + goToProjectHome(); + Assert.assertEquals("Found phase(s) for deleted study", 0, listHelper.getStudyPhaseCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); + goToProjectHome(); + Assert.assertEquals("Found study access data for deleted study", 0, listHelper.getStudyAccessCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); + } + + @Test + public void testInsertAndRefresh() + { + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + int count = manageData.getCount(); + String shortName = "TUS" + count; + String studyId = "TUS_ID" + count; + createStudy(shortName, false); + + Map initialFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + initialFields.put(StudyEditPage.SHORT_NAME, shortName); + initialFields.put(StudyEditPage.STUDY_ID, studyId); + initialFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + manageData.goToInsertNew(); + StudyEditPage editPage = new StudyEditPage(this.getDriver()); + + editPage.setFormFields(initialFields); + editPage.submit(); + + goToProjectHome(); + + StudiesListHelper studyListHelper = new StudiesListHelper(this); + studyListHelper.addStudyAccessEntry(shortName, "/" + PROJECT_NAME + "/" + shortName , "Public", true); + + goToProjectHome(); // there should be no error alert after inserting but before refreshing + DataFinderPage finder = new DataFinderPage(this, true); + + finder.search(studyId); + List dataCards = finder.getDataCards(); + + assertEquals("Should not find newly inserted study without reindex", 0, dataCards.size()); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + manageData.refreshCube(); + + goToProjectHome(); + + finder.search(studyId); + dataCards = finder.getDataCards(); + + assertEquals("Should find newly inserted study after reindex", 1, dataCards.size()); + } +} \ No newline at end of file diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index d36ec605..283670bd 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -35,11 +35,10 @@ import org.labkey.test.pages.study.ManageParticipantGroupsPage; import org.labkey.test.pages.trialshare.DataFinderPage; import org.labkey.test.pages.trialshare.PublicationsListHelper; -import org.labkey.test.pages.trialshare.StudyPropertiesQueryUpdatePage; +import org.labkey.test.pages.trialshare.StudiesListHelper; import org.labkey.test.util.APIContainerHelper; import org.labkey.test.util.AbstractContainerHelper; import org.labkey.test.util.DataRegionTable; -import org.labkey.test.util.ListHelper; import org.labkey.test.util.LogMethod; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.ReadOnlyTest; @@ -99,12 +98,7 @@ public boolean needsSetup() } } - protected void importLists() - { - ListHelper listHelper = new ListHelper(this); - listHelper.importListArchive(dataListArchive); - } - + @Override protected void createStudies() { log("Creating a study container for each study"); @@ -119,7 +113,7 @@ protected void createStudies() createStudy(PUBLIC_STUDY_NAME); createStudy(OPERATIONAL_STUDY_NAME); goToProjectHome(); - StudyPropertiesQueryUpdatePage queryUpdatePage = new StudyPropertiesQueryUpdatePage(this); + StudiesListHelper queryUpdatePage = new StudiesListHelper(this); queryUpdatePage.setStudyContainers(); goToProjectHome(); PublicationsListHelper pubUpdatePage = new PublicationsListHelper(this); From cec6ece9c44b7bc5ea733482723746dc86b52fb0 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 5 Jul 2016 11:47:39 -0700 Subject: [PATCH 336/587] Spec 26558: remove limit on the number of container returned for dropdowns --- resources/web/study/Finder/data/Container.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/web/study/Finder/data/Container.js b/resources/web/study/Finder/data/Container.js index 7091538e..a54a9cdf 100644 --- a/resources/web/study/Finder/data/Container.js +++ b/resources/web/study/Finder/data/Container.js @@ -14,7 +14,8 @@ Ext4.define('LABKEY.study.data.Container', { containerFilter : 'AllFolders', 'query.columns' : "EntityId,DisplayName,Path", 'query.queryName': "Containers", - 'query.sort': "DisplayName" + 'query.sort': "DisplayName", + limit : -1 }, reader : { type : 'json', From 1cc81854f44449b4a5f54c93b3c3c8a6fd3f82a7 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 5 Jul 2016 11:50:23 -0700 Subject: [PATCH 337/587] Spec 26558: reorder fields in publication edit form --- .../panel/PublicationDetailsFormPanel.js | 116 +++++++++--------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js index a1fe2423..147f5b10 100644 --- a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js @@ -142,7 +142,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { xtype : this.mode == "view" ? 'displayfield' : 'textfield', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - fieldLabel : 'Journal', + fieldLabel : 'Journal/Meeting', name : 'journal', labelWidth : this.defaultFieldLabelWidth, width : this.mediumLargeFieldWidth @@ -200,24 +200,18 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { regex : /PMC\d+/ }); + items.push( { - xtype : 'combo', - disabled : this.mode == "view", - disabledCls : 'labkey-combo-disabled', + xtype : this.mode == "view" ? 'displayfield' : 'textarea', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - name : 'manuscriptContainer', - store : { - model : 'LABKEY.study.data.Container', - autoLoad: true - }, - fieldLabel : 'Manuscript Container', + stripNewLines : true, + fieldLabel : 'Keywords', + name : 'keywords', labelWidth : this.defaultFieldLabelWidth, - valueField : 'EntityId', - displayField : 'Path', - editable : false, - width : this.mediumLargeFieldWidth + width : this.mediumFieldWidth, + height : 50 } ); @@ -226,35 +220,24 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { xtype : 'combo', disabled : this.mode == "view", disabledCls : 'labkey-combo-disabled', + multiSelect : true, cls : this.fieldClsName, labelCls : this.fieldLabelClsName, - name : 'permissionsContainer', - store : { - model : 'LABKEY.study.data.Container', + name : 'therapeuticAreas', + store : { + model : 'LABKEY.study.data.TherapeuticArea', autoLoad: true }, - fieldLabel : 'Permissions Container', + fieldLabel : 'Therapeutic Areas', labelWidth : this.defaultFieldLabelWidth, - valueField : 'EntityId', - displayField : 'Path', + valueField : 'TherapeuticArea', + displayField : 'TherapeuticArea', editable : false, - width : this.mediumLargeFieldWidth + delimiter : this.multiSelectDelimiter, + width : this.mediumFieldWidth } ); - items.push( - { - xtype : this.mode == "view" ? 'displayfield' : 'textarea', - cls : this.fieldClsName, - labelCls : this.fieldLabelClsName, - stripNewLines : true, - fieldLabel : 'Keywords', - name : 'keywords', - labelWidth : this.defaultFieldLabelWidth, - width : this.mediumFieldWidth, - height : 50 - } - ); items.push( { @@ -293,6 +276,7 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { } ); + items.push( { xtype : 'combo', @@ -316,28 +300,6 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { } ); - items.push( - { - xtype : 'combo', - disabled : this.mode == "view", - disabledCls : 'labkey-combo-disabled', - multiSelect : true, - cls : this.fieldClsName, - labelCls : this.fieldLabelClsName, - name : 'therapeuticAreas', - store : { - model : 'LABKEY.study.data.TherapeuticArea', - autoLoad: true - }, - fieldLabel : 'Therapeutic Areas', - labelWidth : this.defaultFieldLabelWidth, - valueField : 'TherapeuticArea', - displayField : 'TherapeuticArea', - editable : false, - delimiter : this.multiSelectDelimiter, - width : this.mediumFieldWidth - } - ); items.push( { @@ -404,6 +366,48 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { labelWidth : this.defaultFieldLabelWidth, width : this.largeFieldWidth }); + + items.push( + { + xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', + cls : this.fieldClsName, + labelCls : this.fieldLabelClsName, + name : 'manuscriptContainer', + store : { + model : 'LABKEY.study.data.Container', + autoLoad: true + }, + fieldLabel : 'Manuscript Container', + labelWidth : this.defaultFieldLabelWidth, + valueField : 'EntityId', + displayField : 'Path', + editable : false, + width : this.mediumLargeFieldWidth + } + ); + + items.push( + { + xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', + cls : this.fieldClsName, + labelCls : this.fieldLabelClsName, + name : 'permissionsContainer', + store : { + model : 'LABKEY.study.data.Container', + autoLoad: true + }, + fieldLabel : 'Permissions Container', + labelWidth : this.defaultFieldLabelWidth, + valueField : 'EntityId', + displayField : 'Path', + editable : false, + width : this.mediumLargeFieldWidth + } + ); return items; } }); \ No newline at end of file From cea85c45c52c6af7fa987408fd5363eae5dcc4c8 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 5 Jul 2016 11:51:10 -0700 Subject: [PATCH 338/587] Spec 26558: update list archives and set permissions on public container for public reader --- test/sampledata/DataFinder.lists.zip | Bin 13515 -> 13502 bytes test/sampledata/Lookups.lists.zip | Bin 2702 -> 2806 bytes .../trialshare/ManagePublicationsTest.java | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sampledata/DataFinder.lists.zip b/test/sampledata/DataFinder.lists.zip index 66e4743d0bc19320f12c31d19858400d5c2acdd6..33fe907e6383a971ceceda2a6eaa97de91f782d7 100644 GIT binary patch delta 6711 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 XiT_pmMDda%%q5rdQlUQK`t9+5=k^Ko delta 6549 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 TR0NE&4tpa)kI5zY*Qoyi)`Se* diff --git a/test/sampledata/Lookups.lists.zip b/test/sampledata/Lookups.lists.zip index 3a12538067923425b51d679657a925738507d421..1a0321815e74ec735cdc086b528b2a8cfa993d58 100644 GIT binary patch delta 1330 zcmeAZ{U*vA;LXe;!ob17!N3*%bg~>%Ff)+e$jiqBq9>Oy`GD!eOj%&sfH@RQcQB`b z>G#a4{-6EUKH<^x)X~&^W&~vTo;s_e>FN8>=n;>{S?`l)yu&rTbWi)}J~w(+qUq(M z{g8T@C8?;5d#B*0T83Q!G)z5Z1ye|srskW?&n`N5IBDSd-x0mx#-=kp`laL zw0<)-H>u6?++%fetzq%o6ZY4>?mzB1Yl$|q$R_bA(^bkX>)-C~uiaGtZO6kGj8oEr z&g{K7p@PFjM_BDz@q4p9Qa*b(KR8lvDiLFs79{C$VeTa9ElVz@-&lAt#llHDz>hJo zIVgFPNuE1ayuB6~PbLXf zy$e09`Aptx>#@$+lDqfE6*L7%_I5oF-_^KtyU~@1w^P@3Yuu?%JtwJmPhY>AG0#J1 z=UtB2sI9%Ht_x4pI>P#Nv($1?n*h<>Oh0yfGjUw%a)PIQ!ITdl7q0IN30d>-+wIZ~ z=ks^p<=dX${pVOq&xh~&r$gr)YTI|s&2oa>I(HMLLq+q_CGP~TJ9vES-Ife@!KU&S z0r~0o&)P4O{in@l!`ZVb?@xW>;#Ftse*Ql7P<`p=&A&hIN-N#xKgUurXO-z+uGcYL zEt1>wtv_Xz>{3`A`pfyP!qcz^c|ofdDsKr|%)P;Bq5Xz$FSjvmH8Q*Tw8pCZh}ZJy z;1Vs3=`Z=W?kdtfseA2%qvz~WmhjUh51+XNEW9M26MFZSnVWQ>*yTM-_Z?Vi9ajJP zijMT!d2A@VJRg)aZ9aDM6zV?S{%q5H<9iEgU@e&0COYFFkOeYRkSR9?}pt-QZoo^Lf}GFxqGV6rsy zRtS^br|C}_{az|%Zc#tq7Wip$-JpWVd5--KTat&#bh$n~;mW9CfBn~NHHR9N|a zv>AW@&6r$PAt`pRzUR)J&xHsQ@oFA-+0UBC3WWK9=inn=j} z?{~$kIvVF`*OZ!-Z2l?n^!dV!63yeyb=|)Op0X{>*ec)sbusH}iL$uGlV#a1J-xYb z&da<1U!=TR@BbbY1}C;}xc&&3HF=pqLBPl)!T>8I7@tngy;Fj zp(=Um5u2y^{~99$gB%Oc2#``Z2%GHBAp_3e%Q#HH^j!`MFfGAp4yJ=R?RbG@j|aSj zm^_!$R30S4{rG>MDbOTiAQpz32x5dy{>Z5m22z8FJO&0%6g6jnZpq0kE-BWl$jw1_ VFtUGnP_z|H&g7D3>)`+i0su0O8<_wA delta 1223 zcmew++9%2z;LXe;!ob17!LYgE?#5(3Cgv>-cPH`&gQ&?ROg>=xFjE$oHee0~(;dty zVER3?ssXp2r;euXGcG;fQ)hKFJ$)ZCF$8$CbJWdmw-sh!U=RdigdHG5lUa)EfwTuu z0uDHVteni^l48Az+?==5qVq2sh&0^)uFdz*vh>!$E1{XoHgPwJnq2hx@Jc1UcV@Et zw#)1O%RRp*%DIwDf5o0H8;k$kd|tCD`Tr}`#~U1%&J-z5xA`RFAb7f^^Xr{Tp7(cF z7K_gLnIH50o9gsa2~73=UTGVaT(o_cP-7@{m}{ykgXsjR$>%v98ZEP#dh5)xdsmd_ zxSijjyfA6X`k+-4o_PH`a$|?!x|tHW5_yWZ8ud&>H9Z*bzcrc9%{bZH`tX@*d+Yrz z?Xv~c?)^Aa`nHOv*CJ~}w|LdNEtkVS_biE;+#==mCR*KsdBx&|zy5j|&#AY*9;9;S z#+13czN>MS?|jQIEdvmkURU&6bN?&-T*1qFugeRyqfa>TZ@K2srmm?8H)FGShP;P%*cLS z_U2%OOVXU9TmJm}+ZtQ$Azc#jZH@5lM)pvfZ!EftdR~4K`uo4_6aU?SLn5MQHyY@~ zX)Ix0D|@N!#qDHAPHWkT?4f^}FFtc*-w#e^w~kJgQek3XIL3#b%xc-?>(6E#HsE3Z zP(A<9i+#K9?piy=VkwUqn>EiY+Xmro7oCmGy8X}IPH*A)Rw%tM>e2D|X`3D|%|6Qh z%i)~XjdkV<7sAd>SSz|mv_HJs;@RpoOAk2q>^0NKnDM)L%38hqg%Q^Ar&sD6*LtF@ z_$)24w4FZ1cqU7Nn%N9v0h1W8L}~;EX&7f0#@nDX#u7i zIL*QIE>1gMP*#R#_Q|YVreGOcE@?*d$&p-2VIX Date: Tue, 5 Jul 2016 14:47:06 -0700 Subject: [PATCH 339/587] Spec 26558: reorder publication facets and make submission status visible only to those who can see incomplete manuscripts --- src/org/labkey/trialshare/TrialShareController.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 9824b0eb..d2ab3528 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -648,18 +648,19 @@ private List getPublicationFacets() List facets = new ArrayList<>(); StudyFacetBean facet; - facet = new StudyFacetBean("Status", "Statuses", "Publication.Status", "Status", "[Publication.Status][(All)]", FacetFilter.Type.OR, 1); + facet = new StudyFacetBean("Status", "Statuses", "Publication.Status", "Status", "[Publication.Status][(All)]", FacetFilter.Type.OR, 2); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); facet.setDisplayFacet(TrialShareManager.get().canSeeIncompleteManuscripts(getUser(), getContainer())); facets.add(facet); - facet = new StudyFacetBean("Therapeutic Area", "Therapeutic Areas", "Publication.Therapeutic Area", "Therapeutic Area", "[Publication.Therapeutic Area][(All)]", FacetFilter.Type.OR, 2); + facet = new StudyFacetBean("Therapeutic Area", "Therapeutic Areas", "Publication.Therapeutic Area", "Therapeutic Area", "[Publication.Therapeutic Area][(All)]", FacetFilter.Type.OR, 4); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); facets.add(facet); - facet = new StudyFacetBean("Publication Type", "Publication Types", "Publication.Publication Type", "PublicationType", "[Publication.Publication Type][(All)]", FacetFilter.Type.OR, 3); + facet = new StudyFacetBean("Publication Type", "Publication Types", "Publication.Publication Type", "PublicationType", "[Publication.Publication Type][(All)]", FacetFilter.Type.OR, 1); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); facets.add(facet); - facet = new StudyFacetBean("Submission Status", "Submission Statuses", "Publication.Submission Status", "SubmissionStatus", "[Publication.Submission Status][(All)]", FacetFilter.Type.OR, 4); + facet = new StudyFacetBean("Submission Status", "Submission Statuses", "Publication.Submission Status", "SubmissionStatus", "[Publication.Submission Status][(All)]", FacetFilter.Type.OR, 3); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); + facet.setDisplayFacet(TrialShareManager.get().canSeeIncompleteManuscripts(getUser(), getContainer())); facets.add(facet); facet = new StudyFacetBean("Study", "Studies", "Publication.Study", "Study", "[Publication.Study].[(All)]", FacetFilter.Type.OR, 5); facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); From c1fc5733f2f06e08972e7990cff634bc7743b1c3 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 5 Jul 2016 14:48:25 -0700 Subject: [PATCH 340/587] Spec 26558: remove references to StudyAccess, PublicationAccess and PublicationCondition --- resources/olap/PublicationCube.xml | 5 ---- .../lists/ManuscriptsAndAbstracts.query.xml | 10 -------- .../panel/PublicationDetailsFormPanel.js | 24 ------------------ .../PublicationDocumentProvider.java | 1 - .../labkey/trialshare/TrialShareManager.java | 8 ------ .../query/TrialShareQuerySchema.java | 5 ---- test/sampledata/DataFinder.lists.zip | Bin 13502 -> 14300 bytes test/sampledata/Lookups.lists.zip | Bin 2806 -> 0 bytes .../pages/trialshare/PublicationEditPage.java | 3 --- .../pages/trialshare/StudiesListHelper.java | 8 +++--- .../tests/trialshare/DataFinderTestBase.java | 3 --- .../trialshare/ManagePublicationsTest.java | 11 -------- 12 files changed, 4 insertions(+), 74 deletions(-) delete mode 100644 test/sampledata/Lookups.lists.zip diff --git a/resources/olap/PublicationCube.xml b/resources/olap/PublicationCube.xml index 8786088b..ed69afda 100644 --- a/resources/olap/PublicationCube.xml +++ b/resources/olap/PublicationCube.xml @@ -49,11 +49,6 @@ - -

- - - diff --git a/resources/queries/lists/ManuscriptsAndAbstracts.query.xml b/resources/queries/lists/ManuscriptsAndAbstracts.query.xml index 17f29fb0..64a75185 100644 --- a/resources/queries/lists/ManuscriptsAndAbstracts.query.xml +++ b/resources/queries/lists/ManuscriptsAndAbstracts.query.xml @@ -14,16 +14,6 @@ false - - - lists - PublicationCondition - publicationId - junction - Condition - - false - lists diff --git a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js index 147f5b10..8b440e42 100644 --- a/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/PublicationDetailsFormPanel.js @@ -277,30 +277,6 @@ Ext4.define('LABKEY.study.panel.PublicationDetailsFormPanel', { ); - items.push( - { - xtype : 'combo', - disabled : this.mode == "view", - disabledCls : 'labkey-combo-disabled', - multiSelect : true, - cls : this.fieldClsName, - labelCls : this.fieldLabelClsName, - name : 'conditions', - store : { - model : 'LABKEY.study.data.Condition', - autoLoad: true - }, - fieldLabel : 'Conditions', - labelWidth : this.defaultFieldLabelWidth, - valueField : 'Condition', - displayField : 'Condition', - editable : false, - delimiter : this.multiSelectDelimiter, - width : this.mediumLargeFieldWidth - } - ); - - items.push( { xtype : this.mode == "view" ? 'displayfield' : 'textfield', diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index cbd682b5..e47e8faf 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -89,7 +89,6 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container "psn.StudyShortName, " + "pta.TherapeuticArea\n " + "FROM ManuscriptsAndAbstracts pub\n " + - " LEFT JOIN (SELECT PublicationId, group_concat(Condition) AS Condition FROM PublicationCondition GROUP BY PublicationId) pc on pub.Key = pc.PublicationId\n " + " LEFT JOIN (SELECT PublicationId, group_concat(StudyId) AS StudyId FROM\n " + " (SELECT sp1.StudyId AS StudyId, ps1.PublicationId FROM PublicationStudy ps1 LEFT JOIN StudyProperties sp1 on ps1.StudyId = sp1.StudyId) " + " GROUP BY PublicationId) psid on pub.Key = psid.PublicationId\n " + diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 9f26252a..5d928588 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -227,13 +227,10 @@ public void insertPublication(User user, Container container, PublicationEditBea List> pubData = schema.getPublicationsTableInfo().getUpdateService().insertRows(user, container, Collections.singletonList(fields), batchValidationErrors, null, null); if (batchValidationErrors.hasErrors()) throw batchValidationErrors; - List> dataList = new ArrayList<>(); Integer publicationKey = (Integer) pubData.get(0).get(TrialShareQuerySchema.PUBLICATION_KEY_FIELD); // insert the one-to-many data - // conditions - addJoinTableData(schema.getPublicationConditionTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey, TrialShareQuerySchema.CONDITION_FIELD, publication.getConditions(), user, container); addJoinTableData(schema.getPublicationStudyTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey, TrialShareQuerySchema.STUDY_ID_FIELD, publication.getStudyIds(), user, container); addJoinTableData(schema.getPublicationTherapeuticAreaTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publicationKey, TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, publication.getTherapeuticAreas(), user, container); @@ -273,9 +270,6 @@ public void updatePublication(User user, Container container, PublicationEditBea // update the many-to-one data // first get rid of the current values for this publication. Then add the new data - schema.getPublicationConditionTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationConditionTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); - addJoinTableData(schema.getPublicationConditionTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId(), TrialShareQuerySchema.CONDITION_FIELD, publication.getConditions(), user, container); - schema.getPublicationStudyTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationStudyTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); addJoinTableData(schema.getPublicationStudyTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId(), TrialShareQuerySchema.STUDY_ID_FIELD, publication.getStudyIds(), user, container); @@ -316,7 +310,6 @@ public void deletePublications(@NotNull User user, @NotNull Container container, TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); deleteJoinTableData(schema.getPublicationStudyTableInfo(), "Key", user, container, idFilter); - deleteJoinTableData(schema.getPublicationConditionTableInfo(), "Key", user, container, idFilter); deleteJoinTableData(schema.getPublicationTherapeuticAreaTableInfo(), "Key", user, container, idFilter); List> pkMaps = new ArrayList<>(); @@ -512,7 +505,6 @@ public PublicationEditBean getPublication(Integer id, User user, Container conta PublicationEditBean publication = new PublicationEditBean(studyPublication); SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.PUBLICATION_ID_FIELD), id); publication.setStudyIds(new TableSelector(schema.getPublicationStudyTableInfo(), Collections.singleton(TrialShareQuerySchema.STUDY_ID_FIELD), filter, null).getArrayList(String.class)); - publication.setConditions(new TableSelector(schema.getPublicationConditionTableInfo(), Collections.singleton(TrialShareQuerySchema.CONDITION_FIELD), filter, null).getArrayList(String.class)); publication.setTherapeuticAreas(new TableSelector(schema.getPublicationTherapeuticAreaTableInfo(), Collections.singleton(TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD), filter, null).getArrayList(String.class)); return publication; } diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index e44199cf..31aaff55 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -58,7 +58,6 @@ public class TrialShareQuerySchema static { _requiredPublicationLists.add(PUBLICATION_TABLE); - _requiredPublicationLists.add(PUBLICATION_CONDITION_TABLE); _requiredPublicationLists.add(PUBLICATION_STUDY_TABLE); _requiredPublicationLists.add(PUBLICATION_THERAPEUTIC_AREA_TABLE); } @@ -68,7 +67,6 @@ public class TrialShareQuerySchema static { _requiredStudyLists.add(STUDY_ACCESS_TABLE); _requiredStudyLists.add(STUDY_TABLE); - _requiredStudyLists.add(STUDY_ASSAY_TABLE); _requiredStudyLists.add(STUDY_CONDITION_TABLE); _requiredStudyLists.add(STUDY_AGE_GROUP_TABLE); _requiredStudyLists.add(STUDY_PHASE_TABLE); @@ -232,7 +230,6 @@ public List getStudyTables() { list.add(getStudyPropertiesTableInfo()); list.add(getStudyAccessTableInfo()); list.add(getStudyAgeGroupTableInfo()); - list.add(getStudyAssayTableInfo()); list.add(getStudyConditionTableInfo()); list.add(getStudyPhaseTableInfo()); list.add(getStudyTherapeuticAreaTableInfo()); @@ -242,8 +239,6 @@ public List getStudyTables() { public List getPublicationTables() { List list = new ArrayList<>(); list.add(getPublicationsTableInfo()); - list.add(getPublicationAssayTableInfo()); - list.add(getPublicationConditionTableInfo()); list.add(getPublicationStudyTableInfo()); list.add(getPublicationTherapeuticAreaTableInfo()); return list; diff --git a/test/sampledata/DataFinder.lists.zip b/test/sampledata/DataFinder.lists.zip index 33fe907e6383a971ceceda2a6eaa97de91f782d7..7b256988e571a6e05bc49fa770387109bf38631c 100644 GIT binary patch delta 8410 zcmaKR1yoe;);`*l91-BJqPj!0uau)Iv>~nu!H4T^K+a2>Rw5bo1%0DE7 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�q4*IGTy= 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 O`z`vOk7@ZItp5X&7Mwc( diff --git a/test/sampledata/Lookups.lists.zip b/test/sampledata/Lookups.lists.zip deleted file mode 100644 index 1a0321815e74ec735cdc086b528b2a8cfa993d58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2806 zcmai03pmti7oQmoV`B4#GA?P=T4OGTh6>BNWX#1F29wo=Tt{PQE=FQ4+FGH~YHQPN z!djIGlgK5uFw9V~E|p3dMM<%XX!HI5`o0X`e)h~W&-2dn&N;vLJ@4=Q&S6khmgztc z2m}PlQn*8u3<<26f*^sAj~DP`m*dG;G;WaB){bi?Q{YswS}O63q=^3)srauTw1Y0{ zhz%?0x~7(n{_^7Eg;GC-}`Sa)FO`^l8rqt0~`!uDDA5ZDsmRdr75NUuh{5Cn^f*EW7U7t(POHP1UWu^>ov)l+#mT)7c3>)UNYY zLt9}RHWeTD%}DKvDH5{dftk+mTfgUnmo3T~k65bJC-{$iSHAoBMLSZW5!(N&$N*R7 zl%{QMO`AcvSi|s%Tl>jZ>Z1@L6Vrg7~3x zENgbh(iKQZ7E+aQUyEYk#11x#YJku2i`9#t(gXgC|3`i?1;UWMK6_(6hQy;i3N;F` z?h0O!nY)Zi_V9|K(dhtf$7C2X&YUX8NM-F*S^t=)xhhXvY(EE>UbHXKGGFWC^r8WckM(W^EIeA|STxzA-|DG`yPPVOQCVgbTq_Ot+-gt&z+?V{ z>GcA+7fcypNlfr+J`th3pqNmhfF10_3k{lkc&R&%f_2B?q_kKp#bbaclag_i;sG8O zCvBI>adN5xX(@QCK%yx!48EBx`!DM9XS0W`ftVYXL|lJ@8u zeVYF5e`y)vIN)heXGCuKG z?)Zz*2aKw{dPXVteRA$a`M2+8TKd1tA1|93uL={^dY<0=Ip?DFU&xo+4(&%(MB9x; z3aZv#@||`WS*!4S!u7r=URUm&yS&6%>{v1}bPXo4vguVM*otmZ3Idn{bF&jeDZs1Iy;*4I_p%dfP;~ap&0O1~I7 zI5#>}*EfWlh)J&by;hUSAgb)%TFv;{j{6{3g8#ztj&tSKB-^`Qy0c#kX2q%JKq0h$ zo30TBgFqgySV{;%G2w>_1Z-~j98HVvwMPC%(1PDdtaX+-c-jiBtEp5nPU zLV#{c{Wg6@OQ9-`kQk@`nG1iZ7rP_(M8-Y$^75`u{eOTVuQV4-kt&h{;o4D zj-af*3y@bLlOA_D>b F`ZolB+>8JK diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java b/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java index 2b6a7ec0..ffee052c 100644 --- a/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java @@ -25,7 +25,6 @@ public class PublicationEditPage extends CubeObjectEditPage public static final String PERMISSIONS_CONTAINER ="permissionsContainer"; public static final String KEYWORDS ="keywords"; public static final String STUDIES ="studyIds"; - public static final String CONDITIONS ="conditions"; public static final String THERAPEUTIC_AREAS ="therapeuticAreas"; public static final String LINK1 ="link1"; public static final String DESCRIPTION1 ="description1"; @@ -46,7 +45,6 @@ public class PublicationEditPage extends CubeObjectEditPage static { MULTI_SELECT_FIELD_NAMES.put(STUDIES, "Studies:"); - MULTI_SELECT_FIELD_NAMES.put(CONDITIONS, "Conditions:"); MULTI_SELECT_FIELD_NAMES.put(THERAPEUTIC_AREAS, "Therapeutic Areas:"); } @@ -68,7 +66,6 @@ public class PublicationEditPage extends CubeObjectEditPage FIELD_NAMES.add(PERMISSIONS_CONTAINER); FIELD_NAMES.add(KEYWORDS); FIELD_NAMES.add(STUDIES); - FIELD_NAMES.add(CONDITIONS); FIELD_NAMES.add(THERAPEUTIC_AREAS); FIELD_NAMES.add(LINK1); FIELD_NAMES.add(DESCRIPTION1); diff --git a/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java b/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java index 40fd7e1e..2bc52269 100644 --- a/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java +++ b/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java @@ -41,7 +41,7 @@ public void addStudyAccessEntry(String shortName, String containerName, String v DataRegionTable table = new DataRegionTable("query", _test); table.clickHeaderButtonByText("Insert New"); selectOptionByText(Locators.studyContainerSelect, containerName); - setFormElement(Locators.studyVisibility, visibility); + selectOptionByText(Locators.studyVisibility, visibility); selectOptionByText(Locators.studyIdSelect, shortName); clickButton("Submit"); } @@ -116,9 +116,9 @@ public int getStudyListCount(String listName, String studyId, Boolean navigateTo private static class Locators { - public static final Locator.XPathLocator studyContainerSelect = Locator.tagWithName("select", "quf_StudyContainer"); - public static final Locator.XPathLocator studyIdSelect = Locator.tagWithName("select", "quf_StudyId"); - public static final Locator studyVisibility = Locator.name("quf_Visibility"); + static final Locator.XPathLocator studyContainerSelect = Locator.tagWithName("select", "quf_StudyContainer"); + static final Locator.XPathLocator studyIdSelect = Locator.tagWithName("select", "quf_StudyId"); + static final Locator.XPathLocator studyVisibility = Locator.tagWithName("select", "quf_Visibility"); } } diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java index 6ed7c62d..bac9abe3 100644 --- a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java +++ b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java @@ -58,7 +58,6 @@ public abstract class DataFinderTestBase extends BaseWebDriverTest static final String CONTROLLER = "trialshare"; static final String ACTION = "dataFinder"; static File dataListArchive = TestFileUtils.getSampleData("DataFinder.lists.zip"); - static File lookupListArchive = TestFileUtils.getSampleData("Lookups.lists.zip"); public enum CubeObjectType { @@ -172,8 +171,6 @@ protected void importLists() { ListHelper listHelper = new ListHelper(this); listHelper.importListArchive(dataListArchive); - goToProjectHome(); - listHelper.importListArchive(lookupListArchive); } protected abstract void createStudies(); diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index 68effd4f..5396b612 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -55,7 +55,6 @@ public class ManagePublicationsTest extends DataFinderTestBase EXISTING_PUB_FIELDS.put(PublicationEditPage.PERMISSIONS_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); EXISTING_PUB_FIELDS.put(PublicationEditPage.KEYWORDS, EMPTY_VALUE); EXISTING_PUB_FIELDS.put(PublicationEditPage.STUDIES, EMPTY_VALUE); - EXISTING_PUB_FIELDS.put(PublicationEditPage.CONDITIONS, "Microscopic Polyangiitis"); EXISTING_PUB_FIELDS.put(PublicationEditPage.THERAPEUTIC_AREAS, "Autoimmune"); EXISTING_PUB_FIELDS.put(PublicationEditPage.LINK1, "https://www.immunetolerance.org/sites/files/Specks_NEJM_2013.pdf"); EXISTING_PUB_FIELDS.put(PublicationEditPage.DESCRIPTION1, "Paper on immunetolerance.org"); @@ -226,7 +225,6 @@ public void testInsertWithAllFields() newFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); newFields.put(PublicationEditPage.KEYWORDS, "key words keywords"); newFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); - newFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy"}); newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); newFields.put(PublicationEditPage.LINK1, "http://link/to.this"); newFields.put(PublicationEditPage.DESCRIPTION1, "Link 1 Description"); @@ -264,7 +262,6 @@ public void testInsertMultiValuedFields() newFields.put(PublicationEditPage.STATUS, "In Progress"); newFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID, OPERATIONAL_STUDY_ID}); - newFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy", "Asthma", "Autoimmune Disorders"}); newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune", "Allergy"}); manageData.goToInsertNew(); @@ -296,7 +293,6 @@ public void testEditMultiValuedFields() initialFields.put(PublicationEditPage.STATUS, "In Progress"); initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); - initialFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy", "Asthma"}); initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune", "Allergy"}); manageData.goToInsertNew(); @@ -309,15 +305,12 @@ public void testEditMultiValuedFields() Map newFields = new HashMap<>(); newFields.put(PublicationEditPage.STUDIES, new String[]{OPERATIONAL_STUDY_ID}); - // this removes "Allergy" and adds "Cat Allergy" - newFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy", "Cat Allergy"}); newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); editPage.setFormFields(newFields, false); editPage.submit(); manageData.goToEditRecord((String) initialFields.get(TITLE)); initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID, OPERATIONAL_STUDY_ID}); - initialFields.put(PublicationEditPage.CONDITIONS, new String[]{"Asthma", "Cat Allergy"}); initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune", "Allergy", "T1DM"}); Map unexpectedValues = editPage.compareFormValues(initialFields); @@ -353,7 +346,6 @@ public void testUpdatePublication() initialFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); initialFields.put(PublicationEditPage.KEYWORDS, "key words keywords"); initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); - initialFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy"}); initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); initialFields.put(PublicationEditPage.LINK1, "http://link/to.this"); initialFields.put(PublicationEditPage.DESCRIPTION1, "Link 1 Description"); @@ -419,7 +411,6 @@ public void testInsertAndDelete() initialFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); initialFields.put(PublicationEditPage.STATUS, "In Progress"); initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); - initialFields.put(PublicationEditPage.CONDITIONS, new String[]{"Allergy"}); initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); manageData.goToInsertNew(); PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); @@ -436,8 +427,6 @@ public void testInsertAndDelete() goToProjectHome(); Assert.assertEquals("Found studies for deleted publication", 0, listHelper.getPublicationStudyCount((String) initialFields.get(PublicationEditPage.TITLE), true)); goToProjectHome(); - Assert.assertEquals("Found conditions for deleted publication", 0, listHelper.getPublicationConditionCount((String) initialFields.get(PublicationEditPage.TITLE), true)); - goToProjectHome(); Assert.assertEquals("Found therapeutic areas for deleted publication", 0, listHelper.getPublicationTherapeuticAreaCount((String) initialFields.get(PublicationEditPage.TITLE), true)); } From 428ba054d87dfb111abe9d54a4bc9f679861b1a5 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 5 Jul 2016 18:41:08 -0700 Subject: [PATCH 341/587] Spec 26558: reduce code duplication --- .../org/labkey/test/pages/trialshare/CubeObjectEditPage.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java index 553a8fca..bbad2ab4 100644 --- a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java @@ -35,8 +35,7 @@ public CubeObjectEditPage(WebDriver driver) public void setTextFormValue(String key, String value) { - Locator fieldLocator = Locator.name(key); - setFormElement(fieldLocator, value); + setTextFormValue(key, value, false); } public void setTextFormValue(String key, String value, Boolean waitForSubmit) From afeb69582cfb2daca2748b054947afa49328da86 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 5 Jul 2016 18:41:49 -0700 Subject: [PATCH 342/587] Spec 26558: small cleanup after code review --- .../trialshare/TrialShareController.java | 5 ++- .../labkey/trialshare/TrialShareManager.java | 42 +------------------ 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index d2ab3528..3dfe4808 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.labkey.api.action.ApiAction; @@ -176,7 +177,7 @@ public NavTree appendNavTrail(NavTree root) public ModelAndView getView(Object o, BindException errors) throws Exception { setTitle("TrialShare Data Finder"); - return new JspView("/org/labkey/trialshare/view/dataFinder.jsp", getFinderBean(getContainer(), getViewContext().getActionURL().getParameter(OBJECT_NAME_PARAM))); + return new JspView<>("/org/labkey/trialshare/view/dataFinder.jsp", getFinderBean(getContainer(), getViewContext().getActionURL().getParameter(OBJECT_NAME_PARAM))); } } @@ -473,7 +474,7 @@ public Object execute(Object form, BindException errors) throws Exception for (StudyPublicationBean pub : publications) { - if (pub.getShow() != null && pub.getShow() && pub.hasPermission(getUser())) + if (BooleanUtils.isTrue(pub.getShow()) && pub.hasPermission(getUser())) { pubCounts.putIfAbsent(pub.getStudyId(), new Pair<>(0, 0)); Pair countPair = pubCounts.get(pub.getStudyId()); diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 5d928588..90bbd522 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -16,6 +16,7 @@ package org.labkey.trialshare; +import org.apache.commons.lang3.BooleanUtils; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -132,23 +133,6 @@ public Set getVisibleStudyContainers(User user, Container container) return idSet; } - public Set getVisibleStudies(User user, Container container) - { - Set studyIdSet = new HashSet<>(); - TableInfo containerList = TrialShareQuerySchema.getSchema(user, container).getTable(TrialShareQuerySchema.STUDY_ACCESS_TABLE); - if (containerList != null) - { - List studyAccess = (new TableSelector(containerList, null, null)).getArrayList(StudyAccess.class); - for (StudyAccess study : studyAccess) - { - if (study.hasPermission(user)) - { - studyIdSet.add(study.getCubeIdentifier()); - } - } - } - return studyIdSet; - } public Set getVisibleStudies(User user, Container container, String visibility) { @@ -171,28 +155,6 @@ public Set getVisibleStudies(User user, Container container, String visi return studyIdSet; } - public Set getVisibleAssays(User user, Container container) - { - Set idSet = new HashSet<>(); - - Set operationalStudyIds = getVisibleStudies(user, container, TrialShareQuerySchema.OPERATIONAL_VISIBILITY); - TableInfo assayAccess = TrialShareQuerySchema.getSchema(user, container).getTable(TrialShareQuerySchema.STUDY_ASSAY_TABLE); - if (assayAccess != null) - { - Map valueMapArray[] = new TableSelector(assayAccess, null, null).getMapArray(); - for (Map valueMap : valueMapArray) - { - String visibility = (String) valueMap.get("Visibility"); - String cubeId = "[Study.AssayVisibility].[" + valueMap.get("StudyId") + "]"; - if (visibility == null || (visibility.equalsIgnoreCase(TrialShareQuerySchema.PUBLIC_VISIBILITY))) - idSet.add(cubeId); - else if (visibility.equalsIgnoreCase(TrialShareQuerySchema.OPERATIONAL_VISIBILITY) && operationalStudyIds.contains(valueMap.get("StudyId"))) - idSet.add(cubeId); - } - } - return idSet; - } - public Set getVisiblePublications(User user, Container container) { Set publicationIds = new HashSet<>(); @@ -202,7 +164,7 @@ public Set getVisiblePublications(User user, Container container) List publications = (new TableSelector(publicationsList, null, null)).getArrayList(StudyPublicationBean.class); for (StudyPublicationBean publication : publications) { - if (publication.getShow() != null && publication.getShow() && publication.hasPermission(user)) + if (BooleanUtils.isTrue(publication.getShow()) && publication.hasPermission(user)) { publicationIds.add(publication.getCubeId()); } From c0f201f4608042237fd0930e58bd51aed7fe8f8b Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 5 Jul 2016 19:24:11 -0700 Subject: [PATCH 343/587] Spec 26558: more removal of references to unused lists --- resources/olap/StudyCube.xml | 4 ---- .../PublicationDocumentProvider.java | 3 +-- .../trialshare/StudyDocumentProvider.java | 4 +--- .../query/TrialShareQuerySchema.java | 18 ------------------ .../test/pages/trialshare/ManageDataPage.java | 1 + .../trialshare/PublicationsListHelper.java | 11 ----------- .../trialshare/ManagePublicationsTest.java | 5 ++--- 7 files changed, 5 insertions(+), 41 deletions(-) diff --git a/resources/olap/StudyCube.xml b/resources/olap/StudyCube.xml index d4c95f22..debc432f 100644 --- a/resources/olap/StudyCube.xml +++ b/resources/olap/StudyCube.xml @@ -46,10 +46,6 @@ - - - - diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index e47e8faf..71ba7e98 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -84,7 +84,6 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container "pub.Keywords, " + "pub.PermissionsContainer, " + "pub.ManuscriptContainer, " + - "pc.Condition, " + "psid.StudyId, " + "psn.StudyShortName, " + "pta.TherapeuticArea\n " + @@ -114,7 +113,7 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container StringBuilder keywords = new StringBuilder(); // See #26028: identifiers that have punctuation in them (e.g., DOI) are not indexed well as identifiers, so we use keywords instead - for (String field : new String[]{"Author", "Year", "Status", "Title", "SubmissionStatus", "PublicationType", "Journal", "TherapeuticArea", "StudyShortName", "Condition", "DOI"}) + for (String field : new String[]{"Author", "Year", "Status", "Title", "SubmissionStatus", "PublicationType", "Journal", "TherapeuticArea", "StudyShortName", "DOI"}) { if (results.getString(field) != null) keywords.append(results.getString(field)).append(" "); diff --git a/src/org/labkey/trialshare/StudyDocumentProvider.java b/src/org/labkey/trialshare/StudyDocumentProvider.java index c254d93e..6f71d85a 100644 --- a/src/org/labkey/trialshare/StudyDocumentProvider.java +++ b/src/org/labkey/trialshare/StudyDocumentProvider.java @@ -68,7 +68,6 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container "sa.StudyId, "+ "sa.StudyContainer, "+ "sc.condition, "+ - "sas.Assay, "+ "sph.Phase, "+ "sag.AgeGroup, "+ "sta.TherapeuticArea, "+ @@ -79,7 +78,6 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container "sp.Investigator "+ "FROM StudyAccess sa "+ " LEFT JOIN StudyProperties sp ON sa.StudyId = sp.StudyId "+ - " LEFT JOIN (SELECT StudyId, group_concat(Assay) AS Assay FROM StudyAssay GROUP BY StudyId) sas on sa.StudyId = sas.StudyId "+ " LEFT JOIN (SELECT StudyId, group_concat(Condition) As Condition FROM StudyCondition GROUP BY StudyId) sc on sa.StudyId = sc.StudyId "+ " LEFT JOIN (SELECT StudyId, group_concat(AgeGroup) AS AgeGroup FROM StudyAgeGroup GROUP BY StudyId) sag on sa.StudyId = sag.StudyId "+ " LEFT JOIN (SELECT StudyId, group_concat(Phase) AS Phase FROM StudyPhase GROUP BY StudyId) sph on sa.StudyId = sph.StudyId "+ @@ -102,7 +100,7 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container StringBuilder keywords = new StringBuilder(); // See #26028: we want to avoid stemming of the following fields, so we use keywords instead - for (String field : new String[]{"shortName", "StudyId", "Investigator", "AgeGroup", "Assay", "Condition", "Phase", "TherapeuticArea", "StudyType"}) + for (String field : new String[]{"shortName", "StudyId", "Investigator", "AgeGroup", "Condition", "Phase", "TherapeuticArea", "StudyType"}) { if (results.getString(field) != null) keywords.append(results.getString(field)).append(" "); diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index 31aaff55..7d8794c1 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -26,15 +26,12 @@ public class TrialShareQuerySchema { public static final String STUDY_TABLE = "StudyProperties"; public static final String STUDY_ACCESS_TABLE = "StudyAccess"; - public static final String STUDY_ASSAY_TABLE = "StudyAssay"; public static final String STUDY_CONDITION_TABLE = "StudyCondition"; public static final String STUDY_AGE_GROUP_TABLE = "StudyAgeGroup"; public static final String STUDY_PHASE_TABLE = "StudyPhase"; public static final String STUDY_THERAPEUTIC_AREA_TABLE = "StudyTherapeuticArea"; public static final String PUBLICATION_TABLE = "ManuscriptsAndAbstracts"; - public static final String PUBLICATION_ASSAY_TABLE = "PublicationAssay"; - public static final String PUBLICATION_CONDITION_TABLE = "PublicationCondition"; public static final String PUBLICATION_STUDY_TABLE = "PublicationStudy"; public static final String PUBLICATION_THERAPEUTIC_AREA_TABLE = "PublicationTherapeuticArea"; @@ -133,11 +130,6 @@ public TableInfo getStudyAccessTableInfo() return _listsSchema.getTable(TrialShareQuerySchema.STUDY_ACCESS_TABLE); } - public TableInfo getStudyAssayTableInfo() - { - return _listsSchema.getTable(TrialShareQuerySchema.STUDY_ASSAY_TABLE); - } - public TableInfo getStudyConditionTableInfo() { return _listsSchema.getTable(TrialShareQuerySchema.STUDY_CONDITION_TABLE); @@ -163,16 +155,6 @@ public TableInfo getPublicationsTableInfo() return _listsSchema.getTable(TrialShareQuerySchema.PUBLICATION_TABLE); } - public TableInfo getPublicationAssayTableInfo() - { - return _listsSchema.getTable(TrialShareQuerySchema.PUBLICATION_ASSAY_TABLE); - } - - public TableInfo getPublicationConditionTableInfo() - { - return _listsSchema.getTable(TrialShareQuerySchema.PUBLICATION_CONDITION_TABLE); - } - public TableInfo getPublicationStudyTableInfo() { return _listsSchema.getTable(TrialShareQuerySchema.PUBLICATION_STUDY_TABLE); diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java index 1d7b5d17..030dcc93 100644 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -118,6 +118,7 @@ public void refreshCube() { log("Refreshing cube"); _table.clickHeaderButtonByText("Refresh Cube"); + sleep(2000); // give time for the refresh to happen } private static class Locators diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java b/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java index 9457515c..d494ffb4 100644 --- a/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java +++ b/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java @@ -74,17 +74,6 @@ public int getPublicationCount(String title, Boolean navigateToList) return table.getDataRowCount(); } - public int getPublicationConditionCount(String title, Boolean navigateToList) - { - if (navigateToList) - { - clickAndWait(Locator.linkWithText("PublicationCondition")); - } - DataRegionTable table = new DataRegionTable("query", _test); - table.setFilter("PublicationId", "Equals", title); - return table.getDataRowCount(); - } - public int getPublicationTherapeuticAreaCount(String title, Boolean navigateToList) { if (navigateToList) diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index 5396b612..f53721e3 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -454,8 +454,7 @@ public void testInsertAndRefresh() editPage.submit(); goToProjectHome(); // there should be no error alert after inserting but before refreshing - DataFinderPage finder = new DataFinderPage(this, false); - finder.navigateToPublications(); + DataFinderPage finder = goDirectlyToDataFinderPage(getCurrentContainerPath(), false); finder.search((String) initialFields.get(PublicationEditPage.TITLE)); List dataCards = finder.getDataCards(); @@ -465,7 +464,7 @@ public void testInsertAndRefresh() manageData.refreshCube(); goToProjectHome(); - finder.navigateToPublications(); + finder = goDirectlyToDataFinderPage(getCurrentContainerPath(), false); finder.search((String) initialFields.get(PublicationEditPage.TITLE)); dataCards = finder.getDataCards(); From 944b4e738c5e79358e1a25a55151b35381bdb063 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 5 Jul 2016 19:49:33 -0700 Subject: [PATCH 344/587] Spec 26558: add some better timing for actions in the tests and update study ids and short names to match the tests --- .../test/pages/trialshare/ManageDataPage.java | 2 +- .../test/tests/trialshare/ManageStudiesTest.java | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java index 030dcc93..355baca1 100644 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -73,7 +73,7 @@ public boolean itemIsInList(String keyValue) public void goToInsertNew() { log("Going to insert new " + _objectType); - _table.clickHeaderButtonByText("Insert New"); + doAndWaitForPageToLoad(() -> _table.clickHeaderButtonByText("Insert New")); } public int getRowIndex(String value) diff --git a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java index 56b77476..f95d4166 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java @@ -288,10 +288,6 @@ public void testUpdateStudy() // N.B. leaving out external URL description and Description fields because // not sure how to attach to the iframe updatedFields.put(StudyEditPage.INVESTIGATOR, "investigate updated"); - updatedFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Adult"}); - updatedFields.put(StudyEditPage.PHASES, new String[]{"Phase 2"}); - updatedFields.put(StudyEditPage.CONDITIONS, new String[]{"Asthma"}); - updatedFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"Allergy"}); manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); @@ -312,8 +308,8 @@ public void testInsertAndDelete() int count = manageData.getCount(); Map initialFields = new HashMap<>(); // add the count so multiple runs of this test have distinct titles - initialFields.put(StudyEditPage.SHORT_NAME, "TUS" + count); - initialFields.put(StudyEditPage.STUDY_ID, "TUS_ID" + count); + initialFields.put(StudyEditPage.SHORT_NAME, "TIAD" + count); + initialFields.put(StudyEditPage.STUDY_ID, "TIAD_ID" + count); initialFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count); initialFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Adult"}); initialFields.put(StudyEditPage.PHASES, new String[]{"Phase 2"}); @@ -352,8 +348,8 @@ public void testInsertAndRefresh() ManageDataPage manageData = new ManageDataPage(this, _objectType); int count = manageData.getCount(); - String shortName = "TUS" + count; - String studyId = "TUS_ID" + count; + String shortName = "TIAR" + count; + String studyId = "TIAR_ID" + count; createStudy(shortName, false); Map initialFields = new HashMap<>(); @@ -375,7 +371,7 @@ public void testInsertAndRefresh() studyListHelper.addStudyAccessEntry(shortName, "/" + PROJECT_NAME + "/" + shortName , "Public", true); goToProjectHome(); // there should be no error alert after inserting but before refreshing - DataFinderPage finder = new DataFinderPage(this, true); + DataFinderPage finder = goDirectlyToDataFinderPage(getCurrentContainerPath(), true); finder.search(studyId); List dataCards = finder.getDataCards(); @@ -385,7 +381,7 @@ public void testInsertAndRefresh() goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); manageData.refreshCube(); - goToProjectHome(); + doAndWaitForPageSignal(this::goToProjectHome, finder.getCountSignal()); finder.search(studyId); dataCards = finder.getDataCards(); From dc3f31b50239ec77098f9fdf9912c2f4d2a061f7 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 5 Jul 2016 20:12:08 -0700 Subject: [PATCH 345/587] Spec 26558: change search terms to not use assay names; remove tests for unimplemented features --- .../trialshare/TrialShareDataFinderTest.java | 183 +----------------- 1 file changed, 2 insertions(+), 181 deletions(-) diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 283670bd..45f7d162 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -20,7 +20,6 @@ import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; import org.labkey.test.Locator; @@ -28,17 +27,14 @@ import org.labkey.test.TestTimeoutException; import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Git; -import org.labkey.test.components.study.StudyOverviewWebPart; import org.labkey.test.components.trialshare.PublicationPanel; import org.labkey.test.components.trialshare.StudySummaryWindow; import org.labkey.test.pages.PermissionsEditor; -import org.labkey.test.pages.study.ManageParticipantGroupsPage; import org.labkey.test.pages.trialshare.DataFinderPage; import org.labkey.test.pages.trialshare.PublicationsListHelper; import org.labkey.test.pages.trialshare.StudiesListHelper; import org.labkey.test.util.APIContainerHelper; import org.labkey.test.util.AbstractContainerHelper; -import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.LogMethod; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.ReadOnlyTest; @@ -354,7 +350,6 @@ public void testStudySearch() testSearchTerm(finder, DataFinderPage.Dimension.STUDIES, "Empty", "", 8); testSearchTerm(finder, DataFinderPage.Dimension.STUDIES, "Title", "System", 2); testSearchTerm(finder, DataFinderPage.Dimension.STUDIES, "Multiple Terms", "Tolerant Kidney Transplant", 7); - testSearchTerm(finder, DataFinderPage.Dimension.STUDIES, "Assay Facet", "Array", 2); } @@ -364,11 +359,11 @@ public void testStudySearchPermissions() impersonate(PUBLIC_READER); DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); - finder.search("Array"); + finder.search("transplant"); List studyCards = finder.getDataCards(); - assertEquals("Wrong number of studies after search", 1, studyCards.size()); + assertEquals("Wrong number of studies after search", 2, studyCards.size()); assertEquals("Wrong study card available", "Shapiro", studyCards.get(0).getStudyShortName()); assertCountsSynced(finder, DataFinderPage.Dimension.STUDIES); @@ -439,30 +434,6 @@ public void testStudySummaryWindow() summaryWindow.closeWindow(); } - @Test - @Ignore("Participant counts are not expected to match for this data") - public void testStudyParticipantCounts() - { - Map finderParticipantCounts = new HashMap<>(); - Map studyParticipantCounts = new HashMap<>(); - - DataFinderPage finder = new DataFinderPage(this, true); - for (String studyShortName : loadedStudies) - { - finder.search(studyShortName); - finderParticipantCounts.put(studyShortName, finder.getSummaryCounts().get(DataFinderPage.Dimension.SUBJECTS)); - } - - for (String studyShortName : loadedStudies) - { - clickFolder(studyShortName); - StudyOverviewWebPart studyOverview = new StudyOverviewWebPart(this); - studyParticipantCounts.put(studyShortName, studyOverview.getParticipantCount()); - } - - assertEquals("Participant counts in study finder don't match LabKey studies", finderParticipantCounts, studyParticipantCounts); - } - @Test public void testGoToStudyMenu() { @@ -496,156 +467,6 @@ public void testGoToStudyNoMenuForPublicReader() stopImpersonating(); } - @Test - @Ignore("Session storage not yet in use") - public void testNavigationDoesNotRemoveFinderFilter() - { - DataFinderPage finder = new DataFinderPage(this, true); - DataFinderPage.FacetGrid facetsGrid = finder.getFacetsGrid(); - facetsGrid.toggleFacet(DataFinderPage.Dimension.THERAPEUTIC_AREA, "Allergy"); - - Map> selections = finder.getFacetsGrid().getSelectedMembers(); - clickTab("Manage"); - clickTab("Overview"); - assertEquals("Navigation cleared study finder filter", selections, finder.getFacetsGrid().getSelectedMembers()); - } - - @Test - @Ignore("Session storage not yet in use") - public void testRefreshDoesNotRemoveFinderFilter() - { - DataFinderPage finder = new DataFinderPage(this, true); - DataFinderPage.FacetGrid facetsGrid = finder.getFacetsGrid(); - facetsGrid.toggleFacet(DataFinderPage.Dimension.THERAPEUTIC_AREA, "Allergy"); - - Map> selections = finder.getFacetsGrid().getSelectedMembers(); - refresh(); - assertEquals("'Refresh' cleared study finder filter", selections, finder.getFacetsGrid().getSelectedMembers()); - } - - @Test - @Ignore("Session storage not yet in use") - public void testBackDoesNotRemoveFinderFilter() - { - DataFinderPage finder = new DataFinderPage(this, true); - DataFinderPage.FacetGrid facetGrid = finder.getFacetsGrid(); - facetGrid.toggleFacet(DataFinderPage.Dimension.THERAPEUTIC_AREA, "Allergy"); - - Map> selections = finder.getFacetsGrid().getSelectedMembers(); - clickTab("Manage"); - goBack(); - assertEquals("'Back' cleared study finder filter", selections, finder.getFacetsGrid().getSelectedMembers()); - } - - @Test - @Ignore("Session storage not yet in use") - public void testFinderWebPartAndActionShareFilter() - { - DataFinderPage finder = new DataFinderPage(this, true); - DataFinderPage.FacetGrid facetGrid = finder.getFacetsGrid(); - facetGrid.toggleFacet(DataFinderPage.Dimension.THERAPEUTIC_AREA, "Allergy"); - - Map> selections = finder.getFacetsGrid().getSelectedMembers(); - goDirectlyToDataFinderPage(getProjectName(), true); - assertEquals("WebPart study finder filter didn't get applied", selections, finder.getFacetsGrid().getSelectedMembers()); - } - - - - @Test - @Ignore("Not yet implemented") - public void testGroupSaveAndLoad() - { - DataFinderPage finder = new DataFinderPage(this, true); - DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); - facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"); - - finder.clearAllFilters(); - assertEquals("Group label not as expected", "Unsaved Group", finder.getGroupLabel()); - - Map> selections = new HashMap<>(); - DataFinderPage.FacetGrid facetGrid = finder.getFacetsGrid(); - facetGrid.toggleFacet(DataFinderPage.Dimension.AGE_GROUP, "Adult"); - facetGrid.toggleFacet(DataFinderPage.Dimension.CONDITION, "Acute Kidney Injury"); - - selections.put(DataFinderPage.Dimension.AGE_GROUP, Collections.singletonList("Adult")); - selections.put(DataFinderPage.Dimension.CONDITION, Collections.singletonList("Acute Kidney Injury")); - - Map summaryCounts = finder.getSummaryCounts(); - - // click on "Save" menu and assert "Save" is not active then assert "Save as" is active - DataFinderPage.GroupMenu saveMenu = finder.getMenu(DataFinderPage.Locators.saveMenu); - saveMenu.toggleMenu(); - Assert.assertEquals("Unexpected number of inactive options", 1, saveMenu.getInactiveOptions().size()); - Assert.assertTrue("'Save' option is not an inactive menu option but should be", saveMenu.getInactiveOptions().contains("Save")); - - Assert.assertEquals("Unexpected number of active options", 1, saveMenu.getActiveOptions().size()); - Assert.assertTrue("'Save as' option is not active but should be", saveMenu.getActiveOptions().contains("Save As")); - - String filterName = "testGroupSaveAndLoad" + System.currentTimeMillis(); - saveMenu.chooseOption("Save As", false); - // assert that popup has the proper number of Selected Studies and Subjects - DataRegionTable subjectData = new DataRegionTable("demoDataRegion", this); - Assert.assertEquals("Subject counts on save group window differ from those on data finder", summaryCounts.get(DataFinderPage.Dimension.SUBJECTS).intValue(), subjectData.getDataRowCount()); - finder.saveGroup(filterName); - - assertEquals("Group label not as expected", "Saved group: " + filterName, finder.getGroupLabel()); - - finder.clearAllFilters(); - //load group with test name - DataFinderPage.GroupMenu loadMenu = finder.getMenu(DataFinderPage.Locators.loadMenu); - loadMenu.toggleMenu(); - Assert.assertTrue("Saved group does not appear in load menu", loadMenu.getActiveOptions().contains(filterName)); - loadMenu.chooseOption(filterName, false); - assertEquals("Group label not as expected", "Saved group: " + filterName, finder.getGroupLabel()); - - // assert the selected items are the same and the counts are the same as before. - assertEquals("Summary counts not as expected after load", summaryCounts, finder.getSummaryCounts()); - assertEquals("Selected items not as expected after load", selections, finder.getFacetsGrid().getSelectedMembers()); - // assert that "Save" is now active in the menu - saveMenu = finder.getMenu(DataFinderPage.Locators.saveMenu); - saveMenu.toggleMenu(); - Assert.assertTrue("'Save' option is not an active menu option but should be", saveMenu.getActiveOptions().contains("Save")); - saveMenu.toggleMenu(); // close the menu - - // Choose another dimension and save the summary counts - log("selecting an Assay filter"); - facetGrid.toggleFacet(DataFinderPage.Dimension.ASSAY, "FCM"); - selections.put(DataFinderPage.Dimension.ASSAY, Collections.singletonList("FCM")); - summaryCounts = finder.getSummaryCounts(); - log("Selections is now " + selections); - assertEquals("Selected items not as expected after assay selection", selections, finder.getFacetsGrid().getSelectedMembers()); - - // Save the filter - saveMenu = finder.getMenu(DataFinderPage.Locators.saveMenu); - saveMenu.toggleMenu(); - saveMenu.chooseOption("Save", true); - sleep(1000); // Hack! This seems necessary to give time for saving the filter before loading it again. Waiting for signals doesn't seem to work... - - finder.clearAllFilters(); - - // Load the filter - loadMenu = finder.getMenu(DataFinderPage.Locators.loadMenu); - loadMenu.toggleMenu(); - Assert.assertTrue("Saved filter does not appear in menu", loadMenu.getActiveOptions().contains(filterName)); - loadMenu.chooseOption(filterName, true); - - // assert that the selections are as expected. - assertEquals("Summary counts not as expected after load", summaryCounts, finder.getSummaryCounts()); - assertEquals("Selected items not as expected after load", selections, finder.getFacetsGrid().getSelectedMembers()); - - // manage group and delete the group that was created - DataFinderPage.GroupMenu manageMenu = finder.getMenu(DataFinderPage.Locators.manageMenu); - manageMenu.toggleMenu(); - manageMenu.chooseOption("Manage Groups", false); - waitForText("Manage Participant Groups"); - ManageParticipantGroupsPage managePage = new ManageParticipantGroupsPage(this); - managePage.selectGroup(filterName); - Assert.assertTrue("Delete should be enabled for group created through data finder", managePage.isDeleteEnabled()); - Assert.assertFalse("Edit should not be enabled for group created through data finder", managePage.isEditEnabled()); - managePage.deleteGroup(filterName); - } - @Test public void testSwitchBetweenStudyAndPublication() { From b41be4bf72536923f66b055cd789e3db87418443 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 6 Jul 2016 06:57:56 -0700 Subject: [PATCH 346/587] Spec 26558: make field name structures not static --- .../pages/trialshare/CubeObjectEditPage.java | 45 +++++++++++-------- .../pages/trialshare/PublicationEditPage.java | 24 ++++++++++ .../test/pages/trialshare/StudyEditPage.java | 26 +++++++++++ .../tests/trialshare/DataFinderTestBase.java | 14 +++--- .../trialshare/ManagePublicationsTest.java | 2 +- 5 files changed, 84 insertions(+), 27 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java index bbad2ab4..e211e8f7 100644 --- a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java @@ -9,26 +9,18 @@ import org.openqa.selenium.WebDriver; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Created by susanh on 6/30/16. */ -public class CubeObjectEditPage extends LabKeyPage +public abstract class CubeObjectEditPage extends LabKeyPage { public static final String NOT_EMPTY_VALUE = "NOT EMPTY VALUE"; public static final String EMPTY_VALUE = "EMPTY VALUE"; - public static final Map DROPDOWN_FIELD_NAMES = new HashMap<>(); - - public static final Map MULTI_SELECT_FIELD_NAMES = new HashMap<>(); - - - public static final Set FIELD_NAMES = new HashSet<>(); - - public CubeObjectEditPage(WebDriver driver) + CubeObjectEditPage(WebDriver driver) { super(driver); } @@ -39,13 +31,28 @@ public void setTextFormValue(String key, String value) } public void setTextFormValue(String key, String value, Boolean waitForSubmit) + { + setTextFormValue(key, value, waitForSubmit, false); + } + + public void setTextFormValue(String key, String value, Boolean waitForSubmit, Boolean submitIsDisabled) { Locator fieldLocator = Locator.name(key); setFormElement(fieldLocator, value); if (waitForSubmit) - waitForElement(Locators.submitButton); + { + if (submitIsDisabled) + waitForElement(Locators.submitButton); + else + waitForElement(Locators.disabledSubmitButton); + } + } + public abstract Map getDropdownFieldNames(); + public abstract Map getMultiSelectFieldNames(); + public abstract Set getFieldNames(); + public void selectMenuItem(String label, String value) { _ext4Helper.selectComboBoxItem(label, value); @@ -54,10 +61,10 @@ public void selectMenuItem(String label, String value) public void setFormField(String key, Object value) { log("Setting field " + key + " to " + (value instanceof String[] ? StringUtils.join((String []) value, "; ") : value)); - if (DROPDOWN_FIELD_NAMES.keySet().contains(key)) - _ext4Helper.selectComboBoxItem(DROPDOWN_FIELD_NAMES.get(key), (String) value); - else if (MULTI_SELECT_FIELD_NAMES.keySet().contains(key)) - multiSelectComboBoxItem(MULTI_SELECT_FIELD_NAMES.get(key), (String[]) value); + if (getDropdownFieldNames().keySet().contains(key)) + _ext4Helper.selectComboBoxItem(getDropdownFieldNames().get(key), (String) value); + else if (getMultiSelectFieldNames().keySet().contains(key)) + multiSelectComboBoxItem(getMultiSelectFieldNames().get(key), (String[]) value); else setTextFormValue(key, (String) value); } @@ -92,7 +99,7 @@ public void multiSelectComboBoxItem(String label, @LoggedParam String... selecti public void setFormFields(Map fieldMap) { - log("Setting form fields"); + log("Setting form fields for keys: " + StringUtils.join(fieldMap.keySet(), ", ")); for (String key : fieldMap.keySet()) { setFormField(key, fieldMap.get(key)); @@ -102,7 +109,7 @@ public void setFormFields(Map fieldMap) public Map getFormValues() { Map formValues = new HashMap<>(); - for (String field : FIELD_NAMES) + for (String field : getFieldNames()) { Locator fieldLocator = Locator.name(field); formValues.put(field, getFormElement(fieldLocator)); @@ -152,13 +159,13 @@ public boolean isSubmitEnabled() public void cancel() { - log("Cancelling publication edit"); + log("Cancelling edit"); Locators.cancelButton.findElement(getDriver()).click(); } public void submit() { - log("Submitting publication edit form"); + log("Submitting edit form"); click(Locators.submitButton); waitForElement(Locators.ackSubmit); clickAndWait(Locators.ackSubmit, WAIT_FOR_PAGE); diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java b/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java index ffee052c..d73106f2 100644 --- a/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java @@ -2,7 +2,10 @@ import org.openqa.selenium.WebDriver; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; /** * Created by susanh on 6/30/16. @@ -33,6 +36,7 @@ public class PublicationEditPage extends CubeObjectEditPage public static final String LINK3 ="link3"; public static final String DESCRIPTION3="description3"; + private static final Map DROPDOWN_FIELD_NAMES = new HashMap<>(); static { DROPDOWN_FIELD_NAMES.put(PUBLICATION_TYPE, "Publication Type *:"); @@ -42,12 +46,14 @@ public class PublicationEditPage extends CubeObjectEditPage DROPDOWN_FIELD_NAMES.put(PERMISSIONS_CONTAINER, "Permissions Container:"); } + private static final Map MULTI_SELECT_FIELD_NAMES = new HashMap<>(); static { MULTI_SELECT_FIELD_NAMES.put(STUDIES, "Studies:"); MULTI_SELECT_FIELD_NAMES.put(THERAPEUTIC_AREAS, "Therapeutic Areas:"); } + private static final Set FIELD_NAMES = new HashSet<>(); static { FIELD_NAMES.add(TITLE); @@ -80,6 +86,24 @@ public PublicationEditPage(WebDriver driver) super(driver); } + @Override + public Map getDropdownFieldNames() + { + return DROPDOWN_FIELD_NAMES; + } + + @Override + public Map getMultiSelectFieldNames() + { + return MULTI_SELECT_FIELD_NAMES; + } + + @Override + public Set getFieldNames() + { + return FIELD_NAMES; + } + public void setFormFields(Map fieldMap, Boolean showOnDashboard) { setFormFields(fieldMap); diff --git a/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java b/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java index 9409659d..cdb62a12 100644 --- a/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java @@ -2,6 +2,11 @@ import org.openqa.selenium.WebDriver; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + /** * Created by susanh on 6/30/16. */ @@ -22,11 +27,13 @@ public class StudyEditPage extends CubeObjectEditPage public static final String CONDITIONS = "conditions"; public static final String THERAPEUTIC_AREAS = "therapeuticAreas"; + private static final Map DROPDOWN_FIELD_NAMES = new HashMap<>(); static { DROPDOWN_FIELD_NAMES.put(STUDY_TYPE, "Study Type:"); } + private static final Map MULTI_SELECT_FIELD_NAMES = new HashMap<>(); static { MULTI_SELECT_FIELD_NAMES.put(AGE_GROUPS, "Age Groups:"); @@ -35,6 +42,7 @@ public class StudyEditPage extends CubeObjectEditPage MULTI_SELECT_FIELD_NAMES.put(THERAPEUTIC_AREAS, "Therapeutic Areas:"); } + private static final Set FIELD_NAMES = new HashSet<>(); static { FIELD_NAMES.add(SHORT_NAME); @@ -57,4 +65,22 @@ public StudyEditPage(WebDriver driver) { super(driver); } + + @Override + public Map getDropdownFieldNames() + { + return DROPDOWN_FIELD_NAMES; + } + + @Override + public Map getMultiSelectFieldNames() + { + return MULTI_SELECT_FIELD_NAMES; + } + + @Override + public Set getFieldNames() + { + return FIELD_NAMES; + } } diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java index bac9abe3..8417d42a 100644 --- a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java +++ b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java @@ -48,16 +48,16 @@ public abstract class DataFinderTestBase extends BaseWebDriverTest static final String WEB_PART_NAME = "TrialShare Data Finder"; static final String OPERATIONAL_STUDY_NAME = "DataFinderTestOperationalStudy"; static final String PUBLIC_STUDY_NAME = "DataFinderTestPublicStudy"; - static final String EMAIL_EXTENSION = "@datafinder.test"; - static final String PUBLIC_READER_DISPLAY_NAME = "public_reader"; + private static final String EMAIL_EXTENSION = "@datafinder.test"; + private static final String PUBLIC_READER_DISPLAY_NAME = "public_reader"; static final String PUBLIC_READER = PUBLIC_READER_DISPLAY_NAME + EMAIL_EXTENSION; - static final String CASALE_READER_DISPLAY_NAME = "casale_reader"; + private static final String CASALE_READER_DISPLAY_NAME = "casale_reader"; static final String CASALE_READER = CASALE_READER_DISPLAY_NAME + EMAIL_EXTENSION; - static final String WISPR_READER_DISPLAY_NAME = "wispr_reader"; + private static final String WISPR_READER_DISPLAY_NAME = "wispr_reader"; static final String WISPR_READER = WISPR_READER_DISPLAY_NAME + EMAIL_EXTENSION; - static final String CONTROLLER = "trialshare"; - static final String ACTION = "dataFinder"; - static File dataListArchive = TestFileUtils.getSampleData("DataFinder.lists.zip"); + private static final String CONTROLLER = "trialshare"; + private static final String ACTION = "dataFinder"; + private static File dataListArchive = TestFileUtils.getSampleData("DataFinder.lists.zip"); public enum CubeObjectType { diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index f53721e3..762c2215 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -161,7 +161,7 @@ public void testInvalidPMCID() ManageDataPage manageData = new ManageDataPage(this, _objectType); manageData.goToEditRecord("Quality assessments of un-gated flow cytometry FCS files in a clinical trial setting"); PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - editPage.setTextFormValue("PMCID", "invalid"); + editPage.setTextFormValue("PMCID", "invalid", true, true); Assert.assertFalse("Submit should be disabled when invalid PMCID is input", editPage.isSubmitEnabled()); } From edb5ad708c776bbfd19a15f1e2b1b4d7487bccec Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 6 Jul 2016 08:37:07 -0700 Subject: [PATCH 347/587] Spec 26558: wait for the proper state of the submit button --- .../org/labkey/test/pages/trialshare/CubeObjectEditPage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java index e211e8f7..af5d2c5a 100644 --- a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java @@ -42,9 +42,9 @@ public void setTextFormValue(String key, String value, Boolean waitForSubmit, Bo if (waitForSubmit) { if (submitIsDisabled) - waitForElement(Locators.submitButton); - else waitForElement(Locators.disabledSubmitButton); + else + waitForElement(Locators.submitButton); } } From 438354fc36654e1dd5b500144322163a7a29b794 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 6 Jul 2016 08:56:27 -0700 Subject: [PATCH 348/587] Spec 26558: remove unused method --- .../src/org/labkey/test/pages/trialshare/ManageDataPage.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java index 355baca1..90470c72 100644 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -65,11 +65,6 @@ public void goToManagePublications() doAndWaitForPageToLoad(() -> Locators.managePublicationsLink.findElement(getDriver()).click()); } - public boolean itemIsInList(String keyValue) - { - return _table.getRow(_objectType.getKeyField(), keyValue) >= 0; - } - public void goToInsertNew() { log("Going to insert new " + _objectType); From 0d3be69a387059ae0d695f4711a72cf6a0394677 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 6 Jul 2016 08:57:04 -0700 Subject: [PATCH 349/587] Spec 26558: additional logging to figure out why TeamCity test fails for multi-value editing --- .../test/pages/trialshare/CubeObjectEditPage.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java index af5d2c5a..43dd50b3 100644 --- a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java @@ -67,6 +67,7 @@ else if (getMultiSelectFieldNames().keySet().contains(key)) multiSelectComboBoxItem(getMultiSelectFieldNames().get(key), (String[]) value); else setTextFormValue(key, (String) value); + log("Field " + key + " new value is " + getFormValue(key)); } // the similar method in ext4Helper is looking for a property that does not exist to @@ -106,13 +107,18 @@ public void setFormFields(Map fieldMap) } } + public String getFormValue(String field) + { + Locator fieldLocator = Locator.name(field); + return getFormElement(fieldLocator); + } + public Map getFormValues() { Map formValues = new HashMap<>(); for (String field : getFieldNames()) { - Locator fieldLocator = Locator.name(field); - formValues.put(field, getFormElement(fieldLocator)); + formValues.put(field, getFormValue(field)); } return formValues; } From 76900eaaf41b6958687e34946e553b1f1b2754d0 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 6 Jul 2016 13:25:25 -0700 Subject: [PATCH 350/587] Spec 26558: additional logging to figure out why TeamCity test fails for multi-value editing --- .../org/labkey/test/pages/trialshare/CubeObjectEditPage.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java index 43dd50b3..68c30ae1 100644 --- a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java @@ -76,11 +76,14 @@ public void multiSelectComboBoxItem(String label, @LoggedParam String... selecti { Locator.XPathLocator comboBox = Ext4Helper.Locators.formItemWithLabel(label); _ext4Helper.openComboList(comboBox); + log("Hack-Nap while combo box gets filled. Maybe?"); + sleep(2000); try { for (String selection : selections) { + log("Selecting " + selection); _ext4Helper.selectItemFromOpenComboList(selection, Ext4Helper.TextMatchTechnique.EXACT); } } @@ -88,10 +91,12 @@ public void multiSelectComboBoxItem(String label, @LoggedParam String... selecti { for (String selection : selections) { + log("Selecting " + selection); _ext4Helper.selectItemFromOpenComboList(selection, Ext4Helper.TextMatchTechnique.EXACT); } } + log("Closing combo box " + label); Locator arrowTrigger = comboBox.append("//div[contains(@class,'arrow')]"); arrowTrigger.findElement(this.getDriver()).click(); } From 9bf2fba3d615d53551848a8a163d40da38fa4c36 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 8 Jul 2016 07:28:57 -0700 Subject: [PATCH 351/587] Issue 27072: fix SQL Server grammar for use of "Key" in join statement. --- .../labkey/trialshare/TrialShareManager.java | 33 ++++++++++--------- .../query/TrialShareQuerySchema.java | 16 +++++++-- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 90bbd522..23ce419b 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -232,10 +232,10 @@ public void updatePublication(User user, Container container, PublicationEditBea // update the many-to-one data // first get rid of the current values for this publication. Then add the new data - schema.getPublicationStudyTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationStudyTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); + schema.getPublicationStudyTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationStudyTableInfo(), filter), null, null); addJoinTableData(schema.getPublicationStudyTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId(), TrialShareQuerySchema.STUDY_ID_FIELD, publication.getStudyIds(), user, container); - schema.getPublicationTherapeuticAreaTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationTherapeuticAreaTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); + schema.getPublicationTherapeuticAreaTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getPublicationTherapeuticAreaTableInfo(), filter), null, null); addJoinTableData(schema.getPublicationTherapeuticAreaTableInfo(), TrialShareQuerySchema.PUBLICATION_ID_FIELD, publication.getId(), TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, publication.getTherapeuticAreas(), user, container); transaction.commit(); } @@ -271,8 +271,8 @@ public void deletePublications(@NotNull User user, @NotNull Container container, { TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); - deleteJoinTableData(schema.getPublicationStudyTableInfo(), "Key", user, container, idFilter); - deleteJoinTableData(schema.getPublicationTherapeuticAreaTableInfo(), "Key", user, container, idFilter); + deleteJoinTableData(schema.getPublicationStudyTableInfo(), user, container, idFilter); + deleteJoinTableData(schema.getPublicationTherapeuticAreaTableInfo(), user, container, idFilter); List> pkMaps = new ArrayList<>(); for (Integer id : integerIds) @@ -318,16 +318,16 @@ public void updateStudy(@NotNull User user, @NotNull Container container, StudyE SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(TrialShareQuerySchema.STUDY_ID_FIELD), studyId); // update the many-to-one data. First get rid of the current values for the study - schema.getStudyConditionTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyConditionTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); + schema.getStudyConditionTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyConditionTableInfo(), filter), null, null); addJoinTableData(schema.getStudyConditionTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.CONDITION_FIELD, study.getConditions(), user, container); - schema.getStudyAgeGroupTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyAgeGroupTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); + schema.getStudyAgeGroupTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyAgeGroupTableInfo(), filter), null, null); addJoinTableData(schema.getStudyAgeGroupTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.AGE_GROUP_FIELD, study.getAgeGroups(), user, container); - schema.getStudyPhaseTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyPhaseTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); + schema.getStudyPhaseTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyPhaseTableInfo(), filter), null, null); addJoinTableData(schema.getStudyPhaseTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.PHASE_FIELD, study.getPhases(), user, container); - schema.getStudyTherapeuticAreaTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyTherapeuticAreaTableInfo(), TrialShareQuerySchema.KEY_FIELD, filter), null, null); + schema.getStudyTherapeuticAreaTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyTherapeuticAreaTableInfo(), filter), null, null); addJoinTableData(schema.getStudyTherapeuticAreaTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, study.getTherapeuticAreas(), user, container); transaction.commit(); @@ -392,11 +392,11 @@ public void deleteStudies(@NotNull User user, @NotNull Container container, Set< { TrialShareQuerySchema schema = new TrialShareQuerySchema(user, container); - deleteJoinTableData(schema.getStudyPhaseTableInfo(), "Key", user, container, idFilter); - deleteJoinTableData(schema.getStudyAgeGroupTableInfo(), "Key", user, container, idFilter); - deleteJoinTableData(schema.getStudyConditionTableInfo(), "Key", user, container, idFilter); - deleteJoinTableData(schema.getStudyTherapeuticAreaTableInfo(), "Key", user, container, idFilter); - deleteJoinTableData(schema.getStudyAccessTableInfo(), "Key", user, container, idFilter); + deleteJoinTableData(schema.getStudyPhaseTableInfo(), user, container, idFilter); + deleteJoinTableData(schema.getStudyAgeGroupTableInfo(), user, container, idFilter); + deleteJoinTableData(schema.getStudyConditionTableInfo(), user, container, idFilter); + deleteJoinTableData(schema.getStudyTherapeuticAreaTableInfo(), user, container, idFilter); + deleteJoinTableData(schema.getStudyAccessTableInfo(), user, container, idFilter); List> pkMaps = new ArrayList<>(); for (String id : ids) @@ -418,8 +418,9 @@ public void deleteStudies(@NotNull User user, @NotNull Container container, Set< } - private List> getJoinTableIds(@NotNull TableInfo tableInfo, @NotNull String keyName, SimpleFilter objectIdFilter) + private List> getJoinTableIds(@NotNull TableInfo tableInfo, SimpleFilter objectIdFilter) { + String keyName = tableInfo.getPkColumnNames().get(0); // select the keys of the rows that have the object ids selected by the object filter List keys = new TableSelector(tableInfo, Collections.singleton(keyName), objectIdFilter, null).getArrayList(Integer.class); @@ -434,9 +435,9 @@ private List> getJoinTableIds(@NotNull TableInfo tableInfo, } - private void deleteJoinTableData(@NotNull TableInfo tableInfo, @NotNull String keyName, @NotNull User user, @NotNull Container container, SimpleFilter objectIdFilter) throws SQLException, QueryUpdateServiceException, BatchValidationException, InvalidKeyException + private void deleteJoinTableData(@NotNull TableInfo tableInfo, @NotNull User user, @NotNull Container container, SimpleFilter objectIdFilter) throws SQLException, QueryUpdateServiceException, BatchValidationException, InvalidKeyException { - tableInfo.getUpdateService().deleteRows(user, container, getJoinTableIds(tableInfo, keyName, objectIdFilter), null, null); + tableInfo.getUpdateService().deleteRows(user, container, getJoinTableIds(tableInfo, objectIdFilter), null, null); } private void addJoinTableData(TableInfo tableInfo, String idField, Object id, String dataField, List dataValues, User user, Container container) throws SQLException, QueryUpdateServiceException, BatchValidationException, DuplicateKeyException diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index 7d8794c1..dfa6e56e 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -36,7 +36,6 @@ public class TrialShareQuerySchema public static final String PUBLICATION_THERAPEUTIC_AREA_TABLE = "PublicationTherapeuticArea"; public static final String PUBLICATION_KEY_FIELD = "Key"; // this is the name of the key field in the publication table itself - public static final String KEY_FIELD = "Key"; public static final String PUBLICATION_ID_FIELD = "PublicationId"; public static final String CONDITION_FIELD = "Condition"; public static final String STUDY_ID_FIELD = "StudyId"; @@ -178,13 +177,24 @@ public List getPublicationStudies(Integer id) return new SqlSelector(getSchema().getDbSchema(), sql).getArrayList(StudyBean.class); } + + // This method is necessary because "Key" is reserved in SQLServer but not in Postgres and + // quotes around fields are not well handled in Postgres. + private String getPublicationsKey() + { + String keyField = getPublicationsTableInfo().getPkColumnNames().get(0); + if (getPublicationsTableInfo().getSqlDialect().isSqlServer()) + keyField = "\"" + keyField + "\""; + return keyField; + } + public List getStudyPublications() { SQLFragment sql = new SQLFragment("SELECT pub.*, ps.StudyId FROM " ); sql.append(getPublicationStudyTableInfo(), "ps"); sql.append(" LEFT JOIN "); sql.append(getPublicationsTableInfo(), "pub"); - sql.append(" ON ps.PublicationId = pub.Key "); + sql.append(" ON ps.PublicationId = pub.").append(getPublicationsKey()); return new SqlSelector(getSchema().getDbSchema(), sql).getArrayList(StudyPublicationBean.class); } @@ -195,7 +205,7 @@ public List getStudyPublications(String studyId, @Nullable sql.append(getPublicationsTableInfo(), "pub"); sql.append(" LEFT JOIN "); sql.append(getPublicationStudyTableInfo(), "ps"); - sql.append(" ON ps.PublicationId = pub.Key "); + sql.append(" ON ps.PublicationId = pub.").append(getPublicationsKey()); sql.append("WHERE ps.StudyId = ? "); sql.add(studyId); if (publicationType != null) From 87a508cb1d6a4931a090ca84eeb52f0cd708fb3c Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 8 Jul 2016 07:42:33 -0700 Subject: [PATCH 352/587] Spec 26558: add wait for alert instead of just accepting it --- test/src/org/labkey/test/pages/trialshare/ManageDataPage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java index 90470c72..a93b0f01 100644 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -90,7 +90,7 @@ public void deleteRecord(String keyValue) Integer rowIndex = getRowIndex(keyValue); _table.checkCheckbox(rowIndex); _table.clickHeaderButtonByText("Delete"); - acceptAlert(); + waitForAlert("Are you sure you want to delete the selected row?", 500); } public void showDetails(String keyValue) From cf06991af5dc8879faf9de578a846f4c21da3f2a Mon Sep 17 00:00:00 2001 From: labkey-adam Date: Sat, 9 Jul 2016 07:14:28 -0700 Subject: [PATCH 353/587] Remove @RequiresPermissionClass Make JspBase.getClientDependencies() final Remove collections-generic --- src/org/labkey/trialshare/view/dataFinder.jsp | 9 +++------ src/org/labkey/trialshare/view/studyDetail.jsp | 11 ++++------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/org/labkey/trialshare/view/dataFinder.jsp b/src/org/labkey/trialshare/view/dataFinder.jsp index a245e3c9..de748123 100644 --- a/src/org/labkey/trialshare/view/dataFinder.jsp +++ b/src/org/labkey/trialshare/view/dataFinder.jsp @@ -18,17 +18,14 @@ <%@ page import="com.fasterxml.jackson.databind.ObjectMapper" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> +<%@ page import="org.labkey.api.view.template.ClientDependencies" %> <%@ page import="org.labkey.trialshare.TrialShareController" %> -<%@ page import="org.labkey.api.view.template.ClientDependency" %> -<%@ page import="java.util.LinkedHashSet" %> <%@ page extends="org.labkey.api.jsp.JspBase"%> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%! - public LinkedHashSet getClientDependencies() + public void addClientDependencies(ClientDependencies dependencies) { - LinkedHashSet resources = new LinkedHashSet<>(); - resources.add(ClientDependency.fromPath("study/Finder/datafinder")); - return resources; + dependencies.add("study/Finder/datafinder"); } %> <% diff --git a/src/org/labkey/trialshare/view/studyDetail.jsp b/src/org/labkey/trialshare/view/studyDetail.jsp index da95af20..d33533ca 100644 --- a/src/org/labkey/trialshare/view/studyDetail.jsp +++ b/src/org/labkey/trialshare/view/studyDetail.jsp @@ -20,6 +20,7 @@ <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.api.view.ViewContext" %> +<%@ page import="org.labkey.api.view.template.ClientDependencies" %> <%@ page import="org.labkey.trialshare.TrialShareController" %> <%@ page import="org.labkey.trialshare.data.StudyBean" %> <%@ page import="org.labkey.trialshare.data.StudyPersonnelBean" %> @@ -28,16 +29,12 @@ <%@ page import="java.net.URL" %> <%@ page import="java.util.HashMap" %> <%@ page import="java.util.Map" %> -<%@ page import="org.labkey.api.view.template.ClientDependency" %> -<%@ page import="java.util.LinkedHashSet" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <%! - public LinkedHashSet getClientDependencies() + public void addClientDependencies(ClientDependencies dependencies) { - LinkedHashSet resources = new LinkedHashSet<>(); - resources.add(ClientDependency.fromPath("study/Finder/dataFinder.css")); - resources.add(ClientDependency.fromPath("study/Finder/trialShare.css")); - return resources; + dependencies.add("study/Finder/dataFinder.css"); + dependencies.add("study/Finder/trialShare.css"); } %> <% From d79501c2b03fa0acac7515cd67da3f7c681b926d Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 11 Jul 2016 07:35:35 -0700 Subject: [PATCH 354/587] Spec 26558: add more logging to try to track down problem with delete test --- test/src/org/labkey/test/pages/trialshare/ManageDataPage.java | 4 +++- .../org/labkey/test/tests/trialshare/ManageStudiesTest.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java index a93b0f01..8488f3ec 100644 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -88,9 +88,11 @@ public void deleteRecord(String keyValue) { log("Deleting record with key value '" + keyValue + "'"); Integer rowIndex = getRowIndex(keyValue); + Assert.assertTrue("Record with key '" + keyValue + "' not found", rowIndex >= 0); _table.checkCheckbox(rowIndex); _table.clickHeaderButtonByText("Delete"); - waitForAlert("Are you sure you want to delete the selected row?", 500); + log("Waiting for delete confirmation to show up"); + waitForAlert("Are you sure you want to delete the selected row?", 3000); } public void showDetails(String keyValue) diff --git a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java index f95d4166..b2286156 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java @@ -325,6 +325,7 @@ public void testInsertAndDelete() manageData.deleteRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); + log("Finished deleting record " + initialFields.get(StudyEditPage.STUDY_ID) + ". Going home"); StudiesListHelper listHelper = new StudiesListHelper(this); goToProjectHome(); From 3bebca5677fa630232a3f3cab74a7e989fe4c344 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 11 Jul 2016 09:17:47 -0700 Subject: [PATCH 355/587] Spec 26558: add missing space in SQL query --- src/org/labkey/trialshare/query/TrialShareQuerySchema.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index dfa6e56e..fbd3447f 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -206,7 +206,7 @@ public List getStudyPublications(String studyId, @Nullable sql.append(" LEFT JOIN "); sql.append(getPublicationStudyTableInfo(), "ps"); sql.append(" ON ps.PublicationId = pub.").append(getPublicationsKey()); - sql.append("WHERE ps.StudyId = ? "); + sql.append(" WHERE ps.StudyId = ? "); sql.add(studyId); if (publicationType != null) { From 0df665fbfc7237557f0916cafea9f030e87bc1aa Mon Sep 17 00:00:00 2001 From: labkey-ryans Date: Mon, 11 Jul 2016 11:56:49 -0700 Subject: [PATCH 356/587] extra logging and screenshots for troublesome test --- .../trialshare/ManagePublicationsTest.java | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index 762c2215..226dfcef 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -35,6 +35,7 @@ public class ManagePublicationsTest extends DataFinderTestBase private static final String PROJECT_NAME = "ManagePublicationTest Project"; private static final String OPERATIONAL_STUDY_SUBFOLDER_NAME = "/" + PROJECT_NAME + "/" + OPERATIONAL_STUDY_NAME; private static final String PUBLIC_STUDY_SUBFOLDER_NAME = "/" + PROJECT_NAME + "/" + PUBLIC_STUDY_NAME; + private static int _step = 0; private static final Map EXISTING_PUB_FIELDS = new HashMap<>(); static @@ -71,7 +72,6 @@ protected String getProjectName() return "ManagePublicationTest Project"; } - @Override protected void createStudies() { @@ -104,7 +104,8 @@ public void testManageDataLinkPermissions() switchToWindow(1); ManageDataPage manageData = new ManageDataPage(this, _objectType); Assert.assertTrue("No data shown for publication", manageData.getCount() > 0); - + getDriver().close(); + switchToMainWindow(); log("Impersonating user without insert permission"); goToProjectHome(); impersonate(PUBLIC_READER); @@ -398,11 +399,17 @@ public void testUpdatePublication() public void testInsertAndDelete() { goToProjectHome(); + screenshot("testInsertAndDeleteBegin"); StudiesListHelper studiesListHelper = new StudiesListHelper(this); studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + screenshot("testInsertAndDeleteSetFirstStudyContainer"); + log("testInsertAndDeleteSetFirstStudyContainer at " + getCurrentRelativeURL()); studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); - + screenshot("testInsertAndDeleteSetSecondStudyContainer"); + log("testInsertAndDeleteSetSecondStudyContainer at " + getCurrentRelativeURL()); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + screenshot("testInsertAndDeleteNavigateToManageDataPage"); + log("testInsertAndDeleteNavigateToManageDataPage at " + getCurrentRelativeURL()); ManageDataPage manageData = new ManageDataPage(this, _objectType); Map initialFields = new HashMap<>(); @@ -413,16 +420,24 @@ public void testInsertAndDelete() initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); manageData.goToInsertNew(); + screenshot("testInsertAndDeleteGoToInsertNewFromManageData"); + log("testInsertAndDeleteGoToInsertNewFromManageData at " + getCurrentRelativeURL()); PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); editPage.setFormFields(initialFields, false); + screenshot("testInsertAndDeleteSetEditPageFields"); + log("testInsertAndDeleteSetEditPageFields at " + getCurrentRelativeURL()); editPage.submit(); - + screenshot("testInsertAndDeleteSubmitEditPageFields"); + log("testInsertAndDeleteSubmitEditPageFields at " + getCurrentRelativeURL()); manageData.deleteRecord((String) initialFields.get(PublicationEditPage.TITLE)); - + screenshot("testInsertAndDeleteAfterDeleteRecord"); + log("testInsertAndDeleteAfterDeleteRecord at " + getCurrentRelativeURL()); PublicationsListHelper listHelper = new PublicationsListHelper(this); goToProjectHome(); + screenshot("testInsertAndDeleteGoToHome"); + log("testInsertAndDeleteGoToHome at " + getCurrentRelativeURL()); Assert.assertEquals("Found deleted publication", 0, listHelper.getPublicationCount((String) initialFields.get(PublicationEditPage.TITLE), true)); goToProjectHome(); Assert.assertEquals("Found studies for deleted publication", 0, listHelper.getPublicationStudyCount((String) initialFields.get(PublicationEditPage.TITLE), true)); @@ -430,7 +445,7 @@ public void testInsertAndDelete() Assert.assertEquals("Found therapeutic areas for deleted publication", 0, listHelper.getPublicationTherapeuticAreaCount((String) initialFields.get(PublicationEditPage.TITLE), true)); } - @Test + //@Test public void testInsertAndRefresh() { goToProjectHome(); @@ -469,6 +484,12 @@ public void testInsertAndRefresh() dataCards = finder.getDataCards(); assertEquals("Should find newly inserted publication after reindex", 1, dataCards.size()); + } + private void screenshot(String label) + { + getArtifactCollector().dumpPageSnapshot("ManagePublicationsTestDebug", label+"_step"+_step); + log("screenshot "+label+"_step"+_step+" taken at url " + getDriver().getCurrentUrl()); + _step++; } } \ No newline at end of file From 6d7ce2b41f9e299f6e31acc1ce9233e9fbaa2fad Mon Sep 17 00:00:00 2001 From: labkey-ryans Date: Mon, 11 Jul 2016 11:58:00 -0700 Subject: [PATCH 357/587] close secondary window after switching back to main --- .../org/labkey/test/tests/trialshare/ManageStudiesTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java index f95d4166..3fdea1d4 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java @@ -61,7 +61,8 @@ public void testManageDataLinkPermissions() switchToWindow(1); ManageDataPage manageData = new ManageDataPage(this, _objectType); Assert.assertTrue("No data shown for studies", manageData.getCount() > 0); - + getDriver().close(); + switchToMainWindow(); log("Impersonating user without insert permission"); goToProjectHome(); impersonate(PUBLIC_READER); From b7f7eb325bc1644224147f809a2711fa178572d7 Mon Sep 17 00:00:00 2001 From: labkey-ryans Date: Mon, 11 Jul 2016 12:37:50 -0700 Subject: [PATCH 358/587] additional logging for to troubleshoot alert issue --- .../org/labkey/test/pages/trialshare/ManageDataPage.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java index 8488f3ec..de4d31b2 100644 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -15,12 +15,12 @@ public class ManageDataPage extends LabKeyPage { private DataRegionTable _table; private DataFinderTestBase.CubeObjectType _objectType; - + private BaseWebDriverTest _test; public ManageDataPage(BaseWebDriverTest test, DataFinderTestBase.CubeObjectType objectType) { super(test.getDriver()); - + _test = test; _table = new DataRegionTable("query", test); _objectType = objectType; } @@ -87,11 +87,14 @@ public void goToEditRecord(String keyValue) public void deleteRecord(String keyValue) { log("Deleting record with key value '" + keyValue + "'"); + _test.getArtifactCollector().dumpPageSnapshot("ManagePublicationsTestDebug", "ManageDataPageDeleteRecordBegin"); Integer rowIndex = getRowIndex(keyValue); Assert.assertTrue("Record with key '" + keyValue + "' not found", rowIndex >= 0); _table.checkCheckbox(rowIndex); + _test.getArtifactCollector().dumpPageSnapshot("ManagePublicationsTestDebug", "ManageDataPageDeleteCheckRow"); _table.clickHeaderButtonByText("Delete"); - log("Waiting for delete confirmation to show up"); + sleep(10000); + log("alert present after clicking delete and waiting ten seconds = " + isAlertPresent()); waitForAlert("Are you sure you want to delete the selected row?", 3000); } From 80934bd0e2a0dd6c3bed3dd2567ee6a7e7b3cf8a Mon Sep 17 00:00:00 2001 From: labkey-ryans Date: Mon, 11 Jul 2016 14:19:48 -0700 Subject: [PATCH 359/587] ensure test is on main window before test --- .../org/labkey/test/tests/trialshare/DataFinderTestBase.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java index 8417d42a..67877520 100644 --- a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java +++ b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java @@ -132,6 +132,7 @@ public static void initTest() } + @Override protected BrowserType bestBrowser() { @@ -208,6 +209,7 @@ void createStudy(String name) @Before public void preTest() { + switchToMainWindow(); goToProjectHome(); DataFinderPage finder = new DataFinderPage(this, true); finder.clearSearch(); From a3b3bec8a8aba5043cdbfc0f0ed74dbe20c9d1ef Mon Sep 17 00:00:00 2001 From: labkey-ryans Date: Mon, 11 Jul 2016 16:00:10 -0700 Subject: [PATCH 360/587] remove no longer needed logging --- .../test/pages/trialshare/ManageDataPage.java | 3 -- .../trialshare/ManagePublicationsTest.java | 28 +------------------ 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java index de4d31b2..1aad99e4 100644 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -87,14 +87,11 @@ public void goToEditRecord(String keyValue) public void deleteRecord(String keyValue) { log("Deleting record with key value '" + keyValue + "'"); - _test.getArtifactCollector().dumpPageSnapshot("ManagePublicationsTestDebug", "ManageDataPageDeleteRecordBegin"); Integer rowIndex = getRowIndex(keyValue); Assert.assertTrue("Record with key '" + keyValue + "' not found", rowIndex >= 0); _table.checkCheckbox(rowIndex); - _test.getArtifactCollector().dumpPageSnapshot("ManagePublicationsTestDebug", "ManageDataPageDeleteCheckRow"); _table.clickHeaderButtonByText("Delete"); sleep(10000); - log("alert present after clicking delete and waiting ten seconds = " + isAlertPresent()); waitForAlert("Are you sure you want to delete the selected row?", 3000); } diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index 226dfcef..58cfe49d 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -399,19 +399,11 @@ public void testUpdatePublication() public void testInsertAndDelete() { goToProjectHome(); - screenshot("testInsertAndDeleteBegin"); StudiesListHelper studiesListHelper = new StudiesListHelper(this); studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - screenshot("testInsertAndDeleteSetFirstStudyContainer"); - log("testInsertAndDeleteSetFirstStudyContainer at " + getCurrentRelativeURL()); studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); - screenshot("testInsertAndDeleteSetSecondStudyContainer"); - log("testInsertAndDeleteSetSecondStudyContainer at " + getCurrentRelativeURL()); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - screenshot("testInsertAndDeleteNavigateToManageDataPage"); - log("testInsertAndDeleteNavigateToManageDataPage at " + getCurrentRelativeURL()); ManageDataPage manageData = new ManageDataPage(this, _objectType); - Map initialFields = new HashMap<>(); // add the count so multiple runs of this test have distinct titles initialFields.put(PublicationEditPage.TITLE, "testInsertAndDelete_" + manageData.getCount()); @@ -420,24 +412,13 @@ public void testInsertAndDelete() initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); manageData.goToInsertNew(); - screenshot("testInsertAndDeleteGoToInsertNewFromManageData"); - log("testInsertAndDeleteGoToInsertNewFromManageData at " + getCurrentRelativeURL()); PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - editPage.setFormFields(initialFields, false); - screenshot("testInsertAndDeleteSetEditPageFields"); - log("testInsertAndDeleteSetEditPageFields at " + getCurrentRelativeURL()); editPage.submit(); - screenshot("testInsertAndDeleteSubmitEditPageFields"); - log("testInsertAndDeleteSubmitEditPageFields at " + getCurrentRelativeURL()); manageData.deleteRecord((String) initialFields.get(PublicationEditPage.TITLE)); - screenshot("testInsertAndDeleteAfterDeleteRecord"); - log("testInsertAndDeleteAfterDeleteRecord at " + getCurrentRelativeURL()); PublicationsListHelper listHelper = new PublicationsListHelper(this); goToProjectHome(); - screenshot("testInsertAndDeleteGoToHome"); - log("testInsertAndDeleteGoToHome at " + getCurrentRelativeURL()); Assert.assertEquals("Found deleted publication", 0, listHelper.getPublicationCount((String) initialFields.get(PublicationEditPage.TITLE), true)); goToProjectHome(); Assert.assertEquals("Found studies for deleted publication", 0, listHelper.getPublicationStudyCount((String) initialFields.get(PublicationEditPage.TITLE), true)); @@ -445,7 +426,7 @@ public void testInsertAndDelete() Assert.assertEquals("Found therapeutic areas for deleted publication", 0, listHelper.getPublicationTherapeuticAreaCount((String) initialFields.get(PublicationEditPage.TITLE), true)); } - //@Test + @Test public void testInsertAndRefresh() { goToProjectHome(); @@ -485,11 +466,4 @@ public void testInsertAndRefresh() assertEquals("Should find newly inserted publication after reindex", 1, dataCards.size()); } - - private void screenshot(String label) - { - getArtifactCollector().dumpPageSnapshot("ManagePublicationsTestDebug", label+"_step"+_step); - log("screenshot "+label+"_step"+_step+" taken at url " + getDriver().getCurrentUrl()); - _step++; - } } \ No newline at end of file From feb029110095d950f123cf3e0b8303db6b20c055 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 11 Jul 2016 16:30:06 -0700 Subject: [PATCH 361/587] Spec 26558: switchToMainWindow (and reduce wait for dialog) --- test/src/org/labkey/test/pages/trialshare/ManageDataPage.java | 2 +- .../org/labkey/test/tests/trialshare/DataFinderTestBase.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java index 8488f3ec..dd273b14 100644 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -92,7 +92,7 @@ public void deleteRecord(String keyValue) _table.checkCheckbox(rowIndex); _table.clickHeaderButtonByText("Delete"); log("Waiting for delete confirmation to show up"); - waitForAlert("Are you sure you want to delete the selected row?", 3000); + waitForAlert("Are you sure you want to delete the selected row?", 1000); } public void showDetails(String keyValue) diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java index 8417d42a..a29b2d23 100644 --- a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java +++ b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java @@ -208,6 +208,7 @@ void createStudy(String name) @Before public void preTest() { + switchToMainWindow(); goToProjectHome(); DataFinderPage finder = new DataFinderPage(this, true); finder.clearSearch(); From 5607d903ccf3f393bb4a5dce4acbe9190d3c9f9a Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 12 Jul 2016 07:38:49 -0700 Subject: [PATCH 362/587] Spec 26558: remove unused variable --- .../org/labkey/test/tests/trialshare/ManagePublicationsTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index 58cfe49d..ff690e88 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -35,7 +35,6 @@ public class ManagePublicationsTest extends DataFinderTestBase private static final String PROJECT_NAME = "ManagePublicationTest Project"; private static final String OPERATIONAL_STUDY_SUBFOLDER_NAME = "/" + PROJECT_NAME + "/" + OPERATIONAL_STUDY_NAME; private static final String PUBLIC_STUDY_SUBFOLDER_NAME = "/" + PROJECT_NAME + "/" + PUBLIC_STUDY_NAME; - private static int _step = 0; private static final Map EXISTING_PUB_FIELDS = new HashMap<>(); static From 5c1c64672e8f6364acb2504ab77ca3f565b05b0b Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 12 Jul 2016 11:43:55 -0700 Subject: [PATCH 363/587] modifications required for merge of release16.1 into develop --- .../trialshare/PublicationDocumentProvider.java | 2 +- .../query/ManageCubeObjectQueryView.java | 4 ++-- .../test/pages/trialshare/ManageDataPage.java | 7 +++++-- .../test/pages/trialshare/StudiesListHelper.java | 15 +-------------- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index 09da025b..0aa8af9c 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -111,7 +111,7 @@ public void enumerateDocuments(SearchService.IndexTask task, @NotNull Container Map properties = new HashMap<>(); StringBuilder keywords = new StringBuilder(); - for (String field : new String[]{"Year", "Status", "Title", "SubmissionStatus", "PublicationType", "Journal", "TherapeuticArea" , "Condition"}) + for (String field : new String[]{"Year", "Status", "Title", "SubmissionStatus", "PublicationType", "Journal", "TherapeuticArea"}) { if (results.getString(field) != null) keywords.append(results.getString(field)).append(" "); diff --git a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java index a9270752..77aeb781 100644 --- a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java +++ b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java @@ -64,9 +64,9 @@ protected boolean canDelete() } @Override - protected void populateButtonBar(DataView view, ButtonBar bar, boolean exportAsWebPage) + protected void populateButtonBar(DataView view, ButtonBar bar) { - super.populateButtonBar(view, bar, exportAsWebPage); + super.populateButtonBar(view, bar); addRefreshCubeButton(bar); } diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java index dd273b14..48529170 100644 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -103,12 +103,15 @@ public void showDetails(String keyValue) public Locator.XPathLocator detailsLink(int row) { - return Locator.xpath("//table[@id=" + Locator.xq(_table.getHtmlName()) + "]/tbody/tr[" + (row + 5) + "]/td[3]/a"); + return Locator.tagWithClass("table", "labkey-data-region").append(Locator.xpath("/tbody/tr[" + (row + 5) + "]/td[3]/a ")); +// return Locator.xpath("//table[@id=" + Locator.xq(_table.getTableName()) + "]/tbody/tr[" + (row + 5) + "]/td[3]/a"); } public Locator.XPathLocator editLink(int row) { - return Locator.xpath("//table[@id=" + Locator.xq(_table.getHtmlName()) + "]/tbody/tr[" + (row + 5) + "]/td[2]/a"); + return Locator.tagWithClass("table", "labkey-data-region").append(Locator.xpath("/tbody/tr[" + (row + 5) + "]/td[2]/a ")); +// return Locator.xpath("//table[contains(@class, 'labkey-data-region')][0]/tbody/tr[" + (row + 5) +"]/td[2]/a"); +// return Locator.xpath("//table[@id=" + Locator.xq(_table.getTableName()) + "]/tbody/tr[" + (row + 5) + "]/td[2]/a"); } public void refreshCube() diff --git a/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java b/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java index 2bc52269..51bbd814 100644 --- a/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java +++ b/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java @@ -39,7 +39,7 @@ public void addStudyAccessEntry(String shortName, String containerName, String v clickAndWait(Locator.linkWithText("StudyAccess")); } DataRegionTable table = new DataRegionTable("query", _test); - table.clickHeaderButtonByText("Insert New"); + table.clickHeaderButton("Insert", "Insert New"); selectOptionByText(Locators.studyContainerSelect, containerName); selectOptionByText(Locators.studyVisibility, visibility); selectOptionByText(Locators.studyIdSelect, shortName); @@ -60,19 +60,6 @@ public void setStudyContainer(String studyId, String containerName, Boolean navi clickButton("Submit"); } - private void unlinkStudy(String studyShortName) - { - clickAndWait(Locator.linkWithText("StudyProperties")); - DataRegionTable table = new DataRegionTable("query", _test); - int row = table.getRow("Short Name", studyShortName); - - clickAndWait(table.updateLink(row)); - - selectOptionByText(Locators.studyContainerSelect, ""); - - clickButton("Submit"); - } - public int getStudyCount(String studyId, Boolean navigateToList) { return getStudyListCount("StudyProperties", studyId, navigateToList); From a2c00a5e9ccc6675514b549bb0d55cce0c078cc1 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 12 Jul 2016 14:02:53 -0700 Subject: [PATCH 364/587] add wait for page load when going to edit --- .../labkey/test/pages/trialshare/ManageDataPage.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java index 48529170..06cfeaaf 100644 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -81,7 +81,7 @@ public int getRowIndex(String value) public void goToEditRecord(String keyValue) { Integer rowIndex = getRowIndex(keyValue); - editLink(rowIndex).findElement(getDriver()).click(); + doAndWaitForPageToLoad(() -> editLink(rowIndex).findElement(getDriver()).click()); } public void deleteRecord(String keyValue) @@ -95,23 +95,14 @@ public void deleteRecord(String keyValue) waitForAlert("Are you sure you want to delete the selected row?", 1000); } - public void showDetails(String keyValue) - { - Integer rowIndex = getRowIndex(keyValue); - detailsLink(rowIndex).findElement(getDriver()).click(); - } - public Locator.XPathLocator detailsLink(int row) { return Locator.tagWithClass("table", "labkey-data-region").append(Locator.xpath("/tbody/tr[" + (row + 5) + "]/td[3]/a ")); -// return Locator.xpath("//table[@id=" + Locator.xq(_table.getTableName()) + "]/tbody/tr[" + (row + 5) + "]/td[3]/a"); } public Locator.XPathLocator editLink(int row) { return Locator.tagWithClass("table", "labkey-data-region").append(Locator.xpath("/tbody/tr[" + (row + 5) + "]/td[2]/a ")); -// return Locator.xpath("//table[contains(@class, 'labkey-data-region')][0]/tbody/tr[" + (row + 5) +"]/td[2]/a"); -// return Locator.xpath("//table[@id=" + Locator.xq(_table.getTableName()) + "]/tbody/tr[" + (row + 5) + "]/td[2]/a"); } public void refreshCube() From 54078510c4bdf02373f581da327b458e9aafae53 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 15 Jul 2016 11:33:21 -0700 Subject: [PATCH 365/587] getClientDependencies is final --- .../labkey/trialshare/view/publicationDetails.jsp | 11 ++++------- src/org/labkey/trialshare/view/studyDetails.jsp | 12 ++++-------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/org/labkey/trialshare/view/publicationDetails.jsp b/src/org/labkey/trialshare/view/publicationDetails.jsp index fbd213c6..83f6fc8f 100644 --- a/src/org/labkey/trialshare/view/publicationDetails.jsp +++ b/src/org/labkey/trialshare/view/publicationDetails.jsp @@ -19,18 +19,15 @@ <%@ page import="org.labkey.api.util.UniqueID" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependency" %> +<%@ page import="org.labkey.api.view.template.ClientDependencies" %> <%@ page import="org.labkey.trialshare.TrialShareController" %> -<%@ page import="java.util.LinkedHashSet" %> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%@ page extends="org.labkey.api.jsp.JspBase"%> <%! - public LinkedHashSet getClientDependencies() + @Override + public void addClientDependencies(ClientDependencies dependencies) { - LinkedHashSet resources = new LinkedHashSet<>(); - resources.add(ClientDependency.fromPath("study/Finder/dataFinderEditor")); - - return resources; + dependencies.add("study/Finder/dataFinderEditor"); } %> <% diff --git a/src/org/labkey/trialshare/view/studyDetails.jsp b/src/org/labkey/trialshare/view/studyDetails.jsp index a170c3c7..ac52182b 100644 --- a/src/org/labkey/trialshare/view/studyDetails.jsp +++ b/src/org/labkey/trialshare/view/studyDetails.jsp @@ -19,19 +19,15 @@ <%@ page import="org.labkey.api.util.UniqueID" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependency" %> +<%@ page import="org.labkey.api.view.template.ClientDependencies" %> <%@ page import="org.labkey.trialshare.TrialShareController" %> -<%@ page import="org.springframework.web.servlet.ModelAndView" %> -<%@ page import="java.util.LinkedHashSet" %> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%@ page extends="org.labkey.api.jsp.JspBase"%> <%! - public LinkedHashSet getClientDependencies() + @Override + public void addClientDependencies(ClientDependencies dependencies) { - LinkedHashSet resources = new LinkedHashSet<>(); - resources.add(ClientDependency.fromPath("study/Finder/dataFinderEditor")); - - return resources; + dependencies.add("study/Finder/dataFinderEditor"); } %> <% From 3a50f43056f485454592103456bb99b8cfbbc91f Mon Sep 17 00:00:00 2001 From: XingY Date: Mon, 18 Jul 2016 15:14:58 -0700 Subject: [PATCH 366/587] Spec Issue 26850: Update study import form to include StudyAccess field: UI changes --- .../web/study/Finder/dataFinderEditor.lib.xml | 2 + .../panel/CubeObjectDetailsFormPanel.js | 2 +- .../Finder/panel/StudyAccessTuplePanel.js | 166 ++++++++++++++++++ .../Finder/panel/StudyDetailsFormPanel.js | 25 +++ resources/web/study/Finder/trialShare.css | 24 +++ .../labkey/trialshare/TrialShareManager.java | 1 + .../labkey/trialshare/view/studyDetails.jsp | 16 +- 7 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 resources/web/study/Finder/panel/StudyAccessTuplePanel.js diff --git a/resources/web/study/Finder/dataFinderEditor.lib.xml b/resources/web/study/Finder/dataFinderEditor.lib.xml index ce88b4ef..b2dfee5d 100644 --- a/resources/web/study/Finder/dataFinderEditor.lib.xml +++ b/resources/web/study/Finder/dataFinderEditor.lib.xml @@ -13,12 +13,14 @@ \ No newline at end of file From 68f8d93f34a653882c0818c4aed0a867095a22d8 Mon Sep 17 00:00:00 2001 From: XingY Date: Mon, 18 Jul 2016 15:31:15 -0700 Subject: [PATCH 367/587] Spec Issue 26850: UI for insert/update/view --- .../Finder/panel/StudyAccessTuplePanel.js | 75 ++++++++++--------- .../Finder/panel/StudyDetailsFormPanel.js | 3 +- resources/web/study/Finder/trialShare.css | 2 +- .../labkey/trialshare/view/studyDetails.jsp | 9 ++- 4 files changed, 50 insertions(+), 39 deletions(-) diff --git a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js index cb5d2db8..30b223f2 100644 --- a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js +++ b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js @@ -5,23 +5,25 @@ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { initComponent: function(){ var me = this; this.studyAccessPanels = []; - this.dockedItems = [{ - xtype: 'toolbar', - dock: 'bottom', - ui: 'footer', - style: 'background-color: transparent;', - items: [ - { - text: 'Add...', - //formBind: true, - handler: function (btn) + if (this.mode != "view") { + this.dockedItems = [{ + xtype: 'toolbar', + dock: 'bottom', + ui: 'footer', + style: 'background-color: transparent;', + items: [ { - me.add(me.getStudyAccessPanel(me.getStudyAccessStore(-1))); - me.doLayout(); + text: 'Add...', + //formBind: true, + handler: function (btn) + { + me.add(me.getStudyAccessPanel(me.getStudyAccessStore(-1))); + me.doLayout(); + } } - } - ] - }]; + ] + }]; + } this.callParent(); @@ -53,35 +55,40 @@ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { } }, getStudyAccessPanel : function(studyAccessStore) { + var formItems = []; + var buttonId = Ext4.id(), panelId = Ext4.id(); + if (this.mode != "view") { + var deleteButton = { + id: buttonId, + xtype: 'label', + cls: 'studyaccessdeletebutton', + html: '', + listeners: { + click: { + element: 'el', //bind to the underlying el property on the panel + fn: function (a, button) + { + Ext4.getCmp(panelId).destroy(); + } + } + } + }; + formItems.push(deleteButton); + } + var _id = Ext4.id(); var form = Ext4.create('LABKEY.study.panel.StudyAccessForm', { id: _id, + mode: this.mode, store : studyAccessStore, fieldLabel : 'Container', labelWidth : this.defaultFieldLabelWidth, editable : false, width : this.largeFieldWidth }); + formItems.push(form); - var buttonId = Ext4.id(); - var panelId = Ext4.id(); - var deleteButton = { - id: buttonId, - xtype:'label', - cls: 'studyaccessdeletebutton', - html:'', - listeners: { - click: { - element: 'el', //bind to the underlying el property on the panel - fn: function(a, button){ - Ext4.getCmp(panelId).destroy(); - } - } - } - }; - - - var studyAccessPanel = new Ext4.FormPanel({ + var studyAccessPanel = new Ext4.Panel({ id: panelId, cls: 'studyaccesspanel', layout: { diff --git a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js index ce22cd89..68d4ae00 100644 --- a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js @@ -250,7 +250,8 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { }, Ext4.create('LABKEY.study.panel.StudyAccessTuplePanel', { studyaccesslist: this.studyaccesslist, - width: this.largeFieldWidth + width: this.largeFieldWidth, + mode: this.mode }) ], scope: this diff --git a/resources/web/study/Finder/trialShare.css b/resources/web/study/Finder/trialShare.css index bdcae702..32dca429 100644 --- a/resources/web/study/Finder/trialShare.css +++ b/resources/web/study/Finder/trialShare.css @@ -40,7 +40,6 @@ div.labkey-study-details h2.labkey-study-accession { .studyaccessdeletebutton { padding-left: 10px; - padding-right: 10px; } .studyaccesspanel { @@ -56,6 +55,7 @@ div.labkey-study-details h2.labkey-study-accession { .studyaccessform .x4-panel-body-default{ border-radius: 0; + padding-left: 10px; } .studyaccesstuple { diff --git a/src/org/labkey/trialshare/view/studyDetails.jsp b/src/org/labkey/trialshare/view/studyDetails.jsp index 98f0c4bd..42b30dfa 100644 --- a/src/org/labkey/trialshare/view/studyDetails.jsp +++ b/src/org/labkey/trialshare/view/studyDetails.jsp @@ -44,12 +44,15 @@ String renderId = "study-details-" + UniqueID.getRequestScopedUID(HttpView.currentRequest()); String cubeObjectJson = bean.getCubeObject() == null ? "null" : new JSONObject(bean.getCubeObject()).toString(2); - List accessList = ((StudyBean) bean.getCubeObject()).getStudyAccessList(); + List accessList = bean.getCubeObject() == null ? null :((StudyBean) bean.getCubeObject()).getStudyAccessList(); JSONArray jsonArray = new JSONArray(); - for (StudyAccess access : accessList) + if (accessList != null) { - jsonArray.put(new JSONObject(access)); + for (StudyAccess access : accessList) + { + jsonArray.put(new JSONObject(access)); + } } String studyaccesslist = jsonArray.toString(2); From 09cacf5d86bfb37fcaef8abe6f4f61226ea3f85a Mon Sep 17 00:00:00 2001 From: XingY Date: Mon, 18 Jul 2016 17:27:32 -0700 Subject: [PATCH 368/587] Spec Issue 26850: Set required study access fields, disable/enable Submit when Add/Delete study access record --- .../panel/CubeObjectDetailsFormPanel.js | 3 +++ .../Finder/panel/StudyAccessTuplePanel.js | 22 +++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js index de5634a6..6bf9eb28 100644 --- a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js @@ -10,6 +10,8 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { bodyPadding: 5, + itemId: 'cubedetailsformpanel', + fieldClsName: 'labkey-field-editor', fieldLabelClsName : 'labkey-field-editor-label', @@ -42,6 +44,7 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { items: [ { text: 'Submit', + itemId: 'detailsSubmitBtn', formBind: true, successURL: this.nextStepUrl || LABKEY.ActionURL.getParameter('returnUrl') || this.manageDataUrl, handler: function (btn) diff --git a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js index 30b223f2..9d4d7f58 100644 --- a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js +++ b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js @@ -55,7 +55,7 @@ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { } }, getStudyAccessPanel : function(studyAccessStore) { - var formItems = []; + var formItems = [], me = this; var buttonId = Ext4.id(), panelId = Ext4.id(); if (this.mode != "view") { var deleteButton = { @@ -69,6 +69,12 @@ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { fn: function (a, button) { Ext4.getCmp(panelId).destroy(); + if (Ext4.ComponentQuery.query("#cubedetailsformpanel")[0].getForm().isValid()) { + var submitBtn = Ext4.ComponentQuery.query("#detailsSubmitBtn")[0]; + if (submitBtn && submitBtn.isDisabled) { + submitBtn.enable(); + } + } } } } @@ -80,11 +86,7 @@ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { var form = Ext4.create('LABKEY.study.panel.StudyAccessForm', { id: _id, mode: this.mode, - store : studyAccessStore, - fieldLabel : 'Container', - labelWidth : this.defaultFieldLabelWidth, - editable : false, - width : this.largeFieldWidth + store : studyAccessStore }); formItems.push(form); @@ -96,9 +98,6 @@ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { align: 'middle' }, bodyStyle: 'padding: 4px 0;', - hideLabel: true, - //border: false, - frame: false, items: [deleteButton, form], scope: this }); @@ -126,7 +125,7 @@ Ext4.define('LABKEY.study.panel.StudyAccessForm', { cls : this.fieldClsName, labelCls : this.fieldLabelClsName, allowBlank : false, - fieldLabel : 'Visiblility', + fieldLabel : 'Visiblility *', name : 'visiblility', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth, @@ -140,12 +139,13 @@ Ext4.define('LABKEY.study.panel.StudyAccessForm', { disabledCls : 'labkey-combo-disabled', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, + allowBlank : false, name : 'studyContainer', store : { model : 'LABKEY.study.data.Container', autoLoad: true }, - fieldLabel : 'Study Container', + fieldLabel : 'Study Container *', labelWidth : this.defaultFieldLabelWidth, valueField : 'EntityId', displayField : 'Path', From 2c78cbfad55e1bac30d3b8aa90eec2ecae5588e2 Mon Sep 17 00:00:00 2001 From: XingY Date: Tue, 19 Jul 2016 09:50:16 -0700 Subject: [PATCH 369/587] Spec Issue 26850: submit studyaccess values --- .../Finder/panel/StudyAccessTuplePanel.js | 7 +++---- .../Finder/panel/StudyDetailsFormPanel.js | 20 +++++++++++++++++++ resources/web/study/Finder/trialShare.css | 1 + 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js index 9d4d7f58..9fb2907e 100644 --- a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js +++ b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js @@ -14,7 +14,6 @@ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { items: [ { text: 'Add...', - //formBind: true, handler: function (btn) { me.add(me.getStudyAccessPanel(me.getStudyAccessStore(-1))); @@ -107,7 +106,7 @@ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { Ext4.define('LABKEY.study.panel.StudyAccessForm', { extend: 'Ext.form.Panel', - widget: 'study.studyaccessform', + itemId: 'studyaccessform', defaultFieldLabelWidth: 120, smallFieldWidth: 350, border: false, @@ -125,8 +124,8 @@ Ext4.define('LABKEY.study.panel.StudyAccessForm', { cls : this.fieldClsName, labelCls : this.fieldLabelClsName, allowBlank : false, - fieldLabel : 'Visiblility *', - name : 'visiblility', + fieldLabel : 'Visibility *', + name : 'visibility', labelWidth : this.defaultFieldLabelWidth, width : this.smallFieldWidth, value : this.store.data['visibility'], diff --git a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js index 68d4ae00..8774dad9 100644 --- a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js @@ -281,5 +281,25 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { } return items; + }, + getFieldValues : function() + { + var studyFields = this.callParent(); + studyFields.studyAccessList = []; + var formCmps = Ext4.ComponentQuery.query("#studyaccessform"); + Ext4.each(formCmps, function(formCmp){ + var studyAccessValues = {}; + formCmp.getForm().getFields().each(function(item) + { + var value = item.value; + if (value && item.isStudyAccess) + { + studyAccessValues[item.name] = value; + } + }, this); + studyFields.studyAccessList.push(studyAccessValues); + }); + + return studyFields; } }); \ No newline at end of file diff --git a/resources/web/study/Finder/trialShare.css b/resources/web/study/Finder/trialShare.css index 32dca429..6950ecbf 100644 --- a/resources/web/study/Finder/trialShare.css +++ b/resources/web/study/Finder/trialShare.css @@ -40,6 +40,7 @@ div.labkey-study-details h2.labkey-study-accession { .studyaccessdeletebutton { padding-left: 10px; + cursor: pointer; } .studyaccesspanel { From 69e3c891bebfd8d5417d82dd53159fa4282c45b9 Mon Sep 17 00:00:00 2001 From: XingY Date: Tue, 19 Jul 2016 14:00:44 -0700 Subject: [PATCH 370/587] Spec Issue 26850: update insert and update to use study access, get rid of old study access UI behaviors --- .../panel/CubeObjectDetailsFormPanel.js | 4 -- .../Finder/panel/StudyAccessTuplePanel.js | 20 ++++++++-- .../Finder/panel/StudyDetailsFormPanel.js | 30 -------------- .../trialshare/TrialShareController.java | 6 +-- .../labkey/trialshare/TrialShareManager.java | 40 ++++++++++++++++--- src/org/labkey/trialshare/data/StudyBean.java | 27 ------------- 6 files changed, 54 insertions(+), 73 deletions(-) diff --git a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js index 6bf9eb28..f4c8edc5 100644 --- a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js @@ -110,10 +110,6 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { btn.setDisabled(false); var msg = "Your " + this.objectName.toLowerCase() + " was saved."; - if (this.mode == "insert" && this.objectName == "Study") - { - msg += " Enter study access data to make the study visible in the finder."; - } Ext4.Msg.alert("Success", msg, function(){ window.location = btn.successURL; }, this); diff --git a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js index 9fb2907e..2d9e55a9 100644 --- a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js +++ b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js @@ -120,14 +120,26 @@ Ext4.define('LABKEY.study.panel.StudyAccessForm', { var items = []; items.push( { - xtype : this.mode == "view" ? 'displayfield' : 'textfield', + xtype : 'combo', + disabled : this.mode == "view", + disabledCls : 'labkey-combo-disabled', cls : this.fieldClsName, labelCls : this.fieldLabelClsName, allowBlank : false, - fieldLabel : 'Visibility *', name : 'visibility', + store : Ext4.create('Ext.data.Store', { + fields: ['value'], + data : [ + {"value":"Operational"}, + {"value":"Public"} + ] + }), + fieldLabel : 'Visibility *', labelWidth : this.defaultFieldLabelWidth, - width : this.smallFieldWidth, + valueField : 'value', + displayField : 'value', + editable : false, + width : this.value, value : this.store.data['visibility'], isStudyAccess : true }); @@ -150,7 +162,7 @@ Ext4.define('LABKEY.study.panel.StudyAccessForm', { displayField : 'Path', editable : false, width : this.mediumLargeFieldWidth, - value : this.store.data['studyContainerPath'], + value : this.store.data['studyContainer'], isStudyAccess : true } ); diff --git a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js index 8774dad9..66cd2caf 100644 --- a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js @@ -12,14 +12,6 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { initComponent: function() { - if (this.mode == "insert") - { - this.nextStepUrl = LABKEY.ActionURL.buildURL("list", "grid", LABKEY.ActionURL.getContainer(), - { - listId: this.accessListId, - returnUrl: window.location - }); - } this.callParent(); }, @@ -258,28 +250,6 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { }) ); - //TODO remove - if (this.mode != "insert") - { - items.push ( - { - tag: 'div', - itemId: 'nextStepEl', - html:LABKEY.Utils.textLink({ - href: LABKEY.ActionURL.buildURL("list", "grid", LABKEY.ActionURL.getContainer(), - { - listId: this.accessListId, - 'query.StudyId/ShortName~eq' : this.cubeObject.shortName, - returnUrl: window.location - }), - text: this.mode + ' Study Access Data' - }), - border: false, - cls: 'labkey-data-finder-editor-message' - } - ) - } - return items; }, getFieldValues : function() diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 3dfe4808..09e753a2 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -649,17 +649,17 @@ private List getPublicationFacets() List facets = new ArrayList<>(); StudyFacetBean facet; - facet = new StudyFacetBean("Status", "Statuses", "Publication.Status", "Status", "[Publication.Status][(All)]", FacetFilter.Type.OR, 2); + facet = new StudyFacetBean("Status", "Statuses", "Publication.Status", "Status", "[Publication.Status][(All)]", FacetFilter.Type.OR, 1); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); facet.setDisplayFacet(TrialShareManager.get().canSeeIncompleteManuscripts(getUser(), getContainer())); facets.add(facet); facet = new StudyFacetBean("Therapeutic Area", "Therapeutic Areas", "Publication.Therapeutic Area", "Therapeutic Area", "[Publication.Therapeutic Area][(All)]", FacetFilter.Type.OR, 4); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); facets.add(facet); - facet = new StudyFacetBean("Publication Type", "Publication Types", "Publication.Publication Type", "PublicationType", "[Publication.Publication Type][(All)]", FacetFilter.Type.OR, 1); + facet = new StudyFacetBean("Publication Type", "Publication Types", "Publication.Publication Type", "PublicationType", "[Publication.Publication Type][(All)]", FacetFilter.Type.OR, 3); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); facets.add(facet); - facet = new StudyFacetBean("Submission Status", "Submission Statuses", "Publication.Submission Status", "SubmissionStatus", "[Publication.Submission Status][(All)]", FacetFilter.Type.OR, 3); + facet = new StudyFacetBean("Submission Status", "Submission Statuses", "Publication.Submission Status", "SubmissionStatus", "[Publication.Submission Status][(All)]", FacetFilter.Type.OR, 2); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); facet.setDisplayFacet(TrialShareManager.get().canSeeIncompleteManuscripts(getUser(), getContainer())); facets.add(facet); diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 76a85ed1..813d46ac 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -330,6 +330,9 @@ public void updateStudy(@NotNull User user, @NotNull Container container, StudyE schema.getStudyTherapeuticAreaTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyTherapeuticAreaTableInfo(), filter), null, null); addJoinTableData(schema.getStudyTherapeuticAreaTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, study.getTherapeuticAreas(), user, container); + schema.getStudyAccessTableInfo().getUpdateService().deleteRows(user, container, getJoinTableIds(schema.getStudyAccessTableInfo(), filter), null, null); + addStudyAccessData(schema.getStudyAccessTableInfo(), studyId, study.getStudyAccessList(), user, container); + transaction.commit(); } catch (Exception e) @@ -370,6 +373,7 @@ public void insertStudy(@NotNull User user, @NotNull Container container, StudyE addJoinTableData(schema.getStudyAgeGroupTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.AGE_GROUP_FIELD, study.getAgeGroups(), user, container); addJoinTableData(schema.getStudyPhaseTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.PHASE_FIELD, study.getPhases(), user, container); addJoinTableData(schema.getStudyTherapeuticAreaTableInfo(), TrialShareQuerySchema.STUDY_ID_FIELD, studyId, TrialShareQuerySchema.THERAPEUTIC_AREA_FIELD, study.getTherapeuticAreas(), user, container); + addStudyAccessData(schema.getStudyAccessTableInfo(), studyId, study.getStudyAccessList(), user, container); transaction.commit(); } @@ -440,9 +444,20 @@ private void deleteJoinTableData(@NotNull TableInfo tableInfo, @NotNull User use tableInfo.getUpdateService().deleteRows(user, container, getJoinTableIds(tableInfo, objectIdFilter), null, null); } - private void addJoinTableData(TableInfo tableInfo, String idField, Object id, String dataField, List dataValues, User user, Container container) throws SQLException, QueryUpdateServiceException, BatchValidationException, DuplicateKeyException + private void batchInsertRows(TableInfo tableInfo, User user, Container container, List> dataList) throws SQLException, QueryUpdateServiceException, BatchValidationException, DuplicateKeyException { BatchValidationException batchValidationErrors = new BatchValidationException(); + if (!dataList.isEmpty()) + { + tableInfo.getUpdateService().insertRows(user, container, dataList, batchValidationErrors, null, null); + if (batchValidationErrors.hasErrors()) + throw batchValidationErrors; + dataList.clear(); + } + } + + private void addJoinTableData(TableInfo tableInfo, String idField, Object id, String dataField, List dataValues, User user, Container container) throws SQLException, QueryUpdateServiceException, BatchValidationException, DuplicateKeyException + { List> dataList = new ArrayList<>(); for (String value : dataValues) { @@ -453,10 +468,25 @@ private void addJoinTableData(TableInfo tableInfo, String idField, Object id, St } if (!dataList.isEmpty()) { - tableInfo.getUpdateService().insertRows(user, container, dataList, batchValidationErrors, null, null); - if (batchValidationErrors.hasErrors()) - throw batchValidationErrors; - dataList.clear(); + batchInsertRows(tableInfo, user, container, dataList); + } + } + + private void addStudyAccessData(TableInfo tableInfo, String studyId, List studyAccesses, User user, Container container) throws SQLException, QueryUpdateServiceException, BatchValidationException, DuplicateKeyException + { + List> studyAccessList = new ArrayList<>(); + for (StudyAccess studyAccess : studyAccesses) + { + Map dataMap = new CaseInsensitiveHashMap<>(); + dataMap.put("studyId", studyId); + dataMap.put("visibility", studyAccess.getVisibility()); + dataMap.put("studyContainer", studyAccess.getStudyContainer()); + dataMap.put("displayName", studyAccess.getDisplayName()); + studyAccessList.add(dataMap); + } + if (!studyAccessList.isEmpty()) + { + batchInsertRows(tableInfo, user, container, studyAccessList); } } diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index 7ef88246..a57322ce 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -68,8 +68,6 @@ public class StudyBean private String studyIdPrefix = null; // common prefix used in labeling studies private String availability; private Boolean isHighlighted = false; - private String visibility; - private Boolean isPublic = false; private List _studyAccessList = new ArrayList<>(); private List personnel; @@ -249,31 +247,6 @@ public void setAbstractCount(Integer abstractCount) this.abstractCount = abstractCount; } - public String getVisibility() - { - return visibility; - } - - public void setVisibility(String visibility) - { - this.visibility = visibility; - } - - public Boolean getIsPublic() - { - return isPublic; - } - - public Boolean getIsBorderHighlighted() - { - return !isPublic; - } - - public void setIsPublic(Boolean aPublic) - { - isPublic = aPublic == null ? false : aPublic; - } - public Integer getParticipantCount() { return (Integer) _primaryFields.get(PARTICIPANT_COUNT_FIELD); From f7cea4138e389909f6f98043dd27440be457ce0e Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 20 Jul 2016 13:54:59 -0700 Subject: [PATCH 371/587] Spec Issue 26850: add Edit link, type and status. style updates, fix html --- resources/web/study/Finder/dataFinder.css | 34 +++++++++++++- .../study/Finder/panel/PublicationCards.js | 44 +++++++++++++++---- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index 75a281a0..f279c4b1 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -730,7 +730,9 @@ div.labkey-study-details h3 { .labkey-publication-card .labkey-publication-data-link { - float: right; + float: left; + position: absolute; + bottom: 7px; } @@ -761,4 +763,32 @@ div.labkey-study-details h3 { .labkey-goto-link-icon { display: none; -} \ No newline at end of file +} + +.labkey-publication-content +{ + /* Firefox */ + width: -moz-calc(100% - 180px); + /* WebKit */ + width: -webkit-calc(100% - 180px); + /* Standard */ + width: calc(100% - 180px); + + display: inline-block; +} + +.labkey-publication-right { + padding-left: 10px; + vertical-align: top; + display:inline-block; + height:100%; +} + +.labkey-publication-type { + vertical-align: top; +} + +.labkey-publication-status{ + color:limegreen; + vertical-align: top; +} diff --git a/resources/web/study/Finder/panel/PublicationCards.js b/resources/web/study/Finder/panel/PublicationCards.js index cd175126..60209a2c 100644 --- a/resources/web/study/Finder/panel/PublicationCards.js +++ b/resources/web/study/Finder/panel/PublicationCards.js @@ -49,7 +49,7 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { ' ', ' ', ' ', - '
', + '
', '
{title:htmlEncode}
', '
{authorAbbrev:htmlEncode}
', '
{author:htmlEncode}
', @@ -61,12 +61,6 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { ' ', '
', ' ', - ' ', - ' ', - ' ', '
', ' ', '
', @@ -124,11 +118,35 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { ' ', '
', ' ', - ' ', '
', + ' ', + + '', + '', + '', + '{publicationType:htmlEncode}', + '', + '', + '', + ': ', + '{status:htmlEncode}', + '', + '
', + '', + 'Edit', + '', + '', + '
', + '', + '', + 'Clinical and Assay Data', + '', + '', + '', + '
', + ' ', ' ', - ' ', ' ', '', { @@ -148,6 +166,14 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { html += " labkey-publication-highlight4"; html += '">'; return html; + }, + getEditUrl: function(publicationId) + { + var url = LABKEY.ActionURL.buildURL('trialshare', 'updateData', null, { + "id": publicationId, + "objectName": 'publication' + }); + return url; } } ), From aaefe68cffca0a4a686aa9ce2bd8b4602feab84e Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 20 Jul 2016 16:27:28 -0700 Subject: [PATCH 372/587] Spec Issue 26850: add support for default filtering on load --- resources/web/study/Finder/data/Facet.js | 3 +- .../web/study/Finder/panel/FacetsGrid.js | 61 ++++++++++++++++++- .../trialshare/TrialShareController.java | 10 ++- .../trialshare/data/StudyFacetBean.java | 11 ++++ 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/resources/web/study/Finder/data/Facet.js b/resources/web/study/Finder/data/Facet.js index f8704624..000137ba 100644 --- a/resources/web/study/Finder/data/Facet.js +++ b/resources/web/study/Finder/data/Facet.js @@ -23,7 +23,8 @@ Ext4.define('LABKEY.study.data.Facet', { {name: 'allMemberName'}, // not currently used {name: 'ordinal'}, {name: 'isExpanded', type:'boolean', defaultValue: true}, - {name: 'displayFacet', type:'boolean', defaultValue: true} + {name: 'displayFacet', type:'boolean', defaultValue: true}, + {name: 'defaultSelectedUniqueNames'} ], associations: [ diff --git a/resources/web/study/Finder/panel/FacetsGrid.js b/resources/web/study/Finder/panel/FacetsGrid.js index 9a7c11e1..87c572e9 100644 --- a/resources/web/study/Finder/panel/FacetsGrid.js +++ b/resources/web/study/Finder/panel/FacetsGrid.js @@ -72,6 +72,13 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { ) } ], + listeners : { + viewready : { + fn: function() {this.onViewReady(); }, + scope: this, + single: true + } + }, statics: { hasFilters : function(objectName, facetName) { @@ -98,6 +105,56 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { } }, + onViewReady : function() { + var facetsSelections = []; + if (Ext4.isArray(this.facetStore.data.items) && this.facetStore.data.items.length > 0) { + Ext4.each(this.facetStore.data.items, function(item){ + if (Ext4.isArray(item.data.defaultSelectedUniqueNames)) { + Ext4.each(item.data.defaultSelectedUniqueNames, function(uniqueName){ + facetsSelections.push(uniqueName); + }); + } + }); + } + if (facetsSelections.length > 0) + this.setDefaultFilters(facetsSelections); + + }, + setDefaultFilters : function(facetsSelections) { + if (!this.rendered) { + this.on('render', function() { this.setDefaultFilters(facetsSelections); }, this, {single: true}); + } + + var store = this.getStore(); + var records = [], + recIdx, + recordNotFound = false; + + Ext4.each(facetsSelections, function(val) { + recIdx = store.findBy(function(rec){ + return rec.get('uniqueName') === val; + }); + + if (recIdx != -1) { + records.push(store.getAt(recIdx)); + } + else { + if (!Ext4.isEmpty(val)) { + recordNotFound = true; + return false; + } + } + }, this); + + if (recordNotFound) { + return; + } + + this.getSelectionModel().select(records, false, true); + this.getSelectionModel().fireEvent('selectionchange', this.getSelectionModel(), records, true); + + }, + /** * @Override * Issue 26148: Collapsing a section cause click in sections that follow to be off by 1. @@ -181,11 +238,11 @@ Ext4.define("LABKEY.study.panel.FacetsGrid", { var facetChangeTask = new Ext4.util.DelayedTask(facetSelectionChange, this); - this.getSelectionModel().on('selectionchange', function(selModel, records){ + this.getSelectionModel().on('selectionchange', function(selModel, records, fast){ this.facetStore.clearAllSelectedMembers(); if (records.length > 0) this.facetStore.selectMembers(records); - facetChangeTask.delay(500); + facetChangeTask.delay(fast? 100 : 500); }, this); Ext4.getStore(this.cubeConfig.objectName).on("load", this.onObjectStoreLoad, this); diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 09e753a2..5d1abf8b 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -74,6 +74,7 @@ import org.springframework.web.servlet.ModelAndView; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -648,10 +649,15 @@ private List getPublicationFacets() { List facets = new ArrayList<>(); StudyFacetBean facet; + boolean isInternalUser = TrialShareManager.get().canSeeIncompleteManuscripts(getUser(), getContainer()); facet = new StudyFacetBean("Status", "Statuses", "Publication.Status", "Status", "[Publication.Status][(All)]", FacetFilter.Type.OR, 1); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); - facet.setDisplayFacet(TrialShareManager.get().canSeeIncompleteManuscripts(getUser(), getContainer())); + facet.setDisplayFacet(isInternalUser); + if (isInternalUser) + { + facet.setDefaultSelectedUniqueNames(Arrays.asList("[Publication.Status].[In Progress]")); + } facets.add(facet); facet = new StudyFacetBean("Therapeutic Area", "Therapeutic Areas", "Publication.Therapeutic Area", "Therapeutic Area", "[Publication.Therapeutic Area][(All)]", FacetFilter.Type.OR, 4); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); @@ -661,7 +667,7 @@ private List getPublicationFacets() facets.add(facet); facet = new StudyFacetBean("Submission Status", "Submission Statuses", "Publication.Submission Status", "SubmissionStatus", "[Publication.Submission Status][(All)]", FacetFilter.Type.OR, 2); facet.setFilterOptions(getFacetFilters(false, true, FacetFilter.Type.OR)); - facet.setDisplayFacet(TrialShareManager.get().canSeeIncompleteManuscripts(getUser(), getContainer())); + facet.setDisplayFacet(isInternalUser); facets.add(facet); facet = new StudyFacetBean("Study", "Studies", "Publication.Study", "Study", "[Publication.Study].[(All)]", FacetFilter.Type.OR, 5); facet.setFilterOptions(getFacetFilters(true, true, FacetFilter.Type.OR)); diff --git a/src/org/labkey/trialshare/data/StudyFacetBean.java b/src/org/labkey/trialshare/data/StudyFacetBean.java index bfffa9ab..fc2d52c6 100644 --- a/src/org/labkey/trialshare/data/StudyFacetBean.java +++ b/src/org/labkey/trialshare/data/StudyFacetBean.java @@ -33,6 +33,7 @@ public class StudyFacetBean private List filterOptions; private Integer ordinal; private Boolean displayFacet = true; + private List defaultSelectedUniqueNames; public StudyFacetBean() {} @@ -135,4 +136,14 @@ public void setDisplayFacet(Boolean displayFacet) { this.displayFacet = displayFacet; } + + public List getDefaultSelectedUniqueNames() + { + return defaultSelectedUniqueNames; + } + + public void setDefaultSelectedUniqueNames(List defaultExcludedUniqueNames) + { + this.defaultSelectedUniqueNames = defaultExcludedUniqueNames; + } } From 38531ebd5693c5b7ca1deccd9fc4eff9beb0227b Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 20 Jul 2016 16:58:24 -0700 Subject: [PATCH 373/587] Spec Issue 26850: fix param name --- src/org/labkey/trialshare/data/StudyFacetBean.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/trialshare/data/StudyFacetBean.java b/src/org/labkey/trialshare/data/StudyFacetBean.java index fc2d52c6..a8418505 100644 --- a/src/org/labkey/trialshare/data/StudyFacetBean.java +++ b/src/org/labkey/trialshare/data/StudyFacetBean.java @@ -142,8 +142,8 @@ public List getDefaultSelectedUniqueNames() return defaultSelectedUniqueNames; } - public void setDefaultSelectedUniqueNames(List defaultExcludedUniqueNames) + public void setDefaultSelectedUniqueNames(List defaultSelectedUniqueNames) { - this.defaultSelectedUniqueNames = defaultExcludedUniqueNames; + this.defaultSelectedUniqueNames = defaultSelectedUniqueNames; } } From b13ea778c4e639859355c8520ec1dfbab5ca40fd Mon Sep 17 00:00:00 2001 From: XingY Date: Thu, 21 Jul 2016 11:54:30 -0700 Subject: [PATCH 374/587] Spec Issue 26850: add cls for testing purpose --- resources/web/study/Finder/panel/StudyAccessTuplePanel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js index 2d9e55a9..49ba096c 100644 --- a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js +++ b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js @@ -2,6 +2,7 @@ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { extend: 'Ext.panel.Panel', studyAccessPanels : [], border: false, + studyAccessPanelIndex: 0, initComponent: function(){ var me = this; this.studyAccessPanels = []; @@ -91,7 +92,7 @@ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { var studyAccessPanel = new Ext4.Panel({ id: panelId, - cls: 'studyaccesspanel', + cls: 'studyaccesspanel studyaccesspanelindex' + this.studyAccessPanelIndex++, //add index cls for automated test layout: { type: 'hbox', align: 'middle' From 5904ec490afcb3d7bec5da5fe50305284853d879 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 21 Jul 2016 14:46:03 -0700 Subject: [PATCH 375/587] Spec 26850: success URL after deleting object should use object type --- src/org/labkey/trialshare/TrialShareController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 5d1abf8b..24ecddd8 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -1136,7 +1136,7 @@ else if (form.getObjectName() == ObjectName.study) @Override public URLHelper getSuccessURL(CubeObjectQueryForm queryForm) { - return getManageDataUrl(ObjectName.publication); + return getManageDataUrl(queryForm.getObjectName()); } } From 8634a0b57b64d80ed65ac95d8fd7056f498d9179 Mon Sep 17 00:00:00 2001 From: XingY Date: Thu, 21 Jul 2016 16:59:37 -0700 Subject: [PATCH 376/587] Issue 27243: The DataFinder Cube Cache should be cleared after studyAccess data is changed --- resources/web/study/Finder/panel/PublicationCards.js | 4 ++-- src/org/labkey/trialshare/TrialShareController.java | 4 ++++ src/org/labkey/trialshare/TrialShareManager.java | 11 +++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/resources/web/study/Finder/panel/PublicationCards.js b/resources/web/study/Finder/panel/PublicationCards.js index 60209a2c..39d163e3 100644 --- a/resources/web/study/Finder/panel/PublicationCards.js +++ b/resources/web/study/Finder/panel/PublicationCards.js @@ -133,8 +133,8 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { '', '
', '', - 'Edit', - '', + 'Edit', + '', '', '
', '', diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 5d1abf8b..5ec8576f 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -1061,6 +1061,8 @@ public void validateForm(PublicationEditBean form, Errors errors) public Object execute(PublicationEditBean form, BindException errors) throws Exception { TrialShareManager.get().updatePublication(getUser(), getContainer(), form, errors); + if (!errors.hasErrors()) + TrialShareManager.get().clearCache(errors); return success(); } } @@ -1102,6 +1104,8 @@ public void validateForm(StudyEditBean form, Errors errors) public Object execute(StudyEditBean form, BindException errors) throws Exception { TrialShareManager.get().updateStudy(getUser(), getContainer(), form, errors); + if (!errors.hasErrors()) + TrialShareManager.get().clearCache(errors); return success(); } } diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 813d46ac..83ea122e 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -32,6 +32,7 @@ import org.labkey.api.query.DuplicateKeyException; import org.labkey.api.query.FieldKey; import org.labkey.api.query.InvalidKeyException; +import org.labkey.api.query.QueryService; import org.labkey.api.query.QueryUpdateServiceException; import org.labkey.api.security.User; import org.labkey.trialshare.data.PublicationEditBean; @@ -516,4 +517,14 @@ public StudyEditBean getStudy(String id, User user, Container container) return editStudy; } + public void clearCache(BindException errors) + { + Container _cubeContainer = getCubeContainer(null); + if (_cubeContainer == null) + errors.reject("Container path is required", "Container path not provided"); + if (errors.hasErrors()) + return; + QueryService.get().cubeDataChanged(_cubeContainer); + } + } \ No newline at end of file From dfe9feba40e13bb27263a0d2f773bf525054a9d8 Mon Sep 17 00:00:00 2001 From: XingY Date: Thu, 21 Jul 2016 17:08:20 -0700 Subject: [PATCH 377/587] Spec Issue 26850: fix automated tests --- .../pages/trialshare/CubeObjectEditPage.java | 2 ++ .../test/pages/trialshare/StudyEditPage.java | 17 +++++++++++++++++ .../tests/trialshare/DataFinderTestBase.java | 1 + .../trialshare/ManagePublicationsTest.java | 1 + .../tests/trialshare/ManageStudiesTest.java | 19 ++++++++++++++++--- .../trialshare/TrialShareDataFinderTest.java | 8 +++----- 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java index 68c30ae1..7ac647f6 100644 --- a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java @@ -130,6 +130,8 @@ public Map getFormValues() public Map compareFormValues(Map expectedValues) { + log("Hack-Nap while combo box gets filled. Maybe?"); + sleep(2000); Map formValues = getFormValues(); Map unexpectedValues = new HashMap<>(); for (String key : expectedValues.keySet()) diff --git a/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java b/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java index cdb62a12..d01b40f3 100644 --- a/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java @@ -1,5 +1,6 @@ package org.labkey.test.pages.trialshare; +import org.labkey.test.Locator; import org.openqa.selenium.WebDriver; import java.util.HashMap; @@ -83,4 +84,20 @@ public Set getFieldNames() { return FIELD_NAMES; } + + public Locator.CssLocator getStudyAccessPanelLocator(int i) + { + return Locator.css(".studyaccesspanelindex" + i); + } + + public Locator.CssLocator getStudyAccessPanelRemoveBtn(int i) + { + return getStudyAccessPanelLocator(i).append(Locator.css(".fa-times")); + } + + public void removeStudyAccessPanel(int i) + { + click(getStudyAccessPanelRemoveBtn(i)); + } + } diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java index 67877520..27d0e7d5 100644 --- a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java +++ b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java @@ -231,6 +231,7 @@ DataFinderPage goDirectlyToDataFinderPage(String containerPath, boolean testingS { finder.navigateToPublications(); } + sleep(1000); // HACK! wait so that default filters can be applied return finder; } diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index ff690e88..792e760b 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -460,6 +460,7 @@ public void testInsertAndRefresh() goToProjectHome(); finder = goDirectlyToDataFinderPage(getCurrentContainerPath(), false); + finder.clearAllFilters(); finder.search((String) initialFields.get(PublicationEditPage.TITLE)); dataCards = finder.getDataCards(); diff --git a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java index 02733bb8..44b7702f 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java @@ -102,27 +102,32 @@ public void testRequiredFields() ManageDataPage manageData = new ManageDataPage(this, _objectType); manageData.goToInsertNew(); StudyEditPage editPage = new StudyEditPage(this.getDriver()); + editPage.removeStudyAccessPanel(0); Assert.assertFalse("Submit button should not be enabled", editPage.isSubmitEnabled()); editPage.setTextFormValue("title", "testRequiredFields"); Assert.assertFalse("Submit button should not be enabled with only title", editPage.isSubmitEnabled()); doAndWaitForPageToLoad(editPage::cancel); manageData.goToInsertNew(); + editPage.removeStudyAccessPanel(0); editPage.setTextFormValue("shortName", "ShortName"); Assert.assertFalse("Submit button should not be enabled with only study type", editPage.isSubmitEnabled()); doAndWaitForPageToLoad(editPage::cancel); manageData.goToInsertNew(); + editPage.removeStudyAccessPanel(0); editPage.setTextFormValue("studyId", "StudyId"); Assert.assertFalse("Submit button should not be enabled with only study id", editPage.isSubmitEnabled()); doAndWaitForPageToLoad(editPage::cancel); manageData.goToInsertNew(); + editPage.removeStudyAccessPanel(0); editPage.setTextFormValue("title", "testRequiredFields"); editPage.setTextFormValue("shortName", "ShortName"); Assert.assertFalse("Submit button should not be enabled with title, short name but no studyId", editPage.isSubmitEnabled()); editPage.setTextFormValue("studyId", "StudyId", true); - Assert.assertTrue("Submit button should be enabled with all required fields", editPage.isSubmitEnabled()); + Assert.assertTrue("Submit button should be enabled with all required study fields", editPage.isSubmitEnabled()); + doAndWaitForPageToLoad(editPage::cancel); } @Test @@ -152,6 +157,7 @@ public void testInsertWithAllFields() manageData.goToInsertNew(); StudyEditPage editPage = new StudyEditPage(this.getDriver()); + editPage.removeStudyAccessPanel(0); editPage.setFormFields(newFields); editPage.submit(); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); @@ -184,6 +190,7 @@ public void testInsertMultiValuedFields() manageData.goToInsertNew(); StudyEditPage editPage = new StudyEditPage(this.getDriver()); + editPage.removeStudyAccessPanel(0); editPage.setFormFields(newFields); editPage.submit(); @@ -217,6 +224,7 @@ public void testEditMultiValuedFields() manageData.goToInsertNew(); StudyEditPage editPage = new StudyEditPage(this.getDriver()); + editPage.removeStudyAccessPanel(0); editPage.setFormFields(initialFields); editPage.submit(); @@ -233,6 +241,7 @@ public void testEditMultiValuedFields() newFields.put(StudyEditPage.CONDITIONS, new String[]{"Allergy", "Cat Allergy"}); // this removes "T1DM" newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); + editPage.removeStudyAccessPanel(0); editPage.setFormFields(newFields); editPage.submit(); @@ -274,6 +283,7 @@ public void testUpdateStudy() manageData.goToInsertNew(); StudyEditPage editPage = new StudyEditPage(this.getDriver()); + editPage.removeStudyAccessPanel(0); editPage.setFormFields(initialFields); editPage.submit(); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); @@ -292,6 +302,7 @@ public void testUpdateStudy() manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); + editPage.removeStudyAccessPanel(0); editPage.setFormFields(updatedFields); editPage.submit(); @@ -320,6 +331,7 @@ public void testInsertAndDelete() manageData.goToInsertNew(); StudyEditPage editPage = new StudyEditPage(this.getDriver()); + editPage.removeStudyAccessPanel(0); editPage.setFormFields(initialFields); editPage.submit(); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); @@ -364,6 +376,7 @@ public void testInsertAndRefresh() manageData.goToInsertNew(); StudyEditPage editPage = new StudyEditPage(this.getDriver()); + editPage.removeStudyAccessPanel(0); editPage.setFormFields(initialFields); editPage.submit(); @@ -374,7 +387,6 @@ public void testInsertAndRefresh() goToProjectHome(); // there should be no error alert after inserting but before refreshing DataFinderPage finder = goDirectlyToDataFinderPage(getCurrentContainerPath(), true); - finder.search(studyId); List dataCards = finder.getDataCards(); @@ -384,7 +396,8 @@ public void testInsertAndRefresh() manageData.refreshCube(); doAndWaitForPageSignal(this::goToProjectHome, finder.getCountSignal()); - + // wait for data to load + sleep(1000); finder.search(studyId); dataCards = finder.getDataCards(); diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 45f7d162..a4b0f790 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -374,6 +374,7 @@ public void testStudySearchPermissions() public void testPublicationSearch() { DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); + finder.clearAllFilters(); testSearchTerm(finder, DataFinderPage.Dimension.PUBLICATIONS, "Author", "Asare", 7); testSearchTerm(finder, DataFinderPage.Dimension.PUBLICATIONS, "PMID", "21953143", 1); @@ -488,13 +489,10 @@ public void testFilterOnStatus() String cardText; Map counts; - log("Go to publications and clear any filters that may have been set."); + log("Go to publications and verify the default filtered of 'In Progress' on Status."); DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); - finder.clearAllFilters(); - - log("Filter for 'In Progress' only publications."); DataFinderPage.FacetGrid fg = finder.getFacetsGrid(); - doAndWaitForPageSignal(() -> fg.toggleFacet(DataFinderPage.Dimension.STATUS, "In Progress"), finder.getCountSignal()); + assertEquals("Finder is not filtered by In Progress by default for internal user", Collections.singletonList(DataFinderPage.Dimension.IN_PROGRESS.getHierarchyName()), fg.getSelectedMembers(DataFinderPage.Dimension.STATUS)); log("Validate that the number, content and style of the cards is as expected."); counts = fg.getMemberCounts(DataFinderPage.Dimension.IN_PROGRESS); From 97654dfa3197b16bbea5bbc0b587588aa0feb3ec Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Thu, 21 Jul 2016 19:40:55 -0700 Subject: [PATCH 378/587] Issue 27243: reindex and clear cache when updating or deleting a cube object --- .../trialshare/TrialShareController.java | 10 ++++++++-- .../labkey/trialshare/TrialShareManager.java | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 37d45207..8b332e18 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -1062,7 +1062,7 @@ public Object execute(PublicationEditBean form, BindException errors) throws Exc { TrialShareManager.get().updatePublication(getUser(), getContainer(), form, errors); if (!errors.hasErrors()) - TrialShareManager.get().clearCache(errors); + TrialShareManager.get().refreshPublications(errors); return success(); } } @@ -1105,7 +1105,7 @@ public Object execute(StudyEditBean form, BindException errors) throws Exception { TrialShareManager.get().updateStudy(getUser(), getContainer(), form, errors); if (!errors.hasErrors()) - TrialShareManager.get().clearCache(errors); + TrialShareManager.get().refreshStudies(errors); return success(); } } @@ -1129,9 +1129,15 @@ public boolean handlePost(CubeObjectQueryForm form, BindException errors) throws { Set ids = DataRegionSelection.getSelected(form.getViewContext(), null, true, true); if (form.getObjectName() == ObjectName.publication) + { TrialShareManager.get().deletePublications(getUser(), getContainer(), ids, errors); + TrialShareManager.get().refreshPublications(errors); + } else if (form.getObjectName() == ObjectName.study) + { TrialShareManager.get().deleteStudies(getUser(), getContainer(), ids, errors); + TrialShareManager.get().refreshStudies(errors); + } else errors.reject(ERROR_MSG, "Invalid object name: " + form.getObjectName()); return !errors.hasErrors(); diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 83ea122e..8a9a64b3 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -521,10 +521,25 @@ public void clearCache(BindException errors) { Container _cubeContainer = getCubeContainer(null); if (_cubeContainer == null) - errors.reject("Container path is required", "Container path not provided"); - if (errors.hasErrors()) + { + if (errors != null) + errors.reject("Container path is required", "Container path not provided"); return; + } QueryService.get().cubeDataChanged(_cubeContainer); } + + public void refreshPublications(BindException errors) + { + PublicationDocumentProvider.reindex(); + clearCache(errors); + } + + public void refreshStudies(BindException errors) + { + StudyDocumentProvider.reindex(); + clearCache(errors); + } + } \ No newline at end of file From 40a1fa1653c8ae4dbadaaa8eead527fc31e64a27 Mon Sep 17 00:00:00 2001 From: XingY Date: Fri, 22 Jul 2016 13:46:51 -0700 Subject: [PATCH 379/587] Spec Issue 26850: Code review feedback --- resources/web/study/Finder/data/Visibility.js | 22 +++++ resources/web/study/Finder/dataFinder.css | 2 +- .../web/study/Finder/dataFinderEditor.lib.xml | 2 + .../study/Finder/panel/PublicationCards.js | 2 +- .../web/study/Finder/panel/StudyAccessForm.js | 76 ++++++++++++++++ .../Finder/panel/StudyAccessTuplePanel.js | 87 +------------------ .../Finder/panel/StudyDetailsFormPanel.js | 4 +- resources/web/study/Finder/trialShare.css | 4 +- 8 files changed, 109 insertions(+), 90 deletions(-) create mode 100644 resources/web/study/Finder/data/Visibility.js create mode 100644 resources/web/study/Finder/panel/StudyAccessForm.js diff --git a/resources/web/study/Finder/data/Visibility.js b/resources/web/study/Finder/data/Visibility.js new file mode 100644 index 00000000..b1a52726 --- /dev/null +++ b/resources/web/study/Finder/data/Visibility.js @@ -0,0 +1,22 @@ +Ext4.define('LABKEY.study.data.Visibility', { + extend: 'Ext.data.Model', + + idProperty: 'visibility', + + proxy : { + type : 'ajax', + url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), + extraParams : { + schemaName : 'lists', + queryName : 'visibility' + }, + reader : { + type : 'json', + root : 'rows' + } + }, + + fields: [ + {name: 'visibility'} + ] +}); \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css index f279c4b1..9f9062f6 100644 --- a/resources/web/study/Finder/dataFinder.css +++ b/resources/web/study/Finder/dataFinder.css @@ -777,7 +777,7 @@ div.labkey-study-details h3 { display: inline-block; } -.labkey-publication-right { +.labkey-publication-annotation { padding-left: 10px; vertical-align: top; display:inline-block; diff --git a/resources/web/study/Finder/dataFinderEditor.lib.xml b/resources/web/study/Finder/dataFinderEditor.lib.xml index b2dfee5d..2c77ca77 100644 --- a/resources/web/study/Finder/dataFinderEditor.lib.xml +++ b/resources/web/study/Finder/dataFinderEditor.lib.xml @@ -14,12 +14,14 @@ \ No newline at end of file diff --git a/src/org/labkey/trialshare/view/studyDetails.jsp b/src/org/labkey/trialshare/view/studyDetails.jsp index 42b30dfa..ac0c2fbf 100644 --- a/src/org/labkey/trialshare/view/studyDetails.jsp +++ b/src/org/labkey/trialshare/view/studyDetails.jsp @@ -69,7 +69,8 @@ renderTo: <%=q(renderId)%>, accessListId : <%= bean.getAccessListId() %>, cubeObject : <%= text( cubeObjectJson )%>, - studyaccesslist: <%= text( studyaccesslist )%> + studyaccesslist: <%= text( studyaccesslist )%>, + cubeContainerPath: "<%=h(bean.getCubeContainerPath())%>" }); }); \ No newline at end of file From e8fa1c8c361e3e6427da289b6f5d7a19d37a2c78 Mon Sep 17 00:00:00 2001 From: XingY Date: Thu, 29 Sep 2016 14:41:52 -0700 Subject: [PATCH 423/587] Issue 27990: Finder wizard dropdowns pull from /home lists instead of /studies --- resources/web/study/Finder/data/Visibility.js | 13 ------------- resources/web/study/Finder/panel/StudyAccessForm.js | 6 ++++-- .../web/study/Finder/panel/StudyAccessTuplePanel.js | 3 ++- .../web/study/Finder/panel/StudyDetailsFormPanel.js | 3 ++- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/resources/web/study/Finder/data/Visibility.js b/resources/web/study/Finder/data/Visibility.js index b1a52726..3b848d12 100644 --- a/resources/web/study/Finder/data/Visibility.js +++ b/resources/web/study/Finder/data/Visibility.js @@ -3,19 +3,6 @@ Ext4.define('LABKEY.study.data.Visibility', { idProperty: 'visibility', - proxy : { - type : 'ajax', - url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), - extraParams : { - schemaName : 'lists', - queryName : 'visibility' - }, - reader : { - type : 'json', - root : 'rows' - } - }, - fields: [ {name: 'visibility'} ] diff --git a/resources/web/study/Finder/panel/StudyAccessForm.js b/resources/web/study/Finder/panel/StudyAccessForm.js index ddb15bb1..6105a8e9 100644 --- a/resources/web/study/Finder/panel/StudyAccessForm.js +++ b/resources/web/study/Finder/panel/StudyAccessForm.js @@ -12,6 +12,7 @@ Ext4.define('LABKEY.study.panel.StudyAccessForm', { }, getFormFields: function() { + var me = this; var items = []; items.push( { @@ -23,8 +24,9 @@ Ext4.define('LABKEY.study.panel.StudyAccessForm', { allowBlank : false, name : 'visibility', store : { - model : 'LABKEY.study.data.Visibility', - autoLoad: true + model : 'LABKEY.study.data.Visibility', + autoLoad : true, + proxy : LABKEY.study.util.CubeObjectHelper.getModelProxy('visibility', me.cubeContainerPath) }, fieldLabel : 'Visibility *', labelWidth : this.defaultFieldLabelWidth, diff --git a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js index aa02407b..4804341e 100644 --- a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js +++ b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js @@ -82,7 +82,8 @@ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { var form = Ext4.create('LABKEY.study.panel.StudyAccessForm', { mode: this.mode, - store : studyAccessStore + store : studyAccessStore, + cubeContainerPath: this.cubeContainerPath }); formItems.push(form); diff --git a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js index c2d41794..28320201 100644 --- a/resources/web/study/Finder/panel/StudyDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/StudyDetailsFormPanel.js @@ -255,7 +255,8 @@ Ext4.define('LABKEY.study.panel.StudyDetailsFormPanel', { Ext4.create('LABKEY.study.panel.StudyAccessTuplePanel', { studyaccesslist: this.studyaccesslist, width: this.largeFieldWidth, - mode: this.mode + mode: this.mode, + cubeContainerPath: this.cubeContainerPath }) ], scope: this From 5bb82a758b3b3b51ddd3e7895bc0c0c188c5386a Mon Sep 17 00:00:00 2001 From: XingY Date: Thu, 29 Sep 2016 16:35:23 -0700 Subject: [PATCH 424/587] Issue 27999: Finder wizard not saving publications --- .../web/study/Finder/panel/CubeObjectDetailsFormPanel.js | 2 +- resources/web/study/Finder/panel/FinderCardPanelHeader.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js index 0d4f0671..788b5394 100644 --- a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js @@ -168,7 +168,7 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { } Ext4.Ajax.request({ - url: LABKEY.ActionURL.buildURL('trialShare', this.mode + this.objectName + ".api"), + url: LABKEY.ActionURL.buildURL('trialShare', this.mode + this.objectName + ".api", this.cubeContainerPath), method: 'POST', jsonData: this.getFieldValues(), success: onSuccess, diff --git a/resources/web/study/Finder/panel/FinderCardPanelHeader.js b/resources/web/study/Finder/panel/FinderCardPanelHeader.js index 026a2df0..ccd8a837 100644 --- a/resources/web/study/Finder/panel/FinderCardPanelHeader.js +++ b/resources/web/study/Finder/panel/FinderCardPanelHeader.js @@ -136,7 +136,7 @@ Ext4.define("LABKEY.study.panel.FinderCardPanelHeader", { scope: this, // renderTo: this.objectName + '-manage-data-link', handler: function() { - window.open(LABKEY.ActionURL.buildURL(this.dataModuleName, "manageData.view", null, { + window.open(LABKEY.ActionURL.buildURL(this.dataModuleName, "manageData.view", this.cubeContainerPath, { objectName : this.objectName, 'query.viewName' : 'manageData' }), '_blank'); @@ -154,7 +154,7 @@ Ext4.define("LABKEY.study.panel.FinderCardPanelHeader", { componentCls: 'labkey-finder-insert-new', scope: this, handler: function() { - window.open(LABKEY.ActionURL.buildURL(this.dataModuleName, "insertDataForm.view", null, { + window.open(LABKEY.ActionURL.buildURL(this.dataModuleName, "insertDataForm.view", this.cubeContainerPath, { objectName : this.objectName, 'query.viewName' : 'manageData' }), '_blank'); From 13cde722fc820cd1ffcd6fb6318a52869322e06a Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 4 Oct 2016 07:39:28 -0700 Subject: [PATCH 425/587] Issue 28503 - prevent double encoding of space in URL --- .../web/study/Finder/panel/CubeObjectDetailsFormPanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js index 788b5394..9095392a 100644 --- a/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js +++ b/resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js @@ -73,7 +73,7 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { ] }]; - if(LABKEY.ActionURL.getParameter('objectName') === 'publication') { + if (LABKEY.ActionURL.getParameter('objectName') === 'publication') { this.dockedItems[0].items.push( { xtype: 'tbspacer', @@ -86,7 +86,7 @@ Ext4.define('LABKEY.study.panel.CubeObjectDetailsFormPanel', { handler: function (btn) { var studyIds = this.getForm().getValues().studyIds; - var url = LABKEY.ActionURL.buildURL('project', 'begin.view', 'Studies/' + studyIds[0] + 'OPR/Study%20Data', + var url = LABKEY.ActionURL.buildURL('project', 'begin.view', 'Studies/' + studyIds[0] + 'OPR/Study Data', {pageId: 'Manuscripts', publicationId: LABKEY.ActionURL.getParameter('id')}); window.open(url, '_blank'); }, From 5959e6c636dc2253c49a38b1c7381d29a0ca160f Mon Sep 17 00:00:00 2001 From: labkey-danield Date: Wed, 5 Oct 2016 08:46:32 -0700 Subject: [PATCH 426/587] Updating Trial Share test to validate that work bench button is encoded correctly. --- .../trialshare/ManagePublicationsTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index 1cbadf50..34e20996 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -13,6 +13,9 @@ import org.labkey.test.pages.trialshare.PublicationsListHelper; import org.labkey.test.pages.trialshare.StudiesListHelper; +import java.net.URI; +import java.net.URL; +import java.net.URLDecoder; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -414,6 +417,31 @@ public void testUpdatePublication() manageData.goToEditRecord((String) initialFields.get(TITLE)); Assert.assertTrue("Workbench button should be enabled if data has not changed", editPage.isWorkbenchEnabled()); + + try + { + int winCount = getDriver().getWindowHandles().size(); + clickButton("Workbench", 0); + + // Wait for the window to show up. + while(getDriver().getWindowHandles().size() == winCount) + sleep(500); + + switchToWindow(1); + String url = getDriver().getCurrentUrl(); + String decodedUrl = URLDecoder.decode(url, "UTF-8"); + Assert.assertEquals("Decoded url pointed to by the 'Workbench' button doesn't look right. Is it double encoded? '" + url + "'", decodedUrl, URLDecoder.decode(decodedUrl, "UTF-8")); + } + catch(java.io.UnsupportedEncodingException uee) + { + log("Got a UnsupportedEncodingException, going skip the encoding test."); + } + finally + { + getDriver().close(); + switchToMainWindow(); + } + editPage.setFormFields(updatedFields, false); Assert.assertFalse("Workbench button should be disabled if data has changed", editPage.isWorkbenchEnabled()); editPage.save(); From f6d22a697406c9a573818285c7346a572d6d3c89 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 17 Oct 2016 16:50:04 -0700 Subject: [PATCH 427/587] Issue 28199 - fix foreign key for therapeutic area. --- resources/olap/PublicationCube.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/olap/PublicationCube.xml b/resources/olap/PublicationCube.xml index ed69afda..7ad8e53a 100644 --- a/resources/olap/PublicationCube.xml +++ b/resources/olap/PublicationCube.xml @@ -13,7 +13,7 @@ - +
From de3de89c9be496c0d1523b7b73318c121b511df5 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 19 Oct 2016 14:00:01 -0700 Subject: [PATCH 428/587] Issue 28199 - add automated tests for checking count updates --- .../test/pages/trialshare/DataFinderPage.java | 122 ++++++++++++++---- .../trialshare/ManagePublicationsTest.java | 83 +++++++++++- .../trialshare/TrialShareDataFinderTest.java | 36 +++--- 3 files changed, 195 insertions(+), 46 deletions(-) diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java index cb67d7d8..64cd9ae4 100644 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java @@ -44,6 +44,7 @@ public class DataFinderPage extends LabKeyPage private static final String GROUP_UPDATED_SIGNAL = "participantGroupUpdated"; private static final String PUBLICATION_DETAILS_SIGNAL = "publicationDetailsLoaded"; private boolean testingStudies = true; + private ObjectType objectType = null; private Locator.CssLocator finderLocator = null; public DataFinderPage(BaseWebDriverTest test, boolean testingStudies) @@ -52,10 +53,12 @@ public DataFinderPage(BaseWebDriverTest test, boolean testingStudies) this.testingStudies = testingStudies; if (testingStudies) { + objectType = ObjectType.study; finderLocator = Locators.studyFinder; } else { + objectType = ObjectType.publication; finderLocator = Locators.pubFinder; } } @@ -228,7 +231,7 @@ public List getDataCards() public FacetGrid getFacetsGrid() { - if(testingStudies) + if (testingStudies) return new FacetGrid(DataFinderPage.Locators.studyFacetPanel.findElement(getDriver())); else return new FacetGrid(DataFinderPage.Locators.pubFacetPanel.findElement(getDriver())); @@ -315,7 +318,7 @@ public static class Locators public static final Locator.CssLocator insertNewStudy = Locator.css(".labkey-studies-panel .labkey-finder-insert-new"); public static final Locator.CssLocator insertNewPublication = Locator.css(".labkey-publications-panel .labkey-finder-insert-new"); - public static final Locator.CssLocator getSearchInput(Locator.CssLocator locator) + public static Locator.CssLocator getSearchInput(Locator.CssLocator locator) { return locator.append(" input.labkey-search-box"); } @@ -337,42 +340,46 @@ public static Locator.XPathLocator finderObjectTab(String objectName) } + public enum ObjectType + { + all, study, publication + } + public enum Dimension { // Common - STUDIES("studies", null), + SUMMARY_STUDIES("studies", null, ObjectType.all), // Study focused. - SUBJECTS("subjects", null), - VISIBILITY("visibility","Study.Visibility"), - THERAPEUTIC_AREA("therapeutic area", "Study.Therapeutic Area"), - STUDY_TYPE("study type", "Study.Study Type"), - ASSAY("assay", "Study.Assay"), - AGE_GROUP("age group", "Study.AgeGroup"), - PHASE("phase", "Study.Phase"), - CONDITION("condition", "Study.Condition"), + SUBJECTS("subjects", null, ObjectType.study), + VISIBILITY("visibility","Study.Visibility", ObjectType.study), + THERAPEUTIC_AREA("therapeutic area", "Study.Therapeutic Area", ObjectType.study), + STUDY_TYPE("study type", "Study.Study Type", ObjectType.study), + ASSAY("assay", "Study.Assay", ObjectType.study), + AGE_GROUP("age group", "Study.AgeGroup", ObjectType.study), + PHASE("phase", "Study.Phase", ObjectType.study), + CONDITION("condition", "Study.Condition", ObjectType.study), // Publication focused. - PUBLICATIONS("publications", null), - STATUS("status", "Publication.Status"), - COMPLETE("complete", "Complete"), - IN_PROGRESS("in progress", "In Progress"), - PUBLICATION_TYPE("publication type", "Publication.Publication Type"), - PUB_THERAPEUTIC_AREA("therapeutic area", "Publication.Therapeutic Area"), - YEAR("year", "Publication.Year"), - PUBLICATION_JOURNAL("journal", "Publication.Journal"), - PUB_STUDY("study", "Publication.Study"), - PUB_ASSAY("assay", "Publication.Assay"), - PUB_CONDITION("condition", "Publication.Condition"); - + PUBLICATIONS("publications", null, ObjectType.publication), + STATUS("status", "Publication.Status", ObjectType.publication), + COMPLETE("complete", "Complete", ObjectType.publication), + IN_PROGRESS("in progress", "In Progress", ObjectType.publication), + PUBLICATION_TYPE("publication type", "Publication.Publication Type", ObjectType.publication), + PUB_THERAPEUTIC_AREA("therapeutic area", "Publication.Therapeutic Area", ObjectType.publication), + SUBMISISON_STATUS("submission status", "Publication.Submission Status", ObjectType.publication), + YEAR("year", "Publication.Year", ObjectType.publication), + PUB_STUDY("study", "Publication.Study", ObjectType.publication),; private String caption; private String hierarchyName; + private ObjectType objectType; - Dimension(String caption, String hierarchyName) + Dimension(String caption, String hierarchyName, ObjectType objectType) { this.caption = caption; this.hierarchyName = hierarchyName; + this.objectType = objectType; } public String getCaption() @@ -385,6 +392,11 @@ public String getHierarchyName() return hierarchyName; } + public ObjectType getObjectType() + { + return objectType; + } + public static Dimension fromString(String value) { for (Dimension dimension : values()) @@ -468,6 +480,39 @@ private class Elements public class FacetGrid extends Component { + public class MemberCount + { + private Integer selectedCount; + private Integer totalCount; + + public MemberCount(String countString) + { + String[] values = countString.split("/"); + selectedCount = Integer.valueOf(values[0].trim()); + totalCount = Integer.valueOf(values[1].trim()); + } + + public int getSelectedCount() + { + return selectedCount; + } + + public void setSelectedCount(Integer selectedCount) + { + this.selectedCount = selectedCount; + } + + public int getTotalCount() + { + return totalCount; + } + + public void setTotalCount(Integer totalCount) + { + this.totalCount = totalCount; + } + + } private WebElement grid; private Locators locators; @@ -530,20 +575,43 @@ public List getSelectedMembers(Dimension dimension) return selectedNames; } - public Map getMemberCounts(Dimension dimension) + public Map getMemberCounts(Dimension dimension) { List memberElements = locators.facetMembers(dimension).findElements(getDriver()); - Map countMap = new HashMap<>(); + Map countMap = new HashMap<>(); for (WebElement member : memberElements) { String name = locators.memberName.findElement(member).getText(); String count = locators.memberCount.findElement(member).getText(); - countMap.put(name, count); + MemberCount memberCount = new MemberCount(count); + countMap.put(name, memberCount); } return countMap; } + public Map> getAllMemberCounts() + { + Map> counts = new HashMap<>(); + for (Dimension dimension : Dimension.values()) + { + if (dimension.getObjectType() == objectType) + counts.put(dimension.getCaption(), getMemberCounts(dimension)); + } + return counts; + } + + public int getSelectedCount(Map> counts, DataFinderPage.Dimension dimension, String member) + { + Map dimCount = counts.get(dimension.getCaption()); + if (dimCount == null) + return 0; + DataFinderPage.FacetGrid.MemberCount memberCount = dimCount.get(member); + if (memberCount == null) + return 0; + return memberCount.getSelectedCount(); + } + private class Locators { public Locator.XPathLocator facetMemberRow(Dimension dimension, String name) diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index 34e20996..5427e2d4 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -13,8 +13,6 @@ import org.labkey.test.pages.trialshare.PublicationsListHelper; import org.labkey.test.pages.trialshare.StudiesListHelper; -import java.net.URI; -import java.net.URL; import java.net.URLDecoder; import java.util.HashMap; import java.util.List; @@ -273,6 +271,87 @@ public void testInsertWithAllFields() Assert.assertTrue("Found unexpected values in edit page of newly inserted publication: " + unexpectedValues, unexpectedValues.isEmpty()); } + @Test + public void testCountsAfterInsertAndEdit() + { + goToProjectHome(); + StudiesListHelper studiesListHelper = new StudiesListHelper(this); + studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); + studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); + goToProjectHome(); + DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); + DataFinderPage.FacetGrid fg = finder.getFacetsGrid(); + finder.clearAllFilters(); + Map> beforeCounts = fg.getAllMemberCounts(); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + Map newFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + int count = manageData.getCount(); + newFields.put(PublicationEditPage.TITLE, "testCountsAfterInsertAndEdit_" + count); + newFields.put(PublicationEditPage.STATUS, "In Progress"); + newFields.put(PublicationEditPage.SUBMISSION_STATUS, "Submitted"); + newFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); + newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); + newFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); + newFields.put(PublicationEditPage.YEAR, "2016"); + + newFields.put(PublicationEditPage.MANUSCRIPT_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); + newFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); + + manageData.goToInsertNew(); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + + editPage.setFormFields(newFields, true); + editPage.saveAndClose(); + + goToProjectHome(); + goDirectlyToDataFinderPage(getProjectName(), false); + finder.clearAllFilters(); + Map> afterInsertCounts = fg.getAllMemberCounts(); + assertEquals("Count for 'In Progress' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.STATUS, "In Progress")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STATUS, "In Progress")); + assertEquals("Count for 'Submitted' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted")); + assertEquals("Count for 'Manuscript' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript")); + assertEquals("Count for 'Autoimmune' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Autoimmune")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Autoimmune")); + assertEquals("Count for '" + PUBLIC_STUDY_NAME + "' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PUB_STUDY, PUBLIC_STUDY_ID)+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PUB_STUDY, PUBLIC_STUDY_ID)); + assertEquals("Count for '2016' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.YEAR, "2016")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.YEAR, "2016")); + + + // Now edit the above fields to have different values and check counts again. + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + manageData.goToEditRecord((String) newFields.get(TITLE)); + newFields.remove(PublicationEditPage.TITLE); + newFields.put(PublicationEditPage.STATUS, "Complete"); + newFields.put(PublicationEditPage.SUBMISSION_STATUS, "Draft"); + newFields.put(PublicationEditPage.PUBLICATION_TYPE, "Abstract"); + // for these two multi-select fields, the items that are selected will be deselected and ones not selected will be selected + newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune", "Allergy"}); + newFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID, OPERATIONAL_STUDY_ID}); + newFields.put(PublicationEditPage.YEAR, "2014"); + + editPage.setFormFields(newFields, false); + editPage.saveAndClose(); + goToProjectHome(); + goDirectlyToDataFinderPage(getProjectName(), false); + finder.clearAllFilters(); + Map> afterEditCounts = fg.getAllMemberCounts(); + assertEquals("Count for 'In Progress' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.STATUS, "In Progress"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STATUS, "In Progress")); + assertEquals("Count for 'Complete' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STATUS, "Complete")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STATUS, "Complete")); + assertEquals("Count for 'Submitted' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted")); + assertEquals("Count for 'Draft' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Draft")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Draft")); + assertEquals("Count for 'Manuscript' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript")); + assertEquals("Count for 'Abstract' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Abstract")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Abstract")); + assertEquals("Count for 'Autoimmune' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Autoimmune"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Autoimmune")); + assertEquals("Count for 'Allergy' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Allergy")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Allergy")); + assertEquals("Count for '" + PUBLIC_STUDY_ID + "' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PUB_STUDY, PUBLIC_STUDY_ID), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PUB_STUDY, PUBLIC_STUDY_ID)); + assertEquals("Count for '" + OPERATIONAL_STUDY_ID + "' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PUB_STUDY, OPERATIONAL_STUDY_ID)+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PUB_STUDY, OPERATIONAL_STUDY_ID)); + assertEquals("Count for '2016' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.YEAR, "2016"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.YEAR, "2016")); + assertEquals("Count for '2014' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.YEAR, "2014")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.YEAR, "2014")); + } + @Test public void testInsertMultiValuedFields() { diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 22b6ed22..41abfef6 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -162,7 +162,7 @@ else if (accession.equalsIgnoreCase("WISP-R")) public void testCounts() { DataFinderPage finder = new DataFinderPage(this, true); - assertCountsSynced(finder, DataFinderPage.Dimension.STUDIES); + assertCountsSynced(finder, DataFinderPage.Dimension.SUMMARY_STUDIES); Map studyCounts = finder.getSummaryCounts(); @@ -278,7 +278,7 @@ public void testSelection() facets.toggleFacet(DataFinderPage.Dimension.AGE_GROUP, "Adult"); facets.toggleFacet(DataFinderPage.Dimension.PHASE, "Phase 1"); - assertCountsSynced(finder, DataFinderPage.Dimension.STUDIES); + assertCountsSynced(finder, DataFinderPage.Dimension.SUMMARY_STUDIES); facets.clearFilter(DataFinderPage.Dimension.PHASE); @@ -294,14 +294,14 @@ public void testSelection() finder.clearAllFilters(); assertEquals("Clearing all filters didn't clear selection", Collections.emptyList(), facets.getSelectedMembers(DataFinderPage.Dimension.PHASE)); - assertCountsSynced(finder, DataFinderPage.Dimension.STUDIES); + assertCountsSynced(finder, DataFinderPage.Dimension.SUMMARY_STUDIES); } @Test public void testSelectingEmptyMeasure() { Map expectedCounts = new HashMap<>(); - expectedCounts.put(DataFinderPage.Dimension.STUDIES, 0); + expectedCounts.put(DataFinderPage.Dimension.SUMMARY_STUDIES, 0); expectedCounts.put(DataFinderPage.Dimension.SUBJECTS, 0); DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); @@ -320,10 +320,10 @@ public void testSelectingEmptyMeasure() { if (dimension.getHierarchyName() != null) { - Map memberCounts = facets.getMemberCounts(dimension); - for (Map.Entry memberCount : memberCounts.entrySet()) + Map memberCounts = facets.getMemberCounts(dimension); + for (Map.Entry memberCount : memberCounts.entrySet()) { - assertTrue("Wrong counts for member " + memberCount.getKey() + " of dimension " + dimension + " after selecting empty measure: " + memberCount.getValue(), memberCount.getValue().matches("0 / \\d+")); + assertEquals("Wrong counts for member " + memberCount.getKey() + " of dimension " + dimension + " after selecting empty measure", 0, memberCount.getValue().getSelectedCount()); } } } @@ -341,15 +341,15 @@ public void testStudySearch() List studyCards = finder.getDataCards(); String searchString = studyCards.get(0).getStudyAccession(); - testSearchTerm(finder, DataFinderPage.Dimension.STUDIES, "Study Accession", searchString, 1); + testSearchTerm(finder, DataFinderPage.Dimension.SUMMARY_STUDIES, "Study Accession", searchString, 1); finder.clearSearch(); assertTrue("Clear all should still be active after clearing search with facet selected", finder.clearAllActive()); finder.clearAllFilters(); assertTrue("Clear All should no longer be active", !finder.clearAllActive()); - testSearchTerm(finder, DataFinderPage.Dimension.STUDIES, "Study Short Name and Investigator", "Casale", 1); - testSearchTerm(finder, DataFinderPage.Dimension.STUDIES, "Empty", "", 8); - testSearchTerm(finder, DataFinderPage.Dimension.STUDIES, "Title", "System", 2); - testSearchTerm(finder, DataFinderPage.Dimension.STUDIES, "Multiple Terms", "Tolerant Kidney Transplant", 7); + testSearchTerm(finder, DataFinderPage.Dimension.SUMMARY_STUDIES, "Study Short Name and Investigator", "Casale", 1); + testSearchTerm(finder, DataFinderPage.Dimension.SUMMARY_STUDIES, "Empty", "", 8); + testSearchTerm(finder, DataFinderPage.Dimension.SUMMARY_STUDIES, "Title", "System", 2); + testSearchTerm(finder, DataFinderPage.Dimension.SUMMARY_STUDIES, "Multiple Terms", "Tolerant Kidney Transplant", 7); } @@ -366,7 +366,7 @@ public void testStudySearchPermissions() assertEquals("Wrong number of studies after search", 2, studyCards.size()); assertEquals("Wrong study card available", "Shapiro", studyCards.get(0).getStudyShortName()); - assertCountsSynced(finder, DataFinderPage.Dimension.STUDIES); + assertCountsSynced(finder, DataFinderPage.Dimension.SUMMARY_STUDIES); stopImpersonating(); } @@ -487,7 +487,7 @@ public void testFilterOnStatus() String cardTitle = "Circulating markers of vascular injury"; String cardAuthors = "Monach PA, Tomasson G, Specks U, et al."; String cardText; - Map counts; + Map counts; log("Go to publications and verify the default filtered of 'In Progress' on Status."); DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); @@ -496,7 +496,8 @@ public void testFilterOnStatus() log("Validate that the number, content and style of the cards is as expected."); counts = fg.getMemberCounts(DataFinderPage.Dimension.IN_PROGRESS); - assertEquals("Expected count after filtering for 'In Progress' was not as expected.", "1 / 1", counts.get("In Progress")); + assertEquals("Expected count after filtering for 'In Progress' was not as expected.", 1, counts.get("In Progress").getSelectedCount()); + assertEquals("Expected count after filtering for 'In Progress' was not as expected.", 1, counts.get("In Progress").getTotalCount()); // I have no idea why assertTextPresent returned false for these strings. The below tests appear to be more reliable. scrollIntoView(DataFinderPage.Locators.pubCardBorderHighlight); @@ -518,7 +519,8 @@ public void testFilterOnStatus() log("Validate counts for 'Complete' publications."); counts = fg.getMemberCounts(DataFinderPage.Dimension.COMPLETE); // one is "in progress" and one is set to not show - assertEquals("Expected count after filtering for 'Complete' was not as expected.", "15 / 15", counts.get("Complete")); + assertEquals("Expected count after filtering for 'Complete' was not as expected.", 15, counts.get("Complete").getSelectedCount()); + assertEquals("Expected count after filtering for 'Complete' was not as expected.", 15, counts.get("Complete").getTotalCount()); log("Validate that there are no 'In Progress' cards visible."); assertElementNotPresent("There is a card with the 'In Progress' style, there should not be.", DataFinderPage.Locators.pubCardBorderHighlight); @@ -594,7 +596,7 @@ public void testPublicationDetail() fg.toggleFacet(DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript"); summaryCount = finder.getSummaryCounts(); - assertEquals("Number of studies count not as expected.", 2, summaryCount.get(DataFinderPage.Dimension.STUDIES).intValue()); + assertEquals("Number of studies count not as expected.", 2, summaryCount.get(DataFinderPage.Dimension.SUMMARY_STUDIES).intValue()); log("Show details, this time validate that the missing values are rendered as expected."); card = finder.getDataCards().get(0); From 31be66a16a0ac4827fb35eedb6f24a7205794ea0 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 19 Oct 2016 14:59:03 -0700 Subject: [PATCH 429/587] Issue 28199 - add tests for study counts --- .../tests/trialshare/ManageStudiesTest.java | 82 ++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java index f66023b9..f71a5a43 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java @@ -184,6 +184,84 @@ public void testInsertWithAllFields() Assert.assertTrue("Found unexpected values in edit page of newly inserted study: " + unexpectedValues, unexpectedValues.isEmpty()); } + @Test + public void testCountsAfterInsertAndEdit() + { + goToProjectHome(); + DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); + DataFinderPage.FacetGrid fg = finder.getFacetsGrid(); + finder.clearAllFilters(); + Map> beforeCounts = fg.getAllMemberCounts(); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + ManageDataPage manageData = new ManageDataPage(this, _objectType); + + int count = manageData.getCount(); + Map newFields = new HashMap<>(); + // add the count so multiple runs of this test have distinct titles + newFields.put(StudyEditPage.SHORT_NAME, "TCAIAE" + count); + newFields.put(StudyEditPage.STUDY_ID, "TCAIAE_ID" + count); + newFields.put(StudyEditPage.TITLE, "testCountsAfterInsertAndEdit_" + count); + + newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); + newFields.put(StudyEditPage.STUDY_TYPE, "Interventional"); + newFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child"}); + newFields.put(StudyEditPage.PHASES, new String[]{"Phase 0"}); + newFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema"}); + + manageData.goToInsertNew(); + StudyEditPage editPage = new StudyEditPage(this.getDriver()); + + editPage.setFormFields(newFields); + + Map studyAccessFields = new HashMap<>(); + studyAccessFields.put(StudyEditPage.VISIBILITY, "Public"); + studyAccessFields.put(StudyEditPage.STUDY_CONTAINER, "/home"); + + log("Set values for the first study access form"); + editPage.setStudyAccessFormValues(0, studyAccessFields); + editPage.saveAndClose(); + + goToProjectHome(); + goDirectlyToDataFinderPage(getProjectName(), true); + + Map> afterInsertCounts = fg.getAllMemberCounts(); + assertEquals("Count for 'T1DM' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "TIDM")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "T1DM")); + assertEquals("Count for 'Interventional' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.STUDY_TYPE, "Interventional")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STUDY_TYPE, "Interventional")); + assertEquals("Count for 'Child' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.AGE_GROUP, "Child")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.AGE_GROUP, "Child")); + assertEquals("Count for 'Phase 0' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PHASE, "Phase 0")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PHASE, "Phase 0")); + assertEquals("Count for 'Eczema' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.CONDITION, "Eczema")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.CONDITION, "Eczema")); + + goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + manageData.goToEditRecord((String) newFields.get(StudyEditPage.STUDY_ID)); + newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"Transplant"}); // add transplant and leave T1DM + newFields.put(StudyEditPage.STUDY_TYPE, "Expanded Access"); + newFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Senior"}); // add senior and leave child + newFields.put(StudyEditPage.PHASES, new String[]{"Phase 0", "Phase 1"}); // remove Phase 0 and add Phase 1 + newFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema", "Allergy", "Cat Allergy"}); // remove Eczema and add two allergies + newFields.remove(StudyEditPage.SHORT_NAME); + newFields.remove(StudyEditPage.STUDY_ID); + newFields.remove(StudyEditPage.TITLE); + + editPage.setFormFields(newFields); + editPage.saveAndClose(); + goToProjectHome(); + goDirectlyToDataFinderPage(getProjectName(), true); + finder.clearAllFilters(); + Map> afterEditCounts = fg.getAllMemberCounts(); + assertEquals("Count for 'T1DM' updated when it should not have been", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "T1DM"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "T1DM")); + assertEquals("Count for 'Transplant' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Transplant") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Transplant")); + assertEquals("Count for 'Interventional' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.STUDY_TYPE, "Interventional"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STUDY_TYPE, "Intervetnional")); + assertEquals("Count for 'Expanded Access' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STUDY_TYPE, "Expanded Access") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STUDY_TYPE, "Expanded Access")); + assertEquals("Count for 'Child' updated when it should not have been", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.AGE_GROUP, "Child"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.AGE_GROUP, "Child")); + assertEquals("Count for 'Senior' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.AGE_GROUP, "Senior") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.AGE_GROUP, "Senior")); + assertEquals("Count for 'Phase 0' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PHASE, "Phase 0"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PHASE, "Phase 0")); + assertEquals("Count for 'Phase 1' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PHASE, "Phase 1") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PHASE, "Phase 1")); + assertEquals("Count for 'Eczema' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.CONDITION, "Eczema"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.CONDITION, "Eczema")); + assertEquals("Count for 'Allergy' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.CONDITION, "Allergy") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.CONDITION, "Allergy")); + assertEquals("Count for 'Cat Allergy' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.CONDITION, "Cat Allergy") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.CONDITION, "Cat Allergy")); + } + @Test public void testInsertMultiValuedFields() { @@ -373,8 +451,8 @@ public void testInsertWithoutRefresh() ManageDataPage manageData = new ManageDataPage(this, _objectType); int count = manageData.getCount(); - String shortName = "TIAR" + count; - String studyId = "TIAR_ID" + count; + String shortName = "TIWR" + count; + String studyId = "TIWR_ID" + count; createStudy(shortName, false); Map initialFields = new HashMap<>(); From a81e78c7d7e8e1b84e0e9ac2285019126ef0ad10 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 21 Oct 2016 08:27:49 -0700 Subject: [PATCH 430/587] Issue 28241 - add returnUrl for cube object edit page; make all tests use data finder in a separate project from the data lists and studies --- .../Finder/panel/FinderCardPanelHeader.js | 3 +- .../study/Finder/panel/PublicationCards.js | 3 +- .../query/ManageCubeObjectQueryView.java | 1 + .../trialshare/PublicationPanel.java | 7 +- .../pages/trialshare/CubeObjectEditPage.java | 10 +- .../test/pages/trialshare/DataFinderPage.java | 33 +----- .../tests/trialshare/DataFinderTestBase.java | 67 ++++++++--- .../trialshare/ManagePublicationsTest.java | 106 ++++++++++++------ .../tests/trialshare/ManageStudiesTest.java | 91 +++++++-------- .../trialshare/TrialShareDataFinderTest.java | 51 ++------- 10 files changed, 198 insertions(+), 174 deletions(-) diff --git a/resources/web/study/Finder/panel/FinderCardPanelHeader.js b/resources/web/study/Finder/panel/FinderCardPanelHeader.js index ccd8a837..172c5e51 100644 --- a/resources/web/study/Finder/panel/FinderCardPanelHeader.js +++ b/resources/web/study/Finder/panel/FinderCardPanelHeader.js @@ -138,7 +138,8 @@ Ext4.define("LABKEY.study.panel.FinderCardPanelHeader", { handler: function() { window.open(LABKEY.ActionURL.buildURL(this.dataModuleName, "manageData.view", this.cubeContainerPath, { objectName : this.objectName, - 'query.viewName' : 'manageData' + 'query.viewName' : 'manageData', + returnUrl : window.location }), '_blank'); } }); diff --git a/resources/web/study/Finder/panel/PublicationCards.js b/resources/web/study/Finder/panel/PublicationCards.js index 0aead171..e5d4fbf8 100644 --- a/resources/web/study/Finder/panel/PublicationCards.js +++ b/resources/web/study/Finder/panel/PublicationCards.js @@ -171,7 +171,8 @@ Ext4.define("LABKEY.study.panel.PublicationCards", { { var url = LABKEY.ActionURL.buildURL('trialshare', 'updateData', null, { "id": publicationId, - "objectName": 'publication' + "objectName": 'publication', + "returnUrl" : window.location + (Object.keys(LABKEY.ActionURL.getParameters()).length ? "&" : "?") + "object=publication" }); return url; } diff --git a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java index 77aeb781..c64d7f4a 100644 --- a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java +++ b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java @@ -119,6 +119,7 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep String id = String.valueOf(ctx.get(keyFieldKey)); ActionURL actionUrl = new ActionURL(TrialShareController.UpdateDataAction.class, cubeContainer).addParameter("id", id); actionUrl.addParameter("objectName", getCubeObjectName().toString()); + actionUrl.addReturnURL(getViewContext().getActionURL()); out.write(PageFlowUtil.textLink("Edit", actionUrl)); } }; diff --git a/test/src/org/labkey/test/components/trialshare/PublicationPanel.java b/test/src/org/labkey/test/components/trialshare/PublicationPanel.java index b0810019..3027c230 100644 --- a/test/src/org/labkey/test/components/trialshare/PublicationPanel.java +++ b/test/src/org/labkey/test/components/trialshare/PublicationPanel.java @@ -20,7 +20,6 @@ import org.labkey.test.components.Component; import org.labkey.test.util.LogMethod; import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.ExpectedConditions; public class PublicationPanel extends Component { @@ -95,6 +94,11 @@ public String getStudyShortName() return getTextOfElement(Locators.studyShortName); } + public void clickEditLink() + { + Locators.editLink.findElement(_test.getDriver()).click(); + } + @LogMethod(quiet = true) private String getTextOfElement(Locator el) { @@ -120,6 +124,7 @@ private static class Locators private static Locator.XPathLocator PMCID = Locator.xpath("//div[contains(@class, 'labkey-publication-detail')]//span[contains(@class, 'labkey-publication-identifier')]//a[contains(text(), 'PMCID')]"); private static Locator.XPathLocator DOI = Locator.xpath("//div[contains(@class, 'labkey-publication-detail')]//span[contains(@class, 'labkey-publication-identifier')]//a[contains(text(), 'DOI')]"); private static Locator.CssLocator studyShortName = Locator.css(".labkey-study-short-name"); + private static Locator.XPathLocator editLink = Locator.linkWithText("Edit"); } } diff --git a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java index 8c70df11..8bfdc1bf 100644 --- a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java @@ -188,11 +188,11 @@ public void save() waitForElement(Locators.updateTitle); } - public void saveAndClose() + public void saveAndClose(String returnToHeader) { log("Saving and closing edit form"); click(Locators.saveAndCloseButton); - waitForElement(Locators.manageTitle); + waitForElement(Locators.getTitleTextLocator(returnToHeader)); } private static class Locators @@ -203,8 +203,12 @@ private static class Locators static final Locator disabledSaveAndCloseButton = Locator.css("a.x4-disabled").withText("SAVE AND CLOSE"); // why do we need to have the all caps text here? static final Locator cancelButton = Locator.linkWithText("Cancel"); static final Locator disabledWorkbenchButton = Locator.css("a.x4-disabled").withText("WORKBENCH"); - static final Locator manageTitle = Locator.css(".labkey-wp-title-text").containing("Manage"); static final Locator updateTitle = Locator.css(".labkey-nav-page-header").containing("Update"); + + static Locator getTitleTextLocator(String text) + { + return Locator.css(".labkey-wp-title-text").containing(text); + } } } diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java index 64cd9ae4..66a1850b 100644 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java @@ -144,33 +144,6 @@ public void clearSearch() search(""); } - public void saveGroup(String name) - { - setFormElement(DataFinderPage.Locators.groupLabelInput, name); - clickButtonContainingText("Save", BaseWebDriverTest.WAIT_FOR_EXT_MASK_TO_DISSAPEAR); - waitForGroupUpdate(); - } - - public String getGroupLabel() - { - return DataFinderPage.Locators.groupLabel.findElement(getDriver()).getText().trim(); - } - - public GroupMenu getMenu(Locator locator) - { - return new GroupMenu(locator.findElement(getDriver())); - } - - public boolean menuIsDisabled(Locator.CssLocator locator) - { - return isElementPresent(locator.append(" .labkey-disabled-text-link")); - } - - public void openMenu(Locator locator) - { - locator.findElement(getDriver()).click(); - } - public void navigateToStudies() { selectDataFinderObject("Studies"); @@ -183,6 +156,11 @@ public void navigateToPublications() assertElementVisible(DataFinderPage.Locators.pubFinder); } + public boolean isFinderObjectSelected(String text) + { + return text.equals(Locators.activeFinderObject.findElement(getDriver()).getText()); + } + public void selectDataFinderObject(String text) { Locators.finderObjectTab(text).findElement(getDriver()).click(); @@ -317,6 +295,7 @@ public static class Locators public static final Locator.CssLocator manageStudyData = Locator.css(".labkey-studies-panel .labkey-finder-manage-data"); public static final Locator.CssLocator insertNewStudy = Locator.css(".labkey-studies-panel .labkey-finder-insert-new"); public static final Locator.CssLocator insertNewPublication = Locator.css(".labkey-publications-panel .labkey-finder-insert-new"); + public static final Locator.CssLocator activeFinderObject = Locator.css(".x4-tab.x4-tab-active"); public static Locator.CssLocator getSearchInput(Locator.CssLocator locator) { diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java index f2990d88..64d12ba8 100644 --- a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java +++ b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java @@ -24,6 +24,7 @@ import org.labkey.test.TestFileUtils; import org.labkey.test.TestTimeoutException; import org.labkey.test.WebTestHelper; +import org.labkey.test.pages.PermissionsEditor; import org.labkey.test.pages.trialshare.DataFinderPage; import org.labkey.test.util.APIContainerHelper; import org.labkey.test.util.AbstractContainerHelper; @@ -45,6 +46,7 @@ public abstract class DataFinderTestBase extends BaseWebDriverTest { + static final String DATA_PROJECT_NAME = "TrialShareDataFinderTestData Project"; static final String MODULE_NAME = "TrialShare"; static final String WEB_PART_NAME = "TrialShare Data Finder"; static final String OPERATIONAL_STUDY_NAME = "DataFinderTestOperationalStudy"; @@ -62,7 +64,8 @@ public abstract class DataFinderTestBase extends BaseWebDriverTest public ApiPermissionsHelper _apiPermissionsHelper = new ApiPermissionsHelper(this); - public enum CubeObjectType { + public enum CubeObjectType + { study("StudyId", "Manage Studies", new String[]{"Study Id", "Short Name", "Title"}), publication("Title", "Manage Publications", new String[]{"Key", "Title", "Status", "Publication Type"}); @@ -96,7 +99,9 @@ public String getManageDataTitle() } static final Map> studySubsets = new HashMap<>(); - static { + + static + { Set operationalSet = new HashSet<>(); studySubsets.put("Operational", operationalSet); @@ -112,10 +117,12 @@ public String getManageDataTitle() publicSet.add("Shapiro"); publicSet.add("Casale"); publicSet.add("Vincenti"); - }; + } protected static Set loadedStudies = new HashSet<>(); - static { + + static + { loadedStudies.add("DataFinderTestPublicCasale"); loadedStudies.add("DataFinderTestOperationalWISP-R"); } @@ -124,18 +131,19 @@ public String getManageDataTitle() protected void doCleanup(boolean afterTest) throws TestTimeoutException { _containerHelper.deleteProject(getProjectName(), afterTest); + for (String project : _containerHelper.getCreatedProjects()) + _containerHelper.deleteProject(project, afterTest); } @BeforeClass public static void initTest() { - DataFinderTestBase init = (DataFinderTestBase)getCurrentTest(); + DataFinderTestBase init = (DataFinderTestBase) getCurrentTest(); init.setUpProject(); } - @Override protected BrowserType bestBrowser() { @@ -148,25 +156,43 @@ public List getAssociatedModules() return Collections.singletonList("TrialShare"); } + public String getDataProjectName() + { + return DATA_PROJECT_NAME; + } - protected void setUpProject() + protected void setUpDataProject() { AbstractContainerHelper containerHelper = new APIContainerHelper(this); - containerHelper.createProject(getProjectName(), "Custom"); + containerHelper.deleteProject(getDataProjectName(), false); + containerHelper.createProject(getDataProjectName(), "Custom"); + containerHelper.addCreatedProject(getDataProjectName()); containerHelper.enableModule(MODULE_NAME); - goToProjectHome(); + goToProjectHome(getDataProjectName()); importLists(); - createStudies(); + createStudies(getDataProjectName()); createUsers(); List propList = new ArrayList<>(); // set the site-default value for this so it will work as expected from the Admin Console. - propList.add(new ModulePropertyValue("TrialShare", "/", "DataFinderCubeContainer", getProjectName())); + propList.add(new ModulePropertyValue("TrialShare", "/", "DataFinderCubeContainer", getDataProjectName())); setModuleProperties(propList); reindexForSearch(); + } + + protected void setUpProject() + { + setUpDataProject(); + + AbstractContainerHelper containerHelper = new APIContainerHelper(this); + + containerHelper.createProject(getProjectName(), "Custom"); + containerHelper.enableModule(MODULE_NAME); + makeProjectReadable(getProjectName()); + goToProjectHome(); new PortalHelper(this).addWebPart(WEB_PART_NAME); } @@ -177,10 +203,19 @@ protected void importLists() listHelper.importListArchive(dataListArchive); } - protected abstract void createStudies(); + protected abstract void createStudies(String parentProjectName); protected abstract void createUsers(); + protected void makeProjectReadable(String projectName) + { + PermissionsEditor permissionsEditor = new PermissionsEditor(this); + + goToProjectHome(projectName); + clickAdminMenuItem("Folder", "Permissions"); + permissionsEditor.setSiteGroupPermissions("All Site Users", "Reader"); + } + @LogMethod protected void reindexForSearch() { @@ -191,21 +226,21 @@ protected void reindexForSearch() clickButton("OK"); } - void createStudy(String studyName, Boolean operational) + void createStudy(String parentProjectName, String studyName, Boolean operational) { log("creating study " + studyName); AbstractContainerHelper containerHelper = new APIContainerHelper(this); File studyArchive = operational ? TestFileUtils.getSampleData(OPERATIONAL_STUDY_NAME + ".folder.zip") : TestFileUtils.getSampleData(PUBLIC_STUDY_NAME + ".folder.zip"); - containerHelper.createSubfolder(getProjectName(), studyName, "Study"); + containerHelper.createSubfolder(parentProjectName, studyName, "Study"); importStudyFromZip(studyArchive, true, true); } - void createStudy(String name) + void createStudy(String parentProjectName, String name) { AbstractContainerHelper containerHelper = new APIContainerHelper(this); File studyArchive = TestFileUtils.getSampleData(name + ".folder.zip"); - containerHelper.createSubfolder(getProjectName(), name, "Study"); + containerHelper.createSubfolder(parentProjectName, name, "Study"); importStudyFromZip(studyArchive, true, true); } diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index 5427e2d4..f470a18f 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -6,6 +6,7 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.labkey.test.categories.Git; +import org.labkey.test.components.trialshare.PublicationPanel; import org.labkey.test.pages.PermissionsEditor; import org.labkey.test.pages.trialshare.DataFinderPage; import org.labkey.test.pages.trialshare.ManageDataPage; @@ -34,8 +35,8 @@ public class ManagePublicationsTest extends DataFinderTestBase private static final String PUBLIC_STUDY_ID = "Casale"; private static final String OPERATIONAL_STUDY_ID = "WISP-R"; private static final String PROJECT_NAME = "ManagePublicationTest Project"; - private static final String OPERATIONAL_STUDY_SUBFOLDER_NAME = "/" + PROJECT_NAME + "/" + OPERATIONAL_STUDY_NAME; - private static final String PUBLIC_STUDY_SUBFOLDER_NAME = "/" + PROJECT_NAME + "/" + PUBLIC_STUDY_NAME; + private static final String OPERATIONAL_STUDY_SUBFOLDER_NAME = "/" + DATA_PROJECT_NAME + "/" + OPERATIONAL_STUDY_NAME; + private static final String PUBLIC_STUDY_SUBFOLDER_NAME = "/" + DATA_PROJECT_NAME + "/" + PUBLIC_STUDY_NAME; private static final Map EXISTING_PUB_FIELDS = new HashMap<>(); static @@ -69,14 +70,14 @@ public class ManagePublicationsTest extends DataFinderTestBase @Override protected String getProjectName() { - return "ManagePublicationTest Project"; + return PROJECT_NAME; } @Override - protected void createStudies() + protected void createStudies(String parentProjectName) { - createStudy(PUBLIC_STUDY_NAME); - createStudy(OPERATIONAL_STUDY_NAME); + createStudy(parentProjectName, PUBLIC_STUDY_NAME); + createStudy(parentProjectName, OPERATIONAL_STUDY_NAME); } @Override @@ -84,11 +85,9 @@ protected void createUsers() { _userHelper.createUser(PUBLIC_READER); - PermissionsEditor permissionsEditor = new PermissionsEditor(this); + makeProjectReadable(getDataProjectName()); - goToProjectHome(); - clickAdminMenuItem("Folder", "Permissions"); - permissionsEditor.setSiteGroupPermissions("All Site Users", "Reader"); + PermissionsEditor permissionsEditor = new PermissionsEditor(this); permissionsEditor.selectFolder(PUBLIC_STUDY_NAME); _apiPermissionsHelper.setUserPermissions(PUBLIC_READER, "Reader"); @@ -98,7 +97,7 @@ protected void createUsers() public void testInsertNewDataLinkPermissions() { log("Checking for insert new data link"); - DataFinderPage dataFinder = goDirectlyToDataFinderPage(getCurrentContainerPath(), false); + DataFinderPage dataFinder = goDirectlyToDataFinderPage(getProjectName(), false); Assert.assertTrue("Insert New link is not available", dataFinder.canInsertNewData()); dataFinder.goToInsertNewData(); switchToWindow(1); @@ -108,7 +107,7 @@ public void testInsertNewDataLinkPermissions() log("Impersonating user without insert permission"); goToProjectHome(); impersonate(PUBLIC_READER); - goDirectlyToDataFinderPage(getCurrentContainerPath(), false); + goDirectlyToDataFinderPage(getProjectName(), false); Assert.assertFalse("Insert New link should not be available", dataFinder.canManageData()); } @@ -116,7 +115,7 @@ public void testInsertNewDataLinkPermissions() public void testManageDataLinkPermissions() { log("Checking for manage data link"); - DataFinderPage dataFinder = goDirectlyToDataFinderPage(getCurrentContainerPath(), false); + DataFinderPage dataFinder = goDirectlyToDataFinderPage(getProjectName(), false); Assert.assertTrue("Manage Data link is not available", dataFinder.canManageData()); dataFinder.goToManageData(); switchToWindow(1); @@ -127,7 +126,7 @@ public void testManageDataLinkPermissions() log("Impersonating user without insert permission"); goToProjectHome(); impersonate(PUBLIC_READER); - goDirectlyToDataFinderPage(getCurrentContainerPath(), false); + goDirectlyToDataFinderPage(getProjectName(), false); Assert.assertFalse("Manage Data link should not be available", dataFinder.canManageData()); goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); assertTextPresent("User does not have permission"); @@ -136,7 +135,7 @@ public void testManageDataLinkPermissions() @Test public void testSwitchToStudies() { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); Assert.assertTrue("Should see a link to manage studies", manageData.hasManageStudiesLink()); manageData.goToManageStudies(); @@ -152,7 +151,7 @@ public void testGoToInsertNewAndCancel() manageData.goToInsertNew(); PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); Assert.assertFalse("Submit button should not be enabled", editPage.isSubmitEnabled()); - doAndWaitForPageToLoad(() -> editPage.cancel()); + doAndWaitForPageToLoad(editPage::cancel); Assert.assertTrue("Should be manage publications view", manageData.isManageDataView()); } @@ -215,9 +214,43 @@ public void testRequiredFields() } @Test - public void testInsertWithAllFields() + public void testSaveAndCloseReturnUrl() + { + goToProjectHome(); + DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); + finder.clearAllFilters(); + DataFinderPage.DataCard card = finder.getDataCards().get(0); + PublicationPanel publicationPanel = card.viewDetail(); + publicationPanel.clickEditLink(); + switchToWindow(1); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + editPage.saveAndClose("Data Finder"); + sleep(1000); + // we should go back to the finder page and have the publication tab active + Assert.assertTrue("Publications tab on finder should be active after save and close", finder.isFinderObjectSelected("Publications")); + } + + @Test + public void testCancelReturnUrl() { goToProjectHome(); + DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); + finder.clearAllFilters(); + DataFinderPage.DataCard card = finder.getDataCards().get(0); + PublicationPanel publicationPanel = card.viewDetail(); + publicationPanel.clickEditLink(); + switchToWindow(1); + PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); + doAndWaitForPageToLoad(editPage::cancel); + sleep(500); + // we should go back to the finder page and have the publication tab active + Assert.assertTrue("Publications tab on finder should be active after cancel", finder.isFinderObjectSelected("Publications")); + } + + @Test + public void testInsertWithAllFields() + { + goToProjectHome(getDataProjectName()); StudiesListHelper studiesListHelper = new StudiesListHelper(this); studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); @@ -265,7 +298,7 @@ public void testInsertWithAllFields() newFields.remove(PublicationEditPage.STUDIES); newFields.remove(PublicationEditPage.THERAPEUTIC_AREAS); editPage.setFormFields(newFields, false); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); manageData.goToEditRecord((String) newFields.get(TITLE)); unexpectedValues = editPage.compareFormValues(newFields); Assert.assertTrue("Found unexpected values in edit page of newly inserted publication: " + unexpectedValues, unexpectedValues.isEmpty()); @@ -274,7 +307,7 @@ public void testInsertWithAllFields() @Test public void testCountsAfterInsertAndEdit() { - goToProjectHome(); + goToProjectHome(getDataProjectName()); StudiesListHelper studiesListHelper = new StudiesListHelper(this); studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); @@ -305,7 +338,7 @@ public void testCountsAfterInsertAndEdit() PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); editPage.setFormFields(newFields, true); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); goToProjectHome(); goDirectlyToDataFinderPage(getProjectName(), false); @@ -324,7 +357,7 @@ public void testCountsAfterInsertAndEdit() goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); manageData.goToEditRecord((String) newFields.get(TITLE)); newFields.remove(PublicationEditPage.TITLE); - newFields.put(PublicationEditPage.STATUS, "Complete"); + // not updating status since the default data has no other in-progress publications and making them all "complete" removes status and submission status newFields.put(PublicationEditPage.SUBMISSION_STATUS, "Draft"); newFields.put(PublicationEditPage.PUBLICATION_TYPE, "Abstract"); // for these two multi-select fields, the items that are selected will be deselected and ones not selected will be selected @@ -333,13 +366,12 @@ public void testCountsAfterInsertAndEdit() newFields.put(PublicationEditPage.YEAR, "2014"); editPage.setFormFields(newFields, false); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); goToProjectHome(); - goDirectlyToDataFinderPage(getProjectName(), false); + finder.selectDataFinderObject("Publications"); finder.clearAllFilters(); Map> afterEditCounts = fg.getAllMemberCounts(); - assertEquals("Count for 'In Progress' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.STATUS, "In Progress"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STATUS, "In Progress")); - assertEquals("Count for 'Complete' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STATUS, "Complete")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STATUS, "Complete")); + assertEquals("Count for 'In Progress' updated but should not have been", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STATUS, "In Progress"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STATUS, "In Progress")); assertEquals("Count for 'Submitted' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted")); assertEquals("Count for 'Draft' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Draft")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Draft")); assertEquals("Count for 'Manuscript' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript")); @@ -355,7 +387,7 @@ public void testCountsAfterInsertAndEdit() @Test public void testInsertMultiValuedFields() { - goToProjectHome(); + goToProjectHome(getDataProjectName()); StudiesListHelper studiesListHelper = new StudiesListHelper(this); studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); @@ -376,7 +408,7 @@ public void testInsertMultiValuedFields() PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); editPage.setFormFields(newFields, false); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); manageData.goToEditRecord((String) newFields.get(TITLE)); Map unexpectedValues = editPage.compareFormValues(newFields); @@ -386,7 +418,7 @@ public void testInsertMultiValuedFields() @Test public void testEditMultiValuedFields() { - goToProjectHome(); + goToProjectHome(getDataProjectName()); StudiesListHelper studiesListHelper = new StudiesListHelper(this); studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); @@ -407,7 +439,7 @@ public void testEditMultiValuedFields() PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); editPage.setFormFields(initialFields, false); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); manageData.goToEditRecord((String) initialFields.get(TITLE)); @@ -415,7 +447,7 @@ public void testEditMultiValuedFields() newFields.put(PublicationEditPage.STUDIES, new String[]{OPERATIONAL_STUDY_ID}); newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); editPage.setFormFields(newFields, false); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); manageData.goToEditRecord((String) initialFields.get(TITLE)); initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID, OPERATIONAL_STUDY_ID}); @@ -428,7 +460,7 @@ public void testEditMultiValuedFields() @Test public void testUpdatePublication() { - goToProjectHome(); + goToProjectHome(getDataProjectName()); StudiesListHelper studiesListHelper = new StudiesListHelper(this); studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); @@ -466,7 +498,7 @@ public void testUpdatePublication() PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); editPage.setFormFields(initialFields, false); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); Map updatedFields = new HashMap<>(); // add the count so multiple runs of this test have distinct titles @@ -532,11 +564,11 @@ public void testUpdatePublication() @Test public void testInsertAndDelete() { - goToProjectHome(); + goToProjectHome(getDataProjectName()); StudiesListHelper studiesListHelper = new StudiesListHelper(this); studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); Map initialFields = new HashMap<>(); // add the count so multiple runs of this test have distinct titles @@ -548,7 +580,7 @@ public void testInsertAndDelete() manageData.goToInsertNew(); PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); editPage.setFormFields(initialFields, false); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); manageData.deleteRecord((String) initialFields.get(PublicationEditPage.TITLE)); PublicationsListHelper listHelper = new PublicationsListHelper(this); @@ -563,7 +595,7 @@ public void testInsertAndDelete() @Test public void testInsertWithoutRefresh() { - goToProjectHome(); + goToProjectHome(getDataProjectName()); StudiesListHelper studiesListHelper = new StudiesListHelper(this); studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); @@ -581,7 +613,7 @@ public void testInsertWithoutRefresh() PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); editPage.setFormFields(initialFields, true); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); goToProjectHome(); // there should be no error alert after inserting but before refreshing DataFinderPage finder = goDirectlyToDataFinderPage(getCurrentContainerPath(), false); diff --git a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java index f71a5a43..54b66b64 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java @@ -5,7 +5,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.labkey.test.categories.Git; -import org.labkey.test.pages.PermissionsEditor; import org.labkey.test.pages.trialshare.DataFinderPage; import org.labkey.test.pages.trialshare.ManageDataPage; import org.labkey.test.pages.trialshare.StudiesListHelper; @@ -35,7 +34,7 @@ protected String getProjectName() } @Override - protected void createStudies() + protected void createStudies(String parentProjectName) { } @@ -44,18 +43,14 @@ protected void createUsers() { _userHelper.createUser(PUBLIC_READER); - PermissionsEditor permissionsEditor = new PermissionsEditor(this); - - goToProjectHome(); - clickAdminMenuItem("Folder", "Permissions"); - permissionsEditor.setSiteGroupPermissions("All Site Users", "Reader"); + makeProjectReadable(getDataProjectName()); } @Test public void testInsertNewDataLinkPermissions() { log("Checking for absence of insert new data link"); - DataFinderPage dataFinder = goDirectlyToDataFinderPage(getCurrentContainerPath(), true); + DataFinderPage dataFinder = goDirectlyToDataFinderPage(getProjectName(), true); Assert.assertFalse("Insert New link is shown for studies", dataFinder.canInsertNewData()); } @@ -63,7 +58,7 @@ public void testInsertNewDataLinkPermissions() public void testManageDataLinkPermissions() { log("Checking for manage data link"); - DataFinderPage dataFinder = goDirectlyToDataFinderPage(getCurrentContainerPath(), true); + DataFinderPage dataFinder = goDirectlyToDataFinderPage(getProjectName(), true); Assert.assertTrue("Manage Data link is not available", dataFinder.canManageData()); dataFinder.goToManageData(); switchToWindow(1); @@ -74,16 +69,16 @@ public void testManageDataLinkPermissions() log("Impersonating user without insert permission"); goToProjectHome(); impersonate(PUBLIC_READER); - goDirectlyToDataFinderPage(getCurrentContainerPath(), true); + goDirectlyToDataFinderPage(getProjectName(), true); Assert.assertFalse("Manage Data link should not be available", dataFinder.canManageData()); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); assertTextPresent("User does not have permission"); } @Test public void testSwitchToPublications() { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); Assert.assertTrue("Should see a link to manage publications", manageData.hasManagePublicationsLink()); manageData.goToManagePublications(); @@ -94,19 +89,19 @@ public void testSwitchToPublications() @Test public void testGoToInsertNewAndCancel() { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); manageData.goToInsertNew(); StudyEditPage editPage = new StudyEditPage(this.getDriver()); Assert.assertFalse("Submit button should not be enabled", editPage.isSubmitEnabled()); - doAndWaitForPageToLoad(() -> editPage.cancel()); + doAndWaitForPageToLoad(editPage::cancel); Assert.assertTrue("Should be manage studies view", manageData.isManageDataView()); } @Test public void testRequiredFields() { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); manageData.goToInsertNew(); StudyEditPage editPage = new StudyEditPage(this.getDriver()); @@ -141,7 +136,7 @@ public void testRequiredFields() @Test public void testInsertWithAllFields() { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); int count = manageData.getCount(); @@ -178,7 +173,7 @@ public void testInsertWithAllFields() newFields.remove(StudyEditPage.PHASES); editPage.removeStudyAccessPanel(0); editPage.setFormFields(newFields); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); manageData.goToEditRecord((String) newFields.get(StudyEditPage.STUDY_ID)); unexpectedValues = editPage.compareFormValues(newFields); Assert.assertTrue("Found unexpected values in edit page of newly inserted study: " + unexpectedValues, unexpectedValues.isEmpty()); @@ -193,7 +188,7 @@ public void testCountsAfterInsertAndEdit() finder.clearAllFilters(); Map> beforeCounts = fg.getAllMemberCounts(); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); int count = manageData.getCount(); @@ -220,7 +215,7 @@ public void testCountsAfterInsertAndEdit() log("Set values for the first study access form"); editPage.setStudyAccessFormValues(0, studyAccessFields); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); goToProjectHome(); goDirectlyToDataFinderPage(getProjectName(), true); @@ -232,7 +227,7 @@ public void testCountsAfterInsertAndEdit() assertEquals("Count for 'Phase 0' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PHASE, "Phase 0")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PHASE, "Phase 0")); assertEquals("Count for 'Eczema' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.CONDITION, "Eczema")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.CONDITION, "Eczema")); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); manageData.goToEditRecord((String) newFields.get(StudyEditPage.STUDY_ID)); newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"Transplant"}); // add transplant and leave T1DM newFields.put(StudyEditPage.STUDY_TYPE, "Expanded Access"); @@ -244,7 +239,7 @@ public void testCountsAfterInsertAndEdit() newFields.remove(StudyEditPage.TITLE); editPage.setFormFields(newFields); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); goToProjectHome(); goDirectlyToDataFinderPage(getProjectName(), true); finder.clearAllFilters(); @@ -265,7 +260,7 @@ public void testCountsAfterInsertAndEdit() @Test public void testInsertMultiValuedFields() { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); int count = manageData.getCount(); @@ -287,9 +282,9 @@ public void testInsertMultiValuedFields() editPage.removeStudyAccessPanel(0); editPage.setFormFields(newFields); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); manageData.goToEditRecord((String) newFields.get(StudyEditPage.STUDY_ID)); Map unexpectedValues = editPage.compareFormValues(newFields); @@ -299,7 +294,7 @@ public void testInsertMultiValuedFields() @Test public void testEditMultiValuedFields() { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); int count = manageData.getCount(); @@ -321,9 +316,9 @@ public void testEditMultiValuedFields() editPage.removeStudyAccessPanel(0); editPage.setFormFields(initialFields); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); @@ -338,7 +333,7 @@ public void testEditMultiValuedFields() newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); editPage.removeStudyAccessPanel(0); editPage.setFormFields(newFields); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); @@ -354,7 +349,7 @@ public void testEditMultiValuedFields() @Test public void testUpdateStudy() { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); int count = manageData.getCount(); @@ -380,7 +375,7 @@ public void testUpdateStudy() editPage.removeStudyAccessPanel(0); editPage.setFormFields(initialFields); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); Map updatedFields = new HashMap<>(); updatedFields.put(StudyEditPage.SHORT_NAME, "TUS" + count + "_U"); @@ -403,7 +398,7 @@ public void testUpdateStudy() @Test public void testInsertAndDelete() { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); int count = manageData.getCount(); @@ -422,8 +417,8 @@ public void testInsertAndDelete() editPage.removeStudyAccessPanel(0); editPage.setFormFields(initialFields); - editPage.saveAndClose(); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + editPage.saveAndClose("Manage"); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); manageData.deleteRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); @@ -447,13 +442,13 @@ public void testInsertAndDelete() @Test public void testInsertWithoutRefresh() { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); int count = manageData.getCount(); String shortName = "TIWR" + count; String studyId = "TIWR_ID" + count; - createStudy(shortName, false); + createStudy(getDataProjectName(), shortName, false); Map initialFields = new HashMap<>(); // add the count so multiple runs of this test have distinct titles @@ -461,23 +456,23 @@ public void testInsertWithoutRefresh() initialFields.put(StudyEditPage.STUDY_ID, studyId); initialFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); manageData.goToInsertNew(); StudyEditPage editPage = new StudyEditPage(this.getDriver()); Map studyAccessFields = new HashMap<>(); studyAccessFields.put(StudyEditPage.VISIBILITY, "Public"); - studyAccessFields.put(StudyEditPage.STUDY_CONTAINER, "/" + PROJECT_NAME + "/" + shortName); + studyAccessFields.put(StudyEditPage.STUDY_CONTAINER, "/" + DATA_PROJECT_NAME + "/" + shortName); studyAccessFields.put(StudyEditPage.DISPLAY_NAME, shortName); log("Set values for the first study access form"); editPage.setStudyAccessFormValues(0, studyAccessFields); editPage.setFormFields(initialFields); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); goToProjectHome(); // there should be no error alert after inserting but before refreshing - DataFinderPage finder = goDirectlyToDataFinderPage(getCurrentContainerPath(), true); + DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); finder.clearAllFilters(); finder.search(studyId); List dataCards = finder.getDataCards(); @@ -488,20 +483,20 @@ public void testInsertWithoutRefresh() @Test public void testStudyAccessPanel() { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); ManageDataPage manageData = new ManageDataPage(this, _objectType); int count = manageData.getCount(); String shortName = "TSAP" + count; String studyId = "TSAP_ID" + count; - createStudy(shortName, false); + createStudy(getDataProjectName(), shortName, false); Map initialFields = new HashMap<>(); initialFields.put(StudyEditPage.SHORT_NAME, shortName); initialFields.put(StudyEditPage.STUDY_ID, studyId); initialFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); manageData.goToInsertNew(); StudyEditPage editPage = new StudyEditPage(this.getDriver()); editPage.setFormFields(initialFields); @@ -511,7 +506,7 @@ public void testStudyAccessPanel() Map studyAccessFields = new HashMap<>(); studyAccessFields.put(StudyEditPage.VISIBILITY, firstVisibility); - studyAccessFields.put(StudyEditPage.STUDY_CONTAINER, "/" + PROJECT_NAME + "/" + shortName); + studyAccessFields.put(StudyEditPage.STUDY_CONTAINER, "/" + DATA_PROJECT_NAME + "/" + shortName); studyAccessFields.put(StudyEditPage.DISPLAY_NAME, firstDisplayName); log("Set values for the first study access form"); @@ -522,7 +517,7 @@ public void testStudyAccessPanel() Map secondStudyAccessFields = new HashMap<>(); secondStudyAccessFields.put(StudyEditPage.VISIBILITY, secondVisibility); - secondStudyAccessFields.put(StudyEditPage.STUDY_CONTAINER, "/" + PROJECT_NAME + "/" + shortName); + secondStudyAccessFields.put(StudyEditPage.STUDY_CONTAINER, "/" + DATA_PROJECT_NAME + "/" + shortName); secondStudyAccessFields.put(StudyEditPage.DISPLAY_NAME, secondDisplayName); log("Add another study access record"); @@ -530,9 +525,9 @@ public void testStudyAccessPanel() log("Set values for the second study access form"); editPage.setStudyAccessFormValues(1, secondStudyAccessFields); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); //wait for combo store to load @@ -545,9 +540,9 @@ public void testStudyAccessPanel() log("Change study access display name"); firstDisplayName = firstDisplayName + "_updated"; editPage.setStudyAccessDisplayName(0, firstDisplayName); - editPage.saveAndClose(); + editPage.saveAndClose("Manage"); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); + goDirectlyToManageDataPage(getDataProjectName(), _objectType); manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); log("Verify the second study access record is deleted successfully"); assertElementNotPresent(editPage.getStudyAccessPanelLocator(1)); diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 41abfef6..052aaf7f 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -23,7 +23,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.labkey.test.Locator; -import org.labkey.test.ModulePropertyValue; import org.labkey.test.TestTimeoutException; import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Git; @@ -33,15 +32,11 @@ import org.labkey.test.pages.trialshare.DataFinderPage; import org.labkey.test.pages.trialshare.PublicationsListHelper; import org.labkey.test.pages.trialshare.StudiesListHelper; -import org.labkey.test.util.APIContainerHelper; -import org.labkey.test.util.AbstractContainerHelper; import org.labkey.test.util.LogMethod; -import org.labkey.test.util.PortalHelper; import org.labkey.test.util.ReadOnlyTest; import org.openqa.selenium.support.ui.ExpectedConditions; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -95,7 +90,7 @@ public boolean needsSetup() } @Override - protected void createStudies() + protected void createStudies(String parentProjectName) { log("Creating a study container for each study"); for (String subset : studySubsets.keySet()) @@ -103,23 +98,23 @@ protected void createStudies() for (String accession : studySubsets.get(subset)) { String name = "DataFinderTest" + subset + accession; - createStudy(name, subset.equalsIgnoreCase("operational")); + createStudy(getDataProjectName(), name, subset.equalsIgnoreCase("operational")); } } - createStudy(PUBLIC_STUDY_NAME); - createStudy(OPERATIONAL_STUDY_NAME); - goToProjectHome(); + createStudy(parentProjectName, PUBLIC_STUDY_NAME); + createStudy(parentProjectName, OPERATIONAL_STUDY_NAME); + goToProjectHome(getDataProjectName()); StudiesListHelper queryUpdatePage = new StudiesListHelper(this); queryUpdatePage.setStudyContainers(); - goToProjectHome(); + goToProjectHome(getDataProjectName()); PublicationsListHelper pubUpdatePage = new PublicationsListHelper(this); - pubUpdatePage.setPermissionsContainers("/" + getProjectName() + "/" + PUBLIC_STUDY_NAME, "/" + getProjectName() + "/" + OPERATIONAL_STUDY_NAME); + pubUpdatePage.setPermissionsContainers("/" + getDataProjectName() + "/" + PUBLIC_STUDY_NAME, "/" + getDataProjectName() + "/" + OPERATIONAL_STUDY_NAME); } protected void createUsers() { log("Creating users and setting permissions"); - goToProjectHome(); + goToProjectHome(getDataProjectName()); _userHelper.createUser(PUBLIC_READER); _userHelper.createUser(CASALE_READER); @@ -127,7 +122,7 @@ protected void createUsers() PermissionsEditor permissionsEditor = new PermissionsEditor(this); - goToProjectHome(); + goToProjectHome(getDataProjectName()); clickAdminMenuItem("Folder", "Permissions"); permissionsEditor.setSiteGroupPermissions("All Site Users", "Reader"); @@ -218,7 +213,7 @@ public void testPublicAccess() List cards = finder.getDataCards(); Assert.assertEquals("Number of studies not as expected", studySubsets.get("Public").size(), cards.size()); stopImpersonating(); - doAndWaitForPageSignal(() -> goToProjectHome(), finder.getCountSignal()); + doAndWaitForPageSignal(this::goToProjectHome, finder.getCountSignal()); sleep(1000); Assert.assertTrue("Admin user should see visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); @@ -244,30 +239,6 @@ public void testOperationalAccess() stopImpersonating(); } - - @Test - public void testDataFinderRelocation() - { - log("Test that we can put the data finder in a project other than the one with the cube definition and lists"); - AbstractContainerHelper containerHelper = new APIContainerHelper(this); - containerHelper.deleteProject(RELOCATED_DATA_FINDER_PROJECT, false); - containerHelper.createProject(RELOCATED_DATA_FINDER_PROJECT, "Custom"); - containerHelper.addCreatedProject(RELOCATED_DATA_FINDER_PROJECT); - containerHelper.enableModule(MODULE_NAME); - List propList = new ArrayList<>(); - propList.add(new ModulePropertyValue("TrialShare", "/" + RELOCATED_DATA_FINDER_PROJECT, "DataFinderCubeContainer", getProjectName())); - setModuleProperties(propList); - - goToProjectHome(RELOCATED_DATA_FINDER_PROJECT); - new PortalHelper(this).addWebPart(WEB_PART_NAME); - DataFinderPage finder = new DataFinderPage(this, true); - DataFinderPage.FacetGrid facetGrid = finder.getFacetsGrid(); - Assert.assertTrue("Should see the visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); - facetGrid.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Public"); - Assert.assertEquals("Should see all the public data cards", 4, finder.getDataCards().size()); - goToProjectHome(); - } - @Test public void testSelection() { @@ -448,7 +419,7 @@ public void testGoToStudyMenu() DataFinderPage.DataCard card = dataCards.get(0); Assert.assertEquals("DIAMOND", card.getStudyShortName()); log("Go to operational study"); - card.clickGoToStudy("/" + getProjectName() + "/DataFinderTestOperationalDIAMOND"); + card.clickGoToStudy("/" + getDataProjectName() + "/DataFinderTestOperationalDIAMOND"); } @Test From 106a165c58543064aa1a117d98bfd489dfe837f4 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 21 Oct 2016 08:33:45 -0700 Subject: [PATCH 431/587] Issue 28199 - update automated test with proper assertions for fresh project --- .../labkey/test/tests/trialshare/ManagePublicationsTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index 5427e2d4..fe463456 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -324,7 +324,7 @@ public void testCountsAfterInsertAndEdit() goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); manageData.goToEditRecord((String) newFields.get(TITLE)); newFields.remove(PublicationEditPage.TITLE); - newFields.put(PublicationEditPage.STATUS, "Complete"); + // Don't update Status because we will be left with nothing in progress and then the status and submission status facets disappear newFields.put(PublicationEditPage.SUBMISSION_STATUS, "Draft"); newFields.put(PublicationEditPage.PUBLICATION_TYPE, "Abstract"); // for these two multi-select fields, the items that are selected will be deselected and ones not selected will be selected @@ -338,8 +338,7 @@ public void testCountsAfterInsertAndEdit() goDirectlyToDataFinderPage(getProjectName(), false); finder.clearAllFilters(); Map> afterEditCounts = fg.getAllMemberCounts(); - assertEquals("Count for 'In Progress' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.STATUS, "In Progress"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STATUS, "In Progress")); - assertEquals("Count for 'Complete' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STATUS, "Complete")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STATUS, "Complete")); + assertEquals("Count for 'In Progress' updated when it should not have been", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STATUS, "In Progress"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STATUS, "In Progress")); assertEquals("Count for 'Submitted' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted")); assertEquals("Count for 'Draft' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Draft")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Draft")); assertEquals("Count for 'Manuscript' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript")); From 0ac0a7373a0fc6d4a61f528c734df6a886650ce1 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Fri, 21 Oct 2016 11:35:15 -0700 Subject: [PATCH 432/587] Issue 28241 - update insertAndDelete tests to go to data project when checking for lists. --- .../test/tests/trialshare/DataFinderTestBase.java | 1 - .../tests/trialshare/ManagePublicationsTest.java | 8 ++++---- .../test/tests/trialshare/ManageStudiesTest.java | 12 ++++++------ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java index 64d12ba8..06a71cd8 100644 --- a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java +++ b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java @@ -167,7 +167,6 @@ protected void setUpDataProject() containerHelper.deleteProject(getDataProjectName(), false); containerHelper.createProject(getDataProjectName(), "Custom"); - containerHelper.addCreatedProject(getDataProjectName()); containerHelper.enableModule(MODULE_NAME); goToProjectHome(getDataProjectName()); importLists(); diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index f470a18f..193ff6af 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -225,7 +225,7 @@ public void testSaveAndCloseReturnUrl() switchToWindow(1); PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); editPage.saveAndClose("Data Finder"); - sleep(1000); + sleep(1000); // yes, it's a hack. But everyone needs sleep at some point. // we should go back to the finder page and have the publication tab active Assert.assertTrue("Publications tab on finder should be active after save and close", finder.isFinderObjectSelected("Publications")); } @@ -584,11 +584,11 @@ public void testInsertAndDelete() manageData.deleteRecord((String) initialFields.get(PublicationEditPage.TITLE)); PublicationsListHelper listHelper = new PublicationsListHelper(this); - goToProjectHome(); + goToProjectHome(getDataProjectName()); Assert.assertEquals("Found deleted publication", 0, listHelper.getPublicationCount((String) initialFields.get(PublicationEditPage.TITLE), true)); - goToProjectHome(); + goToProjectHome(getDataProjectName()); Assert.assertEquals("Found studies for deleted publication", 0, listHelper.getPublicationStudyCount((String) initialFields.get(PublicationEditPage.TITLE), true)); - goToProjectHome(); + goToProjectHome(getDataProjectName()); Assert.assertEquals("Found therapeutic areas for deleted publication", 0, listHelper.getPublicationTherapeuticAreaCount((String) initialFields.get(PublicationEditPage.TITLE), true)); } diff --git a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java index 54b66b64..8e7b4a1f 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java @@ -425,17 +425,17 @@ public void testInsertAndDelete() log("Finished deleting record " + initialFields.get(StudyEditPage.STUDY_ID) + ". Going home"); StudiesListHelper listHelper = new StudiesListHelper(this); - goToProjectHome(); + goToProjectHome(getDataProjectName()); Assert.assertEquals("Found deleted study", 0, listHelper.getStudyCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); - goToProjectHome(); + goToProjectHome(getDataProjectName()); Assert.assertEquals("Found age group(s) for deleted study", 0, listHelper.getStudyAgeGroupCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); - goToProjectHome(); + goToProjectHome(getDataProjectName()); Assert.assertEquals("Found condition(s) for deleted study", 0, listHelper.getStudyConditionCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); - goToProjectHome(); + goToProjectHome(getDataProjectName()); Assert.assertEquals("Found therapeutic area(s) for deleted study", 0, listHelper.getStudyTherapeuticAreaCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); - goToProjectHome(); + goToProjectHome(getDataProjectName()); Assert.assertEquals("Found phase(s) for deleted study", 0, listHelper.getStudyPhaseCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); - goToProjectHome(); + goToProjectHome(getDataProjectName()); Assert.assertEquals("Found study access data for deleted study", 0, listHelper.getStudyAccessCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); } From 06d2ff9fa1d0b10a40d493c919581b2861306238 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 23 Oct 2016 19:10:42 -0700 Subject: [PATCH 433/587] Issue 28241 make separate data projects for each test --- .../labkey/test/tests/trialshare/DataFinderTestBase.java | 6 +----- .../test/tests/trialshare/ManagePublicationsTest.java | 4 ++++ .../labkey/test/tests/trialshare/ManageStudiesTest.java | 4 ++++ .../test/tests/trialshare/TrialShareDataFinderTest.java | 8 +++++++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java index 06a71cd8..5511abd8 100644 --- a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java +++ b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java @@ -46,7 +46,6 @@ public abstract class DataFinderTestBase extends BaseWebDriverTest { - static final String DATA_PROJECT_NAME = "TrialShareDataFinderTestData Project"; static final String MODULE_NAME = "TrialShare"; static final String WEB_PART_NAME = "TrialShare Data Finder"; static final String OPERATIONAL_STUDY_NAME = "DataFinderTestOperationalStudy"; @@ -156,10 +155,7 @@ public List getAssociatedModules() return Collections.singletonList("TrialShare"); } - public String getDataProjectName() - { - return DATA_PROJECT_NAME; - } + public abstract String getDataProjectName(); protected void setUpDataProject() { diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index 193ff6af..c8255c23 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -35,6 +35,7 @@ public class ManagePublicationsTest extends DataFinderTestBase private static final String PUBLIC_STUDY_ID = "Casale"; private static final String OPERATIONAL_STUDY_ID = "WISP-R"; private static final String PROJECT_NAME = "ManagePublicationTest Project"; + private static final String DATA_PROJECT_NAME = "ManagePublicationsTestData Project"; private static final String OPERATIONAL_STUDY_SUBFOLDER_NAME = "/" + DATA_PROJECT_NAME + "/" + OPERATIONAL_STUDY_NAME; private static final String PUBLIC_STUDY_SUBFOLDER_NAME = "/" + DATA_PROJECT_NAME + "/" + PUBLIC_STUDY_NAME; @@ -73,6 +74,9 @@ protected String getProjectName() return PROJECT_NAME; } + @Override + public String getDataProjectName() { return DATA_PROJECT_NAME; } + @Override protected void createStudies(String parentProjectName) { diff --git a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java index 8e7b4a1f..7efe5ae5 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java @@ -25,6 +25,7 @@ public class ManageStudiesTest extends DataFinderTestBase CubeObjectType _objectType = CubeObjectType.study; private static final String PROJECT_NAME = "ManageStudiesTest Project"; + private static final String DATA_PROJECT_NAME = "ManageStudiesTestData Project"; @Nullable @Override @@ -33,6 +34,9 @@ protected String getProjectName() return PROJECT_NAME; } + @Override + public String getDataProjectName() { return DATA_PROJECT_NAME; } + @Override protected void createStudies(String parentProjectName) { diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java index 052aaf7f..b4a0df28 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java @@ -53,6 +53,9 @@ public class TrialShareDataFinderTest extends DataFinderTestBase implements Read { private static final String RELOCATED_DATA_FINDER_PROJECT = "RelocatedDataFinder"; + private static final String PROJECT_NAME = "TrialShareDataFinderTest Project"; + private static final String DATA_PROJECT_NAME = "TrialShareDataFinderTestData Project"; + @Override protected void doCleanup(boolean afterTest) throws TestTimeoutException { @@ -72,9 +75,12 @@ public static void initTest() @Override protected String getProjectName() { - return "TrialShareDataFinderTest Project"; + return PROJECT_NAME; } + @Override + public String getDataProjectName() { return DATA_PROJECT_NAME; } + @Override public boolean needsSetup() { From 6194d8c7210e9a21ab162c5944a92f3a9516354d Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Sun, 23 Oct 2016 19:46:51 -0700 Subject: [PATCH 434/587] Issue 28241 avoid concurrent modification problem when deleting projects; close window created during test --- .../org/labkey/test/tests/trialshare/DataFinderTestBase.java | 5 ++--- .../labkey/test/tests/trialshare/ManagePublicationsTest.java | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java index 5511abd8..51c51fa4 100644 --- a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java +++ b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java @@ -130,8 +130,7 @@ public String getManageDataTitle() protected void doCleanup(boolean afterTest) throws TestTimeoutException { _containerHelper.deleteProject(getProjectName(), afterTest); - for (String project : _containerHelper.getCreatedProjects()) - _containerHelper.deleteProject(project, afterTest); + _containerHelper.deleteProject(getDataProjectName(), afterTest); } @BeforeClass @@ -146,7 +145,7 @@ public static void initTest() @Override protected BrowserType bestBrowser() { - return BrowserType.CHROME; + return BrowserType.FIREFOX; } @Override diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index c8255c23..a7c87e4e 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -84,6 +84,7 @@ protected void createStudies(String parentProjectName) createStudy(parentProjectName, OPERATIONAL_STUDY_NAME); } + @Override protected void createUsers() { @@ -232,6 +233,8 @@ public void testSaveAndCloseReturnUrl() sleep(1000); // yes, it's a hack. But everyone needs sleep at some point. // we should go back to the finder page and have the publication tab active Assert.assertTrue("Publications tab on finder should be active after save and close", finder.isFinderObjectSelected("Publications")); + getDriver().close(); + switchToMainWindow(); } @Test @@ -249,6 +252,8 @@ public void testCancelReturnUrl() sleep(500); // we should go back to the finder page and have the publication tab active Assert.assertTrue("Publications tab on finder should be active after cancel", finder.isFinderObjectSelected("Publications")); + getDriver().close(); + switchToMainWindow(); } @Test From 702837911bd53af4882e3bb957cec3a6d282ab98 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 24 Oct 2016 07:52:06 -0700 Subject: [PATCH 435/587] Issue 28241 wait for mask to disappear before choosing object type in data finder --- test/src/org/labkey/test/pages/trialshare/DataFinderPage.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java index 66a1850b..19cc05ad 100644 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java @@ -163,6 +163,7 @@ public boolean isFinderObjectSelected(String text) public void selectDataFinderObject(String text) { + _ext4Helper.waitForMaskToDisappear(); Locators.finderObjectTab(text).findElement(getDriver()).click(); } From 59d3722672623a0a1a5f6f804fdad91861aca701 Mon Sep 17 00:00:00 2001 From: Cory Nathe Date: Mon, 24 Oct 2016 16:44:35 +0000 Subject: [PATCH 436/587] Spec #27882: Add summary statistics to data grids - refactor so that summary stats are registered via a module - refactor so base set of stat types are named accordingly: Aggregate.BaseType - create an Aggregate.Type interface that both base and premium summary stats can implement - allow name, full label, and display value for each summary stat type - allow a summary stat to override thw generate SQL if it doesn't want to use the base implementation - move BaseAggregatesAnalyticsProvider to API so it can be used by premium module SVN r47143 |2016-10-24 16:44:35 +0000 --- src/org/scharp/atlas/pepdb/PepDBController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 88aaded1..c0fb730e 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -1253,7 +1253,7 @@ else if (PepDBSchema.COLUMN_PARENT_POOL_ID.equals(col.getName())) { ColumnInfo ci = rgn.getTable().getColumn("peptide_id"); QuerySettings qs = new QuerySettings(getViewContext(), rgn.getName()); - qs.addAggregates(new Aggregate(ci, Aggregate.Type.COUNT)); + qs.addAggregates(new Aggregate(ci, Aggregate.BaseType.COUNT)); qs.setMaxRows(Table.ALL_ROWS); rgn.setSettings(qs); // We want MOST of the query settings into our dataregion settings, but we still want to paginate the rows. From 9f298f6e2726a8919ac3f9e9a4cbe783134b58c3 Mon Sep 17 00:00:00 2001 From: labkey-eyounske Date: Thu, 27 Oct 2016 21:17:21 -0700 Subject: [PATCH 437/587] Spec 27883: Update all source code copyrights & licenses --- resources/web/study/Finder/data/AgeGroup.js | 5 +++++ resources/web/study/Finder/data/Condition.js | 5 +++++ resources/web/study/Finder/data/Container.js | 5 +++++ resources/web/study/Finder/data/CubeConfig.js | 5 +++++ resources/web/study/Finder/data/CubeObjects.js | 2 +- resources/web/study/Finder/data/Phase.js | 5 +++++ .../web/study/Finder/data/PublicationType.js | 5 +++++ resources/web/study/Finder/data/Status.js | 5 +++++ resources/web/study/Finder/data/StudyAccess.js | 2 +- resources/web/study/Finder/data/StudyType.js | 5 +++++ .../web/study/Finder/data/SubmissionStatus.js | 5 +++++ .../web/study/Finder/data/TherapeuticArea.js | 5 +++++ resources/web/study/Finder/data/Visibility.js | 5 +++++ .../web/study/Finder/panel/StudyAccessForm.js | 5 +++++ .../study/Finder/panel/StudyAccessTuplePanel.js | 5 +++++ .../web/study/Finder/util/CubeObjectHelper.js | 5 +++++ resources/web/study/Finder/view/GridTable.js | 5 +++++ .../trialshare/PublicationDocumentProvider.java | 2 +- .../labkey/trialshare/StudyDocumentProvider.java | 2 +- src/org/labkey/trialshare/TrialShareManager.java | 2 +- .../labkey/trialshare/data/CubeConfigBean.java | 15 +++++++++++++++ .../trialshare/data/PublicationEditBean.java | 15 +++++++++++++++ src/org/labkey/trialshare/data/StudyAccess.java | 15 +++++++++++++++ src/org/labkey/trialshare/data/StudyEditBean.java | 15 +++++++++++++++ src/org/labkey/trialshare/data/URLData.java | 15 +++++++++++++++ .../query/ManageCubeObjectQueryView.java | 15 +++++++++++++++ .../query/ManagePublicationsQueryView.java | 15 +++++++++++++++ .../trialshare/query/ManageStudiesQueryView.java | 15 +++++++++++++++ .../trialshare/query/TrialShareQuerySchema.java | 15 +++++++++++++++ src/org/labkey/trialshare/view/cubeAdmin.jsp | 2 +- .../test/pages/trialshare/CubeObjectEditPage.java | 15 +++++++++++++++ .../test/pages/trialshare/ManageDataPage.java | 15 +++++++++++++++ .../pages/trialshare/PublicationEditPage.java | 15 +++++++++++++++ .../pages/trialshare/PublicationsListHelper.java | 15 +++++++++++++++ .../test/pages/trialshare/StudiesListHelper.java | 15 +++++++++++++++ .../test/pages/trialshare/StudyEditPage.java | 15 +++++++++++++++ .../tests/trialshare/ManagePublicationsTest.java | 15 +++++++++++++++ .../test/tests/trialshare/ManageStudiesTest.java | 15 +++++++++++++++ 38 files changed, 336 insertions(+), 6 deletions(-) diff --git a/resources/web/study/Finder/data/AgeGroup.js b/resources/web/study/Finder/data/AgeGroup.js index eb5be2a5..62c1cc74 100644 --- a/resources/web/study/Finder/data/AgeGroup.js +++ b/resources/web/study/Finder/data/AgeGroup.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.data.AgeGroup', { extend: 'Ext.data.Model', diff --git a/resources/web/study/Finder/data/Condition.js b/resources/web/study/Finder/data/Condition.js index 76cb050d..72a7f8e4 100644 --- a/resources/web/study/Finder/data/Condition.js +++ b/resources/web/study/Finder/data/Condition.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.data.Condition', { extend: 'Ext.data.Model', diff --git a/resources/web/study/Finder/data/Container.js b/resources/web/study/Finder/data/Container.js index a54a9cdf..da551f9f 100644 --- a/resources/web/study/Finder/data/Container.js +++ b/resources/web/study/Finder/data/Container.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.data.Container', { extend: 'Ext.data.Model', diff --git a/resources/web/study/Finder/data/CubeConfig.js b/resources/web/study/Finder/data/CubeConfig.js index 4592fad0..c1cf9c1d 100644 --- a/resources/web/study/Finder/data/CubeConfig.js +++ b/resources/web/study/Finder/data/CubeConfig.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ // Consider: Use "cube.js" app metadata overlay paradigm Ext4.define('LABKEY.study.data.CubeConfig', { extend: 'Ext.data.Model', diff --git a/resources/web/study/Finder/data/CubeObjects.js b/resources/web/study/Finder/data/CubeObjects.js index 576928bf..3fcd0a44 100644 --- a/resources/web/study/Finder/data/CubeObjects.js +++ b/resources/web/study/Finder/data/CubeObjects.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 LabKey Corporation + * Copyright (c) 2016 LabKey Corporation * * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 */ diff --git a/resources/web/study/Finder/data/Phase.js b/resources/web/study/Finder/data/Phase.js index c5e1bfb3..f09a9743 100644 --- a/resources/web/study/Finder/data/Phase.js +++ b/resources/web/study/Finder/data/Phase.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.data.Phase', { extend: 'Ext.data.Model', diff --git a/resources/web/study/Finder/data/PublicationType.js b/resources/web/study/Finder/data/PublicationType.js index 8907e4a8..28e19ba0 100644 --- a/resources/web/study/Finder/data/PublicationType.js +++ b/resources/web/study/Finder/data/PublicationType.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.data.PublicationType', { extend: 'Ext.data.Model', diff --git a/resources/web/study/Finder/data/Status.js b/resources/web/study/Finder/data/Status.js index 09688c4f..b7c65b4c 100644 --- a/resources/web/study/Finder/data/Status.js +++ b/resources/web/study/Finder/data/Status.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.data.Status', { extend: 'Ext.data.Model', diff --git a/resources/web/study/Finder/data/StudyAccess.js b/resources/web/study/Finder/data/StudyAccess.js index eebda5cb..99d4098c 100644 --- a/resources/web/study/Finder/data/StudyAccess.js +++ b/resources/web/study/Finder/data/StudyAccess.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 LabKey Corporation + * Copyright (c) 2016 LabKey Corporation * * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 */ diff --git a/resources/web/study/Finder/data/StudyType.js b/resources/web/study/Finder/data/StudyType.js index 44e2eba0..90358025 100644 --- a/resources/web/study/Finder/data/StudyType.js +++ b/resources/web/study/Finder/data/StudyType.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.data.StudyType', { extend: 'Ext.data.Model', diff --git a/resources/web/study/Finder/data/SubmissionStatus.js b/resources/web/study/Finder/data/SubmissionStatus.js index 877938ad..d961fc10 100644 --- a/resources/web/study/Finder/data/SubmissionStatus.js +++ b/resources/web/study/Finder/data/SubmissionStatus.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.data.SubmissionStatus', { extend: 'Ext.data.Model', diff --git a/resources/web/study/Finder/data/TherapeuticArea.js b/resources/web/study/Finder/data/TherapeuticArea.js index a913787e..9997bc11 100644 --- a/resources/web/study/Finder/data/TherapeuticArea.js +++ b/resources/web/study/Finder/data/TherapeuticArea.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.data.TherapeuticArea', { extend: 'Ext.data.Model', diff --git a/resources/web/study/Finder/data/Visibility.js b/resources/web/study/Finder/data/Visibility.js index 3b848d12..486086f8 100644 --- a/resources/web/study/Finder/data/Visibility.js +++ b/resources/web/study/Finder/data/Visibility.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.data.Visibility', { extend: 'Ext.data.Model', diff --git a/resources/web/study/Finder/panel/StudyAccessForm.js b/resources/web/study/Finder/panel/StudyAccessForm.js index 6105a8e9..9e50765c 100644 --- a/resources/web/study/Finder/panel/StudyAccessForm.js +++ b/resources/web/study/Finder/panel/StudyAccessForm.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.panel.StudyAccessForm', { extend: 'Ext.form.Panel', itemId: 'studyaccessform', diff --git a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js index 4804341e..f0642f38 100644 --- a/resources/web/study/Finder/panel/StudyAccessTuplePanel.js +++ b/resources/web/study/Finder/panel/StudyAccessTuplePanel.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.panel.StudyAccessTuplePanel', { extend: 'Ext.panel.Panel', studyAccessPanels : [], diff --git a/resources/web/study/Finder/util/CubeObjectHelper.js b/resources/web/study/Finder/util/CubeObjectHelper.js index 9878a72a..a5bfca66 100644 --- a/resources/web/study/Finder/util/CubeObjectHelper.js +++ b/resources/web/study/Finder/util/CubeObjectHelper.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define('LABKEY.study.util.CubeObjectHelper', { singleton: true, diff --git a/resources/web/study/Finder/view/GridTable.js b/resources/web/study/Finder/view/GridTable.js index abe3f5d9..82b287c5 100644 --- a/resources/web/study/Finder/view/GridTable.js +++ b/resources/web/study/Finder/view/GridTable.js @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ Ext4.define("LABKEY.study.view.GridTable", { extend: 'Ext.view.Table', diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index 0aa8af9c..b66df504 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 LabKey Corporation + * Copyright (c) 2016 LabKey Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/org/labkey/trialshare/StudyDocumentProvider.java b/src/org/labkey/trialshare/StudyDocumentProvider.java index 83cd7106..2fe1ee54 100644 --- a/src/org/labkey/trialshare/StudyDocumentProvider.java +++ b/src/org/labkey/trialshare/StudyDocumentProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 LabKey Corporation + * Copyright (c) 2016 LabKey Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 5b376d4e..6ce2f9df 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 LabKey Corporation + * Copyright (c) 2015-2016 LabKey Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/org/labkey/trialshare/data/CubeConfigBean.java b/src/org/labkey/trialshare/data/CubeConfigBean.java index 9361cdcb..3aaff2b8 100644 --- a/src/org/labkey/trialshare/data/CubeConfigBean.java +++ b/src/org/labkey/trialshare/data/CubeConfigBean.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.trialshare.data; import org.jetbrains.annotations.Nullable; diff --git a/src/org/labkey/trialshare/data/PublicationEditBean.java b/src/org/labkey/trialshare/data/PublicationEditBean.java index be163704..87c1a7a2 100644 --- a/src/org/labkey/trialshare/data/PublicationEditBean.java +++ b/src/org/labkey/trialshare/data/PublicationEditBean.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.trialshare.data; import java.util.ArrayList; diff --git a/src/org/labkey/trialshare/data/StudyAccess.java b/src/org/labkey/trialshare/data/StudyAccess.java index 44a4fa42..a654b4b1 100644 --- a/src/org/labkey/trialshare/data/StudyAccess.java +++ b/src/org/labkey/trialshare/data/StudyAccess.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.trialshare.data; import org.labkey.api.data.Container; diff --git a/src/org/labkey/trialshare/data/StudyEditBean.java b/src/org/labkey/trialshare/data/StudyEditBean.java index d11c51d4..fd522983 100644 --- a/src/org/labkey/trialshare/data/StudyEditBean.java +++ b/src/org/labkey/trialshare/data/StudyEditBean.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.trialshare.data; import java.util.ArrayList; diff --git a/src/org/labkey/trialshare/data/URLData.java b/src/org/labkey/trialshare/data/URLData.java index 1657b156..e335d292 100644 --- a/src/org/labkey/trialshare/data/URLData.java +++ b/src/org/labkey/trialshare/data/URLData.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.trialshare.data; /** diff --git a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java index c64d7f4a..5f66554b 100644 --- a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java +++ b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.trialshare.query; import org.labkey.api.data.ActionButton; diff --git a/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java b/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java index 1441ddd4..73e83ce3 100644 --- a/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java +++ b/src/org/labkey/trialshare/query/ManagePublicationsQueryView.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.trialshare.query; import org.labkey.api.data.Container; diff --git a/src/org/labkey/trialshare/query/ManageStudiesQueryView.java b/src/org/labkey/trialshare/query/ManageStudiesQueryView.java index e7caf3eb..d9001f98 100644 --- a/src/org/labkey/trialshare/query/ManageStudiesQueryView.java +++ b/src/org/labkey/trialshare/query/ManageStudiesQueryView.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.trialshare.query; import org.labkey.api.data.Container; diff --git a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java index fbd3447f..ec3f9a45 100644 --- a/src/org/labkey/trialshare/query/TrialShareQuerySchema.java +++ b/src/org/labkey/trialshare/query/TrialShareQuerySchema.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.trialshare.query; import org.jetbrains.annotations.Nullable; diff --git a/src/org/labkey/trialshare/view/cubeAdmin.jsp b/src/org/labkey/trialshare/view/cubeAdmin.jsp index f2ffb94e..8fc75ca0 100644 --- a/src/org/labkey/trialshare/view/cubeAdmin.jsp +++ b/src/org/labkey/trialshare/view/cubeAdmin.jsp @@ -1,6 +1,6 @@ <% /* - * Copyright (c) 2015-2016 LabKey Corporation + * Copyright (c) 2016 LabKey Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java index 8bfdc1bf..c5f48764 100644 --- a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.test.pages.trialshare; import org.apache.commons.lang3.StringUtils; diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java index bf8687c7..36d7f8f0 100644 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.test.pages.trialshare; import org.apache.commons.lang3.StringUtils; diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java b/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java index d73106f2..0a6f6e16 100644 --- a/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.test.pages.trialshare; import org.openqa.selenium.WebDriver; diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java b/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java index d494ffb4..7f45278d 100644 --- a/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java +++ b/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.test.pages.trialshare; import org.labkey.test.BaseWebDriverTest; diff --git a/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java b/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java index da3ca939..2e8d65fe 100644 --- a/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java +++ b/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.test.pages.trialshare; import org.labkey.test.BaseWebDriverTest; diff --git a/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java b/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java index 11b5c609..4ebc08b2 100644 --- a/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java +++ b/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.test.pages.trialshare; import org.labkey.test.Locator; diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java index a7c87e4e..5bf78bfc 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.test.tests.trialshare; import org.jetbrains.annotations.Nullable; diff --git a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java index 7efe5ae5..bab26c31 100644 --- a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2016 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.labkey.test.tests.trialshare; import org.jetbrains.annotations.Nullable; From 916685b5dfd331708d1b89f0aea2e3027621e929 Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Thu, 10 Nov 2016 01:13:19 +0000 Subject: [PATCH 438/587] Remove useless XML namespaces to clean up files SVN r47581 |2016-11-10 01:13:19 +0000 --- resources/schemas/pepdb.xml | 1278 +++++++++++++++++------------------ 1 file changed, 639 insertions(+), 639 deletions(-) diff --git a/resources/schemas/pepdb.xml b/resources/schemas/pepdb.xml index a25b06db..9f16b961 100644 --- a/resources/schemas/pepdb.xml +++ b/resources/schemas/pepdb.xml @@ -1,644 +1,644 @@ - - - - - - - - - - - - - - - - - - Peptide Id - - - Peptide Group Id - - - Lab ID - - - Peptide Group Name - - - Pathogen - - pathogen_id - pathogen - pepdb - - - - Peptide Sequence - - - Protein Category - - protein_cat_id - protein_category - pepdb - - - - Sequence Length - - - AA Start - - - AA End - - - Is Child - - - Is Parent - - - Optimal Epitope List - - optimal_epitope_list_id - optimal_epitope_list - pepdb - - - - HLA Restriction - - - Peptide Flag - - - Peptide Flag Reason - - - Frequency Number - - - Frequency Update Date - - - - - - - - - Created By - - UserId - Users - core - - - - dd-MMM-yy - - - Modified By - - UserId - Users - core - - - - dd-MMM-yy - - - - 1 - true - - - Pool Type - - pool_type_id - pool_type - pepdb - - true - - - 1 - - - - - - - - - - - Created By - - UserId - Users - core - - - - dd-MMM-yy - - - Modified By - - UserId - Users - core - - - - dd-MMM-yy - - - - 1 - true - - - Pool Type - - pool_type_id - pool_type - pepdb - - true - - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Child ID - - - Child Sequence - - - Child Protein Category - - protein_cat_id - protein_category - pepdb - - - - Child Peptide Group - - peptide_group_id - peptide_group - pepdb - - - - Child Lab ID - - - Child Sequence Length - - - Child AAStart - - - Child AAEnd - - - Child Optimal Epitope List - - optimal_epitope_list_id - optimal_epitope_list - pepdb - - - - Child HLA Restriction - - - Child Peptide Flag - - - Child Flag Reason - - - Parent ID - - - Parent Sequence - - - Parent Protein Category - - protein_cat_id - protein_category - pepdb - - - - Parent Peptide Group - - peptide_group_id - peptide_group - pepdb - - - - Parent Lab ID - - - Parent Sequence Length - - - Parent AAStart - - - Parent AAEnd - - - Parent Peptide Flagged - - - Parent Flag Reason - 2 - - - - - - - - Created By - - UserId - Users - core - - - - dd-MMM-yy - - - Modified By - - UserId - Users - core - - - - dd-MMM-yy - - - - 1 - true - - - Protein Category - true - - protein_cat_id - protein_category - pepdb - - - - AAStart - true - - - AAEnd - true - - - true - - - Is Child - true - - - Is Parent - true - - - Source File Name - 1 - true - - - 1 - - - Optimal Epitope List - - optimal_epitope_list_id - optimal_epitope_list - pepdb - - - - 1 - - - Peptide Is Flagged - - - Peptide Flag Reason - 2 - - - - - - - - Peptide ID - - - Peptide Pool ID - - - Peptide Sequence - - - Protein Category - - protein_cat_id - protein_category - pepdb - - - - Peptide Group - - peptide_group_id - peptide_group - pepdb - - - - Lab ID - - - Sequence Length - - - AAStart - - - AAEnd - - - Is Child - - - Is Parent - - - Peptide Flag - - - Flag Reason - 2 - - - Pool Type - - pool_type_id - pool_type - pepdb - - - - Peptide Pool Name - - - Pool Type - - - Archived - - - - - - - - - - - - - - Created By - true - - UserId - Users - core - - - - true - dd-MMM-yy - - - true - Modified By - - UserId - Users - core - - - - true - dd-MMM-yy - - - - 1 - Peptide Group Name - - - Pathogen - - pathogen_id - pathogen - pepdb - - - - 1 - - - Clade - - clade_id - clade - pepdb - - - - Pep Align Ref - - pep_align_ref_id - pep_align_ref - pepdb - - - - Group Type - - group_type_id - group_type - pepdb - - - - peptide_group_id - - - - - - - - - - - - - - - - - - - - - -
+ + + + +
+ + + + + +
+ + + + + Peptide Id + + + Peptide Group Id + + + Lab ID + + + Peptide Group Name + + + Pathogen + + pathogen_id + pathogen + pepdb + + + + Peptide Sequence + + + Protein Category + + protein_cat_id + protein_category + pepdb + + + + Sequence Length + + + AA Start + + + AA End + + + Is Child + + + Is Parent + + + Optimal Epitope List + + optimal_epitope_list_id + optimal_epitope_list + pepdb + + + + HLA Restriction + + + Peptide Flag + + + Peptide Flag Reason + + + Frequency Number + + + Frequency Update Date + + + +
+ + + + + Created By + + UserId + Users + core + + + + dd-MMM-yy + + + Modified By + + UserId + Users + core + + + + dd-MMM-yy + + + + 1 + true + + + Pool Type + + pool_type_id + pool_type + pepdb + + true + + + 1 + + + + + +
+ + + + + Created By + + UserId + Users + core + + + + dd-MMM-yy + + + Modified By + + UserId + Users + core + + + + dd-MMM-yy + + + + 1 + true + + + Pool Type + + pool_type_id + pool_type + pepdb + + true + + + + 1 + + + + + + +
+ + + + + +
+ + + + + + + + + + +
+ + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + Child ID + + + Child Sequence + + + Child Protein Category + + protein_cat_id + protein_category + pepdb + + + + Child Peptide Group + + peptide_group_id + peptide_group + pepdb + + + + Child Lab ID + + + Child Sequence Length + + + Child AAStart + + + Child AAEnd + + + Child Optimal Epitope List + + optimal_epitope_list_id + optimal_epitope_list + pepdb + + + + Child HLA Restriction + + + Child Peptide Flag + + + Child Flag Reason + + + Parent ID + + + Parent Sequence + + + Parent Protein Category + + protein_cat_id + protein_category + pepdb + + + + Parent Peptide Group + + peptide_group_id + peptide_group + pepdb + + + + Parent Lab ID + + + Parent Sequence Length + + + Parent AAStart + + + Parent AAEnd + + + Parent Peptide Flagged + + + Parent Flag Reason + 2 + + +
+ + + + + Created By + + UserId + Users + core + + + + dd-MMM-yy + + + Modified By + + UserId + Users + core + + + + dd-MMM-yy + + + + 1 + true + + + Protein Category + true + + protein_cat_id + protein_category + pepdb + + + + AAStart + true + + + AAEnd + true + + + true + + + Is Child + true + + + Is Parent + true + + + Source File Name + 1 + true + + + 1 + + + Optimal Epitope List + + optimal_epitope_list_id + optimal_epitope_list + pepdb + + + + 1 + + + Peptide Is Flagged + + + Peptide Flag Reason + 2 + + +
+ + + + + Peptide ID + + + Peptide Pool ID + + + Peptide Sequence + + + Protein Category + + protein_cat_id + protein_category + pepdb + + + + Peptide Group + + peptide_group_id + peptide_group + pepdb + + + + Lab ID + + + Sequence Length + + + AAStart + + + AAEnd + + + Is Child + + + Is Parent + + + Peptide Flag + + + Flag Reason + 2 + + + Pool Type + + pool_type_id + pool_type + pepdb + + + + Peptide Pool Name + + + Pool Type + + + Archived + + +
+ + + + + +
+ + + + + Created By + true + + UserId + Users + core + + + + true + dd-MMM-yy + + + true + Modified By + + UserId + Users + core + + + + true + dd-MMM-yy + + + + 1 + Peptide Group Name + + + Pathogen + + pathogen_id + pathogen + pepdb + + + + 1 + + + Clade + + clade_id + clade + pepdb + + + + Pep Align Ref + + pep_align_ref_id + pep_align_ref + pepdb + + + + Group Type + + group_type_id + group_type + pepdb + + + + peptide_group_id +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + From 6f9d189bb8ac3ed737d25c231f39fcffe601831c Mon Sep 17 00:00:00 2001 From: labkey-adam Date: Fri, 11 Nov 2016 22:42:04 -0800 Subject: [PATCH 439/587] Issue 23582: Issues with loading & caching module-based reports Report descriptors now hold a category id or name instead of a ViewCategory object. This removes some container-specific state from module reports (which are global), eliminates the strange requirement to pass in container & user to retrieve these module resources, and stops auto-creating categories. --- src/org/labkey/trialshare/data/StudyPublicationBean.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/trialshare/data/StudyPublicationBean.java b/src/org/labkey/trialshare/data/StudyPublicationBean.java index c6da64f2..b1365fbb 100644 --- a/src/org/labkey/trialshare/data/StudyPublicationBean.java +++ b/src/org/labkey/trialshare/data/StudyPublicationBean.java @@ -457,7 +457,7 @@ public void setThumbnails(User user, ActionURL actionURL) for (Report report : ReportService.get().getReports(user, container)) { - ViewCategory category = report.getDescriptor().getCategory(); + ViewCategory category = report.getDescriptor().getCategory(container); if (figureCategories.contains(category)) { @@ -481,7 +481,7 @@ public List getFigureReportCategories(User user, Container contain for (Report report : ReportService.get().getReports(user, container)) { - ViewCategory category = report.getDescriptor().getCategory(); + ViewCategory category = report.getDescriptor().getCategory(container); if (category != null && category.getLabel().contains(FIGURES_CATEGORY_TEXT)) { From 5b5b857134a06ac3fa774a5d7c9e52fc000b89fd Mon Sep 17 00:00:00 2001 From: XingY Date: Thu, 1 Dec 2016 15:49:46 -0800 Subject: [PATCH 440/587] Issue 28475: Use natural sort in study/publication form drop downs --- .../web/study/Finder/data/FacetMembers.js | 6 +++ resources/web/study/Finder/dataFinder.lib.xml | 1 + .../panel/PublicationDetailsFormPanel.js | 9 ++-- .../web/study/Finder/panel/StudyCards.js | 3 +- .../web/study/Finder/util/CubeObjectHelper.js | 42 +++++++++++++++++++ 5 files changed, 53 insertions(+), 8 deletions(-) diff --git a/resources/web/study/Finder/data/FacetMembers.js b/resources/web/study/Finder/data/FacetMembers.js index 84ab293f..d0066178 100644 --- a/resources/web/study/Finder/data/FacetMembers.js +++ b/resources/web/study/Finder/data/FacetMembers.js @@ -16,10 +16,16 @@ Ext4.define('LABKEY.study.store.FacetMembers', { rank1 = o1.get('facet').get("ordinal"); rank2 = o2.get('facet').get("ordinal"); if (rank1 === rank2) + { + if (o1.get("level") === '[Publication.Study].[Study]' && o1.get("level") == o2.get("level")) + { + return LABKEY.study.util.CubeObjectHelper.naturalSortFn(o1.get("name"), o2.get("name")); + } if (o1.get("name") === o2.get("name")) return 0; else return o1.get("name") < o2.get("name") ? -1 : 1; + } else return rank1 < rank2 ? -1 : 1; diff --git a/resources/web/study/Finder/dataFinder.lib.xml b/resources/web/study/Finder/dataFinder.lib.xml index 41b1a0a0..02c3df0b 100644 --- a/resources/web/study/Finder/dataFinder.lib.xml +++ b/resources/web/study/Finder/dataFinder.lib.xml @@ -3,6 +3,7 @@ - - -

Search for Peptides using different criteria :



- - - - - - - - - - - <%if(bean.getQueryKey() != null && bean.getQueryKey().equals(PepDBSchema.COLUMN_PROTEIN_CAT_ID)){%> - - - - - - - - - - - <%}%> - <%if(bean.getQueryKey() != null && bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)){%> - - - - - - <%}%> - - - - -
Search Criteria : - -
- <%=(bean.getQueryKey() != null && bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE))?" Equals / Contains : ":" Equals : "%> - - - <%if(bean.getQueryKey() == null || bean.getQueryKey().length() == 0){ %> - - <%} - else if(bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)){ - PeptideGroup[] peptideGroups = PepDBManager.getPeptideGroups(); - %> - - <% } - else if (bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_POOL_ID)) { - PeptidePool[] peptidePools = PepDBManager.getPeptidePools(); - %> - - <% } - else if (bean.getQueryKey().equals(PepDBSchema.COLUMN_PROTEIN_CAT_ID)) { - ProteinCategory[] proteinCategories = PepDBManager.getProteinCategory(); - %> - - <%} - else if(bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE) || bean.getQueryKey().equals(PepDBSchema.COLUMN_PARENT_SEQUENCE) || bean.getQueryKey().equals(PepDBSchema.COLUMN_CHILD_SEQUENCE)){%> - "/>   - <%}%> -
- Amino Acid Start Position: - "/> -
- Amino Acid End Position: - "/> -
- PEPTIDE NUMBER: - "/> -
- -
-
+<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> +<%@ page import="org.labkey.api.view.HttpView"%> +<%@ page import="org.scharp.atlas.pepdb.PepDBManager" %> +<%@ page import="org.scharp.atlas.pepdb.model.PeptideGroup" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBBaseController.*" %> +<%@ page import="java.sql.SQLException" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBSchema" %> +<%@ page import="org.labkey.api.view.JspView" %> +<%@ page import="org.scharp.atlas.pepdb.model.PeptidePool" %> +<%@ page import="org.scharp.atlas.pepdb.model.ProteinCategory" %> +<%@ page extends="org.labkey.api.jsp.JspBase" %> +<% + JspView me = (JspView) HttpView.currentView(); + PeptideQueryForm bean = me.getModelBean(); + if(bean.getMessage() != null){ +%> +<%=bean.getMessage()%><%}%> + + + +

Search for Peptides using different criteria :



+ + + + + + + + + + + <%if(bean.getQueryKey() != null && bean.getQueryKey().equals(PepDBSchema.COLUMN_PROTEIN_CAT_ID)){%> + + + + + + + + + + + <%}%> + <%if(bean.getQueryKey() != null && bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)){%> + + + + + + <%}%> + + + + +
Search Criteria : + +
+ <%=(bean.getQueryKey() != null && bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE))?" Equals / Contains : ":" Equals : "%> + + + <%if(bean.getQueryKey() == null || bean.getQueryKey().length() == 0){ %> + + <%} + else if(bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID)){ + PeptideGroup[] peptideGroups = PepDBManager.getPeptideGroups(); + %> + + <% } + else if (bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_POOL_ID)) { + PeptidePool[] peptidePools = PepDBManager.getPeptidePools(); + %> + + <% } + else if (bean.getQueryKey().equals(PepDBSchema.COLUMN_PROTEIN_CAT_ID)) { + ProteinCategory[] proteinCategories = PepDBManager.getProteinCategory(); + %> + + <%} + else if(bean.getQueryKey().equals(PepDBSchema.COLUMN_PEPTIDE_SEQUENCE) || bean.getQueryKey().equals(PepDBSchema.COLUMN_PARENT_SEQUENCE) || bean.getQueryKey().equals(PepDBSchema.COLUMN_CHILD_SEQUENCE)){%> + "/>   + <%}%> +
+ Amino Acid Start Position: + "/> +
+ Amino Acid End Position: + "/> +
+ PEPTIDE NUMBER: + "/> +
+ +
+
diff --git a/test/sampledata/test_import_files/peptide_file/gagptegprac.txt b/test/sampledata/test_import_files/peptide_file/gagptegprac.txt index 3d862dd6..bdbf80b9 100755 --- a/test/sampledata/test_import_files/peptide_file/gagptegprac.txt +++ b/test/sampledata/test_import_files/peptide_file/gagptegprac.txt @@ -1,17 +1,17 @@ -PEPTIDE SEQUENCE IS CHILD PROTEIN CATEGORY PEPTIDE GROUP ID SEQUENCE LENGTH AASTART AAEND IN A LIST HLA RESTRICTION -WIILGLNKIVRMYSP FALSE p24 gagptegprac GAG1-1 15 133 147 -TLEEMMTACQGVGGP FALSE p24 gagptegprac GAG1-2 15 210 224 -RQGPKEPFRDYVDRF FALSE p24 gagptegprac GAG1-3 15 154 168 -REPRGSDIAGTTSTL FALSE p24 gagptegprac GAG1-4 15 97 111 -HQMKDCTERQANFLG FALSE p2p7p1p6 gagptegprac GAG1-5 15 58 72 -LNTVGGHQAAMQMLK FALSE p24 gagptegprac GAG1-6 15 56 70 -GEIYKRWIILGLNKI FALSE p24 gagptegprac GAG1-7 15 127 141 -ALSEGATPQDLNTML FALSE p24 gagptegprac GAG1-8 15 42 56 -KCGKEGHQMKDCTER FALSE p2p7p1p6 gagptegprac GAG2-1 15 52 66 -TLLVQNANPDCKTIL FALSE p24 gagptegprac GAG2-2 15 188 202 -TERQANFLGKIWPSH FALSE p2p7p1p6 gagptegprac GAG2-3 15 64 78 -NCRAPRKKGCWKCGK FALSE p2p7p1p6 gagptegprac GAG2-4 15 41 55 -NKIVRMYSPVSILDI FALSE p24 gagptegprac GAG2-5 15 139 153 -EEKAFSPEVIPMFSA FALSE p24 gagptegprac GAG2-6 15 28 42 -GPGHKARVLAEAMSQ FALSE p24-p2p7p1p6 gagptegprac GAG2-7 15 223 6 -TINEEAAEWDRLHPV FALSE p24 gagptegprac GAG2-8 15 72 86 +PEPTIDE SEQUENCE IS CHILD PROTEIN CATEGORY PEPTIDE GROUP ID SEQUENCE LENGTH AASTART AAEND IN A LIST HLA RESTRICTION +WIILGLNKIVRMYSP FALSE p24 gagptegprac GAG1-1 15 133 147 +TLEEMMTACQGVGGP FALSE p24 gagptegprac GAG1-2 15 210 224 +RQGPKEPFRDYVDRF FALSE p24 gagptegprac GAG1-3 15 154 168 +REPRGSDIAGTTSTL FALSE p24 gagptegprac GAG1-4 15 97 111 +HQMKDCTERQANFLG FALSE p2p7p1p6 gagptegprac GAG1-5 15 58 72 +LNTVGGHQAAMQMLK FALSE p24 gagptegprac GAG1-6 15 56 70 +GEIYKRWIILGLNKI FALSE p24 gagptegprac GAG1-7 15 127 141 +ALSEGATPQDLNTML FALSE p24 gagptegprac GAG1-8 15 42 56 +KCGKEGHQMKDCTER FALSE p2p7p1p6 gagptegprac GAG2-1 15 52 66 +TLLVQNANPDCKTIL FALSE p24 gagptegprac GAG2-2 15 188 202 +TERQANFLGKIWPSH FALSE p2p7p1p6 gagptegprac GAG2-3 15 64 78 +NCRAPRKKGCWKCGK FALSE p2p7p1p6 gagptegprac GAG2-4 15 41 55 +NKIVRMYSPVSILDI FALSE p24 gagptegprac GAG2-5 15 139 153 +EEKAFSPEVIPMFSA FALSE p24 gagptegprac GAG2-6 15 28 42 +GPGHKARVLAEAMSQ FALSE p24-p2p7p1p6 gagptegprac GAG2-7 15 223 6 +TINEEAAEWDRLHPV FALSE p24 gagptegprac GAG2-8 15 72 86 diff --git a/test/sampledata/test_import_files/peptide_file/gagptegprac1.txt b/test/sampledata/test_import_files/peptide_file/gagptegprac1.txt index 5104fcd1..22c44b81 100755 --- a/test/sampledata/test_import_files/peptide_file/gagptegprac1.txt +++ b/test/sampledata/test_import_files/peptide_file/gagptegprac1.txt @@ -1,4 +1,4 @@ -PEPTIDE SEQUENCE IS CHILD PROTEIN CATEGORY PEPTIDE GROUP ID SEQUENCE LENGTH AASTART AAEND IN A LIST HLA RESTRICTION -WIILGLNKIVRMYSP FALSE p24 gagptegprac1 GAG1-1 15 133 147 -TLEEMMTACQGVGGP FALSE p24 gagptegprac1 GAG1-2 15 210 224 -RQGPKEPFRDYVDRF FALSE p24 gagptegprac1 GAG1-3 15 154 168 +PEPTIDE SEQUENCE IS CHILD PROTEIN CATEGORY PEPTIDE GROUP ID SEQUENCE LENGTH AASTART AAEND IN A LIST HLA RESTRICTION +WIILGLNKIVRMYSP FALSE p24 gagptegprac1 GAG1-1 15 133 147 +TLEEMMTACQGVGGP FALSE p24 gagptegprac1 GAG1-2 15 210 224 +RQGPKEPFRDYVDRF FALSE p24 gagptegprac1 GAG1-3 15 154 168 diff --git a/test/sampledata/test_import_files/pool_description_file/pool_description.txt b/test/sampledata/test_import_files/pool_description_file/pool_description.txt index 778f816d..ecd81127 100755 --- a/test/sampledata/test_import_files/pool_description_file/pool_description.txt +++ b/test/sampledata/test_import_files/pool_description_file/pool_description.txt @@ -1,2 +1,2 @@ -Pool Name Pool Type Parent Pool Name Matrix Pool Id -Prac_Pool Pool +Pool Name Pool Type Parent Pool Name Matrix Pool Id +Prac_Pool Pool diff --git a/test/sampledata/test_import_files/pool_detail_file/pool_details.txt b/test/sampledata/test_import_files/pool_detail_file/pool_details.txt index 6a363032..03f732d1 100755 --- a/test/sampledata/test_import_files/pool_detail_file/pool_details.txt +++ b/test/sampledata/test_import_files/pool_detail_file/pool_details.txt @@ -1,3 +1,3 @@ -POOL NAME PEPTIDE SEQUENCE PEPTIDE GROUP -Prac_Pool WIILGLNKIVRMYSP gagptegprac -Prac_Pool REPRGSDIAGTTSTL gagptegprac +POOL NAME PEPTIDE SEQUENCE PEPTIDE GROUP +Prac_Pool WIILGLNKIVRMYSP gagptegprac +Prac_Pool REPRGSDIAGTTSTL gagptegprac diff --git a/test/src/org/labkey/test/tests/external/scharp/PepDBModuleTest.java b/test/src/org/labkey/test/tests/external/scharp/PepDBModuleTest.java index 2d245d46..10f17a9f 100644 --- a/test/src/org/labkey/test/tests/external/scharp/PepDBModuleTest.java +++ b/test/src/org/labkey/test/tests/external/scharp/PepDBModuleTest.java @@ -1,383 +1,383 @@ -/* - * Copyright (c) 2013-2015 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.test.tests.external.scharp; - -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.labkey.remoteapi.CommandException; -import org.labkey.remoteapi.Connection; -import org.labkey.remoteapi.query.ContainerFilter; -import org.labkey.remoteapi.query.DeleteRowsCommand; -import org.labkey.remoteapi.query.Row; -import org.labkey.remoteapi.query.RowMap; -import org.labkey.remoteapi.query.SelectRowsCommand; -import org.labkey.remoteapi.query.SelectRowsResponse; -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.Locator; -import org.labkey.test.TestFileUtils; -import org.labkey.test.TestTimeoutException; -import org.labkey.test.WebTestHelper; -import org.labkey.test.categories.Disabled; -import org.labkey.test.categories.External; -import org.labkey.test.util.LogMethod; -import org.labkey.test.util.PostgresOnlyTest; -import org.openqa.selenium.By; -import org.openqa.selenium.support.ui.Select; - -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -@Category({External.class, Disabled.class}) -public class PepDBModuleTest extends BaseWebDriverTest implements PostgresOnlyTest -{ - - public static final String FOLDER_TYPE = "Custom"; - // Folder type defined in customFolder.foldertype.xml - public static final String MODULE_NAME = "PepDB"; - public static final String USER_SCHEMA_NAME = "pepdb"; - public static final String FOLDER_NAME = "PepDB"; - private int peptideStartIndex = 0; - - protected String getProjectName() - { - return getClass().getSimpleName() + " Project"; - } - - // Setup duplicates the folder structure and webparts used in production. - private void setupProject() - { - log("Starting setupProject()"); - assertModuleDeployed(MODULE_NAME); - _containerHelper.createProject(getProjectName(), FOLDER_TYPE); - - _containerHelper.enableModule(getProjectName(), MODULE_NAME); - _containerHelper.createSubfolder(getProjectName(), "Labs", FOLDER_TYPE); - _containerHelper.createSubfolder(getProjectName() + "/Labs", "Test", FOLDER_TYPE); - _containerHelper.createSubfolder(getProjectName() + "/Labs/Test", FOLDER_NAME, FOLDER_TYPE); - clickFolder("Labs"); - clickFolder("Test"); - clickFolder(FOLDER_NAME); - - _containerHelper.enableModules(Arrays.asList("Issues", "Wiki", "PepDB")); - _containerHelper.disableModules("Portal"); - setDefaultModule("PepDB"); - log("Finished setupProject()"); - } - - /** - * Set which view in this folder should be the default - * - * @param moduleName - */ - void setDefaultModule(String moduleName) - { - goToFolderManagement(); - clickAndWait(Locator.linkWithText("Folder Type")); - selectOptionByText(Locator.name("defaultModule"), moduleName); - clickButton("Update Folder"); - } - - @Test - public void testSteps() throws Exception - { - setupProject(); - assertModuleEnabled("Issues"); - assertModuleEnabled("Wiki"); - assertModuleEnabled("PepDB"); - log("Expected modules enabled."); - - beginAt("/pepdb/" + getProjectName() + "/Labs/Test/" + FOLDER_NAME + "/begin.view?"); - - /* Insert the Peptide Group" */ - getDriver().findElement(By.linkText("Insert a New Group")).click(); - getDriver().findElement(By.name("peptide_group_name")).clear(); - getDriver().findElement(By.name("peptide_group_name")).sendKeys("gagptegprac"); - new Select(getDriver().findElement(By.name("pathogen_id"))).selectByVisibleText("Other"); - new Select(getDriver().findElement(By.name("clade_id"))).selectByVisibleText("Other"); - new Select(getDriver().findElement(By.name("group_type_id"))).selectByVisibleText("Other"); - getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); - clickFolder(FOLDER_NAME); - - // Import some test Peptides from a file. - clickAndWait(Locator.linkWithText("Import Peptides")); - new Select(getDriver().findElement(By.id("actionType"))).selectByVisibleText("Peptides"); - getDriver().findElement(By.name("pFile")).sendKeys(getSampledataPath() + "/peptide_file/gagptegprac.txt"); - clickButton("Import Peptides"); - - /* Import Peptide Pool 'Pool Descriptions' file. - * ./test_import_files/pool_description_file/pool_description.txt - */ - clickFolder(FOLDER_NAME); - getDriver().findElement(By.linkText("Import Peptide Pools")).click(); - new Select(getDriver().findElement(By.name("actionType"))).selectByVisibleText("Pool Descriptions"); - - getDriver().findElement(By.name("pFile")).sendKeys(getSampledataPath() + "/pool_description_file/pool_description.txt"); - getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); - - /* Import Peptide Pool 'Peptides in Pool' file. - ./test_import_files/pool_detail_file/pool_details.txt - */ - clickFolder(FOLDER_NAME); - getDriver().findElement(By.linkText("Import Peptide Pools")).click(); - new Select(getDriver().findElement(By.name("actionType"))).selectByVisibleText("Peptides in Pool"); - - getDriver().findElement(By.name("pFile")).sendKeys(getSampledataPath() + "/pool_detail_file/pool_details.txt"); - getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); - - /* Search for the Peptides belonging to our just-imported pool */ - clickFolder(FOLDER_NAME); - getDriver().findElement(By.linkText("Search for Peptides by Criteria")).click(); - new Select(getDriver().findElement(By.name("queryKey"))).selectByVisibleText("Peptides in a Peptide Pool"); - selectOptionByTextContaining(getDriver().findElement(By.name("queryValue")), "Prac_Pool"); - - getDriver().findElement(By.name("action_type")).click(); - - // Identify the index at which our peptide IDs start. - findPeptideStartIndex(); - // List the peptides in the the 'gagptegprac' Peptide Group. We expect there to be 16 of them. - clickFolder(FOLDER_NAME); - getDriver().findElement(By.linkText("Search for Peptides by Criteria")).click(); - waitForText("Search for Peptides using different criteria : "); - new Select(getDriver().findElement(By.id("queryKey"))).selectByVisibleText("Peptides in a Peptide Group"); - new Select(getDriver().findElement(By.id("queryValue"))).selectByVisibleText("gagptegprac"); - - getDriver().findElement(By.name("action_type")).click(); - - assertTextPresentInThisOrder("There are (16) peptides in the 'gagptegprac' peptide group. "); - // Select a newly uploaded peptide, #3 and edit it to have a storage location of 'Kitchen Sink' - getDriver().findElement(By.linkText(pepString(4))).click(); - // Verify the expected record's content - assertTrue(getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody")).getText().matches("^[\\s\\S]*Peptide Id\\s*" + pepString(4) + "\nPeptide Sequence\\s*REPRGSDIAGTTSTL\nProtein Category\\s*p24\nSequence Length\\s*15\nAAStart\\s*97\nAAEnd\\s*111\nIs Child\\s*false\nIs Parent\\s*false[\\s\\S]*$")); - - getDriver().findElement(By.xpath("//form[@id='peptides']/div/span[2]/a/span")).click(); - getDriver().findElement(By.name("storage_location")).clear(); - getDriver().findElement(By.name("storage_location")).sendKeys("Kitchen Sink"); - clickAndWait(Locator.xpath("//span[text()='Save Changes']")); - - // Assert that the Storage Location now contains "Kitchen Sink" - assertTrue(getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody")).getText().matches("^[\\s\\S]*Peptide Id\\s*" + pepString(4) + "\nPeptide Sequence\\s*REPRGSDIAGTTSTL\nProtein Category\\s*p24\nSequence Length\\s*15\nAAStart\\s*97\nAAEnd\\s*111\nIs Child\\s*false\nIs Parent\\s*false[\\s\\S]*$")); - getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); - - // Search for a single, newly-uploaded peptide and verify it displays as expected. - getDriver().findElement(By.name("peptide_id")).clear(); - getDriver().findElement(By.name("peptide_id")).sendKeys(Integer.toString(peptideStartIndex + 9)); - clickButton("Find"); - // Verify the expected record's content - assertTrue(getDriver().findElement(By.id("peptides")).getText().matches("^[\\s\\S]*Peptide Id\\s*" + pepString(9) + "\nPeptide Sequence\\s*KCGKEGHQMKDCTER\nProtein Category\\s*p2p7p1p6\nSequence Length\\s*15\nAAStart\\s*52\nAAEnd\\s*66\nIs Child\\s*false\nIs Parent\\s*false\nStorage Location\\s*\n[\\s\\S]*$")); - - getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); - - - /* - * - * 'Search for Peptides by Criteria' using 'Peptides in a Peptide Pool' - * using the newly imported Peptide Pool. - * - * Verify that the peptides found match what was expected (by the pool import), and their values include the - * columns imported earlier during the importing of the peptides. - * - */ - clickFolder(FOLDER_NAME); - getDriver().findElement(By.linkText("Search for Peptides by Criteria")).click(); - new Select(getDriver().findElement(By.id("queryKey"))).selectByVisibleText("Peptides in a Peptide Pool"); - - selectOptionByTextContaining(getDriver().findElement(By.name("queryValue")), "Prac_Pool"); - getDriver().findElement(By.name("action_type")).click(); - getDriver().findElement(By.linkText(pepString(4))).click(); - - StringBuffer verificationErrors = new StringBuffer(); - try - { - assertEquals("Peptide Sequence", getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody/tr[2]/td")).getText()); - } - catch (Error e) - { - verificationErrors.append(e.toString()); - } - try - { - assertEquals("REPRGSDIAGTTSTL", getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody/tr[2]/td[2]")).getText()); - } - catch (Error e) - { - verificationErrors.append(e.toString()); - } - try - { - assertEquals("gagptegprac (LAB ID =GAG1-4)", getDriver().findElement(By.cssSelector("#bodypanel > div > table > tbody > tr > td")).getText()); - } - catch (Error e) - { - verificationErrors.append(e.toString()); - } - - } - - @LogMethod - private void cleanupSchema(Connection cn) throws IOException - { - if (cn == null) - { - cn = createDefaultConnection(false); - } - - cleanupTable(cn, "pepdb", "peptide_pool_assignment"); - cleanupTable(cn, "pepdb", "peptide_group_assignment"); - cleanupTable(cn, "pepdb", "peptides"); - cleanupTable(cn, "pepdb", "peptide_pool"); - cleanupTable(cn, "pepdb", "peptide_group"); - - } - - @LogMethod - private void cleanupTable(Connection cn, String schemaName, String tableName) throws IOException - { - log("** Deleting all " + tableName + " in all containers"); - try - { - SelectRowsCommand selectCmd = new SelectRowsCommand(schemaName, tableName); - selectCmd.setMaxRows(-1); - selectCmd.setContainerFilter(ContainerFilter.AllFolders); - selectCmd.setColumns(Arrays.asList("*")); - SelectRowsResponse selectResp = selectCmd.execute(cn, getProjectName()); - if (selectResp.getRowCount().intValue() > 0) - { - DeleteRowsCommand deleteCmd = new DeleteRowsCommand(schemaName, tableName); - deleteCmd.setRows(selectResp.getRows()); - deleteCmd.execute(cn, getProjectName()); - assertEquals("Expected no rows remaining", 0, selectCmd.execute(cn, getProjectName()).getRowCount().intValue()); - } - } - catch (CommandException e) - { - log("** Error during cleanupTable:"); - e.printStackTrace(System.out); - } - } - - // We have to expose our schema as an external schema in order to run the selectRow and deleteRow commands. - void ensureExternalSchema(String containerPath) - { - log("** Ensure ExternalSchema: " + USER_SCHEMA_NAME); - - beginAt("/query/" + containerPath + "/admin.view"); - - if (!isTextPresent("reload")) - { - assertTextPresent("new external schema"); - log("** Creating ExternalSchema: " + USER_SCHEMA_NAME); - clickAndWait(Locator.linkWithText("new external schema")); - checkCheckbox(Locator.name("includeSystem")); - setFormElement(Locator.name("userSchemaName"), USER_SCHEMA_NAME); - setFormElement(Locator.name("sourceSchemaName"), USER_SCHEMA_NAME); - pressEnter(Locator.name("sourceSchemaName")); - checkCheckbox(Locator.name("editable")); - uncheckCheckbox(Locator.name("indexable")); - clickButton("Create"); - } - assertTextPresent(USER_SCHEMA_NAME); - assertTextNotPresent("reload all schemas"); // Present only for external schemas > 1 - } - - // Identify the index at which our peptide IDs start. - private void findPeptideStartIndex() throws IOException - { - Connection cn = createDefaultConnection(false); - try - { - ensureExternalSchema(getProjectName()); - SelectRowsCommand selectCmd = new SelectRowsCommand("pepdb", "peptides"); - selectCmd.setMaxRows(1); - selectCmd.setContainerFilter(ContainerFilter.AllFolders); - selectCmd.setColumns(Arrays.asList("*")); - SelectRowsResponse selectResp = selectCmd.execute(cn, getProjectName()); - - if (selectResp.getRowCount().intValue() > 0) - { - Row convertedRow = new RowMap(selectResp.getRows().get(0)); - peptideStartIndex = ((int) convertedRow.getValue("peptide_id")) - 1; - } - } - catch (CommandException e) - { - log("** Error during findPeptideStartIndex:"); - e.printStackTrace(System.out); - } - } - - // Convert the peptide ID integer into its string representation. - private String pepString(int peptideIdOffset) - { - //return String.format("P%06d", peptideStartIndex + peptideIdOffset); - return String.format("P%d", peptideStartIndex + peptideIdOffset); - } - - protected void assertModuleDeployed(String moduleName) - { - log("Ensuring that that '" + moduleName + "' module is deployed"); - goToAdminConsole(); - assertTextPresent(moduleName); - } - - protected void assertModuleEnabled(String moduleName) - { - log("Ensuring that that '" + moduleName + "' module is enabled"); - goToFolderManagement(); - clickAndWait(Locator.linkWithText("Folder Type")); - assertElementPresent(Locator.xpath("//input[@type='checkbox' and @checked and @title='" + moduleName + "']")); - } - - protected void assertModuleEnabledByDefault(String moduleName) - { - log("Ensuring that that '" + moduleName + "' module is enabled"); - goToFolderManagement(); - clickAndWait(Locator.linkWithText("Folder Type")); - assertElementPresent(Locator.xpath("//input[@type='checkbox' and @checked and @disabled and @title='" + moduleName + "']")); - } - - @Override - protected void doCleanup(boolean afterTest) throws TestTimeoutException - { - Connection cn = WebTestHelper.getRemoteApiConnection(); - try - { - cleanupSchema(cn); - _containerHelper.deleteProject(getProjectName(), afterTest); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - // super method deletes the project. - super.doCleanup(afterTest); - } - - public List getAssociatedModules() - { - return Arrays.asList("pepdb"); - } - - public static String getSampledataPath() - { - File path = new File(TestFileUtils.getLabKeyRoot(), "externalModules/scharp/pepdb/test/sampledata/test_import_files"); - return path.toString(); - } -} +/* + * Copyright (c) 2013-2015 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.labkey.test.tests.external.scharp; + +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.labkey.remoteapi.CommandException; +import org.labkey.remoteapi.Connection; +import org.labkey.remoteapi.query.ContainerFilter; +import org.labkey.remoteapi.query.DeleteRowsCommand; +import org.labkey.remoteapi.query.Row; +import org.labkey.remoteapi.query.RowMap; +import org.labkey.remoteapi.query.SelectRowsCommand; +import org.labkey.remoteapi.query.SelectRowsResponse; +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.Locator; +import org.labkey.test.TestFileUtils; +import org.labkey.test.TestTimeoutException; +import org.labkey.test.WebTestHelper; +import org.labkey.test.categories.Disabled; +import org.labkey.test.categories.External; +import org.labkey.test.util.LogMethod; +import org.labkey.test.util.PostgresOnlyTest; +import org.openqa.selenium.By; +import org.openqa.selenium.support.ui.Select; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@Category({External.class, Disabled.class}) +public class PepDBModuleTest extends BaseWebDriverTest implements PostgresOnlyTest +{ + + public static final String FOLDER_TYPE = "Custom"; + // Folder type defined in customFolder.foldertype.xml + public static final String MODULE_NAME = "PepDB"; + public static final String USER_SCHEMA_NAME = "pepdb"; + public static final String FOLDER_NAME = "PepDB"; + private int peptideStartIndex = 0; + + protected String getProjectName() + { + return getClass().getSimpleName() + " Project"; + } + + // Setup duplicates the folder structure and webparts used in production. + private void setupProject() + { + log("Starting setupProject()"); + assertModuleDeployed(MODULE_NAME); + _containerHelper.createProject(getProjectName(), FOLDER_TYPE); + + _containerHelper.enableModule(getProjectName(), MODULE_NAME); + _containerHelper.createSubfolder(getProjectName(), "Labs", FOLDER_TYPE); + _containerHelper.createSubfolder(getProjectName() + "/Labs", "Test", FOLDER_TYPE); + _containerHelper.createSubfolder(getProjectName() + "/Labs/Test", FOLDER_NAME, FOLDER_TYPE); + clickFolder("Labs"); + clickFolder("Test"); + clickFolder(FOLDER_NAME); + + _containerHelper.enableModules(Arrays.asList("Issues", "Wiki", "PepDB")); + _containerHelper.disableModules("Portal"); + setDefaultModule("PepDB"); + log("Finished setupProject()"); + } + + /** + * Set which view in this folder should be the default + * + * @param moduleName + */ + void setDefaultModule(String moduleName) + { + goToFolderManagement(); + clickAndWait(Locator.linkWithText("Folder Type")); + selectOptionByText(Locator.name("defaultModule"), moduleName); + clickButton("Update Folder"); + } + + @Test + public void testSteps() throws Exception + { + setupProject(); + assertModuleEnabled("Issues"); + assertModuleEnabled("Wiki"); + assertModuleEnabled("PepDB"); + log("Expected modules enabled."); + + beginAt("/pepdb/" + getProjectName() + "/Labs/Test/" + FOLDER_NAME + "/begin.view?"); + + /* Insert the Peptide Group" */ + getDriver().findElement(By.linkText("Insert a New Group")).click(); + getDriver().findElement(By.name("peptide_group_name")).clear(); + getDriver().findElement(By.name("peptide_group_name")).sendKeys("gagptegprac"); + new Select(getDriver().findElement(By.name("pathogen_id"))).selectByVisibleText("Other"); + new Select(getDriver().findElement(By.name("clade_id"))).selectByVisibleText("Other"); + new Select(getDriver().findElement(By.name("group_type_id"))).selectByVisibleText("Other"); + getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); + clickFolder(FOLDER_NAME); + + // Import some test Peptides from a file. + clickAndWait(Locator.linkWithText("Import Peptides")); + new Select(getDriver().findElement(By.id("actionType"))).selectByVisibleText("Peptides"); + getDriver().findElement(By.name("pFile")).sendKeys(getSampledataPath() + "/peptide_file/gagptegprac.txt"); + clickButton("Import Peptides"); + + /* Import Peptide Pool 'Pool Descriptions' file. + * ./test_import_files/pool_description_file/pool_description.txt + */ + clickFolder(FOLDER_NAME); + getDriver().findElement(By.linkText("Import Peptide Pools")).click(); + new Select(getDriver().findElement(By.name("actionType"))).selectByVisibleText("Pool Descriptions"); + + getDriver().findElement(By.name("pFile")).sendKeys(getSampledataPath() + "/pool_description_file/pool_description.txt"); + getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); + + /* Import Peptide Pool 'Peptides in Pool' file. + ./test_import_files/pool_detail_file/pool_details.txt + */ + clickFolder(FOLDER_NAME); + getDriver().findElement(By.linkText("Import Peptide Pools")).click(); + new Select(getDriver().findElement(By.name("actionType"))).selectByVisibleText("Peptides in Pool"); + + getDriver().findElement(By.name("pFile")).sendKeys(getSampledataPath() + "/pool_detail_file/pool_details.txt"); + getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); + + /* Search for the Peptides belonging to our just-imported pool */ + clickFolder(FOLDER_NAME); + getDriver().findElement(By.linkText("Search for Peptides by Criteria")).click(); + new Select(getDriver().findElement(By.name("queryKey"))).selectByVisibleText("Peptides in a Peptide Pool"); + selectOptionByTextContaining(getDriver().findElement(By.name("queryValue")), "Prac_Pool"); + + getDriver().findElement(By.name("action_type")).click(); + + // Identify the index at which our peptide IDs start. + findPeptideStartIndex(); + // List the peptides in the the 'gagptegprac' Peptide Group. We expect there to be 16 of them. + clickFolder(FOLDER_NAME); + getDriver().findElement(By.linkText("Search for Peptides by Criteria")).click(); + waitForText("Search for Peptides using different criteria : "); + new Select(getDriver().findElement(By.id("queryKey"))).selectByVisibleText("Peptides in a Peptide Group"); + new Select(getDriver().findElement(By.id("queryValue"))).selectByVisibleText("gagptegprac"); + + getDriver().findElement(By.name("action_type")).click(); + + assertTextPresentInThisOrder("There are (16) peptides in the 'gagptegprac' peptide group. "); + // Select a newly uploaded peptide, #3 and edit it to have a storage location of 'Kitchen Sink' + getDriver().findElement(By.linkText(pepString(4))).click(); + // Verify the expected record's content + assertTrue(getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody")).getText().matches("^[\\s\\S]*Peptide Id\\s*" + pepString(4) + "\nPeptide Sequence\\s*REPRGSDIAGTTSTL\nProtein Category\\s*p24\nSequence Length\\s*15\nAAStart\\s*97\nAAEnd\\s*111\nIs Child\\s*false\nIs Parent\\s*false[\\s\\S]*$")); + + getDriver().findElement(By.xpath("//form[@id='peptides']/div/span[2]/a/span")).click(); + getDriver().findElement(By.name("storage_location")).clear(); + getDriver().findElement(By.name("storage_location")).sendKeys("Kitchen Sink"); + clickAndWait(Locator.xpath("//span[text()='Save Changes']")); + + // Assert that the Storage Location now contains "Kitchen Sink" + assertTrue(getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody")).getText().matches("^[\\s\\S]*Peptide Id\\s*" + pepString(4) + "\nPeptide Sequence\\s*REPRGSDIAGTTSTL\nProtein Category\\s*p24\nSequence Length\\s*15\nAAStart\\s*97\nAAEnd\\s*111\nIs Child\\s*false\nIs Parent\\s*false[\\s\\S]*$")); + getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); + + // Search for a single, newly-uploaded peptide and verify it displays as expected. + getDriver().findElement(By.name("peptide_id")).clear(); + getDriver().findElement(By.name("peptide_id")).sendKeys(Integer.toString(peptideStartIndex + 9)); + clickButton("Find"); + // Verify the expected record's content + assertTrue(getDriver().findElement(By.id("peptides")).getText().matches("^[\\s\\S]*Peptide Id\\s*" + pepString(9) + "\nPeptide Sequence\\s*KCGKEGHQMKDCTER\nProtein Category\\s*p2p7p1p6\nSequence Length\\s*15\nAAStart\\s*52\nAAEnd\\s*66\nIs Child\\s*false\nIs Parent\\s*false\nStorage Location\\s*\n[\\s\\S]*$")); + + getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); + + + /* + * + * 'Search for Peptides by Criteria' using 'Peptides in a Peptide Pool' + * using the newly imported Peptide Pool. + * + * Verify that the peptides found match what was expected (by the pool import), and their values include the + * columns imported earlier during the importing of the peptides. + * + */ + clickFolder(FOLDER_NAME); + getDriver().findElement(By.linkText("Search for Peptides by Criteria")).click(); + new Select(getDriver().findElement(By.id("queryKey"))).selectByVisibleText("Peptides in a Peptide Pool"); + + selectOptionByTextContaining(getDriver().findElement(By.name("queryValue")), "Prac_Pool"); + getDriver().findElement(By.name("action_type")).click(); + getDriver().findElement(By.linkText(pepString(4))).click(); + + StringBuffer verificationErrors = new StringBuffer(); + try + { + assertEquals("Peptide Sequence", getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody/tr[2]/td")).getText()); + } + catch (Error e) + { + verificationErrors.append(e.toString()); + } + try + { + assertEquals("REPRGSDIAGTTSTL", getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody/tr[2]/td[2]")).getText()); + } + catch (Error e) + { + verificationErrors.append(e.toString()); + } + try + { + assertEquals("gagptegprac (LAB ID =GAG1-4)", getDriver().findElement(By.cssSelector("#bodypanel > div > table > tbody > tr > td")).getText()); + } + catch (Error e) + { + verificationErrors.append(e.toString()); + } + + } + + @LogMethod + private void cleanupSchema(Connection cn) throws IOException + { + if (cn == null) + { + cn = createDefaultConnection(false); + } + + cleanupTable(cn, "pepdb", "peptide_pool_assignment"); + cleanupTable(cn, "pepdb", "peptide_group_assignment"); + cleanupTable(cn, "pepdb", "peptides"); + cleanupTable(cn, "pepdb", "peptide_pool"); + cleanupTable(cn, "pepdb", "peptide_group"); + + } + + @LogMethod + private void cleanupTable(Connection cn, String schemaName, String tableName) throws IOException + { + log("** Deleting all " + tableName + " in all containers"); + try + { + SelectRowsCommand selectCmd = new SelectRowsCommand(schemaName, tableName); + selectCmd.setMaxRows(-1); + selectCmd.setContainerFilter(ContainerFilter.AllFolders); + selectCmd.setColumns(Arrays.asList("*")); + SelectRowsResponse selectResp = selectCmd.execute(cn, getProjectName()); + if (selectResp.getRowCount().intValue() > 0) + { + DeleteRowsCommand deleteCmd = new DeleteRowsCommand(schemaName, tableName); + deleteCmd.setRows(selectResp.getRows()); + deleteCmd.execute(cn, getProjectName()); + assertEquals("Expected no rows remaining", 0, selectCmd.execute(cn, getProjectName()).getRowCount().intValue()); + } + } + catch (CommandException e) + { + log("** Error during cleanupTable:"); + e.printStackTrace(System.out); + } + } + + // We have to expose our schema as an external schema in order to run the selectRow and deleteRow commands. + void ensureExternalSchema(String containerPath) + { + log("** Ensure ExternalSchema: " + USER_SCHEMA_NAME); + + beginAt("/query/" + containerPath + "/admin.view"); + + if (!isTextPresent("reload")) + { + assertTextPresent("new external schema"); + log("** Creating ExternalSchema: " + USER_SCHEMA_NAME); + clickAndWait(Locator.linkWithText("new external schema")); + checkCheckbox(Locator.name("includeSystem")); + setFormElement(Locator.name("userSchemaName"), USER_SCHEMA_NAME); + setFormElement(Locator.name("sourceSchemaName"), USER_SCHEMA_NAME); + pressEnter(Locator.name("sourceSchemaName")); + checkCheckbox(Locator.name("editable")); + uncheckCheckbox(Locator.name("indexable")); + clickButton("Create"); + } + assertTextPresent(USER_SCHEMA_NAME); + assertTextNotPresent("reload all schemas"); // Present only for external schemas > 1 + } + + // Identify the index at which our peptide IDs start. + private void findPeptideStartIndex() throws IOException + { + Connection cn = createDefaultConnection(false); + try + { + ensureExternalSchema(getProjectName()); + SelectRowsCommand selectCmd = new SelectRowsCommand("pepdb", "peptides"); + selectCmd.setMaxRows(1); + selectCmd.setContainerFilter(ContainerFilter.AllFolders); + selectCmd.setColumns(Arrays.asList("*")); + SelectRowsResponse selectResp = selectCmd.execute(cn, getProjectName()); + + if (selectResp.getRowCount().intValue() > 0) + { + Row convertedRow = new RowMap(selectResp.getRows().get(0)); + peptideStartIndex = ((int) convertedRow.getValue("peptide_id")) - 1; + } + } + catch (CommandException e) + { + log("** Error during findPeptideStartIndex:"); + e.printStackTrace(System.out); + } + } + + // Convert the peptide ID integer into its string representation. + private String pepString(int peptideIdOffset) + { + //return String.format("P%06d", peptideStartIndex + peptideIdOffset); + return String.format("P%d", peptideStartIndex + peptideIdOffset); + } + + protected void assertModuleDeployed(String moduleName) + { + log("Ensuring that that '" + moduleName + "' module is deployed"); + goToAdminConsole(); + assertTextPresent(moduleName); + } + + protected void assertModuleEnabled(String moduleName) + { + log("Ensuring that that '" + moduleName + "' module is enabled"); + goToFolderManagement(); + clickAndWait(Locator.linkWithText("Folder Type")); + assertElementPresent(Locator.xpath("//input[@type='checkbox' and @checked and @title='" + moduleName + "']")); + } + + protected void assertModuleEnabledByDefault(String moduleName) + { + log("Ensuring that that '" + moduleName + "' module is enabled"); + goToFolderManagement(); + clickAndWait(Locator.linkWithText("Folder Type")); + assertElementPresent(Locator.xpath("//input[@type='checkbox' and @checked and @disabled and @title='" + moduleName + "']")); + } + + @Override + protected void doCleanup(boolean afterTest) throws TestTimeoutException + { + Connection cn = WebTestHelper.getRemoteApiConnection(); + try + { + cleanupSchema(cn); + _containerHelper.deleteProject(getProjectName(), afterTest); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + // super method deletes the project. + super.doCleanup(afterTest); + } + + public List getAssociatedModules() + { + return Arrays.asList("pepdb"); + } + + public static String getSampledataPath() + { + File path = new File(TestFileUtils.getLabKeyRoot(), "externalModules/scharp/pepdb/test/sampledata/test_import_files"); + return path.toString(); + } +} From 4bfc693251e34ea26f162c8018b2986d36e44c6a Mon Sep 17 00:00:00 2001 From: Karl Lum Date: Thu, 30 Apr 2020 16:41:09 -0700 Subject: [PATCH 528/587] Create pull_request_template.md --- pull_request_template.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 pull_request_template.md diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 00000000..2a1c8ac4 --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,8 @@ +#### Rationale + + +#### Related Pull Requests +* + +#### Changes +* From 441c9a22fd25a2ee0666f78ab23288436d8f9d33 Mon Sep 17 00:00:00 2001 From: Karl Lum Date: Thu, 30 Apr 2020 16:41:15 -0700 Subject: [PATCH 529/587] Create pull_request_template.md --- pull_request_template.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 pull_request_template.md diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 00000000..2a1c8ac4 --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,8 @@ +#### Rationale + + +#### Related Pull Requests +* + +#### Changes +* From b15412c84dab40d6fde5dbe27088df234cfadba2 Mon Sep 17 00:00:00 2001 From: labkey-matthewb Date: Thu, 14 May 2020 21:13:30 -0700 Subject: [PATCH 530/587] ContainerFilter is now constructed with a container (#69) specify base Container of ContainerFilter at construction time --- src/org/labkey/trialshare/data/StudyBean.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/data/StudyBean.java b/src/org/labkey/trialshare/data/StudyBean.java index 7ecea557..ea1e75ac 100644 --- a/src/org/labkey/trialshare/data/StudyBean.java +++ b/src/org/labkey/trialshare/data/StudyBean.java @@ -303,7 +303,7 @@ public static Collection> getStudyProperties(Container c, Us TableInfo sp = s.getTable("StudyProperties"); if (sp.supportsContainerFilter()) { - ContainerFilter cf = new ContainerFilter.AllInProject(user); + ContainerFilter cf = ContainerFilter.Type.AllInProject.create(s); ((ContainerFilterable) sp).setContainerFilter(cf); } return new TableSelector(sp).getMapCollection(); From ed2fed4fffe81fd2882030898b9d4d604ad4e503 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Tue, 26 May 2020 18:45:53 -0700 Subject: [PATCH 531/587] appendNavTrail() to addNavTrail() migration (#1) --- .../atlas/pepdb/PepDBBaseController.java | 14 +++ .../atlas/pepdb/PepDBContainerListener.java | 3 + .../scharp/atlas/pepdb/PepDBController.java | 110 ++++++++++++------ .../atlas/pepdb/query/PepDBQuerySchema.java | 1 + .../external/scharp/PepDBModuleTest.java | 2 + 5 files changed, 95 insertions(+), 35 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBBaseController.java b/src/org/scharp/atlas/pepdb/PepDBBaseController.java index 7b2b4140..90fab2d6 100644 --- a/src/org/scharp/atlas/pepdb/PepDBBaseController.java +++ b/src/org/scharp/atlas/pepdb/PepDBBaseController.java @@ -428,6 +428,7 @@ public DCpeptideId(ColumnInfo col) super(col); } + @Override public void renderGridCellContents(RenderContext ctx, Writer out) throws IOException { ColumnInfo c = getColumnInfo(); @@ -455,6 +456,7 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep } } + @Override @NotNull public String getFormattedValue(RenderContext ctx) { StringBuilder sb = new StringBuilder("P"); @@ -462,6 +464,7 @@ public String getFormattedValue(RenderContext ctx) { return sb.toString(); } + @Override public Object getDisplayValue(RenderContext ctx) { ColumnInfo c = getColumnInfo(); @@ -483,11 +486,13 @@ public Object getDisplayValue(RenderContext ctx) } } + @Override public Class getValueClass() { return Integer.class; } + @Override public Class getDisplayValueClass() { return String.class; @@ -501,6 +506,7 @@ public DCpeptidePoolId(ColumnInfo col) super(col); } + @Override public void renderGridCellContents(RenderContext ctx, Writer out) throws IOException { ColumnInfo c = getColumnInfo(); @@ -527,6 +533,7 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep } } + @Override @NotNull public String getFormattedValue(RenderContext ctx) { StringBuilder sb = new StringBuilder("PP"); @@ -534,6 +541,7 @@ public String getFormattedValue(RenderContext ctx) { return sb.toString(); } + @Override public Object getDisplayValue(RenderContext ctx) { ColumnInfo c = getColumnInfo(); @@ -555,11 +563,13 @@ public Object getDisplayValue(RenderContext ctx) } } + @Override public Class getValueClass() { return Integer.class; } + @Override public Class getDisplayValueClass() { return String.class; @@ -573,6 +583,7 @@ public DCparentPoolId(ColumnInfo col) super(col); } + @Override public void renderGridCellContents(RenderContext ctx, Writer out) throws IOException { ColumnInfo c = getColumnInfo(); @@ -602,6 +613,7 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep } } + @Override public Object getDisplayValue(RenderContext ctx) { ColumnInfo c = getColumnInfo(); @@ -626,11 +638,13 @@ public Object getDisplayValue(RenderContext ctx) } } + @Override public Class getValueClass() { return Integer.class; } + @Override public Class getDisplayValueClass() { return String.class; diff --git a/src/org/scharp/atlas/pepdb/PepDBContainerListener.java b/src/org/scharp/atlas/pepdb/PepDBContainerListener.java index 07000a24..553fb089 100644 --- a/src/org/scharp/atlas/pepdb/PepDBContainerListener.java +++ b/src/org/scharp/atlas/pepdb/PepDBContainerListener.java @@ -22,10 +22,12 @@ public class PepDBContainerListener implements ContainerListener private static final Logger _log = Logger.getLogger(PepDBContainerListener.class); + @Override public void containerCreated(Container c, User user) { } + @Override public void containerDeleted(Container c, User user) { } @@ -42,6 +44,7 @@ public Collection canMove(Container c, Container newParent, User user) return Collections.emptyList(); } + @Override public void propertyChange(PropertyChangeEvent evt) { } diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 0151a909..90ce2e0e 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -32,9 +32,8 @@ */ public class PepDBController extends PepDBBaseController { - private final static Logger _log = Logger.getLogger(PepDBController.class); - private static DefaultActionResolver _actionResolver = - new DefaultActionResolver(PepDBController.class); + private static final Logger _log = Logger.getLogger(PepDBController.class); + private static final DefaultActionResolver _actionResolver = new DefaultActionResolver(PepDBController.class); private static final String PAGE_INDEX = "/org/scharp/atlas/pepdb/view/index.jsp"; private static final String PAGE_PEPTIDE_GROUP_SELECT = "/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp"; @@ -67,22 +66,24 @@ protected HttpServletResponse getResponse() @RequiresPermission(ReadPermission.class) public class BeginAction extends SimpleViewAction { + @Override public ModelAndView getView(DisplayPeptideForm form, BindException errors) throws Exception { JspView v = new JspView(PAGE_INDEX, form, errors); return v; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Begin", peptideURL("begin")); - return root; } } @RequiresPermission(ReadPermission.class) public class SearchForPeptidesAction extends FormViewAction { + @Override public ModelAndView getView(PeptideQueryForm form, boolean reshow, BindException errors) throws Exception { ViewContext ctx = getViewContext(); @@ -94,6 +95,7 @@ public ModelAndView getView(PeptideQueryForm form, boolean reshow, BindException return v; } + @Override public boolean handlePost(PeptideQueryForm form, BindException errors) throws Exception { String actionType = getRequest().getParameter("action_type"); @@ -103,11 +105,13 @@ public boolean handlePost(PeptideQueryForm form, BindException errors) throws Ex return false; } + @Override public void validateCommand(PeptideQueryForm form, Errors errors) { return; } + @Override public ActionURL getSuccessURL(PeptideQueryForm form) { ActionURL urlTest = new ActionURL(GetPeptidesAction.class, getContainer()); @@ -128,22 +132,22 @@ public ActionURL getSuccessURL(PeptideQueryForm form) return urlTest; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Search For Peptides By Criteria", peptideURL("searchForPeptides")); - return root; } } @RequiresPermission(ReadPermission.class) public class GetPeptidesAction extends SimpleViewAction { + @Override public ModelAndView getView(PeptideQueryForm form, BindException errors) throws Exception { if (!form.validate(errors)) { - JspView v = new JspView(PAGE_PEPTIDE_GROUP_SELECT, form, errors); - return v; + return new JspView<>(PAGE_PEPTIDE_GROUP_SELECT, form, errors); } PropertyValues pv = this.getPropertyValues(); ViewContext ctx = getViewContext(); @@ -181,16 +185,17 @@ public ModelAndView getView(PeptideQueryForm form, BindException errors) throws return gridView; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Get Peptides By Criteria", peptideURL("getPeptides")); - return root; } } @RequiresPermission(ReadPermission.class) public class DisplayPeptideAction extends SimpleViewAction { + @Override public ModelAndView getView(DisplayPeptideForm form, BindException errors) throws Exception { String pepId = form.getPeptide_id(); @@ -242,16 +247,17 @@ public ModelAndView getView(DisplayPeptideForm form, BindException errors) throw return box; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Display Peptide Details", peptideURL("displayPeptide")); - return root; } } @RequiresPermission(UpdatePermission.class) public class EditPeptideAction extends FormViewAction { + @Override public ModelAndView getView(PeptideForm form, boolean reshow, BindException errors) throws Exception { Peptides p = PepDBManager.getPeptideById(form.getBean().getPeptide_id()); @@ -277,6 +283,7 @@ public ModelAndView getView(PeptideForm form, boolean reshow, BindException erro return uView; } + @Override public boolean handlePost(PeptideForm form, BindException errors) throws Exception { Peptides bean = form.getBean(); @@ -287,6 +294,7 @@ public boolean handlePost(PeptideForm form, BindException errors) throws Excepti return true; } + @Override public void validateCommand(PeptideForm form, Errors errors) { Peptides bean = form.getBean(); @@ -296,6 +304,7 @@ public void validateCommand(PeptideForm form, Errors errors) errors.reject(null, "If a peptide is not flagged then Peptide Flag Reason must be blank."); } + @Override public ActionURL getSuccessURL(PeptideForm form) { ActionURL url = new ActionURL(DisplayPeptideAction.class, getContainer()); @@ -303,16 +312,17 @@ public ActionURL getSuccessURL(PeptideForm form) return url; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Edit Peptide", peptideURL("editPeptide")); - return root; } } @RequiresPermission(UpdatePermission.class) public class EditPeptidePoolAction extends FormViewAction { + @Override public ModelAndView getView(PeptidePoolForm form, boolean reshow, BindException errors) throws Exception { PeptidePool p = PepDBManager.getPeptidePoolByID(form.getBean().getPeptide_pool_id()); @@ -330,6 +340,7 @@ public ModelAndView getView(PeptidePoolForm form, boolean reshow, BindException return uView; } + @Override public boolean handlePost(PeptidePoolForm form, BindException errors) throws Exception { PeptidePool bean = form.getBean(); @@ -340,11 +351,13 @@ public boolean handlePost(PeptidePoolForm form, BindException errors) throws Exc return true; } + @Override public void validateCommand(PeptidePoolForm form, Errors errors) { } + @Override public ActionURL getSuccessURL(PeptidePoolForm form) { ActionURL url = new ActionURL(DisplayPeptidePoolInformationAction.class, getContainer()); @@ -352,16 +365,17 @@ public ActionURL getSuccessURL(PeptidePoolForm form) return url; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Edit Peptide Pool", peptideURL("editPeptidePool")); - return root; } } @RequiresPermission(ReadPermission.class) public class ShowAllPeptideGroupsAction extends SimpleViewAction { + @Override public ModelAndView getView(PeptideQueryForm form, BindException errors) throws Exception { TableInfo tableInfo = PepDBSchema.getInstance().getTableInfoPeptideGroups(); @@ -386,16 +400,17 @@ public ModelAndView getView(PeptideQueryForm form, BindException errors) throws return gridView; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Display All Peptide Groups", peptideURL("showAllPeptideGroups")); - return root; } } @RequiresPermission(ReadPermission.class) public class DisplayPeptideGroupInformationAction extends SimpleViewAction { + @Override public ModelAndView getView(PeptideAndGroupForm form, BindException errors) throws Exception { _log.debug("PeptideAndGroupForm: " + form.toString()); @@ -427,16 +442,17 @@ public ModelAndView getView(PeptideAndGroupForm form, BindException errors) thro return vBox; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Display Peptide Group Details", peptideURL("displayPeptideGroupInformation")); - return root; } } @RequiresPermission(ReadPermission.class) public class ShowAllPeptidePoolsAction extends SimpleViewAction { + @Override public ModelAndView getView(PeptideQueryForm form, BindException errors) throws Exception { TableInfo tableInfo = PepDBSchema.getInstance().getTableInfoViewPoolDetails(); @@ -457,16 +473,17 @@ public ModelAndView getView(PeptideQueryForm form, BindException errors) throws return gridView; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Display All Peptide Pools", peptideURL("showAllPeptidePools")); - return root; } } @RequiresPermission(ReadPermission.class) public class DisplayPeptidePoolInformationAction extends SimpleViewAction { + @Override public ModelAndView getView(PeptideAndPoolForm form, BindException errors) throws Exception { _log.debug("PeptideAndPoolForm: " + form.toString()); @@ -505,16 +522,17 @@ public ModelAndView getView(PeptideAndPoolForm form, BindException errors) throw } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Display Peptide Pool Details", peptideURL("displayPeptidePoolInformation")); - return root; } } @RequiresPermission(UpdatePermission.class) public class UpdatePeptideGroupAction extends FormViewAction { + @Override public ModelAndView getView(PeptideGroupForm form, boolean reshow, BindException errors) throws Exception { PeptideGroup pg = PepDBManager.getPeptideGroupByID(form.getBean().getPeptide_group_id()); @@ -534,6 +552,7 @@ public ModelAndView getView(PeptideGroupForm form, boolean reshow, BindException return uView; } + @Override public boolean handlePost(PeptideGroupForm form, BindException errors) throws Exception { PeptideGroup bean = form.getBean(); @@ -544,6 +563,7 @@ public boolean handlePost(PeptideGroupForm form, BindException errors) throws Ex return true; } + @Override public void validateCommand(PeptideGroupForm form, Errors errors) { try @@ -557,6 +577,7 @@ public void validateCommand(PeptideGroupForm form, Errors errors) } } + @Override public ActionURL getSuccessURL(PeptideGroupForm form) { ActionURL url = new ActionURL(DisplayPeptideGroupInformationAction.class, getContainer()); @@ -564,16 +585,17 @@ public ActionURL getSuccessURL(PeptideGroupForm form) return url; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Update Peptide Group", peptideURL("updatePeptideGroup")); - return root; } } @RequiresPermission(UpdatePermission.class) public class InsertPeptideGroupAction extends FormViewAction { + @Override public ModelAndView getView(PeptideGroupForm form, boolean reshow, BindException errors) throws Exception { ButtonBar bb = new ButtonBar(); @@ -593,6 +615,7 @@ public ModelAndView getView(PeptideGroupForm form, boolean reshow, BindException return iView; } + @Override public boolean handlePost(PeptideGroupForm form, BindException errors) throws Exception { PeptideGroup group = form.getBean(); @@ -602,6 +625,7 @@ public boolean handlePost(PeptideGroupForm form, BindException errors) throws Ex return true; } + @Override public void validateCommand(PeptideGroupForm form, Errors errors) { try @@ -615,6 +639,7 @@ public void validateCommand(PeptideGroupForm form, Errors errors) } } + @Override public ActionURL getSuccessURL(PeptideGroupForm form) { ActionURL url = new ActionURL(DisplayPeptideGroupInformationAction.class, getContainer()); @@ -622,10 +647,10 @@ public ActionURL getSuccessURL(PeptideGroupForm form) return url; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Insert Peptide Group", peptideURL("insertPeptideGroup")); - return root; } } @@ -634,12 +659,14 @@ public class ImportPeptidesAction extends FormViewAction { private List resultPeptides = new LinkedList(); + @Override public ModelAndView getView(FileForm form, boolean reshow, BindException errors) throws Exception { JspView v = new JspView(PAGE_IMPORT_PEPTIDES, form, errors); return v; } + @Override public boolean handlePost(FileForm form, BindException errors) throws Exception { try @@ -671,11 +698,13 @@ public boolean handlePost(FileForm form, BindException errors) throws Exception return true; } + @Override public void validateCommand(FileForm form, Errors errors) { } + @Override public ActionURL getSuccessURL(FileForm form) { ActionURL url = new ActionURL(DisplayResultAction.class, getContainer()); @@ -683,16 +712,17 @@ public ActionURL getSuccessURL(FileForm form) return url; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Import Peptides", peptideURL("importPeptides")); - return root; } } @RequiresPermission(UpdatePermission.class) public class DisplayResultAction extends SimpleViewAction { + @Override public ModelAndView getView(FileForm form, BindException errors) throws Exception { PeptideQueryForm form1 = new PeptideQueryForm(); @@ -701,10 +731,10 @@ public ModelAndView getView(FileForm form, BindException errors) throws Exceptio return v; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Display Results Page", peptideURL("displayResult")); - return root; } } @@ -713,12 +743,14 @@ public class ImportPeptidePoolsAction extends FormViewAction { ActionURL url = null; + @Override public ModelAndView getView(FileForm form, boolean reshow, BindException errors) throws Exception { JspView v = new JspView("/org/scharp/atlas/pepdb/view/importPools.jsp", form, errors); return v; } + @Override public boolean handlePost(FileForm form, BindException errors) throws Exception { try @@ -748,11 +780,13 @@ public boolean handlePost(FileForm form, BindException errors) throws Exception } } + @Override public void validateCommand(FileForm form, Errors errors) { } + @Override public ActionURL getSuccessURL(FileForm form) { ActionURL url = new ActionURL(ImportPeptidePoolsAction.class, getContainer()); @@ -760,10 +794,10 @@ public ActionURL getSuccessURL(FileForm form) return url; } - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { root.addChild("Import Peptide Pools", peptideURL("importPeptidePools")); - return root; } } @@ -807,6 +841,7 @@ public void printExcel(Object bean, HttpServletResponse response, BindException @RequiresPermission(ReadPermission.class) public class PeptidesInPoolExcelExportAction extends PeptideExcelExportAction { + @Override public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception { PeptideQueryForm form = new PeptideQueryForm(); @@ -820,6 +855,7 @@ public void export(Object bean, HttpServletResponse response, BindException erro @RequiresPermission(ReadPermission.class) public class PoolsInPoolExcelExportAction extends PeptideExcelExportAction { + @Override public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception { PeptideQueryForm form = new PeptideQueryForm(); @@ -833,6 +869,7 @@ public void export(Object bean, HttpServletResponse response, BindException erro @RequiresPermission(ReadPermission.class) public class PeptideDefaultExcelExportAction extends PeptideExcelExportAction { + @Override public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception { ViewContext ctx = getViewContext(); @@ -874,6 +911,7 @@ public void printText(Object bean, HttpServletResponse response, BindException e @RequiresPermission(ReadPermission.class) public class PeptidesInPoolTextExportAction extends PeptideTextExportAction { + @Override public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception { PeptideQueryForm form = new PeptideQueryForm(); @@ -887,6 +925,7 @@ public void export(Object bean, HttpServletResponse response, BindException erro @RequiresPermission(ReadPermission.class) public class PoolsInPoolTextExportAction extends PeptideTextExportAction { + @Override public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception { PeptideQueryForm form = new PeptideQueryForm(); @@ -900,6 +939,7 @@ public void export(Object bean, HttpServletResponse response, BindException erro @RequiresPermission(ReadPermission.class) public class PeptideDefaultTextExportAction extends PeptideTextExportAction { + @Override public void export(Object bean, HttpServletResponse response, BindException errors) throws Exception { ViewContext ctx = getViewContext(); diff --git a/src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java b/src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java index c8509feb..06e03b2a 100644 --- a/src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java +++ b/src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java @@ -27,6 +27,7 @@ static public void register(Module module) { DefaultSchema.registerProvider(SCHEMA_NAME, new DefaultSchema.SchemaProvider(module) { + @Override public QuerySchema createSchema(DefaultSchema schema, Module module) { return new PepDBQuerySchema(schema.getUser(), schema.getContainer()); diff --git a/test/src/org/labkey/test/tests/external/scharp/PepDBModuleTest.java b/test/src/org/labkey/test/tests/external/scharp/PepDBModuleTest.java index 10f17a9f..96c5a2ed 100644 --- a/test/src/org/labkey/test/tests/external/scharp/PepDBModuleTest.java +++ b/test/src/org/labkey/test/tests/external/scharp/PepDBModuleTest.java @@ -56,6 +56,7 @@ public class PepDBModuleTest extends BaseWebDriverTest implements PostgresOnlyTe public static final String FOLDER_NAME = "PepDB"; private int peptideStartIndex = 0; + @Override protected String getProjectName() { return getClass().getSimpleName() + " Project"; @@ -370,6 +371,7 @@ protected void doCleanup(boolean afterTest) throws TestTimeoutException super.doCleanup(afterTest); } + @Override public List getAssociatedModules() { return Arrays.asList("pepdb"); From 8be0b009a726d294ed082b77b63b613a5a7835e0 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Wed, 27 May 2020 06:33:33 -0700 Subject: [PATCH 532/587] appendNavTrail() to addNavTrail() migration (#70) --- .../labkey/trialshare/StudyITNFolderType.java | 2 ++ .../trialshare/TrialShareController.java | 30 +++++++++++-------- .../query/ManagePublicationsQueryView.java | 1 + .../query/ManageStudiesQueryView.java | 1 + .../tests/trialshare/ManageStudiesTest.java | 1 + .../trialshare/TrialShareDataFinderTest.java | 1 + 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/org/labkey/trialshare/StudyITNFolderType.java b/src/org/labkey/trialshare/StudyITNFolderType.java index 96d285bb..08607fa8 100644 --- a/src/org/labkey/trialshare/StudyITNFolderType.java +++ b/src/org/labkey/trialshare/StudyITNFolderType.java @@ -62,6 +62,7 @@ public Set getLegacyNames() return Collections.singleton("Study Redesign (ITN)"); } + @Override protected String getFolderTitle(ViewContext ctx) { Study study = StudyService.get().getStudy(ctx.getContainer()); @@ -75,6 +76,7 @@ public String getStartPageLabel(ViewContext ctx) return study == null ? "New Study" : study.getLabel(); } + @Override public List getDefaultTabs() { return PAGES; diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 7f6fb482..bd57ca39 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -196,9 +196,9 @@ public TrialShareController() @RequiresPermission(ReadPermission.class) public class BeginAction extends SimpleViewAction { - public NavTree appendNavTrail(NavTree root) + @Override + public void addNavTrail(NavTree root) { - return root; } @Override @@ -564,6 +564,7 @@ public class TrialShareExportAction extends MutatingApiAction Date: Thu, 25 Jun 2020 10:19:44 -0700 Subject: [PATCH 533/587] Add release workflow definitions (#2) --- .github/workflows/branch_release.yml | 23 +++++++++++++++++++++++ .github/workflows/merge_release.yml | 28 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 .github/workflows/branch_release.yml create mode 100644 .github/workflows/merge_release.yml diff --git a/.github/workflows/branch_release.yml b/.github/workflows/branch_release.yml new file mode 100644 index 00000000..286510e3 --- /dev/null +++ b/.github/workflows/branch_release.yml @@ -0,0 +1,23 @@ +--- +# Workflow to automate creation and updating of release branches +name: Release Branching + +# Trigger on tags created by TeamCity +on: + push: + tags: + - '*' + +jobs: + branch_release: + if: github.event.created && github.event.sender.login == 'labkey-teamcity' + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Create branches and PRs + uses: LabKey/gitHubActions/branch-release@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge_release.yml b/.github/workflows/merge_release.yml new file mode 100644 index 00000000..559f6ef8 --- /dev/null +++ b/.github/workflows/merge_release.yml @@ -0,0 +1,28 @@ +--- +# Workflow to automate merging release branches +name: Release Merging + +# Trigger on PR approval +on: + pull_request_review: + types: + - submitted + +jobs: + merge_release: + if: > + github.event.review.state == 'approved' && + github.event.pull_request.user.login == 'github-actions[bot]' + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Merge PR + uses: LabKey/gitHubActions/merge-release@master + with: + target_branch: ${{ github.event.pull_request.base.ref }} + merge_branch: ${{ github.event.pull_request.head.ref }} + pr_number: ${{ github.event.pull_request.number }} + github_token: ${{ secrets.GITHUB_TOKEN }} From 46e2c5d137bd0d8b8f80e0003fe477ea808116ac Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Thu, 25 Jun 2020 10:20:42 -0700 Subject: [PATCH 534/587] Add release workflow definitions (#71) --- .github/workflows/branch_release.yml | 23 +++++++++++++++++++++++ .github/workflows/merge_release.yml | 28 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 .github/workflows/branch_release.yml create mode 100644 .github/workflows/merge_release.yml diff --git a/.github/workflows/branch_release.yml b/.github/workflows/branch_release.yml new file mode 100644 index 00000000..286510e3 --- /dev/null +++ b/.github/workflows/branch_release.yml @@ -0,0 +1,23 @@ +--- +# Workflow to automate creation and updating of release branches +name: Release Branching + +# Trigger on tags created by TeamCity +on: + push: + tags: + - '*' + +jobs: + branch_release: + if: github.event.created && github.event.sender.login == 'labkey-teamcity' + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Create branches and PRs + uses: LabKey/gitHubActions/branch-release@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge_release.yml b/.github/workflows/merge_release.yml new file mode 100644 index 00000000..559f6ef8 --- /dev/null +++ b/.github/workflows/merge_release.yml @@ -0,0 +1,28 @@ +--- +# Workflow to automate merging release branches +name: Release Merging + +# Trigger on PR approval +on: + pull_request_review: + types: + - submitted + +jobs: + merge_release: + if: > + github.event.review.state == 'approved' && + github.event.pull_request.user.login == 'github-actions[bot]' + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Merge PR + uses: LabKey/gitHubActions/merge-release@master + with: + target_branch: ${{ github.event.pull_request.base.ref }} + merge_branch: ${{ github.event.pull_request.head.ref }} + pr_number: ${{ github.event.pull_request.number }} + github_token: ${{ secrets.GITHUB_TOKEN }} From 6d7b19545cb9e26e21b0db91e36ca389ba046bf0 Mon Sep 17 00:00:00 2001 From: Ankur Juneja Date: Wed, 5 Aug 2020 13:13:59 -0700 Subject: [PATCH 535/587] Item 7490: upgrade log4j (#3) --- .../atlas/pepdb/PepDBBaseController.java | 28 +++++---- .../atlas/pepdb/PepDBContainerListener.java | 7 ++- .../scharp/atlas/pepdb/PepDBController.java | 57 ++++++++++++++----- src/org/scharp/atlas/pepdb/PepDBManager.java | 5 +- src/org/scharp/atlas/pepdb/PepDBModule.java | 5 +- .../scharp/atlas/pepdb/PeptideImporter.java | 17 ++++-- src/org/scharp/atlas/pepdb/PoolImporter.java | 20 ++++--- .../scharp/atlas/pepdb/model/PeptidePool.java | 5 +- .../atlas/pepdb/query/PepDBQuerySchema.java | 4 -- .../scharp/atlas/pepdb/view/PepDBWebPart.java | 9 +-- .../scharp/atlas/pepdb/view/pepDBWebPart.jsp | 2 +- .../atlas/pepdb/view/peptideDetails.jsp | 6 +- .../atlas/pepdb/view/peptideGroupSelect.jsp | 7 +-- 13 files changed, 109 insertions(+), 63 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBBaseController.java b/src/org/scharp/atlas/pepdb/PepDBBaseController.java index 90fab2d6..c43f2a34 100644 --- a/src/org/scharp/atlas/pepdb/PepDBBaseController.java +++ b/src/org/scharp/atlas/pepdb/PepDBBaseController.java @@ -1,23 +1,31 @@ package org.scharp.atlas.pepdb; +import org.apache.commons.beanutils.ConversionException; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.labkey.api.action.SpringActionController; -import org.labkey.api.data.*; -import org.labkey.api.util.DateUtil; import org.labkey.api.attachments.AttachmentFile; -import org.apache.log4j.Logger; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.beanutils.ConversionException; -import org.springframework.validation.Errors; +import org.labkey.api.data.BeanViewForm; +import org.labkey.api.data.ColumnInfo; +import org.labkey.api.data.DataColumn; +import org.labkey.api.data.RenderContext; +import org.labkey.api.data.SimpleFilter; +import org.labkey.api.data.Sort; +import org.labkey.api.data.TableInfo; +import org.labkey.api.util.DateUtil; import org.scharp.atlas.pepdb.model.PeptideGroup; +import org.scharp.atlas.pepdb.model.PeptidePool; import org.scharp.atlas.pepdb.model.Peptides; import org.scharp.atlas.pepdb.model.ProteinCategory; -import org.scharp.atlas.pepdb.model.PeptidePool; +import org.springframework.validation.Errors; -import java.util.*; -import java.io.Writer; import java.io.IOException; +import java.io.Writer; import java.sql.SQLException; +import java.util.List; +import java.util.Map; /** * Created by IntelliJ IDEA. @@ -28,7 +36,7 @@ */ public class PepDBBaseController extends SpringActionController { - private final static Logger _log = Logger.getLogger(PepDBBaseController.class); + private final static Logger _log = LogManager.getLogger(PepDBBaseController.class); private static final String VIEW_DISPLAY_PEPTIDE = "displayPeptide.view"; protected static final String QRY_STRING_PEPTIDE_ID = "peptideId"; diff --git a/src/org/scharp/atlas/pepdb/PepDBContainerListener.java b/src/org/scharp/atlas/pepdb/PepDBContainerListener.java index 553fb089..88027139 100644 --- a/src/org/scharp/atlas/pepdb/PepDBContainerListener.java +++ b/src/org/scharp/atlas/pepdb/PepDBContainerListener.java @@ -1,10 +1,11 @@ package org.scharp.atlas.pepdb; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; -import org.labkey.api.data.ContainerManager.ContainerListener; import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerManager.ContainerListener; import org.labkey.api.security.User; -import org.apache.log4j.Logger; import java.beans.PropertyChangeEvent; import java.util.Collection; @@ -20,7 +21,7 @@ public class PepDBContainerListener implements ContainerListener { - private static final Logger _log = Logger.getLogger(PepDBContainerListener.class); + private static final Logger _log = LogManager.getLogger(PepDBContainerListener.class); @Override public void containerCreated(Container c, User user) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 90ce2e0e..10d2e246 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -1,29 +1,58 @@ package org.scharp.atlas.pepdb; -import org.labkey.api.action.*; -import org.labkey.api.security.RequiresPermission; -import org.labkey.api.view.*; -import org.labkey.api.data.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.labkey.api.action.ExportAction; +import org.labkey.api.action.FormViewAction; +import org.labkey.api.action.SimpleViewAction; +import org.labkey.api.attachments.AttachmentFile; +import org.labkey.api.data.ActionButton; +import org.labkey.api.data.Aggregate; +import org.labkey.api.data.ButtonBar; +import org.labkey.api.data.ColumnInfo; +import org.labkey.api.data.CompareType; +import org.labkey.api.data.Container; +import org.labkey.api.data.DataRegion; +import org.labkey.api.data.DisplayColumn; +import org.labkey.api.data.ExcelWriter; +import org.labkey.api.data.RenderContext; +import org.labkey.api.data.SimpleFilter; +import org.labkey.api.data.Sort; +import org.labkey.api.data.TSVGridWriter; +import org.labkey.api.data.Table; +import org.labkey.api.data.TableInfo; import org.labkey.api.query.FieldKey; +import org.labkey.api.query.QuerySettings; +import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.security.permissions.UpdatePermission; -import org.labkey.api.attachments.AttachmentFile; -import org.labkey.api.query.QuerySettings; -import org.apache.log4j.Logger; -import org.springframework.web.servlet.ModelAndView; +import org.labkey.api.view.ActionURL; +import org.labkey.api.view.DetailsView; +import org.labkey.api.view.GridView; +import org.labkey.api.view.HttpView; +import org.labkey.api.view.InsertView; +import org.labkey.api.view.JspView; +import org.labkey.api.view.NavTree; +import org.labkey.api.view.UpdateView; +import org.labkey.api.view.VBox; +import org.labkey.api.view.ViewContext; +import org.scharp.atlas.pepdb.model.PeptideGroup; +import org.scharp.atlas.pepdb.model.PeptidePool; +import org.scharp.atlas.pepdb.model.Peptides; +import org.scharp.atlas.pepdb.model.ProteinCategory; +import org.springframework.beans.PropertyValues; import org.springframework.validation.BindException; import org.springframework.validation.Errors; -import org.springframework.beans.PropertyValues; -import org.scharp.atlas.pepdb.model.*; +import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import java.util.List; +import java.io.IOException; +import java.sql.SQLException; import java.util.ArrayList; import java.util.LinkedList; -import java.sql.SQLException; -import java.io.IOException; +import java.util.List; /** * User: sravani @@ -32,7 +61,7 @@ */ public class PepDBController extends PepDBBaseController { - private static final Logger _log = Logger.getLogger(PepDBController.class); + private static final Logger _log = LogManager.getLogger(PepDBController.class); private static final DefaultActionResolver _actionResolver = new DefaultActionResolver(PepDBController.class); private static final String PAGE_INDEX = "/org/scharp/atlas/pepdb/view/index.jsp"; diff --git a/src/org/scharp/atlas/pepdb/PepDBManager.java b/src/org/scharp/atlas/pepdb/PepDBManager.java index 70766686..251e17aa 100644 --- a/src/org/scharp/atlas/pepdb/PepDBManager.java +++ b/src/org/scharp/atlas/pepdb/PepDBManager.java @@ -1,6 +1,7 @@ package org.scharp.atlas.pepdb; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.labkey.api.data.Container; import org.labkey.api.data.SQLFragment; import org.labkey.api.data.SimpleFilter; @@ -33,7 +34,7 @@ public class PepDBManager { - private static Logger log = Logger.getLogger(PepDBManager.class); + private static Logger log = LogManager.getLogger(PepDBManager.class); private static PepDBSchema schema = PepDBSchema.getInstance(); /** * Static class diff --git a/src/org/scharp/atlas/pepdb/PepDBModule.java b/src/org/scharp/atlas/pepdb/PepDBModule.java index 729db7ae..6296370d 100644 --- a/src/org/scharp/atlas/pepdb/PepDBModule.java +++ b/src/org/scharp/atlas/pepdb/PepDBModule.java @@ -1,6 +1,7 @@ package org.scharp.atlas.pepdb; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; @@ -24,7 +25,7 @@ public class PepDBModule extends DefaultModule { - private static final Logger _log = Logger.getLogger(DefaultModule.class); + private static final Logger _log = LogManager.getLogger(DefaultModule.class); public static final String NAME = "PepDB"; @Override diff --git a/src/org/scharp/atlas/pepdb/PeptideImporter.java b/src/org/scharp/atlas/pepdb/PeptideImporter.java index 07ae3f97..6e38e844 100644 --- a/src/org/scharp/atlas/pepdb/PeptideImporter.java +++ b/src/org/scharp/atlas/pepdb/PeptideImporter.java @@ -1,16 +1,21 @@ package org.scharp.atlas.pepdb; -import org.scharp.atlas.pepdb.model.*; -import org.labkey.api.security.User; import org.labkey.api.attachments.AttachmentFile; +import org.labkey.api.security.User; +import org.scharp.atlas.pepdb.model.OptimalEpitopeList; +import org.scharp.atlas.pepdb.model.Parent; +import org.scharp.atlas.pepdb.model.PeptideGroup; +import org.scharp.atlas.pepdb.model.Peptides; +import org.scharp.atlas.pepdb.model.ProteinCategory; +import org.scharp.atlas.pepdb.model.Source; import org.springframework.validation.Errors; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.ArrayList; -import java.sql.SQLException; -import java.io.InputStreamReader; -import java.io.BufferedReader; /** * Created by IntelliJ IDEA. diff --git a/src/org/scharp/atlas/pepdb/PoolImporter.java b/src/org/scharp/atlas/pepdb/PoolImporter.java index f9a68b5e..21ec2fa0 100644 --- a/src/org/scharp/atlas/pepdb/PoolImporter.java +++ b/src/org/scharp/atlas/pepdb/PoolImporter.java @@ -1,18 +1,22 @@ package org.scharp.atlas.pepdb; -import org.labkey.api.security.User; import org.labkey.api.attachments.AttachmentFile; -import org.scharp.atlas.pepdb.PepDBBaseController.*; -import org.scharp.atlas.pepdb.model.*; +import org.labkey.api.security.User; +import org.scharp.atlas.pepdb.PepDBBaseController.FileForm; +import org.scharp.atlas.pepdb.model.PeptideGroup; +import org.scharp.atlas.pepdb.model.PeptidePool; +import org.scharp.atlas.pepdb.model.PeptidePoolAssignment; +import org.scharp.atlas.pepdb.model.Peptides; +import org.scharp.atlas.pepdb.model.PoolType; +import org.scharp.atlas.pepdb.model.Source; import org.springframework.validation.Errors; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.sql.SQLException; import java.util.ArrayList; -import java.util.HashMap; import java.util.Arrays; -import java.sql.SQLException; -import java.sql.Array; -import java.io.InputStreamReader; -import java.io.BufferedReader; +import java.util.HashMap; /** * Created by IntelliJ IDEA. diff --git a/src/org/scharp/atlas/pepdb/model/PeptidePool.java b/src/org/scharp/atlas/pepdb/model/PeptidePool.java index 9fe8fa11..6d8fe143 100644 --- a/src/org/scharp/atlas/pepdb/model/PeptidePool.java +++ b/src/org/scharp/atlas/pepdb/model/PeptidePool.java @@ -1,7 +1,8 @@ package org.scharp.atlas.pepdb.model; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.labkey.api.data.Entity; -import org.apache.log4j.Logger; /** * Created by IntelliJ IDEA. @@ -21,7 +22,7 @@ public class PeptidePool extends Entity private Integer parent_pool_id; private String parent_pool_name; private String matrix_peptide_pool_id; - private static Logger log = Logger.getLogger(PeptidePool.class); + private static Logger log = LogManager.getLogger(PeptidePool.class); public PeptidePool() { diff --git a/src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java b/src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java index 06e03b2a..44bf18d3 100644 --- a/src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java +++ b/src/org/scharp/atlas/pepdb/query/PepDBQuerySchema.java @@ -1,17 +1,13 @@ package org.scharp.atlas.pepdb.query; import org.jetbrains.annotations.NotNull; -import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerFilter; -import org.labkey.api.data.DatabaseTableType; import org.labkey.api.data.DbSchema; import org.labkey.api.data.TableInfo; import org.labkey.api.module.Module; import org.labkey.api.query.DefaultSchema; import org.labkey.api.query.QuerySchema; -import org.labkey.api.query.QueryUpdateService; -import org.labkey.api.query.SimpleQueryUpdateService; import org.labkey.api.query.SimpleUserSchema; import org.labkey.api.security.User; diff --git a/src/org/scharp/atlas/pepdb/view/PepDBWebPart.java b/src/org/scharp/atlas/pepdb/view/PepDBWebPart.java index 6196b093..ec1dfce3 100644 --- a/src/org/scharp/atlas/pepdb/view/PepDBWebPart.java +++ b/src/org/scharp/atlas/pepdb/view/PepDBWebPart.java @@ -1,16 +1,17 @@ package org.scharp.atlas.pepdb.view; -import javax.servlet.ServletException; - -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.labkey.api.view.JspView; +import javax.servlet.ServletException; + /** * @version $Id$ */ public class PepDBWebPart extends JspView { - private static Logger _log = Logger.getLogger(PepDBWebPart.class); + private static Logger _log = LogManager.getLogger(PepDBWebPart.class); /** * diff --git a/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp b/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp index c62ebdbb..6751e918 100644 --- a/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp +++ b/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp @@ -1,6 +1,6 @@ +<%@ page import="org.labkey.api.view.ActionURL"%> <%@ page import="org.labkey.api.view.HttpView"%> <%@ page import="org.labkey.api.view.ViewContext"%> -<%@ page import="org.labkey.api.view.ActionURL"%> <%@ page import="org.scharp.atlas.pepdb.model.PeptideGroup" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <% diff --git a/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp b/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp index ddda419e..f1dc3f84 100644 --- a/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp +++ b/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp @@ -1,12 +1,12 @@ <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%@ page import="org.labkey.api.view.HttpView"%> -<%@ page import="org.scharp.atlas.pepdb.PepDBManager" %> -<%@ page import="org.scharp.atlas.pepdb.PepDBBaseController.*" %> <%@ page import="org.labkey.api.view.JspView" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBBaseController.PeptideQueryForm" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBManager" %> <%@ page import="org.scharp.atlas.pepdb.model.PeptidePool" %> <%@ page import="org.scharp.atlas.pepdb.model.Source" %> -<%@ page import="java.util.List" %> <%@ page import="java.util.Arrays" %> +<%@ page import="java.util.List" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <% JspView me = (JspView) HttpView.currentView(); diff --git a/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp index b0034730..bb9849a6 100644 --- a/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp +++ b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp @@ -1,11 +1,10 @@ <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%@ page import="org.labkey.api.view.HttpView"%> +<%@ page import="org.labkey.api.view.JspView" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBBaseController.PeptideQueryForm" %> <%@ page import="org.scharp.atlas.pepdb.PepDBManager" %> -<%@ page import="org.scharp.atlas.pepdb.model.PeptideGroup" %> -<%@ page import="org.scharp.atlas.pepdb.PepDBBaseController.*" %> -<%@ page import="java.sql.SQLException" %> <%@ page import="org.scharp.atlas.pepdb.PepDBSchema" %> -<%@ page import="org.labkey.api.view.JspView" %> +<%@ page import="org.scharp.atlas.pepdb.model.PeptideGroup" %> <%@ page import="org.scharp.atlas.pepdb.model.PeptidePool" %> <%@ page import="org.scharp.atlas.pepdb.model.ProteinCategory" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> From 67e5fef3e679f26fe15d00f651b1bd697b5c5c4e Mon Sep 17 00:00:00 2001 From: Ankur Juneja Date: Wed, 5 Aug 2020 13:24:23 -0700 Subject: [PATCH 536/587] Item 7490: upgrade log4j (#72) --- src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java | 5 +++-- src/org/labkey/trialshare/PublicationDocumentProvider.java | 5 +++-- src/org/labkey/trialshare/StudyDocumentProvider.java | 5 +++-- src/org/labkey/trialshare/TrialShareController.java | 4 ++-- src/org/labkey/trialshare/TrialShareManager.java | 5 +++-- src/org/labkey/trialshare/data/FacetFilter.java | 3 --- src/org/labkey/trialshare/data/StudyFacetMember.java | 1 - .../labkey/trialshare/query/ManageCubeObjectQueryView.java | 1 - 8 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java b/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java index d057e89e..f2d4edf1 100644 --- a/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java +++ b/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java @@ -1,6 +1,7 @@ package org.labkey.trialshare; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; import org.labkey.api.security.Group; @@ -16,7 +17,7 @@ public class ITNSpecimenRequestCustomizer implements SpecimenService.SpecimenRequestCustomizer { - private static final Logger LOG = Logger.getLogger(ITNSpecimenRequestCustomizer.class); + private static final Logger LOG = LogManager.getLogger(ITNSpecimenRequestCustomizer.class); @Override public boolean allowEmptyRequests() diff --git a/src/org/labkey/trialshare/PublicationDocumentProvider.java b/src/org/labkey/trialshare/PublicationDocumentProvider.java index a76cd19b..cc46e996 100644 --- a/src/org/labkey/trialshare/PublicationDocumentProvider.java +++ b/src/org/labkey/trialshare/PublicationDocumentProvider.java @@ -15,7 +15,8 @@ */ package org.labkey.trialshare; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; @@ -39,7 +40,7 @@ public class PublicationDocumentProvider implements SearchService.DocumentProvider { - private static final Logger _logger = Logger.getLogger(PublicationDocumentProvider.class); + private static final Logger _logger = LogManager.getLogger(PublicationDocumentProvider.class); public static Container getDocumentContainer() { diff --git a/src/org/labkey/trialshare/StudyDocumentProvider.java b/src/org/labkey/trialshare/StudyDocumentProvider.java index c962f244..a799f247 100644 --- a/src/org/labkey/trialshare/StudyDocumentProvider.java +++ b/src/org/labkey/trialshare/StudyDocumentProvider.java @@ -15,7 +15,8 @@ */ package org.labkey.trialshare; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.labkey.api.data.Container; import org.labkey.api.query.QuerySchema; @@ -36,7 +37,7 @@ public class StudyDocumentProvider implements SearchService.DocumentProvider { - private static final Logger _logger = Logger.getLogger(StudyDocumentProvider.class); + private static final Logger _logger = LogManager.getLogger(StudyDocumentProvider.class); public static Container getDocumentContainer() { return TrialShareManager.get().getCubeContainer(null); diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index bd57ca39..8c3479d8 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.ObjectReader; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.Assert; @@ -594,7 +594,7 @@ public ApiResponse execute(TrialShareExportForm form, BindException errors) thro // todo: super important boolean false in 17.2 replaced by PHI enum. what is new correct setting? PHI.Limited FolderExportContext ctx = new FolderExportContext(getUser(), container, types.stream().map(FolderDataTypes::getDescription).collect(Collectors.toSet()), "new", false, PHI.NotPHI, false, - false, false, new StaticLoggerGetter(Logger.getLogger(FolderWriterImpl.class))); + false, false, new StaticLoggerGetter(LogManager.getLogger(FolderWriterImpl.class))); PipeRoot root = PipelineService.get().findPipelineRoot(container); if (root == null || !root.isValid()) diff --git a/src/org/labkey/trialshare/TrialShareManager.java b/src/org/labkey/trialshare/TrialShareManager.java index 9d88ccaa..b2424541 100644 --- a/src/org/labkey/trialshare/TrialShareManager.java +++ b/src/org/labkey/trialshare/TrialShareManager.java @@ -17,7 +17,8 @@ package org.labkey.trialshare; import org.apache.commons.lang3.BooleanUtils; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.api.collections.CaseInsensitiveHashMap; @@ -58,7 +59,7 @@ public class TrialShareManager { - private static final Logger _logger = Logger.getLogger(TrialShareManager.class); + private static final Logger _logger = LogManager.getLogger(TrialShareManager.class); private static final TrialShareManager _instance = new TrialShareManager(); private TrialShareManager() diff --git a/src/org/labkey/trialshare/data/FacetFilter.java b/src/org/labkey/trialshare/data/FacetFilter.java index d32c6e04..8a185dde 100644 --- a/src/org/labkey/trialshare/data/FacetFilter.java +++ b/src/org/labkey/trialshare/data/FacetFilter.java @@ -15,9 +15,6 @@ */ package org.labkey.trialshare.data; -import org.labkey.api.action.Marshal; -import org.labkey.api.action.Marshaller; - /** * Created by susanh on 12/14/15. */ diff --git a/src/org/labkey/trialshare/data/StudyFacetMember.java b/src/org/labkey/trialshare/data/StudyFacetMember.java index 47f52197..8191f4a3 100644 --- a/src/org/labkey/trialshare/data/StudyFacetMember.java +++ b/src/org/labkey/trialshare/data/StudyFacetMember.java @@ -16,7 +16,6 @@ package org.labkey.trialshare.data; import java.util.List; -import java.util.Map; /** * Created by susanh on 12/8/15. diff --git a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java index f0a46e5a..376eaba6 100644 --- a/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java +++ b/src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java @@ -18,7 +18,6 @@ import org.labkey.api.data.ActionButton; import org.labkey.api.data.ButtonBar; import org.labkey.api.data.Container; -import org.labkey.api.data.DataRegion; import org.labkey.api.data.TableInfo; import org.labkey.api.query.DetailsURL; import org.labkey.api.query.QueryAction; From a6cd41ef47270bfffa37d42fc0674a1df6bf7c92 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 24 Aug 2020 16:12:18 -0700 Subject: [PATCH 537/587] Eliminate all remaining out.print(String) uses in pepdb JSPs (#4) --- src/org/scharp/atlas/pepdb/PepDBSchema.java | 4 +-- .../atlas/pepdb/view/importPeptides.jsp | 6 ++-- .../scharp/atlas/pepdb/view/importPools.jsp | 12 +++---- src/org/scharp/atlas/pepdb/view/index.jsp | 2 +- .../atlas/pepdb/view/peptideDetails.jsp | 18 +++++------ .../atlas/pepdb/view/peptideGroupSelect.jsp | 32 +++++++++---------- 6 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBSchema.java b/src/org/scharp/atlas/pepdb/PepDBSchema.java index 54907aba..c7ef2d9e 100644 --- a/src/org/scharp/atlas/pepdb/PepDBSchema.java +++ b/src/org/scharp/atlas/pepdb/PepDBSchema.java @@ -40,8 +40,8 @@ public class PepDBSchema public static final String COLUMN_PEPTIDE_POOL_ID = "peptide_pool_id"; public static final String COLUMN_PARENT_POOL_ID = "parent_pool_id"; public static final String COLUMN_PEPTIDE_SEQUENCE = "peptide_sequence"; - public static final String COLUMN_OPTIMAL_EPITOPE_LIST_ID = "optimal_epitope_list_id"; - public static final String COLUMN_AMINO_ACID_START_POS = "amino_acid_start_pos"; + public static final String COLUMN_OPTIMAL_EPITOPE_LIST_ID = "optimal_epitope_list_id"; + public static final String COLUMN_AMINO_ACID_START_POS = "amino_acid_start_pos"; public static final String COLUMN_AMINO_ACID_END_POS = "amino_acid_end_pos"; public static final String COLUMN_IS_CHILD = "child"; public static final String COLUMN_PEPTIDE_ID_IN_GROUP = "peptide_id_in_group"; diff --git a/src/org/scharp/atlas/pepdb/view/importPeptides.jsp b/src/org/scharp/atlas/pepdb/view/importPeptides.jsp index e305b8cd..e0f28944 100644 --- a/src/org/scharp/atlas/pepdb/view/importPeptides.jsp +++ b/src/org/scharp/atlas/pepdb/view/importPeptides.jsp @@ -3,8 +3,6 @@ <%@ page import="org.labkey.api.view.JspView"%> <%@ page import="org.scharp.atlas.pepdb.PepDBController" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> - -
<% JspView me = (JspView) HttpView.currentView(); @@ -21,8 +19,8 @@ File Type : - - - + + + diff --git a/src/org/scharp/atlas/pepdb/view/index.jsp b/src/org/scharp/atlas/pepdb/view/index.jsp index bb43af47..8348b7c8 100644 --- a/src/org/scharp/atlas/pepdb/view/index.jsp +++ b/src/org/scharp/atlas/pepdb/view/index.jsp @@ -34,7 +34,7 @@ PepDBController.DisplayPeptideForm form = (PepDBController.DisplayPeptideForm) (HttpView.currentModel()); %> -Lookup Peptide by Id: "/>   <%= button("Find").submit(true) %> +Lookup Peptide by Id:   <%= button("Find").submit(true) %>

Peptide Pools :

diff --git a/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp b/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp index f1dc3f84..d53c3a84 100644 --- a/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp +++ b/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp @@ -17,15 +17,15 @@ Source[] sources = PepDBManager.getSourcesForPeptide(bean.getQueryValue()); if (sources != null && sources.length > 0) {%> -

Peptide P<%=bean.getQueryValue()%> is a member of these Peptide Groups:

- <% List sourceList = Arrays.asList(sources); - for (Source source : sourceList) +

Peptide P<%=h(bean.getQueryValue())%> is a member of these Peptide Groups:

+ <% + for (Source source : sources) { %> <%= link(source.getPeptide_group_name()).href("displayPeptideGroupInformation.view?peptide_group_id=" + source.getPeptide_group_id().toString()) %> - (PEPTIDE NUMBER =<%=source.getPeptide_id_in_group()%>) + (PEPTIDE NUMBER =<%=h(source.getPeptide_id_in_group())%>) <%if(source.getFrequency_number() != null){%> - Frequency Number = <%= source.getFrequency_number()%> @@ -41,16 +41,16 @@ PeptidePool[] pools = PepDBManager.getPoolsForPeptide(bean.getQueryValue()); if (pools != null && pools.length > 0) { - List poolList = Arrays.asList(pools); %> -

Peptide P<%=bean.getQueryValue()%> is a member of these Peptide Pools:

- <% for (PeptidePool pool : poolList) + %> +

Peptide P<%=h(bean.getQueryValue())%> is a member of these Peptide Pools:

+ <% for (PeptidePool pool : pools) {%> <%= link("PP"+pool.getPeptide_pool_id()).href( "displayPeptidePoolInformation.view?peptide_pool_id=" +pool.getPeptide_pool_id()) %> - - <%=pool.getPeptide_pool_name()%> - <%=pool.getPool_type_desc()%> + <%=h(pool.getPeptide_pool_name())%> + <%=h(pool.getPool_type_desc())%> <% }} // end if statement %> diff --git a/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp index bb9849a6..edb171ef 100644 --- a/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp +++ b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp @@ -13,7 +13,7 @@ PeptideQueryForm bean = me.getModelBean(); if(bean.getMessage() != null){ %> -<%=bean.getMessage()%><%}%> +<%=h(bean.getMessage())%><%}%> - +

Search for Peptides using different criteria :



From 94d1ed10f356a8356f248e7aafcd96807f6a7b88 Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Sun, 27 Sep 2020 14:59:14 -0700 Subject: [PATCH 541/587] Switch to DisplayColumn.getFormattedHtml() and use HtmlString (#7) Fix assorted encoding problems --- .../atlas/pepdb/PepDBBaseController.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBBaseController.java b/src/org/scharp/atlas/pepdb/PepDBBaseController.java index c43f2a34..f7ac3f93 100644 --- a/src/org/scharp/atlas/pepdb/PepDBBaseController.java +++ b/src/org/scharp/atlas/pepdb/PepDBBaseController.java @@ -15,6 +15,8 @@ import org.labkey.api.data.Sort; import org.labkey.api.data.TableInfo; import org.labkey.api.util.DateUtil; +import org.labkey.api.util.HtmlString; +import org.labkey.api.util.HtmlStringBuilder; import org.scharp.atlas.pepdb.model.PeptideGroup; import org.scharp.atlas.pepdb.model.PeptidePool; import org.scharp.atlas.pepdb.model.Peptides; @@ -466,10 +468,10 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep @Override @NotNull - public String getFormattedValue(RenderContext ctx) { - StringBuilder sb = new StringBuilder("P"); - sb.append(super.getFormattedValue(ctx)); - return sb.toString(); + public HtmlString getFormattedHtml(RenderContext ctx) { + HtmlStringBuilder hsb = HtmlStringBuilder.of("P"); + hsb.append(super.getFormattedHtml(ctx)); + return hsb.getHtmlString(); } @Override @@ -543,10 +545,10 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep @Override @NotNull - public String getFormattedValue(RenderContext ctx) { - StringBuilder sb = new StringBuilder("PP"); - sb.append(super.getFormattedValue(ctx)); - return sb.toString(); + public HtmlString getFormattedHtml(RenderContext ctx) { + HtmlStringBuilder hsb = HtmlStringBuilder.of("PP"); + hsb.append(super.getFormattedHtml(ctx)); + return hsb.getHtmlString(); } @Override From 08715329db5938235a96ed24dc175aa82d8b94eb Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 14 Oct 2020 08:49:09 -0700 Subject: [PATCH 542/587] Add some missing module dependencies and move some dependency declarations from module.properties files into build.gradle files (#74) --- build.gradle | 3 +++ module.properties | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 build.gradle diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..be82913d --- /dev/null +++ b/build.gradle @@ -0,0 +1,3 @@ +import org.labkey.gradle.util.BuildUtils + +BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "study"), depProjectConfig: "published", depExtension: "module") diff --git a/module.properties b/module.properties index b445cec0..2c7ab896 100644 --- a/module.properties +++ b/module.properties @@ -4,4 +4,3 @@ Description: Provides a web part for exploring the studies and manuscripts avail URL: https://www.itntrialshare.org License: Apache 2.0 LicenseURL: http://www.apache.org/licenses/LICENSE-2.0 -ModuleDependencies: Study From beda53b8b05198470ca41a48481d94bcb6224872 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 2 Nov 2020 09:14:12 -0800 Subject: [PATCH 543/587] Remove all DataFinder code from trialShare repo (#75) --- resources/olap/PublicationCube.xml | 62 - resources/olap/StudyCube.xml | 59 - .../lists/ManuscriptsAndAbstracts.query.xml | 31 - .../manageData.qview.xml | 8 - .../queries/lists/StudyProperties.query.xml | 50 - .../StudyProperties/manageData.qview.xml | 7 - resources/web/study/Finder/data/AgeGroup.js | 14 - resources/web/study/Finder/data/Condition.js | 14 - resources/web/study/Finder/data/Container.js | 36 - resources/web/study/Finder/data/CubeConfig.js | 31 - .../web/study/Finder/data/CubeObjects.js | 81 - resources/web/study/Finder/data/Facet.js | 52 - .../web/study/Finder/data/FacetFilter.js | 13 - .../web/study/Finder/data/FacetMember.js | 23 - .../web/study/Finder/data/FacetMembers.js | 43 - resources/web/study/Finder/data/Facets.js | 637 -------- resources/web/study/Finder/data/Phase.js | 14 - .../web/study/Finder/data/Publication.js | 51 - .../web/study/Finder/data/PublicationType.js | 14 - resources/web/study/Finder/data/Status.js | 14 - resources/web/study/Finder/data/Study.js | 49 - .../web/study/Finder/data/StudyAccess.js | 19 - resources/web/study/Finder/data/StudyType.js | 14 - .../web/study/Finder/data/SubmissionStatus.js | 14 - resources/web/study/Finder/data/Subset.js | 14 - resources/web/study/Finder/data/Subsets.js | 39 - .../web/study/Finder/data/TherapeuticArea.js | 14 - resources/web/study/Finder/data/Visibility.js | 14 - resources/web/study/Finder/dataFinder.css | 812 ---------- resources/web/study/Finder/dataFinder.lib.xml | 41 - .../web/study/Finder/dataFinderEditor.lib.xml | 33 - .../panel/CubeObjectDetailsFormPanel.js | 199 --- .../web/study/Finder/panel/CubeObjects.js | 159 -- .../study/Finder/panel/FacetPanelHeader.js | 226 --- .../web/study/Finder/panel/FacetSelection.js | 153 -- .../web/study/Finder/panel/FacetsGrid.js | 432 ------ resources/web/study/Finder/panel/Finder.js | 104 -- .../web/study/Finder/panel/FinderCard.js | 131 -- .../web/study/Finder/panel/FinderCardDeck.js | 42 - .../Finder/panel/FinderCardPanelHeader.js | 248 --- .../study/Finder/panel/PublicationCards.js | 296 ---- .../panel/PublicationDetailsFormPanel.js | 469 ------ .../study/Finder/panel/PublicationSummary.js | 59 - .../web/study/Finder/panel/StudyAccessForm.js | 86 -- .../Finder/panel/StudyAccessTuplePanel.js | 112 -- .../web/study/Finder/panel/StudyCards.js | 235 --- .../Finder/panel/StudyDetailsFormPanel.js | 288 ---- .../web/study/Finder/panel/StudySummary.js | 48 - resources/web/study/Finder/trialShare.css | 66 - .../web/study/Finder/util/CubeObjectHelper.js | 74 - resources/web/study/Finder/view/GridTable.js | 34 - .../PublicationDocumentProvider.java | 180 --- .../trialshare/StudyDocumentProvider.java | 156 -- .../trialshare/TrialShareController.java | 1358 ----------------- .../labkey/trialshare/TrialShareManager.java | 591 ------- .../labkey/trialshare/TrialShareModule.java | 56 +- .../trialshare/data/CubeConfigBean.java | 256 ---- .../labkey/trialshare/data/FacetFilter.java | 58 - .../trialshare/data/PublicationEditBean.java | 67 - .../labkey/trialshare/data/StudyAccess.java | 118 -- src/org/labkey/trialshare/data/StudyBean.java | 386 ----- .../labkey/trialshare/data/StudyEditBean.java | 77 - .../trialshare/data/StudyFacetBean.java | 147 -- .../trialshare/data/StudyFacetMember.java | 102 -- .../trialshare/data/StudyPersonnelBean.java | 119 -- .../trialshare/data/StudyPublicationBean.java | 619 -------- .../labkey/trialshare/data/StudySubset.java | 56 - src/org/labkey/trialshare/data/URLData.java | 67 - .../query/ManageCubeObjectQueryView.java | 125 -- .../query/ManagePublicationsQueryView.java | 66 - .../query/ManageStudiesQueryView.java | 68 - .../query/TrialShareQuerySchema.java | 253 --- .../trialshare/view/DataFinderWebPart.java | 40 - src/org/labkey/trialshare/view/cubeAdmin.jsp | 115 -- src/org/labkey/trialshare/view/dataFinder.jsp | 54 - src/org/labkey/trialshare/view/manageData.jsp | 54 - .../trialshare/view/publicationDetails.jsp | 56 - .../labkey/trialshare/view/studyDetail.jsp | 147 -- .../labkey/trialshare/view/studyDetails.jsp | 73 - test/sampledata/DataFinder.lists.zip | Bin 14300 -> 0 bytes .../DataFinderTestOperationalStudy.folder.zip | Bin 13423 -> 0 bytes ...DataFinderTestOperationalWISP-R.folder.zip | Bin 13295 -> 0 bytes .../DataFinderTestPublicCasale.folder.zip | Bin 13288 -> 0 bytes .../DataFinderTestPublicStudy.folder.zip | Bin 13393 -> 0 bytes .../trialshare/PublicationPanel.java | 130 -- .../trialshare/StudySummaryPanel.java | 147 -- .../trialshare/StudySummaryWindow.java | 49 - .../pages/trialshare/CubeObjectEditPage.java | 277 ---- .../test/pages/trialshare/DataFinderPage.java | 767 ---------- .../test/pages/trialshare/ManageDataPage.java | 131 -- .../pages/trialshare/PublicationEditPage.java | 136 -- .../trialshare/PublicationsListHelper.java | 119 -- .../pages/trialshare/StudiesListHelper.java | 126 -- .../test/pages/trialshare/StudyEditPage.java | 222 --- .../tests/trialshare/DataFinderTestBase.java | 269 ---- .../trialshare/ManagePublicationsTest.java | 644 -------- .../tests/trialshare/ManageStudiesTest.java | 576 ------- .../trialshare/TrialShareDataFinderTest.java | 588 ------- 98 files changed, 1 insertion(+), 15422 deletions(-) delete mode 100644 resources/olap/PublicationCube.xml delete mode 100644 resources/olap/StudyCube.xml delete mode 100644 resources/queries/lists/ManuscriptsAndAbstracts.query.xml delete mode 100644 resources/queries/lists/ManuscriptsAndAbstracts/manageData.qview.xml delete mode 100644 resources/queries/lists/StudyProperties.query.xml delete mode 100644 resources/queries/lists/StudyProperties/manageData.qview.xml delete mode 100644 resources/web/study/Finder/data/AgeGroup.js delete mode 100644 resources/web/study/Finder/data/Condition.js delete mode 100644 resources/web/study/Finder/data/Container.js delete mode 100644 resources/web/study/Finder/data/CubeConfig.js delete mode 100644 resources/web/study/Finder/data/CubeObjects.js delete mode 100644 resources/web/study/Finder/data/Facet.js delete mode 100644 resources/web/study/Finder/data/FacetFilter.js delete mode 100644 resources/web/study/Finder/data/FacetMember.js delete mode 100644 resources/web/study/Finder/data/FacetMembers.js delete mode 100644 resources/web/study/Finder/data/Facets.js delete mode 100644 resources/web/study/Finder/data/Phase.js delete mode 100644 resources/web/study/Finder/data/Publication.js delete mode 100644 resources/web/study/Finder/data/PublicationType.js delete mode 100644 resources/web/study/Finder/data/Status.js delete mode 100644 resources/web/study/Finder/data/Study.js delete mode 100644 resources/web/study/Finder/data/StudyAccess.js delete mode 100644 resources/web/study/Finder/data/StudyType.js delete mode 100644 resources/web/study/Finder/data/SubmissionStatus.js delete mode 100644 resources/web/study/Finder/data/Subset.js delete mode 100644 resources/web/study/Finder/data/Subsets.js delete mode 100644 resources/web/study/Finder/data/TherapeuticArea.js delete mode 100644 resources/web/study/Finder/data/Visibility.js delete mode 100644 resources/web/study/Finder/dataFinder.css delete mode 100644 resources/web/study/Finder/dataFinder.lib.xml delete mode 100644 resources/web/study/Finder/dataFinderEditor.lib.xml delete mode 100644 resources/web/study/Finder/panel/CubeObjectDetailsFormPanel.js delete mode 100644 resources/web/study/Finder/panel/CubeObjects.js delete mode 100644 resources/web/study/Finder/panel/FacetPanelHeader.js delete mode 100644 resources/web/study/Finder/panel/FacetSelection.js delete mode 100644 resources/web/study/Finder/panel/FacetsGrid.js delete mode 100644 resources/web/study/Finder/panel/Finder.js delete mode 100644 resources/web/study/Finder/panel/FinderCard.js delete mode 100644 resources/web/study/Finder/panel/FinderCardDeck.js delete mode 100644 resources/web/study/Finder/panel/FinderCardPanelHeader.js delete mode 100644 resources/web/study/Finder/panel/PublicationCards.js delete mode 100644 resources/web/study/Finder/panel/PublicationDetailsFormPanel.js delete mode 100644 resources/web/study/Finder/panel/PublicationSummary.js delete mode 100644 resources/web/study/Finder/panel/StudyAccessForm.js delete mode 100644 resources/web/study/Finder/panel/StudyAccessTuplePanel.js delete mode 100644 resources/web/study/Finder/panel/StudyCards.js delete mode 100644 resources/web/study/Finder/panel/StudyDetailsFormPanel.js delete mode 100644 resources/web/study/Finder/panel/StudySummary.js delete mode 100644 resources/web/study/Finder/trialShare.css delete mode 100644 resources/web/study/Finder/util/CubeObjectHelper.js delete mode 100644 resources/web/study/Finder/view/GridTable.js delete mode 100644 src/org/labkey/trialshare/PublicationDocumentProvider.java delete mode 100644 src/org/labkey/trialshare/StudyDocumentProvider.java delete mode 100644 src/org/labkey/trialshare/TrialShareManager.java delete mode 100644 src/org/labkey/trialshare/data/CubeConfigBean.java delete mode 100644 src/org/labkey/trialshare/data/FacetFilter.java delete mode 100644 src/org/labkey/trialshare/data/PublicationEditBean.java delete mode 100644 src/org/labkey/trialshare/data/StudyAccess.java delete mode 100644 src/org/labkey/trialshare/data/StudyBean.java delete mode 100644 src/org/labkey/trialshare/data/StudyEditBean.java delete mode 100644 src/org/labkey/trialshare/data/StudyFacetBean.java delete mode 100644 src/org/labkey/trialshare/data/StudyFacetMember.java delete mode 100644 src/org/labkey/trialshare/data/StudyPersonnelBean.java delete mode 100644 src/org/labkey/trialshare/data/StudyPublicationBean.java delete mode 100644 src/org/labkey/trialshare/data/StudySubset.java delete mode 100644 src/org/labkey/trialshare/data/URLData.java delete mode 100644 src/org/labkey/trialshare/query/ManageCubeObjectQueryView.java delete mode 100644 src/org/labkey/trialshare/query/ManagePublicationsQueryView.java delete mode 100644 src/org/labkey/trialshare/query/ManageStudiesQueryView.java delete mode 100644 src/org/labkey/trialshare/query/TrialShareQuerySchema.java delete mode 100644 src/org/labkey/trialshare/view/DataFinderWebPart.java delete mode 100644 src/org/labkey/trialshare/view/cubeAdmin.jsp delete mode 100644 src/org/labkey/trialshare/view/dataFinder.jsp delete mode 100644 src/org/labkey/trialshare/view/manageData.jsp delete mode 100644 src/org/labkey/trialshare/view/publicationDetails.jsp delete mode 100644 src/org/labkey/trialshare/view/studyDetail.jsp delete mode 100644 src/org/labkey/trialshare/view/studyDetails.jsp delete mode 100644 test/sampledata/DataFinder.lists.zip delete mode 100644 test/sampledata/DataFinderTestOperationalStudy.folder.zip delete mode 100644 test/sampledata/DataFinderTestOperationalWISP-R.folder.zip delete mode 100644 test/sampledata/DataFinderTestPublicCasale.folder.zip delete mode 100644 test/sampledata/DataFinderTestPublicStudy.folder.zip delete mode 100644 test/src/org/labkey/test/components/trialshare/PublicationPanel.java delete mode 100644 test/src/org/labkey/test/components/trialshare/StudySummaryPanel.java delete mode 100644 test/src/org/labkey/test/components/trialshare/StudySummaryWindow.java delete mode 100644 test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java delete mode 100644 test/src/org/labkey/test/pages/trialshare/DataFinderPage.java delete mode 100644 test/src/org/labkey/test/pages/trialshare/ManageDataPage.java delete mode 100644 test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java delete mode 100644 test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java delete mode 100644 test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java delete mode 100644 test/src/org/labkey/test/pages/trialshare/StudyEditPage.java delete mode 100644 test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java delete mode 100644 test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java delete mode 100644 test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java delete mode 100644 test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java diff --git a/resources/olap/PublicationCube.xml b/resources/olap/PublicationCube.xml deleted file mode 100644 index 7ad8e53a..00000000 --- a/resources/olap/PublicationCube.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - FALSE - - -
-
- - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/olap/StudyCube.xml b/resources/olap/StudyCube.xml deleted file mode 100644 index debc432f..00000000 --- a/resources/olap/StudyCube.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - FALSE - - -
-
- - - - - - - - - - - - - - - -
- - - - -
- - - - -
- - - - -
- - - - - - - - - - - - - - - - - diff --git a/resources/queries/lists/ManuscriptsAndAbstracts.query.xml b/resources/queries/lists/ManuscriptsAndAbstracts.query.xml deleted file mode 100644 index 64a75185..00000000 --- a/resources/queries/lists/ManuscriptsAndAbstracts.query.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - -
- - - Studies - - lists - PublicationStudy - publicationId - junction - StudyId - - false - - - - lists - PublicationTherapeuticArea - publicationId - junction - TherapeuticArea - - false - - -
- - - diff --git a/resources/queries/lists/ManuscriptsAndAbstracts/manageData.qview.xml b/resources/queries/lists/ManuscriptsAndAbstracts/manageData.qview.xml deleted file mode 100644 index 835edc5d..00000000 --- a/resources/queries/lists/ManuscriptsAndAbstracts/manageData.qview.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/resources/queries/lists/StudyProperties.query.xml b/resources/queries/lists/StudyProperties.query.xml deleted file mode 100644 index c4177c35..00000000 --- a/resources/queries/lists/StudyProperties.query.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - lists - StudyAgeGroup - StudyId - junction - AgeGroup - - false - - - - lists - StudyPhase - StudyId - junction - Phase - - false - - - - lists - StudyCondition - StudyId - junction - Condition - - false - - - - lists - StudyTherapeuticArea - StudyId - junction - TherapeuticArea - - false - - -
-
-
-
diff --git a/resources/queries/lists/StudyProperties/manageData.qview.xml b/resources/queries/lists/StudyProperties/manageData.qview.xml deleted file mode 100644 index 8aa6eaaf..00000000 --- a/resources/queries/lists/StudyProperties/manageData.qview.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/resources/web/study/Finder/data/AgeGroup.js b/resources/web/study/Finder/data/AgeGroup.js deleted file mode 100644 index 62c1cc74..00000000 --- a/resources/web/study/Finder/data/AgeGroup.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.AgeGroup', { - extend: 'Ext.data.Model', - - idProperty: 'ageGroup', - - fields: [ - {name: 'ageGroup'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Condition.js b/resources/web/study/Finder/data/Condition.js deleted file mode 100644 index 72a7f8e4..00000000 --- a/resources/web/study/Finder/data/Condition.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.Condition', { - extend: 'Ext.data.Model', - - idProperty: 'condition', - - fields: [ - {name: 'condition'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Container.js b/resources/web/study/Finder/data/Container.js deleted file mode 100644 index da551f9f..00000000 --- a/resources/web/study/Finder/data/Container.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.Container', { - extend: 'Ext.data.Model', - - idProperty: 'entityId', - - containerFilter: "AllFolders", - - proxy : { - type : 'ajax', - url : LABKEY.ActionURL.buildURL('query', 'selectRows.api'), - extraParams : { - schemaName : 'core', - queryName : 'containers', - containerFilter : 'AllFolders', - 'query.columns' : "EntityId,DisplayName,Path", - 'query.queryName': "Containers", - 'query.sort': "DisplayName", - limit : -1 - }, - reader : { - type : 'json', - root : 'rows' - } - }, - - fields: [ - {name: 'EntityId'}, - {name: 'DisplayName'}, - {name: 'Path'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/CubeConfig.js b/resources/web/study/Finder/data/CubeConfig.js deleted file mode 100644 index c1cf9c1d..00000000 --- a/resources/web/study/Finder/data/CubeConfig.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -// Consider: Use "cube.js" app metadata overlay paradigm -Ext4.define('LABKEY.study.data.CubeConfig', { - extend: 'Ext.data.Model', - idProperty : 'objectName', - - fields: [ - {name: 'objectName'}, - {name: 'objectNamePlural'}, - {name: 'cubeName'}, - {name: 'dataModuleName'}, - {name: 'configId'}, - {name: 'schemaName'}, - {name: 'showSearch'}, - {name: 'filterByLevel'}, - {name: 'countDistinctLevel'}, - {name: 'filterByFacetUniqueName'}, - {name: 'showParticipantFilters'}, - {name: 'isDefault'}, - {name: 'subsetLevelName'}, - {name: 'searchCategory'}, - {name: 'searchScope'}, - {name: 'cubeContainerPath'}, - {name: 'hasContainerFilter', type: 'boolean', defaultValue: false}, - {name: 'countField'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/CubeObjects.js b/resources/web/study/Finder/data/CubeObjects.js deleted file mode 100644 index 3fcd0a44..00000000 --- a/resources/web/study/Finder/data/CubeObjects.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.store.CubeObjects', { - extend: 'Ext.data.Store', - autoLoad: false, - - listeners: { - 'load' : { - fn : function(store, records, options) { - store.isLoaded = true; - }, - scope: this - } - }, - - setSearchFilters: function(searchSelectedMembers) { - // console.log("setting searchSelectedMembers ", searchSelectedMembers); - this.searchSelectedMembers = searchSelectedMembers; - }, - - updateSearchFilters: function(searchSelectedMembers) { - // console.log("searchSelectedMembers: ", searchSelectedMembers); - if (searchSelectedMembers == null || searchSelectedMembers) // null is a value we want to retain but undefined is not - this.searchSelectedMembers = searchSelectedMembers; - this.updateFilters(); - }, - - updateFacetFilters: function(facetSelectedMembers, selectedSubset) { - // console.log("updateFacetFilters: this.facetSelectedMembers ", this.facetSelectedMembers, " facetSelectedMembers ", facetSelectedMembers, " this.selectedSubset ", this.selectedSubset, " selectedSubset ", selectedSubset); - if (facetSelectedMembers != undefined) - this.facetSelectedMembers = facetSelectedMembers; - if (selectedSubset) - this.selectedSubset = selectedSubset; - // console.log("update facet filters with searchSelectedMembers ", this.searchSelectedMembers); - this.updateFilters() - }, - - setUnfilteredCount: function() - { - if (!this.countField || this.countField == this.storeId) - this.unfilteredCount = this.count(); - else - this.unfilteredCount = this.sum(this.countField); - }, - - updateFilters: function() - { - var object; - - // console.log("updateFilters with this.searchSelectedMembers ", this.searchSelectedMembers, " this.facetSelectedMembers ", this.facetSelectedMembers); - this.suspendEvents(false); - this.clearFilter(); - this.setUnfilteredCount(); - for (var i = 0; i < this.count(); i++) { - object = this.getAt(i); - object.set({ - "isSelected":this.facetSelectedMembers[object.get(object.idProperty)] !== undefined, - "isSelectedBySearch": this.searchSelectedMembers == null || this.searchSelectedMembers[object.get(object.idProperty)] !== undefined - }); - } - this.resumeEvents(); - - this.filter([ - {property: 'isSelected', value: true}, - {property: 'isSelectedBySearch', value: true} - ]); - }, - - selectAll : function() { - for (var i = 0; i < this.count(); i++) { - var cubeObj = this.getAt(i); - cubeObj.set({ - "isSelected": true, - "isSelectedBySearch": true - }); - } - } -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Facet.js b/resources/web/study/Finder/data/Facet.js deleted file mode 100644 index 000137ba..00000000 --- a/resources/web/study/Finder/data/Facet.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2015-2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.Facet', { - extend: 'Ext.data.Model', - - idProperty : 'name', - - fields: [ - {name: 'name'}, - {name: 'pluralName'}, // not currently used - {name: 'members'}, - {name: 'selectedMembers'}, - {name: 'filterOptions'}, - {name: 'currentFilterType'}, - {name: 'currentFilterCaption'}, - {name: 'allMemberCount', type:'int'}, // not currently used - {name: 'hierarchy'}, - {name: 'hierarchyName'}, - {name: 'levelName'}, - {name: 'allMemberName'}, // not currently used - {name: 'ordinal'}, - {name: 'isExpanded', type:'boolean', defaultValue: true}, - {name: 'displayFacet', type:'boolean', defaultValue: true}, - {name: 'defaultSelectedUniqueNames'} - ], - - associations: [ - { - type: 'hasMany', - model: 'LABKEY.study.data.FacetMember', - name: 'members', - associationKey: 'members' - }, - { - type: 'hasMany', - model: 'LABKEY.study.data.FacetFilter', - name: 'filterOptions', - associationKey: 'filterOptions' - }, - { - type: 'hasMany', - model: 'LABKEY.study.data.FacetMember', - name: 'selectedMembers', - associationKey: 'selectedMembers' - } - ] -}); - - diff --git a/resources/web/study/Finder/data/FacetFilter.js b/resources/web/study/Finder/data/FacetFilter.js deleted file mode 100644 index ed2388e3..00000000 --- a/resources/web/study/Finder/data/FacetFilter.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2015-2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.FacetFilter', { - extend: 'Ext.data.Model', - fields :[ - {name: 'type'}, - {name: 'caption'}, - {name: 'default'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/FacetMember.js b/resources/web/study/Finder/data/FacetMember.js deleted file mode 100644 index 8199988a..00000000 --- a/resources/web/study/Finder/data/FacetMember.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2015-2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.FacetMember', { - extend: 'Ext.data.Model', - - idProperty : 'uniqueName', - - fields: [ - {name: 'name'}, - {name: 'uniqueName'}, - {name: 'count'}, - {name: 'percent'}, - {name: 'unfilteredPercent'}, - {name: 'facet'}, - {name: 'facetName'}, - {name: 'level'}, - {name: 'unfilteredCount', type: 'int', defaultValue: 0} - ] - -}); diff --git a/resources/web/study/Finder/data/FacetMembers.js b/resources/web/study/Finder/data/FacetMembers.js deleted file mode 100644 index d0066178..00000000 --- a/resources/web/study/Finder/data/FacetMembers.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2015-2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.store.FacetMembers', { - extend: 'Ext.data.Store', - model: 'LABKEY.study.data.FacetMember', - - autoLoad: false, - - groupers: [ - { - property: 'facetName', - sorterFn: function(o1, o2){ - rank1 = o1.get('facet').get("ordinal"); - rank2 = o2.get('facet').get("ordinal"); - if (rank1 === rank2) - { - if (o1.get("level") === '[Publication.Study].[Study]' && o1.get("level") == o2.get("level")) - { - return LABKEY.study.util.CubeObjectHelper.naturalSortFn(o1.get("name"), o2.get("name")); - } - if (o1.get("name") === o2.get("name")) - return 0; - else - return o1.get("name") < o2.get("name") ? -1 : 1; - } - else - return rank1 < rank2 ? -1 : 1; - - } - } - ], - - zeroCounts : function() { - this.each(function(record) { - record.set("count", 0); - record.set("percent", 0); - }); - } - -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Facets.js b/resources/web/study/Finder/data/Facets.js deleted file mode 100644 index c4f47464..00000000 --- a/resources/web/study/Finder/data/Facets.js +++ /dev/null @@ -1,637 +0,0 @@ -/* - * Copyright (c) 2015-2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.store.Facets', { - extend: 'Ext.data.Store', - model: 'LABKEY.study.data.Facet', - autoLoad: false, - - mdx: null, - isLoaded: false, - - proxy : { - type: "ajax", - //url: // set in constructor - reader: { - type: 'json', - root: 'data' - } - }, - listeners: { - 'load' : { - fn : function(store, records, options) { - store.isLoaded = true; - store.loadFromCube(); - }, - scope: this - } - }, - - clearAllSelectedMembers: function() { - for (var f = 0; f < this.count(); f++) { - var facet = this.getAt(f); - if (facet.get("name") != this.cubeConfig.objectName) - { - this.getAt(f).data.selectedMembers = []; - } - } - }, - - selectMembers : function(members) { - for (var i = 0; i < members.length; i++) - { - this.getById(members[i].data.facetName).data.selectedMembers.push(members[i]); - } - }, - - loadFromCube : function() { - if (this.isLoaded && this.mdx) - { - var cube = this.mdx._cube; - var facetMembersStore = Ext4.getStore(this.cubeConfig.objectName + "FacetMembers"); - for (var f = 0; f < this.count(); f++) - { - var facet = this.getAt(f); - facet.data.hierarchy = cube.hierarchyMap[facet.get("hierarchyName")]; - facet.data.level = facet.data.hierarchy.levelMap[facet.get("levelName")]; - facet.data.members = []; - var defaultFilter = this.getDefaultFilterOption(facet.filterOptionsStore); - facet.data.currentFilterType = defaultFilter.get("type"); - facet.data.currentFilterCaption = defaultFilter.get("caption"); - for (var m = 0; m < facet.data.level.members.length; m++) - { - var src = facet.data.level.members[m]; - if (src.name == "#notnull") - continue; - var member = { - name: src.name, - uniqueName: src.uniqueName, - level: src.level.uniqueName, - count: 0, - percent: 0, - facetName: facet.get("name"), - facet : facet - }; - if (facet.get("name") != this.cubeConfig.objectName) - facetMembersStore.add(member); - facet.data.members.push(member); - } - if (facet.get("name") == this.cubeConfig.objectName) { - facet.data.selectedMembers = facet.data.members; - } - } - facetMembersStore.filter( - [ - {filterFn: function(item) { - return item.get("facet").get("displayFacet"); - }} - ] - ); - facetMembersStore.sort(); - this.updateCountsAsync(false); - } - }, - - getDefaultFilterOption : function(optionsStore) { - for (var i = 0; i < optionsStore.count(); i++) { - if (optionsStore.getAt(i).data.isDefault) - return optionsStore.getAt(i); - } - return optionsStore.getAt(0); - }, - - getObjectSubsetFilter: function() { - var store = Ext4.getStore(this.cubeConfig.objectName); - - if (!store || !store.selectedSubset) - return null; - else - return {level: this.cubeConfig.subsetLevelName, members: [ store.selectedSubset ]}; - - }, - - getFiltersForCountDistinct: function(filtersMap) { - this.addFilterMapData(filtersMap, this.getObjectSubsetFilter()); - this.addFilterMapData(filtersMap, this.getSearchFilter()); - this.addFilterMapData(filtersMap, this.getCustomFilters()); - var filters = []; - for (var level in filtersMap) { - if (!filtersMap.hasOwnProperty(level)) - continue; - if (Ext4.isObject(filtersMap[level])) - { - for (var level2 in filtersMap[level]) - { - filters.push({ - level: this.cubeConfig.filterByLevel, - membersQuery: {level: level2, members: filtersMap[level][level2]} - }); - } - } - else - filters.push({level: level, members: filtersMap[level]}); - } - this.addSelectedMembersFilters(filters); - return filters; - }, - - addFilterMapData : function(filtersMap, newFilters) - { - if (newFilters != null) - { - if (filtersMap[newFilters.level]) - filtersMap[newFilters.level].concat(newFilters.members); - else - filtersMap[newFilters.level] = newFilters.members; - } - return filtersMap; - }, - - getSearchFilter : function() - { - var store = Ext4.getStore(this.cubeConfig.objectName); - if (store.searchSelectedMembers != null) - { - var selection = []; - for (var key in store.searchSelectedMembers) - { - selection = selection.concat(store.searchSelectedMembers[key]); - } - return {level: this.cubeConfig.filterByLevel, members: selection}; - } - return null; - }, - - getCustomFilters: function() - { - return null; - }, - - getValidFacetMemberSubset : function(possibleMembers) - { - var validMembers = []; - var facetMemberStore = Ext4.getStore(this.cubeConfig.objectName + "FacetMembers"); - for (var i = 0; i < possibleMembers.length; i++) - { - if (facetMemberStore.getById(possibleMembers[i])) - validMembers.push(possibleMembers[i]); - } - return validMembers; - }, - - updateCountsAsync: function (isSavedGroup) - { - if (!this.isLoaded || !this.mdx) - { - console.log("Store not ready for count update. Please try again later."); - return; - } - - var store = Ext4.getStore(this.cubeConfig.objectName); - if (store.searchSelectedMembers != null) - this.makeCountDistinctQueries({}); // search has already filtered results to the ones the user has access to - else - { - // console.log("updateCountsAsync called"); - var url = LABKEY.ActionURL.buildURL(this.dataModuleName, "accessibleMembers.api", null, { - "objectName": this.cubeConfig.objectName - }); - Ext4.Ajax.request({ - url: url, - success: function (response) - { - var o = Ext4.decode(response.responseText); - if (o.success) - { - var filters = {}; - for (var level in o.data) - { - if (!o.data.hasOwnProperty(level)) - continue; - if (Ext4.isObject(o.data[level]) || (Ext4.isArray(o.data[level]) && o.data[level].length)) - { - var includedMembers = this.removeMembersNotInCube(level, o.data[level]); - if (!filters[level]) - filters[level] = includedMembers; - else - filters[level] = filters[level].concat(includedMembers); - } - } - this.makeCountDistinctQueries(filters); - } - else - { - console.log("Problem making request for accessible members", o); - } - - }, - scope: this - }); - } - }, - - removeMembersNotInCube : function(level, filterData) - { - if (!filterData) - return filterData; - if (Ext4.isArray(filterData)) - { - // split the level name into its parts to use for accessing the cube members - var levelParts = level.replace(/[\[\]]/g, "").split("."); - if (levelParts && levelParts.length > 1) - { - // find the uniqueNames of the current members of the cube for the given level - var uniqueNames = this.mdx._cube.hierarchyMap[levelParts[0]].levelMap[levelParts[1]].members.map(function (m) - { - return m.uniqueName - }); - // filter out the accessible members that are not currently part of the cube - var includedMembers = filterData.filter(function (m) - { - return uniqueNames.indexOf(m) >= 0; - }); - return includedMembers; - } - - } - else if (Ext4.isObject(filterData)) - { - for (var nextLevel in filterData) - { - if (filterData.hasOwnProperty(nextLevel)) - filterData[nextLevel] = this.removeMembersNotInCube(nextLevel, filterData[nextLevel]); - } - return filterData; - } - }, - - makeCountDistinctQueries : function(filtersMap) - { - this.cellSetPositions = null; - this.cellSetCells = null; - this.cellSets = {}; - if (this.cubeConfig.objectName == "Publication") - this.makeCountDistinctQuery(filtersMap); - else - { - this.makeVisibilityCountDistinctQuery(filtersMap); - this.makeCountDistinctQuery(filtersMap); - } - - }, - - makeCountDistinctQuery: function(filtersMap) - { - var filters = this.getFiltersForCountDistinct(filtersMap); - - var multiColumnCount = this.cubeConfig.countField && this.cubeConfig.countField != this.cubeConfig.objectName; - - var config = - { - "sql": true, - configId: this.cubeConfig.configId, - schemaName: this.cubeConfig.schemaName, - container: this.cubeConfig.cubeContainerId, - containerPath : this.cubeConfig.cubeContainerPath, - name: this.cubeConfig.name, - success: function (cellSet, mdx, config) - { - this.updateCounts(cellSet, "Default", multiColumnCount); - }, - scope: this, - - // query - onRows: this.getOnRowsData(), - countFilter: filters, - includeNullMemberInCount: false, - countDistinctLevel: this.cubeConfig.countDistinctLevel - }; - if (multiColumnCount) - { - config.onCols = { operator: "UNION", arguments: [{level: this.cubeConfig.filterByLevel}] }; - } - // console.log("Making count distinct query with config", config); - this.mdx.query(config); - - }, - - addSelectedMembersFilters: function(filters) - { - var facet; - for (var f = 0; f < this.count(); f++) - { - facet = this.getAt(f); - var selectedMembers = facet.get("selectedMembers"); - if (facet.get("name") != this.cubeConfig.objectName) - { - if (!selectedMembers || selectedMembers.length == 0) - continue; - if (facet.get("currentFilterType") === "OR") - { - var names = []; - selectedMembers.forEach(function (m) - { - names.push(m.data.uniqueName) - }); - filters.push({ - level: this.cubeConfig.filterByLevel, - membersQuery: {level: selectedMembers[0].data.level, members: names} - }); - } - else - { - for (var i = 0; i < selectedMembers.length; i++) - { - var filterMember = selectedMembers[i]; - filters.push({ - level: this.cubeConfig.filterByLevel, - membersQuery: {level: filterMember.data.level, members: [filterMember.data.uniqueName]} - }); - } - } - } - } - }, - - getOnRowsData: function() - { - var onRows = { operator: "UNION", arguments: [] }; - onRows.arguments.push({level: this.cubeConfig.filterByLevel}); - for (f = 0; f < this.count(); f++) - { - facet = this.getAt(f); - if (facet.get("name") == "Subject") - onRows.arguments.push({level: facet.data.hierarchy.levels[0].uniqueName}); - else if (facet.get("name") == this.cubeConfig.objectName || (this.cubeConfig.objectName == "Study" && facet.get("name") == "Visibility")) - continue; - else - onRows.arguments.push({level: facet.data.level.uniqueName}); - } - return onRows; - }, - - mergeCellSets: function(newCellSet) - { - if (!this.cellSetPositions) - { - this.cellSetPositions = newCellSet.axes[1].positions; - this.cellSetCells = newCellSet.cells; - } - else - { - // console.log("before", this.cellSet); - this.cellSetPositions = this.cellSetPositions.concat(newCellSet.axes[1].positions); - this.cellSetCells = this.cellSetCells.concat(newCellSet.cells); - // console.log("after", this.cellSet); - } - }, - - makeVisibilityCountDistinctQuery : function(filtersMap) - { - var filters = this.getFiltersForCountDistinct(filtersMap); - - var onRows = { operator: "UNION", arguments: [] }; - onRows.arguments.push({level: "[Study.Visibility].[Visibility]"}); - - var multiColumnCount = this.cubeConfig.countField && this.cubeConfig.countField != this.cubeConfig.objectName; - - var config = - { - "sql": true, - configId: this.cubeConfig.configId, - schemaName: this.cubeConfig.schemaName, - container: this.cubeConfig.cubeContainerId, - containerPath : this.cubeConfig.cubeContainerPath, - name: this.cubeConfig.name, - success: function (cellSet, mdx, config) - { - this.updateCounts(cellSet, "Visibility", multiColumnCount); - }, - scope: this, - - // query - onRows: onRows, - countFilter: filters, - includeNullMemberInCount: false, - countDistinctLevel: "[Study].[Container]" - }; - if (multiColumnCount) - { - config.onCols = { operator: "UNION", arguments: [{level: this.cubeConfig.filterByLevel}] }; - } - // console.log("Making count distinct query with config", config); - this.mdx.query(config); - }, - - updateCountsZero : function () - { - var facetMembersStore = Ext4.getStore(this.cubeConfig.objectName + "FacetMembers"); - facetMembersStore.zeroCounts(); - //this.saveFilterState(); - //this.updateContainerFilter(); - //this.changeSubjectGroup(); - //this.doneRendering(); - }, - - updateCounts: function(cellSet, cellSetType, multiColumnCount) - { - this.mergeCellSets(cellSet); - if (this.cubeConfig.objectName == "Publication") - this.updateCountsUnion(multiColumnCount); - else - { - this.cellSets[cellSetType] = true; - if (this.cellSets.Visibility && this.cellSets.Default) - this.updateCountsUnion(multiColumnCount); - } - }, - - /* handle query response to update all the member counts with all filters applied */ - updateCountsUnion : function (multiColumnCount) - { - var cellSet = {cells: this.cellSetCells}; - var facet, member, f, m; - // map from hierarchyName to facet - var map = {}; - var facetStore = this; - var facetMembersStore = Ext4.getStore(this.cubeConfig.objectName + "FacetMembers"); - var objectStore = Ext4.getStore(this.cubeConfig.objectName); - facetMembersStore.suspendEvents(false); - - for (f = 0; f < facetStore.count(); f++) - { - facet = this.getAt(f); - map[facet.data.hierarchy.uniqueName] = facet; - if (facet.get("name") == this.cubeConfig.objectName) { - facet.data.selectedMembers = []; - } - } - - this.updateCountsZero(); - // var positions = this.getAxisPositions(cellSet, 1); - var positions = this.cellSetPositions.map(function(inner){return inner[0]}); - var data; - if (!objectStore.unfilteredCount) - objectStore.setUnfilteredCount(); - if (multiColumnCount) - data = this.getMultiColumnData(cellSet); - else - data = this.getDataOneColumn(cellSet, 0); - - var max = 0; - var selectedMembers = {}; - for (var i = 0; i < positions.length; i++) - { - var resultMember = positions[i]; - var hierarchyName = resultMember.level.hierarchy.uniqueName; - - facet = map[hierarchyName]; - - var isCubeObjectCount = facet.get("name") == this.cubeConfig.objectName; - // a bit of hackery here because cube objects that are present in the counts should become selected members - // even if the countField value is 0. We return -1 in the data to indicate selected but with a 0 count. - // So if this member is a cube object, we make a negative count positive (1) and otherwise we make the count 0. - var count = (data[i] >= 0 ? data[i] : isCubeObjectCount ? 1 : 0); - member = facetMembersStore.getById(resultMember.uniqueName); - if (isCubeObjectCount) - { - if (count > 0) - { - selectedMembers[resultMember.name] = resultMember; - facet.data.selectedMembers.push(resultMember); - } - } - else if (!member) - { - if (-1 == resultMember.uniqueName.indexOf("#") && "(All)" != resultMember.name) - console.log("member not found: " + resultMember.uniqueName); - } - else if (facet.get("displayFacet")) - { - member.set("count", count); - if (count > max) - max = count; - if (!member.data.unfilteredCount) - member.set("unfilteredCount", count); - } - } - - for (f = 0; f < facetStore.count(); f++) - { - facet = facetStore.getAt(f); - if (facet.data.hierarchy.uniqueName !== this.cubeConfig.filterByFacetUniqueName) - { - var facetTotal = 0; - for (m = 0; m < facet.data.members.length; m++) - { - member = facetMembersStore.getById(facet.data.members[m].uniqueName); - if (facet.get("displayFacet")) - { - if (objectStore) - { - member.set("unfilteredPercent", objectStore.unfilteredCount == 0 ? 0 : 100 * member.data.unfilteredCount / objectStore.unfilteredCount); - member.set("percent", objectStore.unfilteredCount == 0 ? 0 : (100.0 * member.data.count) / objectStore.unfilteredCount); - } - else // not sure this is necessary - { - member.set("unfilteredPercent", 100 * member.data.unfilteredCount / max); - member.set("percent", max == 0 ? 0 : (100.0 * member.data.count) / max); - } - } - } - } - if (!facet.data.allMemberCount) - facet.set("allMemberCount", facetTotal); - } - - facetMembersStore.resumeEvents(); - facetMembersStore.fireEvent("refresh"); - - this.updateMemberFilter(selectedMembers); - - //this.saveFilterState(); - //this.updateContainerFilter(); - //if (!isSavedGroup) - // this.changeSubjectGroup(); - - this.fireEvent("countsUpdated"); - LABKEY.Utils.signalWebDriverTest('dataFinder' + this.cubeConfig.objectName + 'CountsUpdated'); - }, - - updateMemberFilter : function(selectedMembers) { - var store = Ext4.getStore(this.cubeConfig.objectName); - store.updateFacetFilters(selectedMembers); - }, - - getAxisPositions : function(cellSet, axisIndex) - { - var positions = cellSet.axes[axisIndex].positions; - if (positions.length > 0 && positions[0].length > 1) - { - console.log("warning: axis has nested members"); - throw "illegal state"; - } - return positions.map(function(inner){return inner[0]}); - }, - - getData : function(cellSet,defaultValue) - { - var cells = cellSet.cells; - return cells.map(function(row) - { - return row.map(function(col){return col.value ? col.value : defaultValue;}); - }); - }, - - getMultiColumnData : function(cellSet) - { - var columnPositions = this.getAxisPositions(cellSet, 0); - var cells = cellSet.cells; - var objectStore = Ext4.getStore(this.cubeConfig.objectName); - return cells.map(function(row) - { - var isSelected = false; - var sum = 0; - for (var i = 0; i < row.length; i++) - { - var object = objectStore.getById(columnPositions[i].name); - if (object && object.get(this.cubeConfig.countField) !== undefined) - { - isSelected = isSelected || row[i].value > 0; // we want members to be selected from the filter even if their countField is 0 - sum += row[i].value * object.get(this.cubeConfig.countField) - } - else - { - console.log("no object in store with id " + columnPositions[i].name); - } - } - // return -1 to indicate that the member was selected, even though the sum of count - // field was 0 (because, for example, there may be operational studies (cube objects) that have 0 - // participants (count field) currently) - return sum > 0 ? sum : (isSelected ? -1 : 0); - }, this); - }, - - getDataOneColumn : function(cellSet,defaultValue) - { - var cells = cellSet.cells; - if (cells.length > 0 && cells[0].length > 1) - { - console.log("warning cellSet has more than one column"); - throw "illegal state"; - } - return cells.map(function(row) - { - return row[0].value ? row[0].value : defaultValue; - }); - }, - - constructor: function(config) - { - this.proxy.url = LABKEY.ActionURL.buildURL(config.dataModuleName, "Facets.api", LABKEY.containerPath, {objectName: config.cubeConfig.objectName}); - this.callParent([config]); - } - -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Phase.js b/resources/web/study/Finder/data/Phase.js deleted file mode 100644 index f09a9743..00000000 --- a/resources/web/study/Finder/data/Phase.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.Phase', { - extend: 'Ext.data.Model', - - idProperty: 'phase', - - fields: [ - {name: 'phase'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Publication.js b/resources/web/study/Finder/data/Publication.js deleted file mode 100644 index ed90c357..00000000 --- a/resources/web/study/Finder/data/Publication.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.Publication', { - extend: 'Ext.data.Model', - - idProperty: 'id', - - - proxy : { - type: "ajax", - //url: set before calling "load". - reader: { - type: 'json', - root: 'data' - } - }, - - fields : [ - {name: 'id', type:'int'}, - {name: 'studyId'}, - {name: 'pmid'}, // PubMed Id - {name: 'pmcid'}, // PubMed Central reference number - {name: 'doi'}, // Digital Object Identifier - {name: 'author'}, - {name: 'authorAbbrev'}, - {name: 'issue'}, - {name: 'journal'}, - {name: 'pages'}, - {name: 'title'}, - {name: 'year'}, - {name: 'citation'}, - {name: 'status'}, - {name: 'url'}, - {name: 'dataUrl'}, - {name: 'studies'}, - {name: 'publicationType'}, - {name: 'abstractText'}, - {name: 'keywords'}, - {name: 'thumbnails'}, - {name: 'keywords'}, - {name: 'urls'}, - {name: 'isSelected', type: 'boolean', defaultValue: true}, - {name: 'isSelectedBySearch', type: 'boolean', defaultValue: false}, - {name: 'isHighlighted', type: 'boolean', defaultValue: false}, - {name: 'viewState', type: 'string', defaultValue: 'collapsed'} - ] - -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/PublicationType.js b/resources/web/study/Finder/data/PublicationType.js deleted file mode 100644 index 28e19ba0..00000000 --- a/resources/web/study/Finder/data/PublicationType.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.PublicationType', { - extend: 'Ext.data.Model', - - idProperty: 'PublicationType', - - fields: [ - {name: 'PublicationType'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Status.js b/resources/web/study/Finder/data/Status.js deleted file mode 100644 index b7c65b4c..00000000 --- a/resources/web/study/Finder/data/Status.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.Status', { - extend: 'Ext.data.Model', - - idProperty: 'status', - - fields: [ - {name: 'status'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Study.js b/resources/web/study/Finder/data/Study.js deleted file mode 100644 index 1817bda8..00000000 --- a/resources/web/study/Finder/data/Study.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.Study', { - extend: 'Ext.data.Model', - - idProperty : 'studyId', - - proxy : { - type: "ajax", - //url: set before calling "load". - reader: { - type: 'json', - root: 'data' - } - }, - - fields: [ - {name: 'studyId'}, - {name: 'title'}, - {name: 'url'}, - {name: 'externalUrl'}, - {name: 'externalUrlDescription'}, - {name: 'shortName'}, - {name: 'iconUrl'}, - {name: 'investigator'}, - {name: 'visibility'}, - {name: 'isPublic', type: 'boolean'}, - {name: 'abstractCount', type: 'int'}, - {name: 'manuscriptCount', type: 'int'}, - {name: 'participantCount', type: 'int'}, - {name: 'isSelected', type: 'boolean', defaultValue: true}, - {name: 'isSelectedBySearch', type: 'boolean', defaultValue: false}, - {name: 'isHighlighted', type: 'boolean', defaultValue: false}, - {name: 'isBorderHighlighted', type: 'boolean', defaultValue: false}, - {name: 'studyAccessList'} - ], - - associations: [ - { - type: 'hasMany', - model: 'LABKEY.study.data.StudyAccess', - name: 'studyAccessList', - associationKey: 'studyAccessList' - } - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/StudyAccess.js b/resources/web/study/Finder/data/StudyAccess.js deleted file mode 100644 index 99d4098c..00000000 --- a/resources/web/study/Finder/data/StudyAccess.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.StudyAccess', { - extend: 'Ext.data.Model', - - idProperty : 'key', - - fields: [ - {name: 'key'}, - {name: 'displayName'}, - {name: 'studyId'}, - {name: 'studyContainerPath'}, - {name: 'visibility'} - ] - -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/StudyType.js b/resources/web/study/Finder/data/StudyType.js deleted file mode 100644 index 90358025..00000000 --- a/resources/web/study/Finder/data/StudyType.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.StudyType', { - extend: 'Ext.data.Model', - - idProperty: 'studyType', - - fields: [ - {name: 'studyType'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/SubmissionStatus.js b/resources/web/study/Finder/data/SubmissionStatus.js deleted file mode 100644 index d961fc10..00000000 --- a/resources/web/study/Finder/data/SubmissionStatus.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.SubmissionStatus', { - extend: 'Ext.data.Model', - - idProperty: 'status', - - fields: [ - {name: 'Status'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Subset.js b/resources/web/study/Finder/data/Subset.js deleted file mode 100644 index 13a881b4..00000000 --- a/resources/web/study/Finder/data/Subset.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.Subset', { - extend: 'Ext.data.Model', - - fields: [ - {name: 'id'}, - {name: 'name'}, - {name: 'isDefault', type:'boolean'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Subsets.js b/resources/web/study/Finder/data/Subsets.js deleted file mode 100644 index db4caf01..00000000 --- a/resources/web/study/Finder/data/Subsets.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.store.Subsets', { - extend: "Ext.data.Store", - autoLoad: true, - model: 'LABKEY.study.data.Subset', - defaultValue: null, - proxy: { - type: 'ajax', - //url: set in constructor below - reader: { - type: 'json', - root: 'data' - } - }, - listeners: { - 'load' : { - fn : function(store, records, options) { - for (var i = 0; i < records.length; i++) - { - if (records[i].data.isDefault) - { - store.defaultValue = records[i]; - break; - } - } - }, - scope: this - } - }, - - constructor: function(config) { - this.proxy.url = LABKEY.ActionURL.buildURL(config.dataModuleName, "subsets.api", config.cubeContainerPath, {objectName: config.objectName}); - this.callParent(config); - } -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/TherapeuticArea.js b/resources/web/study/Finder/data/TherapeuticArea.js deleted file mode 100644 index 9997bc11..00000000 --- a/resources/web/study/Finder/data/TherapeuticArea.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.TherapeuticArea', { - extend: 'Ext.data.Model', - - idProperty: 'therapeuticArea', - - fields: [ - {name: 'therapeuticArea'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/data/Visibility.js b/resources/web/study/Finder/data/Visibility.js deleted file mode 100644 index 486086f8..00000000 --- a/resources/web/study/Finder/data/Visibility.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 - */ -Ext4.define('LABKEY.study.data.Visibility', { - extend: 'Ext.data.Model', - - idProperty: 'visibility', - - fields: [ - {name: 'visibility'} - ] -}); \ No newline at end of file diff --git a/resources/web/study/Finder/dataFinder.css b/resources/web/study/Finder/dataFinder.css deleted file mode 100644 index 6731243e..00000000 --- a/resources/web/study/Finder/dataFinder.css +++ /dev/null @@ -1,812 +0,0 @@ -.labkey-data-finder-view -{ - padding-left: 2px; -} - -.labkey-data-finder-view .x4-tab -{ - border-bottom-width: 0; -} - -.labkey-data-finder-view .x4-tab-bar-strip -{ - height: 0; - border-top-width: 0; -} - -.labkey-data-finder-view .x4-tab-default-top-active -{ - background-image: -webkit-linear-gradient(top,rgb(240, 240, 240),rgb(240, 240, 240)); -} - -.labkey-data-finder-view .x4-tab-bar-body-default-top { - padding-bottom: 0; -} - -.labkey-data-finder-card-deck-view .x4-panel-body-default { - border-top-width: 0; -} - -.labkey-data-finder-editor -{ - padding: 5px; -} - -.labkey-combo-disabled .x4-form-item-body -{ - opacity: 0.7 -} - -.labkey-combo-disabled .x4-trigger-cell -{ - display: none; -} - -.labkey-data-finder-editor-message -{ - padding-bottom: 20px; -} - -.labkey-field-editor .x4-field-label-cell -{ - padding-left: 5px; - background-color: #E7EFF4; -} - -.labkey-studies-panel, -.labkey-publications-panel -{ - padding: 0; -} - -.labkey-finder-object-selection { - padding-left: 10px; -} - -.labkey-studies-panel { - background-color: #ffffff -} - -DIV.labkey-data-finder-outer { - min-height:100px; - min-width:400px; -} - -.labkey-data-finder-view -{ - height: 500px; -} - -DIV.labkey-study-cards { - background-color: #ffffff -} - -DIV.labkey-finder-facets { - background-color: #ffffff -} - -.labkey-data-finder-inner -{ - background-color:#ffffff; - height:100%; - width:100%; -} - -.labkey-data-finder-outer .x4-border-layout-ct -{ - background-color:#ffffff; -} - - -/* labkey-study-card */ -.labkey-study-card-header -{ - color:black; - font-variant:small-caps; -} - -.labkey-publication-goto, -.labkey-study-card-summary, -.labkey-study-card-accession -{ - float:left; -} - -.labkey-study-card-goto, -.labkey-study-card-pi, -.labkey-study-card-short-name -{ - float:right; -} - -.labkey-publication-title, -.labkey-study-card-divider, -.labkey-study-card-title, -.labkey-study-card-description -{ - clear:both; -} - -.labkey-study-card-publications -{ - margin-top: 10px; -} - -.labkey-text-link.labkey-study-card-abstract-count, -.labkey-text-link.labkey-study-card-manuscript-count -{ - float:right; - margin-bottom: 1px; - padding-top : 2px; -} - -.labkey-study-card.labkey-study-border-highlight -{ - /*border: 2px solid #126495;*/ -} - -DIV.labkey-study-card -{ - background-color:#F8F8F8; - border:1pt solid #AAAAAA; - padding: 5pt; - margin: 5pt; - float:left; - position:relative; - width:171pt; - height:140pt; - overflow-y:hidden; -} - -.labkey-study-card.labkey-publication-highlight -{ - background-color:rgba(81, 158, 218, 0.2); -} - -.labkey-publication-highlight1 -{ - border: 1pt solid #AAAAAA; -} - -.labkey-publication-highlight3 -{ - background-color:rgba(81, 158, 218, 0.1); -} - -TABLE.labkey-study-search -{ - margin: 5px 0 0 5px; -} - -SPAN.labkey-study-search -{ - position: relative; -} - -/* search area */ -TD.labkey-search-box -{ - float:left; - padding:10pt; -} -INPUT.labkey-search-box -{ - width:200pt; -} - -DIV.labkey-search-message -{ - padding-left:10pt; -} - -.labkey-facet-selection-panel -{ - padding: 0 0 5px 0; - vertical-align: text-top; - height:100%; - background-color: white; -} - -.labkey-facet-selection-panel > DIV -{ - height:100%; - overflow-y:auto; -} - -DIV.labkey-facet-summary LI.labkey-facet-member -{ - clear:both; - padding:2pt; - margin:1px 0 1px 0; - border:1px solid #ffffff; - height:14pt; - position:relative; - width:100%; -} - -/* facets */ - -.labkey-clear-all -{ - float: right; - background-color: rgb(240, 240, 240); - background-image: none; - color: black; - border: none; -} - -.labkey-clear-all .x4-btn-inner { - text-transform: none; - font-weight: normal; - font-size:100%; -} - -.labkey-clear-all.inactive -{ - cursor: default; -} - -.labkey-clear-all.inactive .x4-btn-inner -{ - color: grey; - cursor:default; -} - -.labkey-clear-all.active .x4-btn-inner -{ - color:black; -} - -.labkey-clear-filter -{ - float:right; -} - -DIV.labkey-facet-summary -{ - border-bottom : solid 1px rgb(124,124,124); - border-left: solid 1px rgb(180, 180, 180); - border-right: solid 1px rgb(180, 180, 180); - border-top-width:0; -} - -.active -{ - cursor:pointer; -} - -.labkey-facet-summary-header -{ - border-left-style: solid; - border-left-width: 1px; - border-left-color: rgb(180, 180, 180); -} - -.labkey-facet-header -{ - padding:4pt; - background-color: rgb(240, 240, 240); -} - -.labkey-facet-header -{ - padding-left: 10px; -} - -.labkey-finder-card-header -{ - padding: 3px 8px 3px 8px; -} - -.labkey-finder-card-header, -.labkey-facet-header .x4-panel-body-default -{ - background-color: rgb(240, 240, 240); - border:none; -} - -.labkey-facet-header .labkey-facet-caption -{ - font-weight:400; - padding-bottom: 10px; -} - -.labkey-facet-name -{ - float:left -} - -.labkey-facet-header .labkey-filter-options -{ - font-size: 11px; - padding: 3px 0 0 20px; -} - -.labkey-finder-facets .x4-grid-row { - border-width: 0; -} - -.labkey-finder-facets .x4-grid-row-selected .x4-grid-td { - background-color: white; - background-image: none; -} - -.labkey-finder-facets .x4-grid-row-selected .labkey-facet-unfilteredPercent-bar, -.labkey-finder-facets .x4-grid-row-selected .labkey-facet-percent-bar { - background-color: rgba(81, 158, 218, 0.2); -} - -.labkey-finder-facets .x4-grid-cell-special .x4-grid-cell-inner { - background-color: white; - background-image: none; -} - -.labkey-finder-facets .x4-grid-group-hd { - background-color:rgb(240, 240, 240); - border-color: #7c7c7c; - border-bottom-width:1px; -} - -.labkey-finder-facets .x4-grid-cell-special { - border-right-width: 0; -} - -.labkey-finder-facets .x4-grid-group-hd .x4-grid-group-title { - background-position: left 28% -} - -.labkey-facet-header .inactive -{ - cursor:default; - color:grey; -} - -.labkey-facet-header .active -{ - display:inline-block; -} - -DIV.labkey-facet-summary UL -{ - list-style-type:none; - padding-left:0; - padding-right:5pt; - margin:0; -} - -DIV.labkey-facet-summary LI.labkey-facet-member -{ - clear:both; - cursor:default; - padding:2pt; - margin:1px 0 1px 0; - border:1px solid #ffffff; - height:14pt; - position:relative; - width:100%; -} - -.labkey-facet-summary .labkey-facet-member-name -{ - padding-left: 8px; -} - -SPAN.labkey-facet-member:hover SPAN.labkey-facet-unfilteredPercent-bar, -SPAN.labkey-facet-member:hover SPAN.labkey-facet-percent-bar -{ - background-color:rgba(81, 158, 218, 0.2); -} - -.labkey-empty-member -{ - color:#888888; -} - -LI.labkey-facet-member .member-indicator -{ - position:relative; - display:inline-block; - width:16px; - z-index:2; -} - -SPAN.labkey-facet-member .labkey-facet-member-name, -LI.labkey-facet-member .labkey-facet-member-name -{ - float: left; - padding-bottom: 3px; - display:inline-block; - position:relative; - max-width:150pt; - overflow-x: hidden; - overflow-y: hidden; - text-overflow: ellipsis; - white-space: nowrap; - z-index:2; -} - -SPAN.labkey-facet-member .labkey-facet-member-count, -LI.labkey-facet-member .labkey-facet-member-count -{ - position:relative; - float:right; - z-index:2; - padding-right: 8px; - font-size: 90%; -} - -.x4-grid-row-selected SPAN.labkey-facet-unfilteredPercent-bar, -.x4-grid-row-selected SPAN.labkey-facet-percent-bar -{ - background-color:rgba(81, 158, 218, 0.2); -} - -.labkey-finder-facets .x4-grid-view -{ - overflow: hidden; -} - -.labkey-finder-facets .x4-grid-row-focused .x4-grid-td, -.labkey-finder-facets .x4-grid-row-before-focused .x4-grid-td, -.labkey-finder-facets .x4-grid-cell -{ - border-bottom-style: hidden; - border-top-style: hidden; - background-image: none; - background-color: white; -} - - -.x4-grid-row SPAN.labkey-facet-percent-bar, -.x4-grid-row SPAN.labkey-facet-unfilteredPercent-bar -{ - height:14pt; - position:absolute; right:0; - z-index:0; - padding-right: 8px; -} - -.x4-grid-row SPAN.labkey-facet-percent-bar -{ - background-color:rgba(200,200,200,0.3); -} - -.x4-grid-row SPAN.labkey-facet-unfilteredPercent-bar -{ - background-color:rgba(222,222,222,0.3); -} - -/* filter type popup */ -DIV.labkey-filter-popup -{ - position: absolute; - min-width: 80px; - margin: 2px 0 0; - list-style: none; - background-color: white; - border-color:#b4b4b4; - border-width: 1px; - border-style: solid; - box-shadow: rgb(136, 136, 136) 0 0 6px; - z-index: 100; - -webkit-padding-start: 0 -} - - -.labkey-filter-popup > .labkey-dropdown-menu -{ - display: block; - min-width: 130px; -} - -/* menus */ - -.labkey-filter-options > span.inactive -{ - color: black; - cursor: default; -} - -.labkey-study-detail .x4-window-header { - background-color: #F8F8F8; - padding: 0; - box-shadow: #F8F8F8 0 0 0 0; -} - -.labkey-study-detail { - border-color: #F8F8F8; - padding: 0; -} - -.labkey-study-details .labkey-study-links { - padding: 5px 0 5px 10px; -} - -div.labkey-study-details { - background-color: white; - padding: 10px 20px 20px 20px; - margin-top: 10px; - box-shadow: 0 1px 1px rgba(0,0,0,0.15), -1px 0 0 rgba(0,0,0,0.06), 1px 0 0 rgba(0,0,0,0.06), 0 1px 0 rgba(0,0,0,0.12); -} - -div.labkey-study-details h2.labkey-study-accession { - display: inline-block; - text-transform: uppercase; - font-weight: normal; - background-color: #126495; - color: white; - font-size: 13px; - padding: 9px 20px 7px 20px; - margin-top: -20px; - margin-left: -20px; -} - -div.labkey-study-details h2.labkey-study-short-name { - display: inline-block; - text-transform: uppercase; - font-weight: normal; - background-color: #126495; - color: white; - font-size: 13px; - padding: 9px 20px 7px 20px; - float: right; - margin-right: -20px; - margin-top: -10px; -} - - -div.labkey-study-details h3 { - text-transform: uppercase; - font-size: 14px; - font-weight: normal; - padding: 10px 0 10px 50px; - border-bottom: 1px solid darkgray; -} - -#labkey-study-details-content { - padding-top: 1em; -} - -#labkey-study-details-content .detail { - font-size: 15px; - padding-left: 30px; - padding-bottom: 5px; -} - -#labkey-study-details-content .detail div { - font-size: 15px; -} - -#labkey-study-details-content h3 { - margin-bottom: 0.5em; - margin-top: 0.5em; -} - -#labkey-study-details-content div { - padding: 3px; -} - -#labkey-study-details-content div.label, a.label { - font-size: 12px; - color: #a9a9a9; - vertical-align: text-top; -} - -#labkey-study-assays-content .detail div { - font-size: 15px; - padding: 3px; -} - -.labkey-study-publication-header { - color: #cc541f; - font-size: 120%; - font-variant: small-caps; -} - -.labkey-publication-journal { - font-size:80%; - text-decoration:underline; -} - -.labkey-publication-year { - font-size:80%; -} - -.labkey-publication-title { - font-weight: bold; -} - - - -.labkey-publication-author { - font-style: italic; -} - -.labkey-publication-cards .x4-box-inner { - overflow: auto; -} - -.fa-plus-square:hover, -.fa-minus-square:hover -{ - cursor: pointer; -} - -.labkey-publication-card.collapsed #fullAuthorList { - display: none; -} - -.labkey-publication-card.expanded #abbreviatedAuthorList { - display: none; -} - -.labkey-publication-card.expanded #fullAuthorList { - display: inherit; -} - -.labkey-publication-card.collapsed .fa-plus-square -{ - position:absolute; - float:left; - display:inherit; -} -.labkey-publication-card.collapsed .fa-minus-square -{ - display:none; -} - -.labkey-publication-card.expanded .fa-plus-square -{ - display:none; -} -.labkey-publication-card.expanded .fa-minus-square -{ - position:absolute; - float:left; - display:inline; -} - -.labkey-publication-detail -{ - clear: left; -} - -.labkey-publication-detail.collapsed -{ - display:none; -} - -.labkey-publication .labkey-figures-list -{ - padding-left: 0px; - padding-top: 10px; - list-style: none; -} - -.labkey-figures-list .labkey-figure -{ - display: inline; -} - -.labkey-publication-detail-label -{ - color: #3495D2; -} - -.labkey-figure img -{ - max-height: 100px; - box-shadow: 1px 1px 2px 2px #ccc; - margin-right: 8px; - margin-bottom: 8px; -} - -#labkey-publication-details-content, -.labkey-publication-card .labkey-publication-author, -.labkey-publication-card .labkey-publication-title, -.labkey-publication-card .labkey-publication-goto, -.labkey-publication-card .labkey-publication-goto:hover, -.labkey-publication-card .labkey-publication-citation { - padding-left: 20px; - clear: both; -} - -.labkey-publication-card .labkey-publication-title, -.labkey-publication-card .labkey-publication-citation { - padding-bottom:5px; - -} - -.labkey-publication-card -{ - border-bottom:1px solid #AAAAAA; - padding: 5pt; - margin: 5pt; - float:left; - position:relative; - width:90%; - overflow-y:hidden; -} - -.labkey-publication-card .labkey-publication-data-link -{ - float: left; - position: absolute; - bottom: 7px; -} - - -.labkey-publication-detail .labkey-publication-studies, -.labkey-publication-detail .labkey-publication-data-link, -.labkey-publication-detail .labkey-publication-links, -.labkey-publication-detail .labkey-publication-keywords, -.labkey-publication-detail .labkey-publication-abstract -{ - padding-top: 8px; -} - -.labkey-publication-detail .labkey-publication-abstract -{ - padding-bottom: 5px -} - -.labkey-finder-label -{ - font-weight: bold; -} - -.labkey-data-link-icon -{ - display: none; -} - -.labkey-goto-link-icon -{ - display: none; -} - -.labkey-publication-content -{ - /* Firefox */ - width: -moz-calc(100% - 180px); - /* WebKit */ - width: -webkit-calc(100% - 180px); - /* Standard */ - width: calc(100% - 180px); - - display: inline-block; -} - -.labkey-publication-annotation { - padding-left: 10px; - vertical-align: top; - display:inline-block; - height:100%; -} - -.labkey-publication-type { - vertical-align: top; -} - -.labkey-publication-status{ - color:limegreen; - vertical-align: top; -} - -/* ugly hack to overlay arrow background image, see .x4-btn-default-small */ -a.labkey-button-link, a.labkey-button-link:visited { - background-image: none; - background-image: url(../../_images/arrow_right.png); /* for IE */ - background-image: url(../../_images/arrow_right.png), -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #e4e4e4)); - background-image: url(../../_images/arrow_right.png), -webkit-linear-gradient(top, #ffffff, #e4e4e4); - background-image: url(../../_images/arrow_right.png), -moz-linear-gradient(top, #ffffff, #e4e4e4); - background-image: url(../../_images/arrow_right.png), -o-linear-gradient(top, #ffffff, #e4e4e4); - background-image: url(../../_images/arrow_right.png), linear-gradient(top, #ffffff, #e4e4e4); - background-repeat: no-repeat, repeat; - background-position: right; - background-position-y: 0; - padding: 2px 14px 2px 2px; - vertical-align: middle; - white-space: nowrap; - top: 1px; -} diff --git a/resources/web/study/Finder/dataFinder.lib.xml b/resources/web/study/Finder/dataFinder.lib.xml deleted file mode 100644 index 02c3df0b..00000000 --- a/resources/web/study/Finder/dataFinder.lib.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - diff --git a/src/org/labkey/trialshare/view/dataFinder.jsp b/src/org/labkey/trialshare/view/dataFinder.jsp deleted file mode 100644 index 1a97e485..00000000 --- a/src/org/labkey/trialshare/view/dataFinder.jsp +++ /dev/null @@ -1,54 +0,0 @@ -<% -/* - * Copyright (c) 2015-2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -%> -<%@ page import="com.fasterxml.jackson.databind.ObjectMapper" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.trialshare.TrialShareController" %> -<%@ page extends="org.labkey.api.jsp.JspBase"%> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - dependencies.add("study/Finder/datafinder"); - } -%> -<% - JspView me = (JspView) HttpView.currentView(); - TrialShareController.FinderBean bean = me.getModelBean(); - - ObjectMapper jsonMapper = new ObjectMapper(); -%> - - - - -
-
diff --git a/src/org/labkey/trialshare/view/manageData.jsp b/src/org/labkey/trialshare/view/manageData.jsp deleted file mode 100644 index 2a112cb7..00000000 --- a/src/org/labkey/trialshare/view/manageData.jsp +++ /dev/null @@ -1,54 +0,0 @@ -<% - /* - * Copyright (c) 2016-2019 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -%> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.trialshare.TrialShareController" %> -<%@ page import="org.springframework.web.servlet.ModelAndView" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase"%> -<% - JspView me = (JspView) HttpView.currentView(); - TrialShareController.CubeObjectNameForm bean = me.getModelBean(); - TrialShareController.ObjectName thisObjectName = bean.getObjectName(); - ModelAndView manageObjectsView = me.getView("manageObjectsView"); - -%> - -

- You can insert, edit, or delete <%=h(thisObjectName.getPluralName().toLowerCase())%> from this page. - Remember to refresh the cube when you are ready for your changes to show on the data finder. -

-

-<% - for (TrialShareController.ObjectName objectName : TrialShareController.ObjectName.values()) - { - if (objectName != thisObjectName) - { - out.println(link(" Manage " + objectName.getPluralName(), urlFor(TrialShareController.ManageDataAction.class) - .addParameter("objectName", objectName.toString()) - .addParameter("query.viewName", "manageData"))); - } - } -%> -

-<% - if (manageObjectsView != null) - { - me.include(manageObjectsView, out); - } -%> \ No newline at end of file diff --git a/src/org/labkey/trialshare/view/publicationDetails.jsp b/src/org/labkey/trialshare/view/publicationDetails.jsp deleted file mode 100644 index ec70ad57..00000000 --- a/src/org/labkey/trialshare/view/publicationDetails.jsp +++ /dev/null @@ -1,56 +0,0 @@ -<% -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -%> -<%@ page import="org.json.JSONObject" %> -<%@ page import="org.labkey.api.util.JavaScriptFragment" %> -<%@ page import="org.labkey.api.util.UniqueID" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.trialshare.TrialShareController" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase"%> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - dependencies.add("study/Finder/dataFinderEditor"); - } -%> -<% - TrialShareController.CubeObjectDetailForm bean = ((JspView) HttpView.currentView()).getModelBean(); - - String renderId = "publication-details-" + UniqueID.getRequestScopedUID(HttpView.currentRequest()); - - JavaScriptFragment cubeObjectJson = bean.getCubeObject() == null ? JavaScriptFragment.NULL : new JSONObject(bean.getCubeObject()).getJavaScriptFragment(2); -%> - - -
- - \ No newline at end of file diff --git a/src/org/labkey/trialshare/view/studyDetail.jsp b/src/org/labkey/trialshare/view/studyDetail.jsp deleted file mode 100644 index 5ad1d9a9..00000000 --- a/src/org/labkey/trialshare/view/studyDetail.jsp +++ /dev/null @@ -1,147 +0,0 @@ -<% -/* - * Copyright (c) 2015-2019 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -%> -<%@ page import="org.apache.commons.lang3.StringUtils" %> -<%@ page import="org.labkey.api.util.HtmlString" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.trialshare.TrialShareController" %> -<%@ page import="org.labkey.trialshare.data.StudyBean" %> -<%@ page import="org.labkey.trialshare.data.StudyPersonnelBean" %> -<%@ page import="org.labkey.trialshare.data.StudyPublicationBean" %> -<%@ page import="org.labkey.trialshare.data.URLData" %> -<%@ page import="java.net.URL" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - dependencies.add("study/Finder/dataFinder.css"); - dependencies.add("study/Finder/trialShare.css"); - } -%> -<% - JspView me = (JspView) HttpView.currentView(); - - TrialShareController.StudyDetailBean studyDetail = me.getModelBean(); - StudyBean study = studyDetail.getStudy(); - String description = study.getDescription(); - final HtmlString descriptionHTML; - if (StringUtils.isEmpty(description)) - descriptionHTML = h(study.getBriefDescription()); - else - descriptionHTML = HtmlString.unsafe(description); - - String studyUrl = study.getUrl(getUser()); -%> - -
-

<% if (null!=studyUrl) {%><%}%><%=h(study.getStudyId())%><% if (null!=studyUrl) {%><%}%>

-

<% if (null!=study.getShortName()) {%><%}%><%=h(study.getShortName())%><% if (null!=study.getShortName()) {%><%}%>

-
-<% if (null != study.getIconUrl()) {%><%}%> -

<%=h(study.getTitle())%>

-
<% - - if (null != study.getPersonnel()) - { - for (StudyPersonnelBean p : study.getPersonnel()) - { - if ("Principal Investigator".equals(p.getRole_in_study())) - { - %>
- <%=h(p.getHonorific())%> <%=h(p.getFirst_name())%> <%=h(p.getLast_name())%> - <%=h(p.getOrganization())%> -
<% - } - } - } - %><% - if (studyDetail.getDetailType() == TrialShareController.DetailType.study) - { - %>
<%=descriptionHTML%>
- <% - } - %> - - <% if (null != studyUrl || null != study.getExternalURL()) - { %> - - <% } %> -
-
- <% - - if (null != study.getPublications() && study.getPublications().size() > 0) - { - %><%=h(studyDetail.getDetailType().getSectionHeader())%><% - for (StudyPublicationBean pub : study.getPublications()) - { - if (pub.getTitle() != null) - { - %>

<%=h(pub.getJournal())%> <%=h(pub.getYear())%>
<% - %><%=h(pub.getTitle())%><% - if (!StringUtils.isEmpty(pub.getCitation())) - { - %>
<%=h(pub.getCitation())%><% - } - %><% - if (!StringUtils.isEmpty(pub.getAuthor())) - { - %>
<%=h(pub.getAuthor())%><% - } - %><% - if (!StringUtils.isEmpty(pub.getPMID())) - { - %>
<%=link("PubMed").href("http://www.ncbi.nlm.nih.gov/pubmed/?term=" + pub.getPMID()).target("_blank")%><% - } - for (URLData urlData : pub.getUrls()) - { - if (urlData != null && !StringUtils.isEmpty(urlData.getLink())) - { - %>
<%=link(urlData.getLinkText()).href(urlData.getLink()).target("_blank")%><% - } - } - %>

<% - } - } - } - %>
-
- -
-
- - diff --git a/src/org/labkey/trialshare/view/studyDetails.jsp b/src/org/labkey/trialshare/view/studyDetails.jsp deleted file mode 100644 index 7a776703..00000000 --- a/src/org/labkey/trialshare/view/studyDetails.jsp +++ /dev/null @@ -1,73 +0,0 @@ -<% -/* - * Copyright (c) 2016 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -%> -<%@ page import="org.json.JSONArray" %> -<%@ page import="org.json.JSONObject" %> -<%@ page import="org.labkey.api.util.JavaScriptFragment" %> -<%@ page import="org.labkey.api.util.UniqueID" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.trialshare.TrialShareController" %> -<%@ page import="org.labkey.trialshare.data.StudyAccess" %> -<%@ page import="org.labkey.trialshare.data.StudyBean" %> -<%@ page import="java.util.List" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase"%> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - dependencies.add("study/Finder/dataFinderEditor"); - } -%> -<% - JspView me = (JspView) HttpView.currentView(); - TrialShareController.CubeObjectDetailForm bean = me.getModelBean(); - - String renderId = "study-details-" + UniqueID.getRequestScopedUID(HttpView.currentRequest()); - JavaScriptFragment cubeObjectJson = bean.getCubeObject() == null ? JavaScriptFragment.NULL : new JSONObject(bean.getCubeObject()).getJavaScriptFragment(2); - List accessList = bean.getCubeObject() == null ? null :((StudyBean) bean.getCubeObject()).getStudyAccessList(); - - JSONArray jsonArray = new JSONArray(); - if (accessList != null) - { - for (StudyAccess access : accessList) - { - jsonArray.put(new JSONObject(access)); - } - } - - JavaScriptFragment studyaccesslist = jsonArray.getJavaScriptFragment(2); -%> - -
- - \ No newline at end of file diff --git a/test/sampledata/DataFinder.lists.zip b/test/sampledata/DataFinder.lists.zip deleted file mode 100644 index 7b256988e571a6e05bc49fa770387109bf38631c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14300 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 diff --git a/test/sampledata/DataFinderTestOperationalStudy.folder.zip b/test/sampledata/DataFinderTestOperationalStudy.folder.zip deleted file mode 100644 index 62f98dbec87c878ac6c01d3cedaf3d3b437012cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13423 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 F{|AKjsb&BG diff --git a/test/sampledata/DataFinderTestPublicStudy.folder.zip b/test/sampledata/DataFinderTestPublicStudy.folder.zip deleted file mode 100644 index 89d0b6af873c07358da3aa87e7f6b02bc8eebf06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13393 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 getPapers() - { - List paperEls = Locator.css(".labkey-study-papers > p").findElements(_panel); - List papers = new ArrayList<>(); - - for (WebElement el : paperEls) - { - papers.add(new Paper(el)); - } - - return papers; - } - - protected Elements elements() - { - return new Elements(); - } - - protected class Elements extends ComponentElements - { - @Override - protected SearchContext getContext() - { - return _panel; - } - - WebElement accession = new LazyWebElement(Locator.css(".labkey-study-accession"), this); - WebElement shortName = new LazyWebElement(Locator.css(".labkey-study-short-name"), this); - WebElement title = new LazyWebElement(Locator.css(".labkey-study-title"), this); - WebElement PI = new LazyWebElement(Locator.css(".labkey-study-pi"), this); - WebElement organization = new LazyWebElement(Locator.css(".labkey-study-organization"), this); - } - - private static class Locators - { - private static Locator self = Locator.css(".labkey-study-details"); - private static Locator accession = Locator.css(".labkey-study-accession"); - } - - private class Paper - { - private WebElement paper; - - private Paper(WebElement el) - { - this.paper = el; - } - - public String getJournal() - { - return Locator.css(".labkey-publication-journal").findElement(paper).getText(); - } - - public String getYear() - { - return Locator.css(".labkey-publication-year").findElement(paper).getText(); - } - - public String getTitle() - { - return Locator.css(".labkey-publication-title").findElement(paper).getText(); - } - - public WebElement getPubMedLink() - { - return Locator.linkWithText("PubMed").findElement(paper); - } - - public String getCitation() - { - return Locator.css(".labkey-publication-citation").findElement(paper).getText(); - } - - } -} diff --git a/test/src/org/labkey/test/components/trialshare/StudySummaryWindow.java b/test/src/org/labkey/test/components/trialshare/StudySummaryWindow.java deleted file mode 100644 index 37afeb19..00000000 --- a/test/src/org/labkey/test/components/trialshare/StudySummaryWindow.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2016-2019 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.test.components.trialshare; - -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.Locator; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.ExpectedConditions; - -public class StudySummaryWindow extends StudySummaryPanel -{ - private WebElement _window; - - public StudySummaryWindow(BaseWebDriverTest test) - { - super(test); - _window = Locator.css("div.labkey-study-detail").findElement(test.getDriver()); - } - - public void closeWindow() - { - elements().closeButton.findElement(_window).click(); - _test.shortWait().until(ExpectedConditions.stalenessOf(_window)); - } - - @Override - public Elements elements() - { - return new Elements(); - } - - private class Elements extends StudySummaryPanel.Elements - { - Locator.CssLocator closeButton = Locator.css(".x4-tool-close"); - } -} diff --git a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java b/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java deleted file mode 100644 index 9f91adfb..00000000 --- a/test/src/org/labkey/test/pages/trialshare/CubeObjectEditPage.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (c) 2016-2018 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.test.pages.trialshare; - -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.Nullable; -import org.junit.Assert; -import org.labkey.test.Locator; -import org.labkey.test.components.ext4.Checkbox; -import org.labkey.test.components.ext4.ComboBox; -import org.labkey.test.pages.LabKeyPage; -import org.labkey.test.util.LogMethod; -import org.labkey.test.util.LoggedParam; -import org.labkey.test.util.TestLogger; -import org.openqa.selenium.JavascriptException; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.junit.Assert.assertEquals; -import static org.labkey.test.components.ext4.Checkbox.Ext4Checkbox; - -public abstract class CubeObjectEditPage extends LabKeyPage -{ - public static final String NOT_EMPTY_VALUE = "NOT EMPTY VALUE"; - public static final String EMPTY_VALUE = ""; - - CubeObjectEditPage(WebDriver driver) - { - super(driver); - addPageLoadListener(new PageLoadListener() - { - @Override - public void afterPageLoad() - { - clearCache(); - } - }); - } - - public void setTextFormValue(String fieldName, String value) - { - setTextFormValue(fieldName, value, null); - } - - public void setTextFormValue(String fieldName, String value, @Nullable Boolean expectSubmitEnabled) - { - WebElement field = Locator.name(fieldName).findElement(elementCache()); - setFormElement(field, value); - fireEvent(field, SeleniumEvent.blur); - waitFor(() -> !field.getAttribute("class").contains("x4-field-focus"), 1000); - if (expectSubmitEnabled != null) - { - if (!waitFor(() -> expectSubmitEnabled == isSubmitEnabled(), WAIT_FOR_JAVASCRIPT)) - assertEquals("Submit button state not enabled as expected", expectSubmitEnabled, isSubmitEnabled()); - } - } - - public abstract Map getDropdownFieldNames(); - public abstract Map getMultiSelectFieldNames(); - public abstract Set getFieldNames(); - - @LogMethod - public void setFormField(@LoggedParam String fieldName, @LoggedParam Object value) - { - String expectedValue; - if (getMultiSelectFieldNames().keySet().contains(fieldName)) - { - String[] options = (String[]) value; - String formValue = getFormValue(fieldName); - List selection = new ArrayList<>(Arrays.asList(formValue.split(";\\s*"))); - for (String option : options) - { - if (selection.contains(option)) - selection.remove(option); - else - selection.add(option); - } - selection.remove(""); - expectedValue = String.join("; ", selection); - if (formValue.isEmpty()) - log("Setting field " + fieldName + " to " + expectedValue); - else - log(String.format("Updating field %s: currently \"%s\", toggling [%s]", fieldName, formValue, String.join(", ", options))); - - ComboBox comboBox = elementCache().findComboBox(fieldName); - comboBox.toggleComboBoxItems(options); - } - else - { - expectedValue = (String) value; - log("Setting field " + fieldName + " to " + expectedValue); - if (getDropdownFieldNames().keySet().contains(fieldName)) - { - ComboBox comboBox = elementCache().findComboBox(fieldName); - comboBox.selectComboBoxItem(expectedValue); - } - else - { - setTextFormValue(fieldName, expectedValue); - } - } - assertEquals(expectedValue, getFormValue(fieldName)); - log("Field " + fieldName + " new value is " + getFormValue(fieldName)); - } - - public void setFormFields(Map fieldMap) - { - for (String fieldName : fieldMap.keySet()) - { - setFormField(fieldName, fieldMap.get(fieldName)); - } - } - - public String getFormValue(String field) - { - elementCache().cancelButton.isDisplayed(); - Locator fieldLocator = Locator.name(field); - return StringUtils.trimToEmpty(getFormElement(fieldLocator)); - } - - public Map getFormValues() - { - Map formValues = new HashMap<>(); - for (String field : getFieldNames()) - { - formValues.put(field, getFormValue(field)); - } - return formValues; - } - - public Map compareFormValues(Map expectedValues) - { - elementCache().cancelButton.isDisplayed(); // Make sure - Map formValues = getFormValues(); - Map unexpectedValues = new HashMap<>(); - for (String fieldName : expectedValues.keySet()) - { - String expectedValue; - if (expectedValues.get(fieldName) instanceof String) - { - expectedValue = (String) expectedValues.get(fieldName); - } - else // should be an array of Strings - { - expectedValue = StringUtils.join((String[]) expectedValues.get(fieldName), "; "); - } - String formValue = StringUtils.trimToEmpty(formValues.get(fieldName)); - log("Comparing field values for " + fieldName + " expecting " + expectedValue + " actual " + formValue); - if (expectedValue.equals(NOT_EMPTY_VALUE)) - { - if (formValue.isEmpty()) - unexpectedValues.put(fieldName, "expected: " + expectedValue + " actual: " + formValue); - } - else if (!expectedValue.equals(formValue)) - { - unexpectedValues.put(fieldName, " expected: \"" + expectedValue + "\" but was: \"" + formValue + "\""); - } - } - return unexpectedValues; - } - - public boolean isSubmitEnabled() - { - return !elementCache().saveAndCloseButton.getAttribute("class").contains("disabled"); - } - - public boolean isWorkbenchEnabled() - { - return !elementCache().workbenchButton.getAttribute("class").contains("disabled"); - } - - public void cancel() - { - log("Cancelling edit"); - clickAndWait(elementCache().cancelButton); - } - - public void save() - { - log("Saving edit form"); - Assert.assertTrue("Save buttons are disabled", isSubmitEnabled()); - clickAndWait(elementCache().saveButton); - } - - public void saveAndClose(String returnToHeader) - { - log("Saving and closing edit form"); - Assert.assertTrue("Save buttons are disabled", isSubmitEnabled()); - clickAndWait(elementCache().saveAndCloseButton); - waitForElement(Locator.css(".labkey-wp-title-text").containing(returnToHeader)); - } - - @Override - protected ElementCache elementCache() - { - return (ElementCache) super.elementCache(); - } - - protected abstract String initialFocus(); - - @Override - protected ElementCache newElementCache() - { - return new ElementCache(); - } - - protected class ElementCache extends LabKeyPage.ElementCache - { - public ElementCache() - { - // Wait for form to set initial focus - boolean focused = waitFor(() -> { - try - { - return initialFocus().equals(executeScript("return document.activeElement.type;")); - } - catch (JavascriptException ignore) - { - return false; - } - }, 2000); - - if (!focused) - TestLogger.warn("Expected element did not receive focus. Continuing."); - } - - final Checkbox showOnDashField = Ext4Checkbox().withLabel("Show on Dashboard:").findWhenNeeded(this); - - // Buttons become stale when becoming enabled or disabled so refindWhenNeeded - final WebElement saveButton = Locator.linkWithText("Save").refindWhenNeeded(this).withTimeout(10000); - final WebElement saveAndCloseButton = Locator.linkWithText("Save And Close").refindWhenNeeded(this).withTimeout(10000); - final WebElement cancelButton = Locator.linkWithText("Cancel").refindWhenNeeded(this).withTimeout(10000); - final WebElement workbenchButton = Locator.linkWithText("Workbench").refindWhenNeeded(this).withTimeout(10000); - - private ComboBox findComboBox(String fieldName) - { - String label; - boolean isMultiSelect; - if (getDropdownFieldNames().keySet().contains(fieldName)) - { - label = getDropdownFieldNames().get(fieldName); - isMultiSelect = false; - } - else if (getMultiSelectFieldNames().keySet().contains(fieldName)) - { - label = getMultiSelectFieldNames().get(fieldName); - isMultiSelect = true; - } - else - { - throw new IllegalArgumentException("No combo-box specified for '" + fieldName + "' in " + this.getClass().getSimpleName()); - } - - return new ComboBox.ComboBoxFinder(getDriver()).withLabel(label).find(this).setMultiSelect(isMultiSelect); - } - } -} diff --git a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java b/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java deleted file mode 100644 index f7611c9a..00000000 --- a/test/src/org/labkey/test/pages/trialshare/DataFinderPage.java +++ /dev/null @@ -1,767 +0,0 @@ -/* - * Copyright (c) 2016-2018 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.test.pages.trialshare; - -import org.junit.Assert; -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.Locator; -import org.labkey.test.components.Component; -import org.labkey.test.components.trialshare.PublicationPanel; -import org.labkey.test.components.trialshare.StudySummaryWindow; -import org.labkey.test.pages.LabKeyPage; -import org.labkey.test.util.Ext4Helper; -import org.labkey.test.util.LogMethod; -import org.labkey.test.util.LoggedParam; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.WebElement; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import static org.labkey.test.Locators.pageSignal; - -public class DataFinderPage extends LabKeyPage -{ - public static final String STUDY_COUNT_SIGNAL = "dataFinderStudyCountsUpdated"; - public static final String PUBLICATION_COUNT_SIGNAL = "dataFinderPublicationCountsUpdated"; - public static final String STUDY_FACET_SIGNAL = "dataFinderStudyFacetsReady"; - public static final String PUBLICATION_FACET_SIGNAL = "dataFinderPublicationFacetReady"; - private static final String GROUP_UPDATED_SIGNAL = "participantGroupUpdated"; - private static final String PUBLICATION_DETAILS_SIGNAL = "publicationDetailsLoaded"; - private boolean testingStudies = true; - private ObjectType objectType = null; - private Locator.CssLocator finderLocator = null; - - public DataFinderPage(BaseWebDriverTest test, boolean testingStudies) - { - super(test); - this.testingStudies = testingStudies; - if (testingStudies) - { - objectType = ObjectType.study; - finderLocator = Locators.studyFinder; - } - else - { - objectType = ObjectType.publication; - finderLocator = Locators.pubFinder; - } - } - - public boolean canManageData() - { - return this.testingStudies ? isElementPresent(Locators.manageStudyData) : isElementPresent(Locators.managePublicationData); - } - - public boolean canInsertNewData() - { - return this.testingStudies ? isElementPresent(Locators.insertNewStudy) : isElementPresent(Locators.insertNewPublication); - } - - public void goToInsertNewData() - { - if (this.testingStudies) - Locators.insertNewStudy.findElement(this.getDriver()).click(); - else - Locators.insertNewPublication.findElement(this.getDriver()).click(); - } - - public void goToManageData() - { - if (this.testingStudies) - Locators.manageStudyData.findElement(this.getDriver()).click(); - else - Locators.managePublicationData.findElement(this.getDriver()).click(); - } - - public String getCountSignal() - { - return (this.testingStudies ? STUDY_COUNT_SIGNAL : PUBLICATION_COUNT_SIGNAL); - } - - public String getFacetReadySignal() - { - return (this.testingStudies ? STUDY_FACET_SIGNAL : PUBLICATION_FACET_SIGNAL); - } - - @Override - protected void waitForPage() - { - waitForElement(pageSignal(getCountSignal())); - } - - protected void waitForGroupUpdate() - { - waitForElement(pageSignal(GROUP_UPDATED_SIGNAL)); - } - - - public boolean hasStudySubsetCombo() - { - return isElementPresent(Locators.studySubsetCombo); - } - - public void selectStudySubset(String text) - { - // FIXME isn't there a general method for asking "is this combo list item already selected"? - _ext4Helper.openComboList(DataFinderPage.Locators.studySubsetCombo); - if (!isElementPresent(Ext4Helper.Locators.comboListItemSelected().withText(text))) - { - doAndWaitForPageSignal(() ->_ext4Helper.selectItemFromOpenComboList(text, Ext4Helper.TextMatchTechnique.EXACT), getCountSignal()); - - } - else // FIXME you should be able to just close the combo box at this point, but the close method assumes you've chosen something from teh lis - { - _ext4Helper.selectItemFromOpenComboList(text, Ext4Helper.TextMatchTechnique.EXACT); - } - } - - @LogMethod - public void search(@LoggedParam final String search) - { - doAndWaitForPageSignal(() -> setFormElement(Locators.getSearchInput(finderLocator), search), getCountSignal()); - } - - @LogMethod(quiet = true) - public void clearSearch() - { - if (isElementPresent(Locators.getSearchInput(finderLocator)) && !getFormElement(Locators.getSearchInput(finderLocator)).isEmpty()) - search(""); - } - - public void navigateToStudies() - { - selectDataFinderObject("Studies"); - assertElementVisible(DataFinderPage.Locators.studyFinder); - } - - public void navigateToPublications() - { - selectDataFinderObject("Publications"); - assertElementVisible(DataFinderPage.Locators.pubFinder); - } - - public String getSelectedFinderObject() - { - return Locators.activeFinderObject.findElement(getDriver()).getText(); - } - - public void selectDataFinderObject(String text) - { - _ext4Helper.waitForMaskToDisappear(); - Locators.finderObjectTab(text).findElement(getDriver()).click(); - sleep(500); - } - - public Map getSummaryCounts() - { - WebElement summaryElement; - if (testingStudies) - summaryElement = DataFinderPage.Locators.studySummaryArea.findElement(getDriver()); - else - summaryElement = DataFinderPage.Locators.pubSummaryArea.findElement(getDriver()); - - SummaryPanel summary = new SummaryPanel(summaryElement); - - Map countMap = new HashMap<>(); - for (String value : summary.getValues()) - { - String[] parts = value.split("\n"); - Dimension dimension = Dimension.fromString(parts[0]); - Integer count = Integer.parseInt(parts[1].trim().replace(",","")); - countMap.put(dimension, count); - } - return countMap; - } - - public List getDataCards() - { - List cardEls; - List cards = new ArrayList<>(); - - Locator locator = testingStudies? Locators.studyCard : Locators.pubCard; - - if (isElementPresent(locator)) - { - scrollIntoView(locator); - cardEls = locator.findElements(getDriver()); - for (WebElement el : cardEls) - { - cards.add(new DataCard(el)); - } - } - - return cards; - } - - public FacetGrid getFacetsGrid() - { - if (testingStudies) - return new FacetGrid(DataFinderPage.Locators.studyFacetPanel.findElement(getDriver())); - else - return new FacetGrid(DataFinderPage.Locators.pubFacetPanel.findElement(getDriver())); - } - - public boolean clearAllActive() - { - Locator.CssLocator clearAllLocator = DataFinderPage.Locators.getClearAll(finderLocator); - scrollIntoView(clearAllLocator); - Locator activeClearAllLocator = DataFinderPage.Locators.getActiveClearAll(clearAllLocator); - return isElementPresent(activeClearAllLocator); - } - - public void clearAllFilters() - { - Locator.CssLocator clearAllLocator = DataFinderPage.Locators.getClearAll(finderLocator); - scrollIntoView(clearAllLocator); - Locator activeClearAllLocator = DataFinderPage.Locators.getActiveClearAll(clearAllLocator); - if (isElementPresent(activeClearAllLocator)) - { - final WebElement clearAll = activeClearAllLocator.findElement(getDriver()); - if (clearAll.isDisplayed()) - { - doAndWaitForPageSignal(clearAll::click, getCountSignal()); - } - } - assertElementNotPresent(activeClearAllLocator); - } - - public void dismissTour() - { - shortWait().until(new Function() - { - @Override - public Boolean apply(WebDriver webDriver) - { - try - { - return (Boolean) executeScript("" + - "if (window.hopscotch)" + - " return !hopscotch.endTour().isActive;" + - "else" + - " return true;"); - } - catch (WebDriverException recheck) - { - return false; - } - } - - @Override - public String toString() - { - return "tour to be dismissed."; - } - }); - } - - public static class Locators - { - - public static final Locator.CssLocator cardDeck = Locator.css(".labkey-data-finder-card-deck-view"); - public static final Locator.CssLocator studyFinder = Locator.css(".labkey-study-finder-card"); - - public static final Locator.XPathLocator finderObjectCombo = Ext4Helper.Locators.formItemWithInputNamed("configSelect"); - public static final Locator.XPathLocator studySubsetCombo = Ext4Helper.Locators.formItemWithInputNamed("subsetSelect"); - public static final Locator.CssLocator studyCard = studyFinder.append(Locator.css(".labkey-study-card")); - public static final Locator.CssLocator studySelectionPanel = studyFinder.append(Locator.css(".labkey-facet-selection-panel")); - public static final Locator.CssLocator studyFacetPanel = studySelectionPanel.append(Locator.css(" .labkey-study-facets")); - public static final Locator.CssLocator studySummaryArea = studySelectionPanel.append(Locator.css("#summaryArea")); - public static final Locator.CssLocator pubFinder = Locator.css(".labkey-publication-finder-card"); - public static final Locator.CssLocator pubSelectionPanel = pubFinder.append(Locator.css(".labkey-facet-selection-panel")); - public static final Locator.CssLocator pubFacetPanel = pubSelectionPanel.append(Locator.css(" .labkey-publication-facets")); - public static final Locator.CssLocator pubSummaryArea = pubSelectionPanel.append(Locator.css("#summaryArea")); - public static final Locator.CssLocator pubCard = pubFinder.append(Locator.css(".labkey-publication-card")); - public static final Locator.CssLocator pubCardBorderHighlight = pubFinder.append(Locator.css(".labkey-publication-highlight1")); - public static final Locator.CssLocator pubCardBackgroundHighlight = pubFinder.append(Locator.css(".labkey-publication-highlight3")); - public static final Locator.CssLocator groupLabel = Locator.css(".labkey-group-label"); - public static final Locator groupLabelInput = Locator.name("groupLabel"); - public static final Locator.CssLocator saveMenu = Locator.css("#saveMenu"); - public static final Locator.CssLocator loadMenu = Locator.css("#loadMenu"); - public static final Locator.IdLocator manageMenu = Locator.id("manageMenu"); - public static final Locator.CssLocator managePublicationData = Locator.css(".labkey-publications-panel .labkey-finder-manage-data"); - public static final Locator.CssLocator manageStudyData = Locator.css(".labkey-studies-panel .labkey-finder-manage-data"); - public static final Locator.CssLocator insertNewStudy = Locator.css(".labkey-studies-panel .labkey-finder-insert-new"); - public static final Locator.CssLocator insertNewPublication = Locator.css(".labkey-publications-panel .labkey-finder-insert-new"); - public static final Locator.CssLocator activeFinderObject = Locator.css(".x4-tab.x4-tab-active"); - - public static Locator.CssLocator getSearchInput(Locator.CssLocator locator) - { - return locator.append(" input.labkey-search-box"); - } - - public static Locator.CssLocator getClearAll(Locator.CssLocator locator) - { - return locator.append(" .labkey-clear-all"); - } - - public static Locator.CssLocator getActiveClearAll(Locator.CssLocator locator) - { - return locator.append(".active"); - } - - public static Locator.XPathLocator finderObjectTab(String objectName) - { - return Locator.tagWithText("span", objectName); - } - - } - - public enum ObjectType - { - all, study, publication - } - - public enum Dimension - { - // Common - SUMMARY_STUDIES("studies", null, ObjectType.all), - - // Study focused. - SUBJECTS("subjects", null, ObjectType.study), - VISIBILITY("visibility","Study.Visibility", ObjectType.study), - THERAPEUTIC_AREA("therapeutic area", "Study.Therapeutic Area", ObjectType.study), - STUDY_TYPE("study type", "Study.Study Type", ObjectType.study), - ASSAY("assay", "Study.Assay", ObjectType.study), - AGE_GROUP("age group", "Study.AgeGroup", ObjectType.study), - PHASE("phase", "Study.Phase", ObjectType.study), - CONDITION("condition", "Study.Condition", ObjectType.study), - - // Publication focused. - PUBLICATIONS("publications", null, ObjectType.publication), - STATUS("status", "Publication.Status", ObjectType.publication), - COMPLETE("complete", "Complete", ObjectType.publication), - IN_PROGRESS("in progress", "In Progress", ObjectType.publication), - PUBLICATION_TYPE("publication type", "Publication.Publication Type", ObjectType.publication), - PUB_THERAPEUTIC_AREA("therapeutic area", "Publication.Therapeutic Area", ObjectType.publication), - SUBMISISON_STATUS("submission status", "Publication.Submission Status", ObjectType.publication), - YEAR("year", "Publication.Year", ObjectType.publication), - PUB_STUDY("study", "Publication.Study", ObjectType.publication),; - - private String caption; - private String hierarchyName; - private ObjectType objectType; - - Dimension(String caption, String hierarchyName, ObjectType objectType) - { - this.caption = caption; - this.hierarchyName = hierarchyName; - this.objectType = objectType; - } - - public String getCaption() - { - return caption; - } - - public String getHierarchyName() - { - return hierarchyName; - } - - public ObjectType getObjectType() - { - return objectType; - } - - public static Dimension fromString(String value) - { - for (Dimension dimension : values()) - { - if (value.toLowerCase().equals(dimension.getCaption())) - return dimension; - } - - throw new IllegalArgumentException("No such dimension: " + value); - } - } - - - public class GroupMenu extends Component - { - - private final WebElement menu; - private final Elements elements; - - private GroupMenu(WebElement menu) - { - this.menu = menu; - elements = new Elements(); - } - - public void toggleMenu() - { - this.menu.click(); - } - - @Override - public WebElement getComponentElement() - { - return menu; - } - - public List getActiveOptions() - { - return getOptions(elements.activeOption); - } - - public List getInactiveOptions() - { - return getOptions(elements.inactiveOption); - } - - public void chooseOption(String optionText, boolean waitForUpdate) - { - log("Choosing menu option " + optionText); - List activeOptions = findElements(elements.activeOption); - for (WebElement option : activeOptions) - { - if (optionText.equals(option.getText().trim())) - { - option.click(); - if (waitForUpdate) - waitForGroupUpdate(); - return; - } - } - } - - private List getOptions(Locator locator) - { - List options = findElements(locator); - List optionStrings = new ArrayList(); - for (WebElement option : options) - { - optionStrings.add(option.getText().trim()); - } - return optionStrings; - } - - private class Elements - { - public Locator.CssLocator activeOption = Locator.css(".menu-item-link:not(.inactive)"); - public Locator.CssLocator inactiveOption = Locator.css(".menu-item-link.inactive"); - } - } - - public class FacetGrid extends Component - { - - public class MemberCount - { - private Integer selectedCount; - private Integer totalCount; - - public MemberCount(String countString) - { - String[] values = countString.split("/"); - selectedCount = Integer.valueOf(values[0].trim()); - totalCount = Integer.valueOf(values[1].trim()); - } - - public int getSelectedCount() - { - return selectedCount; - } - - public void setSelectedCount(Integer selectedCount) - { - this.selectedCount = selectedCount; - } - - public int getTotalCount() - { - return totalCount; - } - - public void setTotalCount(Integer totalCount) - { - this.totalCount = totalCount; - } - - } - private WebElement grid; - private Locators locators; - - public FacetGrid(WebElement grid) - { - this.grid = grid; - this.locators = new Locators(); - } - - @Override - public WebElement getComponentElement() - { - return this.grid; - } - - public boolean facetIsPresent(Dimension dimension) - { - return isElementPresent(locators.facetGroup(dimension)); - } - - public void toggleFacet(Dimension dimension, String name) - { - Locator.XPathLocator rowLocator = locators.facetMemberName(dimension, name); - scrollIntoView(rowLocator); - WebElement row = rowLocator.findElement(getDriver()); - - doAndWaitForPageSignal(() -> row.click(), getCountSignal()); - } - - public void clearFilter(Dimension dimension) - { - WebElement clearEl = locators.facetClear(dimension).findElement(getDriver()); - scrollIntoView(clearEl); - Assert.assertFalse("Attempting to clear filter that is not active", clearEl.getAttribute("class").contains("inactive")); - clickAt(locators.facetClear(dimension), 3, 3, 0); - waitForElement(Locator.xpath("//tr[contains(@data-recordid,'" + dimension.getHierarchyName() + "')]//span[contains(@class,'labkey-clear-filter')][contains(@class, 'inactive')]")); - } - - public Map> getSelectedMembers() - { - Map> selections = new HashMap<>(); - for (Dimension dimension : Dimension.values()) - { - if (dimension.getHierarchyName() != null) - { - selections.put(dimension, getSelectedMembers(dimension)); - } - } - return selections; - } - - public List getSelectedMembers(Dimension dimension) - { - List selectedElements = locators.facetMemberSelected(dimension).findElements(getDriver()); - List selectedNames = new ArrayList<>(); - for (WebElement element: selectedElements) - { - selectedNames.add(locators.memberName.findElement(element).getText()); - } - return selectedNames; - } - - public Map getMemberCounts(Dimension dimension) - { - List memberElements = locators.facetMembers(dimension).findElements(getDriver()); - Map countMap = new HashMap<>(); - for (WebElement member : memberElements) - { - String name = locators.memberName.findElement(member).getText(); - String count = locators.memberCount.findElement(member).getText(); - MemberCount memberCount = new MemberCount(count); - countMap.put(name, memberCount); - } - return countMap; - - } - - public Map> getAllMemberCounts() - { - Map> counts = new HashMap<>(); - for (Dimension dimension : Dimension.values()) - { - if (dimension.getObjectType() == objectType) - counts.put(dimension.getCaption(), getMemberCounts(dimension)); - } - return counts; - } - - public int getSelectedCount(Map> counts, DataFinderPage.Dimension dimension, String member) - { - Map dimCount = counts.get(dimension.getCaption()); - if (dimCount == null) - return 0; - DataFinderPage.FacetGrid.MemberCount memberCount = dimCount.get(member); - if (memberCount == null) - return 0; - return memberCount.getSelectedCount(); - } - - private class Locators - { - public Locator.XPathLocator facetMemberRow(Dimension dimension, String name) - { - return Locator.tagWithAttribute("tr", "data-recordid", "[" + dimension.getHierarchyName() + "].[" + name + "]"); - } - - public Locator.XPathLocator facetMemberName(Dimension dimension, String name) - { - return facetMemberRow(dimension, name).append("//span[contains(@class, 'labkey-facet-member-name')]"); - } - - public Locator.XPathLocator facetGroup(Dimension dimension) - { - return Locator.xpath("//tr[contains(@data-recordid,'" + dimension.getHierarchyName() + "')]"); - } - - public Locator.XPathLocator facetGroupTitle(Dimension dimension) - { - //tr[contains(@data-recordid,"Study.Therapeutic Area")]//div[@class="x4-grid-group-title"] - return facetGroup(dimension).append("//div[@class='x4-grid-group-title']"); - } - - public Locator.XPathLocator facetClear(Dimension dimension) - { - return Locator.xpath("//tr[contains(@data-recordid,'" + dimension.getHierarchyName() + "')]//span[contains(@class,'labkey-clear-filter')]"); - } - - public Locator.XPathLocator facetMemberSelected(Dimension dimension) - { - //tr[contains(@data-recordid, 'Therapeutic Area')][contains(@class, 'x4-grid-row-selected')] - return facetGroup(dimension).append("[contains(@class, 'x4-grid-row-selected')]"); - } - - public Locator.XPathLocator facetMembers(Dimension dimension) - { - - return facetGroup(dimension).append("//span[contains(concat(' ', normalize-space(@class), ' '), ' labkey-facet-member ')]"); - } - - public Locator.CssLocator memberCount = Locator.css(".labkey-facet-member-count"); - public Locator.CssLocator memberName = Locator.css(".labkey-facet-member-name"); - public Locator.CssLocator emptyMemberName = Locator.css(".labkey-empty-member .labkey-facet-member-name"); - public Locator.CssLocator nonEmptyMemberName = Locator.css(".labkey-facet-member:not(.labkey-empty-member) .labkey-facet-member-name"); - public Locator.CssLocator selectedMemberName = Locator.css(".x4-grid-row-selected .labkey-facet-member-name"); - public Locator.CssLocator nonEmptyNonSelectedMemberName = Locator.css(".x4-grid-row:not(.x4-grid-row-selected) .labkey-facet-member:not(.labkey-empty-member) .labkey-member-name"); - } - } - - public class SummaryPanel extends Component - { - private WebElement panel; - private Elements elements; - private Dimension dimension; - - private SummaryPanel(WebElement panel) - { - this.panel = panel; - elements = new Elements(); - } - - @Override - public WebElement getComponentElement() - { - return panel; - } - - public List getValues() - { - return getTexts(findElements(elements.member)); - } - - private class Elements - { - public Locator.CssLocator member = Locator.css(".labkey-facet-member"); - } - } - - public class DataCard - { - WebElement card; - Locators locators; - String title; - String accession; - String pi; - - private DataCard(WebElement card) - { - this.card = card; - locators = new Locators(); - } - - public WebElement getCardElement() - { - return card; - } - - public StudySummaryWindow viewStudySummary() - { - clickAt(locators.viewStudyLink.findElement(card), 3, 3, 0); - return new StudySummaryWindow(_test); - } - - public void clickGoToStudy(String choice) - { - _ext4Helper.clickExt4MenuButton(false, locators.goToStudyLink, false, choice); - } - - public void clickGoToStudy() - { - locators.goToStudyLink.findElement(card).click(); - } - - public String getStudyAccession() - { - return locators.studyAccession.findElement(card).getText(); - } - - public String getStudyShortName() - { - return locators.studyShortName.findElement(card).getText(); - } - - public String getStudyPI() - { - return locators.studyPI.findElement(card).getText(); - } - - public String getTitle() - { - if(testingStudies) - return locators.studyTitle.findElement(card).getText(); - else - return locators.pubTitle.findElement(card).getText(); - } - - public PublicationPanel viewDetail() - { - doAndWaitForPageSignal(() -> locators.pubMoreDetails.findElement(card).click(), PUBLICATION_DETAILS_SIGNAL); - return new PublicationPanel(_test); - } - - public void hideDetail() - { - clickAt(locators.pubLessDetails.findElement(card), 3, 3, 0); - } - - public void clickViewDocument() - { - locators.pubViewDocument.findElement(card).click(); - } - - private class Locators - { - public Locator viewStudyLink = Locator.linkWithText("view summary"); - public Locator goToStudyLink = Locator.linkWithText("go to study"); - public Locator.XPathLocator goToStudyMenu = Locator.tagWithClass("div", "labkey-study-goto-menu"); - public Locator studyAccession = Locator.css(".labkey-study-card-accession"); - public Locator studyShortName = Locator.css(".labkey-study-card-short-name"); - public Locator studyPI = Locator.css(".labkey-study-card-pi"); - public Locator studyTitle = Locator.css(".labkey-study-card-title"); - public Locator pubTitle = Locator.css(".labkey-publication-title"); - public Locator pubViewDocument = Locator.linkWithText("view document"); - public Locator pubMoreDetails = Locator.tagWithClass("i", "fa-plus-square"); - public Locator pubLessDetails = Locator.tagWithClass("i", "fa-minus-square"); - } - } - -} diff --git a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java b/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java deleted file mode 100644 index b3cfbce3..00000000 --- a/test/src/org/labkey/test/pages/trialshare/ManageDataPage.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2016-2018 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.test.pages.trialshare; - -import org.apache.commons.lang3.StringUtils; -import org.junit.Assert; -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.Locator; -import org.labkey.test.pages.LabKeyPage; -import org.labkey.test.tests.trialshare.DataFinderTestBase; -import org.labkey.test.util.DataRegionTable; - -/** - * Created by susanh on 6/30/16. - */ -public class ManageDataPage extends LabKeyPage -{ - private DataRegionTable _table; - private DataFinderTestBase.CubeObjectType _objectType; - - - public ManageDataPage(BaseWebDriverTest test, DataFinderTestBase.CubeObjectType objectType) - { - super(test.getDriver()); - - _table = new DataRegionTable("query", test); - _objectType = objectType; - } - - public int getCount() - { - return _table.getDataRowCount(); - } - - public boolean isManageDataView() - { - return isTextPresent(_objectType.getManageDataTitle()) && hasExpectedColumns(); - } - - public boolean hasExpectedColumns() - { - for (String header : _table.getColumnLabels()) - { - if (!StringUtils.isEmpty(header) && !header.trim().isEmpty() && !_objectType.getManageDataHeaders().contains(header)) - return false; - } - return true; - } - - public boolean hasManageStudiesLink() - { - return isElementPresent(Locators.manageStudiesLink); - } - - public void goToManageStudies() - { - doAndWaitForPageToLoad(() -> Locators.manageStudiesLink.findElement(getDriver()).click()); - } - - public boolean hasManagePublicationsLink() - { - return isElementPresent(Locators.managePublicationsLink); - } - - public void goToManagePublications() - { - doAndWaitForPageToLoad(() -> Locators.managePublicationsLink.findElement(getDriver()).click()); - } - - public void goToInsertNew() - { - log("Going to insert new " + _objectType); - doAndWaitForPageToLoad(() -> _table.clickInsertNewRow()); - } - - public int getRowIndex(String value) - { - Integer rowIndex = _table.getRowIndex(_objectType.getKeyField(), value); - Assert.assertTrue("Did not find row with " + _objectType.getKeyField() + " '" + value + "'", rowIndex >= 0); - return rowIndex; - } - - public void goToEditRecord(String keyValue) - { - Integer rowIndex = getRowIndex(keyValue); - _table.clickEditRow(rowIndex); - } - - public void deleteRecord(String keyValue) - { - log("Deleting record with key value '" + keyValue + "'"); - Integer rowIndex = getRowIndex(keyValue); - Assert.assertTrue("Record with key '" + keyValue + "' not found", rowIndex >= 0); - _table.checkCheckbox(rowIndex); - click(Locator.tagWithAttribute("a","data-original-title","Delete")); - log("Waiting for delete confirmation to show up"); - waitForAlert("Are you sure you want to delete the selected row?", 1000); - } - - public Locator.XPathLocator detailsLink(int row) - { - return Locator.tagWithClass("table", "labkey-data-region").append(Locator.xpath("/tbody/tr[" + (row + 3) + "]/td[3]/a ")); - } - - public void refreshCube() - { - log("Refreshing cube"); - _table.clickHeaderButtonAndWait("Refresh Cube"); - sleep(2000); // give time for the refresh to happen - } - - private static class Locators - { - public static Locator manageStudiesLink = Locator.linkWithText("Manage Studies"); - public static Locator managePublicationsLink = Locator.linkWithText("Manage Publications"); - } - -} diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java b/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java deleted file mode 100644 index 16cad850..00000000 --- a/test/src/org/labkey/test/pages/trialshare/PublicationEditPage.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2016-2018 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.test.pages.trialshare; - -import org.openqa.selenium.WebDriver; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * Created by susanh on 6/30/16. - */ -public class PublicationEditPage extends CubeObjectEditPage -{ - public static final String TITLE = "title"; - public static final String PUBLICATION_TYPE ="publicationType"; - public static final String STATUS ="status"; - public static final String SUBMISSION_STATUS ="submissionStatus"; - public static final String AUTHOR ="author"; - public static final String CITATION ="citation"; - public static final String YEAR ="year"; - public static final String JOURNAL ="journal"; - public static final String ABSTRACT ="abstractText"; - public static final String DOI ="DOI"; - public static final String PMID ="PMID"; - public static final String PMCID ="PMCID"; - public static final String MANUSCRIPT_CONTAINER ="manuscriptContainer"; - public static final String PERMISSIONS_CONTAINER ="permissionsContainer"; - public static final String KEYWORDS ="keywords"; - public static final String STUDIES ="studyIds"; - public static final String THERAPEUTIC_AREAS ="therapeuticAreas"; - public static final String LINK1 ="link1"; - public static final String DESCRIPTION1 ="description1"; - public static final String LINK2 ="link2"; - public static final String DESCRIPTION2 ="description2"; - public static final String LINK3 ="link3"; - public static final String DESCRIPTION3="description3"; - - private static final Map DROPDOWN_FIELD_NAMES = new HashMap<>(); - static - { - DROPDOWN_FIELD_NAMES.put(PUBLICATION_TYPE, "Publication Type *:"); - DROPDOWN_FIELD_NAMES.put(STATUS, "Status *:"); - DROPDOWN_FIELD_NAMES.put(SUBMISSION_STATUS, "Submission Status:"); - DROPDOWN_FIELD_NAMES.put(MANUSCRIPT_CONTAINER, "Manuscript Container:"); - DROPDOWN_FIELD_NAMES.put(PERMISSIONS_CONTAINER, "Permissions Container:"); - } - - private static final Map MULTI_SELECT_FIELD_NAMES = new HashMap<>(); - static - { - MULTI_SELECT_FIELD_NAMES.put(STUDIES, "Studies:"); - MULTI_SELECT_FIELD_NAMES.put(THERAPEUTIC_AREAS, "Therapeutic Areas:"); - } - - private static final Set FIELD_NAMES = new HashSet<>(); - static - { - FIELD_NAMES.add(TITLE); - FIELD_NAMES.add(PUBLICATION_TYPE); - FIELD_NAMES.add(STATUS); - FIELD_NAMES.add(SUBMISSION_STATUS); - FIELD_NAMES.add(AUTHOR); - FIELD_NAMES.add(CITATION); - FIELD_NAMES.add(YEAR); - FIELD_NAMES.add(JOURNAL); - FIELD_NAMES.add(ABSTRACT); - FIELD_NAMES.add(DOI); - FIELD_NAMES.add(PMID); - FIELD_NAMES.add(PMCID); - FIELD_NAMES.add(MANUSCRIPT_CONTAINER); - FIELD_NAMES.add(PERMISSIONS_CONTAINER); - FIELD_NAMES.add(KEYWORDS); - FIELD_NAMES.add(STUDIES); - FIELD_NAMES.add(THERAPEUTIC_AREAS); - FIELD_NAMES.add(LINK1); - FIELD_NAMES.add(DESCRIPTION1); - FIELD_NAMES.add(LINK2); - FIELD_NAMES.add(DESCRIPTION2); - FIELD_NAMES.add(LINK3); - FIELD_NAMES.add(DESCRIPTION3); - } - - public PublicationEditPage(WebDriver driver) - { - super(driver); - } - - @Override - public Map getDropdownFieldNames() - { - return DROPDOWN_FIELD_NAMES; - } - - @Override - public Map getMultiSelectFieldNames() - { - return MULTI_SELECT_FIELD_NAMES; - } - - @Override - public Set getFieldNames() - { - return FIELD_NAMES; - } - - public void setFormFields(Map fieldMap, Boolean showOnDashboard) - { - setFormFields(fieldMap); - if (showOnDashboard) - { - elementCache().showOnDashField.check(); - } - } - - @Override - protected String initialFocus() - { - return "title"; - } -} diff --git a/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java b/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java deleted file mode 100644 index ccce6c12..00000000 --- a/test/src/org/labkey/test/pages/trialshare/PublicationsListHelper.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2016-2017 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.test.pages.trialshare; - -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.Locator; -import org.labkey.test.pages.LabKeyPage; -import org.labkey.test.util.DataRegionTable; - -public class PublicationsListHelper extends LabKeyPage -{ - public PublicationsListHelper(BaseWebDriverTest test) - { - super(test); - } - - public void setPermissionsContainers(String publicStudyName, String operationalStudyName) - { - log("Setting up permissions container for publications"); - clickAndWait(Locator.linkWithText("ManuscriptsAndAbstracts")); - DataRegionTable table = new DataRegionTable("query", getDriver()); - - for (int i = 0; i < table.getDataRowCount(); i++) - { - table.clickEditRow(i); - String status = Locators.statusValue.findElement(getDriver()).getAttribute("value"); - if ("Complete".equalsIgnoreCase(status)) - selectOptionByText(Locators.permissionsContainerSelect,publicStudyName ); - else - selectOptionByText(Locators.permissionsContainerSelect,operationalStudyName ); - clickButton("Submit"); - } - } - - public void setPermissionsContainer(String title, String containerName, Boolean navigateToList) - { - log("Setting up permission container for publication with title: '" + title + "'"); - if (navigateToList) - { - clickAndWait(Locator.linkWithText("ManuscriptsAndAbstracts")); - } - DataRegionTable table = new DataRegionTable("query", getDriver()); - - int rowIndex = table.getRowIndex("Title", title); - table.clickEditRow(rowIndex); - selectOptionByText(Locators.permissionsContainerSelect, containerName); - clickButton("Submit"); - } - - public void setManuscriptContainer(String title, String containerName, Boolean navigateToList) - { - log("Setting up manuscript container for publication with title: '" + title + "'"); - if (navigateToList) - { - clickAndWait(Locator.linkWithText("ManuscriptsAndAbstracts")); - } - DataRegionTable table = new DataRegionTable("query", getDriver()); - - int rowIndex = table.getRowIndex("Title", title); - table.clickEditRow(rowIndex); - selectOptionByText(Locators.manuscriptContainerSelect, containerName); - clickButton("Submit"); - } - - public int getPublicationCount(String title, Boolean navigateToList) - { - if (navigateToList) - { - clickAndWait(Locator.linkWithText("ManuscriptsAndAbstracts")); - } - DataRegionTable table = new DataRegionTable("query", getDriver()); - table.setFilter("Title", "Equals", title); - return table.getDataRowCount(); - } - - public int getPublicationTherapeuticAreaCount(String title, Boolean navigateToList) - { - if (navigateToList) - { - clickAndWait(Locator.linkWithText("PublicationTherapeuticArea")); - } - DataRegionTable table = new DataRegionTable("query", getDriver()); - table.setFilter("PublicationId", "Equals", title); - return table.getDataRowCount(); - } - - - public int getPublicationStudyCount(String title, Boolean navigateToList) - { - if (navigateToList) - { - clickAndWait(Locator.linkWithText("PublicationStudy")); - } - DataRegionTable table = new DataRegionTable("query", getDriver()); - table.setFilter("PublicationId", "Equals", title); - return table.getDataRowCount(); - } - - private static class Locators - { - static final Locator.XPathLocator manuscriptContainerSelect = Locator.tagWithName("select", "quf_ManuscriptContainer"); - static final Locator.XPathLocator statusValue = Locator.input("quf_Status"); - static final Locator.XPathLocator permissionsContainerSelect = Locator.tagWithName("select", "quf_PermissionsContainer"); - } - -} diff --git a/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java b/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java deleted file mode 100644 index 7107a9b8..00000000 --- a/test/src/org/labkey/test/pages/trialshare/StudiesListHelper.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2016-2017 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.test.pages.trialshare; - -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.Locator; -import org.labkey.test.pages.LabKeyPage; -import org.labkey.test.util.DataRegionTable; - -/** - * Created by susanh on 2/5/16. - */ -public class StudiesListHelper extends LabKeyPage -{ - public StudiesListHelper(BaseWebDriverTest test) - { - super(test); - } - - public void setStudyContainers() - { - log("Setting up study container links"); - String projectName = getCurrentProject(); - clickAndWait(Locator.linkWithText("StudyAccess")); - DataRegionTable table = new DataRegionTable("query", _test); - for (int i = 0; i < table.getDataRowCount(); i++) - { - String name = table.getDataAsText(i, "StudyId"); - Boolean isPublic = "Public".equalsIgnoreCase(table.getDataAsText(i, "Visibility")); - clickAndWait(table.updateLink(i)); - selectOptionByText(Locators.studyContainerSelect, "/" + projectName + "/DataFinderTest" + (isPublic ? "Public" : "Operational") + name); - clickButton("Submit"); - } - } - - public void addStudyAccessEntry(String shortName, String containerName, String visibility, Boolean navigateToList) - { - log("Adding study access entry for study name " + shortName + " and container " + containerName + " to " + visibility); - if (navigateToList) - { - clickAndWait(Locator.linkWithText("StudyAccess")); - } - DataRegionTable table = new DataRegionTable("query", _test); - table.clickInsertNewRow(); - selectOptionByText(Locators.studyContainerSelect, containerName); - selectOptionByText(Locators.studyVisibility, visibility); - selectOptionByText(Locators.studyIdSelect, shortName); - clickButton("Submit"); - } - - public void setStudyContainer(String studyId, String containerName, Boolean navigateToList) - { - log("Setting up study container link for studyId " + studyId); - if (navigateToList) - { - clickAndWait(Locator.linkWithText("StudyAccess")); - } - DataRegionTable table = new DataRegionTable("query", _test); - int rowIndex = table.getRow("StudyId", studyId); - clickAndWait(table.updateLink(rowIndex)); - selectOptionByText(Locators.studyContainerSelect, containerName); - clickButton("Submit"); - } - - public int getStudyCount(String studyId, Boolean navigateToList) - { - return getStudyListCount("StudyProperties", studyId, navigateToList); - } - - public int getStudyAgeGroupCount(String studyId, Boolean navigateToList) - { - return getStudyListCount("StudyAgeGroup", studyId, navigateToList); - } - - public int getStudyConditionCount(String studyId, Boolean navigateToList) - { - return getStudyListCount("StudyCondition", studyId, navigateToList); - } - - public int getStudyTherapeuticAreaCount(String studyId, Boolean navigateToList) - { - return getStudyListCount("StudyTherapeuticArea", studyId, navigateToList); - } - - public int getStudyPhaseCount(String studyId, Boolean navigateToList) - { - return getStudyListCount("StudyPhase", studyId, navigateToList); - } - - public int getStudyAccessCount(String studyId, Boolean navigateToList) - { - return getStudyListCount("StudyAccess", studyId, navigateToList); - } - - public int getStudyListCount(String listName, String studyId, Boolean navigateToList) - { - if (navigateToList) - { - clickAndWait(Locator.linkWithText(listName)); - } - DataRegionTable table = new DataRegionTable("query", getDriver()); - table.setFilter("StudyId", "Equals", studyId); - return table.getDataRowCount(); - } - - private static class Locators - { - static final Locator.XPathLocator studyContainerSelect = Locator.tagWithName("select", "quf_StudyContainer"); - static final Locator.XPathLocator studyIdSelect = Locator.tagWithName("select", "quf_StudyId"); - static final Locator.XPathLocator studyVisibility = Locator.tagWithName("select", "quf_Visibility"); - } - -} diff --git a/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java b/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java deleted file mode 100644 index 3348da19..00000000 --- a/test/src/org/labkey/test/pages/trialshare/StudyEditPage.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (c) 2016-2018 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.test.pages.trialshare; - -import org.labkey.test.Locator; -import org.labkey.test.components.ext4.ComboBox; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.ExpectedConditions; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Created by susanh on 6/30/16. - */ -public class StudyEditPage extends CubeObjectEditPage -{ - public static final String SHORT_NAME = "shortName"; - public static final String STUDY_ID = "studyId"; - public static final String TITLE = "title"; - public static final String PARTICIPANT_COUNT = "participantCount"; - public static final String STUDY_TYPE ="studyType"; - public static final String ICON_URL = "iconUrl"; - public static final String EXTERNAL_URL = "externalURL"; - public static final String EXTERNAL_URL_DESCRIPTION = "externalUrlDescription"; - public static final String DESCRIPTION = "description"; - public static final String INVESTIGATOR = "investigator"; - public static final String AGE_GROUPS = "ageGroups"; - public static final String PHASES = "phases"; - public static final String CONDITIONS = "conditions"; - public static final String THERAPEUTIC_AREAS = "therapeuticAreas"; - public static final String VISIBILITY = "Visibility *:"; - public static final String STUDY_CONTAINER = "Study Container *:"; - public static final String DISPLAY_NAME = "Display Name:"; - - private static final Map DROPDOWN_FIELD_NAMES = new HashMap<>(); - static - { - DROPDOWN_FIELD_NAMES.put(STUDY_TYPE, "Study Type:"); - } - - private static final Map MULTI_SELECT_FIELD_NAMES = new HashMap<>(); - static - { - MULTI_SELECT_FIELD_NAMES.put(AGE_GROUPS, "Age Groups:"); - MULTI_SELECT_FIELD_NAMES.put(PHASES, "Phases:"); - MULTI_SELECT_FIELD_NAMES.put(CONDITIONS, "Conditions:"); - MULTI_SELECT_FIELD_NAMES.put(THERAPEUTIC_AREAS, "Therapeutic Areas:"); - } - - private static final Set FIELD_NAMES = new HashSet<>(); - static - { - FIELD_NAMES.add(SHORT_NAME); - FIELD_NAMES.add(STUDY_ID); - FIELD_NAMES.add(TITLE); - FIELD_NAMES.add(PARTICIPANT_COUNT); - FIELD_NAMES.add(STUDY_TYPE); - FIELD_NAMES.add(ICON_URL); - FIELD_NAMES.add(EXTERNAL_URL); - FIELD_NAMES.add(EXTERNAL_URL_DESCRIPTION); - FIELD_NAMES.add(DESCRIPTION); - FIELD_NAMES.add(INVESTIGATOR); - FIELD_NAMES.add(AGE_GROUPS); - FIELD_NAMES.add(PHASES); - FIELD_NAMES.add(CONDITIONS); - FIELD_NAMES.add(THERAPEUTIC_AREAS); - } - - public StudyEditPage(WebDriver driver) - { - super(driver); - } - - @Override - public Map getDropdownFieldNames() - { - return DROPDOWN_FIELD_NAMES; - } - - @Override - public Map getMultiSelectFieldNames() - { - return MULTI_SELECT_FIELD_NAMES; - } - - @Override - public Set getFieldNames() - { - return FIELD_NAMES; - } - - public void setStudyAccessVisibility(int panelIndex, String value) - { - elementCache().findStudyAccessVisibility(panelIndex).selectComboBoxItem(value); - } - - public void setStudyAccessStudyContainer(int panelIndex, String value) - { - elementCache().findStudyAccessStudyContainer(panelIndex).selectComboBoxItem(value); - } - - public void setStudyAccessDisplayName(int panelIndex, String value) - { - log("Setting study access display name to " + value); - WebElement studyAccessDisplayName = elementCache().findStudyAccessDisplayName(panelIndex); - setFormElement(studyAccessDisplayName, value); - fireEvent(studyAccessDisplayName, SeleniumEvent.blur); - sleep(1500); // we need to wait for the updated value to propagate through the Javascript store - } - - public String getStudyAccessDisplayNameValue(int panelIndex) - { - return getFormElement(elementCache().findStudyAccessDisplayName(panelIndex)); - } - - public int getStudyAccessCount() - { - return elementCache().findStudyAccessPanels().size(); - } - - public void setStudyAccessFormValues(int panelIndex, String visibility, String studyContainer, String displayName) - { - // wait for combo to load - sleep(1000); - setStudyAccessVisibility(panelIndex, visibility); - if (displayName != null) - setStudyAccessDisplayName(panelIndex, displayName); - // wait for combo to load - sleep(1000); - setStudyAccessStudyContainer(panelIndex, studyContainer); - } - - public void removeStudyAccessPanel(int panelIndex) - { - WebElement studyAccessPanelRemoveBtn = elementCache().getStudyAccessPanelRemoveBtn(panelIndex); - studyAccessPanelRemoveBtn.click(); - shortWait().until(ExpectedConditions.stalenessOf(studyAccessPanelRemoveBtn)); - } - - public void addStudyAccessPanel() - { - int initialCount = elementCache().findStudyAccessPanels().size(); - elementCache().addStudyAccessButton.click(); - elementCache().findStudyAccessPanel(initialCount); - } - - public void setStudyAccessFormValues(int i, Map newFields) - { - setStudyAccessFormValues(i, (String) newFields.get(VISIBILITY), (String) newFields.get(STUDY_CONTAINER), (String) newFields.get(DISPLAY_NAME)); - } - - @Override - protected String initialFocus() - { - return "shortName"; - } - - @Override - protected ElementCache elementCache() - { - return (ElementCache) super.elementCache(); - } - - @Override - protected CubeObjectEditPage.ElementCache newElementCache() - { - return new ElementCache(); - } - - protected class ElementCache extends CubeObjectEditPage.ElementCache - { - protected List findStudyAccessPanels() - { - return Locator.tagWithClass("div", "studyaccesspanel").findElements(this); - } - - protected WebElement findStudyAccessPanel(int panelIndex) - { - return Locator.tagWithClass("div", "studyaccesspanelindex" + panelIndex).waitForElement(this, 10000); - } - - public ComboBox findStudyAccessVisibility(int panelIndex) - { - return ComboBox.ComboBox(getDriver()).withLabel("Visibility *:").find(findStudyAccessPanel(panelIndex)); - } - - public ComboBox findStudyAccessStudyContainer(int panelIndex) - { - return ComboBox.ComboBox(getDriver()).withLabel("Study Container *:").find(findStudyAccessPanel(panelIndex)); - } - - public WebElement findStudyAccessDisplayName(int panelIndex) - { - return Locator.tagWithName("input", "displayName").findElement(findStudyAccessPanel(panelIndex)); - } - - public WebElement getStudyAccessPanelRemoveBtn(int panelIndex) - { - return Locator.tagWithClass("span", "fa-times").findElement(findStudyAccessPanel(panelIndex)); - } - - protected WebElement addStudyAccessButton = Locator.linkWithText("Add...").findWhenNeeded(this); - } -} diff --git a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java b/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java deleted file mode 100644 index 9160bfc7..00000000 --- a/test/src/org/labkey/test/tests/trialshare/DataFinderTestBase.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (c) 2016-2018 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.labkey.test.tests.trialshare; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.Locator; -import org.labkey.test.ModulePropertyValue; -import org.labkey.test.TestFileUtils; -import org.labkey.test.TestTimeoutException; -import org.labkey.test.WebTestHelper; -import org.labkey.test.pages.trialshare.DataFinderPage; -import org.labkey.test.util.APIContainerHelper; -import org.labkey.test.util.AbstractContainerHelper; -import org.labkey.test.util.ApiPermissionsHelper; -import org.labkey.test.util.ListHelper; -import org.labkey.test.util.LogMethod; -import org.labkey.test.util.PortalHelper; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; - -public abstract class DataFinderTestBase extends BaseWebDriverTest -{ - static final String MODULE_NAME = "TrialShare"; - static final String WEB_PART_NAME = "TrialShare Data Finder"; - static final String OPERATIONAL_STUDY_NAME = "DataFinderTestOperationalStudy"; - static final String PUBLIC_STUDY_NAME = "DataFinderTestPublicStudy"; - private static final String EMAIL_EXTENSION = "@datafinder.test"; - private static final String PUBLIC_READER_DISPLAY_NAME = "public_reader"; - static final String PUBLIC_READER = PUBLIC_READER_DISPLAY_NAME + EMAIL_EXTENSION; - private static final String CASALE_READER_DISPLAY_NAME = "casale_reader"; - static final String CASALE_READER = CASALE_READER_DISPLAY_NAME + EMAIL_EXTENSION; - private static final String WISPR_READER_DISPLAY_NAME = "wispr_reader"; - static final String WISPR_READER = WISPR_READER_DISPLAY_NAME + EMAIL_EXTENSION; - private static final String CONTROLLER = "trialshare"; - private static final String ACTION = "dataFinder"; - private static File dataListArchive = TestFileUtils.getSampleData("DataFinder.lists.zip"); - - public ApiPermissionsHelper _apiPermissionsHelper = new ApiPermissionsHelper(this); - - public enum CubeObjectType - { - study("StudyId", "Manage Studies", new String[]{"Study Id", "Short Name", "Title"}), - publication("Title", "Manage Publications", new String[]{"Key", "Title", "Status", "Publication Type"}); - - private String _keyField; - private String _manageDataTitle; - private List _manageDataHeaders; - - CubeObjectType(String keyField, String manageDataTitle, String[] manageDataHeaders) - { - _keyField = keyField; - _manageDataHeaders = Arrays.asList(manageDataHeaders); - _manageDataTitle = manageDataTitle; - } - - public String getKeyField() - { - return _keyField; - } - - public List getManageDataHeaders() - { - return _manageDataHeaders; - } - - public String getManageDataTitle() - { - return _manageDataTitle; - } - } - - static final Map> studySubsets = new HashMap<>(); - static - { - Set operationalSet = new HashSet<>(); - studySubsets.put("Operational", operationalSet); - operationalSet.add("TILT"); - operationalSet.add("WISP-R"); - operationalSet.add("ACCEPTOR"); - operationalSet.add("FACTOR"); - operationalSet.add("DIAMOND"); - - Set publicSet = new HashSet<>(); - studySubsets.put("Public", publicSet); - publicSet.add("DIAMOND"); - publicSet.add("Shapiro"); - publicSet.add("Casale"); - publicSet.add("Vincenti"); - } - - protected static Set loadedStudies = new HashSet<>(); - - static - { - loadedStudies.add("DataFinderTestPublicCasale"); - loadedStudies.add("DataFinderTestOperationalWISP-R"); - } - - @Override - protected void doCleanup(boolean afterTest) throws TestTimeoutException - { - _containerHelper.deleteProject(getProjectName(), afterTest); - _containerHelper.deleteProject(getDataProjectName(), afterTest); - _userHelper.deleteUsers(false, PUBLIC_READER, CASALE_READER, WISPR_READER); - } - - @BeforeClass - public static void initTest() - { - DataFinderTestBase init = (DataFinderTestBase) getCurrentTest(); - - init.setUpProject(); - } - - @Override - protected BrowserType bestBrowser() - { - return BrowserType.FIREFOX; - } - - @Override - public List getAssociatedModules() - { - return Collections.singletonList("TrialShare"); - } - - public abstract String getDataProjectName(); - - protected void setUpDataProject() - { - AbstractContainerHelper containerHelper = new APIContainerHelper(this); - - containerHelper.deleteProject(getDataProjectName(), false); - containerHelper.createProject(getDataProjectName(), "Custom"); - containerHelper.enableModule(MODULE_NAME); - goToProjectHome(getDataProjectName()); - importLists(); - createStudies(getDataProjectName()); - createUsers(); - - List propList = new ArrayList<>(); - // set the site-default value for this so it will work as expected from the Admin Console. - propList.add(new ModulePropertyValue("TrialShare", "/", "DataFinderCubeContainer", getDataProjectName())); - setModuleProperties(propList); - - reindexForSearch(); - - } - - protected void setUpProject() - { - setUpDataProject(); - - AbstractContainerHelper containerHelper = new APIContainerHelper(this); - - containerHelper.createProject(getProjectName(), "Custom"); - containerHelper.enableModule(MODULE_NAME); - makeProjectReadable(getProjectName()); - - new PortalHelper(this).addWebPart(WEB_PART_NAME); - } - - protected void importLists() - { - ListHelper listHelper = new ListHelper(this); - listHelper.importListArchive(dataListArchive); - } - - protected abstract void createStudies(String parentProjectName); - - protected abstract void createUsers(); - - protected void makeProjectReadable(String projectName) - { - goToProjectHome(projectName); - _apiPermissionsHelper.setSiteGroupPermissions("All Site Users", "Reader"); - } - - @LogMethod - protected void reindexForSearch() - { - log("Reindexing data for full-text search"); - goToAdminConsole().goToSettingsSection(); - clickAndWait(Locator.linkWithText("Data Cube")); - clickButton("Reindex", 0); - clickButton("OK"); - } - - void createStudy(String parentProjectName, String studyName, Boolean operational) - { - log("creating study " + studyName); - AbstractContainerHelper containerHelper = new APIContainerHelper(this); - File studyArchive = operational ? TestFileUtils.getSampleData(OPERATIONAL_STUDY_NAME + ".folder.zip") : TestFileUtils.getSampleData(PUBLIC_STUDY_NAME + ".folder.zip"); - containerHelper.createSubfolder(parentProjectName, studyName, "Study"); - importStudyFromZip(studyArchive, true, true); - } - - void createStudy(String parentProjectName, String name) - { - AbstractContainerHelper containerHelper = new APIContainerHelper(this); - - File studyArchive = TestFileUtils.getSampleData(name + ".folder.zip"); - containerHelper.createSubfolder(parentProjectName, name, "Study"); - importStudyFromZip(studyArchive, true, true); - } - - @Before - public void preTest() - { - switchToMainWindow(); - goToProjectHome(); - DataFinderPage finder = new DataFinderPage(this, true); - finder.clearSearch(); - try - { - finder.clearAllFilters(); - } - catch (NoSuchElementException ignore) {} - finder.dismissTour(); - } - - DataFinderPage goDirectlyToDataFinderPage(String containerPath, boolean testingStudies) - { - log("Going directly to data finder page"); - DataFinderPage finder = new DataFinderPage(this, testingStudies); - doAndWaitForPageSignal(() -> beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, ACTION)), finder.getCountSignal(), longWait()); - sleep(1000); // HACK! - if (!testingStudies) - { - finder.navigateToPublications(); - } - sleep(1000); // HACK! wait so that default filters can be applied - return finder; - } - - void goDirectlyToManageDataPage(String containerPath, CubeObjectType objectType) - { - log("Going directly to manage data page for " + objectType.toString()); - Map params = new HashMap<>(); - params.put("objectName", objectType.toString()); - params.put("query.viewName", "manageData"); - beginAt(WebTestHelper.buildURL(CONTROLLER, containerPath, "manageData", params)); - } -} \ No newline at end of file diff --git a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java b/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java deleted file mode 100644 index 250c084e..00000000 --- a/test/src/org/labkey/test/tests/trialshare/ManagePublicationsTest.java +++ /dev/null @@ -1,644 +0,0 @@ -/* - * Copyright (c) 2016-2018 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.test.tests.trialshare; - -import org.jetbrains.annotations.Nullable; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.labkey.test.Locator; -import org.labkey.test.WebTestHelper; -import org.labkey.test.categories.Git; -import org.labkey.test.components.trialshare.PublicationPanel; -import org.labkey.test.pages.trialshare.DataFinderPage; -import org.labkey.test.pages.trialshare.ManageDataPage; -import org.labkey.test.pages.trialshare.PublicationEditPage; -import org.labkey.test.pages.trialshare.PublicationsListHelper; -import org.labkey.test.pages.trialshare.StudiesListHelper; -import org.labkey.test.util.Maps; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.labkey.test.pages.trialshare.PublicationEditPage.EMPTY_VALUE; -import static org.labkey.test.pages.trialshare.PublicationEditPage.NOT_EMPTY_VALUE; -import static org.labkey.test.pages.trialshare.PublicationEditPage.TITLE; - -@Category({Git.class}) -public class ManagePublicationsTest extends DataFinderTestBase -{ - private CubeObjectType _objectType = CubeObjectType.publication; - - private static final String PUBLIC_STUDY_ID = "Casale"; - private static final String OPERATIONAL_STUDY_ID = "WISP-R"; - private static final String PROJECT_NAME = "ManagePublicationTest Project"; - private static final String DATA_PROJECT_NAME = "ManagePublicationsTestData Project"; - private static final String OPERATIONAL_STUDY_SUBFOLDER_NAME = "/" + DATA_PROJECT_NAME + "/" + OPERATIONAL_STUDY_NAME; - private static final String PUBLIC_STUDY_SUBFOLDER_NAME = "/" + DATA_PROJECT_NAME + "/" + PUBLIC_STUDY_NAME; - - private static final Map EXISTING_PUB_FIELDS = new HashMap<>(); - static - { - EXISTING_PUB_FIELDS.put(TITLE,"Efficacy of Remission-Induction Regimens for ANCA-Associated Vasculitis" ); - EXISTING_PUB_FIELDS.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.STATUS, "Complete"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.SUBMISSION_STATUS, "Submitted"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.AUTHOR, NOT_EMPTY_VALUE); - EXISTING_PUB_FIELDS.put(PublicationEditPage.CITATION, "New Eng J Med 2013; 369:417-427"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.YEAR, "2013"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.JOURNAL, "New England Journal of Medicine"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.ABSTRACT, NOT_EMPTY_VALUE); - EXISTING_PUB_FIELDS.put(PublicationEditPage.DOI, EMPTY_VALUE); - EXISTING_PUB_FIELDS.put(PublicationEditPage.PMID, "23902481"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.PMCID, EMPTY_VALUE); - EXISTING_PUB_FIELDS.put(PublicationEditPage.MANUSCRIPT_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); - EXISTING_PUB_FIELDS.put(PublicationEditPage.PERMISSIONS_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); - EXISTING_PUB_FIELDS.put(PublicationEditPage.KEYWORDS, EMPTY_VALUE); - EXISTING_PUB_FIELDS.put(PublicationEditPage.STUDIES, EMPTY_VALUE); - EXISTING_PUB_FIELDS.put(PublicationEditPage.THERAPEUTIC_AREAS, "Autoimmune"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.LINK1, "https://www.immunetolerance.org/sites/files/Specks_NEJM_2013.pdf"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.DESCRIPTION1, "Paper on immunetolerance.org"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.LINK2, "http://www.ncbi.nlm.nih.gov/pubmed/23902481"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.DESCRIPTION2, "PubMed.gov Citation"); - EXISTING_PUB_FIELDS.put(PublicationEditPage.LINK3, EMPTY_VALUE); - EXISTING_PUB_FIELDS.put(PublicationEditPage.DESCRIPTION3, EMPTY_VALUE); - } - - @Nullable - @Override - protected String getProjectName() - { - return PROJECT_NAME; - } - - @Override - public String getDataProjectName() { return DATA_PROJECT_NAME; } - - @Override - protected void createStudies(String parentProjectName) - { - createStudy(parentProjectName, PUBLIC_STUDY_NAME); - createStudy(parentProjectName, OPERATIONAL_STUDY_NAME); - } - - @Override - protected BrowserType bestBrowser() - { - return BrowserType.CHROME; - } - - @Override - protected void createUsers() - { - _userHelper.createUser(PUBLIC_READER); - - makeProjectReadable(getDataProjectName()); - clickFolder(PUBLIC_STUDY_NAME); - _apiPermissionsHelper.setUserPermissions(PUBLIC_READER, "Reader"); - } - - @Test - public void testInsertNewDataLinkPermissions() - { - log("Checking for insert new data link"); - DataFinderPage dataFinder = goDirectlyToDataFinderPage(getProjectName(), false); - Assert.assertTrue("Insert New link is not available", dataFinder.canInsertNewData()); - dataFinder.goToInsertNewData(); - switchToWindow(1); - waitForText("Insert Publication"); - getDriver().close(); - switchToMainWindow(); - log("Impersonating user without insert permission"); - goToProjectHome(); - impersonate(PUBLIC_READER); - goDirectlyToDataFinderPage(getProjectName(), false); - Assert.assertFalse("Insert New link should not be available", dataFinder.canManageData()); - } - - @Test - public void testManageDataLinkPermissions() - { - log("Checking for manage data link"); - DataFinderPage dataFinder = goDirectlyToDataFinderPage(getProjectName(), false); - Assert.assertTrue("Manage Data link is not available", dataFinder.canManageData()); - dataFinder.goToManageData(); - switchToWindow(1); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - Assert.assertTrue("No data shown for publication", manageData.getCount() > 0); - getDriver().close(); - switchToMainWindow(); - log("Impersonating user without insert permission"); - goToProjectHome(); - impersonate(PUBLIC_READER); - goDirectlyToDataFinderPage(getProjectName(), false); - Assert.assertFalse("Manage Data link should not be available", dataFinder.canManageData()); - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - assertTextPresent("User does not have permission"); - } - - @Test - public void testSwitchToStudies() - { - goDirectlyToManageDataPage(getProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - Assert.assertTrue("Should see a link to manage studies", manageData.hasManageStudiesLink()); - manageData.goToManageStudies(); - ManageDataPage manageStudiesData = new ManageDataPage(this, CubeObjectType.study); - Assert.assertTrue("Should be manage studies view", manageStudiesData.isManageDataView()); - } - - @Test - public void testGoToInsertNewAndCancel() - { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - manageData.goToInsertNew(); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - Assert.assertFalse("Submit button should not be enabled", editPage.isSubmitEnabled()); - doAndWaitForPageToLoad(editPage::cancel); - Assert.assertTrue("Should be manage publications view", manageData.isManageDataView()); - } - - @Test @Ignore("Finding the fields that are display/disabled fields is not yet implemented") - public void testViewDetails() - { - goToProjectHome(); - PublicationsListHelper pubUpdatePage = new PublicationsListHelper(this); - pubUpdatePage.setPermissionsContainer((String) EXISTING_PUB_FIELDS.get(TITLE), "/" + getProjectName() + "/" + OPERATIONAL_STUDY_NAME, true); - pubUpdatePage.setManuscriptContainer((String) EXISTING_PUB_FIELDS.get(TITLE), "/" + getProjectName() + "/" + PUBLIC_STUDY_NAME, false); - - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - // FIXME on the details page, many fields are not form fields, so finding them is more difficult - manageData.goToEditRecord((String) EXISTING_PUB_FIELDS.get(TITLE)); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - Map unexpectedValues = editPage.compareFormValues(EXISTING_PUB_FIELDS); - Assert.assertTrue("Found unexpected values: " + unexpectedValues, unexpectedValues.isEmpty()); - } - - @Test - public void testInvalidPMCID() - { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - manageData.goToEditRecord("Quality assessments of un-gated flow cytometry FCS files in a clinical trial setting"); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - editPage.setTextFormValue("PMCID", "invalid", false); - Assert.assertFalse("Submit should be disabled when invalid PMCID is input", editPage.isSubmitEnabled()); - } - - @Test - public void testRequiredFields() - { - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - manageData.goToInsertNew(); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - Assert.assertFalse("Submit button should not be enabled", editPage.isSubmitEnabled()); - editPage.setTextFormValue("title", "testRequiredFields"); - Assert.assertFalse("Submit button should not be enabled with only title", editPage.isSubmitEnabled()); - editPage.cancel(); - - manageData.goToInsertNew(); - editPage.setFormField(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); - Assert.assertFalse("Submit button should not be enabled with only publication type", editPage.isSubmitEnabled()); - editPage.cancel(); - - manageData.goToInsertNew(); - editPage.setFormField(PublicationEditPage.STATUS, "Complete"); - Assert.assertFalse("Submit button should not be enabled with only status", editPage.isSubmitEnabled()); - editPage.cancel(); - - manageData.goToInsertNew(); - editPage.setTextFormValue("title", "testRequiredFields"); - editPage.setFormField(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); - Assert.assertFalse("Submit button should not be enabled with title, publication type but no status", editPage.isSubmitEnabled()); - editPage.setFormField(PublicationEditPage.STATUS, "Complete"); - Assert.assertTrue("Submit button should be enabled with all required fields", editPage.isSubmitEnabled()); - } - - @Test - public void testSaveAndCloseReturnUrl() - { - goToProjectHome(); - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); - finder.clearAllFilters(); - DataFinderPage.DataCard card = finder.getDataCards().get(0); - PublicationPanel publicationPanel = card.viewDetail(); - publicationPanel.clickEditLink(); - switchToWindow(1); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - editPage.saveAndClose("Data Finder"); - sleep(1000); // yes, it's a hack. But everyone needs sleep at some point. - // we should go back to the finder page and have the publication tab active - Assert.assertEquals("Wrong tab on finder active after save and close", "Publications", finder.getSelectedFinderObject()); - getDriver().close(); - switchToMainWindow(); - } - - @Test - public void testCancelReturnUrl() - { - goToProjectHome(); - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); - finder.clearAllFilters(); - DataFinderPage.DataCard card = finder.getDataCards().get(0); - PublicationPanel publicationPanel = card.viewDetail(); - publicationPanel.clickEditLink(); - switchToWindow(1); - { - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - editPage.cancel(); - // we should go back to the finder page and have the publication tab active - Assert.assertEquals("Wrong tab on finder active after cancel", "Publications", new DataFinderPage(this, false).getSelectedFinderObject()); - getDriver().close(); - } - switchToMainWindow(); - } - - @Test - public void testInsertWithAllFields() - { - goToProjectHome(getDataProjectName()); - StudiesListHelper studiesListHelper = new StudiesListHelper(this); - studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); - - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - Map newFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - int count = manageData.getCount(); - newFields.put(PublicationEditPage.TITLE, "testInsertWithAllFields_" + count); - newFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); - newFields.put(PublicationEditPage.STATUS, "In Progress"); - newFields.put(PublicationEditPage.SUBMISSION_STATUS, "Submitted"); - newFields.put(PublicationEditPage.AUTHOR, "test1, test2"); - newFields.put(PublicationEditPage.CITATION, "test publications: v24, no 15"); - newFields.put(PublicationEditPage.YEAR, "2016"); - newFields.put(PublicationEditPage.JOURNAL, "Test Publications"); - // TODO this field is not editable like a text field. -// newFields.put(PublicationEditPage.ABSTRACT, "We are concrete."); - newFields.put(PublicationEditPage.DOI, "doi:123/445"); - newFields.put(PublicationEditPage.PMID, "1212"); - newFields.put(PublicationEditPage.PMCID, "PMC1411"); - newFields.put(PublicationEditPage.MANUSCRIPT_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); - newFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); - newFields.put(PublicationEditPage.KEYWORDS, "key words keywords"); - newFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); - newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); - newFields.put(PublicationEditPage.LINK1, "http://link/to.this"); - newFields.put(PublicationEditPage.DESCRIPTION1, "Link 1 Description"); - newFields.put(PublicationEditPage.LINK2, "http://also/link/to.this"); - newFields.put(PublicationEditPage.DESCRIPTION2, "Link 2 Description"); - newFields.put(PublicationEditPage.LINK3, "http://finally/link/to.this"); - newFields.put(PublicationEditPage.DESCRIPTION3, "Link 3 Description"); - - manageData.goToInsertNew(); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - - editPage.setFormFields(newFields, false); - editPage.save(); - Map unexpectedValues = editPage.compareFormValues(newFields); - Assert.assertTrue("Found unexpected values in edit page of newly inserted publication: " + unexpectedValues, unexpectedValues.isEmpty()); - - newFields.put(PublicationEditPage.TITLE, "testInsertWithAllFields_" + count + "saveAndClose"); - newFields.remove(PublicationEditPage.STUDIES); - newFields.remove(PublicationEditPage.THERAPEUTIC_AREAS); - editPage.setFormFields(newFields, false); - editPage.saveAndClose("Manage"); - manageData.goToEditRecord((String) newFields.get(TITLE)); - unexpectedValues = editPage.compareFormValues(newFields); - Assert.assertTrue("Found unexpected values in edit page of newly inserted publication: " + unexpectedValues, unexpectedValues.isEmpty()); - } - - @Test - public void testCountsAfterInsertAndEdit() - { - goToProjectHome(getDataProjectName()); - StudiesListHelper studiesListHelper = new StudiesListHelper(this); - studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); - goToProjectHome(); - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); - DataFinderPage.FacetGrid fg = finder.getFacetsGrid(); - finder.clearAllFilters(); - Map> beforeCounts = fg.getAllMemberCounts(); - - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - Map newFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - int count = manageData.getCount(); - newFields.put(PublicationEditPage.TITLE, "testCountsAfterInsertAndEdit_" + count); - newFields.put(PublicationEditPage.STATUS, "In Progress"); - newFields.put(PublicationEditPage.SUBMISSION_STATUS, "Submitted"); - newFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); - newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); - newFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); - newFields.put(PublicationEditPage.YEAR, "2016"); - - newFields.put(PublicationEditPage.MANUSCRIPT_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); - newFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); - - manageData.goToInsertNew(); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - - editPage.setFormFields(newFields, true); - editPage.saveAndClose("Manage"); - - goToProjectHome(); - goDirectlyToDataFinderPage(getProjectName(), false); - finder.clearAllFilters(); - Map> afterInsertCounts = fg.getAllMemberCounts(); - assertEquals("Count for 'In Progress' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.STATUS, "In Progress")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STATUS, "In Progress")); - assertEquals("Count for 'Submitted' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted")); - assertEquals("Count for 'Manuscript' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript")); - assertEquals("Count for 'Autoimmune' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Autoimmune")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Autoimmune")); - assertEquals("Count for '" + PUBLIC_STUDY_NAME + "' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PUB_STUDY, PUBLIC_STUDY_ID)+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PUB_STUDY, PUBLIC_STUDY_ID)); - assertEquals("Count for '2016' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.YEAR, "2016")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.YEAR, "2016")); - - - // Now edit the above fields to have different values and check counts again. - - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - manageData.goToEditRecord((String) newFields.get(TITLE)); - newFields.remove(PublicationEditPage.TITLE); - // not updating status since the default data has no other in-progress publications and making them all "complete" removes status and submission status - newFields.put(PublicationEditPage.SUBMISSION_STATUS, "Draft"); - newFields.put(PublicationEditPage.PUBLICATION_TYPE, "Abstract"); - // for these two multi-select fields, the items that are selected will be deselected and ones not selected will be selected - newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune", "Allergy"}); - newFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID, OPERATIONAL_STUDY_ID}); - newFields.put(PublicationEditPage.YEAR, "2014"); - - editPage.setFormFields(newFields, false); - editPage.saveAndClose("Manage"); - goToProjectHome(); - finder.selectDataFinderObject("Publications"); - finder.clearAllFilters(); - Map> afterEditCounts = fg.getAllMemberCounts(); - assertEquals("Count for 'In Progress' updated but should not have been", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STATUS, "In Progress"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STATUS, "In Progress")); - assertEquals("Count for 'Submitted' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Submitted")); - assertEquals("Count for 'Draft' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Draft")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.SUBMISISON_STATUS, "Draft")); - assertEquals("Count for 'Manuscript' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript")); - assertEquals("Count for 'Abstract' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Abstract")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PUBLICATION_TYPE, "Abstract")); - assertEquals("Count for 'Autoimmune' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Autoimmune"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Autoimmune")); - assertEquals("Count for 'Allergy' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Allergy")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Allergy")); - assertEquals("Count for '" + PUBLIC_STUDY_ID + "' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PUB_STUDY, PUBLIC_STUDY_ID), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PUB_STUDY, PUBLIC_STUDY_ID)); - assertEquals("Count for '" + OPERATIONAL_STUDY_ID + "' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PUB_STUDY, OPERATIONAL_STUDY_ID)+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PUB_STUDY, OPERATIONAL_STUDY_ID)); - assertEquals("Count for '2016' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.YEAR, "2016"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.YEAR, "2016")); - assertEquals("Count for '2014' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.YEAR, "2014")+1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.YEAR, "2014")); - } - - @Test - public void testInsertMultiValuedFields() - { - goToProjectHome(getDataProjectName()); - StudiesListHelper studiesListHelper = new StudiesListHelper(this); - studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); - - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - Map newFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - newFields.put(PublicationEditPage.TITLE, "testInsertMultiValuedFields_" + manageData.getCount()); - newFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); - newFields.put(PublicationEditPage.STATUS, "In Progress"); - - newFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID, OPERATIONAL_STUDY_ID}); - newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune", "Allergy"}); - - manageData.goToInsertNew(); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - - editPage.setFormFields(newFields, false); - editPage.saveAndClose("Manage"); - - manageData.goToEditRecord((String) newFields.get(TITLE)); - Map unexpectedValues = editPage.compareFormValues(newFields); - Assert.assertTrue("Found unexpected values in edit page of newly inserted publication: " + unexpectedValues, unexpectedValues.isEmpty()); - } - - @Test - public void testEditMultiValuedFields() - { - goToProjectHome(getDataProjectName()); - StudiesListHelper studiesListHelper = new StudiesListHelper(this); - studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); - - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - Map initialFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - initialFields.put(PublicationEditPage.TITLE, "testEditMultiValuedFields_" + manageData.getCount()); - initialFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); - initialFields.put(PublicationEditPage.STATUS, "In Progress"); - - initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); - initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune", "Allergy"}); - - manageData.goToInsertNew(); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - - editPage.setFormFields(initialFields, false); - editPage.saveAndClose("Manage"); - - manageData.goToEditRecord((String) initialFields.get(TITLE)); - - Map newFields = new HashMap<>(); - newFields.put(PublicationEditPage.STUDIES, new String[]{OPERATIONAL_STUDY_ID}); - newFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); - editPage.setFormFields(newFields, false); - editPage.saveAndClose("Manage"); - - manageData.goToEditRecord((String) initialFields.get(TITLE)); - initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID, OPERATIONAL_STUDY_ID}); - initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune", "Allergy", "T1DM"}); - - Map unexpectedValues = editPage.compareFormValues(initialFields); - Assert.assertTrue("Found unexpected values in edit page of updated publication: " + unexpectedValues, unexpectedValues.isEmpty()); - } - - @Test - public void testUpdatePublication() - { - goToProjectHome(getDataProjectName()); - StudiesListHelper studiesListHelper = new StudiesListHelper(this); - studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); - - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - Map initialFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - initialFields.put(PublicationEditPage.TITLE, "testUpdatePublication_" + manageData.getCount()); - initialFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); - initialFields.put(PublicationEditPage.STATUS, "In Progress"); - initialFields.put(PublicationEditPage.SUBMISSION_STATUS, "Submitted"); - initialFields.put(PublicationEditPage.AUTHOR, "test1, test2"); - initialFields.put(PublicationEditPage.CITATION, "test publications: v24, no 15"); - initialFields.put(PublicationEditPage.YEAR, "2016"); - initialFields.put(PublicationEditPage.JOURNAL, "Test Publications"); -// newFields.put(PublicationEditPage.ABSTRACT, "We are concrete."); - initialFields.put(PublicationEditPage.DOI, "doi:123/445"); - initialFields.put(PublicationEditPage.PMID, "1212"); - initialFields.put(PublicationEditPage.PMCID, "PMC1411"); - initialFields.put(PublicationEditPage.MANUSCRIPT_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); - initialFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); - initialFields.put(PublicationEditPage.KEYWORDS, "key words keywords"); - initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); - initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); - initialFields.put(PublicationEditPage.LINK1, "http://link/to.this"); - initialFields.put(PublicationEditPage.DESCRIPTION1, "Link 1 Description"); - initialFields.put(PublicationEditPage.LINK2, "http://also/link/to.this"); - initialFields.put(PublicationEditPage.DESCRIPTION2, "Link 2 Description"); - initialFields.put(PublicationEditPage.LINK3, "http://finally/link/to.this"); - initialFields.put(PublicationEditPage.DESCRIPTION3, "Link 3 Description"); - - manageData.goToInsertNew(); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - - editPage.setFormFields(initialFields, false); - editPage.saveAndClose("Manage"); - - Map updatedFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - int count = manageData.getCount(); - updatedFields.put(PublicationEditPage.TITLE, "testUpdatePublication_" + count + "_updated"); - updatedFields.put(PublicationEditPage.PUBLICATION_TYPE, "Abstract"); - updatedFields.put(PublicationEditPage.STATUS, "Complete"); - updatedFields.put(PublicationEditPage.SUBMISSION_STATUS, "Submitted"); - updatedFields.put(PublicationEditPage.AUTHOR, "test1, test2 updated"); - updatedFields.put(PublicationEditPage.CITATION, "test publications: v24, no 15 updated"); - updatedFields.put(PublicationEditPage.YEAR, "2015"); - updatedFields.put(PublicationEditPage.JOURNAL, "Test Publications updated"); - updatedFields.put(PublicationEditPage.DOI, "doi:123/445-u"); - updatedFields.put(PublicationEditPage.PMID, "1213"); - updatedFields.put(PublicationEditPage.PMCID, "PMC1412"); - updatedFields.put(PublicationEditPage.MANUSCRIPT_CONTAINER, OPERATIONAL_STUDY_SUBFOLDER_NAME); - updatedFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); - updatedFields.put(PublicationEditPage.KEYWORDS, "key words keywords updated"); - - // multi-value fields are tested separately - updatedFields.put(PublicationEditPage.LINK1, "http://link/to.this updated"); - updatedFields.put(PublicationEditPage.DESCRIPTION1, "Link 1 Description updated"); - updatedFields.put(PublicationEditPage.LINK2, "http://also/link/to.this updated"); - updatedFields.put(PublicationEditPage.DESCRIPTION2, "Link 2 Description updated"); - updatedFields.put(PublicationEditPage.LINK3, "http://finally/link/to.this updated"); - updatedFields.put(PublicationEditPage.DESCRIPTION3, "Link 3 Description updated"); - - manageData.goToEditRecord((String) initialFields.get(TITLE)); - Assert.assertTrue("Workbench button should be enabled if data has not changed", editPage.isWorkbenchEnabled()); - // Use JavaScript. WebElement.getAttribute("value") returns the display value rather than the value attribute - String studyId = (String) executeScript("return arguments[0].getAttribute('value');", Locator.name("studyIds").findElement(getDriver())); - String publicationId = getUrlParam("id"); - String workbenchContainer = "Studies/" + studyId + "OPR/Study Data"; - - clickButton("Workbench", 0); - - switchToWindow(1); - { - String url = getDriver().getCurrentUrl(); - // From workbench button handler in CubeObjectDetailsFormPanel.js - String expectedWorkbenchUrl = WebTestHelper.buildURL("project", workbenchContainer, "begin", Maps.of("pageId", "Manuscripts", "publicationId", publicationId)); - - Assert.assertEquals("Wrong url pointed to by the 'Workbench' button.", expectedWorkbenchUrl, url); - getDriver().close(); - } - switchToMainWindow(); - - editPage.setFormFields(updatedFields, false); - Assert.assertFalse("Workbench button should be disabled if data has changed", editPage.isWorkbenchEnabled()); - editPage.save(); - sleep(500); // HACK! Tests on Windows need a break here. - Map unexpectedValues = editPage.compareFormValues(updatedFields); - Assert.assertTrue("Found unexpected values in edit page of updated publication: " + unexpectedValues, unexpectedValues.isEmpty()); - } - - @Test - public void testInsertAndDelete() - { - goToProjectHome(getDataProjectName()); - StudiesListHelper studiesListHelper = new StudiesListHelper(this); - studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - Map initialFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - initialFields.put(PublicationEditPage.TITLE, "testInsertAndDelete_" + manageData.getCount()); - initialFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); - initialFields.put(PublicationEditPage.STATUS, "In Progress"); - initialFields.put(PublicationEditPage.STUDIES, new String[]{PUBLIC_STUDY_ID}); - initialFields.put(PublicationEditPage.THERAPEUTIC_AREAS, new String[]{"Autoimmune"}); - manageData.goToInsertNew(); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - editPage.setFormFields(initialFields, false); - editPage.saveAndClose("Manage"); - manageData.deleteRecord((String) initialFields.get(PublicationEditPage.TITLE)); - PublicationsListHelper listHelper = new PublicationsListHelper(this); - - goToProjectHome(getDataProjectName()); - Assert.assertEquals("Found deleted publication", 0, listHelper.getPublicationCount((String) initialFields.get(PublicationEditPage.TITLE), true)); - goToProjectHome(getDataProjectName()); - Assert.assertEquals("Found studies for deleted publication", 0, listHelper.getPublicationStudyCount((String) initialFields.get(PublicationEditPage.TITLE), true)); - goToProjectHome(getDataProjectName()); - Assert.assertEquals("Found therapeutic areas for deleted publication", 0, listHelper.getPublicationTherapeuticAreaCount((String) initialFields.get(PublicationEditPage.TITLE), true)); - } - - @Test - public void testInsertWithoutRefresh() - { - goToProjectHome(getDataProjectName()); - StudiesListHelper studiesListHelper = new StudiesListHelper(this); - studiesListHelper.setStudyContainer(PUBLIC_STUDY_ID, PUBLIC_STUDY_SUBFOLDER_NAME, true); - studiesListHelper.setStudyContainer(OPERATIONAL_STUDY_ID, OPERATIONAL_STUDY_SUBFOLDER_NAME, false); - - goDirectlyToManageDataPage(getCurrentContainerPath(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - Map initialFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - initialFields.put(PublicationEditPage.TITLE, "testInsertAndDelete_" + manageData.getCount()); - initialFields.put(PublicationEditPage.PUBLICATION_TYPE, "Manuscript"); - initialFields.put(PublicationEditPage.STATUS, "Complete"); - initialFields.put(PublicationEditPage.PERMISSIONS_CONTAINER, PUBLIC_STUDY_SUBFOLDER_NAME); - manageData.goToInsertNew(); - PublicationEditPage editPage = new PublicationEditPage(this.getDriver()); - - editPage.setFormFields(initialFields, true); - editPage.saveAndClose("Manage"); - - goToProjectHome(); // there should be no error alert after inserting but before refreshing - DataFinderPage finder = goDirectlyToDataFinderPage(getCurrentContainerPath(), false); - finder.clearAllFilters(); - finder.search((String) initialFields.get(PublicationEditPage.TITLE)); - List dataCards = finder.getDataCards(); - - assertEquals("Should find newly inserted publication", 1, dataCards.size()); - } -} \ No newline at end of file diff --git a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java b/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java deleted file mode 100644 index 9e296a56..00000000 --- a/test/src/org/labkey/test/tests/trialshare/ManageStudiesTest.java +++ /dev/null @@ -1,576 +0,0 @@ -/* - * Copyright (c) 2016-2018 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.test.tests.trialshare; - -import org.jetbrains.annotations.Nullable; -import org.junit.Assert; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.labkey.test.categories.Git; -import org.labkey.test.pages.trialshare.DataFinderPage; -import org.labkey.test.pages.trialshare.ManageDataPage; -import org.labkey.test.pages.trialshare.StudiesListHelper; -import org.labkey.test.pages.trialshare.StudyEditPage; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.Assert.assertEquals; - -@Category({Git.class}) -public class ManageStudiesTest extends DataFinderTestBase -{ - CubeObjectType _objectType = CubeObjectType.study; - - private static final String PROJECT_NAME = "ManageStudiesTest Project"; - private static final String DATA_PROJECT_NAME = "ManageStudiesTestData Project"; - - @Override - public BrowserType bestBrowser() - { - return BrowserType.CHROME; - } - - @Nullable - @Override - protected String getProjectName() - { - return PROJECT_NAME; - } - - @Override - public String getDataProjectName() { return DATA_PROJECT_NAME; } - - @Override - protected void createStudies(String parentProjectName) - { - } - - @Override - protected void createUsers() - { - _userHelper.createUser(PUBLIC_READER); - - makeProjectReadable(getDataProjectName()); - } - - @Test - public void testInsertNewDataLinkPermissions() - { - log("Checking for absence of insert new data link"); - DataFinderPage dataFinder = goDirectlyToDataFinderPage(getProjectName(), true); - Assert.assertFalse("Insert New link is shown for studies", dataFinder.canInsertNewData()); - } - - @Test - public void testManageDataLinkPermissions() - { - log("Checking for manage data link"); - DataFinderPage dataFinder = goDirectlyToDataFinderPage(getProjectName(), true); - Assert.assertTrue("Manage Data link is not available", dataFinder.canManageData()); - dataFinder.goToManageData(); - switchToWindow(1); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - Assert.assertTrue("No data shown for studies", manageData.getCount() > 0); - getDriver().close(); - switchToMainWindow(); - log("Impersonating user without insert permission"); - goToProjectHome(); - impersonate(PUBLIC_READER); - goDirectlyToDataFinderPage(getProjectName(), true); - Assert.assertFalse("Manage Data link should not be available", dataFinder.canManageData()); - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - assertTextPresent("User does not have permission"); - } - - @Test - public void testSwitchToPublications() - { - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - Assert.assertTrue("Should see a link to manage publications", manageData.hasManagePublicationsLink()); - manageData.goToManagePublications(); - ManageDataPage managePublicationsData = new ManageDataPage(this, CubeObjectType.publication); - Assert.assertTrue("Should be manage publications view", managePublicationsData.isManageDataView()); - } - - @Test - public void testGoToInsertNewAndCancel() - { - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - manageData.goToInsertNew(); - StudyEditPage editPage = new StudyEditPage(this.getDriver()); - Assert.assertFalse("Submit button should not be enabled", editPage.isSubmitEnabled()); - doAndWaitForPageToLoad(editPage::cancel); - Assert.assertTrue("Should be manage studies view", manageData.isManageDataView()); - } - - @Test - public void testRequiredFields() - { - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - manageData.goToInsertNew(); - StudyEditPage editPage = new StudyEditPage(this.getDriver()); - editPage.removeStudyAccessPanel(0); - Assert.assertFalse("Submit button should not be enabled", editPage.isSubmitEnabled()); - editPage.setTextFormValue("title", "testRequiredFields"); - Assert.assertFalse("Submit button should not be enabled with only title", editPage.isSubmitEnabled()); - doAndWaitForPageToLoad(editPage::cancel); - - manageData.goToInsertNew(); - editPage.removeStudyAccessPanel(0); - editPage.setTextFormValue("shortName", "ShortName"); - Assert.assertFalse("Submit button should not be enabled with only study type", editPage.isSubmitEnabled()); - doAndWaitForPageToLoad(editPage::cancel); - - manageData.goToInsertNew(); - editPage.removeStudyAccessPanel(0); - editPage.setTextFormValue("studyId", "StudyId"); - Assert.assertFalse("Submit button should not be enabled with only study id", editPage.isSubmitEnabled()); - doAndWaitForPageToLoad(editPage::cancel); - - manageData.goToInsertNew(); - editPage.removeStudyAccessPanel(0); - editPage.setTextFormValue("title", "testRequiredFields"); - editPage.setTextFormValue("shortName", "ShortName"); - Assert.assertFalse("Submit button should not be enabled with title, short name but no studyId", editPage.isSubmitEnabled()); - editPage.setTextFormValue("studyId", "StudyId", true); - Assert.assertTrue("Submit button should be enabled with all required study fields", editPage.isSubmitEnabled()); - editPage.cancel(); - } - - @Test - public void testInsertWithAllFields() - { - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - int count = manageData.getCount(); - Map newFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - newFields.put(StudyEditPage.SHORT_NAME, "TIWAF" + count); - newFields.put(StudyEditPage.STUDY_ID, "TIWAF_ID" + count); - newFields.put(StudyEditPage.TITLE, "testInsertWithAllFields_" + count); - newFields.put(StudyEditPage.PARTICIPANT_COUNT, String.valueOf(count)); - newFields.put(StudyEditPage.STUDY_TYPE, "Interventional"); - newFields.put(StudyEditPage.ICON_URL, "not your regular url"); - newFields.put(StudyEditPage.EXTERNAL_URL, "external url"); - // N.B. leaving out external URL description and Description fields because - // not sure how to attach to the iframe - newFields.put(StudyEditPage.INVESTIGATOR, "investigate"); - - newFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child"}); - newFields.put(StudyEditPage.PHASES, new String[]{"Phase 0"}); - newFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema"}); - newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); - - manageData.goToInsertNew(); - StudyEditPage editPage = new StudyEditPage(this.getDriver()); - - editPage.removeStudyAccessPanel(0); - editPage.setFormFields(newFields); - editPage.save(); - Map unexpectedValues = editPage.compareFormValues(newFields); - Assert.assertTrue("Found unexpected values in edit page of newly inserted study: " + unexpectedValues, unexpectedValues.isEmpty()); - - newFields.put(StudyEditPage.TITLE, "testInsertWithAllFields_" + count + "again"); - newFields.remove(StudyEditPage.THERAPEUTIC_AREAS); - newFields.remove(StudyEditPage.AGE_GROUPS); - newFields.remove(StudyEditPage.CONDITIONS); - newFields.remove(StudyEditPage.PHASES); - editPage.removeStudyAccessPanel(0); - editPage.setFormFields(newFields); - editPage.saveAndClose("Manage"); - manageData.goToEditRecord((String) newFields.get(StudyEditPage.STUDY_ID)); - unexpectedValues = editPage.compareFormValues(newFields); - Assert.assertTrue("Found unexpected values in edit page of newly inserted study: " + unexpectedValues, unexpectedValues.isEmpty()); - } - - @Test - public void testCountsAfterInsertAndEdit() - { - goToProjectHome(); - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); - DataFinderPage.FacetGrid fg = finder.getFacetsGrid(); - finder.clearAllFilters(); - Map> beforeCounts = fg.getAllMemberCounts(); - - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - int count = manageData.getCount(); - Map newFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - newFields.put(StudyEditPage.SHORT_NAME, "TCAIAE" + count); - newFields.put(StudyEditPage.STUDY_ID, "TCAIAE_ID" + count); - newFields.put(StudyEditPage.TITLE, "testCountsAfterInsertAndEdit_" + count); - - newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); - newFields.put(StudyEditPage.STUDY_TYPE, "Interventional"); - newFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child"}); - newFields.put(StudyEditPage.PHASES, new String[]{"Phase 0"}); - newFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema"}); - - manageData.goToInsertNew(); - StudyEditPage editPage = new StudyEditPage(this.getDriver()); - - editPage.setFormFields(newFields); - - Map studyAccessFields = new HashMap<>(); - studyAccessFields.put(StudyEditPage.VISIBILITY, "Public"); - studyAccessFields.put(StudyEditPage.STUDY_CONTAINER, "/home"); - - log("Set values for the first study access form"); - editPage.setStudyAccessFormValues(0, studyAccessFields); - editPage.saveAndClose("Manage"); - - goToProjectHome(); - goDirectlyToDataFinderPage(getProjectName(), true); - - Map> afterInsertCounts = fg.getAllMemberCounts(); - assertEquals("Count for 'T1DM' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "TIDM")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "T1DM")); - assertEquals("Count for 'Interventional' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.STUDY_TYPE, "Interventional")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STUDY_TYPE, "Interventional")); - assertEquals("Count for 'Child' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.AGE_GROUP, "Child")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.AGE_GROUP, "Child")); - assertEquals("Count for 'Phase 0' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PHASE, "Phase 0")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PHASE, "Phase 0")); - assertEquals("Count for 'Eczema' not updated", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.CONDITION, "Eczema")+1, fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.CONDITION, "Eczema")); - - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - manageData.goToEditRecord((String) newFields.get(StudyEditPage.STUDY_ID)); - newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"Transplant"}); // add transplant and leave T1DM - newFields.put(StudyEditPage.STUDY_TYPE, "Expanded Access"); - newFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Senior"}); // add senior and leave child - newFields.put(StudyEditPage.PHASES, new String[]{"Phase 0", "Phase 1"}); // remove Phase 0 and add Phase 1 - newFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema", "Allergy", "Cat Allergy"}); // remove Eczema and add two allergies - newFields.remove(StudyEditPage.SHORT_NAME); - newFields.remove(StudyEditPage.STUDY_ID); - newFields.remove(StudyEditPage.TITLE); - - editPage.setFormFields(newFields); - editPage.saveAndClose("Manage"); - goToProjectHome(); - goDirectlyToDataFinderPage(getProjectName(), true); - finder.clearAllFilters(); - Map> afterEditCounts = fg.getAllMemberCounts(); - assertEquals("Count for 'T1DM' updated when it should not have been", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "T1DM"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "T1DM")); - assertEquals("Count for 'Transplant' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Transplant") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.THERAPEUTIC_AREA, "Transplant")); - assertEquals("Count for 'Interventional' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.STUDY_TYPE, "Interventional"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STUDY_TYPE, "Intervetnional")); - assertEquals("Count for 'Expanded Access' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.STUDY_TYPE, "Expanded Access") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.STUDY_TYPE, "Expanded Access")); - assertEquals("Count for 'Child' updated when it should not have been", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.AGE_GROUP, "Child"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.AGE_GROUP, "Child")); - assertEquals("Count for 'Senior' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.AGE_GROUP, "Senior") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.AGE_GROUP, "Senior")); - assertEquals("Count for 'Phase 0' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.PHASE, "Phase 0"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PHASE, "Phase 0")); - assertEquals("Count for 'Phase 1' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.PHASE, "Phase 1") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.PHASE, "Phase 1")); - assertEquals("Count for 'Eczema' not updated to original value", fg.getSelectedCount(beforeCounts, DataFinderPage.Dimension.CONDITION, "Eczema"), fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.CONDITION, "Eczema")); - assertEquals("Count for 'Allergy' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.CONDITION, "Allergy") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.CONDITION, "Allergy")); - assertEquals("Count for 'Cat Allergy' not updated", fg.getSelectedCount(afterInsertCounts, DataFinderPage.Dimension.CONDITION, "Cat Allergy") + 1, fg.getSelectedCount(afterEditCounts, DataFinderPage.Dimension.CONDITION, "Cat Allergy")); - } - - @Test - public void testInsertMultiValuedFields() - { - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - int count = manageData.getCount(); - Map newFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - newFields.put(StudyEditPage.SHORT_NAME, "TIMVF" + count); - newFields.put(StudyEditPage.STUDY_ID, "TIMVF_ID" + count); - newFields.put(StudyEditPage.TITLE, "testInsertMultiValuedFields_" + count); - - // N.B. leaving out external URL description and Description fields because - // not sure how to attach to the iframe - newFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child", "Adult"}); - newFields.put(StudyEditPage.PHASES, new String[]{"Phase 0", "Phase 4"}); - newFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema", "Bone Marrow Transplantation", "Hay Fever"}); - newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM", "Allergy"}); - - manageData.goToInsertNew(); - StudyEditPage editPage = new StudyEditPage(this.getDriver()); - - editPage.removeStudyAccessPanel(0); - editPage.setFormFields(newFields); - editPage.saveAndClose("Manage"); - - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - - manageData.goToEditRecord((String) newFields.get(StudyEditPage.STUDY_ID)); - Map unexpectedValues = editPage.compareFormValues(newFields); - Assert.assertTrue("Found unexpected values in edit page of newly inserted publication: " + unexpectedValues, unexpectedValues.isEmpty()); - } - - @Test - public void testEditMultiValuedFields() - { - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - int count = manageData.getCount(); - Map initialFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - initialFields.put(StudyEditPage.SHORT_NAME, "TEMVF" + count); - initialFields.put(StudyEditPage.STUDY_ID, "TEMVF_ID" + count); - initialFields.put(StudyEditPage.TITLE, "testEditMultiValuedFields_" + count); - - // N.B. leaving out external URL description and Description fields because - // not sure how to attach to the iframe - initialFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child", "Adult"}); - initialFields.put(StudyEditPage.PHASES, new String[]{"Phase 0", "Phase 4"}); - initialFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema", "Bone Marrow Transplantation", "Hay Fever"}); - initialFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM", "Allergy"}); - - manageData.goToInsertNew(); - StudyEditPage editPage = new StudyEditPage(this.getDriver()); - - editPage.removeStudyAccessPanel(0); - editPage.setFormFields(initialFields); - editPage.saveAndClose("Manage"); - - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - - manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); - - Map newFields = new HashMap<>(); - // this removes "Child" and adds "Senior" - newFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child", "Senior"}); - // this removes both Phase 0 and Phase 4 - newFields.put(StudyEditPage.PHASES, new String[]{"Phase 0", "Phase 4"}); - // this adds "Allergy" and "Cat Allergy" - newFields.put(StudyEditPage.CONDITIONS, new String[]{"Allergy", "Cat Allergy"}); - // this removes "T1DM" - newFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); - editPage.removeStudyAccessPanel(0); - editPage.setFormFields(newFields); - editPage.saveAndClose("Manage"); - - - manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); - initialFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Adult", "Senior"}); - initialFields.put(StudyEditPage.PHASES, new String[]{}); - initialFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema", "Bone Marrow Transplantation", "Hay Fever", "Allergy", "Cat Allergy"}); - initialFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"Allergy"}); - - Map unexpectedValues = editPage.compareFormValues(initialFields); - Assert.assertTrue("Found unexpected values in edit page of updated publication: " + unexpectedValues, unexpectedValues.isEmpty()); - } - - @Test - public void testUpdateStudy() - { - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - int count = manageData.getCount(); - Map initialFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - initialFields.put(StudyEditPage.SHORT_NAME, "TUS" + count); - initialFields.put(StudyEditPage.STUDY_ID, "TUS_ID" + count); - initialFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count); - initialFields.put(StudyEditPage.PARTICIPANT_COUNT, String.valueOf(count)); - initialFields.put(StudyEditPage.STUDY_TYPE, "Interventional"); - initialFields.put(StudyEditPage.ICON_URL, "not your regular url"); - initialFields.put(StudyEditPage.EXTERNAL_URL, "external url"); - // N.B. leaving out external URL description and Description fields because - // not sure how to attach to the iframe - initialFields.put(StudyEditPage.INVESTIGATOR, "investigate"); - initialFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Child"}); - initialFields.put(StudyEditPage.PHASES, new String[]{"Phase 0"}); - initialFields.put(StudyEditPage.CONDITIONS, new String[]{"Eczema"}); - initialFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"T1DM"}); - - manageData.goToInsertNew(); - StudyEditPage editPage = new StudyEditPage(this.getDriver()); - - editPage.removeStudyAccessPanel(0); - editPage.setFormFields(initialFields); - editPage.saveAndClose("Manage"); - - Map updatedFields = new HashMap<>(); - updatedFields.put(StudyEditPage.SHORT_NAME, "TUS" + count + "_U"); - updatedFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count + "_updated"); - updatedFields.put(StudyEditPage.PARTICIPANT_COUNT, String.valueOf(count+1)); - updatedFields.put(StudyEditPage.STUDY_TYPE, "Observational"); - updatedFields.put(StudyEditPage.ICON_URL, "not your regular url updated"); - updatedFields.put(StudyEditPage.EXTERNAL_URL, "external url updated"); - // N.B. leaving out external URL description and Description fields because - // not sure how to attach to the iframe - updatedFields.put(StudyEditPage.INVESTIGATOR, "investigate updated"); - sleep(500); - manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); - editPage.setFormFields(updatedFields); - editPage.removeStudyAccessPanel(0); - editPage.save(); - Map unexpectedValues = editPage.compareFormValues(updatedFields); - Assert.assertTrue("Found unexpected values in edit page of updated study: " + unexpectedValues, unexpectedValues.isEmpty()); - } - - @Test - public void testInsertAndDelete() - { - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - int count = manageData.getCount(); - Map initialFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - initialFields.put(StudyEditPage.SHORT_NAME, "TIAD" + count); - initialFields.put(StudyEditPage.STUDY_ID, "TIAD_ID" + count); - initialFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count); - initialFields.put(StudyEditPage.AGE_GROUPS, new String[]{"Adult"}); - initialFields.put(StudyEditPage.PHASES, new String[]{"Phase 2"}); - initialFields.put(StudyEditPage.CONDITIONS, new String[]{"Asthma"}); - initialFields.put(StudyEditPage.THERAPEUTIC_AREAS, new String[]{"Allergy"}); - - manageData.goToInsertNew(); - StudyEditPage editPage = new StudyEditPage(this.getDriver()); - - editPage.removeStudyAccessPanel(0); - editPage.setFormFields(initialFields); - editPage.saveAndClose("Manage"); - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - - manageData.deleteRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); - - log("Finished deleting record " + initialFields.get(StudyEditPage.STUDY_ID) + ". Going home"); - StudiesListHelper listHelper = new StudiesListHelper(this); - - goToProjectHome(getDataProjectName()); - Assert.assertEquals("Found deleted study", 0, listHelper.getStudyCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); - goToProjectHome(getDataProjectName()); - Assert.assertEquals("Found age group(s) for deleted study", 0, listHelper.getStudyAgeGroupCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); - goToProjectHome(getDataProjectName()); - Assert.assertEquals("Found condition(s) for deleted study", 0, listHelper.getStudyConditionCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); - goToProjectHome(getDataProjectName()); - Assert.assertEquals("Found therapeutic area(s) for deleted study", 0, listHelper.getStudyTherapeuticAreaCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); - goToProjectHome(getDataProjectName()); - Assert.assertEquals("Found phase(s) for deleted study", 0, listHelper.getStudyPhaseCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); - goToProjectHome(getDataProjectName()); - Assert.assertEquals("Found study access data for deleted study", 0, listHelper.getStudyAccessCount((String) initialFields.get(StudyEditPage.STUDY_ID), true)); - } - - @Test - public void testInsertWithoutRefresh() - { - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - int count = manageData.getCount(); - String shortName = "TIWR" + count; - String studyId = "TIWR_ID" + count; - createStudy(getDataProjectName(), shortName, false); - - Map initialFields = new HashMap<>(); - // add the count so multiple runs of this test have distinct titles - initialFields.put(StudyEditPage.SHORT_NAME, shortName); - initialFields.put(StudyEditPage.STUDY_ID, studyId); - initialFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count); - - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - manageData.goToInsertNew(); - StudyEditPage editPage = new StudyEditPage(this.getDriver()); - - Map studyAccessFields = new HashMap<>(); - studyAccessFields.put(StudyEditPage.VISIBILITY, "Public"); - studyAccessFields.put(StudyEditPage.STUDY_CONTAINER, "/" + DATA_PROJECT_NAME + "/" + shortName); - studyAccessFields.put(StudyEditPage.DISPLAY_NAME, shortName); - - log("Set values for the first study access form"); - editPage.setStudyAccessFormValues(0, studyAccessFields); - - editPage.setFormFields(initialFields); - editPage.saveAndClose("Manage"); - - goToProjectHome(); // there should be no error alert after inserting but before refreshing - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); - finder.clearAllFilters(); - finder.search(studyId); - List dataCards = finder.getDataCards(); - - assertEquals("Should find newly inserted study", 1, dataCards.size()); - } - - @Test - public void testStudyAccessPanel() - { - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - ManageDataPage manageData = new ManageDataPage(this, _objectType); - - int count = manageData.getCount(); - String shortName = "TSAP" + count; - String studyId = "TSAP_ID" + count; - createStudy(getDataProjectName(), shortName, false); - - Map initialFields = new HashMap<>(); - initialFields.put(StudyEditPage.SHORT_NAME, shortName); - initialFields.put(StudyEditPage.STUDY_ID, studyId); - initialFields.put(StudyEditPage.TITLE, "testUpdateStudy_" + count); - - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - manageData.goToInsertNew(); - StudyEditPage editPage = new StudyEditPage(this.getDriver()); - editPage.setFormFields(initialFields); - - String firstVisibility = "Public"; - String firstDisplayName = shortName + " " + firstVisibility; - - Map studyAccessFields = new HashMap<>(); - studyAccessFields.put(StudyEditPage.VISIBILITY, firstVisibility); - studyAccessFields.put(StudyEditPage.STUDY_CONTAINER, "/" + DATA_PROJECT_NAME + "/" + shortName); - studyAccessFields.put(StudyEditPage.DISPLAY_NAME, firstDisplayName); - - log("Set values for the first study access form"); - editPage.setStudyAccessFormValues(0, studyAccessFields); - - String secondVisibility = "Operational"; - String secondDisplayName = shortName + " " + secondVisibility; - - Map secondStudyAccessFields = new HashMap<>(); - secondStudyAccessFields.put(StudyEditPage.VISIBILITY, secondVisibility); - secondStudyAccessFields.put(StudyEditPage.STUDY_CONTAINER, "/" + DATA_PROJECT_NAME + "/" + shortName); - secondStudyAccessFields.put(StudyEditPage.DISPLAY_NAME, secondDisplayName); - - log("Add another study access record"); - editPage.addStudyAccessPanel(); - log("Set values for the second study access form"); - editPage.setStudyAccessFormValues(1, secondStudyAccessFields); - - editPage.saveAndClose("Manage"); - - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); - - //wait for combo store to load - assertEquals("Display Name value for the first Study Access record is incorrect", firstDisplayName, editPage.getStudyAccessDisplayNameValue(0)); - assertEquals("Display Name value for the second Study Access record is incorrect", secondDisplayName, editPage.getStudyAccessDisplayNameValue(1)); - - log("Remove the second study access record"); - editPage.removeStudyAccessPanel(1); - - log("Change study access display name"); - firstDisplayName = firstDisplayName + "_updated"; - editPage.setStudyAccessDisplayName(0, firstDisplayName); - editPage.saveAndClose("Manage"); - - goDirectlyToManageDataPage(getDataProjectName(), _objectType); - manageData.goToEditRecord((String) initialFields.get(StudyEditPage.STUDY_ID)); - log("Verify the second study access record is deleted successfully"); - assertEquals(1, editPage.getStudyAccessCount()); - - assertEquals("Failed to update Display Name field", firstDisplayName, editPage.getStudyAccessDisplayNameValue(0)); - } -} \ No newline at end of file diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java deleted file mode 100644 index c243f3bb..00000000 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareDataFinderTest.java +++ /dev/null @@ -1,588 +0,0 @@ -/* - * Copyright (c) 2016-2018 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.labkey.test.tests.trialshare; - -import org.apache.commons.lang3.StringUtils; -import org.junit.Assert; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.labkey.test.Locator; -import org.labkey.test.categories.Git; -import org.labkey.test.components.trialshare.PublicationPanel; -import org.labkey.test.components.trialshare.StudySummaryWindow; -import org.labkey.test.pages.PermissionsEditor; -import org.labkey.test.pages.trialshare.DataFinderPage; -import org.labkey.test.pages.trialshare.PublicationsListHelper; -import org.labkey.test.pages.trialshare.StudiesListHelper; -import org.labkey.test.util.LogMethod; -import org.labkey.test.util.ReadOnlyTest; -import org.openqa.selenium.support.ui.ExpectedConditions; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; - -@Category({Git.class}) -public class TrialShareDataFinderTest extends DataFinderTestBase implements ReadOnlyTest -{ - private static final String PROJECT_NAME = "TrialShareDataFinderTest Project"; - private static final String DATA_PROJECT_NAME = "TrialShareDataFinderTestData Project"; - - @Override - protected String getProjectName() - { - return PROJECT_NAME; - } - @Override - protected BrowserType bestBrowser() - { - return BrowserType.CHROME; - } - @Override - public String getDataProjectName() { return DATA_PROJECT_NAME; } - - @Override - protected void setUpProject() - { - if (needsSetup()) - super.setUpProject(); - } - - @Override - public boolean needsSetup() - { - if (!_studyHelper.doesStudyExist(getDataProjectName() + "/" + PUBLIC_STUDY_NAME)) - return true; - if (!_studyHelper.doesStudyExist(getDataProjectName() + "/" + OPERATIONAL_STUDY_NAME)) - return true; - return !_containerHelper.doesContainerExist(getProjectName()); - } - - @Override - protected void createStudies(String parentProjectName) - { - log("Creating a study container for each study"); - for (String subset : studySubsets.keySet()) - { - for (String accession : studySubsets.get(subset)) - { - String name = "DataFinderTest" + subset + accession; - createStudy(getDataProjectName(), name, subset.equalsIgnoreCase("operational")); - } - } - createStudy(parentProjectName, PUBLIC_STUDY_NAME); - createStudy(parentProjectName, OPERATIONAL_STUDY_NAME); - goToProjectHome(getDataProjectName()); - StudiesListHelper queryUpdatePage = new StudiesListHelper(this); - queryUpdatePage.setStudyContainers(); - goToProjectHome(getDataProjectName()); - PublicationsListHelper pubUpdatePage = new PublicationsListHelper(this); - pubUpdatePage.setPermissionsContainers("/" + getDataProjectName() + "/" + PUBLIC_STUDY_NAME, "/" + getDataProjectName() + "/" + OPERATIONAL_STUDY_NAME); - } - - @Override - protected void createUsers() - { - log("Creating users and setting permissions"); - goToProjectHome(getDataProjectName()); - - _userHelper.createUser(PUBLIC_READER); - _userHelper.createUser(CASALE_READER); - _userHelper.createUser(WISPR_READER); - - PermissionsEditor permissionsEditor = new PermissionsEditor(this); - - goToProjectHome(getDataProjectName()); - clickAdminMenuItem("Folder", "Permissions"); - permissionsEditor.setSiteGroupPermissions("All Site Users", "Reader"); - - for (String subset : studySubsets.keySet()) - { - for (String accession : studySubsets.get(subset)) - { - String name = "DataFinderTest" + subset + accession; - doAndWaitForPageToLoad(() -> permissionsEditor.selectFolder(name)); - sleep(500); // HACK, but waitForPageLoad doesn't do the trick here. Perhaps waitForElement would work... - if (subset.equalsIgnoreCase("public")) - { - _apiPermissionsHelper.setUserPermissions(PUBLIC_READER, "Reader"); - _apiPermissionsHelper.setUserPermissions(WISPR_READER, "Reader"); - } - if (accession.equalsIgnoreCase("Casale")) - { - _apiPermissionsHelper.setUserPermissions(CASALE_READER, "Reader"); - } - else if (accession.equalsIgnoreCase("WISP-R")) - { - _apiPermissionsHelper.setUserPermissions(WISPR_READER, "Reader"); - } - } - } - permissionsEditor.selectFolder(PUBLIC_STUDY_NAME); - _apiPermissionsHelper.setUserPermissions(PUBLIC_READER, "Reader"); - _apiPermissionsHelper.setUserPermissions(WISPR_READER, "Reader"); - } - - @Test - public void testCounts() - { - DataFinderPage finder = new DataFinderPage(this, true); - assertCountsSynced(finder, DataFinderPage.Dimension.SUMMARY_STUDIES); - - Map studyCounts = finder.getSummaryCounts(); - - for (Map.Entry count : studyCounts.entrySet()) - { - if (count.getKey().getCaption() != null) - assertNotEquals("No " + count.getKey().getCaption(), 0, count.getValue().intValue()); - } - } - - @Test - public void testStudyCards() - { - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); - - List studyCards = finder.getDataCards(); - - studyCards.get(0).viewStudySummary(); - } - - @Test - public void testStudySubset() - { - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); - - DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); - Set linkedStudyNames = new HashSet<>(); - for (String subset : studySubsets.keySet()) - { - log("Toggle facet: " + subset); - facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, subset); - List studyCards = finder.getDataCards(); - Set studies = new HashSet<>(); - for (DataFinderPage.DataCard studyCard : studyCards) - { - studies.add(studyCard.getStudyShortName()); - } - assertEquals("Wrong study cards for studies", studySubsets.get(subset), studies); - facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, subset); - linkedStudyNames.addAll(getTexts(Locator.tagWithClass("div", "labkey-study-card").withPredicate(Locator.linkWithText("go to study")) - .append(Locator.tagWithClass("span", "labkey-study-card-short-name")).findElements(getDriver()))); - } - } - - @Test - public void testPublicAccess() - { - goToProjectHome(); - impersonate(PUBLIC_READER); - DataFinderPage finder = new DataFinderPage(this, true); - DataFinderPage.FacetGrid facetGrid = finder.getFacetsGrid(); - Assert.assertFalse("Public user should not see the visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); - List cards = finder.getDataCards(); - Assert.assertEquals("Number of studies not as expected", studySubsets.get("Public").size(), cards.size()); - stopImpersonating(); - doAndWaitForPageSignal(this::goToProjectHome, finder.getCountSignal()); - - sleep(1000); - Assert.assertTrue("Admin user should see visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); - - doAndWaitForPageSignal(() -> impersonate(CASALE_READER), finder.getCountSignal()); - Assert.assertFalse("User with access to only Casale study should not see the visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); - cards = finder.getDataCards(); - Assert.assertEquals("User with access to only Casale study should see only that study", 1, cards.size()); - stopImpersonating(); - } - - @Test - public void testOperationalAccess() - { - goToProjectHome(); - impersonate(WISPR_READER); - DataFinderPage finder = new DataFinderPage(this, true); - DataFinderPage.FacetGrid facetGrid = finder.getFacetsGrid(); - Assert.assertTrue("Operational user should see visibility facet", facetGrid.facetIsPresent(DataFinderPage.Dimension.VISIBILITY)); - facetGrid.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"); - List cards = finder.getDataCards(); - Assert.assertEquals("User with access to only WISP-R study should see only that study", 1, cards.size()); - stopImpersonating(); - } - - @Test - public void testSelection() - { - DataFinderPage finder = new DataFinderPage(this, true); - - DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); - facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Public"); - facets.toggleFacet(DataFinderPage.Dimension.AGE_GROUP, "Adult"); - facets.toggleFacet(DataFinderPage.Dimension.PHASE, "Phase 1"); - - assertCountsSynced(finder, DataFinderPage.Dimension.SUMMARY_STUDIES); - - facets.clearFilter(DataFinderPage.Dimension.PHASE); - - assertEquals("Clearing Phase filters did not remove selection", Collections.emptyList(), facets.getSelectedMembers(DataFinderPage.Dimension.PHASE)); - - // re-select - facets.toggleFacet(DataFinderPage.Dimension.PHASE, "Phase 1"); - // deselect - facets.toggleFacet(DataFinderPage.Dimension.AGE_GROUP, "Adult"); - assertEquals("Clearing selection did not remove selection", Collections.emptyList(), facets.getSelectedMembers(DataFinderPage.Dimension.AGE_GROUP)); - assertEquals("Clearing selection removed other filter", Collections.singletonList("Phase 1"), facets.getSelectedMembers(DataFinderPage.Dimension.PHASE)); - - finder.clearAllFilters(); - assertEquals("Clearing all filters didn't clear selection", Collections.emptyList(), facets.getSelectedMembers(DataFinderPage.Dimension.PHASE)); - - assertCountsSynced(finder, DataFinderPage.Dimension.SUMMARY_STUDIES); - } - - @Test - public void testSelectingEmptyMeasure() - { - Map expectedCounts = new HashMap<>(); - expectedCounts.put(DataFinderPage.Dimension.SUMMARY_STUDIES, 0); - expectedCounts.put(DataFinderPage.Dimension.SUBJECTS, 0); - - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); - DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); - facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"); - facets.toggleFacet(DataFinderPage.Dimension.CONDITION, "Allergy"); - - facets = finder.getFacetsGrid(); - List filteredStudyCards = finder.getDataCards(); - assertEquals("Study cards visible after selection", 0, filteredStudyCards.size()); - - Map filteredSummaryCounts = finder.getSummaryCounts(); - assertEquals("Wrong counts after selecting empty measure", expectedCounts, filteredSummaryCounts); - - for (DataFinderPage.Dimension dimension : DataFinderPage.Dimension.values()) - { - if (dimension.getHierarchyName() != null) - { - Map memberCounts = facets.getMemberCounts(dimension); - for (Map.Entry memberCount : memberCounts.entrySet()) - { - assertEquals("Wrong counts for member " + memberCount.getKey() + " of dimension " + dimension + " after selecting empty measure", 0, memberCount.getValue().getSelectedCount()); - } - } - } - } - - @Test - public void testStudySearch() - { - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); - - DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); - facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Operational"); - - - List studyCards = finder.getDataCards(); - String searchString = studyCards.get(0).getStudyAccession(); - - testSearchTerm(finder, DataFinderPage.Dimension.SUMMARY_STUDIES, "Study Accession", searchString, 1); - finder.clearSearch(); - assertTrue("Clear all should still be active after clearing search with facet selected", finder.clearAllActive()); - finder.clearAllFilters(); - assertTrue("Clear All should no longer be active", !finder.clearAllActive()); - testSearchTerm(finder, DataFinderPage.Dimension.SUMMARY_STUDIES, "Study Short Name and Investigator", "Casale", 1); - testSearchTerm(finder, DataFinderPage.Dimension.SUMMARY_STUDIES, "Empty", "", 8); - testSearchTerm(finder, DataFinderPage.Dimension.SUMMARY_STUDIES, "Title", "System", 2); - testSearchTerm(finder, DataFinderPage.Dimension.SUMMARY_STUDIES, "Multiple Terms", "Tolerant Kidney Transplant", 7); - } - - - @Test - public void testStudySearchPermissions() - { - impersonate(PUBLIC_READER); - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); - - finder.search("transplant"); - - List studyCards = finder.getDataCards(); - - assertEquals("Wrong number of studies after search", 2, studyCards.size()); - assertEquals("Wrong study card available", "Shapiro", studyCards.get(0).getStudyShortName()); - - assertCountsSynced(finder, DataFinderPage.Dimension.SUMMARY_STUDIES); - stopImpersonating(); - } - - @Test - public void testPublicationSearch() - { - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); - finder.clearAllFilters(); - - testSearchTerm(finder, DataFinderPage.Dimension.PUBLICATIONS, "Author", "Asare", 7); - testSearchTerm(finder, DataFinderPage.Dimension.PUBLICATIONS, "PMID", "21953143", 1); - testSearchTerm(finder, DataFinderPage.Dimension.PUBLICATIONS, "Keyword", "Alemtuzier", 1); - testSearchTerm(finder, DataFinderPage.Dimension.PUBLICATIONS, "Abstract", "Lorem Ipsum", 1); - testSearchTerm(finder, DataFinderPage.Dimension.PUBLICATIONS, "Citation", "New Eng", 2); - testSearchTerm(finder, DataFinderPage.Dimension.PUBLICATIONS, "Study, facet value, regular word", "FACTOR", 3); - testSearchTerm(finder, DataFinderPage.Dimension.PUBLICATIONS, "Journal facet", "\"New England Journal\"", 1); - testSearchTerm(finder, DataFinderPage.Dimension.PUBLICATIONS, "Empty", "", 16); - - finder.clearSearch(); - assertTrue("Clear All should no longer be active", !finder.clearAllActive()); - } - - private void testSearchTerm(DataFinderPage finder, DataFinderPage.Dimension dimension, String field, String term, int expectedCount) - { - List dataCards = finder.getDataCards(); - finder.search(term); - if (expectedCount > 0) - shortWait().until(ExpectedConditions.stalenessOf(dataCards.get(0).getCardElement())); - if (!StringUtils.isEmpty(term)) - { - assertTrue("Clear All should be active after entering search term", finder.clearAllActive()); - } - dataCards = finder.getDataCards(); - assertEquals("Wrong number of cards after search for '" + term + "' (" + field + ")", expectedCount, dataCards.size()); - assertCountsSynced(finder, dimension); - } - - - @Test - public void testPublicationSearchPermissions() - { - impersonate(PUBLIC_READER); - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); - finder.search("Progress"); - List dataCards = finder.getDataCards(); - - assertEquals("Wrong number of cards after search", 0, dataCards.size()); - - assertCountsSynced(finder, DataFinderPage.Dimension.PUBLICATIONS); - stopImpersonating(); - } - - @Test - public void testStudySummaryWindow() - { - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); - - DataFinderPage.DataCard studyCard = finder.getDataCards().get(0); - - StudySummaryWindow summaryWindow = studyCard.viewStudySummary(); - - assertEquals("Study card does not match summary (Accession)", studyCard.getStudyAccession().toLowerCase(), summaryWindow.getAccession().toLowerCase()); - assertEquals("Study card does not match summary (Short Name)", studyCard.getStudyShortName().toLowerCase(), summaryWindow.getShortName().toLowerCase()); - assertEquals("Study card does not match summary (Title)", studyCard.getTitle().toUpperCase(), summaryWindow.getTitle().toUpperCase()); - - summaryWindow.closeWindow(); - } - - @Test - public void testGoToStudyMenu() - { - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); - DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); - log("Filtering to show DIAMOND card with two study containers"); - facets.toggleFacet(DataFinderPage.Dimension.VISIBILITY, "Public"); - doAndWaitForPageSignal(() -> facets.toggleFacet(DataFinderPage.Dimension.CONDITION, "Lupus Nephritis"), finder.getCountSignal()); - List dataCards = finder.getDataCards(); - Assert.assertEquals("Should have a single data card at this point", 1, dataCards.size()); - DataFinderPage.DataCard card = dataCards.get(0); - Assert.assertEquals("DIAMOND", card.getStudyShortName()); - log("Go to operational study"); - card.clickGoToStudy("/" + getDataProjectName() + "/DataFinderTestOperationalDIAMOND"); - } - - @Test - public void testGoToStudyNoMenuForPublicReader() - { - log("Impersonating public reader who should see only one go to study link"); - impersonate(PUBLIC_READER); - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), true); - DataFinderPage.FacetGrid facets = finder.getFacetsGrid(); - log("Filtering to show DIAMOND card"); - doAndWaitForPageSignal(() -> facets.toggleFacet(DataFinderPage.Dimension.CONDITION, "Lupus Nephritis"), finder.getCountSignal()); - List dataCards = finder.getDataCards(); - Assert.assertEquals("Should have a single data card at this point", 1, dataCards.size()); - DataFinderPage.DataCard card = dataCards.get(0); - Assert.assertEquals("DIAMOND", card.getStudyShortName()); - card.clickGoToStudy(); - stopImpersonating(); - } - - @Test - public void testSwitchBetweenStudyAndPublication() - { - DataFinderPage finder = new DataFinderPage(this, true); - log("Start at home."); - doAndWaitForPageSignal(() -> goToProjectHome(), finder.getCountSignal()); - waitForElement(DataFinderPage.Locators.studyFinder); - log("Click the 'Publications' tab"); - finder.navigateToPublications(); - log("Go back by clicking the 'Studies' tab"); - finder.navigateToStudies(); - } - - @Test - public void testFilterOnStatus() - { - String cardTitle = "Circulating markers of vascular injury"; - String cardAuthors = "Monach PA, Tomasson G, Specks U, et al."; - String cardText; - Map counts; - - log("Go to publications and verify the default filtered of 'In Progress' on Status."); - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); - DataFinderPage.FacetGrid fg = finder.getFacetsGrid(); - assertEquals("Finder is not filtered by In Progress by default for internal user", Collections.singletonList(DataFinderPage.Dimension.IN_PROGRESS.getHierarchyName()), fg.getSelectedMembers(DataFinderPage.Dimension.STATUS)); - - log("Validate that the number, content and style of the cards is as expected."); - counts = fg.getMemberCounts(DataFinderPage.Dimension.IN_PROGRESS); - assertEquals("Expected count after filtering for 'In Progress' was not as expected.", 1, counts.get("In Progress").getSelectedCount()); - assertEquals("Expected count after filtering for 'In Progress' was not as expected.", 1, counts.get("In Progress").getTotalCount()); - - // I have no idea why assertTextPresent returned false for these strings. The below tests appear to be more reliable. - scrollIntoView(DataFinderPage.Locators.pubCardBorderHighlight); - cardText = getText(DataFinderPage.Locators.pubCardBorderHighlight); - assertTrue("Could not find '" + cardTitle + "' on card.", cardText.contains(cardTitle)); - assertTrue("Could not find '" + cardAuthors + "' on card.", cardText.contains(cardAuthors)); - - log("Validate that there is only one publication card present and has the correct style."); - assertElementPresent(DataFinderPage.Locators.pubCard, 1); - assertElementVisible(DataFinderPage.Locators.pubCardBorderHighlight); - assertElementVisible(DataFinderPage.Locators.pubCardBackgroundHighlight); - assertElementPresent(DataFinderPage.Locators.pubCardBorderHighlight, 1); - assertElementPresent(DataFinderPage.Locators.pubCardBackgroundHighlight, 1); - - log("Remove existing filters, and apply the 'Complete' filter."); - finder.clearAllFilters(); - fg.toggleFacet(DataFinderPage.Dimension.STATUS, "Complete"); - - log("Validate counts for 'Complete' publications."); - counts = fg.getMemberCounts(DataFinderPage.Dimension.COMPLETE); - // one is "in progress" and one is set to not show - assertEquals("Expected count after filtering for 'Complete' was not as expected.", 15, counts.get("Complete").getSelectedCount()); - assertEquals("Expected count after filtering for 'Complete' was not as expected.", 15, counts.get("Complete").getTotalCount()); - - log("Validate that there are no 'In Progress' cards visible."); - assertElementNotPresent("There is a card with the 'In Progress' style, there should not be.", DataFinderPage.Locators.pubCardBorderHighlight); - - } - - @Test - public void testPublicationStatusForReader() - { - impersonate(PUBLIC_READER); - log("Go to publications and clear any filters that may have been set."); - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); - finder.clearAllFilters(); - Map summaryCount = finder.getSummaryCounts(); - DataFinderPage.FacetGrid fg = finder.getFacetsGrid(); - Assert.assertFalse("Status facet should not be present for someone with only read permission", fg.facetIsPresent(DataFinderPage.Dimension.STATUS)); - - // one publication has "show on dash" set to false; one publication is "in progress" and thus not visible to the public - Assert.assertEquals("Publication count should not count incomplete publications", (Integer) 15, summaryCount.get(DataFinderPage.Dimension.PUBLICATIONS)); - stopImpersonating(); - } - - @Test - public void testPublicationDetail() - { - DataFinderPage.FacetGrid fg; - Map summaryCount; - - - log("Go to publications and clear any filters that may have been set."); - DataFinderPage finder = goDirectlyToDataFinderPage(getProjectName(), false); - - finder.clearAllFilters(); - - log("Filter for a publication that has DOI, PMID and PMCID values."); - fg = finder.getFacetsGrid(); - doAndWaitForPageSignal(() -> fg.toggleFacet(DataFinderPage.Dimension.STATUS, "In Progress"), finder.getCountSignal()); - - - summaryCount = finder.getSummaryCounts(); - assertTrue("Number of publication cards returned does not match dimension count. Number of cards: " + finder.getDataCards().size() + " Count in dimension: " + summaryCount.get(DataFinderPage.Dimension.PUBLICATIONS), summaryCount.get(DataFinderPage.Dimension.PUBLICATIONS) == finder.getDataCards().size()); - - log("Click the 'More Details' and validate that the detail content is as expected."); - DataFinderPage.DataCard card = finder.getDataCards().get(0); - PublicationPanel publicationPanel = card.viewDetail(); - - assertTrue("Author value not as expected on detail page: " + publicationPanel.getAuthor(), publicationPanel.getAuthor().contains("Monach PA, Tomasson G, Specks U, Stone JH, Cuthbertson D")); - assertTrue("Title value not as expected on detail page:" + publicationPanel.getTitle(), publicationPanel.getTitle().contains("Circulating markers of vascular injury and angiogenesis in Antineutrophil Cytoplasmic Antibody-Associated Vasculitis.")); - assertTrue("Citation value not as expected on detail page:" + publicationPanel.getCitation(), publicationPanel.getCitation().contains("Arthritis Rheum 63: 3988-3997, 2011")); - assertTrue("PMID value not as expected on detail page:" + publicationPanel.getPMID(), publicationPanel.getPMID().contains("21953143")); - assertTrue("PMCID value not as expected on detail page:" + publicationPanel.getPMCID(), publicationPanel.getPMCID().contains("PMC3227746")); - assertTrue("DOI value not as expected on detail page:" + publicationPanel.getDOI(), publicationPanel.getDOI().contains("10.1002/ART.30615")); - assertTrue("Studies value not as expected on detail page:" + publicationPanel.getStudyShortName(), publicationPanel.getStudyShortName().contains("RAVE")); - - card = finder.getDataCards().get(0); - card.hideDetail(); - publicationPanel = new PublicationPanel(this); - Assert.assertFalse("Author value not as expected in collapsed view", publicationPanel.getAuthor().contains("Cuthbertson")); - Assert.assertFalse("PMID should not be displayed in collapsed view", publicationPanel.isPMIDDisplayed()); - Assert.assertFalse("PMCID should not be displayed in collapsed view", publicationPanel.isPMCIDDisplayed()); - - // open it up again and make sure we have only one copy of the fields - card = finder.getDataCards().get(0); - card.viewDetail(); - Assert.assertEquals("Should still have just one PMID", 1, publicationPanel.getPMIDCount()); - - - log("Go to another publication that doesn't have the same type of detail."); - finder.clearAllFilters(); - fg.toggleFacet(DataFinderPage.Dimension.PUB_STUDY, "RAVE"); - fg.toggleFacet(DataFinderPage.Dimension.STATUS, "Complete"); - fg.toggleFacet(DataFinderPage.Dimension.PUB_THERAPEUTIC_AREA, "Autoimmune"); - fg.toggleFacet(DataFinderPage.Dimension.PUBLICATION_TYPE, "Manuscript"); - - summaryCount = finder.getSummaryCounts(); - assertEquals("Number of studies count not as expected.", 2, summaryCount.get(DataFinderPage.Dimension.SUMMARY_STUDIES).intValue()); - - log("Show details, this time validate that the missing values are rendered as expected."); - card = finder.getDataCards().get(0); - publicationPanel = card.viewDetail(); - - assertTrue("Author value not as expected on detail page:" + publicationPanel.getAuthor(), publicationPanel.getAuthor().contains("Ytterberg SR, Mueller M, Sejismundo LP, Mieras K, Stone JH.")); - assertTrue("Title value not as expected on detail page.", publicationPanel.getTitle().contains("Efficacy of Remission-Induction Regimens for ANCA-Associated Vasculitis")); - assertTrue("Citation value not as expected on detail page.", publicationPanel.getCitation().contains("New Eng J Med 2013; 369:417-427")); - assertTrue("PMID value not as expected on detail page.", publicationPanel.getPMID().contains("23902481")); - assertTrue("PMCID value not as expected on detail page.", publicationPanel.getPMCID().contains("")); - assertTrue("DOI value not as expected on detail page.", publicationPanel.getDOI().contains("")); - assertTrue("Studies value not as expected on detail page.", publicationPanel.getStudyShortName().contains("RAVE")); - - card = finder.getDataCards().get(0); - card.hideDetail(); - } - - - @LogMethod(quiet = true) - private void assertCountsSynced(DataFinderPage finder, DataFinderPage.Dimension dimension) - { - List dataCards = finder.getDataCards(); - Map summaryCounts = finder.getSummaryCounts(); - - assertEquals("Summary count mismatch", dataCards.size(), summaryCounts.get(dimension).intValue()); - } -} \ No newline at end of file From 579ebf75046c1292f4a23734de69f3261a6094ed Mon Sep 17 00:00:00 2001 From: Karl Lum Date: Mon, 16 Nov 2020 17:33:16 -0800 Subject: [PATCH 544/587] exclude Notebooks from the trial share export test (#76) --- src/org/labkey/trialshare/TrialShareController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 22e8a001..2cc401fc 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -571,7 +571,9 @@ public boolean shouldExport(TrialShareExportForm form) public static class TrialShareExportTest { - private final Collection folderWriters = FolderSerializationRegistry.get().getRegisteredFolderWriters(); + private final Collection folderWriters = FolderSerializationRegistry.get().getRegisteredFolderWriters().stream() + .filter(fw -> !"Notebooks".equals(fw.getDataType())) + .collect(Collectors.toList()); @Test public void testDataTypes() From f678bdfdb6cea1bbfeb86d52292bce344304dab6 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Tue, 15 Dec 2020 13:44:29 -0800 Subject: [PATCH 545/587] Add PR validation workflow (#77) --- .github/workflows/validate_pr.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/validate_pr.yml diff --git a/.github/workflows/validate_pr.yml b/.github/workflows/validate_pr.yml new file mode 100644 index 00000000..5eaa2d48 --- /dev/null +++ b/.github/workflows/validate_pr.yml @@ -0,0 +1,26 @@ +--- +# Workflow to validate Pull Request branches +name: PR Validation + +# Trigger on PR creation +on: + pull_request: + types: + - opened + - reopened + - ready_for_review + +jobs: + validate_pr: + if: github.event.pull_request.head.repo.owner.login == 'LabKey' + runs-on: ubuntu-latest + + steps: + - name: Validate PR Branches + uses: labkey-tchad/gitHubActions/validate-pr@master + with: + pr_base: ${{ github.event.pull_request.base.ref }} + pr_head: ${{ github.event.pull_request.head.ref }} + pr_number: ${{ github.event.pull_request.number }} + pr_title: ${{ github.event.pull_request.title }} + github_token: ${{ secrets.GITHUB_TOKEN }} From a628d446f544841160287a6b30db081ec3973d23 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Tue, 15 Dec 2020 13:47:40 -0800 Subject: [PATCH 546/587] Add PR validation workflow (#8) --- .github/workflows/validate_pr.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/validate_pr.yml diff --git a/.github/workflows/validate_pr.yml b/.github/workflows/validate_pr.yml new file mode 100644 index 00000000..5eaa2d48 --- /dev/null +++ b/.github/workflows/validate_pr.yml @@ -0,0 +1,26 @@ +--- +# Workflow to validate Pull Request branches +name: PR Validation + +# Trigger on PR creation +on: + pull_request: + types: + - opened + - reopened + - ready_for_review + +jobs: + validate_pr: + if: github.event.pull_request.head.repo.owner.login == 'LabKey' + runs-on: ubuntu-latest + + steps: + - name: Validate PR Branches + uses: labkey-tchad/gitHubActions/validate-pr@master + with: + pr_base: ${{ github.event.pull_request.base.ref }} + pr_head: ${{ github.event.pull_request.head.ref }} + pr_number: ${{ github.event.pull_request.number }} + pr_title: ${{ github.event.pull_request.title }} + github_token: ${{ secrets.GITHUB_TOKEN }} From 9b2839e318ff870a27c171208484d014b3460a2f Mon Sep 17 00:00:00 2001 From: Karl Lum Date: Fri, 18 Dec 2020 10:36:52 -0800 Subject: [PATCH 547/587] add sample types to expected export types (#78) --- .../labkey/trialshare/TrialShareController.java | 17 +++++++++++++++-- .../tests/trialshare/TrialShareExportTest.java | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 2cc401fc..9c553abd 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -113,6 +113,7 @@ public static class TrialShareExportForm private boolean externalSchemaDefinitions; private boolean wikisAndTheirAttachments; private boolean notificationSettings; + private boolean sampleTypes; public boolean getMissingValueIndicators() { @@ -413,6 +414,16 @@ public void setNotificationSettings(boolean notificationSettings) { this.notificationSettings = notificationSettings; } + + public boolean getSampleTypes() + { + return sampleTypes; + } + + public void setSampleTypes(boolean sampleTypes) + { + this.sampleTypes = sampleTypes; + } } /* @@ -513,7 +524,8 @@ private static Set getDefaultExportDataTypes() FolderDataTypes.etlDefinitions, FolderDataTypes.lists, FolderDataTypes.wikisAndAttachments, - FolderDataTypes.notificationSettings); + FolderDataTypes.notificationSettings, + FolderDataTypes.sampleTypes); } enum FolderDataTypes @@ -547,7 +559,8 @@ enum FolderDataTypes reportsAndCharts("Reports and Charts", TrialShareExportForm::getReportsAndCharts), externalSchemaDefinitions("External schema definitions", TrialShareExportForm::getExternalSchemaDefinitions), wikisAndAttachments("Wikis and their attachments", TrialShareExportForm::getWikisAndTheirAttachments), - notificationSettings("Notification settings", TrialShareExportForm::getNotificationSettings); + notificationSettings("Notification settings", TrialShareExportForm::getNotificationSettings), + sampleTypes("Sample Types", TrialShareExportForm::getSampleTypes); private final String _description; private final Function _formChecker; diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java index d682e276..eeee152c 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java @@ -46,7 +46,7 @@ public void testTrialShareExportActionDefault() throws Exception goToModule("FileContent"); _fileBrowserHelper.selectFileBrowserItem("/export/folder.xml"); List fileList = _fileBrowserHelper.getFileList(); - List expectedFiles = Arrays.asList("etls", "wikis", "folder.xml", "pages.xml"); + List expectedFiles = Arrays.asList("etls", "sample-types", "wikis", "folder.xml", "pages.xml"); assertEquals("Default export should include several folder objects", expectedFiles, fileList); } From 0e8912b2434f69274422ffd9764b7fbe14582b98 Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Wed, 23 Dec 2020 12:30:38 -0800 Subject: [PATCH 548/587] add plugins block in anticipation of removal of modules/build.gradle (#79) --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index be82913d..e38e5251 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,7 @@ import org.labkey.gradle.util.BuildUtils +plugins { + id 'org.labkey.build.module' +} + BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "study"), depProjectConfig: "published", depExtension: "module") From d29026f974a61e9cf559fd580294a9d39adce106 Mon Sep 17 00:00:00 2001 From: Karl Lum Date: Mon, 28 Dec 2020 20:16:57 -0800 Subject: [PATCH 549/587] automated test fixes (#80) --- .../labkey/trialshare/TrialShareController.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 9c553abd..b54709e1 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -113,7 +113,7 @@ public static class TrialShareExportForm private boolean externalSchemaDefinitions; private boolean wikisAndTheirAttachments; private boolean notificationSettings; - private boolean sampleTypes; + private boolean sampleTypesAndDataClasses; public boolean getMissingValueIndicators() { @@ -415,14 +415,14 @@ public void setNotificationSettings(boolean notificationSettings) this.notificationSettings = notificationSettings; } - public boolean getSampleTypes() + public boolean getSampleTypesAndDataClasses() { - return sampleTypes; + return sampleTypesAndDataClasses; } - public void setSampleTypes(boolean sampleTypes) + public void setSampleTypesAndDataClasses(boolean sampleTypesAndDataClasses) { - this.sampleTypes = sampleTypes; + this.sampleTypesAndDataClasses = sampleTypesAndDataClasses; } } @@ -525,7 +525,7 @@ private static Set getDefaultExportDataTypes() FolderDataTypes.lists, FolderDataTypes.wikisAndAttachments, FolderDataTypes.notificationSettings, - FolderDataTypes.sampleTypes); + FolderDataTypes.sampleTypesAndDataClasses); } enum FolderDataTypes @@ -560,7 +560,7 @@ enum FolderDataTypes externalSchemaDefinitions("External schema definitions", TrialShareExportForm::getExternalSchemaDefinitions), wikisAndAttachments("Wikis and their attachments", TrialShareExportForm::getWikisAndTheirAttachments), notificationSettings("Notification settings", TrialShareExportForm::getNotificationSettings), - sampleTypes("Sample Types", TrialShareExportForm::getSampleTypes); + sampleTypesAndDataClasses("Sample Types and Data Classes", TrialShareExportForm::getSampleTypesAndDataClasses); private final String _description; private final Function _formChecker; From 80b597ea937880d95713b0d057e12ea63c4d9299 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Wed, 6 Jan 2021 10:49:17 -0800 Subject: [PATCH 550/587] Issue 38722: Migrate ExcelWriter, TSVGridWriter, DataRegion, et al to use ResultsFactory or similar (#9) --- src/org/scharp/atlas/pepdb/PepDBController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 10d2e246..7efe3b8b 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -842,7 +842,7 @@ public void printExcel(Object bean, HttpServletResponse response, BindException DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); context.setBaseFilter(form.getFilter()); context.setBaseSort(form.getSort()); - try (ExcelWriter ew = new ExcelWriter(rgn.getResults(context), rgn.getDisplayColumns())) + try (ExcelWriter ew = new ExcelWriter(()->rgn.getResults(context), rgn.getDisplayColumns())) { ew.setAutoSize(true); ew.setFilenamePrefix(form.getMessage()); @@ -918,13 +918,12 @@ public void printText(Object bean, HttpServletResponse response, BindException e { try { - ViewContext ctx = getViewContext(); RenderContext context = new RenderContext(getViewContext()); DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); context.setBaseFilter(form.getFilter()); context.setBaseSort(form.getSort()); - try (TSVGridWriter tsv = new TSVGridWriter(rgn.getResults(context), rgn.getDisplayColumns())) + try (TSVGridWriter tsv = new TSVGridWriter(()->rgn.getResults(context), rgn.getDisplayColumns())) { tsv.setFilenamePrefix(form.getMessage()); tsv.write(getResponse()); From fe54edfcfff340cf6e98e551fcebf6ef8cd72210 Mon Sep 17 00:00:00 2001 From: Karl Lum Date: Tue, 12 Jan 2021 15:39:20 -0800 Subject: [PATCH 551/587] test fix (#82) --- src/org/labkey/trialshare/TrialShareController.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index b54709e1..799e773f 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -584,8 +584,11 @@ public boolean shouldExport(TrialShareExportForm form) public static class TrialShareExportTest { + // ignore these export types because they won't appear in the folder export UI + private static final Set _ignoredFolderWriters = Set.of("Notebooks", "LabBooks"); + private final Collection folderWriters = FolderSerializationRegistry.get().getRegisteredFolderWriters().stream() - .filter(fw -> !"Notebooks".equals(fw.getDataType())) + .filter(fw -> !_ignoredFolderWriters.contains(fw.getDataType())) .collect(Collectors.toList()); @Test From 2ea536a2af87048ce8185322683b1ae57531e692 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Thu, 14 Jan 2021 15:08:06 -0800 Subject: [PATCH 552/587] Adjust for specimen refactor (#81) --- build.gradle | 1 + .../trialshare/ITNSpecimenRequestCustomizer.java | 8 ++++---- .../labkey/trialshare/StudyITNFolderType.java | 16 ++++++++-------- .../test/tests/trialshare/ITNSpecimenTest.java | 11 ++++++----- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/build.gradle b/build.gradle index e38e5251..0747ba0a 100644 --- a/build.gradle +++ b/build.gradle @@ -4,4 +4,5 @@ plugins { id 'org.labkey.build.module' } +BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "study"), depProjectConfig: "apiJarFile") BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "study"), depProjectConfig: "published", depExtension: "module") diff --git a/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java b/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java index f2d4edf1..96356c1b 100644 --- a/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java +++ b/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java @@ -7,7 +7,7 @@ import org.labkey.api.security.Group; import org.labkey.api.security.GroupManager; import org.labkey.api.security.User; -import org.labkey.api.study.SamplesUrls; +import org.labkey.api.study.SpecimenUrls; import org.labkey.api.study.SpecimenService; import org.labkey.api.util.HtmlString; import org.labkey.api.util.PageFlowUtil; @@ -52,13 +52,13 @@ public boolean canChangeStatus(User user) @Override public HtmlString getSubmittedMessage(Container c, int requestId) { - SamplesUrls samplesUrls = PageFlowUtil.urlProvider(SamplesUrls.class); + SpecimenUrls specimenUrls = PageFlowUtil.urlProvider(SpecimenUrls.class); return HtmlString.unsafe("Thank you for your request. A representative from the ITN will be in touch " + "with you. You can also contact us at " + - link("trialsharesupport@immunetolerance.org").href("mailto:trialsharesupport@immunetolerance.org?Body=" + PageFlowUtil.filter(samplesUrls.getRequestDetailsURL(c, requestId).getURIString())).clearClasses() + + link("trialsharesupport@immunetolerance.org").href("mailto:trialsharesupport@immunetolerance.org?Body=" + PageFlowUtil.filter(specimenUrls.getRequestDetailsURL(c, requestId).getURIString())).clearClasses() + " to follow up with us regarding this request.
" + "You may also update this request at any calendar to add comments or supporting " + - "documents by clicking here " + link("Update Request").href(samplesUrls.getManageRequestStatusURL(c, requestId))); + "documents by clicking here " + link("Update Request").href(specimenUrls.getManageRequestStatusURL(c, requestId))); } @Override diff --git a/src/org/labkey/trialshare/StudyITNFolderType.java b/src/org/labkey/trialshare/StudyITNFolderType.java index 08607fa8..2114a576 100644 --- a/src/org/labkey/trialshare/StudyITNFolderType.java +++ b/src/org/labkey/trialshare/StudyITNFolderType.java @@ -17,6 +17,7 @@ import org.jetbrains.annotations.NotNull; import org.labkey.api.module.MultiPortalFolderType; +import org.labkey.api.specimen.SpecimensPage; import org.labkey.api.study.Study; import org.labkey.api.study.StudyFolderTabs; import org.labkey.api.study.StudyService; @@ -24,7 +25,6 @@ import org.labkey.api.view.Portal; import org.labkey.api.view.ViewContext; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; @@ -37,13 +37,13 @@ public class StudyITNFolderType extends MultiPortalFolderType { private static final String STUDY_ITN_FOLDER_TYPE_NAME = "Study (ITN)"; - private static final List PAGES = Arrays.asList( - new StudyFolderTabs.OverviewPage("Overview"), - new StudyFolderTabs.DataAnalysisPage("Data & Reports"), - new StudyFolderTabs.ParticipantsPage("Participants"), - new StudyFolderTabs.SpecimensPage("Specimens"), - new StudyFolderTabs.ManagePage("Manage") - ); + private static final List PAGES = List.of( + new StudyFolderTabs.OverviewPage("Overview"), + new StudyFolderTabs.DataAnalysisPage("Data & Reports"), + new StudyFolderTabs.ParticipantsPage("Participants"), + new SpecimensPage("Specimens"), + new StudyFolderTabs.ManagePage("Manage") + ); StudyITNFolderType(TrialShareModule module) { diff --git a/test/src/org/labkey/test/tests/trialshare/ITNSpecimenTest.java b/test/src/org/labkey/test/tests/trialshare/ITNSpecimenTest.java index ab6aae3f..edb1a82d 100644 --- a/test/src/org/labkey/test/tests/trialshare/ITNSpecimenTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ITNSpecimenTest.java @@ -15,7 +15,7 @@ import org.labkey.test.components.study.specimen.SpecimenDetailGrid; import org.labkey.test.pages.study.specimen.ManageRequestPage; import org.labkey.test.pages.study.specimen.ManageRequestStatusPage; -import org.labkey.test.pages.study.specimen.ShowCreateSampleRequestPage; +import org.labkey.test.pages.study.specimen.ShowCreateSpecimenRequestPage; import org.labkey.test.pages.study.specimen.TypeSummaryReportPage; import org.labkey.test.util.ApiPermissionsHelper; import org.labkey.test.util.ExperimentalFeaturesHelper; @@ -61,6 +61,7 @@ public static void setupProject() private void doSetup() { _containerHelper.createProject(getProjectName(), "Study"); + _containerHelper.enableModule("Specimen"); importStudyFromZip(STUDY_ARCHIVE); new SpecimenHelper(this).setupRequestStatuses(); } @@ -108,10 +109,10 @@ public void testSpecimenRequestCustomization() clickAndWait(Locator.linkWithText("Urine")); SpecimenDetailGrid specimenGrid = specimenHelper.findSpecimenDetailGrid(); specimenGrid.checkCheckbox(0); - ShowCreateSampleRequestPage requestPage = specimenGrid.createNewRequest(); + ShowCreateSpecimenRequestPage requestPage = specimenGrid.createNewRequest(); // TODO: Refactor to use SampleManagementErrorLogger after it has been moved out of sampleManagement - TestLogger.log("\"Reqeusting Location\" should not be available for ITN specimen requests"); + TestLogger.log("\"Requesting Location\" should not be available for ITN specimen requests"); assertElementNotVisible(Locator.id("destinationLocation")); ManageRequestPage manageRequestPage = requestPage @@ -125,7 +126,7 @@ public void testSpecimenRequestCustomization() specimenGrid.checkCheckbox(0); specimenGrid.clickHeaderButtonAndWait("Remove Selected"); - TestLogger.log("Should be able to submit a request without any samples"); + TestLogger.log("Should be able to submit a request without any specimens"); manageRequestPage = manageRequestPage .submitRequest(); @@ -144,7 +145,7 @@ public void testRequestPermissionCustomization() permissions.addMemberToRole(SPECIMEN_COORDINATOR, "Reader", MemberType.user, getProjectName()); permissions.createGlobalPermissionsGroup(MAGIC_GROUP); - ManageRequestStatusPage manageRequestStatusPage = ShowCreateSampleRequestPage.beginAt(this, getProjectName()) + ManageRequestStatusPage manageRequestStatusPage = ShowCreateSpecimenRequestPage.beginAt(this, getProjectName()) .setDetails("A", "B") .clickCreateAndViewDetails() .submitRequest() From 68006fb2e861c9acc16ec33380a385ae833fd894 Mon Sep 17 00:00:00 2001 From: Cory Nathe Date: Wed, 27 Jan 2021 15:19:34 -0600 Subject: [PATCH 553/587] TrialShareController.TrialShareExportTest fix to add "Inventory locations and items" (#83) * TrialShareController.TrialShareExportTest fix to add "Inventory locations and items" * TrialShareExportTest.testTrialShareExportActionDefault fix to add "inventory" to expected archive dir list --- .../trialshare/TrialShareController.java | 18 ++++++++++++++++-- .../tests/trialshare/TrialShareExportTest.java | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 799e773f..f52de6b4 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -114,6 +114,7 @@ public static class TrialShareExportForm private boolean wikisAndTheirAttachments; private boolean notificationSettings; private boolean sampleTypesAndDataClasses; + private boolean inventoryLocationsAndItems; public boolean getMissingValueIndicators() { @@ -424,6 +425,16 @@ public void setSampleTypesAndDataClasses(boolean sampleTypesAndDataClasses) { this.sampleTypesAndDataClasses = sampleTypesAndDataClasses; } + + public boolean getInventoryLocationsAndItems() + { + return inventoryLocationsAndItems; + } + + public void setInventoryLocationsAndItems(boolean inventoryLocationsAndItems) + { + this.inventoryLocationsAndItems = inventoryLocationsAndItems; + } } /* @@ -525,7 +536,9 @@ private static Set getDefaultExportDataTypes() FolderDataTypes.lists, FolderDataTypes.wikisAndAttachments, FolderDataTypes.notificationSettings, - FolderDataTypes.sampleTypesAndDataClasses); + FolderDataTypes.sampleTypesAndDataClasses, + FolderDataTypes.inventoryLocationsAndItems + ); } enum FolderDataTypes @@ -560,7 +573,8 @@ enum FolderDataTypes externalSchemaDefinitions("External schema definitions", TrialShareExportForm::getExternalSchemaDefinitions), wikisAndAttachments("Wikis and their attachments", TrialShareExportForm::getWikisAndTheirAttachments), notificationSettings("Notification settings", TrialShareExportForm::getNotificationSettings), - sampleTypesAndDataClasses("Sample Types and Data Classes", TrialShareExportForm::getSampleTypesAndDataClasses); + sampleTypesAndDataClasses("Sample Types and Data Classes", TrialShareExportForm::getSampleTypesAndDataClasses), + inventoryLocationsAndItems("Inventory locations and items", TrialShareExportForm::getInventoryLocationsAndItems); private final String _description; private final Function _formChecker; diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java index eeee152c..fd74f098 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java @@ -46,7 +46,7 @@ public void testTrialShareExportActionDefault() throws Exception goToModule("FileContent"); _fileBrowserHelper.selectFileBrowserItem("/export/folder.xml"); List fileList = _fileBrowserHelper.getFileList(); - List expectedFiles = Arrays.asList("etls", "sample-types", "wikis", "folder.xml", "pages.xml"); + List expectedFiles = Arrays.asList("etls", "inventory", "sample-types", "wikis", "folder.xml", "pages.xml"); assertEquals("Default export should include several folder objects", expectedFiles, fileList); } From dae733c9be1cf9d832db9a33c53d87a3e1f4bc43 Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Fri, 29 Jan 2021 14:43:47 -0800 Subject: [PATCH 554/587] Item 8294: Job Template Import and Export (#84) --- .../trialshare/TrialShareController.java | 18 ++++++++++++++++-- .../tests/trialshare/TrialShareExportTest.java | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index f52de6b4..45e1d97f 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -115,6 +115,7 @@ public static class TrialShareExportForm private boolean notificationSettings; private boolean sampleTypesAndDataClasses; private boolean inventoryLocationsAndItems; + private boolean experimentsAndRuns; public boolean getMissingValueIndicators() { @@ -435,6 +436,17 @@ public void setInventoryLocationsAndItems(boolean inventoryLocationsAndItems) { this.inventoryLocationsAndItems = inventoryLocationsAndItems; } + + public boolean getExperimentsAndRuns() + { + return experimentsAndRuns; + } + + public void setExperimentsAndRuns(boolean experimentsAndRuns) + { + this.experimentsAndRuns = experimentsAndRuns; + } + } /* @@ -537,7 +549,8 @@ private static Set getDefaultExportDataTypes() FolderDataTypes.wikisAndAttachments, FolderDataTypes.notificationSettings, FolderDataTypes.sampleTypesAndDataClasses, - FolderDataTypes.inventoryLocationsAndItems + FolderDataTypes.inventoryLocationsAndItems, + FolderDataTypes.experiments ); } @@ -574,7 +587,8 @@ enum FolderDataTypes wikisAndAttachments("Wikis and their attachments", TrialShareExportForm::getWikisAndTheirAttachments), notificationSettings("Notification settings", TrialShareExportForm::getNotificationSettings), sampleTypesAndDataClasses("Sample Types and Data Classes", TrialShareExportForm::getSampleTypesAndDataClasses), - inventoryLocationsAndItems("Inventory locations and items", TrialShareExportForm::getInventoryLocationsAndItems); + inventoryLocationsAndItems("Inventory locations and items", TrialShareExportForm::getInventoryLocationsAndItems), + experiments("Experiments, Protocols, and Runs", TrialShareExportForm::getExperimentsAndRuns); private final String _description; private final Function _formChecker; diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java index fd74f098..3dc0c4c6 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java @@ -46,7 +46,7 @@ public void testTrialShareExportActionDefault() throws Exception goToModule("FileContent"); _fileBrowserHelper.selectFileBrowserItem("/export/folder.xml"); List fileList = _fileBrowserHelper.getFileList(); - List expectedFiles = Arrays.asList("etls", "inventory", "sample-types", "wikis", "folder.xml", "pages.xml"); + List expectedFiles = Arrays.asList("etls", "inventory", "sample-types", "wikis", "xar", "folder.xml", "pages.xml"); assertEquals("Default export should include several folder objects", expectedFiles, fileList); } From 5925b40ed628d96b0e576165e8d38827582af34e Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Mon, 8 Feb 2021 17:49:23 -0800 Subject: [PATCH 555/587] Add build.gradle file to apply plugin (#10) --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 build.gradle diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..405ae240 --- /dev/null +++ b/build.gradle @@ -0,0 +1,3 @@ +plugins { + id 'org.labkey.build.module' +} From 53d714a326db3a954f2e73701b8dd7160e7f2cdc Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 12 Feb 2021 11:40:26 -0800 Subject: [PATCH 556/587] Add dependency on specimen module (#85) --- build.gradle | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 0747ba0a..75fb6e8b 100644 --- a/build.gradle +++ b/build.gradle @@ -4,5 +4,9 @@ plugins { id 'org.labkey.build.module' } -BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "study"), depProjectConfig: "apiJarFile") -BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "study"), depProjectConfig: "published", depExtension: "module") +dependencies { + BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "study"), depProjectConfig: "apiJarFile") + BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "study"), depProjectConfig: "published", depExtension: "module") + BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "specimen"), depProjectConfig: "published", depExtension: "module") +} + From 32db4f52e1dbe7b08c97ab6e7b3503f2b92331e8 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Thu, 22 Apr 2021 10:40:49 -0700 Subject: [PATCH 557/587] Rename action branch to develop (#86) * Rename action branch to develop * Update repository for validate_pr workflow --- .github/workflows/branch_release.yml | 2 +- .github/workflows/merge_release.yml | 2 +- .github/workflows/validate_pr.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/branch_release.yml b/.github/workflows/branch_release.yml index 286510e3..e0146463 100644 --- a/.github/workflows/branch_release.yml +++ b/.github/workflows/branch_release.yml @@ -18,6 +18,6 @@ jobs: uses: actions/checkout@v2 - name: Create branches and PRs - uses: LabKey/gitHubActions/branch-release@master + uses: LabKey/gitHubActions/branch-release@develop with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge_release.yml b/.github/workflows/merge_release.yml index 559f6ef8..289c7461 100644 --- a/.github/workflows/merge_release.yml +++ b/.github/workflows/merge_release.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v2 - name: Merge PR - uses: LabKey/gitHubActions/merge-release@master + uses: LabKey/gitHubActions/merge-release@develop with: target_branch: ${{ github.event.pull_request.base.ref }} merge_branch: ${{ github.event.pull_request.head.ref }} diff --git a/.github/workflows/validate_pr.yml b/.github/workflows/validate_pr.yml index 5eaa2d48..b567d8ae 100644 --- a/.github/workflows/validate_pr.yml +++ b/.github/workflows/validate_pr.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Validate PR Branches - uses: labkey-tchad/gitHubActions/validate-pr@master + uses: LabKey/gitHubActions/validate-pr@develop with: pr_base: ${{ github.event.pull_request.base.ref }} pr_head: ${{ github.event.pull_request.head.ref }} From 01aaa113d7bf09ef811f92793bb570462b525779 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Thu, 22 Apr 2021 10:48:26 -0700 Subject: [PATCH 558/587] Rename action branch to develop (#11) * Rename action branch to develop * Update repository for validate_pr workflow --- .github/workflows/branch_release.yml | 2 +- .github/workflows/merge_release.yml | 2 +- .github/workflows/validate_pr.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/branch_release.yml b/.github/workflows/branch_release.yml index 286510e3..e0146463 100644 --- a/.github/workflows/branch_release.yml +++ b/.github/workflows/branch_release.yml @@ -18,6 +18,6 @@ jobs: uses: actions/checkout@v2 - name: Create branches and PRs - uses: LabKey/gitHubActions/branch-release@master + uses: LabKey/gitHubActions/branch-release@develop with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge_release.yml b/.github/workflows/merge_release.yml index 559f6ef8..289c7461 100644 --- a/.github/workflows/merge_release.yml +++ b/.github/workflows/merge_release.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v2 - name: Merge PR - uses: LabKey/gitHubActions/merge-release@master + uses: LabKey/gitHubActions/merge-release@develop with: target_branch: ${{ github.event.pull_request.base.ref }} merge_branch: ${{ github.event.pull_request.head.ref }} diff --git a/.github/workflows/validate_pr.yml b/.github/workflows/validate_pr.yml index 5eaa2d48..b567d8ae 100644 --- a/.github/workflows/validate_pr.yml +++ b/.github/workflows/validate_pr.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Validate PR Branches - uses: labkey-tchad/gitHubActions/validate-pr@master + uses: LabKey/gitHubActions/validate-pr@develop with: pr_base: ${{ github.event.pull_request.base.ref }} pr_head: ${{ github.event.pull_request.head.ref }} From c81c9d248af96812dfe991b75bca164d7117a947 Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Mon, 7 Jun 2021 12:52:07 -0700 Subject: [PATCH 559/587] Fix TrialShareController.TrialShareExportTest - match dataset export changes (#87) Co-authored-by: labkey-jeckels --- .../trialshare/TrialShareController.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 45e1d97f..856026a0 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -526,13 +526,16 @@ private static Set getDefaultExportDataTypes() FolderDataTypes.mvIndicators, FolderDataTypes.study, - FolderDataTypes.assayDatasets, + FolderDataTypes.assayDatasetData, + FolderDataTypes.assayDatasetDefinitions, FolderDataTypes.assaySchedule, FolderDataTypes.categories, FolderDataTypes.cohortSettings, - FolderDataTypes.crfDatasets, FolderDataTypes.customParticipantView, - FolderDataTypes.datasetData, + FolderDataTypes.sampleDatasetData, + FolderDataTypes.sampleDatasetDefinitions, + FolderDataTypes.studyDatasetData, + FolderDataTypes.studyDatasetDefinitions, FolderDataTypes.participantCommentSettings, FolderDataTypes.participantGroups, FolderDataTypes.protocolDocuments, @@ -558,13 +561,16 @@ enum FolderDataTypes { mvIndicators("Missing value indicators", TrialShareExportForm::getMissingValueIndicators), study("Study", TrialShareExportForm::getStudy), - assayDatasets("Assay Datasets", TrialShareExportForm::getAssayDatasets), + assayDatasetDefinitions("Datasets: Assay Dataset Definitions", TrialShareExportForm::getAssayDatasets), + assayDatasetData("Datasets: Assay Dataset Data", TrialShareExportForm::getAssayDatasets), assaySchedule("Assay Schedule", TrialShareExportForm::getAssaySchedule), categories("Categories", TrialShareExportForm::getCategories), cohortSettings("Cohort Settings", TrialShareExportForm::getCohortSettings), - crfDatasets("CRF Datasets", TrialShareExportForm::getCrfDatasets), customParticipantView("Custom Participant View", TrialShareExportForm::getCustomParticipantView), - datasetData("Dataset Data", TrialShareExportForm::getDatasetData), + sampleDatasetData("Datasets: Sample Dataset Data", TrialShareExportForm::getDatasetData), + sampleDatasetDefinitions("Datasets: Sample Dataset Definitions", TrialShareExportForm::getDatasetData), + studyDatasetData("Datasets: Study Dataset Data", TrialShareExportForm::getCrfDatasets), + studyDatasetDefinitions("Datasets: Study Dataset Definitions", TrialShareExportForm::getCrfDatasets), etlDefinitions("ETL Definitions", TrialShareExportForm::getEtlDefinitions), participantCommentSettings("Participant Comment Settings", TrialShareExportForm::getParticipantCommentSettings), participantGroups("Participant Groups", TrialShareExportForm::getParticipantGroups), From 393962b1fcf3a00528f526721c66ad7388760206 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Wed, 7 Jul 2021 16:50:00 -0700 Subject: [PATCH 560/587] Migrate use of boxed primitive constructors (#12) --- .../scharp/atlas/pepdb/PepDBBaseController.java | 14 ++++++-------- src/org/scharp/atlas/pepdb/PeptideImporter.java | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBBaseController.java b/src/org/scharp/atlas/pepdb/PepDBBaseController.java index f7ac3f93..26bc80bd 100644 --- a/src/org/scharp/atlas/pepdb/PepDBBaseController.java +++ b/src/org/scharp/atlas/pepdb/PepDBBaseController.java @@ -663,9 +663,9 @@ public Class getDisplayValueClass() public static Integer validateInteger(String value) { - try{ - Integer intValue = new Integer(value); - return intValue; + try + { + return Integer.valueOf(value); } catch(NumberFormatException e){return null;} } @@ -690,12 +690,10 @@ public static java.util.Date isValidDate(String sDateIn) { public static Float validateFloat(String value) { - try{ - Float floatValue = new Float(value); - return floatValue; + try + { + return Float.valueOf(value); } catch(NumberFormatException e){return null;} } - - } diff --git a/src/org/scharp/atlas/pepdb/PeptideImporter.java b/src/org/scharp/atlas/pepdb/PeptideImporter.java index 6e38e844..24ba9a9b 100644 --- a/src/org/scharp/atlas/pepdb/PeptideImporter.java +++ b/src/org/scharp/atlas/pepdb/PeptideImporter.java @@ -305,9 +305,9 @@ private Peptides createPeptide(String line,String fileName) throws SQLException public static Integer validateInteger(String value) { - try{ - Integer intValue = new Integer(value); - return intValue; + try + { + return Integer.valueOf(value); } catch(NumberFormatException e){return null;} } From 9c9ab6aae54a8db5c63c2dd188ab60a37a8babee Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Thu, 8 Jul 2021 13:43:14 -0700 Subject: [PATCH 561/587] Move all remaining specimen actions (i.e., specimen comments) to specimen module. Eliminate old specimen controller. (#88) --- src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java b/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java index 96356c1b..036ea738 100644 --- a/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java +++ b/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java @@ -55,7 +55,7 @@ public HtmlString getSubmittedMessage(Container c, int requestId) SpecimenUrls specimenUrls = PageFlowUtil.urlProvider(SpecimenUrls.class); return HtmlString.unsafe("Thank you for your request. A representative from the ITN will be in touch " + "with you. You can also contact us at " + - link("trialsharesupport@immunetolerance.org").href("mailto:trialsharesupport@immunetolerance.org?Body=" + PageFlowUtil.filter(specimenUrls.getRequestDetailsURL(c, requestId).getURIString())).clearClasses() + + link("trialsharesupport@immunetolerance.org").href("mailto:trialsharesupport@immunetolerance.org?Body=" + PageFlowUtil.filter(specimenUrls.getManageRequestURL(c, requestId).getURIString())).clearClasses() + " to follow up with us regarding this request.
" + "You may also update this request at any calendar to add comments or supporting " + "documents by clicking here " + link("Update Request").href(specimenUrls.getManageRequestStatusURL(c, requestId))); From 6cddad7c328b684edcd14d8ebde2f05ec5f5b6b9 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Wed, 18 Aug 2021 10:21:32 -0700 Subject: [PATCH 562/587] Update and Enable PepDB module test (#13) * Update pepdb actions to use current LabKey link syntax --- .../atlas/pepdb/PepDBBaseController.java | 41 ++-- .../atlas/pepdb/view/importPeptides.jsp | 3 +- .../scharp/atlas/pepdb/view/importPools.jsp | 2 +- src/org/scharp/atlas/pepdb/view/index.jsp | 26 ++- .../scharp/atlas/pepdb/view/pepDBWebPart.jsp | 4 +- .../atlas/pepdb/view/peptideDetails.jsp | 8 +- .../scharp => pepdb}/PepDBModuleTest.java | 188 +++++++----------- 7 files changed, 119 insertions(+), 153 deletions(-) rename test/src/org/labkey/test/tests/{external/scharp => pepdb}/PepDBModuleTest.java (56%) diff --git a/src/org/scharp/atlas/pepdb/PepDBBaseController.java b/src/org/scharp/atlas/pepdb/PepDBBaseController.java index 26bc80bd..ee914c14 100644 --- a/src/org/scharp/atlas/pepdb/PepDBBaseController.java +++ b/src/org/scharp/atlas/pepdb/PepDBBaseController.java @@ -17,6 +17,8 @@ import org.labkey.api.util.DateUtil; import org.labkey.api.util.HtmlString; import org.labkey.api.util.HtmlStringBuilder; +import org.labkey.api.util.Link; +import org.labkey.api.view.ActionURL; import org.scharp.atlas.pepdb.model.PeptideGroup; import org.scharp.atlas.pepdb.model.PeptidePool; import org.scharp.atlas.pepdb.model.Peptides; @@ -450,13 +452,12 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep Integer peptideId = (Integer) rowMap.get(c.getName()); try { - String href = "displayPeptide.view?" + PepDBSchema.COLUMN_PEPTIDE_ID + "=" + peptideId; - out.write("
"); - out.write("P"+peptideId); - out.write(""); - out.write("PP" + peptidePoolId); - out.write(""); + new Link.LinkBuilder("PP" + peptidePoolId).clearClasses() + .target("_self") + .href(new ActionURL(PepDBController.DisplayPeptidePoolInformationAction.class, getContainer()) + .addParameter(PepDBSchema.COLUMN_PEPTIDE_POOL_ID, peptidePoolId)) + .build() + .appendTo(out); } catch (Exception e) { @@ -605,15 +605,14 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep Integer parentPoolId = (Integer) rowMap.get(c.getName()); try { - String href = "displayPeptidePoolInformation.view?" + PepDBSchema.COLUMN_PEPTIDE_POOL_ID + "=" + parentPoolId; if(parentPoolId != null) { - out.write(""); - out.write("PP" + parentPoolId); - out.write(""); + new Link.LinkBuilder("PP" + parentPoolId).clearClasses() + .target("_self") + .href(new ActionURL(PepDBController.DisplayPeptidePoolInformationAction.class, getContainer()) + .addParameter(PepDBSchema.COLUMN_PEPTIDE_POOL_ID, parentPoolId)) + .build() + .appendTo(out); } } catch (Exception e) diff --git a/src/org/scharp/atlas/pepdb/view/importPeptides.jsp b/src/org/scharp/atlas/pepdb/view/importPeptides.jsp index 470b5582..02dd4fae 100644 --- a/src/org/scharp/atlas/pepdb/view/importPeptides.jsp +++ b/src/org/scharp/atlas/pepdb/view/importPeptides.jsp @@ -2,6 +2,7 @@ <%@ page import="org.labkey.api.view.HttpView"%> <%@ page import="org.labkey.api.view.JspView"%> <%@ page import="org.scharp.atlas.pepdb.PepDBBaseController.FileForm" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBController" %> <%@ page import="org.scharp.atlas.pepdb.PepDBController.ImportPeptidesAction" %> <%@ page extends="org.labkey.api.jsp.JspBase" %>
@@ -38,7 +39,7 @@ - <%= button("Import Peptides").submit(true) %> <%= button("Back").href("begin.view") %> + <%= button("Import Peptides").submit(true) %> <%= button("Back").href(urlFor(PepDBController.BeginAction.class)) %>
Note: The File must be .txt extension and It should be tab delimited.

diff --git a/src/org/scharp/atlas/pepdb/view/importPools.jsp b/src/org/scharp/atlas/pepdb/view/importPools.jsp index 38ac9803..86858df0 100644 --- a/src/org/scharp/atlas/pepdb/view/importPools.jsp +++ b/src/org/scharp/atlas/pepdb/view/importPools.jsp @@ -40,7 +40,7 @@ - <%= button("Import Peptide Pools").submit(true) %> <%= button("Back").href("begin.view") %> + <%= button("Import Peptide Pools").submit(true) %> <%= button("Back").href(urlFor(PepDBController.BeginAction.class)) %>
Note: The File must be .txt extension and It should be tab delimited.

diff --git a/src/org/scharp/atlas/pepdb/view/index.jsp b/src/org/scharp/atlas/pepdb/view/index.jsp index 540f7ab1..dcd0a34b 100644 --- a/src/org/scharp/atlas/pepdb/view/index.jsp +++ b/src/org/scharp/atlas/pepdb/view/index.jsp @@ -4,31 +4,35 @@ <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.ViewContext" %> <%@ page import="org.scharp.atlas.pepdb.PepDBBaseController.DisplayPeptideForm" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBController" %> <%@ page import="org.scharp.atlas.pepdb.PepDBController.DisplayPeptideAction" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <% ViewContext ctx = getViewContext(); - User user = ctx.getUser(); %> + User user = ctx.getUser(); + final var canUpdate = ctx.getContainer().hasPermission(user, UpdatePermission.class); +%>

If you see some of the links disabled then you don't have permission to enter data. If you need to enter any data contact Atlas Administrator.

Peptide Groups :

    -
  • List Peptide Groups
  • - <%if(ctx.getContainer().hasPermission(user, UpdatePermission.class)){%> -
  • Insert a New Group
  • +
  • <%= link("List Peptide Groups").href(urlFor(PepDBController.ShowAllPeptideGroupsAction.class)).clearClasses() %>
  • + <% + if(canUpdate){%> +
  • <%= link("Insert a New Group").href(urlFor(PepDBController.InsertPeptideGroupAction.class)).clearClasses() %>
  • <%}else{%>
  • Insert a New Group
  • <%}%>

Peptides :

    -
  • Search for Peptides by Criteria
  • - <%if(ctx.getContainer().hasPermission(user, UpdatePermission.class)){%> -
  • Import Peptides
  • +
  • <%= link("Search for Peptides by Criteria").href(urlFor(PepDBController.SearchForPeptidesAction.class)).clearClasses() %>
  • + <%if(canUpdate){%> +
  • <%= link("Import Peptides").href(urlFor(PepDBController.ImportPeptidesAction.class)).clearClasses() %>
  • <%}else{%>
  • Import Peptides
  • <%}%> -
  • Peptides From Last Import
  • +
  • <%= link("Peptides From Last Import").href(urlFor(PepDBController.DisplayResultAction.class)).clearClasses() %>
<% @@ -40,10 +44,10 @@ Lookup Peptide by Id: Peptide Pools :
    - <%if(ctx.getContainer().hasPermission(user, UpdatePermission.class)){%> -
  • Import Peptide Pools
  • + <%if(canUpdate){%> +
  • <%= link("Import Peptide Pools").href(urlFor(PepDBController.ImportPeptidePoolsAction.class)).clearClasses() %>
  • <%}else{%>
  • Import Peptide Pools
  • <%}%> -
  • List All Peptide Pools
  • +
  • <%= link("List All Peptide Pools").href(urlFor(PepDBController.ShowAllPeptidePoolsAction.class)).clearClasses() %>
diff --git a/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp b/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp index 6751e918..04f46ed7 100644 --- a/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp +++ b/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp @@ -1,6 +1,6 @@ -<%@ page import="org.labkey.api.view.ActionURL"%> <%@ page import="org.labkey.api.view.HttpView"%> <%@ page import="org.labkey.api.view.ViewContext"%> +<%@ page import="org.scharp.atlas.pepdb.PepDBController"%> <%@ page import="org.scharp.atlas.pepdb.model.PeptideGroup" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <% @@ -8,4 +8,4 @@ PeptideGroup[] peptides = (PeptideGroup[]) context.get("peptides"); %> This container contains <%= peptides.length %> peptide groups.
-<%= button("View Grid").href(new ActionURL("PepDB", "showAllPeptideGroups", context.getContainer())) %> \ No newline at end of file +<%= button("View Grid").href(urlFor(PepDBController.ShowAllPeptideGroupsAction.class)) %> diff --git a/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp b/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp index fa6b39ec..120f8e98 100644 --- a/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp +++ b/src/org/scharp/atlas/pepdb/view/peptideDetails.jsp @@ -2,6 +2,7 @@ <%@ page import="org.labkey.api.view.HttpView"%> <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.scharp.atlas.pepdb.PepDBBaseController.PeptideQueryForm" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBController" %> <%@ page import="org.scharp.atlas.pepdb.PepDBManager" %> <%@ page import="org.scharp.atlas.pepdb.model.PeptidePool" %> <%@ page import="org.scharp.atlas.pepdb.model.Source" %> @@ -22,7 +23,8 @@ - <%= link(source.getPeptide_group_name()).href("displayPeptideGroupInformation.view?peptide_group_id=" + source.getPeptide_group_id().toString()) %> + <%= link(source.getPeptide_group_name()).href(urlFor(PepDBController.DisplayPeptideGroupInformationAction.class) + .addParameter("peptide_group_id", source.getPeptide_group_id().toString())).clearClasses() %> (PEPTIDE NUMBER =<%=h(source.getPeptide_id_in_group())%>) <%if(source.getFrequency_number() != null){%> - Frequency Number = @@ -45,8 +47,8 @@ {%> - <%= link("PP"+pool.getPeptide_pool_id()).href( - "displayPeptidePoolInformation.view?peptide_pool_id=" +pool.getPeptide_pool_id()) %> - + <%= link("PP"+pool.getPeptide_pool_id()).href(urlFor(PepDBController.DisplayPeptidePoolInformationAction.class) + .addParameter("peptide_pool_id", pool.getPeptide_pool_id())).clearClasses() %> - <%=h(pool.getPeptide_pool_name())%> <%=h(pool.getPool_type_desc())%> diff --git a/test/src/org/labkey/test/tests/external/scharp/PepDBModuleTest.java b/test/src/org/labkey/test/tests/pepdb/PepDBModuleTest.java similarity index 56% rename from test/src/org/labkey/test/tests/external/scharp/PepDBModuleTest.java rename to test/src/org/labkey/test/tests/pepdb/PepDBModuleTest.java index 96c5a2ed..94e97f28 100644 --- a/test/src/org/labkey/test/tests/external/scharp/PepDBModuleTest.java +++ b/test/src/org/labkey/test/tests/pepdb/PepDBModuleTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.labkey.test.tests.external.scharp; +package org.labkey.test.tests.pepdb; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -30,12 +30,9 @@ import org.labkey.test.TestFileUtils; import org.labkey.test.TestTimeoutException; import org.labkey.test.WebTestHelper; -import org.labkey.test.categories.Disabled; import org.labkey.test.categories.External; import org.labkey.test.util.LogMethod; import org.labkey.test.util.PostgresOnlyTest; -import org.openqa.selenium.By; -import org.openqa.selenium.support.ui.Select; import java.io.File; import java.io.IOException; @@ -45,7 +42,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -@Category({External.class, Disabled.class}) +@Category({External.class}) public class PepDBModuleTest extends BaseWebDriverTest implements PostgresOnlyTest { @@ -105,86 +102,84 @@ public void testSteps() throws Exception assertModuleEnabled("PepDB"); log("Expected modules enabled."); - beginAt("/pepdb/" + getProjectName() + "/Labs/Test/" + FOLDER_NAME + "/begin.view?"); + beginAt(WebTestHelper.buildURL("pepdb", getProjectName() + "/Labs/Test/" + FOLDER_NAME, "begin")); /* Insert the Peptide Group" */ - getDriver().findElement(By.linkText("Insert a New Group")).click(); - getDriver().findElement(By.name("peptide_group_name")).clear(); - getDriver().findElement(By.name("peptide_group_name")).sendKeys("gagptegprac"); - new Select(getDriver().findElement(By.name("pathogen_id"))).selectByVisibleText("Other"); - new Select(getDriver().findElement(By.name("clade_id"))).selectByVisibleText("Other"); - new Select(getDriver().findElement(By.name("group_type_id"))).selectByVisibleText("Other"); - getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); + clickAndWait(Locator.linkWithText("Insert a New Group")); + setFormElement(Locator.name("peptide_group_name"), "gagptegprac"); + selectOptionByText(Locator.name("pathogen_id"), "Other"); + selectOptionByText(Locator.name("clade_id"), "Other"); + selectOptionByText(Locator.name("group_type_id"), "Other"); + clickAndWait(Locator.css("a.labkey-button > span")); clickFolder(FOLDER_NAME); // Import some test Peptides from a file. clickAndWait(Locator.linkWithText("Import Peptides")); - new Select(getDriver().findElement(By.id("actionType"))).selectByVisibleText("Peptides"); - getDriver().findElement(By.name("pFile")).sendKeys(getSampledataPath() + "/peptide_file/gagptegprac.txt"); + selectOptionByText(Locator.id("actionType"), "Peptides"); + setFormElement(Locator.name("pFile"), getSampleData("/peptide_file/gagptegprac.txt")); clickButton("Import Peptides"); /* Import Peptide Pool 'Pool Descriptions' file. * ./test_import_files/pool_description_file/pool_description.txt */ clickFolder(FOLDER_NAME); - getDriver().findElement(By.linkText("Import Peptide Pools")).click(); - new Select(getDriver().findElement(By.name("actionType"))).selectByVisibleText("Pool Descriptions"); + clickAndWait(Locator.linkWithText("Import Peptide Pools")); + selectOptionByText(Locator.name("actionType"), "Pool Descriptions"); - getDriver().findElement(By.name("pFile")).sendKeys(getSampledataPath() + "/pool_description_file/pool_description.txt"); - getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); + setFormElement(Locator.name("pFile"), getSampleData("/pool_description_file/pool_description.txt")); + clickAndWait(Locator.css("a.labkey-button > span")); /* Import Peptide Pool 'Peptides in Pool' file. ./test_import_files/pool_detail_file/pool_details.txt */ clickFolder(FOLDER_NAME); - getDriver().findElement(By.linkText("Import Peptide Pools")).click(); - new Select(getDriver().findElement(By.name("actionType"))).selectByVisibleText("Peptides in Pool"); + clickAndWait(Locator.linkWithText("Import Peptide Pools")); + selectOptionByText(Locator.name("actionType"), "Peptides in Pool"); - getDriver().findElement(By.name("pFile")).sendKeys(getSampledataPath() + "/pool_detail_file/pool_details.txt"); - getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); + setFormElement(Locator.name("pFile"), getSampleData("/pool_detail_file/pool_details.txt")); + clickAndWait(Locator.css("a.labkey-button > span")); /* Search for the Peptides belonging to our just-imported pool */ clickFolder(FOLDER_NAME); - getDriver().findElement(By.linkText("Search for Peptides by Criteria")).click(); - new Select(getDriver().findElement(By.name("queryKey"))).selectByVisibleText("Peptides in a Peptide Pool"); - selectOptionByTextContaining(getDriver().findElement(By.name("queryValue")), "Prac_Pool"); + clickAndWait(Locator.linkWithText("Search for Peptides by Criteria")); + selectOptionByText(Locator.name("queryKey"), "Peptides in a Peptide Pool"); + selectOptionByTextContaining(getDriver().findElement(Locator.name("queryValue")), "Prac_Pool"); - getDriver().findElement(By.name("action_type")).click(); + clickAndWait(Locator.name("action_type")); // Identify the index at which our peptide IDs start. findPeptideStartIndex(); // List the peptides in the the 'gagptegprac' Peptide Group. We expect there to be 16 of them. clickFolder(FOLDER_NAME); - getDriver().findElement(By.linkText("Search for Peptides by Criteria")).click(); + clickAndWait(Locator.linkWithText("Search for Peptides by Criteria")); waitForText("Search for Peptides using different criteria : "); - new Select(getDriver().findElement(By.id("queryKey"))).selectByVisibleText("Peptides in a Peptide Group"); - new Select(getDriver().findElement(By.id("queryValue"))).selectByVisibleText("gagptegprac"); + selectOptionByText(Locator.id("queryKey"), "Peptides in a Peptide Group"); + selectOptionByText(Locator.id("queryValue"), "gagptegprac"); - getDriver().findElement(By.name("action_type")).click(); + clickAndWait(Locator.name("action_type")); - assertTextPresentInThisOrder("There are (16) peptides in the 'gagptegprac' peptide group. "); + assertTextPresentInThisOrder("There are (16) peptides in the 'gagptegprac' peptide group."); // Select a newly uploaded peptide, #3 and edit it to have a storage location of 'Kitchen Sink' - getDriver().findElement(By.linkText(pepString(4))).click(); + clickAndWait(Locator.linkWithText(pepString(4))); // Verify the expected record's content - assertTrue(getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody")).getText().matches("^[\\s\\S]*Peptide Id\\s*" + pepString(4) + "\nPeptide Sequence\\s*REPRGSDIAGTTSTL\nProtein Category\\s*p24\nSequence Length\\s*15\nAAStart\\s*97\nAAEnd\\s*111\nIs Child\\s*false\nIs Parent\\s*false[\\s\\S]*$")); + assertTrue(getDriver().findElement(Locator.xpath("//form[@lk-region-form='peptides']/table/tbody")).getText().matches("^[\\s\\S]*Peptide Id:\\s*" + pepString(4) + "\nPeptide Sequence:\\s*REPRGSDIAGTTSTL\nProtein Category:\\s*p24\nSequence Length:\\s*15\nAAStart:\\s*97\nAAEnd:\\s*111\nIs Child:\\s*false\nIs Parent:\\s*false[\\s\\S]*$")); - getDriver().findElement(By.xpath("//form[@id='peptides']/div/span[2]/a/span")).click(); - getDriver().findElement(By.name("storage_location")).clear(); - getDriver().findElement(By.name("storage_location")).sendKeys("Kitchen Sink"); + clickAndWait(Locator.linkWithText("Edit Peptide")); + setFormElement(Locator.name("storage_location"), "Kitchen Sink"); clickAndWait(Locator.xpath("//span[text()='Save Changes']")); // Assert that the Storage Location now contains "Kitchen Sink" - assertTrue(getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody")).getText().matches("^[\\s\\S]*Peptide Id\\s*" + pepString(4) + "\nPeptide Sequence\\s*REPRGSDIAGTTSTL\nProtein Category\\s*p24\nSequence Length\\s*15\nAAStart\\s*97\nAAEnd\\s*111\nIs Child\\s*false\nIs Parent\\s*false[\\s\\S]*$")); - getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); + assertTrue(getDriver().findElement(Locator.xpath("//form[@lk-region-form='peptides']/table/tbody")).getText().matches("^[\\s\\S]*Peptide Id:\\s*" + pepString(4) + "\nPeptide Sequence:\\s*REPRGSDIAGTTSTL\nProtein Category:\\s*p24\nSequence Length:\\s*15\nAAStart:\\s*97\nAAEnd:\\s*111\nIs Child:\\s*false\nIs Parent:\\s*false[\\s\\S]*$")); + clickAndWait(Locator.css("a.labkey-button > span")); // Search for a single, newly-uploaded peptide and verify it displays as expected. - getDriver().findElement(By.name("peptide_id")).clear(); - getDriver().findElement(By.name("peptide_id")).sendKeys(Integer.toString(peptideStartIndex + 9)); + setFormElement(Locator.name("peptide_id"), Integer.toString(peptideStartIndex + 9)); clickButton("Find"); // Verify the expected record's content - assertTrue(getDriver().findElement(By.id("peptides")).getText().matches("^[\\s\\S]*Peptide Id\\s*" + pepString(9) + "\nPeptide Sequence\\s*KCGKEGHQMKDCTER\nProtein Category\\s*p2p7p1p6\nSequence Length\\s*15\nAAStart\\s*52\nAAEnd\\s*66\nIs Child\\s*false\nIs Parent\\s*false\nStorage Location\\s*\n[\\s\\S]*$")); + assertTrue(getDriver().findElement(Locator.tagWithAttribute("form", "lk-region-form", "peptides")).getText() + .matches("^[\\s\\S]*Peptide Id:\\s*" + pepString(9) + "\nPeptide Sequence:\\s*KCGKEGHQMKDCTER\nProtein Category:\\s*p2p7p1p6\nSequence Length:\\s*15\nAAStart:\\s*52\nAAEnd:\\s*66\nIs Child:\\s*false\nIs Parent:\\s*false\nStorage Location:\\s*\n[\\s\\S]*$")); - getDriver().findElement(By.cssSelector("a.labkey-button > span")).click(); + clickAndWait(Locator.css("a.labkey-button > span")); /* @@ -197,48 +192,23 @@ public void testSteps() throws Exception * */ clickFolder(FOLDER_NAME); - getDriver().findElement(By.linkText("Search for Peptides by Criteria")).click(); - new Select(getDriver().findElement(By.id("queryKey"))).selectByVisibleText("Peptides in a Peptide Pool"); + clickAndWait(Locator.linkWithText("Search for Peptides by Criteria")); + selectOptionByText(Locator.id("queryKey"), "Peptides in a Peptide Pool"); - selectOptionByTextContaining(getDriver().findElement(By.name("queryValue")), "Prac_Pool"); - getDriver().findElement(By.name("action_type")).click(); - getDriver().findElement(By.linkText(pepString(4))).click(); - - StringBuffer verificationErrors = new StringBuffer(); - try - { - assertEquals("Peptide Sequence", getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody/tr[2]/td")).getText()); - } - catch (Error e) - { - verificationErrors.append(e.toString()); - } - try - { - assertEquals("REPRGSDIAGTTSTL", getDriver().findElement(By.xpath("//form[@id='peptides']/table/tbody/tr[2]/td[2]")).getText()); - } - catch (Error e) - { - verificationErrors.append(e.toString()); - } - try - { - assertEquals("gagptegprac (LAB ID =GAG1-4)", getDriver().findElement(By.cssSelector("#bodypanel > div > table > tbody > tr > td")).getText()); - } - catch (Error e) - { - verificationErrors.append(e.toString()); - } + selectOptionByTextContaining(getDriver().findElement(Locator.name("queryValue")), "Prac_Pool"); + clickAndWait(Locator.name("action_type")); + clickAndWait(Locator.linkWithText(pepString(4))); + checker().verifyEquals("", "Peptide Sequence:", getDriver().findElement(Locator.xpath("//form[@lk-region-form='peptides']/table/tbody/tr[2]/td")).getText()); + checker().verifyEquals("", "REPRGSDIAGTTSTL", getDriver().findElement(Locator.xpath("//form[@lk-region-form='peptides']/table/tbody/tr[2]/td[2]")).getText()); + checker().verifyEquals("", "gagptegprac (PEPTIDE NUMBER =GAG1-4)", getDriver().findElement(Locator.css(".lk-body-ct div:nth-of-type(2) > table > tbody > tr > td")).getText()); + checker().recordResults(); } @LogMethod - private void cleanupSchema(Connection cn) throws IOException + private void cleanupSchema() throws IOException, CommandException { - if (cn == null) - { - cn = createDefaultConnection(false); - } + Connection cn = createDefaultConnection(); cleanupTable(cn, "pepdb", "peptide_pool_assignment"); cleanupTable(cn, "pepdb", "peptide_group_assignment"); @@ -249,28 +219,19 @@ private void cleanupSchema(Connection cn) throws IOException } @LogMethod - private void cleanupTable(Connection cn, String schemaName, String tableName) throws IOException + private void cleanupTable(Connection cn, String schemaName, String tableName) throws IOException, CommandException { - log("** Deleting all " + tableName + " in all containers"); - try - { - SelectRowsCommand selectCmd = new SelectRowsCommand(schemaName, tableName); - selectCmd.setMaxRows(-1); - selectCmd.setContainerFilter(ContainerFilter.AllFolders); - selectCmd.setColumns(Arrays.asList("*")); - SelectRowsResponse selectResp = selectCmd.execute(cn, getProjectName()); - if (selectResp.getRowCount().intValue() > 0) - { - DeleteRowsCommand deleteCmd = new DeleteRowsCommand(schemaName, tableName); - deleteCmd.setRows(selectResp.getRows()); - deleteCmd.execute(cn, getProjectName()); - assertEquals("Expected no rows remaining", 0, selectCmd.execute(cn, getProjectName()).getRowCount().intValue()); - } - } - catch (CommandException e) + SelectRowsCommand selectCmd = new SelectRowsCommand(schemaName, tableName); + selectCmd.setMaxRows(-1); + selectCmd.setContainerFilter(ContainerFilter.AllFolders); + selectCmd.setColumns(Arrays.asList("*")); + SelectRowsResponse selectResp = selectCmd.execute(cn, getProjectName()); + if (selectResp.getRowCount().intValue() > 0) { - log("** Error during cleanupTable:"); - e.printStackTrace(System.out); + DeleteRowsCommand deleteCmd = new DeleteRowsCommand(schemaName, tableName); + deleteCmd.setRows(selectResp.getRows()); + deleteCmd.execute(cn, getProjectName()); + assertEquals("Expected no rows remaining", 0, selectCmd.execute(cn, getProjectName()).getRowCount().intValue()); } } @@ -279,7 +240,7 @@ void ensureExternalSchema(String containerPath) { log("** Ensure ExternalSchema: " + USER_SCHEMA_NAME); - beginAt("/query/" + containerPath + "/admin.view"); + beginAt(WebTestHelper.buildURL("query", containerPath, "admin")); if (!isTextPresent("reload")) { @@ -301,7 +262,7 @@ void ensureExternalSchema(String containerPath) // Identify the index at which our peptide IDs start. private void findPeptideStartIndex() throws IOException { - Connection cn = createDefaultConnection(false); + Connection cn = createDefaultConnection(); try { ensureExternalSchema(getProjectName()); @@ -357,18 +318,18 @@ protected void assertModuleEnabledByDefault(String moduleName) @Override protected void doCleanup(boolean afterTest) throws TestTimeoutException { - Connection cn = WebTestHelper.getRemoteApiConnection(); - try - { - cleanupSchema(cn); - _containerHelper.deleteProject(getProjectName(), afterTest); - } - catch (IOException e) + if (afterTest) { - throw new RuntimeException(e); + try + { + cleanupSchema(); + } + catch (IOException | CommandException e) + { + throw new RuntimeException(e); + } } - // super method deletes the project. - super.doCleanup(afterTest); + _containerHelper.deleteProject(getProjectName(), afterTest); } @Override @@ -377,9 +338,8 @@ public List getAssociatedModules() return Arrays.asList("pepdb"); } - public static String getSampledataPath() + public static File getSampleData(String sampleDataPath) { - File path = new File(TestFileUtils.getLabKeyRoot(), "externalModules/scharp/pepdb/test/sampledata/test_import_files"); - return path.toString(); + return TestFileUtils.getSampleData("test_import_files" + sampleDataPath); } } From ca112a8e270d3eac02dc0ad2cb733cf02dfffcbb Mon Sep 17 00:00:00 2001 From: Susan Hert Date: Tue, 16 Nov 2021 07:28:04 -0800 Subject: [PATCH 563/587] Update qcStateSettings description based on sample status availability. (#90) --- src/org/labkey/trialshare/TrialShareController.java | 5 +++-- .../labkey/test/tests/trialshare/TrialShareExportTest.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 856026a0..2d3dea17 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -36,6 +36,7 @@ import org.labkey.api.pipeline.PipeRoot; import org.labkey.api.pipeline.PipelineService; import org.labkey.api.pipeline.PipelineUrls; +import org.labkey.api.qc.SampleStatusService; import org.labkey.api.security.IgnoresTermsOfUse; import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.AdminPermission; @@ -575,7 +576,7 @@ enum FolderDataTypes participantCommentSettings("Participant Comment Settings", TrialShareExportForm::getParticipantCommentSettings), participantGroups("Participant Groups", TrialShareExportForm::getParticipantGroups), protocolDocuments("Protocol Documents", TrialShareExportForm::getProtocolDocuments), - qcStateSettings("QC State Settings", TrialShareExportForm::getQcStateSettings), + qcStateSettings(SampleStatusService.get().supportsSampleStatus() ? "Sample Status and QC State Settings" : "QC State Settings", TrialShareExportForm::getQcStateSettings), specimenSettings("Specimen Settings", TrialShareExportForm::getSpecimenSettings), specimens("Specimens", TrialShareExportForm::getSpecimens), treatmentData("Treatment Data", TrialShareExportForm::getTreatmentData), @@ -681,4 +682,4 @@ public Set getRegisteredDataTypes(boolean onlyDefault) return dataTypes; } } -} \ No newline at end of file +} diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java index 3dc0c4c6..6566e94c 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java @@ -46,7 +46,7 @@ public void testTrialShareExportActionDefault() throws Exception goToModule("FileContent"); _fileBrowserHelper.selectFileBrowserItem("/export/folder.xml"); List fileList = _fileBrowserHelper.getFileList(); - List expectedFiles = Arrays.asList("etls", "inventory", "sample-types", "wikis", "xar", "folder.xml", "pages.xml"); + List expectedFiles = Arrays.asList("etls", "inventory", "sample-types", "wikis", "xar", "data_states.xml", "folder.xml", "pages.xml"); assertEquals("Default export should include several folder objects", expectedFiles, fileList); } From e4013452409fca58112824a095a93bf453b85b97 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Thu, 23 Dec 2021 08:51:19 -0800 Subject: [PATCH 564/587] Remove unnecessary usage of deprecated method (#14) --- test/src/org/labkey/test/tests/pepdb/PepDBModuleTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/test/src/org/labkey/test/tests/pepdb/PepDBModuleTest.java b/test/src/org/labkey/test/tests/pepdb/PepDBModuleTest.java index 94e97f28..2b5ac550 100644 --- a/test/src/org/labkey/test/tests/pepdb/PepDBModuleTest.java +++ b/test/src/org/labkey/test/tests/pepdb/PepDBModuleTest.java @@ -202,7 +202,6 @@ public void testSteps() throws Exception checker().verifyEquals("", "Peptide Sequence:", getDriver().findElement(Locator.xpath("//form[@lk-region-form='peptides']/table/tbody/tr[2]/td")).getText()); checker().verifyEquals("", "REPRGSDIAGTTSTL", getDriver().findElement(Locator.xpath("//form[@lk-region-form='peptides']/table/tbody/tr[2]/td[2]")).getText()); checker().verifyEquals("", "gagptegprac (PEPTIDE NUMBER =GAG1-4)", getDriver().findElement(Locator.css(".lk-body-ct div:nth-of-type(2) > table > tbody > tr > td")).getText()); - checker().recordResults(); } @LogMethod From cda4203065e21b292de21ef76c3323bbbe0b22a2 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 27 Dec 2021 16:44:34 -0800 Subject: [PATCH 565/587] Data finder enhancements for ITN (#91) --- module.properties | 4 +- resources/olap/ITN_PublicationCube.xml | 41 +++++++++++++++++++++ resources/olap/ITN_StudyCube.xml | 51 ++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 resources/olap/ITN_PublicationCube.xml create mode 100644 resources/olap/ITN_StudyCube.xml diff --git a/module.properties b/module.properties index 2c7ab896..fb0db517 100644 --- a/module.properties +++ b/module.properties @@ -1,6 +1,6 @@ ModuleClass: org.labkey.trialshare.TrialShareModule -Label: Data Finder for ITN -Description: Provides a web part for exploring the studies and manuscripts available within ITN. +Label: Custom specimen behavior and Data Finder configuration for ITN +Description: Overrides some behavior of the specimen module and provides Data Finder cube definitions for ITN. URL: https://www.itntrialshare.org License: Apache 2.0 LicenseURL: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/resources/olap/ITN_PublicationCube.xml b/resources/olap/ITN_PublicationCube.xml new file mode 100644 index 00000000..32705231 --- /dev/null +++ b/resources/olap/ITN_PublicationCube.xml @@ -0,0 +1,41 @@ + + + + + + FALSE + + + + + + + + + +
+ + + true + + + + +
+
+ + + + + + + true + + + + + + + + + diff --git a/resources/olap/ITN_StudyCube.xml b/resources/olap/ITN_StudyCube.xml new file mode 100644 index 00000000..7fe29f68 --- /dev/null +++ b/resources/olap/ITN_StudyCube.xml @@ -0,0 +1,51 @@ + + + + + + FALSE + + +
+ + + + + + + + + + + + + +
+ + + true + + + +
+ + + +
+ + + +
+ + + +
+ + + + + + + + + From d3748857ada5aa7adcb4078064870193b33747b1 Mon Sep 17 00:00:00 2001 From: labkey-sweta Date: Wed, 12 Jan 2022 10:52:54 -0800 Subject: [PATCH 566/587] Waiting for the link before clicking (#95) --- test/src/org/labkey/test/tests/trialshare/ITNSpecimenTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/org/labkey/test/tests/trialshare/ITNSpecimenTest.java b/test/src/org/labkey/test/tests/trialshare/ITNSpecimenTest.java index edb1a82d..27df1745 100644 --- a/test/src/org/labkey/test/tests/trialshare/ITNSpecimenTest.java +++ b/test/src/org/labkey/test/tests/trialshare/ITNSpecimenTest.java @@ -106,7 +106,7 @@ public void testSpecimenRequestCustomization() SpecimenHelper specimenHelper = new SpecimenHelper(this); clickPortalTab("Specimen Data"); - clickAndWait(Locator.linkWithText("Urine")); + waitAndClickAndWait(Locator.linkWithText("Urine")); SpecimenDetailGrid specimenGrid = specimenHelper.findSpecimenDetailGrid(); specimenGrid.checkCheckbox(0); ShowCreateSpecimenRequestPage requestPage = specimenGrid.createNewRequest(); From 0aabeb6e4500f748775b9323c2117cc964b3be26 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Thu, 7 Apr 2022 14:47:07 -0700 Subject: [PATCH 567/587] Refactor import/export context and writer hierarchies (#99) --- src/org/labkey/trialshare/TrialShareController.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 2d3dea17..52b8288b 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -25,7 +25,7 @@ import org.labkey.api.action.Marshaller; import org.labkey.api.action.MutatingApiAction; import org.labkey.api.action.SpringActionController; -import org.labkey.api.admin.AbstractFolderContext; +import org.labkey.api.admin.AbstractFolderContext.ExportType; import org.labkey.api.admin.FolderExportContext; import org.labkey.api.admin.FolderSerializationRegistry; import org.labkey.api.admin.FolderWriter; @@ -44,7 +44,6 @@ import org.labkey.api.view.ActionURL; import org.labkey.api.view.NotFoundException; import org.labkey.api.writer.FileSystemFile; -import org.labkey.api.writer.Writer; import org.springframework.validation.BindException; import java.io.File; @@ -667,15 +666,14 @@ public Set getRegisteredDataTypes(boolean onlyDefault) Set dataTypes = new HashSet<>(); Set filteredFolderWriters; if (onlyDefault) - filteredFolderWriters = folderWriters.stream().filter(fw -> fw.selectedByDefault(AbstractFolderContext.ExportType.ALL)).collect(Collectors.toSet()); + filteredFolderWriters = folderWriters.stream().filter(fw -> fw.selectedByDefault(ExportType.ALL)).collect(Collectors.toSet()); else filteredFolderWriters = new HashSet<>(folderWriters); filteredFolderWriters.forEach(fw -> { dataTypes.add(fw.getDataType()); - Collection children = fw.getChildren(false, false); - if (children != null) - children.forEach(w -> dataTypes.add(w.getDataType())); + fw.getChildren(false, false) + .forEach(w -> dataTypes.add(w.getDataType())); }); dataTypes.remove(null); From fbfe51e749eb9be391e31d527f51275d1db4ae82 Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Mon, 2 May 2022 17:57:19 -0700 Subject: [PATCH 568/587] Add PanoramaQC to expected list of export options (#100) --- src/org/labkey/trialshare/TrialShareController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 52b8288b..fbd7e285 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -446,7 +446,6 @@ public void setExperimentsAndRuns(boolean experimentsAndRuns) { this.experimentsAndRuns = experimentsAndRuns; } - } /* @@ -658,7 +657,9 @@ public void testDefaultDataTypes() Collections.sort(expectedDataTypes); Collections.sort(actualDefaultDataTypes); - Assert.assertEquals("TrialShareExport default data types do not match core defaults", expectedDataTypes, actualDefaultDataTypes); + expectedDataTypes.removeAll(actualDefaultDataTypes); + + Assert.assertTrue("TrialShareExport default data types missing expected values: " + expectedDataTypes, expectedDataTypes.isEmpty()); } public Set getRegisteredDataTypes(boolean onlyDefault) From e3b95a14ff26597680235152dc3c7010dc7d254f Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Wed, 4 May 2022 08:48:56 -0700 Subject: [PATCH 569/587] Add PanoramaQC to expected list of export options (#100) (#101) --- src/org/labkey/trialshare/TrialShareController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index 2d3dea17..5782b615 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -447,7 +447,6 @@ public void setExperimentsAndRuns(boolean experimentsAndRuns) { this.experimentsAndRuns = experimentsAndRuns; } - } /* @@ -659,7 +658,9 @@ public void testDefaultDataTypes() Collections.sort(expectedDataTypes); Collections.sort(actualDefaultDataTypes); - Assert.assertEquals("TrialShareExport default data types do not match core defaults", expectedDataTypes, actualDefaultDataTypes); + expectedDataTypes.removeAll(actualDefaultDataTypes); + + Assert.assertTrue("TrialShareExport default data types missing expected values: " + expectedDataTypes, expectedDataTypes.isEmpty()); } public Set getRegisteredDataTypes(boolean onlyDefault) From 4e1a8ebb6e169cc5b31d127c0e3b2da42af0513b Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 12 Aug 2022 09:28:22 -0700 Subject: [PATCH 570/587] Issue 46060: Excel exports are leaving permanent 'temporary poifiles' (#15) --- src/org/scharp/atlas/pepdb/PepDBController.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 7efe3b8b..7ba2d8cd 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -842,14 +842,12 @@ public void printExcel(Object bean, HttpServletResponse response, BindException DataRegion rgn = getDataRegion(getContainer(), form, Table.ALL_ROWS); context.setBaseFilter(form.getFilter()); context.setBaseSort(form.getSort()); - try (ExcelWriter ew = new ExcelWriter(()->rgn.getResults(context), rgn.getDisplayColumns())) - { - ew.setAutoSize(true); - ew.setFilenamePrefix(form.getMessage()); - ew.setSheetName(form.getMessage()); - ew.setFooter(form.getMessage()); - ew.write(getResponse()); - } + ExcelWriter ew = new ExcelWriter(()->rgn.getResults(context), rgn.getDisplayColumns()); + ew.setAutoSize(true); + ew.setFilenamePrefix(form.getMessage()); + ew.setSheetName(form.getMessage()); + ew.setFooter(form.getMessage()); + ew.renderWorkbook(getResponse()); } catch (SQLException e) { @@ -863,7 +861,6 @@ public void printExcel(Object bean, HttpServletResponse response, BindException { _log.error("export: " + e); } - } } From c2ff00adbb93c45dbd9c72637c505588c03d34e7 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 12 Aug 2022 20:09:57 -0700 Subject: [PATCH 571/587] Manipulate and pass around ActionURLs, not Strings (#16) --- src/org/scharp/atlas/pepdb/PepDBController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 7ba2d8cd..08dd13d8 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -423,7 +423,7 @@ public ModelAndView getView(PeptideQueryForm form, BindException errors) throws DisplayColumn col = rgn.getDisplayColumn(PepDBSchema.COLUMN_PEPTIDE_GROUP_NAME); ActionURL displayAction = new ActionURL(DisplayPeptideGroupInformationAction.class, getContainer()); displayAction.addParameter(PepDBSchema.COLUMN_PEPTIDE_GROUP_ID,"${" + PepDBSchema.COLUMN_PEPTIDE_GROUP_ID + "}"); - col.setURL(displayAction.toString()); + col.setURL(displayAction); GridView gridView = new GridView(rgn, errors); gridView.setTitle("All the Peptide Groups in the System are : "); return gridView; From de25d363f5130b991b82d9d44e4946146c112f3d Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Tue, 24 Jan 2023 18:25:16 -0800 Subject: [PATCH 572/587] Refactor Java remote API hierarchy (#106) --- src/org/labkey/trialshare/TrialShareController.java | 2 +- .../labkey/test/tests/trialshare/TrialShareExportTest.java | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index fbd7e285..bbff71c5 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -74,7 +74,7 @@ public TrialShareController() example usage (via LabKey Remote Java API): - PostCommand pc = new PostCommand("pipeline","TrialShareExport"); + SimplePostCommand pc = new SimplePostCommand("pipeline","TrialShareExport"); JSONObject jo = new JSONObject(); pc.setJsonObject(jo); CommandResponse cr = pc.execute(cn, "/Studies/ITN027AIOPR/Study Data/"); diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java index 6566e94c..afd0165b 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java @@ -3,9 +3,8 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; -import org.labkey.remoteapi.Command; import org.labkey.remoteapi.Connection; -import org.labkey.remoteapi.PostCommand; +import org.labkey.remoteapi.SimplePostCommand; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Git; @@ -40,7 +39,7 @@ public void testTrialShareExportActionDefault() throws Exception _containerHelper.createSubfolder(getProjectName(), subfolder); Connection connection = WebTestHelper.getRemoteApiConnection(); - Command command = new PostCommand("trialShare", "trialShareExport"); + SimplePostCommand command = new SimplePostCommand("trialShare", "trialShareExport"); command.execute(connection, getProjectName() + "/" + subfolder); goToModule("FileContent"); @@ -57,7 +56,7 @@ public void testTrialShareExportActionCustom() throws Exception _containerHelper.createSubfolder(getProjectName(), subfolder); Connection connection = WebTestHelper.getRemoteApiConnection(); - Command command = new PostCommand("trialShare", "trialShareExport"); + SimplePostCommand command = new SimplePostCommand("trialShare", "trialShareExport"); Map params = new HashMap<>(); params.put("webpartPropertiesAndLayout", true); // Add one custom data type to override default command.setParameters(params); From caddbf0c9f65d8fd83b2a316f13dc277af40acda Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Wed, 31 May 2023 14:48:57 -0700 Subject: [PATCH 573/587] Update GitHub workflows to use checkout v3 (#17) --- .github/workflows/branch_release.yml | 2 +- .github/workflows/merge_release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/branch_release.yml b/.github/workflows/branch_release.yml index e0146463..b864e715 100644 --- a/.github/workflows/branch_release.yml +++ b/.github/workflows/branch_release.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Create branches and PRs uses: LabKey/gitHubActions/branch-release@develop diff --git a/.github/workflows/merge_release.yml b/.github/workflows/merge_release.yml index 289c7461..440c9a3b 100644 --- a/.github/workflows/merge_release.yml +++ b/.github/workflows/merge_release.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Merge PR uses: LabKey/gitHubActions/merge-release@develop From 65a2e3452b2a1264116024db5583007809c4fdd5 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Wed, 31 May 2023 14:50:04 -0700 Subject: [PATCH 574/587] Update GitHub workflows to use checkout v3 (#107) --- .github/workflows/branch_release.yml | 2 +- .github/workflows/merge_release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/branch_release.yml b/.github/workflows/branch_release.yml index e0146463..b864e715 100644 --- a/.github/workflows/branch_release.yml +++ b/.github/workflows/branch_release.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Create branches and PRs uses: LabKey/gitHubActions/branch-release@develop diff --git a/.github/workflows/merge_release.yml b/.github/workflows/merge_release.yml index 289c7461..440c9a3b 100644 --- a/.github/workflows/merge_release.yml +++ b/.github/workflows/merge_release.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Merge PR uses: LabKey/gitHubActions/merge-release@develop From c8e68005a6913dc676fcdc7ddb6398c99b24ec80 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Tue, 13 Jun 2023 07:58:01 -0700 Subject: [PATCH 575/587] PepDB needs object deserialization (#18) --- src/org/scharp/atlas/pepdb/PepDBBaseController.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/org/scharp/atlas/pepdb/PepDBBaseController.java b/src/org/scharp/atlas/pepdb/PepDBBaseController.java index ee914c14..a66a731d 100644 --- a/src/org/scharp/atlas/pepdb/PepDBBaseController.java +++ b/src/org/scharp/atlas/pepdb/PepDBBaseController.java @@ -339,6 +339,11 @@ public PeptideForm(String peptideID) set("peptide_id", String.valueOf(peptideID)); } + @Override + protected boolean deserializeOldValues() + { + return true; + } } public static class PeptidePoolForm extends BeanViewForm { From 2195ad3db91628b53c66d83554ed4ccf03f1d4b9 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Thu, 15 Jun 2023 21:02:07 +0000 Subject: [PATCH 576/587] Stop relying on de-serialization (#19) --- .../atlas/pepdb/PepDBBaseController.java | 6 ---- .../scharp/atlas/pepdb/PepDBController.java | 9 +++-- .../scharp/atlas/pepdb/view/PepDBWebPart.java | 33 +++---------------- .../scharp/atlas/pepdb/view/pepDBWebPart.jsp | 8 ++--- 4 files changed, 11 insertions(+), 45 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBBaseController.java b/src/org/scharp/atlas/pepdb/PepDBBaseController.java index a66a731d..57d01f85 100644 --- a/src/org/scharp/atlas/pepdb/PepDBBaseController.java +++ b/src/org/scharp/atlas/pepdb/PepDBBaseController.java @@ -338,12 +338,6 @@ public PeptideForm(String peptideID) this(); set("peptide_id", String.valueOf(peptideID)); } - - @Override - protected boolean deserializeOldValues() - { - return true; - } } public static class PeptidePoolForm extends BeanViewForm { diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 08dd13d8..35ed6c29 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -287,7 +287,7 @@ public void addNavTrail(NavTree root) public class EditPeptideAction extends FormViewAction { @Override - public ModelAndView getView(PeptideForm form, boolean reshow, BindException errors) throws Exception + public ModelAndView getView(PeptideForm form, boolean reshow, BindException errors) { Peptides p = PepDBManager.getPeptideById(form.getBean().getPeptide_id()); UpdateView uView = new UpdateView(form, errors); @@ -315,10 +315,9 @@ public ModelAndView getView(PeptideForm form, boolean reshow, BindException erro @Override public boolean handlePost(PeptideForm form, BindException errors) throws Exception { + Peptides oldBean = PepDBManager.getPeptideById(form.getBean().getPeptide_id()); + form.setOldValues(oldBean); Peptides bean = form.getBean(); - Peptides dbBean = PepDBManager.getPeptideById(bean.getPeptide_id()); - bean.setCreated(dbBean.getCreated()); - bean.setCreatedBy(dbBean.getCreatedBy()); PepDBManager.updatePeptide(getUser(), bean); return true; } @@ -352,7 +351,7 @@ public void addNavTrail(NavTree root) public class EditPeptidePoolAction extends FormViewAction { @Override - public ModelAndView getView(PeptidePoolForm form, boolean reshow, BindException errors) throws Exception + public ModelAndView getView(PeptidePoolForm form, boolean reshow, BindException errors) { PeptidePool p = PepDBManager.getPeptidePoolByID(form.getBean().getPeptide_pool_id()); UpdateView uView = new UpdateView(form, errors); diff --git a/src/org/scharp/atlas/pepdb/view/PepDBWebPart.java b/src/org/scharp/atlas/pepdb/view/PepDBWebPart.java index ec1dfce3..64c16b1b 100644 --- a/src/org/scharp/atlas/pepdb/view/PepDBWebPart.java +++ b/src/org/scharp/atlas/pepdb/view/PepDBWebPart.java @@ -1,37 +1,12 @@ package org.scharp.atlas.pepdb.view; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.labkey.api.view.JspView; -import javax.servlet.ServletException; - -/** - * @version $Id$ - */ -public class PepDBWebPart extends JspView { - - private static Logger _log = LogManager.getLogger(PepDBWebPart.class); - - /** - * - */ - public PepDBWebPart() { +public class PepDBWebPart extends JspView +{ + public PepDBWebPart() + { super("/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp", null); setTitle("PepDB Web Part"); } - - /* (non-Javadoc) - * @see org.labkey.api.view.WebPartView#prepareWebPart(java.lang.Object) - */ - @Override - protected void prepareWebPart(Object object) throws ServletException { - super.prepareWebPart(object); - /* try { - getViewContext().put("peptides", - PepDBManager.getPeptideGroups()); - } catch (SQLException e) { - _log.error("Error retrieving list of PeptideGroups.", e); - } */ - } } diff --git a/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp b/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp index 04f46ed7..e016a654 100644 --- a/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp +++ b/src/org/scharp/atlas/pepdb/view/pepDBWebPart.jsp @@ -1,11 +1,9 @@ -<%@ page import="org.labkey.api.view.HttpView"%> -<%@ page import="org.labkey.api.view.ViewContext"%> <%@ page import="org.scharp.atlas.pepdb.PepDBController"%> -<%@ page import="org.scharp.atlas.pepdb.model.PeptideGroup" %> +<%@ page import="org.scharp.atlas.pepdb.PepDBManager"%> +<%@ page import="org.scharp.atlas.pepdb.model.PeptideGroup"%> <%@ page extends="org.labkey.api.jsp.JspBase" %> <% - ViewContext context = HttpView.currentContext(); - PeptideGroup[] peptides = (PeptideGroup[]) context.get("peptides"); + PeptideGroup[] peptides = PepDBManager.getPeptideGroups(); %> This container contains <%= peptides.length %> peptide groups.
<%= button("View Grid").href(urlFor(PepDBController.ShowAllPeptideGroupsAction.class)) %> From 4b7acd0e887d643f5b2094ad38212ca0e0a3e60f Mon Sep 17 00:00:00 2001 From: Karl Lum Date: Wed, 28 Jun 2023 16:04:46 -0700 Subject: [PATCH 577/587] Issue 47818 : export options to containing metadata only (#108) --- .../trialshare/TrialShareController.java | 82 +++++++++++++++---- .../trialshare/TrialShareExportTest.java | 2 +- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/org/labkey/trialshare/TrialShareController.java b/src/org/labkey/trialshare/TrialShareController.java index bbff71c5..58638335 100644 --- a/src/org/labkey/trialshare/TrialShareController.java +++ b/src/org/labkey/trialshare/TrialShareController.java @@ -106,14 +106,18 @@ public static class TrialShareExportForm private boolean webpartPropertiesAndLayout; private boolean containerSpecificModuleProperties; private boolean roleAssignmentsForUsersAndGroups; - private boolean lists; + private boolean listDesigns; + private boolean listData; private boolean queries; private boolean gridViews; private boolean reportsAndCharts; private boolean externalSchemaDefinitions; private boolean wikisAndTheirAttachments; private boolean notificationSettings; - private boolean sampleTypesAndDataClasses; + private boolean sampleTypeDesigns; + private boolean sampleTypeData; + private boolean dataClassDesigns; + private boolean dataClassData; private boolean inventoryLocationsAndItems; private boolean experimentsAndRuns; @@ -347,14 +351,24 @@ public void setRoleAssignmentsForUsersAndGroups(boolean roleAssignmentsForUsersA this.roleAssignmentsForUsersAndGroups = roleAssignmentsForUsersAndGroups; } - public boolean getLists() + public boolean isListDesigns() { - return lists; + return listDesigns; } - public void setLists(boolean lists) + public void setListDesigns(boolean listDesigns) { - this.lists = lists; + this.listDesigns = listDesigns; + } + + public boolean isListData() + { + return listData; + } + + public void setListData(boolean listData) + { + this.listData = listData; } public boolean getQueries() @@ -417,14 +431,44 @@ public void setNotificationSettings(boolean notificationSettings) this.notificationSettings = notificationSettings; } - public boolean getSampleTypesAndDataClasses() + public boolean getSampleTypeDesigns() + { + return sampleTypeDesigns; + } + + public void setSampleTypeDesigns(boolean sampleTypeDesigns) + { + this.sampleTypeDesigns = sampleTypeDesigns; + } + + public boolean getSampleTypeData() + { + return sampleTypeData; + } + + public void setSampleTypeData(boolean sampleTypeData) + { + this.sampleTypeData = sampleTypeData; + } + + public boolean getDataClassDesigns() + { + return dataClassDesigns; + } + + public void setDataClassDesigns(boolean dataClassDesigns) + { + this.dataClassDesigns = dataClassDesigns; + } + + public boolean getDataClassData() { - return sampleTypesAndDataClasses; + return dataClassData; } - public void setSampleTypesAndDataClasses(boolean sampleTypesAndDataClasses) + public void setDataClassData(boolean dataClassData) { - this.sampleTypesAndDataClasses = sampleTypesAndDataClasses; + this.dataClassData = dataClassData; } public boolean getInventoryLocationsAndItems() @@ -547,10 +591,14 @@ private static Set getDefaultExportDataTypes() FolderDataTypes.reportsAndCharts, FolderDataTypes.externalSchemaDefinitions, FolderDataTypes.etlDefinitions, - FolderDataTypes.lists, + FolderDataTypes.listDesigns, + FolderDataTypes.listData, FolderDataTypes.wikisAndAttachments, FolderDataTypes.notificationSettings, - FolderDataTypes.sampleTypesAndDataClasses, + FolderDataTypes.sampleTypeDesigns, + FolderDataTypes.sampleTypeData, + FolderDataTypes.dataClassDesigns, + FolderDataTypes.dataClassData, FolderDataTypes.inventoryLocationsAndItems, FolderDataTypes.experiments ); @@ -584,14 +632,18 @@ enum FolderDataTypes webpartProperties("Webpart properties and layout", TrialShareExportForm::getWebpartPropertiesAndLayout), moduleProperties("Container specific module properties", TrialShareExportForm::getContainerSpecificModuleProperties), roleAssignments("Role assignments for users and groups", TrialShareExportForm::getRoleAssignmentsForUsersAndGroups), - lists("Lists", TrialShareExportForm::getLists), + listDesigns("List Designs", TrialShareExportForm::isListDesigns), + listData("List Data", TrialShareExportForm::isListData), queries("Queries", TrialShareExportForm::getQueries), gridViews("Grid Views", TrialShareExportForm::getGridViews), reportsAndCharts("Reports and Charts", TrialShareExportForm::getReportsAndCharts), externalSchemaDefinitions("External schema definitions", TrialShareExportForm::getExternalSchemaDefinitions), wikisAndAttachments("Wikis and their attachments", TrialShareExportForm::getWikisAndTheirAttachments), notificationSettings("Notification settings", TrialShareExportForm::getNotificationSettings), - sampleTypesAndDataClasses("Sample Types and Data Classes", TrialShareExportForm::getSampleTypesAndDataClasses), + sampleTypeDesigns("Sample Type Designs", TrialShareExportForm::getSampleTypeDesigns), + sampleTypeData("Sample Type Data", TrialShareExportForm::getSampleTypeData), + dataClassDesigns("Data Class Designs", TrialShareExportForm::getDataClassDesigns), + dataClassData("Data Class Data", TrialShareExportForm::getDataClassData), inventoryLocationsAndItems("Inventory locations and items", TrialShareExportForm::getInventoryLocationsAndItems), experiments("Experiments, Protocols, and Runs", TrialShareExportForm::getExperimentsAndRuns); @@ -667,7 +719,7 @@ public Set getRegisteredDataTypes(boolean onlyDefault) Set dataTypes = new HashSet<>(); Set filteredFolderWriters; if (onlyDefault) - filteredFolderWriters = folderWriters.stream().filter(fw -> fw.selectedByDefault(ExportType.ALL)).collect(Collectors.toSet()); + filteredFolderWriters = folderWriters.stream().filter(fw -> fw.selectedByDefault(ExportType.ALL, false)).collect(Collectors.toSet()); else filteredFolderWriters = new HashSet<>(folderWriters); diff --git a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java index afd0165b..78d189ad 100644 --- a/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java +++ b/test/src/org/labkey/test/tests/trialshare/TrialShareExportTest.java @@ -45,7 +45,7 @@ public void testTrialShareExportActionDefault() throws Exception goToModule("FileContent"); _fileBrowserHelper.selectFileBrowserItem("/export/folder.xml"); List fileList = _fileBrowserHelper.getFileList(); - List expectedFiles = Arrays.asList("etls", "inventory", "sample-types", "wikis", "xar", "data_states.xml", "folder.xml", "pages.xml"); + List expectedFiles = Arrays.asList("data-classes", "etls", "inventory", "sample-types", "wikis", "xar", "data_states.xml", "folder.xml", "pages.xml"); assertEquals("Default export should include several folder objects", expectedFiles, fileList); } From d4a27af1a82ef018c928c8b8586c5d0bd499a2df Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 6 Oct 2023 18:14:43 -0700 Subject: [PATCH 578/587] Migrate User.isInSiteAdminGroup() -> User.hasSiteAdminPermission() (#109) --- src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java b/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java index 036ea738..e342c72e 100644 --- a/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java +++ b/src/org/labkey/trialshare/ITNSpecimenRequestCustomizer.java @@ -43,7 +43,7 @@ public boolean canChangeStatus(User user) Group specimenRequestAdmins = GroupManager.getGroup(ContainerManager.getRoot(), "Specimen Request Administrators", GroupEnumType.SITE); if (specimenRequestAdmins != null) { - return user.isInSiteAdminGroup() || user.isInGroup(specimenRequestAdmins.getUserId()); + return user.hasSiteAdminPermission() || user.isInGroup(specimenRequestAdmins.getUserId()); } LOG.error("Unable to find site group \"Specimen Request Administrators\", allowing user to update specimen request status"); return true; From 5945d0feb04330010be3dca5c0c6fd285741d229 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Thu, 1 Feb 2024 10:13:33 -0800 Subject: [PATCH 579/587] Tomcat 10 - Update namespace of Jakarta EE packages (#21) Co-authored-by: Adam Rauch --- src/org/scharp/atlas/pepdb/PepDBController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/PepDBController.java b/src/org/scharp/atlas/pepdb/PepDBController.java index 35ed6c29..c0998ff6 100644 --- a/src/org/scharp/atlas/pepdb/PepDBController.java +++ b/src/org/scharp/atlas/pepdb/PepDBController.java @@ -45,9 +45,9 @@ import org.springframework.validation.Errors; import org.springframework.web.servlet.ModelAndView; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; From 3b1996ff0e42171315f0c6af4582be50756f3283 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 19 Feb 2024 17:29:47 -0500 Subject: [PATCH 580/587] Add nonce to script tag (#22) --- .../atlas/pepdb/view/peptideGroupSelect.jsp | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp index 3463d9fa..b63268ab 100644 --- a/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp +++ b/src/org/scharp/atlas/pepdb/view/peptideGroupSelect.jsp @@ -15,7 +15,7 @@ if(bean.getMessage() != null){ %> <%=h(bean.getMessage())%><%}%> -