From d8373f2bb964dce7bc5771359d1d745df3e81208 Mon Sep 17 00:00:00 2001 From: Phillip Kelley-Dotson Date: Fri, 6 Nov 2020 22:35:13 -0500 Subject: [PATCH] chore(home-screen): fixes for loading states, flicker issue, and reduction of api calls (#11557) * fixes for loading states, flicker issue, api calls * fix filter bug * add high res images * bug fixes for cards and face pile, change imgs to svgs, and address comments * update from comments * add stopprop * fix tests * add liscenses * remove unused type * fix types * add license * fix lint --- superset-frontend/images/empty-charts.png | Bin 2274 -> 0 bytes superset-frontend/images/empty-charts.svg | 30 ++++ superset-frontend/images/empty-dashboard.png | Bin 1467 -> 0 bytes superset-frontend/images/empty-dashboard.svg | 26 ++++ superset-frontend/images/empty-queries.png | Bin 1791 -> 0 bytes superset-frontend/images/empty-queries.svg | 35 +++++ superset-frontend/images/star-circle.png | Bin 2705 -> 0 bytes superset-frontend/images/star-circle.svg | 22 +++ superset-frontend/images/union.png | Bin 1694 -> 3199 bytes superset-frontend/images/union.svg | 22 +++ .../views/CRUD/welcome/ActivityTable_spec.tsx | 49 +++---- .../views/CRUD/welcome/ChartTable_spec.tsx | 9 +- .../CRUD/welcome/DashboardTable_spec.tsx | 31 ++-- .../views/CRUD/welcome/SavedQueries_spec.tsx | 30 ++-- .../views/CRUD/welcome/Welcome_spec.tsx | 45 +++++- .../src/components/FacePile/index.tsx | 4 +- .../components/ListViewCard/ImageLoader.tsx | 2 +- .../src/components/ListViewCard/index.tsx | 2 +- .../src/views/CRUD/chart/ChartCard.tsx | 69 +++++---- .../views/CRUD/dashboard/DashboardCard.tsx | 69 ++++++--- .../views/CRUD/dashboard/DashboardList.tsx | 1 - superset-frontend/src/views/CRUD/hooks.ts | 11 +- superset-frontend/src/views/CRUD/types.ts | 1 + superset-frontend/src/views/CRUD/utils.tsx | 79 +++++++---- .../src/views/CRUD/welcome/ActivityTable.tsx | 110 ++++++-------- .../src/views/CRUD/welcome/ChartTable.tsx | 34 +++-- .../src/views/CRUD/welcome/DashboardTable.tsx | 38 +++-- .../src/views/CRUD/welcome/EmptyState.tsx | 104 +++++++------- .../src/views/CRUD/welcome/SavedQueries.tsx | 134 ++++++++++++------ .../src/views/CRUD/welcome/Welcome.tsx | 97 ++++++++++++- 30 files changed, 730 insertions(+), 324 deletions(-) delete mode 100644 superset-frontend/images/empty-charts.png create mode 100644 superset-frontend/images/empty-charts.svg delete mode 100644 superset-frontend/images/empty-dashboard.png create mode 100644 superset-frontend/images/empty-dashboard.svg delete mode 100644 superset-frontend/images/empty-queries.png create mode 100644 superset-frontend/images/empty-queries.svg delete mode 100644 superset-frontend/images/star-circle.png create mode 100644 superset-frontend/images/star-circle.svg create mode 100644 superset-frontend/images/union.svg diff --git a/superset-frontend/images/empty-charts.png b/superset-frontend/images/empty-charts.png deleted file mode 100644 index b814d3574961e03990f31b52bf34c78d16347e8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2274 zcmV<82p#u{P)h+WA-jpRrOE`s1=oOVm4(z2vLK{7y2YA`M+@ii>({Sa_wV0-Dgh&Ca@B2ES3;|N45NFxi}RS$snWVR>^)SfdDQWK zYk2(zjw+pE}4%S-6#BYGuBb+vsSVncWNCy3G`iPLu$~f-{5_c)l`1?n= zjB!vK>_dv5NPq}Va;Zi!;f&ryyyn`8V+*J4tSn!WjA0j_)xc*xp%g>4S7CHL3dy<- zN^(_N_oYv4j7eXmq>+qiOgi3^#ucSMrgmT)T@AG$5CpkgTOm1=)}}tRrGfRfOd0r$ z4!=a=sp(J%Hcze^P5Y3Xn)KJzRjj91x=BH|Xn^1sye}~h^W>@;rfU;lm9!4S0KsMK z+L$-8Oq0uKl6)t2b=H9eC76~p-K;unfORnpv*glvQvbATfmjD!gI#c=X<0p+Bv%cV z2G2Q>)sYsHJ)0v}W4&C<=v$hN?dtlQ z+@DqHHAgPux^AL&0@#^JvnXxDyYVMS-=eEmwd}r?V{?0i;H}cNl?@7=jscF)>9D%ke*% zBABfKGMGwJlZ@a}d$?O9hG2$VOm?KFu}l$IBNrK=i#!n`Z&G&OO3BH6;6#5!DuiUWoSnUhRQwh59YYYfYawg5}y zI`tW4shTw%r-JvbVPtr7NwPyxbEfIv=Hy<2gj|_Yl|D#rNygAvIEgix?rJ88!D1{u zH5CZ>2_pV8ZTD?bm?8J{^fZ|uYf+w`psJ(EaH*y6BYjUwa7hAzcduY}Ri!L$1HOVsYbp2&()C>+PNxg{l2qFK5bhJ}|++2uE!xWD=&xRrMC*h`hcP z=Ab@vgd;}qBA47z!Ehl*Ez>j!C$*xv)mCAST+LICaYTp(H^dllPb4CAFc-zFux=^}T6WuJUV0-}iL|&od)f zYlN9)5lLSihv2H&C6|7$*LZ~Wu-mgBn4gKyCb4Iwq!I{B*XQ~L&)inK>Xrpg6>GN~ z6E(u)zU`4wUT$L$9yXN>+{Xr2+1kv!sBlv6$W&@OINU=6odG5OAFpoM!Vu@hV zY;o8W?#_#Lbuc<2nN$TR_;s!guYxnoA7Ji$o4;}@XbV`TynCgATh4VMr z8qht49hw&Qgc%APx!IT+XlpQ-*8P5w+PLiTtfV!oc@2_W2VHw>KFJEU*^NQ=q5PN! z&8@m$T*p@b2_^OEtheyoysoUQ6!3pY&XR`PcUI&&s3ccG z*t*+e9TCYkBY*A%DtM=2r1hUiF168LIBi3_ba9_OEI}g^Ju)dEG{Ty+u5JMjIXXH@ zo|`qfgYYT_6Djqd!~HXzCzif(7F!|IhexjeF#ALAf!sZ|3$mH^a6jhPhususK zxGT*FA)ze_#!YaUJdhYdxL{DAA!Fj*yLa;W^JnK{n(Opt0Wgk6Ha1zK z41#`Vfacb%TXOgAU6};n<0%ZU#sbuE4c8N;w9qVn`}QqwN^)ceYQ%f@?g@-pnH22m zz5uB`(CExWAi?k7zn4#+K1q&rAgMQR-jq9c?xZ4?V0jKDH#djp6a}x%PL4SOcs(e1 zy@PYi5uld03UGRNEq6!FF*bVS^Y-oAJ7;D(0vL5|Et~tSfdZuRN0px~EiEN`(C2(G zMyS}xI*(2T2O7n@aDRS-CXU^MLmlj#28@xm0I{vw#wR z9u~tM$YiR}?qw2s5i-f3P_mg*%v6^W!f~mwd2bKDSXnlyr9NYRahoq=2{?5A1>6gO)LKMgNg7kEK w^Ffjv5wX6ADGof2wK-^4pG!!+YT`!0|1A)>GBSY(DgXcg07*qoM6N<$f-u=G;Q#;t diff --git a/superset-frontend/images/empty-charts.svg b/superset-frontend/images/empty-charts.svg new file mode 100644 index 0000000000..b4cdd99086 --- /dev/null +++ b/superset-frontend/images/empty-charts.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/superset-frontend/images/empty-dashboard.png b/superset-frontend/images/empty-dashboard.png deleted file mode 100644 index b0d44626d7590548d3f767eadb22c17bb1988cc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1467 zcmV;s1w{IZP) zV{<8r2aM+bO^dQ!lF+3+mfGj5Qnh?5cj?pVmQNqsjxs>E+pU$$zH2s{uF8O;GD5MXQmLy%s%ZvKPfyJ(f|pcA5V41dSc?6K&g`Ny10|$J2g@FI z-*PlW`=d%p=E&{!dLCPdt!EuZgl8al9fJGV9W)w^s!B=b$n_Z)D|2u%rsr6Weh3=w zW;%8yWf*@BhS-)8v9Z9h`kBX!$4(i%!Q z^{OzdLPa4jt5=0u6)FmOTD>aFs!&nL(dt#PR)va!?WR}e}b#88M29D!+kB^V4T8;O&2eBg;LTk6T zx9{;Lr{!|F&#D2u*-pSybN4$`A>}K%=%*H23ws>L`7dl=vHOpDu4}GeUtc?ScXzKw zbfjEH=$Ozip6Bi1?pD-m^7cDaVHN%J?q{#pbGTu*I@Z6a8tNAMe2C|M#{U1<+SuxF za%Xw9c zP0Z!xrFwXH;81alVPRkXxMC7Luf_&J!fS-zI9;r)tf7hxVRWauV4<&#amljYH2B5X*mMzjf1G|X04Z{;H#@Eb$@@agltgi^769U*w{!d zmLPi17`e$VhlRpW@;~nhb^)*566VT%C0GZB<=;#A(0c;QBG?{0`dnLE`}lI1js(W8 zPJ_vR6BHCkWtQupQmK64?TOD|%uw;`glV%=!rb6RF~7k5D@J11zccUywn#1pj8y%t z{68VJGyQmE_Joqra1mjL$!aC!0Y+&DV*kU%@CQf@P5H)7jp=Cq36lAx<(3k@7k(bX z7kWHC^hS{=@9Co}F&NPl-H@m@#!Es8B3(3}Ad_p6Cs7d^Mk=|}XxZD4-Gb-_)0t>% zN{C`YB-4dx|AowR+|@}Va~dOZgBTOuKwH<+YYQfQBDFgvkpf*Em1ibI1~D_aQ9%6Y zcb~NQD#i1*{&g9i0D1nb9hi$p-zkViY%W?Cx39H4-p>^8U{(^tn2X%k&{01Ut?%N+ zTm>y_NY-%xEBf)#NuW=P@e^V_!3pB&`E49Z@T)`v8<1O3=Y>h~5Ghi}y4 VhmU(C((wQQ002ovPDHLkV1geIxW51Z diff --git a/superset-frontend/images/empty-dashboard.svg b/superset-frontend/images/empty-dashboard.svg new file mode 100644 index 0000000000..c76eca0c30 --- /dev/null +++ b/superset-frontend/images/empty-dashboard.svg @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/superset-frontend/images/empty-queries.png b/superset-frontend/images/empty-queries.png deleted file mode 100644 index adc51c0814b80bf565aa82b8db714cb24581bbd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1791 zcmV0eGEYG`;}X7eI3YxB+m28J_^;3m7h7dQQM=Z+aZTl0Xmy z8y@oicUNf|B!SeDMpfnfBZ}&G2z{N^Sy@@Fk~pZNqob%?F7GxPjb*G(%jk5B@^xux zsU{w(B#wz-$HvBvWVBG937=HZ=iniAQn{t`)#0;(+iu_Iw5GEL@l5D`17& zrF;aeaC?w%fE8|!@(HlQ?X1*ON58wJzwHZPh1)^sk}*2P0LEs!{XPIzxE%zzHej&@ z>)q_ef)V&w7l-6r_|Zm&5YHX?X9Lh}SZ>!ASt^y{#l^)>;)v98UlA;Go0bVHj=MP? z$5q53ITu@YO>BBm?Y(teJ{IkY)N%us!VOpoH()8;fTeH)mck8KL3V{qG-(Y1h-Cy8 z-sckS@=>M)`x5LsL0>k;dh9~x#~>9HTtKaq%jKA?5 zkB>D#jwJvE0hg9)QeoFDOh%PpnftKH?~uXJ{*>`MKV|yJ`gu7uNSFmlx=cd|wxXdY*%nB2L{Z!Q2 z%z4`Yy2qP^*EHAO6i*d`x4g^}QV!BHa$n>Cju9>G4NdO9_pNr>Y4Pe%p?m|Zh-I*BOnQxML!}NHQ z7hNHiWgPn+-5_?MDmXX27MAz9O*~gHZ~?ALPgk<;%sOGQJA?IRnhXZi+n$^68#8x` zK@1lM-+NV15h@c|ikZ-cx*}PCVhJUZ=S?jwxXuhm6dUB&*YKmGV(8I^4GpAyQ;J+` zSprZ<-tz7Mz2(0o0EOl)pTm**qTX_*;Jjr#LpM1~K)q%4mQ$&>?19uuWs;iZCjCx(H^42)*BBHvHZtG8^=rNyFr?&TTm(z`sY zf_mWzqf}zNWhtP%w=6x%MY!rMtG7H@U0q#8@}pUdI1`WSoDz~GiS(;}z2$y%b8~aZ znS+u|ZAs@Sp&ZZ5%>48lXQ*%u#EJ#Tsg%Zw+9rErp9CPb78Nd(%jM=qKxa#lpEgSH z37{X#kTfv3IglJt`PdF(An=N68vj4P8{Xh+7CfP&I?l}#{*Hb2)r5`v`+LC+AVpLd zE@6(3kFOdplVw^|MP#Rua&Ga4*+$?o$^ps=Zh#avy#GcopnHM~ptMW|)K_VluS{%K z^{4dB0I+0h;fZ9h{{(2(U4ecRt_2F1>!Q{Wpklm-B2VM=)Y0_!)0(P-QM1|9M}TaKt0Feu4Kvk`%c2$EIjT(?Drnz1r;4xY(HZUP9 z5(o6|-Mh5r&%QbIrFE2flTMoLF)=YA4k$8!Ka{SoulI~6?1Ua39^~TULTo;hFJg-` zKR++%`->xrOL)$*fp)41B;d=-OS!wd6W2wkY;9&|XI%nI)twv_;+HfxHz%t8MDJy2 zfos)BwarARg{mlSPq<{vNv1VYi&z$B88{j&?-(Jo9Bm>eW$T1~0E(-IdhMJC;15VIOlI14< zpP)a3DA_NfSdeKl{GdR#(@1hn0X_9DduZK=WLr6{a=T{{=uU zr-|vqgxE>WCKBgnC8WS{6a0cYP4_c16sX7^Z+^g{sUCcQMw{pyA=w!Mi8<&R)wKa; hx2MxmMgguH{Rh;dj}o!JRrCM=002ovPDHLkV1glzR2BdL diff --git a/superset-frontend/images/empty-queries.svg b/superset-frontend/images/empty-queries.svg new file mode 100644 index 0000000000..2239c0ae8e --- /dev/null +++ b/superset-frontend/images/empty-queries.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/superset-frontend/images/star-circle.png b/superset-frontend/images/star-circle.png deleted file mode 100644 index 77fd94d0fe1c4161dde5baf890808f72c399f7ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2705 zcmV;C3U2j@P)1f$;{w-T;sdfY?BgHvq~8uqpHSiwAxv z_yF-LY_K8cv~I&N$o7n+d9`$^h8|hcOlwZx)3Vkd!`Sa(AvuDrN#BuaXci@tiKK}vH7o-c&736}< zahB`r>ofZLFX;j}Pp`NC&v(w~;CD{VfJg@INEzPczYa|)uRzq)9^?j$M z*;#Ufk{k|L@HGgpi{sMlvgKOItu=cSWGs7Ew4l(Cf=yyx5#+se9+<}3CNjBfK%jJFBuXW znOp)^TM2&O6*xmKC6&^O)B4GiC+FjQHzds^aRkng3rQ{O>Aw;alx8?$HNDQHI09$L zwSfqfx1|NxIbWBg*$HwVKYn~gCD~tDS&1dd7N0(Ss#!5UsiddA=s;VG)tBi%CVs60=*HQC?GQ_hjEr2zb*4m< z*HZvzudYZ!7?6Yj3mDYXL|bR40fH;?EmJZPgj8)v5<+5@3&(`n{k}9>=Uw!RU6JL# z70{nwfT}H69|6xPvHm^LSs@VWt*@`o+ITO@>#M7)a~c)uDscP^Yo+67$*e-rrjmRX zsZ9BW92FlwYQnYYwauWb3^_#eG%;_E{ktTL)aG+(#qehDN+TnYvORn1)Tudf9FXB@ z{BEijBVUx^3H)wfQb6jH^-D`j*YN|V^<@QCJm;bns|??^BLolRP zVTNidEg23Y^BgL)@>vjT2l9n=w!#s)cB5sTKrRyGg5IPwoDN!X)HIpQ!7)6BeAlhh zl$UNMH`$Lx91~|Tc8!DFrM4DjOq|u&H4bu_x19GeacJzii27a3%4ymRxlT&Y+n6{H zW7jyy{fYw}6JN$DR}WC8f5XFEmXx6@8H}1_Id+YMLM$htOyhyxz%d~WZ*&;msHBO9 z4HjuJs&z`ALsYEZjJ~TAsOAe9PiUY!u zXbB{FLE@w_Zv9ygzP5k&5aKi6wS-HXlFWXR!3bQht$v%*jO$Df3=G_~b$s*Y4GS(J z^YI?b`+N`!x9N{e?d8}A-j^a0YswY*|TRnjU~yL!4XW&v0l@tpPBgQgWk1km#nO;u&(BF ztMxz)8xi{>*>;C=w<-mahrh6bLqpx4THs}0Lq*hl~XX03~ooD7XL)P0V@JH8h zk19f+_gf`51X_^XEpEVRaXx>~jGb?$bKb0rObr!-o&kihua* z`}gk+!D_{n%bBS2vv!t<*FAFNh@=1?M-6qo1)^(FzqBE_>Ll2QbzUPg9kf=PKZ%@W{((tP5?iMHmG@v(?l6&|Jv>YX5Y!{u#xDPdy-b%Y)?oMj{6xeHqaQdMj1Tb1*eVmSJDV7 zu)p#oC$JAIxTjQ5hVkGj!yYV0jvhT4TZXp+`%qptAk)j-4k`RO8(7I$n#1svV6UGY zvgC+ZN~5)0yQh?<*f5B3#|9-V0ZDnPyzcSi$G?tanS_qUbvtOqcJC_I7w=1sPy)d> zIe^X8j%fi&o~QJwwNB7!Vo-u|NvDa@nFi==9XfQ#STXK@fw{}vw1n^FRGKYDb2sG| zlw`z!N$gde-=X8eu`8{$Sx_QL?B2cGG$_3@OOgd9p3PfXnJ=a>j5f8Y`EEV6R`xHE!KM1Kt7Xat;lGg%pUfIsQS zJo|Y(OfUHTI6i<969h@J*%!Wkh5_Ze5@!F-&*XMFNe?~rP(=I>PK>2B*$Zf*00000 LNkvXXu0mjfgX0-L diff --git a/superset-frontend/images/star-circle.svg b/superset-frontend/images/star-circle.svg new file mode 100644 index 0000000000..a46a1dd0fb --- /dev/null +++ b/superset-frontend/images/star-circle.svg @@ -0,0 +1,22 @@ + + + + + diff --git a/superset-frontend/images/union.png b/superset-frontend/images/union.png index af94c0793e9148478cb1883f824af8d4479277d3..48fa801603ff1a168a29fc3edd5c6d77bb120238 100644 GIT binary patch literal 3199 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 tjS9h5({y+PC0>%IU delta 1684 zcmV;F25b5M7@iF_iBL{Q4GJ0x0000DNk~Le0001Y0000?2nGNE09S-4Y zr}F2^%1Sa~8Ld{UoXh3ThQnc55<(`EY4-d5^Zot(rdUKqETYrt9DgCHNph(ai^b1k z3D(F(i7jQb*=y`>W4jy-233@Vo>&g@&HSDq?`KGR$oH?vcUvsL3b{Bz1tqqC?HpHs zRT2h{;R45Sj9pI&_QWv84KJlO5=_-=1+&%_W$+FXi7Qsq8Q#J(-POP#3Re~bG_t9yHU=Mpoqms}OgR=E$c zr)d;Rcw9I>zYoE%P91lw#9DHvV%bE7JU&&LYb+a-xMSeR+T2x$WrGq|3>-w=phOu1Ym@6@+2BMK18Zq>T`U`psA6C(a$PJNj;Lbb&~jZY ztA{9J;LvR@q`FPHu{Ea#M_xn_`^Y6yxNk7$-NyIDfe*#>q`FiMuPbN<2I~WcvNS|MbDm z&W`*nf#i0(UCv104fB%cdE$tSKe7FdtqIY+OeQmYd3jk{^wM%6w2;r|kJPV*l*JJT zm0T`YQTt77oGf^Ce}C`c{o(WT^Fq-J<+6lA=#SYT(ETwGVhH4F-cx5Y3l0Y#(Mxe$!iK)r&9H@t>|KRvbxyRrh}~ zYM{!Rp`(HCI5@`6fXTYe7ShRbuBpg+luFL9D&(Jjr#}E zDr#ZLqZ+P2Y*ieo0Y3T2OFIkG$KtStR)4D{IzW_jmWAa|le^KY_ETFPu}m^bY(X?Y zuX)By>{?4e*4EbK>FLQ|XbhQ4#PUKq>2x~UmRLl1v#ow!RyH;^Bn2AC#%?E1vfU-E^ zVtiX}EVMRR^)4b%jHYH{l-1G&MSoYSwefm?6(T((SCGlIKN^cl=sdIp;7T?ZBPm04 zk)tC>3*uPB8Go|BN*ass)OR?=fm;{SnD*3X ezSOwIEyFiH_L9QndUg!}0000 + + + + diff --git a/superset-frontend/spec/javascripts/views/CRUD/welcome/ActivityTable_spec.tsx b/superset-frontend/spec/javascripts/views/CRUD/welcome/ActivityTable_spec.tsx index eba5d66862..0193303f1a 100644 --- a/superset-frontend/spec/javascripts/views/CRUD/welcome/ActivityTable_spec.tsx +++ b/superset-frontend/spec/javascripts/views/CRUD/welcome/ActivityTable_spec.tsx @@ -19,8 +19,6 @@ import React from 'react'; import { styledMount as mount } from 'spec/helpers/theming'; import thunk from 'redux-thunk'; -import fetchMock from 'fetch-mock'; - import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; import configureStore from 'redux-mock-store'; import ActivityTable from 'src/views/CRUD/welcome/ActivityTable'; @@ -28,12 +26,8 @@ import ActivityTable from 'src/views/CRUD/welcome/ActivityTable'; const mockStore = configureStore([thunk]); const store = mockStore({}); -const chartsEndpoint = 'glob:*/api/v1/chart/?*'; -const dashboardEndpoint = 'glob:*/api/v1/dashboard/?*'; -const savedQueryEndpoint = 'glob:*/api/v1/saved_query/?*'; - -fetchMock.get(chartsEndpoint, { - result: [ +const mockData = { + Viewed: [ { slice_name: 'ChartyChart', changed_on_utc: '24 Feb 2014 10:13:14', @@ -42,10 +36,7 @@ fetchMock.get(chartsEndpoint, { table: {}, }, ], -}); - -fetchMock.get(dashboardEndpoint, { - result: [ + Edited: [ { dashboard_title: 'Dashboard_Test', changed_on_utc: '24 Feb 2014 10:13:14', @@ -53,18 +44,23 @@ fetchMock.get(dashboardEndpoint, { id: '3', }, ], -}); - -fetchMock.get(savedQueryEndpoint, { - result: [], -}); + Created: [ + { + dashboard_title: 'Dashboard_Test', + changed_on_utc: '24 Feb 2014 10:13:14', + url: '/fakeUrl/dashboard', + id: '3', + }, + ], +}; describe('ActivityTable', () => { const activityProps = { - user: { - userId: '1', - }, - activityFilter: 'Edited', + activeChild: 'Edited', + activityData: mockData, + setActiveChild: jest.fn(), + user: { userId: '1' }, + loading: false, }; const wrapper = mount(, { context: { store }, @@ -77,11 +73,10 @@ describe('ActivityTable', () => { it('the component renders ', () => { expect(wrapper.find(ActivityTable)).toExist(); }); - - it('calls batch method and renders ListViewCArd', async () => { - const chartCall = fetchMock.calls(/chart\/\?q/); - const dashboardCall = fetchMock.calls(/dashboard\/\?q/); - expect(chartCall).toHaveLength(2); - expect(dashboardCall).toHaveLength(2); + it('renders tabs with three buttons', () => { + expect(wrapper.find('li')).toHaveLength(3); + }); + it('it renders ActivityCards', async () => { + expect(wrapper.find('ListViewCard')).toExist(); }); }); diff --git a/superset-frontend/spec/javascripts/views/CRUD/welcome/ChartTable_spec.tsx b/superset-frontend/spec/javascripts/views/CRUD/welcome/ChartTable_spec.tsx index f8cd0531eb..a2979296de 100644 --- a/superset-frontend/spec/javascripts/views/CRUD/welcome/ChartTable_spec.tsx +++ b/superset-frontend/spec/javascripts/views/CRUD/welcome/ChartTable_spec.tsx @@ -22,6 +22,7 @@ import thunk from 'redux-thunk'; import fetchMock from 'fetch-mock'; import configureStore from 'redux-mock-store'; +import { act } from 'react-dom/test-utils'; import ChartTable from 'src/views/CRUD/welcome/ChartTable'; import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; @@ -64,8 +65,14 @@ describe('ChartTable', () => { }); it('fetches chart favorites and renders chart cards ', async () => { - expect(fetchMock.calls(chartsEndpoint)).toHaveLength(1); + act(() => { + const handler = wrapper.find('li.no-router a').at(0).prop('onClick'); + if (handler) { + handler({} as any); + } + }); await waitForComponentToPaint(wrapper); + expect(fetchMock.calls(chartsEndpoint)).toHaveLength(1); expect(wrapper.find('ChartCard')).toExist(); }); diff --git a/superset-frontend/spec/javascripts/views/CRUD/welcome/DashboardTable_spec.tsx b/superset-frontend/spec/javascripts/views/CRUD/welcome/DashboardTable_spec.tsx index 1ee8d06127..491c582ffd 100644 --- a/superset-frontend/spec/javascripts/views/CRUD/welcome/DashboardTable_spec.tsx +++ b/superset-frontend/spec/javascripts/views/CRUD/welcome/DashboardTable_spec.tsx @@ -24,7 +24,6 @@ import fetchMock from 'fetch-mock'; import { act } from 'react-dom/test-utils'; import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; -import SubMenu from 'src/components/Menu/SubMenu'; import DashboardTable from 'src/views/CRUD/welcome/DashboardTable'; import DashboardCard from 'src/views/CRUD/dashboard/DashboardCard'; @@ -34,6 +33,7 @@ const store = mockStore({}); const dashboardsEndpoint = 'glob:*/api/v1/dashboard/?*'; const dashboardInfoEndpoint = 'glob:*/api/v1/dashboard/_info*'; +const dashboardFavEndpoint = 'glob:*/api/v1/dashboard/favorite_status?*'; const mockDashboards = [ { id: 1, @@ -47,6 +47,9 @@ fetchMock.get(dashboardsEndpoint, { result: mockDashboards }); fetchMock.get(dashboardInfoEndpoint, { permissions: ['can_list', 'can_edit', 'can_delete'], }); +fetchMock.get(dashboardFavEndpoint, { + result: [], +}); describe('DashboardTable', () => { const dashboardProps = { @@ -54,6 +57,7 @@ describe('DashboardTable', () => { user: { userId: '2', }, + mine: mockDashboards, }; const wrapper = mount(, { context: { store }, @@ -68,27 +72,34 @@ describe('DashboardTable', () => { }); it('render a submenu with clickable tabs and buttons', async () => { - expect(wrapper.find(SubMenu)).toExist(); + expect(wrapper.find('SubMenu')).toExist(); expect(wrapper.find('li')).toHaveLength(2); expect(wrapper.find('Button')).toHaveLength(4); act(() => { - wrapper.find('li').at(1).simulate('click'); + const handler = wrapper.find('li.no-router a').at(1).prop('onClick'); + if (handler) { + handler({} as any); + } }); await waitForComponentToPaint(wrapper); expect(fetchMock.calls(/dashboard\/\?q/)).toHaveLength(1); }); - it('fetches dashboards and renders a card', () => { - expect(fetchMock.calls(/dashboard\/\?q/)).toHaveLength(1); - wrapper.setState({ dashboards: mockDashboards }); + it('render DashboardCard', () => { expect(wrapper.find(DashboardCard)).toExist(); }); it('display EmptyState if there is no data', () => { - fetchMock.resetHistory(); - const wrapper = mount(, { - context: { store }, - }); + const wrapper = mount( + , + { + context: { store }, + }, + ); expect(wrapper.find('EmptyState')).toExist(); }); }); diff --git a/superset-frontend/spec/javascripts/views/CRUD/welcome/SavedQueries_spec.tsx b/superset-frontend/spec/javascripts/views/CRUD/welcome/SavedQueries_spec.tsx index 2e290158a7..081cafdaa3 100644 --- a/superset-frontend/spec/javascripts/views/CRUD/welcome/SavedQueries_spec.tsx +++ b/superset-frontend/spec/javascripts/views/CRUD/welcome/SavedQueries_spec.tsx @@ -74,11 +74,22 @@ describe('SavedQueries', () => { user: { userId: '1', }, + mine: mockqueries, }; const wrapper = mount(, { context: { store }, }); + + const clickTab = (idx: number) => { + act(() => { + const handler = wrapper.find('li.no-router a').at(idx).prop('onClick'); + if (handler) { + handler({} as any); + } + }); + }; + beforeAll(async () => { await waitForComponentToPaint(wrapper); }); @@ -87,20 +98,19 @@ describe('SavedQueries', () => { expect(wrapper.find(SavedQueries)).toExist(); }); + it('fetches queries favorites and renders listviewcard cards', async () => { + clickTab(0); + await waitForComponentToPaint(wrapper); + expect(fetchMock.calls(/saved_query\/\?q/)).toHaveLength(1); + expect(wrapper.find('ListViewCard')).toExist(); + }); + it('it renders a submenu with clickable tables and buttons', async () => { expect(wrapper.find(SubMenu)).toExist(); expect(wrapper.find('li')).toHaveLength(2); expect(wrapper.find('button')).toHaveLength(2); - act(() => { - wrapper.find('li').at(1).simulate('click'); - }); - + clickTab(1); await waitForComponentToPaint(wrapper); - expect(fetchMock.calls(/saved_query\/\?q/)).toHaveLength(1); - }); - - it('fetches queries favorites and renders listviewcard cards', () => { - expect(fetchMock.calls(/saved_query\/\?q/)).toHaveLength(1); - expect(wrapper.find('ListViewCard')).toExist(); + expect(fetchMock.calls(/saved_query\/\?q/)).toHaveLength(2); }); }); diff --git a/superset-frontend/spec/javascripts/views/CRUD/welcome/Welcome_spec.tsx b/superset-frontend/spec/javascripts/views/CRUD/welcome/Welcome_spec.tsx index 4cd051c947..7bb22a067c 100644 --- a/superset-frontend/spec/javascripts/views/CRUD/welcome/Welcome_spec.tsx +++ b/superset-frontend/spec/javascripts/views/CRUD/welcome/Welcome_spec.tsx @@ -17,14 +17,46 @@ * under the License. */ import React from 'react'; -import { shallow } from 'enzyme'; +import { styledMount as mount } from 'spec/helpers/theming'; import thunk from 'redux-thunk'; +import fetchMock from 'fetch-mock'; import configureStore from 'redux-mock-store'; import Welcome from 'src/views/CRUD/welcome/Welcome'; const mockStore = configureStore([thunk]); const store = mockStore({}); +const chartsEndpoint = 'glob:*/api/v1/chart/?*'; +const dashboardEndpoint = 'glob:*/api/v1/dashboard/?*'; +const savedQueryEndpoint = 'glob:*/api/v1/saved_query/?*'; + +fetchMock.get(chartsEndpoint, { + result: [ + { + slice_name: 'ChartyChart', + changed_on_utc: '24 Feb 2014 10:13:14', + url: '/fakeUrl/explore', + id: '4', + table: {}, + }, + ], +}); + +fetchMock.get(dashboardEndpoint, { + result: [ + { + dashboard_title: 'Dashboard_Test', + changed_on_utc: '24 Feb 2014 10:13:14', + url: '/fakeUrl/dashboard', + id: '3', + }, + ], +}); + +fetchMock.get(savedQueryEndpoint, { + result: [], +}); + describe('Welcome', () => { const mockedProps = { user: { @@ -37,7 +69,7 @@ describe('Welcome', () => { isActive: true, }, }; - const wrapper = shallow(, { + const wrapper = mount(, { context: { store }, }); @@ -46,6 +78,13 @@ describe('Welcome', () => { }); it('renders all panels on the page on page load', () => { - expect(wrapper.find('CollapsePanel')).toHaveLength(4); + expect(wrapper.find('CollapsePanel')).toHaveLength(8); + }); + + it('calls batch method on page load', () => { + const chartCall = fetchMock.calls(/chart\/\?q/); + const dashboardCall = fetchMock.calls(/dashboard\/\?q/); + expect(chartCall).toHaveLength(2); + expect(dashboardCall).toHaveLength(2); }); }); diff --git a/superset-frontend/src/components/FacePile/index.tsx b/superset-frontend/src/components/FacePile/index.tsx index 9c2af3d7cc..6b0137daa2 100644 --- a/superset-frontend/src/components/FacePile/index.tsx +++ b/superset-frontend/src/components/FacePile/index.tsx @@ -61,8 +61,8 @@ export default function FacePile({ users, maxCount = 4 }: FacePileProps) { borderColor: color, }} > - {first_name[0].toLocaleUpperCase()} - {last_name[0].toLocaleUpperCase()} + {first_name && first_name[0]?.toLocaleUpperCase()} + {last_name && last_name[0]?.toLocaleUpperCase()} ); diff --git a/superset-frontend/src/components/ListViewCard/ImageLoader.tsx b/superset-frontend/src/components/ListViewCard/ImageLoader.tsx index 1bf6f57d90..ba4664327c 100644 --- a/superset-frontend/src/components/ListViewCard/ImageLoader.tsx +++ b/superset-frontend/src/components/ListViewCard/ImageLoader.tsx @@ -40,7 +40,7 @@ interface ImageLoaderProps > { fallback: string; src: string; - isLoading: boolean; + isLoading?: boolean; position: BackgroundPosition; } diff --git a/superset-frontend/src/components/ListViewCard/index.tsx b/superset-frontend/src/components/ListViewCard/index.tsx index b63f3afeaa..fee7d0bda8 100644 --- a/superset-frontend/src/components/ListViewCard/index.tsx +++ b/superset-frontend/src/components/ListViewCard/index.tsx @@ -147,7 +147,7 @@ interface CardProps { imgFallbackURL?: string; imgPosition?: BackgroundPosition; description: string; - loading: boolean; + loading?: boolean; titleRight?: React.ReactNode; coverLeft?: React.ReactNode; coverRight?: React.ReactNode; diff --git a/superset-frontend/src/views/CRUD/chart/ChartCard.tsx b/superset-frontend/src/views/CRUD/chart/ChartCard.tsx index fd307a42bc..34a91c1404 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartCard.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartCard.tsx @@ -28,7 +28,7 @@ import Label from 'src/components/Label'; import { Dropdown, Menu } from 'src/common/components'; import FaveStar from 'src/components/FaveStar'; import FacePile from 'src/components/FacePile'; -import { handleBulkChartExport, handleChartDelete } from '../utils'; +import { handleChartDelete, handleBulkChartExport, CardStyles } from '../utils'; interface ChartCardProps { chart: Chart; @@ -38,9 +38,11 @@ interface ChartCardProps { addDangerToast: (msg: string) => void; addSuccessToast: (msg: string) => void; refreshData: () => void; - loading: boolean; + loading?: boolean; saveFavoriteStatus: (id: number, isStarred: boolean) => void; favoriteStatus: boolean; + chartFilter?: string; + userId?: number; } export default function ChartCard({ @@ -54,6 +56,8 @@ export default function ChartCard({ loading, saveFavoriteStatus, favoriteStatus, + chartFilter, + userId, }: ChartCardProps) { const canEdit = hasPerm('can_edit'); const canDelete = hasPerm('can_delete'); @@ -78,6 +82,8 @@ export default function ChartCard({ addSuccessToast, addDangerToast, refreshData, + chartFilter, + userId, ) } > @@ -117,29 +123,40 @@ export default function ChartCard({ ); return ( - } - coverRight={ - - } - actions={ - - - - - - - } - /> + { + window.location.href = chart.url; + }} + > + } + coverRight={ + + } + actions={ + { + e.stopPropagation(); + e.preventDefault(); + }} + > + + + + + + } + /> + ); } diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx index 2ae3ab7dc3..e45d7fa6be 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx @@ -21,6 +21,7 @@ import { t } from '@superset-ui/core'; import { handleDashboardDelete, handleBulkDashboardExport, + CardStyles, } from 'src/views/CRUD/utils'; import { Dropdown, Menu } from 'src/common/components'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; @@ -31,7 +32,7 @@ import FacePile from 'src/components/FacePile'; import FaveStar from 'src/components/FaveStar'; import { Dashboard } from 'src/views/CRUD/types'; -export interface DashboardCardProps { +interface DashboardCardProps { isChart?: boolean; dashboard: Dashboard; hasPerm: (name: string) => boolean; @@ -43,13 +44,17 @@ export interface DashboardCardProps { openDashboardEditModal?: (d: Dashboard) => void; saveFavoriteStatus: (id: number, isStarred: boolean) => void; favoriteStatus: boolean; + dashboardFilter?: string; + userId?: number; } function DashboardCard({ dashboard, hasPerm, bulkSelectEnabled, + dashboardFilter, refreshData, + userId, addDangerToast, addSuccessToast, openDashboardEditModal, @@ -99,6 +104,8 @@ function DashboardCard({ refreshData, addSuccessToast, addDangerToast, + dashboardFilter, + userId, ) } > @@ -119,28 +126,44 @@ function DashboardCard({ ); return ( - {dashboard.published ? 'published' : 'draft'}} - url={bulkSelectEnabled ? undefined : dashboard.url} - imgURL={dashboard.thumbnail_url} - imgFallbackURL="/static/assets/images/dashboard-card-fallback.png" - description={t('Last modified %s', dashboard.changed_on_delta_humanized)} - coverLeft={} - actions={ - - - - - - - } - /> + { + window.location.href = dashboard.url; + }} + > + {dashboard.published ? 'published' : 'draft'} + } + url={bulkSelectEnabled ? undefined : dashboard.url} + imgURL={dashboard.thumbnail_url} + imgFallbackURL="/static/assets/images/dashboard-card-fallback.png" + description={t( + 'Last modified %s', + dashboard.changed_on_delta_humanized, + )} + coverLeft={} + actions={ + { + e.stopPropagation(); + e.preventDefault(); + }} + > + + + + + + } + /> + ); } diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 7e2e6ae6cc..59853bc21c 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -80,7 +80,6 @@ function DashboardList(props: DashboardListProps) { t('dashboard'), props.addDangerToast, ); - const dashboardIds = useMemo(() => dashboards.map(d => d.id), [dashboards]); const [saveFavoriteStatus, favoriteStatus] = useFavoriteStatus( 'dashboard', diff --git a/superset-frontend/src/views/CRUD/hooks.ts b/superset-frontend/src/views/CRUD/hooks.ts index 054b1e5138..b6963d4032 100644 --- a/superset-frontend/src/views/CRUD/hooks.ts +++ b/superset-frontend/src/views/CRUD/hooks.ts @@ -39,10 +39,11 @@ export function useListViewResource( resourceLabel: string, // resourceLabel for translations handleErrorMsg: (errorMsg: string) => void, infoEnable = true, + defaultCollectionValue: D[] = [], ) { const [state, setState] = useState>({ count: 0, - collection: [], + collection: defaultCollectionValue, loading: true, lastFetchDataConfig: null, permissions: [], @@ -164,10 +165,14 @@ export function useListViewResource( hasPerm, fetchData, toggleBulkSelect, - refreshData: () => { + refreshData: (provideConfig?: FetchDataConfig) => { if (state.lastFetchDataConfig) { - fetchData(state.lastFetchDataConfig); + return fetchData(state.lastFetchDataConfig); } + if (provideConfig) { + return fetchData(provideConfig); + } + return null; }, }; } diff --git a/superset-frontend/src/views/CRUD/types.ts b/superset-frontend/src/views/CRUD/types.ts index 39d0c81367..4ce59123ff 100644 --- a/superset-frontend/src/views/CRUD/types.ts +++ b/superset-frontend/src/views/CRUD/types.ts @@ -28,6 +28,7 @@ export interface DashboardTableProps { addSuccessToast: (message: string) => void; search: string; user?: User; + mine: Array; } export interface Dashboard { diff --git a/superset-frontend/src/views/CRUD/utils.tsx b/superset-frontend/src/views/CRUD/utils.tsx index 27e32df3f5..675e2b3fea 100644 --- a/superset-frontend/src/views/CRUD/utils.tsx +++ b/superset-frontend/src/views/CRUD/utils.tsx @@ -26,6 +26,7 @@ import { import Chart from 'src/types/Chart'; import rison from 'rison'; import getClientErrorObject from 'src/utils/getClientErrorObject'; +import { FetchDataConfig } from 'src/components/ListView'; import { Dashboard } from './types'; const createFetchResourceMethod = (method: string) => ( @@ -168,13 +169,33 @@ export function handleChartDelete( { id, slice_name: sliceName }: Chart, addSuccessToast: (arg0: string) => void, addDangerToast: (arg0: string) => void, - refreshData: () => void, + refreshData: (arg0?: FetchDataConfig | null) => void, + chartFilter?: string, + userId?: number, ) { + const filters = { + pageIndex: 0, + pageSize: 3, + sortBy: [ + { + id: 'changed_on_delta_humanized', + desc: true, + }, + ], + filters: [ + { + id: 'created_by', + operator: 'rel_o_m', + value: `${userId}`, + }, + ], + }; SupersetClient.delete({ endpoint: `/api/v1/chart/${id}`, }).then( () => { - refreshData(); + if (chartFilter === 'Mine') refreshData(filters); + else refreshData(); addSuccessToast(t('Deleted: %s', sliceName)); }, () => { @@ -201,15 +222,35 @@ export function handleBulkDashboardExport(dashboardsToExport: Dashboard[]) { export function handleDashboardDelete( { id, dashboard_title: dashboardTitle }: Dashboard, - refreshData: () => void, + refreshData: (config?: FetchDataConfig | null) => void, addSuccessToast: (arg0: string) => void, addDangerToast: (arg0: string) => void, + dashboardFilter?: string, + userId?: number, ) { return SupersetClient.delete({ endpoint: `/api/v1/dashboard/${id}`, }).then( () => { - refreshData(); + const filters = { + pageIndex: 0, + pageSize: 3, + sortBy: [ + { + id: 'changed_on_delta_humanized', + desc: true, + }, + ], + filters: [ + { + id: 'owners', + operator: 'rel_m_m', + value: `${userId}`, + }, + ], + }; + if (dashboardFilter === 'Mine') refreshData(filters); + else refreshData(); addSuccessToast(t('Deleted: %s', dashboardTitle)); }, createErrorHandler(errMsg => @@ -220,25 +261,6 @@ export function handleDashboardDelete( ); } -export function createChartDeleteFunction( - { id, slice_name: sliceName }: Chart, - addSuccessToast: (arg0: string) => void, - addDangerToast: (arg0: string) => void, - refreshData: () => void, -) { - SupersetClient.delete({ - endpoint: `/api/v1/chart/${id}`, - }).then( - () => { - refreshData(); - addSuccessToast(t('Deleted: %s', sliceName)); - }, - () => { - addDangerToast(t('There was an issue deleting: %s', sliceName)); - }, - ); -} - const breakpoints = [576, 768, 992, 1200]; export const mq = breakpoints.map(bp => `@media (max-width: ${bp}px)`); @@ -258,8 +280,15 @@ export const CardContainer = styled.div` } grid-gap: ${({ theme }) => theme.gridUnit * 8}px; justify-content: left; - padding: ${({ theme }) => theme.gridUnit * 2}px - ${({ theme }) => theme.gridUnit * 6}px; + padding: ${({ theme }) => theme.gridUnit * 6}px; + padding-top: ${({ theme }) => theme.gridUnit * 2}px; +`; + +export const CardStyles = styled.div` + cursor: pointer; + a { + text-decoration: none; + } `; export const IconContainer = styled.div` diff --git a/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx b/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx index 6a3387c460..28d3cb9c53 100644 --- a/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx +++ b/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx @@ -16,15 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import moment from 'moment'; import { styled, t } from '@superset-ui/core'; -import { reject } from 'lodash'; +import Loading from 'src/components/Loading'; import ListViewCard from 'src/components/ListViewCard'; -import { addDangerToast } from 'src/messageToasts/actions'; import SubMenu from 'src/components/Menu/SubMenu'; -import { getRecentAcitivtyObjs, mq } from '../utils'; +import { ActivityData } from './Welcome'; +import { mq, CardStyles } from '../utils'; import EmptyState from './EmptyState'; interface ActivityObjects { @@ -46,13 +46,10 @@ interface ActivityProps { user: { userId: string | number; }; -} - -interface ActivityData { - Created?: Array; - Edited?: Array; - Viewed?: Array; - Examples?: Array; + activeChild: string; + setActiveChild: (arg0: string) => void; + loading: boolean; + activityData: ActivityData; } const ActivityContainer = styled.div` @@ -82,13 +79,12 @@ const ActivityContainer = styled.div` } `; -export default function ActivityTable({ user }: ActivityProps) { - const [activityData, setActivityData] = useState({}); - const [loading, setLoading] = useState(true); - const [activeChild, setActiveChild] = useState('Viewed'); - // this api uses log for data which in some cases can be empty - const recent = `/superset/recent_activity/${user.userId}/?limit=5`; - +export default function ActivityTable({ + loading, + activeChild, + setActiveChild, + activityData, +}: ActivityProps) { const getFilterTitle = (e: ActivityObjects) => { if (e.dashboard_title) return e.dashboard_title; if (e.label) return e.label; @@ -99,7 +95,7 @@ export default function ActivityTable({ user }: ActivityProps) { const getIconName = (e: ActivityObjects) => { if (e.sql) return 'sql'; - if (e.url?.includes('dashboard')) { + if (e.url?.includes('dashboard') || e.item_url?.includes('dashboard')) { return 'nav-dashboard'; } if (e.url?.includes('explore') || e.item_url?.includes('explore')) { @@ -125,7 +121,7 @@ export default function ActivityTable({ user }: ActivityProps) { }, ]; - if (activityData.Viewed) { + if (activityData?.Viewed) { tabs.unshift({ name: 'Viewed', label: t('Viewed'), @@ -143,53 +139,37 @@ export default function ActivityTable({ user }: ActivityProps) { }); } - useEffect(() => { - getRecentAcitivtyObjs(user.userId, recent, addDangerToast) - .then(res => { - const data: any = { - Created: [ - ...res.createdByChart, - ...res.createdByDash, - ...res.createdByQuery, - ], - Edited: [...res.editedChart, ...res.editedDash], - }; - if (res.viewed) { - const filtered = reject(res.viewed, ['item_url', null]).map(r => r); - data.Viewed = filtered; - setActiveChild('Viewed'); - } else { - data.Examples = res.examples; - setActiveChild('Examples'); - } - setActivityData(data); - setLoading(false); - }) - .catch(e => { - setLoading(false); - addDangerToast( - `There was an issue fetching your recent Acitivity: ${e}`, - ); - }); - }, []); - const renderActivity = () => { - return activityData[activeChild].map((e: ActivityObjects) => ( - } - url={e.sql ? `/supserset/sqllab?queryId=${e.id}` : e.url} - title={getFilterTitle(e)} - description={`Last Edited: ${moment(e.changed_on_utc).format( - 'MM/DD/YYYY HH:mm:ss', - )}`} - avatar={getIconName(e)} - actions={null} - /> - )); + const getRecentRef = (e: ActivityObjects) => { + if (activeChild === 'Viewed') { + return e.item_url; + } + return e.sql ? `/superset/sqllab?savedQueryId=${e.id}` : e.url; + }; + return activityData[activeChild].map((e: ActivityObjects) => { + return ( + { + window.location.href = getRecentRef(e); + }} + key={e.id} + > + } + url={e.sql ? `/superset/sqllab?savedQueryId=${e.id}` : e.url} + title={getFilterTitle(e)} + description={`Last Edited: ${moment(e.changed_on_utc).format( + 'MM/DD/YYYY HH:mm:ss', + )}`} + avatar={getIconName(e)} + actions={null} + /> + + ); + }); }; - if (loading) return <>loading ...; + if (loading) return ; return ( <> ; } function ChartTable({ user, addDangerToast, addSuccessToast, + mine, }: ChartTableProps) { const { - state: { loading, resourceCollection: charts, bulkSelectEnabled }, + state: { resourceCollection: charts, bulkSelectEnabled }, setResourceCollection: setCharts, hasPerm, refreshData, fetchData, - } = useListViewResource('chart', t('chart'), addDangerToast); + } = useListViewResource( + 'chart', + t('chart'), + addDangerToast, + true, + mine, + ); const chartIds = useMemo(() => charts.map(c => c.id), [charts]); const [saveFavoriteStatus, favoriteStatus] = useFavoriteStatus( 'chart', @@ -70,10 +78,10 @@ function ChartTable({ const [chartFilter, setChartFilter] = useState('Mine'); - const getFilters = () => { + const getFilters = (filterName: string) => { const filters = []; - if (chartFilter === 'Mine') { + if (filterName === 'Mine') { filters.push({ id: 'created_by', operator: 'rel_o_m', @@ -89,8 +97,8 @@ function ChartTable({ return filters; }; - useEffect(() => { - fetchData({ + const getData = (filter: string) => { + return fetchData({ pageIndex: 0, pageSize: PAGE_SIZE, sortBy: [ @@ -99,9 +107,9 @@ function ChartTable({ desc: true, }, ], - filters: getFilters(), + filters: getFilters(filter), }); - }, [chartFilter]); + }; return ( <> @@ -121,12 +129,13 @@ function ChartTable({ { name: 'Favorite', label: t('Favorite'), - onClick: () => setChartFilter('Favorite'), + onClick: () => + getData('Favorite').then(() => setChartFilter('Favorite')), }, { name: 'Mine', label: t('Mine'), - onClick: () => setChartFilter('Mine'), + onClick: () => getData('Mine').then(() => setChartFilter('Mine')), }, ]} buttons={[ @@ -157,8 +166,9 @@ function ChartTable({ dashboards.map(c => c.id), [dashboards]); const [saveFavoriteStatus, favoriteStatus] = useFavoriteStatus( @@ -83,10 +86,9 @@ function DashboardTable({ ); }; - const getFilters = () => { + const getFilters = (filterName: string) => { const filters = []; - - if (dashboardFilter === 'Mine') { + if (filterName === 'Mine') { filters.push({ id: 'owners', operator: 'rel_m_m', @@ -110,8 +112,8 @@ function DashboardTable({ }); } - useEffect(() => { - fetchData({ + const getData = (filter: string) => { + return fetchData({ pageIndex: 0, pageSize: PAGE_SIZE, sortBy: [ @@ -120,9 +122,9 @@ function DashboardTable({ desc: true, }, ], - filters: getFilters(), + filters: getFilters(filter), }); - }, [dashboardFilter]); + }; return ( <> @@ -132,12 +134,16 @@ function DashboardTable({ { name: 'Favorite', label: t('Favorite'), - onClick: () => setDashboardFilter('Favorite'), + onClick: () => { + getData('Favorite').then(() => setDashboardFilter('Favorite')); + }, }, { name: 'Mine', label: t('Mine'), - onClick: () => setDashboardFilter('Mine'), + onClick: () => { + getData('Mine').then(() => setDashboardFilter('Mine')); + }, }, ]} buttons={[ @@ -169,24 +175,30 @@ function DashboardTable({ onSubmit={handleDashboardEdit} /> )} - {dashboards.length > 0 ? ( + {dashboards.length > 0 && ( {dashboards.map(e => ( setEditModal(dashboard)} + openDashboardEditModal={(dashboard: Dashboard) => + setEditModal(dashboard) + } saveFavoriteStatus={saveFavoriteStatus} favoriteStatus={favoriteStatus[e.id]} /> ))} - ) : ( + )} + {dashboards.length === 0 && ( )} diff --git a/superset-frontend/src/views/CRUD/welcome/EmptyState.tsx b/superset-frontend/src/views/CRUD/welcome/EmptyState.tsx index f145dfe79c..045ce7152b 100644 --- a/superset-frontend/src/views/CRUD/welcome/EmptyState.tsx +++ b/superset-frontend/src/views/CRUD/welcome/EmptyState.tsx @@ -27,7 +27,9 @@ interface EmptyStateProps { tableName: string; tab?: string; } - +const EmptyContainer = styled.div` + min-height: 200px; +`; const ButtonContainer = styled.div` Button { svg { @@ -48,10 +50,10 @@ export default function EmptyState({ tableName, tab }: EmptyStateProps) { SAVED_QUERIES: '/savedqueryview/list/', }; const tableIcon = { - RECENTS: 'union.png', - DASHBOARDS: 'empty-dashboard.png', - CHARTS: 'empty-charts.png', - SAVED_QUERIES: 'empty-queries.png', + RECENTS: 'union.svg', + DASHBOARDS: 'empty-dashboard.svg', + CHARTS: 'empty-charts.svg', + SAVED_QUERIES: 'empty-queries.svg', }; const mine = (
{`No ${ @@ -90,55 +92,59 @@ export default function EmptyState({ tableName, tab }: EmptyStateProps) { // Mine and Recent Activity(all tabs) tab empty state if (tab === 'Mine' || tableName === 'RECENTS') { return ( - - {tableName !== 'RECENTS' && ( - - - - )} - + + + + )} + + ); } // Favorite tab empty state return ( - - {t("You don't have any favorites yet!")} -
- } - > - - + + + ); } diff --git a/superset-frontend/src/views/CRUD/welcome/SavedQueries.tsx b/superset-frontend/src/views/CRUD/welcome/SavedQueries.tsx index f7b65094a5..6de2f3c8e4 100644 --- a/superset-frontend/src/views/CRUD/welcome/SavedQueries.tsx +++ b/superset-frontend/src/views/CRUD/welcome/SavedQueries.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { t, SupersetClient, styled } from '@superset-ui/core'; import withToasts from 'src/messageToasts/enhancers/withToasts'; import { Dropdown, Menu } from 'src/common/components'; @@ -26,8 +26,12 @@ import DeleteModal from 'src/components/DeleteModal'; import Icon from 'src/components/Icon'; import SubMenu from 'src/components/Menu/SubMenu'; import EmptyState from './EmptyState'; - -import { IconContainer, CardContainer, createErrorHandler } from '../utils'; +import { + IconContainer, + CardContainer, + createErrorHandler, + CardStyles, +} from '../utils'; const PAGE_SIZE = 3; @@ -50,6 +54,7 @@ interface SavedQueriesProps { queryFilter: string; addDangerToast: (arg0: string) => void; addSuccessToast: (arg0: string) => void; + mine: Array; } const QueryData = styled.div` @@ -59,7 +64,7 @@ const QueryData = styled.div` border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; .title { font-weight: ${({ theme }) => theme.typography.weights.normal}; - color: ${({ theme }) => theme.colors.grayscale.light2}; + color: ${({ theme }) => theme.colors.grayscale.light1}; } .holder { margin: ${({ theme }) => theme.gridUnit * 2}px; @@ -69,17 +74,24 @@ const SavedQueries = ({ user, addDangerToast, addSuccessToast, + mine, }: SavedQueriesProps) => { const { - state: { loading, resourceCollection: queries }, + state: { resourceCollection: queries }, hasPerm, fetchData, refreshData, - } = useListViewResource('saved_query', t('query'), addDangerToast); + } = useListViewResource( + 'saved_query', + t('query'), + addDangerToast, + true, + mine, + ); const [queryFilter, setQueryFilter] = useState('Mine'); const [queryDeleteModal, setQueryDeleteModal] = useState(false); const [currentlyEdited, setCurrentlyEdited] = useState({}); - + const [ifMine, setMine] = useState(true); const canEdit = hasPerm('can_edit'); const canDelete = hasPerm('can_delete'); @@ -88,7 +100,27 @@ const SavedQueries = ({ endpoint: `/api/v1/saved_query/${id}`, }).then( () => { - refreshData(); + const queryParams = { + filters: [ + { + id: 'created_by', + operator: 'rel_o_m', + value: `${user?.userId}`, + }, + ], + pageSize: PAGE_SIZE, + sortBy: [ + { + id: 'changed_on_delta_humanized', + desc: true, + }, + ], + pageIndex: 0, + }; + // if mine is default there refresh data with current filters + const filter = ifMine ? queryParams : undefined; + refreshData(filter); + setMine(false); setQueryDeleteModal(false); addSuccessToast(t('Deleted: %s', label)); }, @@ -98,9 +130,9 @@ const SavedQueries = ({ ); }; - const getFilters = () => { + const getFilters = (filterName: string) => { const filters = []; - if (queryFilter === 'Mine') { + if (filterName === 'Mine') { filters.push({ id: 'created_by', operator: 'rel_o_m', @@ -116,8 +148,8 @@ const SavedQueries = ({ return filters; }; - useEffect(() => { - fetchData({ + const getData = (filter: string) => { + return fetchData({ pageIndex: 0, pageSize: PAGE_SIZE, sortBy: [ @@ -126,9 +158,9 @@ const SavedQueries = ({ desc: true, }, ], - filters: getFilters(), + filters: getFilters(filter), }); - }, [queryFilter]); + }; const renderMenu = (query: Query) => ( @@ -186,12 +218,14 @@ const SavedQueries = ({ { name: 'Favorite', label: t('Favorite'), - onClick: () => setQueryFilter('Favorite'), + onClick: () => { + getData('Favorite').then(() => setQueryFilter('Favorite')); + }, }, { name: 'Mine', label: t('Mine'), - onClick: () => setQueryFilter('Mine'), + onClick: () => getData('Mine').then(() => setQueryFilter('Mine')), }, ]} buttons={[ @@ -218,35 +252,45 @@ const SavedQueries = ({ {queries.length > 0 ? ( {queries.map(q => ( - -
-
{t('Tables')}
-
{q?.sql_tables?.length}
-
-
-
{t('Datasource Name')}
-
{q?.sql_tables && q.sql_tables[0]?.table}
-
- - } - actions={ - - - - - - } - /> + { + window.location.href = `/superset/sqllab?savedQueryId=${q.id}`; + }} + key={q.id} + > + +
+
{t('Tables')}
+
{q?.sql_tables?.length}
+
+
+
{t('Datasource Name')}
+
{q?.sql_tables && q.sql_tables[0]?.table}
+
+ + } + actions={ + { + e.stopPropagation(); + e.preventDefault(); + }} + > + + + + + } + /> +
))}
) : ( diff --git a/superset-frontend/src/views/CRUD/welcome/Welcome.tsx b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx index f35effcbf9..237a2c1dfe 100644 --- a/superset-frontend/src/views/CRUD/welcome/Welcome.tsx +++ b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx @@ -16,11 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { styled, t } from '@superset-ui/core'; import { Collapse } from 'src/common/components'; import { User } from 'src/types/bootstrapTypes'; -import { mq } from '../utils'; +import { reject } from 'lodash'; +import withToasts from 'src/messageToasts/enhancers/withToasts'; +import Loading from 'src/components/Loading'; +import { getRecentAcitivtyObjs, mq } from '../utils'; + import ActivityTable from './ActivityTable'; import ChartTable from './ChartTable'; import SavedQueries from './SavedQueries'; @@ -30,6 +34,17 @@ const { Panel } = Collapse; interface WelcomeProps { user: User; + addDangerToast: (arg0: string) => void; +} + +export interface ActivityData { + Created?: Array; + Edited?: Array; + Viewed?: Array; + Examples?: Array; + myChart?: Array; + myDash?: Array; + myQuery?: Array; } const WelcomeContainer = styled.div` @@ -70,25 +85,93 @@ const WelcomeContainer = styled.div` font-weight: ${({ theme }) => theme.typography.weights.normal}; font-size: ${({ theme }) => theme.gridUnit * 4}px; } + .ant-collapse-content-box { + min-height: 265px; + .loading.inline { + margin: ${({ theme }) => theme.gridUnit * 12}px auto; + display: block; + } + } `; -export default function Welcome({ user }: WelcomeProps) { +function Welcome({ user, addDangerToast }: WelcomeProps) { + const recent = `/superset/recent_activity/${user.userId}/?limit=6`; + const [activeChild, setActiveChild] = useState('Viewed'); + const [activityData, setActivityData] = useState({}); + const [loading, setLoading] = useState(true); + useEffect(() => { + getRecentAcitivtyObjs(user.userId, recent, addDangerToast) + .then(res => { + const data: any = { + Created: [ + ...res.createdByChart, + ...res.createdByDash, + ...res.createdByQuery, + ], + myChart: res.createdByChart, + myDash: res.createdByDash, + myQuery: res.createdByQuery, + Edited: [...res.editedChart, ...res.editedDash], + }; + if (res.viewed) { + const filtered = reject(res.viewed, ['item_url', null]).map(r => r); + data.Viewed = filtered; + setActiveChild('Viewed'); + } else { + data.Examples = res.examples; + setActiveChild('Examples'); + } + setActivityData(data); + setLoading(false); + }) + .catch(e => { + setLoading(false); + addDangerToast( + `There was an issue fetching your recent acitivity: ${e}`, + ); + }); + }, []); + return ( - + - + {loading ? ( + + ) : ( + + )} - + {loading ? ( + + ) : ( + + )} - + {loading ? ( + + ) : ( + + )} ); } + +export default withToasts(Welcome);