From fb654ad72d76a2acc7365a22d2295403734ab4cc Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Sat, 5 Nov 2022 06:23:16 -0700 Subject: [PATCH 01/17] Adds dash prototype --- dash/.dockerignore | 2 + dash/.gitignore | 1 + dash/Dockerfile | 10 ++ dash/app.py | 75 ++++++++++++++ dash/assets/gitops-logo.png | Bin 0 -> 126582 bytes dash/figures/histogram.py | 118 ++++++++++++++++++++++ dash/figures/project.py | 31 ++++++ dash/models/__init__.py | 0 dash/models/file.py | 98 ++++++++++++++++++ dash/models/observation.py | 62 ++++++++++++ dash/models/project.py | 61 ++++++++++++ dash/pages/__init__.py | 0 dash/pages/cohorts.py | 108 ++++++++++++++++++++ dash/pages/home.py | 24 +++++ dash/pages/profile.py | 60 +++++++++++ dash/requirements.txt | 15 +++ dash/util/__init__.py | 193 ++++++++++++++++++++++++++++++++++++ 17 files changed, 858 insertions(+) create mode 100644 dash/.dockerignore create mode 100644 dash/.gitignore create mode 100644 dash/Dockerfile create mode 100644 dash/app.py create mode 100644 dash/assets/gitops-logo.png create mode 100644 dash/figures/histogram.py create mode 100644 dash/figures/project.py create mode 100644 dash/models/__init__.py create mode 100644 dash/models/file.py create mode 100644 dash/models/observation.py create mode 100644 dash/models/project.py create mode 100644 dash/pages/__init__.py create mode 100644 dash/pages/cohorts.py create mode 100644 dash/pages/home.py create mode 100644 dash/pages/profile.py create mode 100644 dash/requirements.txt create mode 100644 dash/util/__init__.py diff --git a/dash/.dockerignore b/dash/.dockerignore new file mode 100644 index 00000000..ca5e3f26 --- /dev/null +++ b/dash/.dockerignore @@ -0,0 +1,2 @@ +venv/ +.idea/ \ No newline at end of file diff --git a/dash/.gitignore b/dash/.gitignore new file mode 100644 index 00000000..f7275bbb --- /dev/null +++ b/dash/.gitignore @@ -0,0 +1 @@ +venv/ diff --git a/dash/Dockerfile b/dash/Dockerfile new file mode 100644 index 00000000..8544b996 --- /dev/null +++ b/dash/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.10 +WORKDIR /code + +COPY requirements.txt / +RUN pip install -r /requirements.txt +COPY ./ ./ + +EXPOSE 8050 + +CMD ["python", "./app.py"] \ No newline at end of file diff --git a/dash/app.py b/dash/app.py new file mode 100644 index 00000000..f4aa4ae3 --- /dev/null +++ b/dash/app.py @@ -0,0 +1,75 @@ +import logging +import os + +import dash +import dash_bootstrap_components as dbc +from dash import Dash, Output, Input, dcc +from dash import html + +from util import get_authz + +external_stylesheets = [dbc.themes.SKETCHY] + +logger = logging.getLogger('dash') + +print('DASH_URL_BASE_PATHNAME', os.environ.get('DASH_URL_BASE_PATHNAME', default="~not set~")) +app = Dash(__name__, external_stylesheets=external_stylesheets, use_pages=True) + + +@app.server.before_request +def check_privileges(): + """Do this before every call""" + get_authz() + + +# Clientside callback +app.clientside_callback( + """ + // Call fence's /user endpoint, parse the response and update the profile + async function(n_intervals, data) { + const response = await fetch(location.origin + '/user/user'); + if (!response.ok) { + console.log('error retrieving user', response ) + return 'Profile (unauthorized)'; + } else { + const user = await response.json(); + console.log('clientside_callback you are logged in as:', user.username); + return 'Profile (' + user.username + ')'; + } + } + """, + Output('nav_item-profile', 'children'), + Input('clientside-interval', 'n_intervals') +) + + +app.layout = dbc.Container([ + html.H1("ACED", className="display-3"), + html.P( + "A simple dash app.", + className="lead", + ), + + dbc.Nav( + [ + dbc.NavItem( + dbc.NavLink(f"{page['name']}", href=page["relative_path"], id=f'nav_item-{page["name"].lower()}') + ) + for page in dash.page_registry.values() + ] + ), + + html.Hr(className="my-2"), + # define a timed client side action here + dcc.Interval( + id='clientside-interval', + n_intervals=0, + interval=60 * 1000 # in milliseconds check every minute + ), + # other page contents goes here + dash.page_container +]) + + +if __name__ == '__main__': + app.run_server(host="0.0.0.0", debug=True) # diff --git a/dash/assets/gitops-logo.png b/dash/assets/gitops-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..33f5bb2f68a4e70a204775a6ac4362087d9d7631 GIT binary patch literal 126582 zcmeFYWmKF^(?5tLIDtTL4Fq?03GVLh?(PyCf(LhZcXto&?lQQ`z%YOA=e6hE-Lv2J z+g|5%cXe0&s;;W)p3^hk6QLj{j*Nhd009AkEGZ$P1OWln{XuWR!F<%~=g=Pwl8vRX zu!5wpFtLJ@y_uzrDFlQ>M3OqJhVmF@wst~X+>B5PniqN@x}X#sUBErb3Y;Wy-e4-K z%DJgxUsDvNnv1GPT~)x!*P;IWl~<#mqMBH)b$PesKXT8yZ@cb%ov(6O%-@wSWiI(3 zEc|w)e!P@oLVVs6vNRHbGj{QU3A+Cl2Z3P$i8Ry7n-+{gL^ZI6 z0Up2kV-R!;4nsrWAUa~|57PuZ|A2V*$`;XrgHX(CN!OuN+*w6ElQIlItx?*+C)+CP z)s*sa=_TiiN4_zISSZ!0W`%`__Ewmt+&dH`jGjjAhh4Pk0k9!4$lLab`qTY%jYE*MVqr! z$kHUA#9w1T+Y`M2BNf51g_DL|PSHACApCi}9XdcM_;&PL5ua6mVx1udx3$1z2gL$O&uIsy#!MFC@o27>dg9ZL~i@g*~GP9bL71rrm}rqi8N?Ukdh( z+J`UhxTsd;%{~|^CX#5L5|g~-r%d2VyTl({#5$Nk`ufg7i=@d>qwhR!Gh)>8DuedO zj`@Q4U-J(}BI}EGBdA~JpjHjKSIqLl(JT645kkh|$AJcuyqAJ+j9BOOf`6rO4!(|x zjUXNMV+Ds&Igex`4pK={XQL$a$B7(jWs;1=s;}-R|9XQ9*+Mgt*HlcfqRZBT<0wfW znoj;UOhL0h80Flicr)&CZAuJN`;*cDf(u{MfMoP@e)A>J?pc}({Y$GLf>QtlX9a5z z%^Zx`;C1?^$UY)e_y8LV-O=7kh;KhZDc`GZPm$_j&7xq`4gyP1u>vgOQ2Zxg)gr<= zi+)ikkOITp>G657?fLN|U;Jx`7X0RGbIHYu;!(UzEWT)=4TR?nN*Hm@LUIg4JNom& z=mqUMjHI<*84zxt!^^{81d9is1%K(kfX{E6Bg)7B%uVdyT8^o?y$sb7lBwqbM+mX{ zouxV!588YK9cZ}wbEW}np4(1O%XiKp^pMK)xJA?KUhScwpP*Q3j%M5t7qA zl_CX`%C&V*C;*I>eqD$I$zt&N+>rmW7w@<8=nB^p=b1^dV1QwCl_6^A z&151U9^R`(qQy%zVyRQA#INYLsuO$8tT7_c;CTE>vFDN*$TL8IA5pTy>x0IRuK&Ac zzRz^*2R)PbyEmws@b24av&_3*~c4$Ui1h z^rl~meknRCsuF|CuW z2^TsGSF1#q>KBcy9h=OoRILb>+RGLzWS8CMW9I2+^eVfgUppV22@+ze9f?RJy3VlDtOlx$l{L zwS8m!y5E&R&_IrR0i0CaTBJ`fQAk$E)!3aJ_-5EPl#H}&8`d71*NI;TDF0GUTeK;5 z&H5T|tju!udDP#C?XwJT4!DH9pzI^l5V#qv5TGv%&i7R7=!TW|@amjsR%Q z&lhi+0nPf%+RQ5c#$>pr%}+v4iXK(`l~3(4IAPNG?IZTNAZ^yz(CDk>_m&l-S+iMl zOY<}%u4Sj1LtmOmjCtyHt0dJ#)!3%C2g)il`oImN2_s=hNZHzUCs zZt!mRz|cTN=(bBScSB}WrgbJ!`$@-O>tEM0cTcu{FOlK!LsBgWlyvdd5!zUo*yU`wY^dqz?3T_Gy@F0m4>nKVo5!1EuVpXy z7mZgFVC7TW*n;UW3l_`t3!0CxkNFGX1L)-VLjAg8JZ@gDdT~9kWB>FiSmGNJ(e%X zm-!oFG#nkWE#f@9G`u#}Bk=|^FGVMLCq+Mvk=Edtgpi5L!R=@UzVeq?}Th)#MEcsqbSzB6vGH|dk23ukw|5e^H>y*=VcENmTcc@2ZpyZ~QO|~VK z-yArAQ;DN(M#9oy#y(k}&gQRnd>+2abF6T5F!^FzeYSz&3{FpBuW{@Kysh35fxq1- z%SMPrG#(s5fj?(p?jedIu48)AYIU4gQ}x^nrCDowt{x9{4q^GZy|(Qgzmu&>uO)ji zY_|SxmFYYmzRf-4QMag})VuLGEKPE)z^;%|2Q_*1pWYp2A7(5mER8O^s0^yGRyOH% z*jyaD_PZ|A2va{*b7=NhfL@W`kk#2?>?=1-+RJR_OD6LtTbe~|$|~xssJEpyv>G1( z%?q}4J&gWkTXvmYHm@lJdjteL=-f8;lLAUS=cmKYyYMfSI~E0-W+$Kot3V9S8aE}_pUsU!@_Q{7&w5QYCR&WfOBI@q#RPdjDHPWq6Kg-+h^y67L+EjBNJ60Mn-JXcPAYG5uhi`W} zwK`dBSms=&*Ng4i@*_PQuF5dBciY(8_VVKaKc4Z3x&tq#y2V@7z4XugUZ4|UReN~e zx?2W1_E!dG!m45u2seHIdcWO9oR1w(y~tb2PvoQtRQO%KzgehzEFWSS>$!CQ={!H9 ze-VC-eW*Ft2La8mbE^0Xyt(zI^`Ck)-)kNY_ac0!hhY+u1|UY{M2L%cTkJtE#(_v^7`!wA+h2=eiUhDw>N5?)Ht zQ=cotp#WdE`?I4P(E=DIfUmFRz}Fs|kiPz{n?jA%AnIT^@6gFQ{d$D@-kz_f{y^Or zfvrFiDEaGskUsmjxV|Cli`9>>ACaaSl4i295Y!(u9Kw^dJ zQ(0J2@}pHYb}}`!bGEQ|LGW29`xtPe5&KX0M~{!h!o|gbn}NaI-JRZ@h2GxDoPmjp zi;IDgnSq&^?n8pk+0)L&(1Xs-ne;!D{FfdPQ)go*O9vNAdpqKP^cou3ySng^ko;rl zzn}kjPg4)e|FLA}{GV=pILPo1hk=Qnk>S5}e?WQv8Rb^6^f0y26tT4Zh}nk?er9H7 z-have-#Gtc@!yad|AXXU{9nj_Mp*rzjoIIKqC$OKBp|j`iTws^XGj%)wRe?ggmH} z5{_hC^ygQ4uN&kjeoYmJpQeh#hG{s-t53dXfUfq6ii(c%ttYJbc#7#4Fqkju?;Hzw zUYCNDl!TO-lr$xgLjGMGvsEcadNdvv#RyVl=a1D7@yr&fuel+JmVqkqQ=%}!Rk_Gd z4guJwL~8U1A^p<}dg`-(Pe5$aTnoV`0kwipgJe;vz2$n*3XAsvUd3QZa-M6XRgv-yoPu z+MfqD#3VxVdb3=454~W>rq%L|68Cy58bU{=?U(s&5j^l1W*B-QzcsrrKk#n80_F_> zO^^2T5-$1Sm$#GRD;;D8&gmceRetfZAP}Ljl0;q(Qz!@phCLDi^^szjWR~crciSgf zqbqjL@`Ranh=ihXn|lmj@&2il<&~ZgWvBNPP^yAXMCjsofv8pswP2h_0TdCivYO9p zhyBE7!_^U_)z1!3tcQ9%L~z-G^3F~COv^X8Q@?<@B+-ejC=X96c=9aJ+2kECRr_qb zjXj#Vv67+h$6{1Z|FSbx%XIEsGro#`i#WxT24lAZSag`9%O%)7j;JyNHvx7F4zzE>j}SRAf_d*POh~%*xEa% z-#lKy*Gu<(Y>zpVkNIu{hOxdGvwBQ^5j&*Kh(HLlv(uBbCI0g-A{2*b6>Bxy|7OgK)Y%Q zYK191VYGwco1wsgZ_Mkp@1`N%u1!AW7y#KVVfN*Q^%*c@3Y%z}?TLTa6br=sZJAmj z$4``1ooPE^aN_y{cIk4ct_+PN&D`O+XQR`g!(04e*gFNyMiBVkOJ$_w^1CmJX1I77 zIJ$O7J!*H+RBiNTTm(l~dFbEV@_RdpW)Z3jTvxtOWVzcr8aQP7C2&28N#U!Upx5R;Sz7|}it%Ys1@@`ep zIuUI94ZYHO1&s8XlZ=@FG$sdSAVd+3_RLT8Su|J{jnkZFNl|@3o>Z>g7IF!8RjlNxxnNO6-kf- z=?S7~jwb(1@zQ^C#Fp-?)pneZKlE40wjQ8=8P;O((Yw70jrBJMbC58IlRBHb5j+ES z2%0m0X#PVeGhZ2cQRD7f#;k5!ijdZ42xd9=4+UL0yy3+3#HQY~Osj>pH(+h}r*1BD z8pcv;%772J9=h?zU^4RF^FU$mSh8hb!Y=F;CVHiq<#c>EKNeo>)=4{@S}Cw^ zUF=YUm=x&W3T#A&QP!`R~`83+DmOqD(f zCKg}$erzFH)w^R`;6xku6~-Ap3-thw=K#N|6a%}{Rwd>$bH4$79{MkQ!?4T(j3%g{ zmgw<1K{4#VWG)}J&*PjM^VJNln-#e*N8y6ng5}_xf(e{r)D{qsw%Dmhpl}QsQ)b|Q z#?%T=%|>ed|1bHkNXpu&`2^t_?w)?mG-WhqD2lS&`;0)`uh;Z>pEd}`n@U#(ok0QO zE2eE%Km(H5d65f~Y^NZk$Vx@C!T$o}S7A9M_xhy-vJtp4^UIg zsdXuHL##JYpHU5K)_ytuJ0PlAuU8X>hsZdo9dOIr{cqSuiJUG8l4aw@E5Elc`&IZ1xSO2pjZ$k3GNyVtq)tg=< zn0YPPmt6|Tly#AG4hXKu>)W-UUBrP$_4NK)kmocipD54LFn3iORyicu65}7)Z}|Y+ zM}dKNSp8?hCxI7=s8_Ds4&9Ac=TGeYJCC(oscm@g1{E7byyv@_XY3Q){EzIBdro-JwV*kVprN zX9M=!uxY%BBLrtq04z8>Y4}IM_bZ*YPd@E#XIn0KyE1+;$-`^~q=3}JsJU61k-0t5 zJR<)P^T5~;EAVtxUQt8DeRB>qlMS@f6LAZeyywfs^g9veMJToA4t}JPwyoG;|Ib4X ztU%xG2O_P`OS0Nnm;w4iPm8X19*G`=nG1hwK;9}sygkSvnoML-|Ddn!s>82no~^V{ z&i1mpWK3N6tIPI7%lvZPjLJT47j)@U1qeAVWKXX`qzA*0!aeAIpj%M24V*U}pa*Db z^gS-07dQ-V0%;RUbps#$K}o5N?}fc$8pj^~RbV$9687BSI8}S-(x6i!H|-l+GMad( z;LAWmhx`!~qESOhRHV#R>F*rv0@xTXbisLwlowg>BgMq6TIohj3G^91&?YDMYepNjQ3j+67_Qeo}>BZG(OAuOD^u}_(q!>3oVX1 z$|`;K1oGkh%~>~z@yvD6RC>FhsH-D*{`ndS$Y0EiNK@i5;4--dB12^<+c4eamL+_+qINv^U!|(+2qY)n?g6IxrtsdbuUD`E97-owi<^ZuPN=P;?ICbh z^3}NS^xpvMn%0ILl3F)AHh`K2LZtZe7-;qCBJ(}(SupFbb9J~|`%fZka6uEcxOEEE zGc~0gEPi7$GiSd=Gxi|i7S zy&h?AsT?eqo?IE!q0G(_P$$l`e{%qV#*u5i1QU4Nu4R7F}D zp(ij_VoZq9>EMjkHowxfWNiZ#yir^CRMbWN+#pPQ*seM6=Nmk%F@ro(-LOH8v6;>G z3Am4G&At?g$Yi4!y^v4bs@Z7f@dXZN0jh0flq!wgahmgK1d^c{{uYTA7z0ut_5rRG z)e<9>xEwzF>lcuHd;-iR_8 z-~SFuF5Xw0_e#A1x^S7WR&0juvfm5Tq?c}UyWlytAlz!zQ>PF;`IbK}Bv;=)h!rb; z;~0Hoc!~&(n-)Q#A{=5(`|b4!c3UDFX`#X*I_A3gcf5{nC_5P@+ct?7qv@~jP8M#h zV~W?uDyv{wgPaU!N%-6ASr3aDNMQ2!_)+y|3#yJA7WiM*m1B99wSS7$jdrndoZB3y z^Ve75JETYuWNX4AZ+or|^VguXTfHg$Yi7ifwTx2x$ODZqp@Kv*TyE&1?T~3W-i&BJ zhcmNt|ETx zpx@Yk{>enRmek89;*-sLfDRlFmU!A52kfwhYp=BH8y(xtg_nfjX}tT*r7wK;YCW-+ z_o;xws)bOi=f-4b)R8@@>*J5Wa350dh2pJznBe(KG}$qr3D-AK$00La!m!DdNZ;x0 zzQ)axQ|v*<%RxaQZ;2E16d5t=f>slp<3CvM@&nrA@`CE_UGe>7rhL4z*A0hS2)6PW zK?7pH%2*ieoWqi_5DL&>TPoa05&3PG1Nhuva0N#un4RA8{ui%yAGnd;iF?v}U4ew3BQH)zBro7{Z^HeWOJ<3)PTlz%j=sX3>2K@v`%^-E zCX=ITtK50~slS?0hQ(-%Y?ztz7e>Re)L>lkbs2}nW&Q{|BjYdz6yOm*rInh2ZuYU* zt%O)8>-T0!r>cH~m-^fCjYm{%PDR(~>$oJ$VTc(eGuV7LitodE?H1D0vYt0(iSxd= z6^(h{OD5WsWWKeO5U-)l>lvzCdJwyQIksWld68W_U{|z)!D%O{zKx3w=dPRX8k0?| zLlBSTb_2f=!4EaRE0q5ZMjrps(Ahk;Q2BQ}XTIXCF2M=_n>_6rs`XTrFNsk9-UX)u zGS*K0>&^|W-STEQ|CR9iL2wVIyzR#(Xzc!2<{59(qkx1^SFhvfZjYtKdZOI;Y5Kjo z4BVBV*9)bQz}+lxMpV0ZW>$u{YE4;knNcFSc-ZWbGsspRiYE!l<+{z=mjSA?YEG_} z-@{cMh%OAs#B~p`;_>OFT4r05eZ za0c|Mq5}@a8Dx*~+Y13iGHy?1TIFc2fD4b5^}QGyrlnF`J0{J+vezR^CD%5(Eeglz z@?9|{LAgy1`D^}}hwT%bPp;0^H~eWeifkJ+%VkfY;+DSQv%TXc@jqwuY9{BuH&A0_ zwPaE~O%&V3oo98nKVQ_?%P!kz(5)lV5waZ5r_T_bJLkLI!9vur_XBjz{2asQF`7PB|~Pv z5@tOKnjvaiSle8N&wUsPh)t$)d>)*6@->51;Q<$Fhs*l8^T5-3h+y6RIIX$k%g&w#7sDu?Sv{c{AgaHadfv_(n)kykfIFdMWR*hrS817d&%X zNYfB8<`+kvHkSzI(g*}o^^b(*a^xvLP0q)zJ1!B8z9n~^Fes&ec89eW#y6{bK z^MTlguhe8tUf~+T@Jc$$wmE43GpN2H8T7a7q}BOYXDt}z?>snqE(O2wkFD~=$Q<@V zp1&iTf&s~IpHjFsVnKdaS<6shkM(hOWAtPHFfAwMT;K_(%MeND&x#38J-jRhc9TBg z=~9Iicxw=WQjW#P^z!|-P>Oqr0a)<8V=Zgnz`ROq6^?l=U)0e0) zj}{Y>#N_>~{5L#He3e-V~TU19ge|khUA|0LSxj? z@5+ce1`0D*4uTi%>X!G;HruTdvqfOh|G?Y95_i4PbFD6J|FOO2(S0JM@+&!=4Z4B% zq!IGDOY2z*z2rW-U;@paEXB6qxV<`f*?v1~>#oYya_WuEDJBfz&o{auMjR)qUeYKe zHs<;i_+ATOf%DQjL{$=oZZbUzMBaj;-c|FgH-ZO z_f_B;n6YtgH2nIYZ8qPt&6jw&(L}M!Pb(8s$aLOqG3&#f9r)H3MlW4bi6g%smH(K2 z?<$G^C0(fJk>qMFx?`?qn?JGIaF2(lqI-_RH#+7f{zu~3aRaKuQJ*xr^TKfO!Bj82w@f+Y5m;%Vt! zs>#^xyDuP_0FVQ?GEa-0qC0Qu1gysnQF6H+=HGV?q!f~>#m+u<-q2kWmqe5q9Y)g5 zaIVqx$ds1`OJ8Nn=Cs+GuZ4H7ZeG_aan)Od0IYn9f!lt-f4G2VJ~Yo8tmE)%1H8?e2gqTDJ)THPU!-(f`ON0K>_yJr zL&M4!D}TY4msV+bvj@^9m2|fkIZmcu`=P(&bcMj8b)&B|U`-JFn;3rW?u`4Q!JVz= zv9fo+0-da&KI^~f{DNnTsK&9qTFV1s328MwhO&1}>n$Li4U;^on-HIO{*opWE8qcd z8txT`Gh_Y>*HSOvdr3cqcW%LTy*TU*?uu%NR9OIBze~9kzhtEV!nQ-Tj$Is^lBF?R zq;mHw#L-PPQ2{Tpc?m?)F#F1p2i$|USfRe=^puRJ`|KRQ_kaxW+;x3kuS;}SHC!*x z;&4K*WVRNR6p6Lo&hKha;60aj{saP1RLe<^@X!z>!4t9{w(#-=KN6jeK!MJC;)pFRF?5jo0M9TvM!^^sctUshwk$Q|x?4`zZTH#7`=oXE zy@B+Q^M}XZ>(eX&uUmY)^9K?srju)Y4XUX}^LTRliP^5dLm6%W0#)Sgx#tTxtzPE9 z*Rzte+=Dl(rYRHZKzVH1_-7o6B)tR0wQ1*=YzYP~H%qO_V9DDqNuh5jH~G?%h(V&| z7C-(z^Z(2!$N`al&0MX14fDI2V82L}_IG=kayeYuL^`34s!7%AGUQmyz+9rhj4i?< z7xh}d__XI96%jxIab}<;UOs>hJIRy-b2tyqv zV-nnsn|t@^`za;Dnvjj{pK@|iXgP#Jbq1ti!})BeIbeW)IxO#QOzDUvSs5J4tmG&&XLRB>xbXH?f^kgxKJ3|`QIt9o`#kgcWf z;z!7asQAVJP%i=EQ_4PW6|#ymUS=ZYOwQFxo#9G)q*~S1%cL!Uy`QO>>F>?LaYa}9 zb$Oy1wX4WUFX^Ho-QP3S6@2+Ns;K^cOv-+` z5{JU1tpktbclX(^Pqp1CKOp)JTg9xQ7dK#962CcN5jdipc}qf}>cm(#vj4w|TC|$t zB_a^E({D9$T%>JgJnk90orc4F10BoL=vw2P`vX_TTrjmi_0NAe^9@1oE!LfJj4V%$ zCH3#=0IFW6lY6Y%HXWB%m113LGVTa(g@YBWK(Exo*_6iqUheCb#6&_-XQKLM4|vlS zVAc#H8K5U`eU-HqvL;E0lX@T}z-_4xpY9UrMOj0;{np9`cwVezRTdkAOLrdBZEXEB zmx?_5r#5E&~#LnqDu;s2r39e1RS7nMSnQ7CaI!!uf&F^hXv>wOjIzVg`PW>a& zu~j~L?R?t`$X&{c?ueF8>{tr;DLfCo5%<8vtSyJ>sdR4Jlv z7jE0F3q6C?l4;jv1^wSw8rw8P+7jrAq=03@7Urzxm?Gte$s<1c8rNxH@I2xDcDsl7 zS{~n(Q0`z8mA{`*te*R8m~`i*8QL55mJIJ*t!d{Jmgw7`2-G4jxi9f7@;FJ$$erBa zyNXq2cM6(4UcuAv%HCVPzvu{C@^a`Kd);#C?(c;u%2EzxPJYZgAKbdu)RZ&kX8iBz z(MHqv1z4otVPM@Jw5XUQ*(LhBaJ;I?lWA?>(q~VT5456KF&7o1-#=2dQXvlt<|mtP z()Dv&{PJcqtY)x?QZ|ik8fa+<8F;VjRwn%82HSVd_{*NL&f5#In#GeYID1>xCg;VU zAxG-?E`Y7X$zvo)<4JyFLW;eZL-pP?QXq)~N#Lnf)v_*|u!wUxoOnppF9Z^&y_-$a z%7$$0UrDV={H$IGUll6@V!wHqDmLC5|I$>3RBfSVi0NK3=9)r>@ExVTDWc<=^k+|3 z%=qVcuA^&E%?5wg`~AA|7K}*41l}(lZhC2Q_aepTBQIE@5%gDkUdyIBQEF>5G|`}^ zP$7$*g7tS?2yd-U)K~|wa*N4?B)i4l*IUH#%CM>@-EKPFmO%9?E!EnwJZ=vWRAv2xVC zK1XqEg~BtYra(Q7iaap;dIjeH_h&HFw{u>0-u%*>4uNL@H^6(mC9$(olKNq2tJ&z|E&Rx;^TN23#5;`H+N zyr!C3Xy`B%f^bJDg?Ug#R@tyMXU$VZ?mDfD0{x9z&G_0lyGoF)2AD`ajJItCDMj}k z=0u}mBTdKVoDDQZt>&IIH)m^i-J+f*XtJIl$jQ-FwY&-oW9Ia^8h%GvZpTFY;Tp9+pThY{_M%g~h-)pz|{>oi8=r}r& zP;fAhdB;N*6y~2Fpn9)pVQ;9z8}(R~WRRpyVG-=d5)D6x7aV_Ic4j`ey9B2F@=6T@#CS^I&3i&=nq%%aC3gR)Yi5Jt))dEHnGph%)qMo<(nUG%Y35+q?yCV@Z4%;`bQzZFHY1 z9^MYD(Bbr56-Jt3<_WeYKlSOY_E&sa#l_>W#%z*|d?ECGWDw=pQB?6eTWFW7jbp#j zx{^=5*Xu_JlV3VfWrNRiUtS(Lg=y+h%qkv6bS=UyMLEhkIZIXGjp4JaO`V85RS)8P zx~qJ)nydK2|CCqX&TZgb6~>E49cPJdOty${b3#jS?nPl>$$Y~?J)Y-}ppZI9m@~Qo+)`B;* z!C1~e*u}>+H6*q-88fzSs^~h&0;c>^PR`Ts%Q2Hh)5dpYUzVFK2zO9}tprVC_lx6Cv+aE=zlw>-VNzwRdTycwA>N)k)d^dty zyXrvOy)QL!PE>mMh4M47;C)#hi7I}~gu$ctc^2p|dIIjcmadkFBSKy;@D=lja;e0{ z!P$tU+IqdeltAEtCZ73`)ZVMzhvu01`FNE}<3<|kxOumG>ySrD-cRpume%u5+B#~~ zYQL#kvT5q~(}O2CF6I2)4x3$G-G>WK@IhCi;&RDUUQ)Bw$fMin%Dy=iq1c|> zAiKW`ODA=FeRR39C~bqMROpytqFLXBPOH1EjUpX=&3liPXo;qk@QrxC+(ZA+bgTjAs0^6R$s0R@R?q)uoM-> zOUM0RhxM>S@AsbUaC-u`sL{0G@qs1J^NfwM=rm6w19StJzk_Pf`rj1@RgKrQ8b(fK zHjmN2=x6%@77i^FAcw5k8rYK3x84qs0cPCZvOXXk#h3Hch>-Plh_WUars*&XFV`yg zxJRP#*lAOgOZn{L2>a(qVqLvZ1_$BHqYY?KQ6>hO0pSCQuo=iP3-5>S~!&QJALKMh>!o_($EIlVDZA zhB0eLGdQ`~_b!v|rQ%kLluj{(ab~~$>^2kL>iJi3GK<+~34rkSW_ow;zzZ)?8o*e! z#lKo0m{L|5JX*A6c_4&_BWW*SZOd&*XYXoLv?VVU7cJY9%sqQY_4wd-%H5?JN5)vl zjlkEK*sTfrgmU$(%Q0#5txss`y?~-hqK4NEr{d5|l_v4P`_(-06VWbcUX}#_W?ZTe zh{r!ext{QlQ0deM(G+m|?weLpgR#3Sx$iL}pn{w=EA<U7QJf!HGwei8dWqPLH} z)cO&H^0x zE34hOWP_duRasTD=JUSCh6W4T*oAPmlso$UlvfG zakbk_2hSysrbv%>jiD`3vlmoO#N`I{lvrilo5X!JWOjll;PK!Bj6FNSn;e!3_1ExM z{&D1itx>FCwy0g~0z*@z70u(Igl|KWJ@GC zoaW>4EVlLQq@YD9oHk#AIw0yEc>RW@1j#J_N!MqaQ=$P{S?=~J(tkbG?73ButsJGh zD;6#=_y{H1&!3if%769faD+itV2WQyw^*(+4-4oN

32q*S)6h@p%YJ7{(io`KKGJ2%W&B51ru-f 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�e$SiN z#|A^l`Jd$9qk$T+3iv82skHL)vDe*?+usc^h%hM7y~!7bm@K)oB)Uh#6rF zJOcF`OYteQU*CDro1A1)yUe~QT5s~ExhU|B^(&i8L^$IyNDhjD3g|G0`e`cNKESAj{r$WX z$3Yxb+cG4Xt^IAe45K*SLXr36Z~1nbgTR&IY~6a5F6Q07N+6P4X_A}7JekH)4PnDH zsZYwt7@JPJetd01Pb;72UtS{aJ*lr7od~GOI+v49MK{X*@F>9;?CLGVTKG%{VAKe9 zX1&P<4l6OjexPV6D+KbwIcwcX>CcYF*uAM0zPCX16+QxSzz$1xODbSazN4hoiHBE>Qy??ftbs^{&4T7g zfR35RB+cn46`d*LDefGGoSK}jCWFuaKMFqx`wDKm@<735cPEPx{@}M$$SE5 zt3p0KHllTDujPBC3)I6@ZKF{!3%>H%=wJdq++t^mG#T6zOD$6C0XMzxdcpc+_8#Mj zQDt1o+FH$GaAg$2QVJQA@(it4_&b$nG(n5Uit`4vF)FnK%=7ev2|>}&^H*r~8hylx z7FLkL^rx?ZQJmNZ?KccD#!>_>Bx$$T)0fw-#kzB8<6~(7IZf^aE@HvwO{8`Na z`o0NJJ^$n<`mI!l@%EgVGC-fLh+!*#ZU51bwjFaQtwl~mZ{Pm!!MB_)UBB5EB`kUn zn)Ujf6#vLp43D#@^4<`>=+D4GJ4d8q5j~=L?XiiHe@H65IUMAAL3>sno}I`J>emR7 z>Y3MX|7>BSY1|UzR)3YCiK+dQJ1Gk9*@_7=?y7XJe^RNPdE4~Z6%05dGA;bHeY8B|m zE%b)Sms-GP*2V6g!1-RATLhRRa?dtC;Sr} z22X1s+|I$Cvy0rINi>U1rR$y+au$#uYf0hZ?!! z^cH!y((FZRrg&BF`@M`Kc?!Z9oBGGo{cZOTnWCjKqMHC}Cs)wzfL=XIjT@A$^(^Q8 z&v^ApJen>!GQ^xpcCxmD#QqzRZhEhOs*llqMXsv}>KYZAJ5woeH{^PE8JF`Z8{YEW z-(~Bf%rWfyj1lY~%34&N*7q4?LmA+SgL2J-pbo-nx&(J(2)j5YiqN@sFTrWON(3Ai z;h3*WrPc+TGS$wIl`-VcT2F~Z$u`#nsrCdw(nOA3>OhAIG!tnN+5qJFe~y+*pE^l9U2H9ywVP=bCZslT?e83T;ipZw?B3IW&WsewKK>&vlIL z3>v>?dfPPzAd!L^$0?Fg-=|0ttz%w#8;@qcb;5X~<9Po{mDq4-i2Q2vLOWh;r=hs# zzBlQ;09%o`mBpj;Y*%dVBN|%2l~K_F$%BlSLp1$gGL0jf(W+j40jyp{QOf}w!VVh& z$gi?GFL_ONmDl*oZP|*;PPvM&dpG_-(Vn{WAZnBIn8iK)xW|;(YUY#6&YOr^X&9bO zBa#0)nm?0yOUc!Z;;vEWw}%k0=w_9XFIF?N2Ng?abW_znPJ*Y>QwmaS-=KcSAvsr; zNic-yej}M~n7Fo4cSN{{>->4`KeMVm+3Ot8m_>5pQ{VNNaZSMXk6)r~-ZuERj%lsY zr(yuqFwu(IIu!Q(&M3|!0CO$&#gMu_csb^VE8BT}npm3bv-76t!99w>n+!NT7Y}y9 zV#rYx;;Z(G6K&Mx!n$F2fP%H__fz6I+JJrl_Nb>PRFIV2k!!ahAlPcC>5eM26 zV?A&mQZ8C6GB;D%lRIg6pTCEgh3RM(IyBb&D~G$B&OYH(6?^4st+MNmZSEgM@b81Q zC>N$R0aI1BD&apT@9GaD?`r{_5fYlXA25(f+AK!wlX37y6sWBHiTbdIf7O`89WeHY z_1fZH%ODj-AZv1JzAMgr0~`lw2ifd?{C`v_|BGWP7QA!X6)h~l=-43uQhbzb$jSWv zQ{~;6eC*5aU#??PlC>gTjX#qY`(Ovv9kgnWNloEeLt2|NxDnfx*K?)m24TOB)iN3_ zM?0&Ly2JA6JT6>$^tI$NSwyS<7@xTR8>Vz4ouCF5r6%$#t9YFK^{>nQ%GbCnL+~|6 zcFWc?3+!uCv+vF#kI1dln-H%K_*1QJNXkw$|9h^w&Li$q1Fu1%rlgRW0)nHvLK_1L8!$jnZJj`L{8;5db9@)u|5iwFk2%M+;1gMbhHxU~ow z=i5Tx1SP4%bJ#@P%gTxqBGJ_LXy|=*eiN%mW^pkE*OD_|5wD$H4mzRe(0#4yRk{2R z_wzIDcaOI2ERNI3$VZEca=`k$cEQ}K2%g=l%{SRMAxU26K6NPv7;XFA;XiIEa*6%y zf8EkgZ;s|WylHOpNdD}=2P25K$;PDxRSPX@8ya$Ljcil`s}=yhM5a}79{%`zkQn(k z{i+{aG@m&Z&ba{N{-KEK)3pN?rR@b+ss6S`?8TG3JH0kh&D?BM_$k$U>_(qE-KbeV zEE~Tw$iAAgN+vB#o(a zB6Dw+G4B=JH*y{v{3k6|p7m#Dwx&tTNrdbw=Uj^ZpT!`-n{P5udb;=B8SPA7gK1}K z;a5gG-_~|7^s^s>4#{$7DU~Ze!9LRV3BedE!IoI8RYvPbDLa@8 z;E8NOjiKZCeAFn3;Xi9&{x{M~9p~W`5g!*g0n+44a@h=stiS2fAThk7J4+mq4ZloS z#BW^YIo(LH^`F3gMLi zf(k3DwHCJL>9z~6Sjs{zq|7GiDwKF=65@M=>|t%+7WxCJjK$i6?SJ0F4wb7i+AX7X zmw7Kg&UYxic}Aq*XxI`6hZMa8(!>m0M2rcN$Sw+w0QjXiu4_0YcW%p@gKUPPu1`D~ zE%~HN)`~={!zl|Qz%uMxX*ir2CZUb1`MGP6n7GTAp$@#?2gSaM7Z4S;dYXy|A^ImV zz58X2vJwSdNMCsN>gM44IYHU(7bzOVVm_x+|NZAwSP!BGOoHBDkx<}Ge(ODSbFTVr zz(X+-B{6tQ5q?d`I{Z5)_my^Wx%Tm){^nzD85IK;OGtxs~Wl~&*iV+n$HBg66uhn1ad9L3oSk``j!y?5AnzdDK^?cz`t{F*l zr^-(=4$L-jPh)6i!b7gD;(p0$MgP6%j0)b43VM4rB|e{6yf-PZ5%S=ox6jDbK#Tr4 zJTe^7*Ov-zgvhh+h*}oh!KWq>mJghCZSn@-C zR7P`xdERi$jWPHIEVDt#q@QQQfCRXq%Ji!%pYBW{e&=mxkz97F-9Oj$y`If8f%>37 zbnPtlMt(HX<-9aUwfY~>wu<>BZ0m1R@lc|>gu2$gT?F_w?WK|q zt_-BE#}X;WJ^R>G4*$2AgaaJJ-0vgsEg7|7`YQiWJ<{b&toY{l-}!eSe7{NUW$rOq znosQ;G4gc!z5I&1;amOByWR%DJhC=M0k!)@e`e0;@_BChBJFvhGBp)P2TbStMGo5u zU)-w>5-K8(rb~L0gsZ1GjVztbpG581UHM9_dEMp;8IS?|j;BoD_}W&pP&*u}>>+`P zD4+6*n7TA{*0aH+!*XQu3@&9M3#c8z5sDm|JI_nsVjs7XtLb6OzKW@azuP`e7Yl+o zJFEowUso!2X_LrV8}aB3J?t^uR6LziMsMTH4vIwG@QHQ@ncOcYtA@RQxTE z2NKh)++H*%dz9$vyF&zIT9CBv09wp};^E7%-0jib<$BJHmUs1%N01Yj%>yK&GD;yf zP1_@(>JNJ;t^#IpEBv;l_sZ_>aC28X2T^<49EEDhH?^wr*M(ZW$;o+(JT#qx_>qUB zTfmSG-FTHLlI$2A0EqBq$x9LI-yS~~E7>D|#IabvEgTPLu&p>=o?wKA3hU5rqze>W zeCX-2O`o4?WOb(c(Z8z@^UI}0WPkd~UuTpfxCi3Jc)@8E}8wVlJZZyhV(k) zPV(1~rL+Dje>tsv&3o0L{X;o5kp_`<-})*CTV9DaJ*HSM#!(LuCVm>YG6~9gJt|Kg zQQ7Gw1P#WG1z4jb5kn-j<$nut;>*hFv`yY|F>x1)TC-o?e(nCSc;6 zjH)(TDPr~hI=zXWnab@^t?7dfCgXdv8PO0X>f-F}y7m;a5W|+UGjc&!@5&?SqsJn; zbzg0DWZDY3|Cl53J@n+M!?-HC{-t7XkF4PEUZEATjE90F|J;$u|Aj*^Lh;2w(v>#X z7(n~1eJ|qvT>S$ayIQWr5$ndV`(t(o`1<$5RPauE=h+}_xq4GQ9_2$-f@K1ClQTb~ ze(hEWk4JPSWxyl@iQN#&hsJaVTwY0-ROKX9ys5gcm1zR#g^JQf>n9gmgb28wn`Mcj z^+`^8x&nhw1L$7%(p{iZ^OI$<3m-qkF^yw>gwDW834*7Eyy_hDeyGDnkH zI%`GLphk>rTS+`&_E6e-$*1q6_}Q8$SGvDN?=*OeS9n&FYym$j@;^Vh9Jf#tX*oD2w#wyFi0XXj>5P{MZY5M>PRC+$j{SSd{z!FMDm_th~UF-?%o z5WBe(2T<9147%^ zYuZ@6_L*=~8lfLG=7{J{os(5QP9$4N9GsJe>L$i-nV;{&I|trqaXS$%bf+002dqvu zq^p}c7nkf+cb=)mInCNe#!YI1?hfPS$V{~G_LF)zG@ zctOVFEH)4c(>|I63(YBe_-tO6{osj6q=(qa+2FH{hGb&)ZxJ+}&LROmGGb1RrGic58paR&BrCuKUp4 zb-P*~&bgOdi*VxKlN{=yUKiE>^$q*SDjP+(^5^FXni{f*h2gZVQP8-{cflFzCOp;K~kY3#Zt9{=F`q{>ZEm!VA&M095U7sLhYT^R?F z2Q{7JAxG-vM31_L2rbFrtMZ98ZatD^{;F;cy=rn=<{ePED{*-DI&+~`1R$!QXM;2Q z{yuYRbN$Rud1Wj;$O9vhPbEu-3-RdW|W(J{`LD$l`+_}G>0c=85|8*KJVuQ za*!)}6Fau6(|Rk#>^~rb9{sb#6S%MH7*exW3SGsDe!A5N?A<?af6{B9;xB2gYLa}2fp<@LD%>6d|JC4UkS7UB zU;d}FwbLfwYS=`>r2OK(88y%LPfBCh#1VS2+Y;etL%e*2wqlQ~yAg$nv!^V3Oq0xg zu*??=*bf*oHLPQ_|b&H#X%IGDHVGf*EmMSU8Whyw_R(8XL{;Rk!q!8 zA+Fg*nu$YGpOchRgwb~+;oDC}{B-=rrX4=SZuNzC8_B$F->F=SDK-O3a9(aJWMfhUjhf8GK5T*b3ZHGik=8D@ zd5^|q=oML$J7Q{~7}G##KkLbu950r$aWb4s8JmpBUR?#g%v}c23D~y1d6cVuQC+zd z@-=-;gul|MDmS6(&jUa>ivsLX*Y$G)HMx&I68T)$Mzvy7L>P6uk=A1Xp1%(S`;|X< zy#P$+$6QvesSE|&tQ7(c6l(>t=cQSOV)JH2NRRki0n~ig2&WvQ0e)vLVpfx|+0;Bt zMBueBHOf!GIuaaChq1$AXT`UbuW@8dTre5o9D!g=q5HT`zIc+78}2OhSp%(e$b03i z)tTz3Kq{T6Jb*KEa^<(W6}!aFm+LY?E$wxvb#P9nFjYj%LGz#_ET$N!+hmS7Jw;LV zc-AOoPdAkXTtjiyHCF2wkf?uK!?HlKz%|(>B!7E!1sHI-))6~I%>Rqo?tg5w5a0BZ zGeMrV3q9ES>et;W9bB_zeD~cEk$i5dbCTlDFP-*mdN5&sEE6*~@xt1F0znho@SBET zAHM_mhCmoLv8Q_gt3K_i3F))UPYEZq)i(gKX>(2j-|7T^09nC z)v!x5Pm0Vs&x_y{(6Yv@ps4*-;wl5?`#w6{QL!K=;x*&8*85k-f2 zngIIYsp;)^A3Jgi^QeB?^^t3y5}=p&c)wIFgEdwnz_oGgyi4?%L8XzC>0 zJj;rvS70}S18rI4Hx;IIG!!(?Ll>ICWJ|1a#ajn#t{PW+XW9V3qsyK?dG^`eL`;&d zu(z%JZkQs`DJ?P>Bb%yc9wqu-6*#3o(l}6cW@0GfRP28R9Q&uD|Ae#b0kg*4Ds4H3 zhus`lp|J-bX6TCTBNMR5DFtH3$k)9H=Z5yF>%(!4c?qSbY%*^qUCo8x@c zBBR$Na^R{@SI8KXK{dE=n2!?sVEL&&H?7waHXcg`B~#;v5ROky;% z0VM-ech}=DN*=Xu4zNFUiVyT8ODvdp995sn0MM+d<WO-EDXBH6Zdnqn*U-<4_?U?)@w5Hc=a{XVz*HAl0Y_D}2X zoYOqy*u(hBmz0mF9wK99*PH$=?~h#Zl*xnDXNh9I3;M+&Q(rP$B4RrUq;Fj7yd)xd zDLg%t$V??pCt2J3=Q2JKkP~#_i`t?DNt26jXqjczt=R%&-|=|vmY`loKypuJ{H`6P zpHJ5w>Y`dNz5-XdQ-DVY(@G(0^G>(5orYP!jX(;iNe`E4XVoj+hx#Ojyj;03H9nzi z=h!()4^#)?cxScb&YZCbWy3>R5`;KYPi{QV0d+Tqw;O^4ANG{&-;`+(J$QQfgt=}X zmuYRJdX+5F=66D;9HDhaTOU2fK^`HNgO>xkKZqdwJQrr}HG@BcG|F;!?!Ftxa1d4r-y^eXBApOmdm}3KBWbY?H5Y538-1u9GMYHc;^4 zrf^&YS(Tf@b7xgBZvnP1=TcSI)n(uEP9<{qMY5Bj6vZ#Ql`I>^R?<7c6!eY8U_$pJ zvwEf|Ak^aRr)#T;yfnjh23jgT7j>KIoL&in1*5>%6#tn}*TYqLW9ihuT;(*fUzA3^ z_yA+l7X?2IH1DAW2?d?cST+^EW#uk%0k0o-MOoTR+6YQ46C0?Kdcp6Hmcm%1zBMOh z(ckFta$NPrxlFxA`TSlawI_-I(oXJ zEN2Hy~DYd6-kMfM_Z}%RpW($PFsvFgc-ZqjjQx5Q|NsZi_uJ@yWc1o zs8uW-LjV{9Zl*htihyZLK+M*DeJ|XCsWaFqF=yeeQ1#$LEh<5Bp8~h6jCxXPwA~(pN}?r55ed2{Jw|2vh{z?(_ae zgaV{IHnqt-$H*a?jHQ=s)Hb45TrKC##{VcFw-wvy>I@t%GI4x56c_Ko{U$YB3809m|_`|I)tibio5-BiVHeH-?NYq*t@ zqR@d{1>{+}c_3I}v8yDvs|SDW*-Y%-MD*HDV+l){T75!Mb-Fq{&PmEMk9yE&qh9#z zaw)xT>1rLdTyji>MvOTdZxvK{6>S!PV`NfFs~Im`jAfv4PA@YD|F@?KET!(4o_uZE zbMY~>l8Vp&j^l}QGGhbw$mg-xq|Z>7x#1C;f4ehmsK_-oqxd3sjdVp$bED^U0VEimq^F;8U1q@2KTL@RG7 zAHPD0AP!mi(3CqT5nH_4M$*I_;d>dR|_Yv&3k_0iFAQivLHRn{m==p6XXu31wWG7n7$+fYDHs z0L~|GT^W72(l;$S!Wx2dS3c?usJk3o&n^vAm)&TlV`g5Yi{9RVeW3%I=?qaDu~%(m znybRZF)MmdQ!R{Y6ffH^vvX5<^WioOq&yB7KY*1F|@B?iMkF}tGZIh*ui z0uGZSb+f_Yg>Dywl;_CTB4lv~2mR9)CcTEojbjD`%4;No62d?Vc zMUk{e5$kRTHv!W~a?HyHgCHsD5w+*2Wga)mY@Scga^jrGjHV@we;s5C&coZo*IG(F z!}(0T#^bVRI{S4f?*j5zMi0)(v3#Tk(*l*TBi0$r8q< z=t%@$T*gvI^7cT^_=H)f&h*gKgw8Ru&sGXvl{$1hq2Mao1KRr9)EpjQlVYk zmbt6}grTmZk;3c$%BgZYf{qH|<4r?%{1OF{E%!8z2z(A!xA@9qHzI&z5qz4DuMspB z{OeBBqlJqKNh78!*`O@xL))FPnxxmgx!+P9pLTT`s}OUXb7zW}M^3TA?pIOl+pg}5 zAC7lza@LkzKbT~*9cN`8Jzo1y+vY=4UD>A(i`^?X`Zuh>O^lH^Bc*sDQ@TfFpT6t4 z$KKyV+6){}I_$vwEf3T^gt06T{rShY3Tcr1V91U`vcZDw(Q#0)E_~6l=KT-a=GC|> zjcpE#I7TU&nTD^5xS~J4N|ExJzL=(j<+2jI8ZJ-Idn`d#*MB1`c$h!D)Oi5S#a#HA zWI-~fSiZH^zPAQ!IPNnsc*lhu=eQJMy7eHi(&jf*71MDaewMP!n_{7@xV9T(c& zH`g^>`(CXO?z~HLN!28_yzc)@Q&KU{m0o9m+_zHe!^;SEOiwiI*TPT&_t+h-E$&d@ z%WU3E)-{V5D96z&wEtA(-HI@JG9A&F)1vw0<7t#|DeLlL$pdgg11?Km@5jrs&L1$zZWI9*~^3WRj zK|R4pGfLhbXO=X~q4y4M3T&YCvxEF1E1l)R%Bn`=iBLwxF_F7S9%9g4KA{B^?JnRa zLFLOOdzb?v1a@!|too;VpEQslwIz9HR{JkV^YV?+@g70$-1y78s!bEAQcqm!kW#ml zQ?1yiSqYcUVnW9e&9<^)NKt?}-J3?~d5jg+(sNSr+stK&0>%Yk1v*Ql#~9fReX*D@ zcydycb12lXmJF*7Mbly;d7Prr{tQcZ_uM zM6Gn&cP-I7`aY3(>vSouGQ%y*MC%3D<#wqLs+plOP`{TsgdoVl>*JKF9;#&HmRK40 z4gz(HoYt3ygZB8f(Q%%>A$1K@lW6^-#)Ik23$9ocZEd(!;EndEB*p2}!%ncVf7{PW zd-Wpij;teB0T~Op^m7TDcDuXV-CTQ^B5y(Nvv?TMTdNo%Bxf+xRZBjfO-JAoz4GYB z<4D-kGc%TSC)0HJuBA2Gg$s72P0rl-5iG96Mx*3Vpc4k3tgb*kzwuM&A%fmpxp<0w z_e&M5t<#aTjeqwitcNga@4LD%u)rwhDdpdGe{1B-KR`U4{np{NP2k%v@hRw!sGUL< zUyk6kCuTDJ;^R*Ae2%M}o$d`5JdrWbWCPck=y;w4vrvV+QF)k%GbQUo1Vk1L9uEnk zHy+-Uty|YP;DxbX*7jbnr~kYTvrJf5YoURT^w^`j9FlD_RT|}Gsd;u z*ehpYp;OfmN;wbBx{;w*8}JE1eT6ekC(X#NExuMuSCp3Qda!a3S;d7lv~qC}Mv z{^(i^HS~Q`K6#JdI#^&HxqRrQhzE5=3;IBh1MJ%H$*`d^k=zuq2Kg#ts|z&7e-u4H zJKGy7Q_cu}leUn#zJjH=3!~K46s}EO%X?I6D|$C0l3hUfbK<~@HCOIWnlLxLL9gqX zbatPUNbEcqm92DA!x$1^cG7A@)$Db@3!Qv;Jl0%^8aJpKG2wUVg6w`SHT1_hJ7ZFL zUE9M=C7a^1DQ)MwLUJTl+L}$7zw=c5c^;=^29qsG>>6Y|l?dfOD{vE@=y%L=I5i8O?A4}aK<|GGtW z+X`x+GZEoG+JSx)zk2B8rFN*+FTGrjOXzLJwUU(0?Q143xtCT4yM)@8F?21zX1y|S z`W^@z@0lbupI(iJ!!PTg?ErZ6aW=td{AlpS=ZKLUv|5YiOe;9Lk?y#J6pQZIFi=TJ z={<>>3tVVK^Eo{}k(9AsRc>w=G+orj9kFIA|#rDT# z<25uUlzkww`^F&O9IEL?`9HO~^S`x<&jb_c1b;%ylBz)aiIuOxZ_DcZ4owVg0Tui~ zzyWRW;uFeEBvnd?Et1)dJZHzCJF zXA~U!-^psIp2_7qanh5JZJNhPmSPMQqSt#M7O<6u4unL>;Mr>V3H@4;{QeQ@_Zx}e z^1n&x=n|2Hjd#H0pWaGVgyey7lA!+RGG*tpa#S?YS=!;P>uf^%pMSm~928?8Ph-xA#2u-VxXy=Of9aXBRW)ch!O6j%Nz{A@q=ef6G2xoZ%R8lV+wZ(Af0 zKX4+-2z=dRY4}vcueXrUZrC1|{UErN8O^yf8q*Mx+4suX95=GYmCK6t1O4DdWkTrW z&d;B{6XwWGHRy8s&7`~}Sp=4kaaUj1TWTm;%;`7xczO`}uwUPnI!{+)d|Rj7n(sSQ zI}Dkk{xEW?mst8tR>-uzT!O|zQ4TK$lbXeG5ZYCUbfK&r$$5n*fEwp5QyMo-fqnlux{~pi~42IL$zp$6taqIva%c_{v zUED*+aVi<~4|68-b3cf!J`lt7$n_ce7rd_J@d2Xuk9NCy)k>fqTub(ED1#Tdx7RMk z!p5t8X}ZU6C{v%CABIk*rWzklJpE3BFMo}6ITA2}(01^bDR~K=dJw*>Ytudvv8VF% zRC(OETlyesgN!}B+j!6?=nYJrDKn|3!@|UOff0T- zFGOddZ}9)q0Ds!AQT&1@z|jQSyCQY^nz8!QyR?qj5JiK7HR=Q!E`CsNS$3d0gifd; zpW7^p?475qtwZ?EIL`h|oc)d{ZP*qa9qLM)`u`=MeZNe54w+GKr48hD>nrWX(lxU} z%t}{Iqej1`4x}7YKq*Nr%6jRo3Yag#)0$JH6%;+};I+mi53D2j@BF;cuW2lue!rQk zaFpr~cLz1(qP6}EelqOzSK|kjlvr4s48sECL)b-R8@o)9$WoXT^MR*`5YnAOH-0lldYCE3;HjlnH@U- literal 0 HcmV?d00001 diff --git a/dash/figures/histogram.py b/dash/figures/histogram.py new file mode 100644 index 00000000..63b5c8e0 --- /dev/null +++ b/dash/figures/histogram.py @@ -0,0 +1,118 @@ +import pandas as pd +import plotly.express as px +import dash_bootstrap_components as dbc +from dash import html, dcc +import logging + +logger = logging.getLogger('dash') + + +def histogram_figures(histograms): + """An array of histograms figures, dataframes and names. + @return fig[], df[], name[]""" + + dfs = [] + figs = [] + names = [] + + assert len(histograms._aggregation.keys()) == 1 # noqa + entity = list(histograms._aggregation.keys())[0] # noqa + for k, v in histograms._aggregation[entity].items(): # noqa + v.name = k + df = pd.DataFrame(v.histogram) + fig = px.histogram(df, x="key", y="count", title=v.name, log_y=len(v.histogram) > 1, + id={ + 'index': f"{entity}-{k}", + 'type': 'query-parameter' + } + ) + fig.update_layout(legend=dict(orientation="h", title=None), + yaxis_title=None, xaxis_title=None, + plot_bgcolor='rgba(0,0,0,0)' + ) + + figs.append(fig) + dfs.append(df) + names.append(v.name) + + return figs, dfs, names + + +def histogram_selects(histograms): + """An array of histograms checklists dataframes, and names. + @return fig[], df[], name[]""" + + dfs = [] + checklists = [] + names = [] + + assert len(histograms._aggregation.keys()) == 1 # noqa + entity = list(histograms._aggregation.keys())[0] # noqa + for k, v in histograms._aggregation[entity].items(): # noqa + v.name = k + if not any([isinstance(h.key, str) for h in v.histogram]): + continue + df = pd.DataFrame(v.histogram) + checklist = dcc.Checklist( + id={ + 'index': f"{entity}-{k}", + 'type': 'query-parameter' + }, + options=[ + { + 'value': str(h.key), + 'label': html.Div( + [ + html.Div(h.key), + dbc.Badge(h.count, className="ms-1", color="info") + ], + style={'display': 'inline-flex', 'paddingLeft': '2em'} + ) + } + for h in v.histogram + ], + labelStyle={'display': 'flex'} + ) + + checklists.append(checklist) + dfs.append(df) + names.append(v.name) + + return checklists, dfs, names + + +def histogram_sliders(histograms): + """An array of histograms sliders dataframes, and names. + @return slider[], df[], name[]""" + + dfs = [] + sliders = [] + names = [] + + assert len(histograms._aggregation.keys()) == 1 # noqa + entity = list(histograms._aggregation.keys())[0] # noqa + for k, v in histograms._aggregation[entity].items(): # noqa + v.name = k + if any([isinstance(h.key, str) for h in v.histogram]): + continue + if not any([isinstance(h.key, list) for h in v.histogram]): + continue + + assert len(v.histogram) == 1 + df = pd.DataFrame(v.histogram) + min_ = v.histogram[0].key[0] + max_ = v.histogram[0].key[1] + # value = max_, + sliders.append( + dcc.Slider(min_, max_, int((max_ - min_)/10), + id={ + 'index': f"{entity}-{k}", + 'type': 'query-parameter' + } + ) + ) + + dfs.append(df) + names.append(v.name) + + return sliders, dfs, names diff --git a/dash/figures/project.py b/dash/figures/project.py new file mode 100644 index 00000000..38e520a3 --- /dev/null +++ b/dash/figures/project.py @@ -0,0 +1,31 @@ + +import pandas as pd +import plotly.express as px +from inflection import titleize, pluralize +from models.project import project_detail_counts + + +def counts(): + """Horizontal bar graph. + @return fig, df""" + + project_counts = list(project_detail_counts()) + flattened = [] + for p in project_counts: + for k, v in p.items(): + f = {'name': p.project[0].name} + if k == 'project': + continue + f['entity'] = titleize(pluralize(k.replace('_count', ''))) + f['count'] = v + flattened.append(f) + df = pd.DataFrame(flattened) + fig = px.bar(df, x="count", y="entity", color='name', orientation='h', + hover_data=["name", "count"], + height=400, + log_x=True) + fig.update_layout(legend=dict(orientation="h", title=None), yaxis_title=None, xaxis_title=None, + # paper_bgcolor='rgba(0,0,0,0)', + plot_bgcolor='rgba(0,0,0,0)' + ) + return fig, df diff --git a/dash/models/__init__.py b/dash/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dash/models/file.py b/dash/models/file.py new file mode 100644 index 00000000..26d6f60d --- /dev/null +++ b/dash/models/file.py @@ -0,0 +1,98 @@ + +from dotwiz import DotWiz +from util import get_guppy_service + +import logging +logger = logging.getLogger('dash') + + +def get_file_histograms(dot_notation=True): + """Fetch histogram of counts for all projects. + @type dot_notation: bool render results as a lightweight class""" + + histogram_query = """ + query ($filter: JSON) { + _aggregation { + file (filter: $filter, filterSelf: false, accessibility: all) { + + project_id { + histogram { + key + count + } + }, + file_category { + histogram { + key + count + } + }, + data_type { + histogram { + key + count + } + }, + data_format { + histogram { + key + count + } + }, + patient_id { + histogram { + key + count + } + }, + patient_gender { + histogram { + key + count + } + }, + patient_disability_adjusted_life_years { + histogram { + key + count + } + }, + patient_ombCategory { + histogram { + key + count + } + }, + patient_ombCategory_detail { + histogram { + key + count + } + }, + patient_us_core_birthsex { + histogram { + key + count + } + }, + patient_quality_adjusted_life_years { + histogram { + key + count + } + }, + patient_maritalStatus_coding_0_display { + histogram { + key + count + } + } + } + } + } + """ + guppy_service = get_guppy_service() + data = guppy_service.graphql_query(histogram_query, variables={"filter": {"AND": []}})['data'] + if dot_notation: + return DotWiz(data) + return data diff --git a/dash/models/observation.py b/dash/models/observation.py new file mode 100644 index 00000000..8367084a --- /dev/null +++ b/dash/models/observation.py @@ -0,0 +1,62 @@ + +from dotwiz import DotWiz +from util import get_guppy_service + +import logging +logger = logging.getLogger('dash') + + +def get_observation_histograms(dot_notation=True): + """Fetch histogram of counts for all observations. + @type dot_notation: bool render results as a lightweight class""" + + histogram_query = """ + query ($filter: JSON) { + _aggregation { + case(filter: $filter, filterSelf: false, accessibility: all) { + category { + histogram { + key + count + } + } + code_display { + histogram { + key + count + } + } + patient_id { + histogram { + key + count + } + } + encounter_type { + histogram { + key + count + } + } + encounter_start { + histogram { + key + count + } + } + project_id { + histogram { + key + count + } + } + } + } + } + + """ + guppy_service = get_guppy_service() + data = guppy_service.graphql_query(histogram_query, variables={"filter": {"AND": []}})['data'] + if dot_notation: + return DotWiz(data) + return data diff --git a/dash/models/project.py b/dash/models/project.py new file mode 100644 index 00000000..9715c959 --- /dev/null +++ b/dash/models/project.py @@ -0,0 +1,61 @@ +import logging + +from dotwiz import DotWiz +from util import get_submission_service + +logger = logging.getLogger('dash') + + +def get_project_summaries(dot_notation=True): + """Fetch summaries for all projects. + @type dot_notation: bool render results as a lightweight class + """ + project_summaries_query = """ + query gqlHelperHomepageQuery { + projects: project(first: 10000) { + name: project_id + code + id + } + research_subject_count: _research_subject_count + specimen_count: _specimen_count + observation_count: _observation_count + document_reference_count: _document_reference_count + } + """ + query_service = get_submission_service() + data = query_service.query(project_summaries_query)['data'] + # logger.debug(data) + if dot_notation: + return DotWiz(data) + return data + + +def project_detail_counts(dot_notation=True): + """Return detailed information about projects. + @type dot_notation: bool render results as a lightweight class + """ + project_detail_counts_query = """ + query gqlHelperProjectDetailQuery( + $name: [String] + ) { + project(project_id: $name) { + name: project_id + code + id + } + research_subject_count: _research_subject_count(project_id: $name) + specimen_count: _specimen_count(project_id: $name) + observation_count: _observation_count(project_id: $name) + document_reference_count: _document_reference_count(project_id: $name) + } + """ + query_service = get_submission_service() + project_summaries = get_project_summaries() + for project in project_summaries.projects: + data = query_service.query(project_detail_counts_query, variables={'name': project.name})['data'] + # logger.debug(data) + if dot_notation: + yield DotWiz(data) + else: + yield data diff --git a/dash/pages/__init__.py b/dash/pages/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dash/pages/cohorts.py b/dash/pages/cohorts.py new file mode 100644 index 00000000..c518dfa6 --- /dev/null +++ b/dash/pages/cohorts.py @@ -0,0 +1,108 @@ +import logging + +import dash +import dash_bootstrap_components as dbc +from dash import dcc, html, Output, Input, ALL, callback +from figures.histogram import histogram_selects, histogram_sliders +from inflection import titleize, pluralize +from models.file import get_file_histograms +from models.observation import get_observation_histograms +# Define recursive dictionary +from collections import defaultdict +import json +import collections + +logger = logging.getLogger('dash') + +dash.register_page(__name__) + + +def tree_dict(): + """A recursive default dict.""" + return collections.defaultdict(tree_dict) + + +@callback( + Output('query-builder', 'children'), + Input({'type': 'query-parameter', 'index': ALL}, 'value'), + Input({'type': 'query-parameter', 'index': ALL}, 'id') + +) +def display_output(values, ids): + """Build graphql filter.""" + filter_ = tree_dict() + for value, id_ in zip(values, ids): + if not value: + continue + entity, parameter = id_["index"].split('-') + if 'AND' not in filter_[entity]: + filter_[entity]['AND'] = [] + filter_[entity]['AND'].append({'IN': {parameter: value}}) + return json.dumps(filter_, indent=4) + + +def layout(): + """Render the cohort page.""" + + def accordian(items_, dfs_, names_): + """Create an accordian with items for each facet.""" + return html.Div(dbc.Accordion( + [ + dbc.AccordionItem( + [ + item, + ], + id=f"accordian_item-{item.id}", + title=f"{titleize(pluralize(name))}" + ) + for item, df, name in zip(items_, dfs_, names_) + ], + start_collapsed=True, + always_open=True + )) + + # get the data from guppy and plot each aggregation + file_histograms = get_file_histograms() + + items = [] + data_frames = [] + names = [] + + for i, d, n in [histogram_selects(file_histograms), histogram_sliders(file_histograms)]: + items.extend(i) + data_frames.extend(d) + names.extend(n) + + file_accordian = accordian(items, data_frames, names) + + observation_histograms = get_observation_histograms() + + items = [] + data_frames = [] + names = [] + + for i, d, n in [histogram_selects(observation_histograms), histogram_sliders(observation_histograms)]: + items.extend(i) + data_frames.extend(d) + names.extend(n) + + observation_accordian = accordian(items, data_frames, names) + + return [ + html.H2("Cohorts"), + html.Hr(className="my-2"), + html.P('Quis imperdiet massa tincidunt nunc. Convallis tellus id interdum velit. Mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus.'), + html.Hr(className="my-2"), + html.Code( + id="query-builder", + children="Selections go here..."), + dbc.Tabs( + [ + dbc.Tab(file_accordian, label="Files"), + dbc.Tab(observation_accordian, label="Observations"), + ] + ) + ] + + +# variables : {filter: {AND: [{IN: {encounter_type: ["Encounter for symptom"]}}]}} \ No newline at end of file diff --git a/dash/pages/home.py b/dash/pages/home.py new file mode 100644 index 00000000..83954775 --- /dev/null +++ b/dash/pages/home.py @@ -0,0 +1,24 @@ + +import logging + +import dash +from dash import html, dash_table, dcc + +from figures.project import counts as project_counts + +logger = logging.getLogger('dash') + + +dash.register_page(__name__, path='/') + + +def layout(): + """Show the welcome message""" + fig, df = project_counts() + return [ + html.H2("Welcome"), + html.Hr(className="my-2"), + html.P( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), + dcc.Graph(figure=fig) + ] diff --git a/dash/pages/profile.py b/dash/pages/profile.py new file mode 100644 index 00000000..dd37b04b --- /dev/null +++ b/dash/pages/profile.py @@ -0,0 +1,60 @@ +import logging + +import dash_bootstrap_components as dbc +import pandas as pd +from util import get_authz, get_submission_service + +import dash +from dash import Input, Output, callback, dash_table, html + +logger = logging.getLogger('dash') + + +dash.register_page(__name__) + + +def layout(): + # query_service = get_submission_service() + # data = query_service.query("{ program(first:0) { id name projects { id code } } }") + # logger.error(data) + + simplified_authz = {k: ','.join([m['method'] for m in v]) for k, v in get_authz().items()} + df = pd.DataFrame([{'path': k, 'methods': v} for k, v in simplified_authz.items()]) + if 'path' in df: + df['id'] = df['path'] + df.set_index('id', inplace=True, drop=False) + + return [ + html.H2("Authorization"), + html.P( + "You have the following authorization profile.", + className="lead", + ), + html.Hr(className="my-2"), + dbc.Label('Select a row:'), + # see https://stackoverflow.com/questions/61905396/dash-datatable-with-select-all-checkbox + # more tricks https://github.com/Coding-with-Adam/Dash-by-Plotly/blob/master/DataTable/Tips-and-tricks/filter-datatable.py + dash_table.DataTable(df.to_dict('records'), + columns=[ + {'name': i, 'id': i, 'deletable': False} for i in df.columns + # omit the id column + if i != 'id' + ], + id='tbl', + row_selectable='multi', + page_current=0, + page_size=10, + sort_action='native', + cell_selectable=False, + ), + dbc.Alert(id='tbl_out'), + ] + + +@callback(Output('tbl_out', 'children'), + Input('tbl', 'derived_virtual_row_ids'), + Input('tbl', 'selected_row_ids') + ) +def update_graphs(row_ids, selected_row_ids): + return str(selected_row_ids) + diff --git a/dash/requirements.txt b/dash/requirements.txt new file mode 100644 index 00000000..d4996c55 --- /dev/null +++ b/dash/requirements.txt @@ -0,0 +1,15 @@ +pandas +dash +requests +cachelib +dash_bootstrap_components + +gen3 + +# dotnotation for dicts + +dotwiz + +flatten_json==0.1.13 + +inflection \ No newline at end of file diff --git a/dash/util/__init__.py b/dash/util/__init__.py new file mode 100644 index 00000000..4042c65e --- /dev/null +++ b/dash/util/__init__.py @@ -0,0 +1,193 @@ +import logging + +import flask +import requests +from cachelib import SimpleCache +from gen3.auth import endpoint_from_token +logger = logging.getLogger('dash') +from gen3.query import Gen3Query +from gen3.submission import Gen3Submission + +# Cache the access for 60 seconds so that we don't make multiple requests to +# Arborist when a user accesses a webpage and fetches multiple JS/CSS files. +ACCESS_CACHE = SimpleCache(default_timeout=60) + + +def get_guppy_service(endpoint='http://revproxy-service') -> Gen3Query: + """Construct a Query Class + + See https://uc-cdis.github.io/gen3sdk-python/_build/html/query.html#gen3.query.Gen3Query + """ + return Gen3Query(Gen3SessionAuth(endpoint=endpoint)) + + +def get_submission_service(endpoint='http://revproxy-service') -> Gen3Submission: + """Construct a Query Class + + See https://uc-cdis.github.io/gen3sdk-python/_build/html/submission.html#gen3.submission.Gen3Submission.query + """ + return Gen3Submission(Gen3SessionAuth(endpoint=endpoint)) + + +def get_authz(): + """Returns authorization from arborist.""" + # no request active + if not flask.request: + logger.error('get_authz: No flask request') + return {} + + # get incoming headers + if 'Authorization' not in flask.request.headers: + logger.error('get_authz: No Authorization in flask request') + return {} + + authorization = flask.request.headers['Authorization'] + # hit, in cache + if ACCESS_CACHE.has(authorization): + return ACCESS_CACHE.get(authorization) + # miss, go get it + # logger.debug("Refreshing authz") + arborist_response = requests.get('http://revproxy-service/authz/mapping', + headers={'Authorization': authorization}) + arborist_response.raise_for_status() + # update cache + ACCESS_CACHE.set(authorization, arborist_response.json()) + return ACCESS_CACHE.get(authorization) + + +class Gen3SessionAuth(requests.auth.AuthBase): + """ + An Auth helper based on access token. + No attempt is made to refresh. + """ + def __init__(self, access_token=None, endpoint=None): + """ + endpoint + """ + if access_token: + self._access_token = access_token + elif not flask.request: + logger.error('Gen3SessionAuth No flask request') + elif 'Authorization' not in flask.request.headers: + logger.error('Gen3SessionAuth: No Authorization in flask request') + else: + authorization_parts = flask.request.headers['Authorization'].split(' ') + assert len(authorization_parts) == 2, f"Gen3SessionAuth expected bearer token {flask.request.headers['Authorization']}" + self._access_token = authorization_parts[-1] + if endpoint: + self.endpoint = endpoint + else: + self.endpoint = endpoint_from_token(self._access_token) + # logger.debug(f"Gen3SessionAuth _access_token {self._access_token} endpoint {self.endpoint}") + + def __call__(self, request_): + """Adds authorization header to the request + This gets called by the python.requests package on outbound requests + so that authentication can be added. + Args: + request_ (object): The incoming request object + """ + request_.headers["Authorization"] = "bearer " + self._access_token + # logger.debug(f"Gen3SessionAuth request_.headers['Authorization'] {request_.headers['Authorization']}") + return request_ + +# def get_authz_mock(): +# """""" +# return {'/data_file': [{'service': 'fence', 'method': 'file_upload'}, {'service': 'indexd', 'method': '*'}], +# '/open': [{'service': '*', 'method': 'read'}, {'service': '*', 'method': 'read-storage'}], +# '/programs': [{'service': 'indexd', 'method': '*'}], +# '/programs/MyFirstProgram': [{'service': 'indexd', 'method': '*'}], +# '/programs/MyFirstProgram/projects': [{'service': 'indexd', 'method': '*'}], +# '/programs/MyFirstProgram/projects/MyFirstProject': [{'service': '*', 'method': 'create'}, +# {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, +# {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/aced': [{'service': 'indexd', 'method': '*'}], +# '/programs/aced/projects': [{'service': 'indexd', 'method': '*'}], +# '/programs/aced/projects/Alcoholism': [{'service': '*', 'method': 'create'}, +# {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, +# {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/aced/projects/Alzheimers': [{'service': '*', 'method': 'create'}, +# {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, +# {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/aced/projects/Breast_Cancer': [{'service': '*', 'method': 'create'}, +# {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, +# {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/aced/projects/Colon_Cancer': [{'service': '*', 'method': 'create'}, +# {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, +# {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/aced/projects/Diabetes': [{'service': '*', 'method': 'create'}, +# {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, +# {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/aced/projects/Lung_Cancer': [{'service': '*', 'method': 'create'}, +# {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, +# {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/aced/projects/Prostate_Cancer': [{'service': '*', 'method': 'create'}, +# {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, +# {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/jnkns': [{'service': '*', 'method': 'create'}, {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/jnkns/projects': [{'service': '*', 'method': 'create'}, {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/jnkns/projects/jenkins': [{'service': '*', 'method': 'create'}, +# {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, +# {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/program1': [{'service': '*', 'method': 'create'}, {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/program1/projects': [{'service': '*', 'method': 'create'}, {'service': '*', 'method': 'delete'}, +# {'service': '*', 'method': 'read'}, +# {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/programs/program1/projects/P1': [{'service': '*', 'method': 'create'}, +# {'service': '*', 'method': 'delete'}, {'service': '*', 'method': 'read'}, +# {'service': '*', 'method': 'read-storage'}, +# {'service': '*', 'method': 'update'}, +# {'service': '*', 'method': 'write-storage'}, +# {'service': 'indexd', 'method': '*'}], +# '/services/sheepdog/submission/program': [{'service': 'sheepdog', 'method': '*'}], +# '/services/sheepdog/submission/project': [{'service': 'sheepdog', 'method': '*'}], +# '/workspace': [{'service': 'jupyterhub', 'method': 'access'}]} From a318042bb365729c0869814c1440c81bc41d252c Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Sun, 6 Nov 2022 06:06:02 -0800 Subject: [PATCH 02/17] Checklist per property --- dash/figures/histogram.py | 11 ++++++--- dash/models/file.py | 5 ++-- dash/models/observation.py | 8 ++++--- dash/pages/cohorts.py | 49 ++++++++++++++++++++++++++------------ 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/dash/figures/histogram.py b/dash/figures/histogram.py index 63b5c8e0..e27c02bc 100644 --- a/dash/figures/histogram.py +++ b/dash/figures/histogram.py @@ -1,8 +1,9 @@ +import logging + +import dash_bootstrap_components as dbc import pandas as pd import plotly.express as px -import dash_bootstrap_components as dbc from dash import html, dcc -import logging logger = logging.getLogger('dash') @@ -64,7 +65,11 @@ def histogram_selects(histograms): 'label': html.Div( [ html.Div(h.key), - dbc.Badge(h.count, className="ms-1", color="info") + dbc.Badge(h.count, className="ms-1", color="info", + id={ + 'index': f"{entity}-{k}-{h.key}", + 'type': 'term-count' + }) ], style={'display': 'inline-flex', 'paddingLeft': '2em'} ) diff --git a/dash/models/file.py b/dash/models/file.py index 26d6f60d..ef2225e3 100644 --- a/dash/models/file.py +++ b/dash/models/file.py @@ -6,8 +6,9 @@ logger = logging.getLogger('dash') -def get_file_histograms(dot_notation=True): +def get_file_histograms(dot_notation=True, variables={"filter": {"AND": []}}): """Fetch histogram of counts for all projects. + @param variables: a graphql filter @type dot_notation: bool render results as a lightweight class""" histogram_query = """ @@ -92,7 +93,7 @@ def get_file_histograms(dot_notation=True): } """ guppy_service = get_guppy_service() - data = guppy_service.graphql_query(histogram_query, variables={"filter": {"AND": []}})['data'] + data = guppy_service.graphql_query(histogram_query, variables=variables)['data'] if dot_notation: return DotWiz(data) return data diff --git a/dash/models/observation.py b/dash/models/observation.py index 8367084a..f50da53b 100644 --- a/dash/models/observation.py +++ b/dash/models/observation.py @@ -6,9 +6,11 @@ logger = logging.getLogger('dash') -def get_observation_histograms(dot_notation=True): +def get_observation_histograms(dot_notation=True, variables={"filter": {"AND": []}}): """Fetch histogram of counts for all observations. - @type dot_notation: bool render results as a lightweight class""" + @param variables: a graphql filter + @type dot_notation: bool render results as a lightweight class + """ histogram_query = """ query ($filter: JSON) { @@ -56,7 +58,7 @@ def get_observation_histograms(dot_notation=True): """ guppy_service = get_guppy_service() - data = guppy_service.graphql_query(histogram_query, variables={"filter": {"AND": []}})['data'] + data = guppy_service.graphql_query(histogram_query, variables=variables)['data'] if dot_notation: return DotWiz(data) return data diff --git a/dash/pages/cohorts.py b/dash/pages/cohorts.py index c518dfa6..7f09299f 100644 --- a/dash/pages/cohorts.py +++ b/dash/pages/cohorts.py @@ -2,7 +2,7 @@ import dash import dash_bootstrap_components as dbc -from dash import dcc, html, Output, Input, ALL, callback +from dash import dcc, html, Output, Input, ALL, callback, MATCH, State from figures.histogram import histogram_selects, histogram_sliders from inflection import titleize, pluralize from models.file import get_file_histograms @@ -22,23 +22,45 @@ def tree_dict(): return collections.defaultdict(tree_dict) +def build_filters(values, ids): + """Create a filter from selected values and ids.""" + filters = tree_dict() + for value, id_ in zip(values, ids): + if not value: + continue + entity, parameter = id_["index"].split('-') + if 'AND' not in filters[entity]['filter']: + filters[entity]['filter']['AND'] = [] + filters[entity]['filter']['AND'].append({'IN': {parameter: value}}) + return filters + + @callback( Output('query-builder', 'children'), Input({'type': 'query-parameter', 'index': ALL}, 'value'), Input({'type': 'query-parameter', 'index': ALL}, 'id') - ) -def display_output(values, ids): +def display_filters(values, ids): """Build graphql filter.""" - filter_ = tree_dict() - for value, id_ in zip(values, ids): - if not value: - continue - entity, parameter = id_["index"].split('-') - if 'AND' not in filter_[entity]: - filter_[entity]['AND'] = [] - filter_[entity]['AND'].append({'IN': {parameter: value}}) - return json.dumps(filter_, indent=4) + logger.error(('display_filters', values, ids)) + filters = build_filters(values, ids) + return json.dumps(filters, indent=4) + + +@callback( + Output({'type': 'term-count', 'index': MATCH}, 'children'), + Input({'type': 'query-parameter', 'index': MATCH}, 'value'), + State({'type': 'query-parameter', 'index': MATCH}, 'id') +) +def update_counters(values, ids): + """Run a histogram and then update badges.""" + logger.error(('update_counters', values, ids)) + return '' + # filters = build_filters(values, ids) + # if 'file' in filters: + # file_histograms = get_file_histograms(variables=filters['file']) + # logger.error(file_histograms) + # return json.dumps(filters, indent=4) def layout(): @@ -52,7 +74,6 @@ def accordian(items_, dfs_, names_): [ item, ], - id=f"accordian_item-{item.id}", title=f"{titleize(pluralize(name))}" ) for item, df, name in zip(items_, dfs_, names_) @@ -104,5 +125,3 @@ def accordian(items_, dfs_, names_): ) ] - -# variables : {filter: {AND: [{IN: {encounter_type: ["Encounter for symptom"]}}]}} \ No newline at end of file From 10177490f9575f42e2af65532691a2c65c6c0044 Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Sun, 6 Nov 2022 06:12:17 -0800 Subject: [PATCH 03/17] RangeSlider --- dash/figures/histogram.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dash/figures/histogram.py b/dash/figures/histogram.py index e27c02bc..12f1cbcf 100644 --- a/dash/figures/histogram.py +++ b/dash/figures/histogram.py @@ -105,16 +105,16 @@ def histogram_sliders(histograms): assert len(v.histogram) == 1 df = pd.DataFrame(v.histogram) - min_ = v.histogram[0].key[0] - max_ = v.histogram[0].key[1] + min_ = int(v.histogram[0].key[0]) + max_ = int(v.histogram[0].key[1]) # value = max_, sliders.append( - dcc.Slider(min_, max_, int((max_ - min_)/10), - id={ - 'index': f"{entity}-{k}", - 'type': 'query-parameter' - } - ) + dcc.RangeSlider(min_, max_, int((max_ - min_) / 10), + id={ + 'index': f"{entity}-{k}", + 'type': 'query-parameter' + } + ) ) dfs.append(df) From f6a553533fec61f3776ca8336a4dc88f809d28da Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Sun, 6 Nov 2022 09:24:28 -0800 Subject: [PATCH 04/17] Update badges via client side callback --- dash/app.py | 2 +- dash/figures/histogram.py | 2 +- dash/pages/cohorts.py | 79 ++++++++++++++++++++++++++++++++++----- 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/dash/app.py b/dash/app.py index f4aa4ae3..7684ab2f 100644 --- a/dash/app.py +++ b/dash/app.py @@ -22,7 +22,7 @@ def check_privileges(): get_authz() -# Clientside callback +# Clientside callback: refresh token by calling fence's /user endpoint app.clientside_callback( """ // Call fence's /user endpoint, parse the response and update the profile diff --git a/dash/figures/histogram.py b/dash/figures/histogram.py index 12f1cbcf..78b4e01f 100644 --- a/dash/figures/histogram.py +++ b/dash/figures/histogram.py @@ -65,7 +65,7 @@ def histogram_selects(histograms): 'label': html.Div( [ html.Div(h.key), - dbc.Badge(h.count, className="ms-1", color="info", + dbc.Badge(h.count, className="ms-1 term-count", color="info", id={ 'index': f"{entity}-{k}-{h.key}", 'type': 'term-count' diff --git a/dash/pages/cohorts.py b/dash/pages/cohorts.py index 7f09299f..3c5e1887 100644 --- a/dash/pages/cohorts.py +++ b/dash/pages/cohorts.py @@ -2,7 +2,7 @@ import dash import dash_bootstrap_components as dbc -from dash import dcc, html, Output, Input, ALL, callback, MATCH, State +from dash import dcc, html, Output, Input, ALL, callback, MATCH, State, get_app from figures.histogram import histogram_selects, histogram_sliders from inflection import titleize, pluralize from models.file import get_file_histograms @@ -48,19 +48,78 @@ def display_filters(values, ids): @callback( - Output({'type': 'term-count', 'index': MATCH}, 'children'), - Input({'type': 'query-parameter', 'index': MATCH}, 'value'), - State({'type': 'query-parameter', 'index': MATCH}, 'id') + Output('histogram-data', 'data'), + Input({'type': 'query-parameter', 'index': ALL}, 'value'), + Input({'type': 'query-parameter', 'index': ALL}, 'id') ) def update_counters(values, ids): """Run a histogram and then update badges.""" logger.error(('update_counters', values, ids)) - return '' - # filters = build_filters(values, ids) + filters = build_filters(values, ids) + histograms = {'_aggregation': {}} + histogram_fetchers = { + 'file': get_file_histograms, + 'case': get_observation_histograms + } + for entity_name in filters: + fetcher = histogram_fetchers.get(entity_name, None) + if fetcher: + fetcher_results = fetcher(variables=filters[entity_name]) + for aggregation_name in fetcher_results['_aggregation']: + histograms['_aggregation'][aggregation_name] = fetcher_results['_aggregation'][aggregation_name] + return histograms + # # if 'file' in filters: # file_histograms = get_file_histograms(variables=filters['file']) - # logger.error(file_histograms) - # return json.dumps(filters, indent=4) + # return file_histograms + # else: + # return {} + + +# Clientside callback: traverse histogram, match DOM with class name 'term-count' update counts in DOM directly +get_app().clientside_callback( + """ + // traverse histogram, match DOM with class name 'term-count' update counts in DOM directly + async function(histograms) { + // console.log('debug: histogram counters',histograms); + if (Object.keys(histograms).length === 0) { + // console.log("debug: empty histogram"); + return window.dash_clientside.no_update + } + // get all our badges into a lookup hash by id + const termCounts = Array.from(document.getElementsByClassName('term-count')); + const termCountLookup = {} + termCounts.forEach((item) => termCountLookup[JSON.parse(item.id)['index']] = item) + // const entity_name = 'file' ; + Object.keys(histograms._aggregation).forEach((entity_name) => { + console.log('Updating badges', entity_name) + const entity = histograms._aggregation[entity_name] ; + for (const property_name in entity) { + const p = `${entity_name}-${property_name}` + entity[property_name].histogram.forEach((h) => { + // TODO - check range sliders + if (termCountLookup[`${p}-${h.key}`]) { + termCountLookup[`${p}-${h.key}`].innerText = h.count + // remove from array + delete termCountLookup[`${p}-${h.key}`] + } + }) ; + // set items no longer in histogram to 0 + Object.keys(termCountLookup).forEach((k) => { + if (k.startsWith(p)) { + termCountLookup[k].innerText = '0'; + } + }) ; + } + }); + // always return no update since we updated dom directly + return window.dash_clientside.no_update + } + """, + Output('placeholder-dummy', 'children'), + Input('histogram-data', 'data'), + prevent_initial_call=True +) def layout(): @@ -122,6 +181,8 @@ def accordian(items_, dfs_, names_): dbc.Tab(file_accordian, label="Files"), dbc.Tab(observation_accordian, label="Observations"), ] - ) + ), + dcc.Store(id='histogram-data', storage_type='local'), + html.P(id='placeholder-dummy', hidden=True) ] From 6c1fe49aa4fe7fc6a38d1ccf251667d3496755a8 Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Mon, 7 Nov 2022 09:33:29 -0800 Subject: [PATCH 05/17] Adds dash container --- docker-compose.override.yml | 11 +++++++++++ nginx.conf | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/docker-compose.override.yml b/docker-compose.override.yml index c406044d..818baa70 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -123,6 +123,17 @@ services: - minio-manchester - minio-stanford + dash-service: + build: dash + container_name: dash-service + ports: + - "8050:8050" # HOST:CONTAINER + environment: + - DASH_URL_BASE_PATHNAME=/dash/proxy/ + networks: + - devnet + + # expose postgres to host os diff --git a/nginx.conf b/nginx.conf index 49270bb6..edfa0007 100644 --- a/nginx.conf +++ b/nginx.conf @@ -283,6 +283,28 @@ http { location /lw-workspace/ { return 302 /lw-workspace/proxy; } + + location /dash/proxy { + + if ($saved_set_cookie != "") { + add_header Set-Cookie $saved_set_cookie always; + } + + error_page 403 = @errorworkspace; + + proxy_pass http://dash-service:8050; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; + proxy_set_header Authorization "$access_token"; + } + + location /dash/ { + return 302 /dash/proxy; + } + + } From 5bf7d0a7582dcaf85403cf00d566593e453c211a Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Mon, 7 Nov 2022 09:34:11 -0800 Subject: [PATCH 06/17] Queries files based on observations --- dash/models/file.py | 27 ++++++++++++++++++++++++ dash/models/observation.py | 25 ++++++++++++++++++++++ dash/pages/cohorts.py | 43 ++++++++++++++++++++++++-------------- 3 files changed, 79 insertions(+), 16 deletions(-) diff --git a/dash/models/file.py b/dash/models/file.py index ef2225e3..7b9fe657 100644 --- a/dash/models/file.py +++ b/dash/models/file.py @@ -6,6 +6,33 @@ logger = logging.getLogger('dash') +def get_files(dot_notation=True, variables={"filter": {"AND": []}, "sort": []}): + """Fetch histogram of counts for all projects. + @param variables: a graphql filter and sort + @type dot_notation: bool render results as a lightweight class""" + query = """ + query ($sort: JSON,$filter: JSON,) { + file (accessibility: all, offset: 0, first: 1000, , sort: $sort, filter: $filter,) { + file_id + patient_id + file_category + file_name + file_size + object_id + } + _aggregation { + file (filter: $filter, accessibility: all) { + _totalCount + } + } + } + """ + guppy_service = get_guppy_service() + data = guppy_service.graphql_query(query, variables=variables)['data'] + data = DotWiz(data) + return [f for f in data.file] + + def get_file_histograms(dot_notation=True, variables={"filter": {"AND": []}}): """Fetch histogram of counts for all projects. @param variables: a graphql filter diff --git a/dash/models/observation.py b/dash/models/observation.py index f50da53b..ce7f4b22 100644 --- a/dash/models/observation.py +++ b/dash/models/observation.py @@ -6,6 +6,31 @@ logger = logging.getLogger('dash') +def get_patients(variables={"filter": {"AND": []}}): + """Query histogram on patient_id to get all unique patients + @type variables: object graphql filter object + @return list of patient_id strings + """ + query = """ + query ($filter: JSON) { + _aggregation { + case(filter: $filter) { + patient_id { + histogram { + key + count + } + } + } + } + } + """ + guppy_service = get_guppy_service() + data = guppy_service.graphql_query(query, variables=variables)['data'] + data = DotWiz(data) + return [h.key for h in data._aggregation.case.patient_id.histogram] # noqa + + def get_observation_histograms(dot_notation=True, variables={"filter": {"AND": []}}): """Fetch histogram of counts for all observations. @param variables: a graphql filter diff --git a/dash/pages/cohorts.py b/dash/pages/cohorts.py index 3c5e1887..02f43b0a 100644 --- a/dash/pages/cohorts.py +++ b/dash/pages/cohorts.py @@ -2,13 +2,12 @@ import dash import dash_bootstrap_components as dbc -from dash import dcc, html, Output, Input, ALL, callback, MATCH, State, get_app +from dash import dcc, html, Output, Input, ALL, callback, MATCH, State, get_app, dash_table +from dotwiz import DotWiz from figures.histogram import histogram_selects, histogram_sliders from inflection import titleize, pluralize -from models.file import get_file_histograms -from models.observation import get_observation_histograms -# Define recursive dictionary -from collections import defaultdict +from models.file import get_file_histograms, get_files +from models.observation import get_observation_histograms, get_patients import json import collections @@ -42,7 +41,6 @@ def build_filters(values, ids): ) def display_filters(values, ids): """Build graphql filter.""" - logger.error(('display_filters', values, ids)) filters = build_filters(values, ids) return json.dumps(filters, indent=4) @@ -54,26 +52,36 @@ def display_filters(values, ids): ) def update_counters(values, ids): """Run a histogram and then update badges.""" - logger.error(('update_counters', values, ids)) filters = build_filters(values, ids) histograms = {'_aggregation': {}} histogram_fetchers = { 'file': get_file_histograms, 'case': get_observation_histograms } - for entity_name in filters: - fetcher = histogram_fetchers.get(entity_name, None) + for entity_name in histogram_fetchers.keys(): + fetcher = histogram_fetchers.get(entity_name) if fetcher: - fetcher_results = fetcher(variables=filters[entity_name]) + fetcher_results = fetcher(variables=filters.get(entity_name, {"filter": {"AND": []}})) for aggregation_name in fetcher_results['_aggregation']: histograms['_aggregation'][aggregation_name] = fetcher_results['_aggregation'][aggregation_name] return histograms - # - # if 'file' in filters: - # file_histograms = get_file_histograms(variables=filters['file']) - # return file_histograms - # else: - # return {} + + +@callback( + Output('results', 'data'), + Input('query', 'n_clicks'), + Input({'type': 'query-parameter', 'index': ALL}, 'value'), + Input({'type': 'query-parameter', 'index': ALL}, 'id') +) +def query(n_clicks, values, ids): + """Run a histogram and then update badges.""" + filters = build_filters(values, ids) + patient_ids = get_patients(variables=filters.get('case', {"filter": {"AND": []}})) + file_filters = filters.get('file', {"filter": {"AND": []}}) + file_filters['filter']['AND'].append({"IN": {"patient_id": patient_ids}}) + file_filters['sort'] = [] + files = get_files(variables=file_filters) + return files # Clientside callback: traverse histogram, match DOM with class name 'term-count' update counts in DOM directly @@ -176,12 +184,15 @@ def accordian(items_, dfs_, names_): html.Code( id="query-builder", children="Selections go here..."), + html.Hr(className="my-2"), + html.Button('Query', id='query', n_clicks=0, style={'display': 'flex', 'float': 'right'}), dbc.Tabs( [ dbc.Tab(file_accordian, label="Files"), dbc.Tab(observation_accordian, label="Observations"), ] ), + dash_table.DataTable(id='results'), dcc.Store(id='histogram-data', storage_type='local'), html.P(id='placeholder-dummy', hidden=True) ] From 95db4e915352de285b1494ed9ffc901261d52bd9 Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Wed, 23 Nov 2022 08:13:33 -0800 Subject: [PATCH 07/17] Adds fhir service --- docker-compose.override.yml | 12 ++- fhir/README.md | 12 +++ fhir/data/application.yaml | 205 ++++++++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 fhir/README.md create mode 100644 fhir/data/application.yaml diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 818baa70..a2b5eac9 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -134,6 +134,16 @@ services: - devnet + fhir-service: + image: "hapiproject/hapi:latest" + ports: + - "8090:8080" + volumes: + - ./fhir/data:/data/hapi + environment: + SPRING_CONFIG_LOCATION: 'file:///data/hapi/application.yaml' + networks: + - devnet # expose postgres to host os @@ -153,4 +163,4 @@ volumes: data4-1: data4-2: data5-1: - data5-2: \ No newline at end of file + data5-2: diff --git a/fhir/README.md b/fhir/README.md new file mode 100644 index 00000000..65e24977 --- /dev/null +++ b/fhir/README.md @@ -0,0 +1,12 @@ +DROP DATABASE hapi ; CREATE DATABASE hapi owner fhir_user ; + + +\c hapi; +DO $$ DECLARE + r RECORD; +BEGIN + FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP + EXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE '; + END LOOP; +END $$; + diff --git a/fhir/data/application.yaml b/fhir/data/application.yaml new file mode 100644 index 00000000..d1a76a2a --- /dev/null +++ b/fhir/data/application.yaml @@ -0,0 +1,205 @@ +#Adds the option to go to eg. http://localhost:8080/actuator/health for seeing the running configuration +#see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints +management: + endpoints: + web: + exposure: + include: "health,prometheus" +spring: + main: + allow-circular-references: true + #allow-bean-definition-overriding: true + flyway: + enabled: false + check-location: false + baselineOnMigrate: true + datasource: + url: 'jdbc:postgresql://postgres:5432/hapi' + username: 'fhir_user' + password: 'fhir_pass' + driverClassName: org.postgresql.Driver + max-active: 20 + +# url: 'jdbc:h2:file:./target/database/h2' +# #url: jdbc:h2:mem:test_mem +# username: sa +# password: null +# driverClassName: org.h2.Driver +# max-active: 15 + + # database connection pool size + hikari: + maximum-pool-size: 25 + jpa: + properties: + hibernate.format_sql: false + hibernate.show_sql: false + #Hibernate dialect is automatically detected except Postgres and H2. + #If using H2, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect + #If using postgres, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect + + hibernate.dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect + # hibernate.hbm2ddl.auto: update + # hibernate.jdbc.batch_size: 20 + # hibernate.cache.use_query_cache: false + # hibernate.cache.use_second_level_cache: false + # hibernate.cache.use_structured_entries: false + # hibernate.cache.use_minimal_puts: false + ### These settings will enable fulltext search with lucene or elastic + hibernate.search.enabled: true + ### lucene parameters +# hibernate.search.backend.type: lucene +# hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiLuceneAnalysisConfigurer +# hibernate.search.backend.directory.type: local-filesystem +# hibernate.search.backend.directory.root: target/lucenefiles +# hibernate.search.backend.lucene_version: lucene_current + ### elastic parameters ===> see also elasticsearch section below <=== +# hibernate.search.backend.type: elasticsearch +# hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticAnalysisConfigurer +hapi: + fhir: + ### This enables the swagger-ui at /fhir/swagger-ui/index.html as well as the /fhir/api-docs (see https://hapifhir.io/hapi-fhir/docs/server_plain/openapi.html) + openapi_enabled: true + ### This is the FHIR version. Choose between, DSTU2, DSTU3, R4 or R5 + fhir_version: R4 + ### enable to use the ApacheProxyAddressStrategy which uses X-Forwarded-* headers + ### to determine the FHIR server address + # use_apache_address_strategy: false + ### forces the use of the https:// protocol for the returned server address. + ### alternatively, it may be set using the X-Forwarded-Proto header. + # use_apache_address_strategy_https: false + ### enable to set the Server URL + # server_address: http://hapi.fhir.org/baseR4 + # defer_indexing_for_codesystems_of_size: 101 + # install_transitive_ig_dependencies: true + # implementationguides: + ### example from registry (packages.fhir.org) + # swiss: + # name: swiss.mednet.fhir + # version: 0.8.0 + # example not from registry + # ips_1_0_0: + # url: https://build.fhir.org/ig/HL7/fhir-ips/package.tgz + # name: hl7.fhir.uv.ips + # version: 1.0.0 + # supported_resource_types: + # - Patient + # - Observation + ################################################## + # Allowed Bundle Types for persistence (defaults are: COLLECTION,DOCUMENT,MESSAGE) + ################################################## + # allowed_bundle_types: COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET + # allow_cascading_deletes: true + # allow_contains_searches: true + # allow_external_references: true + # allow_multiple_delete: true + # allow_override_default_search_params: true + # auto_create_placeholder_reference_targets: false + # cql_enabled: true + # default_encoding: JSON + # default_pretty_print: true + # default_page_size: 20 + # delete_expunge_enabled: true + # enable_repository_validating_interceptor: true + # enable_index_missing_fields: false + # enable_index_of_type: true + # enable_index_contained_resource: false + ### !!Extended Lucene/Elasticsearch Indexing is still a experimental feature, expect some features (e.g. _total=accurate) to not work as expected!! + ### more information here: https://hapifhir.io/hapi-fhir/docs/server_jpa/elastic.html + advanced_lucene_indexing: false + bulk_export_enabled: true + bulk_import_enabled: true + # enforce_referential_integrity_on_delete: false + # This is an experimental feature, and does not fully support _total and other FHIR features. + # enforce_referential_integrity_on_delete: false + # enforce_referential_integrity_on_write: false + # etag_support_enabled: true + # expunge_enabled: true + client_id_strategy: ANY + # fhirpath_interceptor_enabled: false + # filter_search_enabled: true + # graphql_enabled: true + # narrative_enabled: true + # mdm_enabled: true + # local_base_urls: + # - https://hapi.fhir.org/baseR4 + mdm_enabled: false + # partitioning: + # allow_references_across_partitions: false + # partitioning_include_in_search_hashes: false + cors: + allow_Credentials: true + # These are allowed_origin patterns, see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/cors/CorsConfiguration.html#setAllowedOriginPatterns-java.util.List- + allowed_origin: + - '*' + + # Search coordinator thread pool sizes + search-coord-core-pool-size: 20 + search-coord-max-pool-size: 100 + search-coord-queue-capacity: 200 + + # Threadpool size for BATCH'ed GETs in a bundle. + # bundle_batch_pool_size: 10 + # bundle_batch_pool_max_size: 50 + + # logger: + # error_format: 'ERROR - ${requestVerb} ${requestUrl}' + # format: >- + # Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] + # Operation[${operationType} ${operationName} ${idOrResourceName}] + # UA[${requestHeader.user-agent}] Params[${requestParameters}] + # ResponseEncoding[${responseEncodingNoDefault}] + # log_exceptions: true + # name: fhirtest.access + # max_binary_size: 104857600 + # max_page_size: 200 + # retain_cached_searches_mins: 60 + # reuse_cached_search_results_millis: 60000 + tester: + home: + name: Local Tester + server_address: 'http://localhost:8080/fhir' + refuse_to_fetch_third_party_urls: false + fhir_version: R4 + global: + name: Global Tester + server_address: "http://hapi.fhir.org/baseR4" + refuse_to_fetch_third_party_urls: false + fhir_version: R4 + # validation: + # requests_enabled: true + # responses_enabled: true + # binary_storage_enabled: true + inline_resource_storage_below_size: 10000 +# bulk_export_enabled: true +# subscription: +# resthook_enabled: true +# websocket_enabled: false +# email: +# from: some@test.com +# host: google.com +# port: +# username: +# password: +# auth: +# startTlsEnable: +# startTlsRequired: +# quitWait: +# lastn_enabled: true +# store_resource_in_lucene_index_enabled: true +### This is configuration for normalized quantity serach level default is 0 +### 0: NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED - default +### 1: NORMALIZED_QUANTITY_STORAGE_SUPPORTED +### 2: NORMALIZED_QUANTITY_SEARCH_SUPPORTED +# normalized_quantity_search_level: 2 +#elasticsearch: +# debug: +# pretty_print_json_log: false +# refresh_after_write: false +# enabled: false +# password: SomePassword +# required_index_status: YELLOW +# rest_url: 'localhost:9200' +# protocol: 'http' +# schema_management_strategy: CREATE +# username: SomeUsername \ No newline at end of file From 22b1af09370a2fe5c5ecc856be160d9bb345bd89 Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Wed, 23 Nov 2022 08:18:39 -0800 Subject: [PATCH 08/17] Bump ES memory --- docker-compose.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index cb4bbf76..611710de 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -181,7 +181,9 @@ services: - bootstrap.memory_lock=false # For apple silicon - bootstrap.system_call_filter=false - - "ES_JAVA_OPTS=-Xms2g -Xmx4g" + # For apple silicon + # - "ES_JAVA_OPTS=-Xms6g -Xmx6g -XX:+HeapDumpOnOutOfMemoryError -XX:UseAVX=0" + - "ES_JAVA_OPTS=-Xms6g -Xmx6g" entrypoint: - /bin/bash # mmapfs requires systemctl update - see https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-store.html#mmapfs @@ -276,7 +278,7 @@ services: - devnet volumes: - ./nginx.conf:/etc/nginx/nginx.conf - - ./minio.conf.staging:/etc/nginx/minio.conf + - ./minio.conf:/etc/nginx/minio.conf - ./Secrets/TLS/service.crt:/etc/nginx/ssl/nginx.crt - ./Secrets/TLS/service.key:/etc/nginx/ssl/nginx.key ports: From 4701e499cd38bddeca1eb23608d7b46e6a15758e Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Wed, 23 Nov 2022 08:19:32 -0800 Subject: [PATCH 09/17] Adds comment how to remove objects from bucket --- etl/setup-minio.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/etl/setup-minio.sh b/etl/setup-minio.sh index cff784cd..3bb7e849 100644 --- a/etl/setup-minio.sh +++ b/etl/setup-minio.sh @@ -18,6 +18,14 @@ mc mb ucl/aced-ucl mc mb manchester/aced-manchester mc mb stanford/aced-stanford +## remove all objects from bucket +#mc rm --recursive default/aced-default +#mc rm --recursive default/aced-public +#mc rm --recursive ohsu/aced-ohsu +#mc rm --recursive ucl/aced-ucl +#mc rm --recursive manchester/aced-manchester +#mc rm --recursive stanford/aced-stanford + # add users mc admin user add default $MINIO_TEST_USER $MINIO_TEST_PASSWORD From b3597bcec351fb8db4952d17e6784c9dfd635aa6 Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Wed, 23 Nov 2022 08:20:13 -0800 Subject: [PATCH 10/17] Adds fhir_user --- scripts/postgres_init.sql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/postgres_init.sql b/scripts/postgres_init.sql index 8237ab94..570faa99 100644 --- a/scripts/postgres_init.sql +++ b/scripts/postgres_init.sql @@ -30,3 +30,7 @@ ALTER USER indexd_user WITH SUPERUSER; CREATE USER arborist_user; ALTER USER arborist_user WITH PASSWORD 'arborist_pass'; ALTER USER arborist_user WITH SUPERUSER; + +CREATE USER fhir_user; +ALTER USER fhir_user WITH PASSWORD 'fhir_pass'; +ALTER USER fhir_user WITH SUPERUSER; \ No newline at end of file From 80a18c2b7023feb2ba2adf89e9fae948bbefa514 Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Wed, 23 Nov 2022 08:21:04 -0800 Subject: [PATCH 11/17] Adds more Observation fields --- etl/tube_lite | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/etl/tube_lite b/etl/tube_lite index 13c1d9b3..3531f548 100755 --- a/etl/tube_lite +++ b/etl/tube_lite @@ -152,6 +152,9 @@ query ($first: Int!, $offset: Int!) { component_0_valueQuantity_code component_0_valueQuantity_unit component_0_valueQuantity_value + component_0_valueCodeableConcept_coding_0_system + component_0_valueCodeableConcept_coding_0_code + component_0_valueCodeableConcept_coding_0_display component_1_code_coding_0_code component_1_code_coding_0_display component_1_code_coding_0_system @@ -166,6 +169,9 @@ query ($first: Int!, $offset: Int!) { component_1_valueQuantity_code component_1_valueQuantity_unit component_1_valueQuantity_value + component_1_valueCodeableConcept_coding_0_system + component_1_valueCodeableConcept_coding_0_code + component_1_valueCodeableConcept_coding_0_display } medication_requests { medication_id: id From 67abc841a646cdfd22f072932bc4580ed96f3a43 Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Wed, 23 Nov 2022 08:21:33 -0800 Subject: [PATCH 12/17] Experiment with sidebar --- dash/pages/cohorts.py | 76 +++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/dash/pages/cohorts.py b/dash/pages/cohorts.py index 02f43b0a..685a44e7 100644 --- a/dash/pages/cohorts.py +++ b/dash/pages/cohorts.py @@ -1,15 +1,14 @@ +import collections +import json import logging import dash import dash_bootstrap_components as dbc -from dash import dcc, html, Output, Input, ALL, callback, MATCH, State, get_app, dash_table -from dotwiz import DotWiz +from dash import dcc, html, Output, Input, ALL, callback, get_app, dash_table from figures.histogram import histogram_selects, histogram_sliders from inflection import titleize, pluralize from models.file import get_file_histograms, get_files from models.observation import get_observation_histograms, get_patients -import json -import collections logger = logging.getLogger('dash') @@ -129,6 +128,22 @@ def query(n_clicks, values, ids): prevent_initial_call=True ) +SIDEBAR_STYLE = { + "position": "fixed", + "top": 0, + "left": 0, + "bottom": 0, + "width": "24rem", + # "padding": "2rem 1rem", + "background-color": "#f8f9fa", +} + +CONTENT_STYLE = { + "margin-left": "25rem", + "margin-right": "2rem", + "padding": "2rem 1rem", +} + def layout(): """Render the cohort page.""" @@ -176,24 +191,35 @@ def accordian(items_, dfs_, names_): observation_accordian = accordian(items, data_frames, names) - return [ - html.H2("Cohorts"), - html.Hr(className="my-2"), - html.P('Quis imperdiet massa tincidunt nunc. Convallis tellus id interdum velit. Mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus.'), - html.Hr(className="my-2"), - html.Code( - id="query-builder", - children="Selections go here..."), - html.Hr(className="my-2"), - html.Button('Query', id='query', n_clicks=0, style={'display': 'flex', 'float': 'right'}), - dbc.Tabs( - [ - dbc.Tab(file_accordian, label="Files"), - dbc.Tab(observation_accordian, label="Observations"), - ] - ), - dash_table.DataTable(id='results'), - dcc.Store(id='histogram-data', storage_type='local'), - html.P(id='placeholder-dummy', hidden=True) - ] - + return html.Div([ + dbc.Row([ + dbc.Col([ + html.H2("Cohorts"), + html.Hr(className="my-2"), + html.P( + 'Quis imperdiet massa tincidunt nunc. Convallis tellus id interdum velit. Mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus.'), + html.Hr(className="my-2"), + html.Code( + id="query-builder", + children="Selections go here..."), + ], style=CONTENT_STYLE), + ]), + dbc.Row([ + dbc.Col([ + dbc.Tabs([ + dbc.Tab([html.P('Quis imperdiet massa tincidunt nunc...')], label="Conditions"), + dbc.Tab([html.P('Quis imperdiet massa tincidunt nunc...')], label="Medications"), + dbc.Tab([html.P('Quis imperdiet massa tincidunt nunc...')], label="Demographics"), + dbc.Tab(observation_accordian, label="Observations"), + dbc.Tab(file_accordian, label="Files"), + ]) + ], style=SIDEBAR_STYLE), + dbc.Col([ + html.Hr(className="my-2"), + html.Button('Query', id='query', n_clicks=0, style={'display': 'flex', 'float': 'right'}), + dash_table.DataTable(id='results'), + dcc.Store(id='histogram-data', storage_type='local'), + html.P(id='placeholder-dummy', hidden=True), + ], style=CONTENT_STYLE) + ]) + ]) From bafa8757aed3fae6eaaea552f472b8ebc612aa02 Mon Sep 17 00:00:00 2001 From: Brian Date: Wed, 30 Nov 2022 11:44:18 -0800 Subject: [PATCH 13/17] Add TODO comment - tests JIRA integration ACED-48 --- minio.conf.staging | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/minio.conf.staging b/minio.conf.staging index 807cac4c..32e02774 100644 --- a/minio.conf.staging +++ b/minio.conf.staging @@ -1,6 +1,6 @@ # -# MINIO configuration -# +# MINIO configuration +# # For load balancing, cluster support: coordinate with docker-compose.override.yml upstream minio-default { @@ -45,6 +45,10 @@ # MINIO S3 storage host(s) # server { + # + # TODO this is the only change for staging. Can we implement a script & template combination + # so there is not one of these files for every environment? + # server_name minio-default-staging.aced-idp.org; listen 80; From 560b65d94adfdffecef84c5f6b94ae361ca9f281 Mon Sep 17 00:00:00 2001 From: Liam Beckman Date: Mon, 5 Dec 2022 11:25:32 -0800 Subject: [PATCH 14/17] Change to local data-portal build for Windmill --- docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 611710de..784b0255 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -226,7 +226,8 @@ services: depends_on: - peregrine-service portal-service: - image: "quay.io/cdis/data-portal:3.33.0" # 2021.03 + # image: "quay.io/cdis/data-portal:3.33.0" # 2021.03 + build: data-portal container_name: portal-service command: ["bash", "/var/www/data-portal/waitForContainers.sh"] deploy: From 0e520ef21da7c005a99c242e8016b27ab3009235 Mon Sep 17 00:00:00 2001 From: matthewpeterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Mon, 5 Dec 2022 11:52:11 -0800 Subject: [PATCH 15/17] Add files via upload instructions for local windmill setup --- local_windmill_setup.MD | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 local_windmill_setup.MD diff --git a/local_windmill_setup.MD b/local_windmill_setup.MD new file mode 100644 index 00000000..3352920e --- /dev/null +++ b/local_windmill_setup.MD @@ -0,0 +1,57 @@ +These are the steps for setting up a local Windmill local Windmill/Portal-Service Instance + +1. clone feature/local-data-portal branch and follow the steps to setup in +https://github.com/ACED-IDP/compose-services-training/blob/feature/staging/docs/New_Setup.md + +OR if you already have a working staging implementation: + +txt``` +dc down remove everything. +change line 230 in docker compose yaml: +https://github.com/ACED-IDP/compose-services-training/blob/feature/local-data-portal/docker-compose.yml#L230 +``` + +2. Clone data-portal into your compose-services root directory located here: +https://github.com/uc-cdis/data-portal/ + +3. cd into data portal and build the image locally with: +```sh +docker build -t windmill . +``` + +4. cd into the root of compose services and dc up + +5. if portal services is running and healthy proceed otherwise dc down dc rm -f dc up -d + +6. run this command in data-portal directory : +```sh +npm i +``` + +7. put your gitops.json file in data/config and title it config.json. This is the file that you will edit to make changse in your local windmill build + +8. The below command is what you run when you want to see changes in a gitops file. The hot mapping only works for changes directly to the source code, +but the command should only take 30 seconds at most and will automatically update the page when it has been initiated. + +```sh +HOSTNAME=aced-training.compbio.ohsu.edu APP=config NODE_ENV=dev bash ./runWebpack.sh +change hostname to whatever your hostname is that you were using before. +``` + +this is the new root url of the website. You know you have suceeded in the previous steps when this works: +https://aced-training.compbio.ohsu.edu/dev.html/ + +visit this if your webpage isn't loading https://localhost:9443/bundle.js you will probably have to visit it the first time you load the dev server. + +9. you might need this line if you get an error about certs or versions when running the runWebpack.sh command: +```sh +export NODE_OPTIONS=--openssl-legacy-provider +``` + +10. to get the query page to load make this one line change in data-portal/src/localconf.js:L108 and reload webpack with the above command: +```txt +const graphqlSchemaUrl = `${hostname}${(basename && basename !== '/' && basename !== '/dev.html') ? basename : ''}/data/schema.json`;s +``` + + + From 57fc62b13062b9064ec32aa2a0ec12b625ebf762 Mon Sep 17 00:00:00 2001 From: matthewpeterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Mon, 5 Dec 2022 11:56:40 -0800 Subject: [PATCH 16/17] Add files via upload fixed the formatting --- local_windmill_setup.MD | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/local_windmill_setup.MD b/local_windmill_setup.MD index 3352920e..e3ff2907 100644 --- a/local_windmill_setup.MD +++ b/local_windmill_setup.MD @@ -3,10 +3,13 @@ These are the steps for setting up a local Windmill local Windmill/Portal-Servic 1. clone feature/local-data-portal branch and follow the steps to setup in https://github.com/ACED-IDP/compose-services-training/blob/feature/staging/docs/New_Setup.md +```txt OR if you already have a working staging implementation: -txt``` -dc down remove everything. +```sh +dc down rm -f +``` + change line 230 in docker compose yaml: https://github.com/ACED-IDP/compose-services-training/blob/feature/local-data-portal/docker-compose.yml#L230 ``` @@ -19,16 +22,16 @@ https://github.com/uc-cdis/data-portal/ docker build -t windmill . ``` -4. cd into the root of compose services and dc up +4. cd into the root of compose-services and dc up -5. if portal services is running and healthy proceed otherwise dc down dc rm -f dc up -d +5. If portal-services is running and healthy proceed otherwise dc down dc rm -f dc up -d -6. run this command in data-portal directory : +6. Run this command in data-portal directory : ```sh npm i ``` -7. put your gitops.json file in data/config and title it config.json. This is the file that you will edit to make changse in your local windmill build +7. Put your gitops.json file in data/config and title it config.json. This is the file that you will edit to make changes in your local windmill build 8. The below command is what you run when you want to see changes in a gitops file. The hot mapping only works for changes directly to the source code, but the command should only take 30 seconds at most and will automatically update the page when it has been initiated. @@ -38,17 +41,17 @@ HOSTNAME=aced-training.compbio.ohsu.edu APP=config NODE_ENV=dev bash ./runWebpac change hostname to whatever your hostname is that you were using before. ``` -this is the new root url of the website. You know you have suceeded in the previous steps when this works: +This is the new root url of the website. You know you have suceeded in the previous steps when this works: https://aced-training.compbio.ohsu.edu/dev.html/ -visit this if your webpage isn't loading https://localhost:9443/bundle.js you will probably have to visit it the first time you load the dev server. +Visit this if your webpage isn't loading https://localhost:9443/bundle.js you will probably have to visit it the first time you load the dev server. -9. you might need this line if you get an error about certs or versions when running the runWebpack.sh command: +9. You might need this line if you get an error about certs or versions when running the runWebpack.sh command: ```sh export NODE_OPTIONS=--openssl-legacy-provider ``` -10. to get the query page to load make this one line change in data-portal/src/localconf.js:L108 and reload webpack with the above command: +10. To get the query page to load make this one line change in data-portal/src/localconf.js:L108 and reload webpack with the above command: ```txt const graphqlSchemaUrl = `${hostname}${(basename && basename !== '/' && basename !== '/dev.html') ? basename : ''}/data/schema.json`;s ``` From 7d47bfdf6c13d2d80f75a0f27168da1dc22f7712 Mon Sep 17 00:00:00 2001 From: matthewpeterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Mon, 5 Dec 2022 11:58:24 -0800 Subject: [PATCH 17/17] Add files via upload --- local_windmill_setup.MD | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/local_windmill_setup.MD b/local_windmill_setup.MD index e3ff2907..1d89da0b 100644 --- a/local_windmill_setup.MD +++ b/local_windmill_setup.MD @@ -3,16 +3,17 @@ These are the steps for setting up a local Windmill local Windmill/Portal-Servic 1. clone feature/local-data-portal branch and follow the steps to setup in https://github.com/ACED-IDP/compose-services-training/blob/feature/staging/docs/New_Setup.md -```txt + OR if you already have a working staging implementation: + ```sh dc down rm -f ``` change line 230 in docker compose yaml: https://github.com/ACED-IDP/compose-services-training/blob/feature/local-data-portal/docker-compose.yml#L230 -``` + 2. Clone data-portal into your compose-services root directory located here: https://github.com/uc-cdis/data-portal/ @@ -52,6 +53,7 @@ export NODE_OPTIONS=--openssl-legacy-provider ``` 10. To get the query page to load make this one line change in data-portal/src/localconf.js:L108 and reload webpack with the above command: + ```txt const graphqlSchemaUrl = `${hostname}${(basename && basename !== '/' && basename !== '/dev.html') ? basename : ''}/data/schema.json`;s ```