From 07bae18e18d71fd98a4edf2c9d5994613d22a6f3 Mon Sep 17 00:00:00 2001 From: David Rice Date: Thu, 19 Mar 2026 14:36:35 +0000 Subject: [PATCH] first commit --- README.md | 2 + __pycache__/ui_mainwindow.cpython-313.pyc | Bin 0 -> 7298 bytes appbackground.jpg | Bin 0 -> 23390 bytes arriveico.png | Bin 0 -> 13469 bytes main.py | 291 ++++++++++++++++++++++ sprint_exporter.py | 55 ++++ ui_mainwindow.py | 133 ++++++++++ 7 files changed, 481 insertions(+) create mode 100644 README.md create mode 100644 __pycache__/ui_mainwindow.cpython-313.pyc create mode 100644 appbackground.jpg create mode 100644 arriveico.png create mode 100644 main.py create mode 100644 sprint_exporter.py create mode 100644 ui_mainwindow.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..84d0202 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Introduction +Jira Sprint Exporter - Python diff --git a/__pycache__/ui_mainwindow.cpython-313.pyc b/__pycache__/ui_mainwindow.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ff4fb814d2fd5121d818aa9b79391f4bf44376f GIT binary patch literal 7298 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 i-*Pi~ZsuF=%(q8@Ghem< literal 0 HcmV?d00001 diff --git a/appbackground.jpg b/appbackground.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1c32dc9bb1af7b8dbc2e9373622ad5c8cdeae055 GIT binary patch literal 23390 zcmeHvcT^Nhw{H(QDp4{FK>;PrkaNx;839QH3@~Jd43dK+$sj=xkR&2GC`nP0l98lD zL6nRr0wUox9MAc_`_{Slowe>?_dbd>^zN#?t9JdWc31D+)qOg3x&V-3-7=AkFcc2Lq&G5Db7A zJfguL9Hc40V+i;|-NibWO#|t(1U7em{^wCyRZ|-Z=YjF@@C$*Gd~iWA7)%TfgYt=p z!Qo~}|6jVC)p7cfINjdU(@l(**TsVeVdH9rj=JiFm@xplccmWA%UpIuc zBhnLUg|tJtNV06VykLQ%Y$RE332O3by2&H$Q7Zmuq@MpxeQSS5Yf&2(X(_0Lub8j1 zn={fA0rhoua`6!Jm1H@uTnwbovUypc=PaI%k}O7`+VZYwBvgP$fQOG8gwGq!0@i`H zu@%!*Q2Jd1cqYm6TU8$)A08im9#^y-FHBTal$Q_A3x{)q9NZp$E}jTqZWj;MKWk7x zdRU`TZk{Mt7wB1y2rE}FPe~TA{XZ0QcGJ}StKz@Kz}fjs+jDLYPepG~_`gETL*LI0 z$*YU>aP>l4BNe@oE}pFa64yoks~3OU*nboU+5Zx@vHq)JH!rl)dGBqkd67;?XRr+q zaJXQ9>3KF4{^CAE%MRu2b}j)9%U@g|{TI{UDs!ec2#=V8tF_lzBdQ9LEMN|g4a!Cg zt|SCk5)@J37m!ti!Ibz!MMZ?=Fz#>U!4Kolk-$^x~q7E^LXJ0rk>LpdYtkh~6VNIMqj8Af8V z+NzQ)a2~$jr`k>kPg{^zl0^gM;^q50S0Ck!)bm7~p#>8b;unVT^TS0%_(g=_u-_bp zNVEqy!Op5WH@|b`#pKaQgr_T7-__MglI1V!{G&`0Gy)rhCqe8)nwnwJMVj%Jx171*n z-ZR_%qbt1scQ5}qS3sgsV0VM`Ka&4T;eWgM{MiTa zigrf+b%B(EUZe{IE)cjt-~xdQ1TGNx{||wGxmZXS@b=3Gbf8WbF{)MN%K(D99mW?S}RQX-kk6^6_*#qccI80=$c~0qF{m=0bxC zg7nl`zSSS}#u;sQPM^8D00mlKS03!!nU4##`#0L^-)L)lv@^&f0P?WeIJtU+=kmbW7=S!r`WLnyw*2t(EC`Vt0ASCZo_@ay z1_Dw6;5g~@^jE>@>2V0fzl4*}qE1OPO4{wvSwJ^(b` z1pw;zZU{8uyd6yNKZY$Bbof>Z0K~=sKt2or1h@ad4JASS|FQnW!V|m;vv-OC0axGld8w@+xD0@|rob_$AWmoTkCDKnnS4mT zFG6Uh=QUzFSUR*V^-%2S_@_1x2U=p^={hfy*4@eOr41h>PZ_Y`7jp5`Aa z`=@$(II5>S5!&7RQ24Fzw%xY}{(ZyCuk{KBf7CTv@EWx{1?3^Vj`Ak0kiDt-(|B~e ziB-mMI<4?ZpF|qVf7k{@y|S*JT=_xbrvu})I8j^fNcgiM9}C<=X|{%gyf8b@YV1fC z^}Moylc4$0nVwPBu_w)O2rUmYuJY!4sguTH(;@$`C4lyBg$w^?wb|u?)fL7>al5t3 z>(~vrUVIN9ePcr0UHR;&XTr6HzOzrS7Nb(N(n22n;oH*ffa6rng-8; zg8wMttK-o+vU^gzD+PWK7Z>5ZA>|3LgFUf>^}35!AW=R!aECl^PTJW z+63NX1crq={iDh+m@W{wK;Qy_3j{6@xIo|nfeQpK5V%0#0)YzzE)cjt;D0#+m%+dq z2MK@yCI}`5?(g1W77Q5%gKT(U>H;eRw>yC z<1tgZhwGhL!jCPJ8v?Fhb6zShlVp#_EXiPpd`fu~Qv@HMgS0&Lac!JwCoIgx`}P^x zJit2X<-hh`O0X>6wY(&rQ=FszaOT!QPemwyItd(jO-HUIqEJ>SD)$85`?==Xs-Sso z4`WSNsO1ewRK3yMC!>icapb`nq~PFbfbmlZ1|~Kp79qjq^S~*1hD8dHF=La%WOZ2J z2pnDa8WIXtemP5YXn1l8R8T?BqoA(i^*hQdd;uU>U@*ce4LO;Y zGwP3mt|1Njt)KN%J*IaqJ3JB5H`E(lp6?}>{I(w>v{V@8T*XLTT2Wfg1x*rFw;hSB z3h+tyNB_z$f9o$uEItim)+ZXApc2AN`m7o9jOsac$&;|B{IA}J2qCO!w3Kk~-ev7w z2~Mi03`>BI@(s$)c3yTK3i<9|vysqLElypLtgd@oQcg}9AU>r`Zod7j zEG#^p_<2R(V776+scKVY=y|s*CvF?`Kq~s@`h453aGczUEQg!X-?oN59C5hjLS)Q+ zM4uri-)tRgy@{K=CY*xlwiyt9rzaum6fh?k*H7h9i+OWxi%b=}hcpud`x$a{2IwBQ zrTiY!FS(v|q-HIssX`hq@T*~?yWrDX9$yyBNnOPu!o?!hD?{&B6q1D;`X1rPZ-1WD zvwpey@nfvWTwty_rjX6-+ma2f=bx>x8(KJSd|$HFuQ#Yp*-W4I{E&i|D*Hpq@P&-K znQ+4@d&VB}X2(ejNz92Y)z>bTHbs3>J4cCdVf%fm`lF*RN8crsv#MQkVdz`Ef<8|n z{@Wj~ETs-$wl+6xr|U*NednJxnl2W8-;hC{@g0T;CC*WevW1Sq9#J=+(^uHWN`S2F zeiQB2#%9UQg5=VYIo-4YE}xjZkFtmYM~nc8u4i#{R-(ZpR_to@W9z&_LzZ0uVN_xiW{ZlRQlR4=S$*iI-YB@x5kNy^heq39@Spq z4l1Ksb7HkYI^w+TsI6{p)C=JITpE7K>fnT_{5$7K-^q7ML7WoXtgst`m1G}Xu&W-6 z<@qlmY8WTLpwgS^I0@kpvR=?Vy68wiVLBHPo`&?D;2soGu% zvK^CEp-lKL&SPQt-vVCfeY5)UCc5oV`mOT1DUowxyC9R|A@xq}$^Phx)^QJ`P7vYl zd|glK?Aye2rpGl+3vYBNmLhr$>JQw%KHpKo{FTX;l%GpsdCACx`*e*HORcAlIzhOvm#9B7^Q0YPs%ZX~39r+Gcj+yA22MsP6p;{F z17^+VaYwFCADN69&8b?;ypN7A8*-tkW1Yb)V{q~fb#T6?sR{3AG^P&lmjhKK0e$=J(dr8&bC;V9Oh2I&=3DA(IqepGv@9Mx@_EhfVYc z3IRcg*v*HO-rK(4u5Al8Ar-%y?Dr?WdrhHFI((P3HSs~1Po(D4AQ~O9gT>kev8f2% zE85f|Hc%27){i$X5&2mkyih+_d)Pp=#Ronzdr1gc5V5L^%ByjPJMbotEqlIDn!M=^ zeGDZl#eK8)29!+tUJie1u;EnOc64>T)!i^j(m91ni&CdEIn)JwZI81yjE{cIi zNGzCCQ=MBsOeF20XuH~_itg^0{%?c;G8qX~BK}y{&#Oy{iRu>cyFR-uHP2t5H*g1k z7<%jLcw12_`-u~2k8;yvz^uMx8!~aWNjYr1aE{#B3U{!mI=VdYQLsj*^@-#-^39Or zGG~r^TY}4d;Z?`vt|wMcQec-JlOP_%+1j!?uBTzsxLTt>1UV=Av{BwdCV5i~QVm5K zHMSXjXEN@5AeWtzO{EfoO14oU!EUtV(M$s#84%ykp2?3(mZr=`6Lx9lSGO|v?g~Wa zm^VUw5!2#Vm=zDNN9wF#yu7oukY;j+NA~fv2*z;jq4oE!i_SH##ve@xvKZ~T2_(!8 zYI4P0F#%=y|VviBtGK>=qOF|d1 z#dvvX@=Bou8&-G@S6s7$FoRYTUCx<8g=@+Bwd7ZcD=;b$jmZY0dz;^gM1TDC`=odfA zKYe|rESF4>MhEwYK4j_n)`&xn|*hSoewXH)K9)AKo0#*0pxxveeja@G1CqcJ%(e^?x&w@8sSfKGBoet zCGJ<3#SXsB7l`0bZXqd9JeCQ#`DrOyYua!CDt6C~00YdxXi&qXFc^2gkr2y{_vHg)r45 zX6S=h#m(W_dp#!sW9jIT771ey_$Sd0mPz8_doHkBW`Zs^$DkYEk-bHJ#;yT1-<2*? zmGfn)y-w=3`eyP|ZFyd3Z{5{DCuU4n@x2mjUuF|L#pRCtr44mfO}3PVI+cx0=gRKS z)2p#+Z|vXaLGOFL^$1g}yeuakb$xBQ!Yf6)ARSH=66%;Sl{)9Ym|db~tWa}XFuKmX zlQF8(KM-pDZP~V2PCzWeF_DS4v`tG-@Wz+Ox8%=eje=_P^gb5%$PrhI9PLfu#eC&@ zv>}@Kv&NvayuACCWpeIPqN33E#I#xBn1-&+Q^1fMTtk%4{LwR?1_K`t`|Kr~vrQpb zq}XHtIWrCg3rv<3PC_ZCqic!qsD-le3%H{_>#ul)rquCSS=)Grghiy)Gz^dbc2{wv zF;0Qt5*<4H8?oAw4Tx}F6Jz)dR3*DxXqpCuLg5NKqlGE?PJ6M*UBCI>yJ?+G_nU%7 zs5xJ5(+|Sv3MGAgS83A&zMD|-MyP7>(7m|d;+ELfClR!9qB1?f-G!|FiK51Id=P}b zt`-sB*rUhggx9?o-^1`rrBNsu_l8}>#5i1R!y%KUE92TV;Y)b7c(%$_KXJ^)^7sPO zTlC2iez?E5YH#t-xO=Z3JEKqVLS-tZ zwS4Sa<3Ft^SY9-@cRz{S`dR7bFBY-1Mb?TPYhCY!szX}Jl3>FUa*68IcbR?}&DNr* z40b0XWmNmR+L8a=juZ~2xy;00M>KiK--l?CvsB1Z)bX;NR(3<*Eh__wPFNKFTZUD(s=y|VuNJ|7zp`1&Kq}4tGflq0 z7QW2PFLSkh-Z^Z#x;F!CRle}QdyD19N&+MO(Q@VY{nRhX@p7j*bM5Gw5$4|7cST790( z*lIFTPp45_Vt}~i)!cKVCh$q_(+?jzDl6z!BCcEKlD77Sbh0jCuiW}j(%DEoXe62} z>GR5TxOU*=Yd??8_vAtA(5I-3r>H)=YoAlmTK-Tw)V_;PLUE*Z;n8)@d-pDN`UOoPo-BQ}YL6-dbRSo?C@-OVGIL{Eh08l;Z<_6T#(BmF=6Ock4? z))p{(gPKq`N4C=?rRGJiTve^qh*R%~@W`%%MU}-WVY=$mZ>=W?51|Cx<^l!9t(j)8 zyzJTN7P&8m>~_i!GE#((vdo}g5B^Mju*E%H$}c)(F3@(>#BM6icDJX_|8<0@ITO|L^N+oKXh5j(?o`vnlJaM7&<(N(2iLS4b z1ZR0cit0(KF_R0BOuE!wP`0>@;VYSZ9K%~TpuNNcoAk^_zwlK+@SFyA2`(U_?3+QuFOkaEEX@r`o$sh#?M`>`1cIdUrrQ~ps_L>m4Z z{x(sxG?_b)_)U?X-V8Bhgp|1E% z(&%?9Z9&{hPq=4k7>e{B6J@+kETW82dGKTJ4!TvnuEdm3pG)O{tB9_>A^KbRZ3XXo zD|hSxRWGuIYg_lqA_tQD3EIXRit}1%CySH#CH+fm?$jFf*DaP+>9%Au-%Rw->``(9 z=wF%d3r0?oNbNX7x<%fdh`}WFpM>)|nT$dAe3!KeqAw$`@%sZt>FPv>CQgAk9@93% zqI#2&!p=%3^kD@9xBRzi34R;rH_>TdoZQ+BAI~=+0jucN4;v;LORgf=26_@x#4q`; z^6??XeVjQK8#Glj(gd9}BjE*Qbj^n9&=dy?`r5GmPNJI&_(L~&8MA*4<}8_-%~MA? zZ;h^Avbfd^%X4>`ZFxui!@N+~E6;xmdn6`M%0htQa&+bvOk7ymX!e2s;Surp_J?sw zM4wvk_2%kp*o2V@*y*%>nF-HD*OwDGQC=~n=CK&fF{5mwz_-*S5*prz0Bp})Q!KTz z$iAlHmWS0Xwwokm^Mmr=Ng>qT^QK?o1O+Bd(VIqbL^)B9I7E!9Ft4kzy5LAG+DWzE zF@CJxA&>BuZHR`fJ1j)8;m6r`FCao~*je&CLevXUrdEm8>TUA*D&1ACgkx0<}K!NhJ*duQj8J6)I$#}3`gnr@w{ITMd9 zu|mfFz!mtjQ^56FupOrR4d-JSX=U_LXbO9uU6I}GcCB9$YouBEj`XP8HbCm=P zn4ibivCg;mf}S=hzzmZmLFiaQ`P@T73x>b`8CI8OJ_!^LC>Dh6LmVyKB&YDMYG!b~ zF~G4%&dk@28-5YPa1!+@e}|dSd$CvLS;IHcr`xIae3>kTpKJV5V12wy8 zv3NVc_x4HR!NJvvxE0dHP=bfvZ`IxIF~nJRy!H6FSW$$RZ`vXv)Cpq{Vcz}a3T>fa z(V=H{^n3a&QCUm&_Q$W*!l|vJIZn#W@x5QPB_55aMBzeyM3=27PU%kH-}*(L3_sSb z)?hHL5-&uFUB!4vP4Q-^5ZiYGx6h!XM;c4v{;7h{gOXAQ z)#LIu(_mb9{cwR+*fxddxjZjA_HEigXrIt!ft>~I=Z~x$wjbY-`yTS1n(aFT;jPJ+{jsXbQ>5n*Y-)!pGncGI9G=q;X{j5qUf zdfILu#BeyZ%m?gs!j|y}9mGv}aMm|hP#?S)VusVng%_dg7O#Y8$dlv*HxSWZ1(ha7 zW0&LgH)Ic@L_nvEb@|J@_-ko(i%6A?n3r9?cV$k216}?2aMArEvclCZ0XdDJRrq+5 z3^PU%Zbm+zV*UXI%ANThFrX~8bE)+P*@jW-kc>Dd?JqZ6<*gO7kVvDKIlZDco>18& zh8Mp}86v{X2*=kO=5u1p_*P2#YL^3zx2gbHB3!6djkwGs7^6XKdHME_H!f)FkI|c% z(bqUA3Q$GFILf13WjWR*VE{W>!B?qdd)(`tBs>|1lJ?Pb_!RV*`G6R|udtj}yr$Tf zUg@-)hL;Ap?M_herb3@wI~1?yusRW()93rK&o{M$TeANLr}H-;{*nF|3ukPUA!q;p zNrDs`)L`*}afN@F2#a4ZN;$gyLv8@0?keSd(-$HClpkQ@7*C1fu>7a|xUx!Ex)Iy| zl>LJ50)YzzE)cjt-~xdQ1TGM`K;Qy_3j{6@xIo|nfeQrwS0exhLfJ{rUfw@@tcwMH zQ1LAN5eU^GL0Ce;4M8EvHNztx&qJYS8&FA30jVe_qCuG(_)~d|L;X-HJZK7(PbR;J z+m3el0l1qcT6!sIY~w>E#zw60kw80fl^^dN#aBZ8nZ_t-{$3qYRr9d1E86? z3y&t4mz?5m{uSc#@Ec^I6Za7c{5xvdxUViJFe*K-&Rz51*y$tyytFUR`i14nRxoS# z;%Li~h%uMs&gPnFbMgHwb!l6^(~3HgJ*iEk`*PsBSeaoRjk%+zrXDwW`z9)&Uy8jl0a(X zCc4|6wWd+BbTr8*HD98q2Cx{D5X~!_{-5j@s5z={gL{8MDDor3`Xg0U>#w~k#M}f7 zIYee-`MUSqt1%lwG(Xx)_#rXdzz+v9-! zV67!e12C0Wj<*;Xco~>f(vF4DIkDjFdh6`G%+*%CN|-WwUqA1wA$PtdFmgEruLBzC z1k^r?@UCROf<-H#KziuAJY3b9W&T=y#Q1GUgk<+OpX#363IzRazEHJjyfDhABg*0? zMoab^6d2r?*YZ<4Lgwm7h`~=<5*QoktAE0HcM44R+F$OQDd6H`*2x--#@28X7|UTx z#}dW@m}?W13~{kGY_Czj93}gS(S*|J`P76*&s1|V4+P)(_K1eii}1N@xm*KFsbpm(Wcn1CD6iCDDoepiFEKJ1 z4W4`iz7-zZ@Nm%9*I~xGnmjt8{O55P^=XSz%h)|RIJyQH6vnP=U_9;*n zOvAX1At}*hJs5_Rrvm*x^ZJW-D?2Y$ur5I$y8x3CX7!$eP%mf96kwgRq!17&5%psG z9TtE8lpu+2?kUg}u?<=oCun8h^9Be87WN-T23i?206uQeu>@@lkz6zK70MSfymM}6 zXS?gp>?~EL!V)n+hAB(N-p$BU6=Z^E)Zs|;uI~j0@B2`(xcS{iUm*qpK@M5DXpx8w zplpn;j8mJEX@lKeQDLWF8es0}&8StQ`uV7d1UvfR`Flo|su0@pU01PAvhj_la^!f- zG(#_zwLHFyxZWnQ?OOTSE()GV;@pto2*G+=A0qTj{J>u*-oBevruxySw1t|N`eq>3 z2>UZ>>34q0B?pJU+{3iqJ&&2Yj!J^w)GBFIaUhE}3+;X2i5Xi>Si}}if2%9k34@-| z(!0F;1(kA>xOePjf9@!QFFqt*R(4(5@3G_)+dr8OaPZNa6pu0AR28ZfFy-rm&L+{_1*m%2yu&`n3y;^GYgatcSytC#)h%- zYpbBhq(S4$I3Ox6pEf45(6bEs?2$HJ~8Q;*ZCt(g9$6S@ZgP>U&=FqJ3iG9WI!h64(^=paoUo{~E`KfAcleq9D?}h)7`whT3D>w9Z;jrK zk5arU-sJt%CJsP>73#{pbk%gLq~V!e${AP3h1Fa`imRxex#S<7-e}FON3cS8dZ%oY~D^t$$PF_ZltCHP#nc9N% z`(g~ao)t%d8XE9AsbB|`M3qI;YfY160{(6B%n&c#q zl&oaVJ-Xy$V4DU)$Jlb`DpjOTQq&xfl05|prs6vk;R{=IP%1ipopYSQtQDL^fmhMe zx7$~u+V#F%d%@dTwP|zCIcR>Bc2#Y)*^paa4pKlo`B=8HJz9ovS9lnI`vY}|)Y#QZ zm$`L$_^6O>`A6cQtFx5HXx){k(h)zM4kXO<*oSjR}`sJ6y=&^I; z!pgZjO>xH~+y!*JHrtq1cSKKt3Z5w1_i+`B(JQarTQ76ld*Z86Sbo%d=YvHm5}{te zFQjc178fv5q^iNvH6>Vb*9I@*Cffd+=xfl5faFL3J?x?rQ1>C_E1O6L5rtPFinjAIV@2}Z zw&WYJLN!I~fgHc-zVj6aTSOVC>~<)reQD0u8SN!mN5J^^azjHJ-{wapYB4qPb zVZBkhXu$Ajs{hX?(OV)X^l6>43ZK+Rk~(U-1WpX(zXr+~DB|KB6q48P+~Lm7txv0` z!7_)~(vvux0>(InSOrn;ICaFkGp(gWZpu`#9vlx6j_@BG4z(Yt+)gDhRtxc`m literal 0 HcmV?d00001 diff --git a/arriveico.png b/arriveico.png new file mode 100644 index 0000000000000000000000000000000000000000..ed9e56ecc2b0f0d1589a0f16fe0b5323b55c70db GIT binary patch literal 13469 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 literal 0 HcmV?d00001 diff --git a/main.py b/main.py new file mode 100644 index 0000000..9d83c2c --- /dev/null +++ b/main.py @@ -0,0 +1,291 @@ +#!/user/bin/env python3 +""" +JIRA SPRINT EXPORTER - MAIN.PY +- ENTRY POINT OF APPLICATION + +VERSION: 1.0 + +AUTHOR: D. RICE 18/03/2026 +© 2026 ARRIVE +""" + +# Imports +import sys +from PySide6.QtWidgets import (QApplication, QMainWindow, QTreeWidgetItem, QMessageBox) +from PySide6.QtGui import QIcon +import requests +from requests.auth import HTTPBasicAuth +import re +import csv +import os + +from ui_mainwindow import Ui_MainWindow + +# --- CONFIGURATION --- +JIRA_DOMAIN = "flowbird.atlassian.net" +EMAIL = "david.rice@arrive.com" +API_TOKEN = "ATATT3xFfGF0qWIGGGsvwb0oBnekYh88S8jr8XKbwvRkaoFWwq7cPGS2S5gzZkG-o_JXoDuYR5hOSGiL4GIj5XfTfm05mq313yxmkr8DZqVXDdbgwt8HNcxjLPmcWi9cQDy9wJ-Rc17uXIToYZSvZBCHyWiNZQhh5WlTkTwl0yjgGd-x_F-tOn8=F97E27B2" +BOARD_ID = 3417 # Replace with your board ID + +# Main window class +class MainWindow(QMainWindow): + def __init__(self): + # Initialise main window + super(MainWindow, self).__init__() + + # Finish main window initial setup + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + + # Window Setup + self.setFixedSize(1024, 768) + + # Add UI Logic + self.ui.connButton.setCheckable(True) + self.ui.connButton.clicked.connect(self.conn_button_press) + + # Fetch Data from JIRA + sprints = self.fetch_jira_sprints() + + # Populate Tree Widget + self.populate_tree(sprints) + + def fetch_jira_sprints(self): + auth = HTTPBasicAuth(EMAIL, API_TOKEN) + headers = {"Accept": "application/json"} + sprints_url = f"https://{JIRA_DOMAIN}/rest/agile/1.0/board/{BOARD_ID}/sprint" + + all_sprints = [] + start_at = 0 + max_results = 50 # Jira default limit per request + + print("Fetching Sprints from Jira...") + try: + while True: + params = {"startAt": start_at, "maxResults": max_results} + resp = requests.get(sprints_url, headers=headers, auth=auth, params=params) + resp.raise_for_status() + data = resp.json() + + all_sprints.extend(data.get("values", [])) + + if data.get("isLast", True): + break + + start_at += max_results + + print(f"FOUND {len(all_sprints)} SPRINTS") + return all_sprints + except Exception as e: + print(f"Error fetching Jira data: {e}") + return [] + + def populate_tree(self, sprints): + """Sorts sprints numerically and populates the Tree Widget.""" + year_parents = {} + + # Sort the sprints list before processing + # This regex looks for 'Sprint' followed by a number and converts it to an integer for sorting + def get_sprint_number(s): + match = re.search(r"Sprint\s(\d+)", s.get("name", "")) + return int(match.group(1)) if match else 0 + + sorted_sprints = sorted(sprints, key=get_sprint_number) + + # Process the sorted list + pattern = r"(.*)\s(W\d{2})_(W\d{2})_(\d{4})" + + for sprint in sorted_sprints: + s_name = sprint.get("name", "Unknown Sprint") + s_id = sprint.get("id", "N/A") + + match = re.search(pattern, s_name) + + if match: + prefix, w1, w2, year = match.groups() + + if year not in year_parents: + parent = QTreeWidgetItem(self.ui.tree) + parent.setText(0, year) + year_parents[year] = parent + + cleaned_name = f"{prefix.strip()} {w1}-{w2} (ID: {s_id})" + child = QTreeWidgetItem(year_parents[year]) + child.setText(0, cleaned_name) + else: + # Fallback for "HW Sprint 1" which might be missing the Wxx_Wxx part + fallback_key = "Other" + if fallback_key not in year_parents: + parent = QTreeWidgetItem(self.ui.tree) + parent.setText(0, fallback_key) + year_parents[fallback_key] = parent + + child = QTreeWidgetItem(year_parents[fallback_key]) + child.setText(0, f"{s_name} (ID: {s_id})") + + def conn_button_press(self): + # Get the currently selected item in the tree + selected_items = self.ui.tree.selectedItems() + + msg = QMessageBox() + msg.setIcon(QMessageBox.Icon.Warning) + + # Get the absolute path of the directory where the program resides + current_dir = os.path.dirname(os.path.abspath(__file__)) + + ico_path = os.path.join(current_dir, "arriveico.png") + msg.setWindowIcon(QIcon(ico_path)) + + msg_style = """ + QMessageBox { + background-image: url(""); + background-color: #FF80D4; + border: 1px solid #FF33BB; + border-radius: 10px; + font-weight: bold; + font: 10pt 'Optimism Sans'; + } + + /* The OK button font and style */ + QPushButton { + background-color: #FF80D4; + font-family: "Segoe UI", Arial; /* Set your font here */ + font: 10pt 'Optimism Sans'; + font-weight: bold; + } + """ + msg.setStyleSheet(msg_style) + + if not selected_items: + msg.setWindowTitle("SELECTION REQUIRED") + msg.setText("PLEASE SELECT A SPRINT FROM THE TREE FIRST") + msg.exec() + return + + item = selected_items[0] + + # Check if it's a child (sprint) and not a parent (year) + if item.parent() is None: + msg.setWindowTitle("INVALID SELECTION") + msg.setText("PLEASE SELECT A SPECIFIC SPRINT, NOT A YEAR CATEGORY") + msg.exec() + return + + # Extract ID from string "HW Sprint 2 W04-W05 (ID: 18932)" + text = item.text(0) + match = re.search(r"\(ID:\s(\d+)\)", text) + + if not match: + return + + sprint_id = match.group(1) + self.export_sprint_to_csv(sprint_id) + + def export_sprint_to_csv(self, sprint_id): + # Fetch issues for this specific sprint + issues_url = f"https://{JIRA_DOMAIN}/rest/agile/1.0/sprint/{sprint_id}/issue" + auth = HTTPBasicAuth(EMAIL, API_TOKEN) + + msg = QMessageBox() + msg.setIcon(QMessageBox.Icon.Warning) + + # Get the absolute path of the directory where the program resides + current_dir = os.path.dirname(os.path.abspath(__file__)) + + ico_path = os.path.join(current_dir, "arriveico.png") + msg.setWindowIcon(QIcon(ico_path)) + + msg_style = """ + QMessageBox { + background-image: url(""); + background-color: #FF80D4; + border: 1px solid #FF33BB; + border-radius: 10px; + font-weight: bold; + font: 10pt 'Optimism Sans'; + } + + /* The OK button font and style */ + QPushButton { + background-color: #FF80D4; + font-family: "Segoe UI", Arial; /* Set your font here */ + font: 10pt 'Optimism Sans'; + font-weight: bold; + } + """ + msg.setStyleSheet(msg_style) + + try: + resp = requests.get(issues_url, auth=auth) + resp.raise_for_status() + issues = resp.json().get("issues", []) + + if not issues: + msg.setWindowTitle("NO DATA") + msg.setText("THIS SPRINT CONTAINS NO ISSUES") + msg.exec() + #QMessageBox.information(self, "No Data", "This sprint contains no issues.") + return + + # Write to CSV + # Define your target folder + # Get the absolute path of the directory where the program resides + current_dir = os.path.dirname(os.path.abspath(__file__)) + + target_folder = os.path.join(current_dir, "EXPORT") + #target_folder = r"C:\Users\david.rice\Documents\Python\ARRIVE\EXPORTS" + + # Create the folder if it doesn't exist yet + if not os.path.exists(target_folder): + os.makedirs(target_folder) + + filename = f"SPRINT_{sprint_id}_EXPORT.csv" + + # Join them together into one path + full_path = os.path.join(target_folder, filename) + + with open(full_path, mode='w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + # Header row + writer.writerow(["Key", "Summary", "Labels", "Original Estimate (Hrs)", "Status", "Priority", "Assignee"]) + for issue in issues: + fields = issue.get("fields", {}) + + # Get Labels and join them into one string + labels_list = fields.get("labels", []) + label_text = ", ".join(labels_list) if labels_list else "NONE" + + # Get Original Estimate (Seconds -> Hours) + # Jira returns None if no estimate is set, so we default to 0 + seconds = fields.get("timeoriginalestimate") + estimate_hrs = (seconds / 3600) if seconds else 0 + + writer.writerow([ + issue.get("key"), + fields.get("summary"), + label_text, + f"{estimate_hrs:.2f}", + fields.get("status", {}).get("name"), + fields.get("priority", {}).get("name"), + fields.get("assignee", {}).get("displayName") if fields.get("assignee") else "UNASSIGNED" + ]) + + msg.setIcon(QMessageBox.Icon.Information) + msg.setWindowTitle("SUCCESS") + msg.setText(f"EXPORTED {len(issues)} ISSUES TO {filename}") + msg.exec() + + except Exception as e: + msg.setIcon(QMessageBox.Icon.Critical) + msg.setWindowTitle("ERROR") + msg.setText(f"FAILED TO EXPORT: {str(e)}") + msg.exec() + +# Run main +if __name__ == '__main__': + # Launch main window + app = QApplication(sys.argv) + app.setStyle('Fusion') + window = MainWindow() + window.show() + sys.exit(app.exec()) diff --git a/sprint_exporter.py b/sprint_exporter.py new file mode 100644 index 0000000..4d27be0 --- /dev/null +++ b/sprint_exporter.py @@ -0,0 +1,55 @@ +import requests +from requests.auth import HTTPBasicAuth + +# --- CONFIGURATION --- +JIRA_DOMAIN = "flowbird.atlassian.net" +EMAIL = "david.rice@arrive.com" +API_TOKEN = "ATATT3xFfGF0qWIGGGsvwb0oBnekYh88S8jr8XKbwvRkaoFWwq7cPGS2S5gzZkG-o_JXoDuYR5hOSGiL4GIj5XfTfm05mq313yxmkr8DZqVXDdbgwt8HNcxjLPmcWi9cQDy9wJ-Rc17uXIToYZSvZBCHyWiNZQhh5WlTkTwl0yjgGd-x_F-tOn8=F97E27B2" +BOARD_ID = 3417 # Replace with your board ID + +auth = HTTPBasicAuth(EMAIL, API_TOKEN) +headers = {"Accept": "application/json"} +# --- Get All Sprints for the Board --- +sprints_url = f"https://{JIRA_DOMAIN}/rest/agile/1.0/board/{BOARD_ID}/sprint" +sprints = [] +start_at = 0 +max_results = 50 + +while True: + params = {"startAt": start_at, "maxResults": max_results} + resp = requests.get(sprints_url, headers=headers, auth=auth, params=params) + resp.raise_for_status() + data = resp.json() + sprints.extend(data.get("values", [])) + + if data["isLast"]: + break + + start_at += max_results + +print(f"Found {len(sprints)} sprints.") + +# --- Loop Through Each Sprint and Get Issues --- +for sprint in sprints: + sprint_id = sprint["id"] + sprint_name = sprint["name"] + print(f"\nSprint: {sprint_name} (ID: {sprint_id})") + issues_url = f"https://{JIRA_DOMAIN}/rest/agile/1.0/sprint/{sprint_id}/issue" + start_at_issues = 0 + + while True: + params = {"startAt": start_at_issues, "maxResults": max_results} + issues_resp = requests.get(issues_url, headers=headers, auth=auth, params=params) + issues_resp.raise_for_status() + issues_data = issues_resp.json() + issues = issues_data.get("issues", []) + + for issue in issues: + key = issue["key"] + summary = issue["fields"]["summary"] + print(f" {key}: {summary}") + + if start_at_issues + max_results >= issues_data["total"]: + break + + start_at_issues += max_results \ No newline at end of file diff --git a/ui_mainwindow.py b/ui_mainwindow.py new file mode 100644 index 0000000..23a48f1 --- /dev/null +++ b/ui_mainwindow.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +import os +from PySide6.QtCore import (QCoreApplication, QMetaObject, QRect, Qt) +from PySide6.QtGui import (QFont, QIcon) +from PySide6.QtWidgets import (QFrame, QLabel, QPushButton, QWidget, QTreeWidget) + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + if not MainWindow.objectName(): + MainWindow.setObjectName(u"MainWindow") + + # Get the absolute path of the directory where the program resides + current_dir = os.path.dirname(os.path.abspath(__file__)) + + ico_path = os.path.join(current_dir, "arriveico.png") + + MainWindow.setToolButtonStyle(Qt.ToolButtonIconOnly) + MainWindow.setAnimated(True) + MainWindow.setDocumentMode(False) + MainWindow.setWindowIcon(QIcon(ico_path)) + fontmain = QFont() + fontmain.setFamilies([u"Optimism Sans"]) + fontmain.setPointSize(10) + fontmain.setBold(True) + + MainWindow.setFont(fontmain) + + image_path = os.path.join(current_dir, "appbackground.jpg") + + image_path_css = image_path.replace("\\", "/") + + # --- Define and Apply the Style Sheet --- + bg_style_sheet = f""" + QWidget {{ + background-image: url("{image_path_css}"); + background-repeat: no-repeat; + background-position: center; + }} + """ + + self.centralwidget = QWidget(MainWindow) + self.centralwidget.setObjectName(u"centralwidget") + self.centralwidget.setStyleSheet(bg_style_sheet) + self.header = QLabel(self.centralwidget) + self.header.setObjectName(u"header") + self.header.setGeometry(QRect(50, 35, 650, 50)) + font = QFont() + font.setFamilies([u"Optimism Sans"]) + font.setPointSize(24) + font.setBold(True) + + self.header.setFont(font) + self.header.setStyleSheet("color: #5F016F;") + self.header.setAlignment(Qt.AlignCenter) + + self.test_area = QFrame(self.centralwidget) + self.test_area.setObjectName(u"test_area") + self.test_area.setGeometry(QRect(50, 115, 924, 618)) + self.test_area.setFrameShape(QFrame.StyledPanel) + self.test_area.setFrameShadow(QFrame.Raised) + + button_style = """ + QPushButton { + background-color: #FF80D4; + border: 1px solid #FF33BB; + border-radius: 1px; + } + """ + + self.connButton = QPushButton(self.test_area) + self.connButton.setObjectName(u"connButton") + self.connButton.setGeometry(QRect(640, 25, 125, 25)) + self.connButton.setFont(fontmain) + self.connButton.setStyleSheet(button_style) + + text_label_style = """ + QLabel { + background-image: url(""); + background-color: #FF80D4; + border: 0px solid #FF33BB; + border-radius: 0px; + } + """ + + frame_style = """ + QFrame { + background-image: url(""); + background-color: #FF80D4; + border: 1px solid #FF33BB; + border-radius: 10px; + } + """ + + tree_style = """ + QHeaderView::section { + background-color: transparent; + border: none; + padding: 5px; + font-weight: bold; + font: 10pt 'Optimism Sans'; + } + """ + + self.test_area.setStyleSheet(frame_style) + + self.tree = QTreeWidget(self.test_area) + self.tree.header().setDefaultAlignment(Qt.AlignCenter) + self.tree.setGeometry(QRect(25, 25, 450, 565)) + self.tree.setStyleSheet(tree_style) + self.tree.setColumnCount(1) + self.tree.setFont(fontmain) + self.tree.header().setFont(fontmain) + self.tree.setHeaderLabels(["SPRINT"]) + + MainWindow.setCentralWidget(self.centralwidget) + self.header.raise_() + self.test_area.raise_() + + self.retranslateUi(MainWindow) + + QMetaObject.connectSlotsByName(MainWindow) + # setupUi + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"JIRA SPRINT EXPORTER V1.0", + None)) + self.header.setText(QCoreApplication.translate("MainWindow", u"JIRA SPRINT EXPORTER", None)) + self.connButton.setText(QCoreApplication.translate("MainWindow", u"GENERATE", None)) + + # retranslateUi + +