From b8429894566cc5fc38a85afcc8c0de0a61b577c4 Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Thu, 7 May 2020 22:22:38 +0200 Subject: [PATCH] feat: total upgrade --- .../core/src/layout/InfoCard/InfoCard.tsx | 4 +- plugins/circleci/src/{components => }/App.tsx | 4 +- plugins/circleci/src/api/index.ts | 3 +- .../assets/circle-logo-horizontal-black.png | Bin 0 -> 13927 bytes .../assets/circle-logo-horizontal-white.png | Bin 0 -> 15625 bytes .../src/components/CircleCIFetch/index.ts | 1 - .../circleci/src/components/Layout/Layout.tsx | 4 +- .../components/PluginHeader/PluginHeader.tsx | 51 +++++--- .../src/pages/BuildsPage/BuildsPage.tsx | 29 +---- .../BuildsPage/lib/Builds/Builds.tsx} | 7 +- .../src/pages/BuildsPage/lib/Builds/index.ts | 1 + .../BuildsPage/lib}/CITable/CITable.tsx | 22 +++- .../BuildsPage/lib}/CITable/index.ts | 0 .../DetailedViewPage/DetailedViewPage.tsx | 115 ++++++++++++------ .../lib}/ActionOutput/ActionOutput.tsx | 36 ++++-- .../lib}/ActionOutput/index.ts | 0 .../src/pages/SettingsPage/SettingsPage.tsx | 13 +- plugins/circleci/src/plugin.ts | 2 +- .../src/state/models/buildWithSteps.ts | 77 ++++++++++++ plugins/circleci/src/state/models/index.ts | 3 + plugins/circleci/src/utils/index.ts | 6 +- 21 files changed, 269 insertions(+), 109 deletions(-) rename plugins/circleci/src/{components => }/App.tsx (84%) create mode 100644 plugins/circleci/src/assets/circle-logo-horizontal-black.png create mode 100644 plugins/circleci/src/assets/circle-logo-horizontal-white.png delete mode 100644 plugins/circleci/src/components/CircleCIFetch/index.ts rename plugins/circleci/src/{components/CircleCIFetch/CirleCIFetch.tsx => pages/BuildsPage/lib/Builds/Builds.tsx} (93%) create mode 100644 plugins/circleci/src/pages/BuildsPage/lib/Builds/index.ts rename plugins/circleci/src/{components => pages/BuildsPage/lib}/CITable/CITable.tsx (89%) rename plugins/circleci/src/{components => pages/BuildsPage/lib}/CITable/index.ts (100%) rename plugins/circleci/src/{components => pages/DetailedViewPage/lib}/ActionOutput/ActionOutput.tsx (60%) rename plugins/circleci/src/{components => pages/DetailedViewPage/lib}/ActionOutput/index.ts (100%) create mode 100644 plugins/circleci/src/state/models/buildWithSteps.ts diff --git a/packages/core/src/layout/InfoCard/InfoCard.tsx b/packages/core/src/layout/InfoCard/InfoCard.tsx index 7077167828..42c5401de5 100644 --- a/packages/core/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core/src/layout/InfoCard/InfoCard.tsx @@ -126,6 +126,7 @@ type Props = { actions?: ReactNode; cardClassName?: string; actionsTopRight?: ReactNode; + className?: string; }; const InfoCard: FC = ({ @@ -142,6 +143,7 @@ const InfoCard: FC = ({ actions, cardClassName, actionsTopRight, + className, }) => { const classes = useStyles(); @@ -169,7 +171,7 @@ const InfoCard: FC = ({ } return ( - + {title && ( <> diff --git a/plugins/circleci/src/components/App.tsx b/plugins/circleci/src/App.tsx similarity index 84% rename from plugins/circleci/src/components/App.tsx rename to plugins/circleci/src/App.tsx index 799016130f..ec802a88b3 100644 --- a/plugins/circleci/src/components/App.tsx +++ b/plugins/circleci/src/App.tsx @@ -1,7 +1,7 @@ import { Route, Switch } from 'react-router'; import React from 'react'; import { BuildsPage } from 'pages/BuildsPage'; -// import { DetailedViewPage } from 'pages/DetailedViewPage'; +import { DetailedViewPage } from 'pages/DetailedViewPage'; import { SettingsPage } from 'pages/SettingsPage'; import { Provider, useDispatch } from 'react-redux'; @@ -23,9 +23,9 @@ export const App = () => { + - {/* */} ); }; diff --git a/plugins/circleci/src/api/index.ts b/plugins/circleci/src/api/index.ts index f7645ab760..c44988509f 100644 --- a/plugins/circleci/src/api/index.ts +++ b/plugins/circleci/src/api/index.ts @@ -57,9 +57,10 @@ export class CircleCIApi { } async getBuild(buildNumber: number, options: CircleCIOptions) { + console.log({ buildNumber, options }); return getFullBuild(options.token, buildNumber, { circleHost: this.apiUrl, - ...options, + ...options.vcs, }); } } diff --git a/plugins/circleci/src/assets/circle-logo-horizontal-black.png b/plugins/circleci/src/assets/circle-logo-horizontal-black.png new file mode 100644 index 0000000000000000000000000000000000000000..419d97e69bae509d0575c55f3714bba6a60f3829 GIT binary patch literal 13927 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

jPO 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�$Tc zcWBt5TAg}$L#Rw<5L9Iz1BF#JbCn~2k$PzyK-e$dP&J`o*NRu4k#SjgBs?)!2}o=g zSCF}6BGt4fqz3uK?02?!5^dU$=J|TpJdx37tuS}xcd0vvPjcvTXH>w-Tg??Itht0v zR0_B|`$Hoh2BJ!Z{K$3!m*|a>(C^>B)M}Zi{NTEF#A%dnHPU~AF)G(Ne(-D3ymBK`mT7Wo*=BX>|+Wy5SMdodP0G#V!-Uh zJLPu2bUsj$P`VhfCSS4kl=Xm{J;t}8ZpJvEXGtTK4hrMEvJUl4pnO3y$I7A!4%40hxNg^s+btSLb5cd z7NT>kkHt%Ay3Ku-a9aGefBPzJPql^|m4oZB_Jo>?opf&X$7jOtP*}vKqdtU-$p!Zh z)^2(qk*^UPxnBm+X!&IpYh+QWwEyiDe>&0d5&^f6wM!@xb#VnnY;bOn9y4zyI~_DB z1FvTEz+U41?wiJ%NB!0>TKEjXUatq6GCTBR*97bPFQ<3?KI`kpCkfXofR&-Ta)%Su zkHg68q+I{XT0C1dxnQziZhp|b=^M4b3gEeT>a$Pe8=Dtsl)^j3S-q7Ok>;lGS|UNO zEaHb?5h1or^|#dvNB~A48WEH{??rhu>vJ-jiz^ttVQ8cZ{0R1rF@qC$AC5a2Ko}P#`M(YNLo%S z&9ZbO$@9A23&VGnGE2iYdzI6T?Aac{j#+gS0v0=evD@_$DlISSMH-d(`@Mxmm;D_o za-kE0?4{4qkFVuetWlNb@n(AP6=G7n0$uYEK6a3Kp#S zq(?(Gc6LZZ5czXxYhhYC+aDtLSAW@;Zns3~{92T6)_Lia_bvZD9b2#OQ4~~X!P%%E zhRF$8e>fWyH;OX7zUk|AgUo)_z^;n)FjUJa@u0t2jf@QqqLb$bCBbqHnr&*YT<1UgV0{1g{lkt*RDsmEzIfOxOaLY=zr>jS0>{x`0Pf2! zNo}ypQu`yKpQKL+=>C=ndOHI!*`uXsH%kyl;{aS;{4}p51V!V~FaQ_KZRQn+5PQ26 z{fMYdw|SY-Y_IPN4))8JIs=kjFH8VY1fKx~*J{C++sa(hK)~Y+>Oq0Y&MJpdu9d}CFyksUXz)= zAF;go1GM!6eAY@~sBP2V>CIYx1b7m3Au5GjEQ+-C003AFu1$rs^f%33XUPISv&4C9 zMSKtu2eje!zL=(Q+XDcIdwq1yPknF4oJE8IFQ1gXk<0HRU>0uWGkgwUjA_DHe^MFw zv_H5D3lPF!eaGsP=>SrI4fa2tX3*W4{4R4o1qg(bq{~fr3FWR&U-eX8jraiom3WA=jWnIpvf~V}M3rC* z#v{OFG(xLZurg3%*!0O<>+Y()JqiQ>_!1ekfDj|GEw_-PnZ@&rI(L?%+tAmIfo4bZ pc^9S*W-^2+IREdx5V6F6%P>2|m(LfZ>i$pfloZt!%H-dF{U2wwms<-^EfT`Agw!J4CC$>Q(j_6W2uQfp5=%&jq~yA!bV@DV zu*6+||NC&?@56n$5Br(Vp6{7CGiPSboQ&4ePc_hB_U1*ygztA^6{4YY;9Gwh4uE znd4&H&m#DMf8c6ZC_c84pZx!w|L+)vF(j}0%g+omlCwfDvM{rgy4QB?waD z(NW-e%)i}|ZkLk--pS5h?N1uzmYr~o{M$x%C|XFB+8ZMg7nP!(;GE(DJ6nJEJ)|8n zgYB_Y)^_8dIK;AYL3`JpH9jSq_#wuS5DYQ??VRVB7R*R-Av4oC$xTs*#Vu~mZI6l) z3%D?u+VU}3yZugC)$8{H`9oHP<+ftV-Qf4T@DEft@>wJr-WPWP+7CZhDk)y0e(tx% z*|JVaPVrTQiV)Y>M?4u>@#}ifw=M}SJyfEQETYCSCy3-CnduvS%EY{)o`Pp>MMpZA zfVs}1z@$rvhT`crYShaTKv$pbeqH$S4IhW!yX?U_>n(X7r`Lgu^ltCcXQA6ghZOhEyfm{c@qtebk0qD+Ust-}r?N_A zH*A)JCx%YM-$Ly=0DGcq_I0yNmED5RGnKj*O-`tVUv_+JT#~zYdjpRF6N3FD<6!0x zDoCZnMBQKmk1g)n$zyR~f2WS zF`QpgddomsDWXEPps4ev<;@1cHS(TTD5|bRh{DT8zjm9vaLShmm63|S zp{{(lad?I>Dopp*8YQD*a($3V| zc|(!ctAyZrAB{8=ptQ_QgxEEII>;hvgs+45k^I!70o9BLPjIwibo$Qu@!@-4mAWOH zvgkV9i1VlG4oX9D?ycQ?-Rv^fxrakxp)YUdTgAprQPtGARKdK0{N=eUa<;(m1(+AA;KkSkl^ z%88;c_d&uEI5+|Pe{5~B7!cXGrj-}F_JC|$Q7wEG?j=qEXYVs~E@xHt6m%dj1Iz-<5zh!{sc%x~w8(_CgAkX=j;`bpHH z%J7DW>mKRw<`6g86YL0UKR&P011fbp{Gm$1IuA&M@ukEQU{NAd6k4r%i6LnAe{$Eu z=dXB(Ixf18xFGY&X5cwyRD(+zi%~Zl)v&YWYGUt#6UuB%rgfZZ_AlVt>}Vb@ zspX{_cS4OVYV#A|aK7H-VLs*J@*UI24)BOznJ)?Orp5;!V!u9~Id?zsK~Nl*loN_=c)klcVKP z4-Zc0Xts^Fv?08#G<3p?Zb$fy1_k1q1~m#EdF~L|ZAlknfP?eh==&Tm!8E+=YAl`4 z%(0C#Ezeh}Z9%#1I5*4%^fsSAHmX2{EXL*vF7iy*nQGYMe!xDT{kzbg#!5AZQ=fyQ z)lEO&8XtyNJ?`N-I&(+A!{4q>XimyVImyIIU65+GBty84-@J8b{wlK_I?ScM#YavZB>ugG9QsgfA z)5i9hMz6>9JD@7vQd4sHS2UzWQ0)8rO|l*p*S+m;agMf+v7wd_RRH0`{pQNK?z6pv zVI?Sze9#E<%G94bdpJn3C2`27w&HuJ$dewP0iUPCc)>CMk#WR)a?0uyj)XYRGsBhUa2%;0^ki#`d1 z4*j^j)Ta-wjnB#7ba--r7kPNi_Cg=}YbpF`4(bcHm`x3-8>r zyw%VF0Guhd+1XxFe_`kQ4w0lr%>McSQq!fr*HboDj(qiMmp3hn%_^3SEejT~CLBTA zQ=RzX=i3oZCW@|_`j|jb7)C_US6es@Z;};%>E4w1mu6MfFL^7c8AV92x5<2AVH2#o zsZe7>@JwvfFJ)z-DN_>(rbPifT#({kd(F)$g!cB0edi>s%vv`rKQd+W}H)~ zTn#-#j^%V@A*nzzg|ZHpz9Ltk3hN?IDp z$k?gGI!!QxPx#~vTmcu&Q>pCs7O8MZ_@h-do3uAmk&k6%G0K;El?mccHava+i8+nr z4SNMa-KG$yT%DOBMAy%)`g`;-;}pPk4LMEWMBBB#MS#{6YNpCE2Etb#Asa5%1o_xA63agRh0A%iDd3HAwj49cLSF zOxO9)n8=a9_T2O}Yflu!_r*4Cy4mBq*RtUHQ6ey~=xH1J`OX`0nBw1k{?Xdjio2-w z_zPoCHzc#kGeU#KL^dA8TA7BH5d}5Set4zA4N@qv!ig2TEY%(Z1g&2$C7FyP(@7_| z8bfWipI`rxdZdEDz7%LaZ9nl67ZvS>-b^K(hEfZ<9$tjv$cY=EkqlVMM-0#BI^Z@A zDq~23kj{fJ>&rB4pGjmlpYNF}M=gKx)w>uTI^boxFm{3h03xs4HO4+(V3nS?8#SQP zCoGj?eoYS&M0&*5E&Ed#iqQ64X}U)AT1!E5Sb^1gWq$MbUZ)=xMP{!gRGIn}cefitJ#bg(AC+Y?@C7jpnMVgd`vB5U42Eh*qg3S z{U;L3Q^mVhH;sAn z{P>j<6*1b1hF%A55I(obTUB(Knmnr%XV*Pp<+AGHxcnWsq)G+-cAn6{rRB%=a&nWC z}`a6M}&-5Y7FqSMm!6d0UQ z23Mg1%ECNXm7EyYwQuQCrVe+3o2seJzkH-@(9VDTHiUyhddgZ_rehc=n9uRjPzH&Z zr;zvt{~+`@Fa;at`(*GZF;Yu^|XQoXRp@i zfciNlA2U_jddji_&igBr_Gbw^?)qCXtbHFQ`()M`zS~p3pQ7&1V%C2Ze0B5}I3jjE z^W@GXy+p@;d}1v5?yICyBby%i@Nd^6>XNt|!h&XdcsQs0oZ;32Stfa@s8#oLaBpL6vi!+J6zMdFuZ46;rFFW8`+zYN!OTHe{D z5;*rI*MyP(!64RNOD0;XW+PUaQ>+@9fFlG6YzAAV*@YT*ynb#VqUMcMk|W`esO8JS z#?U%9|F%O>b%SpnkMn8q^JDb38B89-I7fRLP)kYL!aq zYElr}dN(MMHs|xqhc7XL*P1~kN9i!=RBWLE6zffDs%XtKg*Q1@a>{pmJ7!z%{|KJ+4%Nbq6>EnOM{c&XDmD)Qm>Ap*VbFAP7?IOo#faOO$x$msMe;*3xe6`jx7DiQ^(Iir_fi8FMW68ZBg<~KMCIEB{U znOnuPrPwROV%S#Qsk$irfYEH46I9i=s@UW@i24w4Xuef{|OxqTRv~XHO0sKkd%2PqY^%Ko%DoIFyg+Z(c$YCh@zI^I&6f zGHG9*&;WbKC6GUbnQtk8XD-25TmOGVPYD%};R&(!Juxsmwo!<1JGt|mcKJi7{Z_oDwx zHD)KUBUv1^tk;hS)ob?SG(B~ujREhqZ%EKDS)2vLkko(qamfu8GY`_Z$1RapfntX$ z*r}q4pO69<^cjJ?52DyOEa=o!@jrNEhr3@L}STZ7bL8=ZeT6o*^VlRbwdud%@h`F*+ zoGc^Pn(790?odQgDU7+Bg9X$fnbU|v7AnSqs(Ja-PU_Ire;>6m0c~Y^lkfSNp5cEG zO`CH(yK|UIMDxh;Ius5HZ$l-089=DJ+alN#MEAktHT{lKiA&Kdwp?PRVJY$v6THNC z+itd=i)o zR#`B8u;MH%gcGcm>(E5J7eYK1f>j0DLvr_uvPgf#2lC&OpsX@A@Fy14K7U6cAzSFA z!OJ$8$e|b1@<_jBpmMeGjVhfV+w^yDacY^K?$Z&-A9!#uBav{a6YMd5o5HC|JOGq0C^uhLurhW2|Z!}+wk zzf}O4PJ0|%HBqPRH|54w7c_*ZbW~imr22V>5&<$6;#lS&+yg(?zEw}ZcezwWQ58W# zztH{HfpnGr>T0|&$u(Q)9DTxN`gi_Nn#gtDL+eUXB*a^s`P6~(Y_yWxq{6AuQTg6T!71!VP4WGHsju{K(^MFOw#jXua_xvD?-6m=%~ zl~0qiIhe<|vo?Y*C!0<4G*eM7Oa5yNn^e!TtYx&=g`6ybi6+81wk5`ZZ!k%-0Fxxl zk+cY$==!ifKWI;Aa7+{I`OU_ku`S+1#k!w>Ao(?}6CGqvw&9`E+cg(Thz6T9v1>%6 z@0->{+Y3=X&0;$lRs~OxOz~J}Zgx<2@?zJ>cQ&t)o@9&t6s$PgT`4Vc60c`|6g z-PF|HD=>!(=XYvbc9|Zs%bvYSnYAJn=ww6{9E2_fU@7>!$rtjlO^SYg}|; zwP^d}*BGEDryAA7t!dADnUPm-gMDo=q;x5S=Pwj7AUvePPA`-S~= zMw5iLqt4>yKVyI2ho}6qD%Izj8UdZO%VXMVbs9qWHZz{6Z%rei@&~goXmed|T3jLf z8#Kv(5-ClJRJaVm2Epw)%Jr_ZvY$S9r*G^rpJ9^MY7}j%Hr96^wtWP@g&7hc)^sxb z1r=4?9wS&)&|Wfk)9#G2)=Z9!HtHCL!4c@jmQ?ERb4hX}=JcO?EJ8MdkFxH(h_gOa zL>2c?82Vwt^TP#1c+6j)>k&YtxT2s!gl9oH)7ABCiyNx6+gzo3{>8cp^G9TQ?52IM z>f^N!HS$6s;aEksPaG!fb9vpMinKu##(_O1mK(8FvGjP`T@0I&k&=&)Q^ZJS)3pmm zFl}Yxhl3+#uE@oE+HPQL=AZw;Fs!8zW^C zCyQZL%k0LJZy!~%6AR6M|MlDxJX$az^k~rRB?Ef+P|jEEit3UIp08|xV-04f^!7HM zr$c5+KVvTo?eKV$0FMLe*H{COF+@gikMky5FI8x!2$t=XP~c*sAp-Wj?kGSGBf4jK zt)jKr;(mIj!+aC6s?g@SRH*q6dwusPA9>{XH$}TsSN=fbyYd82*8J6D?-NKo-0StA zwSjwj(hM8Twddb)Wpkh&OYpw<30AI`DqpkJcX+?!ap*0T)VVsMlKk0+7AUJ^$kM$0 z!i36_+9Jj|*F|b%1H((^WoSHakYhS{+8WnF&^k1Kd-KbIy3-r~&M?kuHyg5DSV1H{ zX-xGifZB(sXJf{t9z}7rpi9Ow<5VE3Z1&SFu8j;>JP0pX3?PT0NtUHg(M_8MLjl3E zLqaYaiv?}zG_a;3z#rQ*pcGg{~cLqf``3iFICJRR~ z`DGeKD1(FA5hQql(}xl$3rwbs=2dCPkQ6r#aiJZK-5QO(->5;<3_rPQ0|iZm2+FkS zxEBRf3cs3IPui&PCrCPP0+$ur37pgUFn?9{DIk>@|CWQXW>4!#@j&su@7ydy^d7dw zs5NRZ0UVtF@)|33W7HFeCBZUS$slEMk#TtV*R;FTCUr?Q{9M_9BsYt{iGuN%E3VC7 z4Pe-)OA~Dv=?AT=jHpSlEwopi8qx~cXxu5CR?kDa^Z!&8Myrjzz=um_D=+kv>5nME zZIq^f&SAdm(6CqJo`}L6(R`Ok)ciZqy_CcCfl8vp<>rP*Xr>?QUt}<<6jj?oFX1df zPlt_a=VCYww2>~K>Od?zj^opUp?ze>2*8_Jn?7TJ1)ynr(K8 zZgMlcL2^UuI!>~<3qtjBZ63GJE_pK8K&z$%8QwEzE&v5vse->jGiLMvm-H~DWm!M9 z3ZU#w0$J>R391)6rI+PO5q+$S`x$upL1LHgd40E%M@BTRdi@ZgBy}JxZz*BqY18&K zSS8Udw?UI|bfPf>5;#}$c-CXtY~BsS)0bfOwh+1lba`5hWZBzjDqgZRYoXLJtVDONx^&Cs0R7?~f)aNAOZLq)5pHo9m zxeA_oREw}A{Xt8)OfCk?he$)OdVa{_NdB}Td;0d7UR-;qND1J@s6gax{FbaloXaGX z0{GdeBikX;U7()LAWh@Ez(s}wZBA&xu+DPDyUPhT98npSxB!= z;C`>&LDxP;8eCKm*EZFZj4qB)^voZ7v|T=fB_$Vn3Z@Ez-6u~eC|B@bHC;Bw;yXQu^-wM#Fu4pk#CB#@g^G>l!IfR?hOJ8 ztQx&b;yVjJE;dfWwdR9Hm42Gx#v&ws-`^=Nds6CwwwjZia8to`nzLoObx}z|5{ohd z#zzGR&ihxJIL-Y=_}R%pIGQVe@#hLcMO>_ya%2YA#9d5)ZB}dyo)6Fa3z}#y;pANz z>cY4If@f^JYe{a2So*`P@~y>rR@z;3U=ww_&%U7;Yo@pkybFD5J|<1Ut6)>p4jDr5 zs_0q%M|^o)oKS(IBfLE@2Ag)U>D8&Fo)~(CCE}~x z3FQ2jh!B)%kLOFK^KhB*|I9}?u_=GFqzXSLI>#b;*})#+WYb&>yd&>O`Qy&;9|1I$ zz;P$;?`T|{Vr)36A-=W4%8nQt0w4Q>!BbxB{5P#1N58j~p1Afp(d_=%TJq8O^g^EF zf%(3yS8A@Q?FmDCTX+CPRqyV=`540O<&!H0j?SmLMX_5LNdeyl1uJsU;WqrkKKEId zjGWU4ZKL>$=6OZg*ia2)#|F{R!dOyVW%Ff5zd2V@_dLHOl}=yUFp=B)7I31*N4;I2)rpEZfs{nKmmPb3Z6fg&~rf=718r^F|3L0e_Doobz9c(xHiiH=>{3pV+eW zPfd{6BR_uxfjZzdVD~9_5PPn&v^wZs=Nk_Cq-wo3nPj*g`N*H2;x6zu$+%olDkoD1 z&j%}G-aM6!-;sJLqsz&fnWptRXwveLbZKn5vP#}>VB6&qFk6xhdpgR9H?YS)Yp6~0 z2}4Yl+FpvDO1lsH{jh{aFkb5%2Vv`)zjNDo5}I!BPk2?1{IvwLY2s4zR~1uNWk=s{ zaGoXu7t6cYBea)-iA#asw8!%_`TfQohdfJPY%C1_9J3 z3vB|Q(P0U?f1DRd7Zo_*1hT0aI zCS#I|wHGuBrsEGLOD=mYgq6^FVj4}V8>(%K0y+=Th=i!~@Q}!wAv~X#s&7GIV1*|; zU8P+bkK=l52-?TIczQE1j8S(a19`p^y*YZq_#%>pp?c{`!mllXCtOF*$Yjgbnheoi zd+&L#<@K6k*n~i_%wcxCJ-r7e823Pox^(}hF9lt|H>|`Rv!7h&?1u;!RC>H0q=T~o zq@0RwaU|E9eP5<6W%9@sq|@XWg=}$B*zFrx`>N-l7<4*Hkxf+u7u_civtdI=8lp>u{Bz`K+b5*j6W0H5=1i2WvXL4mZHtDFIWd0-()7Ti!W799Ls~bo zYO2(oMI5}Z+SF#iD5nM5Xek2=K5=s9Pmo@P70{mmC<3NsBH9NJ8Z<`hOlD7mX#A-6 zDXRPrJe8?ena6MtWmxlt5j~U==^*SGz4+LdexK-JcjUc#?Zrv$qF7?&F|+y|F2Y(a z`?!%^Icez?R?2&VhKODY?d9H*4ilmDlr~nSq1mmTUMBACB?gV^FNW%xT1hxi6jAJ% zOdHN>pF-kqBL_{DyZN+xjXupp6jbJ8RwQo`MHCw+nRL!aBcfR28DU*;~FVf=5+|>houMGB~gL&ZDUI?krmzy!K>oMPj>s53^RzZ;V#bsH4>CuJn zmx}p{^_kov%uny;dFUQjN%IpW6H`WD)4Rf^cx;5KFaumYiSny@^Lt{%l3+_#duMl{ z?MlLcwx<>%TtnuOBY?GPZCL`ShFI=a^gf{sTIrgw4>P$_Sw}Vxg#-Nw6>=84=CPaaz})hkdfleY9b-jl)H8U>r+q)vtYZ15<^hmzj14 zo7qOOE%K2C#_UZt_+`%+tJFLzDoJ#!>RZelTh|!;71E!DphT=|D<5vN!$T&@a@ztg z1y2M5OW?zNUHN6-+4V&zt6VzWFq6AE?j~B9bBVJ%st_vD5TC<;zFj9j zc1GC7n{>q5)WJ$#A^3>v><+IW6+C)=STD*|uPr`TsoJF36y@qF#7&$!O8rOC5KR<< zWNvzv)aYtL{mvjs^H4ZengwftI2v)z5L^G1h`mxa<0_up^2hAj{CQCl+g(&vl3SV0 zXS$>h;1a#Xs>BOe%h7whLhAa4&AC+f-N+w3_h3_P*#;!~I+xQ^*G0;|Ep=rA_6=<* z?7?Y1@kjgY0KoH^mm9>~2n35VaL^G(V1&>w4z*dI_u1VgU0YUau&iX;(wJbi$PAa} z{bPu6zmJLcSCYNiTNHck1jZH|OflK&|C-Q;E~NvA$o? z;>~Et>Z4c&m_q{r(%@;EQax^Hx^7;kZQionQ;7p%SePCLJ!$YlJOu;ck1- zb$M?PdT4mxJ!~$IX4^~YGyc`(BN!hXBheVIwa>ydSO3wT>b03taQLi}F#ZoRj8SeU z(eSI5fi-b`${($M7ndAWbvaC9(`^$|M)iqYTx&MjKP!p^!01qu@&dzyxdOF|4dt_?+N2TTE#ez(}fHN(lUrlPxb5o~_2{T42Lhi+U8Y_5lNO!$%xL;~z{uDQO;pC`=T)uvBeBbZ?gRmFMSX z4^Sd<_W0}cm|}lyNXwK}2393H-B~GKi=FmtAnqLM#>VvxP&;uKyaQ%Ne;&?+wGM28 z9T`s%Crd-UtlK#=jUJD#fBSjKmA~o}YA3e)gsR?|V!rbG$d|T84P1vxHUD_q?Qfjh zQ$_}}ds&c+=VWH8qBC?Q2DKa$%>HO7-C8UXZ~L@^dCz+T!vS^pU^P>61c+;+8%NSI z3@VK`ntNlGj3bl`JblH_MF5f;1cHfj4YzxURf*UZ@8uFimme)`aukF;RwX=Xfy8_< zIat0!-9t`NW9!eON4BQeQOFs$a-|m=O8_X|Fb(E=A4zx5;QmQ=0#6e)@9CF_^x21S zP|rr^eh<}D0@l|Y~jR6rWr@G zDXrNCm5X5oRvfrR2z0J$c6n%%DkOvEzM4*D;ovxztvk)aI!Rjwc6=Q}x^5z$S@GLx z??3FA9JM*Hj%6g@_QtG++a@|}8-G0steAr45a-GSwYEq_MYVT&{n-VN+HE+IDxbA< zJ0lwp&)&dih6{4%AME#aC#SZ3lhQmlmCcC6`n@H8gPxzP7u^w@c_3i`RekM%DFFb)sa$i^D9tN01wMno;2 z>`S)18Bun@hwBwv8oAZoDV`PBMj@BW74{xM6on=(xVQa<0yD_A+Wj>OLFajV)c3lR z#kkVOQ=RCQbsEskI&F`1#Lg{9Z{Fuu?;?v`-Op8Ho&@dpns_AnP@aO=;L$WWnFh3s zW1|k*-P`@bEu`~%@`r(T_gKeOP%X5kx}SBC57Y`-peqwIY$H)p8!9M(d`t1SpjohB z_gma!eux{>OcHNUWqLqz4!($&IWm9ow4zt{ISq5f*kaHNrlBZ2{Wc0jlcueK_Ibj` z+31fLm->jq#)IpvlkSQk^;|#-sICLtNynD+IXyY_nldD@EkXk zRLyFv;?enryx*a8ulAZue3KaKvjWYlH<#E*KwW~ZBzX+jM3(c8xTh?wqa{E=hH2Ba zz5v0ioT`f)^IV#j54n>Bcz{f@j}g7eXXTe#&uWch*s}iuavxmHa*!Un#@XHX!c=eO zZu)qpI`3OaD=k1W97WlWeyX<)oHVGTo5rwDAk@+|+setbaq(=IIl|;_38Ge-j_)J8 z!F7oa2!c94L!>>H++e~6o2+8kUdOXX+fRBHWv_@agaC-S5e2(?3KHrhxIpV zJ|$&E9zvG4?K`X-VhxfhBfP1)5)~H{*vJuzeH^cA!Z3sc+NLj@$b7=f5E00HaAM` zKB59Y;jt(#q>+HMs|0mbn#GHj$mISqnhoY;6SjH-lzQxv6E-Z{8BD%yxK=|xthzlu z56h5TOHEUqql~_dhkg4xDCyt7Yz{(pLn67 zfLy6)z!)fF1D*xhnq4EGz9*4TEfw*ZwXFpP#@a7X9NZ9eNbk5Dv)G~FVPVhb>$}FI zB}PszWQ$g@o=n$FVx443pz2%_W+{>(zP;JEm)h-Xy2U(#DYGy@cnYL)6~&gK`|v5S zmmGNJ%y`L5>Rtm$gy%OqsE!4kt0o8(yfmER?1aWoIcxttl3mqy!e>NdZGJUT=RvZdA=k-#Q|_Ud;vvlN*q z4_ZA{_0_h#yxqSNf-U=BBdpVc)1Cr7{9fp)0k%4&2f!O%DSJtJO_~)yf@MV*>0!CE z$hnVrZRddPuh8#NY)o@K|C5wuW7-kJPlzt*iK^kJ<9RCuP(anBS~o`|Y$&~8s48Ne zb7mHeVBLAWRwLuVl|90nfQ?9PA*@9{*v-;dscIm{-dd^}tP3Oz`@5{T6i!;RN^}FZ zO%2|V@|a0bS2AN0zYa@7W4EQ@;ink|_UrX#ime5XGi$-WoNIt;acR&zYJ;sAK+uy< zA5RXXX|*GdhyJQsAh7;CYg(YR<}pHjqwplS3pE*(?<+#wSnl*@H7L$UYzXDA_v?vk zQEibQ!|=+lSWYXc#h{GBPk(dyZ4OGPpz@<{1sF8cMADekzzXSUNmHGBB04R}B#Iu+?P5X+y&bE4NHtLd88!CpqgX?nvS+357gcFWK7$2|Fp3>u~rdUmj`{a2Zz= z7#7~v=`8x@KcW=Ujg|3n=q`S<#lbc^`*J1LM^#ica$(NELBH#cem zf%i=*y;}yW_PS*0m1>L@+fllCNaoxm!^u*?IEWg@~9kcPZxOzLa zC5y=U9LV2C)AsOLNBkr*oblXH*p-gtYv)LIAu`ce_TY{s@xE0aZOre4Z{i>l6V=nP zAT=bu(=++wP6dEpCdpPNS6bX6Hzgqz>b>`;6?vD-B>;tCF4|K#Tu&gd+mtfUDr*p$ zn6e-E>m#O6ni>`NHjpVINp4*bdg{Q+bFd2TTt%#Ek}38!qyw)SnKKm((tY#!prmZYE;z{ertOtq#KN~V;-*T-5(5s`RT3PN$rB{}H8 z;FMzZe7|ViD7NNaGA9$%J0+c=Ymps&8h5b)KPtkO*Ugk&?bXq?*3iHzh40i=lagBp zCdUV9>KmS4vq8~>QEZlvOx`I8OREj%}o_N^S~nMt7Cb}k zA4oo&nsZ!S)*z4=l^Adx)b2fwE0GxG6R^V%dQHkxs*7)zC6AQH=(I&ck)X~_K+Kz7j zl;l*K@tetWVedtWT9ex5LGj$!L*k%7<=ORa6=+HQGl|F8Glu*!kzmx;Ll7(WlFl? zRp#pEI5v$?ug*s}FE!yeU==*-?cGfl!9VZ6kYnv1mpdk8fKrpqaFX>Ug;gBgx8gBp zfDzsODfup#JNEHWaH?^=T%@m2VCKrP-jzcsUg|U8j4yLqN4@R&UP@c+18ibdR6fq) zAVJZ+(@eg7lPpVdq~H%c{Q#?qPJi(DD!a)!BaXD#&m3i)i`?&bJJuvqEBItR z+^+HjttmudJckxk^=jzHUS^jJ1xba@p3j%yiXXSwgl`fTvx1{n)uO$4`kzj#iX-v$ zx2konDC#TZ|H;Gv`#4 znRNBEd0+587CQY5n`oB57dK%uH>^W|Eql|(0(kN>b%H6WAa>Ff1ze_vh2&T`cT4G# z6gG(^WYtRuFUww(OREK2HTR9KlKV2pz@q&5z|@|2!z2ZE$nnAmu1~Y;q=zVnBCYmM zyAD|Ib@A`Afwp}?<^{=JicM!uQ!GfU#DFq*!$vXCZJX=Ytk*ntH2E)%Sqcr#w4|=a zc-9(+*g~7o?bgzZW+cl>3NSQ|>t8qdag&Z1t!`$se+3RWpIb)nW6$NH$UR!^fAa}_ zDN-2*S;SUHIX-w$t2(tD4#Q%FN?{$-cZRUs81Q2Ya?+ZphS7f$JrH53Y7>c%(o>|j zC-nCqCoRhJ#pdSYEZRJQyP7Bok*zE1{QihNE3p4)9pK=@_nTv2#ct(Xo4MufB0L*pUx&wqjEcusrJ+ ziR+k2G{n}GB?v2>#x?z8l#CFblbk+Eohz`CtJG$k65HRa`yu=$OS#)m@vD7|V_<@9 z(plYq?7PwF*dCF_I@!WtH04=dIR^i&yUB{s!Y7SX;L#XipMA;*nw6pwJIFUrW%E-j zGSb*6-vX|f^B38JM*qqnaBleV%9zmhFz5~4FfSx^Lr3wN16o({KYdgP!8h-eYJk}a z`Vupp6ViZDY=i>mBi5te%S}=3QRMb-*_l-j^ci6&bx@7+WR)li&=$fNqmPAueu58g zQO~D6^rRvsfEI{}p(x7JEP{BjpME7^O{MiM&wMV)V+m;f#Q&Uzz<)J!WW!PLu_*CT zdMTlqfBYuI2F%j1EUMP%qUhRaz&bf51eq&@u~z~oRSCDuxM)j=h^3}Y1@#YE8C#V= z55v|8N>(&5$f|U6LpvvCZ2m2C^;@WjY$Pgfz){YM*WN@~r^-3r}jx=wP3_ioPlxV-AZOD4H*j2AK3?l^%;|GQwdb@bnj##vMU{{^i74~lkg ( -

+
}>
diff --git a/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx b/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx index f725a979a9..25719c3f42 100644 --- a/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx +++ b/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx @@ -1,18 +1,39 @@ import React from 'react'; -import { Link as RouterLink } from 'react-router-dom'; +import { Link as RouterLink, useLocation } from 'react-router-dom'; import { ContentHeader, SupportButton } from '@backstage/core'; -import { Button } from '@material-ui/core'; -import { Settings as SettingsIcon } from '@material-ui/icons'; - -export const PluginHeader = () => ( - - - A description of your plugin goes here. - -); + {!isSettingsPage && ( + + )} + A description of your plugin goes here. + + ); +}; diff --git a/plugins/circleci/src/pages/BuildsPage/BuildsPage.tsx b/plugins/circleci/src/pages/BuildsPage/BuildsPage.tsx index 32939d2ebe..ff078dcd95 100644 --- a/plugins/circleci/src/pages/BuildsPage/BuildsPage.tsx +++ b/plugins/circleci/src/pages/BuildsPage/BuildsPage.tsx @@ -1,34 +1,17 @@ import React, { FC } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import { - Content, - ContentHeader, - SupportButton, - InfoCard, -} from '@backstage/core'; -import { Button, Grid } from '@material-ui/core'; -import { CircleCIFetch } from 'components/CircleCIFetch'; -import { Settings as SettingsIcon } from '@material-ui/icons'; +import { Content } from '@backstage/core'; +import { Grid } from '@material-ui/core'; +import { Builds } from './lib/Builds'; import { Layout } from 'components/Layout'; +import { PluginHeader } from 'components/PluginHeader'; export const BuildsPage: FC<{}> = () => ( - - - A description of your plugin goes here. - + - - - + diff --git a/plugins/circleci/src/components/CircleCIFetch/CirleCIFetch.tsx b/plugins/circleci/src/pages/BuildsPage/lib/Builds/Builds.tsx similarity index 93% rename from plugins/circleci/src/components/CircleCIFetch/CirleCIFetch.tsx rename to plugins/circleci/src/pages/BuildsPage/lib/Builds/Builds.tsx index 68358dccd6..b23f768c7b 100644 --- a/plugins/circleci/src/components/CircleCIFetch/CirleCIFetch.tsx +++ b/plugins/circleci/src/pages/BuildsPage/lib/Builds/Builds.tsx @@ -78,7 +78,7 @@ const transform = (buildsData: BuildSummary[]): CITableBuildInfo[] => { }); }; -export const CircleCIFetch: FC<{}> = () => { +export const Builds: FC<{}> = () => { const dispatch: Dispatch = useDispatch(); const api = useApi(circleCIApiRef); @@ -89,7 +89,10 @@ export const CircleCIFetch: FC<{}> = () => { }; }, []); const { builds } = useSelector((state: iRootState) => state.builds); + const { repo, owner } = useSelector((state: iRootState) => state.settings); const transformedBuilds = transform(builds); - return ; + return ( + + ); }; diff --git a/plugins/circleci/src/pages/BuildsPage/lib/Builds/index.ts b/plugins/circleci/src/pages/BuildsPage/lib/Builds/index.ts new file mode 100644 index 0000000000..c2649d7919 --- /dev/null +++ b/plugins/circleci/src/pages/BuildsPage/lib/Builds/index.ts @@ -0,0 +1 @@ +export { Builds } from './Builds'; diff --git a/plugins/circleci/src/components/CITable/CITable.tsx b/plugins/circleci/src/pages/BuildsPage/lib/CITable/CITable.tsx similarity index 89% rename from plugins/circleci/src/components/CITable/CITable.tsx rename to plugins/circleci/src/pages/BuildsPage/lib/CITable/CITable.tsx index 52594b9227..ed720446bc 100644 --- a/plugins/circleci/src/components/CITable/CITable.tsx +++ b/plugins/circleci/src/pages/BuildsPage/lib/CITable/CITable.tsx @@ -1,8 +1,14 @@ // Idea for this component to be somehow reusable representation of CI table view import React, { FC } from 'react'; // import { makeStyles } from '@material-ui/core/styles'; -import { Link, CircularProgress, Button } from '@material-ui/core'; -import { Replay as RetryIcon } from '@material-ui/icons'; +import { + Link, + CircularProgress, + Button, + Typography, + Box, +} from '@material-ui/core'; +import { Replay as RetryIcon, GitHub as GithubIcon } from '@material-ui/icons'; import { Link as RouterLink } from 'react-router-dom'; import { StatusFailed, @@ -111,7 +117,7 @@ const generatedColumns: TableColumn[] = [ field: 'id', type: 'numeric', // @ts-ignore - width: '10%', + width: '80px', }, { title: 'Build', @@ -154,13 +160,21 @@ const generatedColumns: TableColumn[] = [ ]; export const CITable: FC<{ builds: CITableBuildInfo[]; -}> = React.memo(({ builds = [] }) => { + projectName: string; +}> = React.memo(({ builds = [], projectName }) => { // const classes = useStyles(); // const isTestDataAvailable = builds.some(build => build.tests); return ( + + + {projectName} + + } columns={generatedColumns} /> ); diff --git a/plugins/circleci/src/components/CITable/index.ts b/plugins/circleci/src/pages/BuildsPage/lib/CITable/index.ts similarity index 100% rename from plugins/circleci/src/components/CITable/index.ts rename to plugins/circleci/src/pages/BuildsPage/lib/CITable/index.ts diff --git a/plugins/circleci/src/pages/DetailedViewPage/DetailedViewPage.tsx b/plugins/circleci/src/pages/DetailedViewPage/DetailedViewPage.tsx index 37ff1e0655..d8ee53114a 100644 --- a/plugins/circleci/src/pages/DetailedViewPage/DetailedViewPage.tsx +++ b/plugins/circleci/src/pages/DetailedViewPage/DetailedViewPage.tsx @@ -5,46 +5,79 @@ import { PluginHeader } from 'components/PluginHeader'; import { BuildWithSteps, BuildStepAction } from 'circleci-api'; import { circleCIApiRef } from 'api'; import { useParams } from 'react-router-dom'; -import { ActionOutput } from '../../components/ActionOutput/ActionOutput'; +import { ActionOutput } from './lib/ActionOutput/ActionOutput'; import { Layout } from 'components/Layout'; +import { Dispatch, iRootState } from 'state/store'; +import { useDispatch, useSelector } from 'react-redux'; + +const BuildName: FC<{ build: BuildWithSteps | null }> = ({ build }) => ( + <> + #{build?.build_num} - {build?.branch} + +); +import { makeStyles } from '@material-ui/core/styles'; +const useStyles = makeStyles((theme) => ({ + failed: { + position: 'relative', + '&:after': { + pointerEvents: 'none', + content: '""', + position: 'absolute', + top: 0, + right: 0, + left: 0, + bottom: 0, + boxShadow: `inset 4px 0px 0px ${theme.palette.error.main}`, + }, + }, + cardContent: { + backgroundColor: theme.palette.background.default, + }, + success: { + position: 'relative', + '&:after': { + pointerEvents: 'none', + content: '""', + position: 'absolute', + top: 0, + right: 0, + left: 0, + bottom: 0, + boxShadow: `inset 4px 0px 0px ${theme.palette.success.main}`, + }, + }, +})); export const DetailedViewPage: FC<{}> = () => { let { buildId = '' } = useParams(); - - const [authed, setAuthed] = React.useState(false); - const [build, setBuild] = React.useState(null); + const classes = useStyles(); + const dispatch: Dispatch = useDispatch(); const api = useApi(circleCIApiRef); React.useEffect(() => { - const getBuildAsync = async () => { - if (!authed) { - await api.restorePersistedSettings(); - await api - .validateToken() - .then(() => { - setAuthed(true); - }) - .catch(() => setAuthed(false)); - } - api.getBuild(buildId).then(setBuild); + dispatch.buildWithSteps.startPolling({ api, buildId: Number(buildId) }); + return () => { + dispatch.buildWithSteps.stopPolling(); }; - getBuildAsync(); - }, [authed, buildId]); + }, []); + const { build } = useSelector((state: iRootState) => state.buildWithSteps); + return ( - - {!api.authed ? ( -
Not authenticated
- ) : ( - - - - - - + + + + + } + cardClassName={classes.cardContent} + > + + - )} +
); @@ -64,14 +97,18 @@ const BuildsList: FC<{ build: BuildWithSteps | null }> = ({ build }) => ( const ActionsList: FC<{ actions: BuildStepAction[]; name: string }> = ({ actions, -}) => ( - <> - {actions.map((action: BuildStepAction) => ( - - ))} - -); +}) => { + const classes = useStyles(); + return ( + <> + {actions.map((action: BuildStepAction) => ( + + ))} + + ); +}; diff --git a/plugins/circleci/src/components/ActionOutput/ActionOutput.tsx b/plugins/circleci/src/pages/DetailedViewPage/lib/ActionOutput/ActionOutput.tsx similarity index 60% rename from plugins/circleci/src/components/ActionOutput/ActionOutput.tsx rename to plugins/circleci/src/pages/DetailedViewPage/lib/ActionOutput/ActionOutput.tsx index 7cc92098a1..18293b790b 100644 --- a/plugins/circleci/src/components/ActionOutput/ActionOutput.tsx +++ b/plugins/circleci/src/pages/DetailedViewPage/lib/ActionOutput/ActionOutput.tsx @@ -7,20 +7,36 @@ import { } from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import { makeStyles } from '@material-ui/core/styles'; import { BuildStepAction } from 'circleci-api'; const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog')); +const useStyles = makeStyles({ + expansionPanelDetails: { + padding: 0, + }, + button: { + order: -1, + marginRight: 0, + // FIXME: how not to hardcode this + marginLeft: '-20px', + }, +}); + export const ActionOutput: FC<{ url: string; name: string; + className?: string; action: BuildStepAction; -}> = ({ url, name }) => { +}> = ({ url, name, className }) => { + const classes = useStyles(); + const [messages, setMessages] = useState([]); useEffect(() => { fetch(url) - .then(res => res.json()) - .then(actionOutput => { + .then((res) => res.json()) + .then((actionOutput) => { actionOutput && setMessages( actionOutput.map(({ message }: { message: string }) => message), @@ -28,20 +44,26 @@ export const ActionOutput: FC<{ }); }, [url]); return ( - + } aria-controls={`panel-${name}-content`} id={`panel-${name}-header`} + IconButtonProps={{ + className: classes.button, + }} > - {name} + {name} - + {messages.length === 0 ? ( 'Nothing here...' ) : ( -
+
diff --git a/plugins/circleci/src/components/ActionOutput/index.ts b/plugins/circleci/src/pages/DetailedViewPage/lib/ActionOutput/index.ts similarity index 100% rename from plugins/circleci/src/components/ActionOutput/index.ts rename to plugins/circleci/src/pages/DetailedViewPage/lib/ActionOutput/index.ts diff --git a/plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx b/plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx index 033c5cd2ee..f99181c26c 100644 --- a/plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx +++ b/plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx @@ -12,17 +12,15 @@ import { import { InfoCard, Content, - ContentHeader, - SupportButton, // StatusOK, // StatusFailed, } from '@backstage/core'; -import { Link as RouterLink } from 'react-router-dom'; import { Layout } from 'components/Layout'; import { SettingsState } from 'state/models/settings'; import { iRootState } from 'state/store'; import { Dispatch } from '../../state/store'; import { Alert } from '@material-ui/lab'; +import { PluginHeader } from 'components/PluginHeader'; export const SettingsPage = () => { const { @@ -60,12 +58,8 @@ export const SettingsPage = () => { return ( - - - A description of your plugin goes here. - + + { setSaved(false)} > Credentials saved. diff --git a/plugins/circleci/src/plugin.ts b/plugins/circleci/src/plugin.ts index 77a223a162..8e308bd12c 100644 --- a/plugins/circleci/src/plugin.ts +++ b/plugins/circleci/src/plugin.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { createPlugin } from '@backstage/core'; -import { App } from './components/App'; +import { App } from './App'; export const plugin = createPlugin({ id: 'circleci', diff --git a/plugins/circleci/src/state/models/buildWithSteps.ts b/plugins/circleci/src/state/models/buildWithSteps.ts new file mode 100644 index 0000000000..109ee08df0 --- /dev/null +++ b/plugins/circleci/src/state/models/buildWithSteps.ts @@ -0,0 +1,77 @@ +import { Dispatch, iRootState } from '../store'; +import { GitType, BuildWithSteps } from 'circleci-api'; +import { CircleCIApi } from 'api'; + +export type BuildState = { + build: BuildWithSteps | null; + pollingIntervalId: number | null; + pollingState: PollingState; +}; + +const INTERVAL_AMOUNT = 1500; + +export enum PollingState { + Polling, + Idle, +} +export const buildWithSteps = { + state: { + build: null, + pollingIntervalId: null, + pollingState: PollingState.Idle, + } as BuildState, + reducers: { + setBuild(state: BuildState, payload: BuildWithSteps) { + if (state.pollingState !== PollingState.Polling) { + return state; + } + return { ...state, build: payload }; + }, + setPollingIntervalId(state: BuildState, payload: number | null) { + return { + ...state, + pollingIntervalId: payload, + pollingState: + payload === null ? PollingState.Idle : PollingState.Polling, + }; + }, + }, + effects: (dispatch: Dispatch) => ({ + async getBuild( + { api, buildId }: { api: CircleCIApi; buildId: number }, + state: iRootState, + ) { + try { + const options = { + token: state.settings.token, + vcs: { + owner: state.settings.owner, + repo: state.settings.repo, + type: GitType.GITHUB, + }, + }; + const build = await api.getBuild(buildId, options); + dispatch.buildWithSteps.setBuild(build); + } catch (e) { + console.log(e); + } + }, + startPolling( + { api, buildId }: { api: CircleCIApi; buildId: number }, + state: iRootState, + ) { + if (state.buildWithSteps.pollingIntervalId) return; + + const intervalId = (setInterval( + () => dispatch.buildWithSteps.getBuild({ buildId, api }), + INTERVAL_AMOUNT, + ) as any) as number; + dispatch.buildWithSteps.setPollingIntervalId(intervalId); + }, + stopPolling(_: any, state: iRootState) { + const currentIntervalId = state.buildWithSteps.pollingIntervalId; + if (currentIntervalId) clearInterval(currentIntervalId); + dispatch.buildWithSteps.setPollingIntervalId(null); + }, + }), +}; diff --git a/plugins/circleci/src/state/models/index.ts b/plugins/circleci/src/state/models/index.ts index 39623b96f9..ff1396d97d 100644 --- a/plugins/circleci/src/state/models/index.ts +++ b/plugins/circleci/src/state/models/index.ts @@ -1,13 +1,16 @@ import { settings } from './settings'; import { builds } from './builds'; +import { buildWithSteps } from './buildWithSteps'; // no need to extend from Models export interface RootModel { settings: typeof settings; builds: typeof builds; + buildWithSteps: typeof buildWithSteps; } export const models = { settings, builds, + buildWithSteps, }; diff --git a/plugins/circleci/src/utils/index.ts b/plugins/circleci/src/utils/index.ts index 77697659e0..2bf45aa3e4 100644 --- a/plugins/circleci/src/utils/index.ts +++ b/plugins/circleci/src/utils/index.ts @@ -1,6 +1,6 @@ import { circleCIApiRef } from 'api'; import { BuildSummary } from 'circleci-api'; -import { CITableBuildInfo } from 'components/CITable'; +import { CITableBuildInfo } from 'pages/BuildsPage/lib/CITable'; const makeReadableStatus = (status: string | undefined) => { if (typeof status === 'undefined') return ''; @@ -22,7 +22,7 @@ const makeReadableStatus = (status: string | undefined) => { }; export const transformBuildSummary = ( - api: typeof circleCIApiRef.T, + _: typeof circleCIApiRef.T, buildData: BuildSummary, ) => { const tableBuildInfo: CITableBuildInfo = { @@ -31,7 +31,7 @@ export const transformBuildSummary = ( ? buildData.subject + (buildData.retry_of ? ` (retry of #${buildData.retry_of})` : '') : '', - onRetryClick: () => api.retry(String(buildData.build_num)), + onRetryClick: () => {}, //api.retry(String(buildData.build_num)), source: { branchName: String(buildData.branch), commit: {