From 257ce8bca22da96e9d86f62b1a7e0267274ff58b Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Tue, 8 Oct 2024 23:02:24 +0100 Subject: [PATCH] integrations: Create incoming webhook for Airbyte. Note about the documentation: There are currently two "Save changes" buttons on the Airbyte "Notifications" settings page, so the instructions specify which one to use for clarity. --- static/images/integrations/airbyte/001.png | Bin 0 -> 39721 bytes .../integrations/bot_avatars/airbyte.png | Bin 0 -> 3137 bytes static/images/integrations/logos/airbyte.svg | Bin 0 -> 2244 bytes zerver/lib/integrations.py | 2 + zerver/webhooks/airbyte/__init__.py | 0 zerver/webhooks/airbyte/doc.md | 28 +++++ .../fixtures/airbyte_job_payload_failure.json | 83 +++++++++++++++ .../fixtures/airbyte_job_payload_success.json | 83 +++++++++++++++ .../test_airbyte_job_hello_world_failure.json | 3 + .../test_airbyte_job_hello_world_success.json | 4 + zerver/webhooks/airbyte/tests.py | 70 +++++++++++++ zerver/webhooks/airbyte/view.py | 97 ++++++++++++++++++ 12 files changed, 370 insertions(+) create mode 100644 static/images/integrations/airbyte/001.png create mode 100644 static/images/integrations/bot_avatars/airbyte.png create mode 100644 static/images/integrations/logos/airbyte.svg create mode 100644 zerver/webhooks/airbyte/__init__.py create mode 100644 zerver/webhooks/airbyte/doc.md create mode 100644 zerver/webhooks/airbyte/fixtures/airbyte_job_payload_failure.json create mode 100644 zerver/webhooks/airbyte/fixtures/airbyte_job_payload_success.json create mode 100644 zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_failure.json create mode 100644 zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_success.json create mode 100644 zerver/webhooks/airbyte/tests.py create mode 100644 zerver/webhooks/airbyte/view.py diff --git a/static/images/integrations/airbyte/001.png b/static/images/integrations/airbyte/001.png new file mode 100644 index 0000000000000000000000000000000000000000..f90bf6e885c78a07e4aa88e2930f99957172e20c GIT binary patch literal 39721 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

#VX?PZe*@A0>%2*M;1 zj?OXSjnI#IvI4bt1uWC6(vXkLaJ4?|oT?am5ar<%MkA4B*NtdHugds$Z?arJYH_VF z2bs~clu7q9eSGwIQiQOZCeD#Rcfe&c%gacE8^%DnSXx3zErrD}+Xc`Xx8q4%CjhzN zr+#A!pAs8kw>a4cCVfS8R4KH7pZsWP zs79uKk>Ib#4T6G<{j_3<{!dKcXmGW9g}yuaHq0O74hU$BBzG!AY+s=WZ%BYd#s z&Dk1ZL(R7VRuGtWjk-_Om5@&?US_GrL-H~IQ|*hz{|oxgD=X^^ThET~8mXx2!uVr8 zF|A3;UVet8YHkiM7+o*@Ai{T!pNBf=6Eky`OHUQn)VbWXyA$JN9$PhqMl(3e1*HN) zyVU5$d1XdnL@wxh41#`j82A}$>F#}P4AFa0y@t&V!NFD}dY^--@22uE8a9|G^leGK zKUD+BPsoH}Rk?mfR(#V=j*X-ESxdi^^9AGCQ;e#g@U!RUzaQn7Tv&`pcC(-TYdN^KO6ZaY{I$P)8fwC8%(c1OzY)&A` zAnWV=9Bew}u?~`$VCt7)?>7>&=nwz;dzT6j5WBRq^qL9RV}8;4wJ3rm>$fb0i2wIi zf{cPt994g}aL`KB{eQ-CVO_Xh^em4Rd7wVtfztg}_usjV+XX`Q=?NjZX|p77rqY+a zG`ZPp+PFuUlza--)1p+icrjeQ7L3-;QxZN6fOBZ`dk@T|22)I!rx`oPfJZ_1kb_G+p*$CD{kQ~6X zBcEN7!NqM@_L`Q7_apN-g3kcYc8V)1StH5gKi#P|XkHm|6z@GTAV@muZa6g z?c0b7ID@%~_&On5`gb^l!;8S*#kTtf8oLnB;c>=K!YEkuDRDoIdne(jc--+7{C!Ya zVFQx8Omi=Wa6EX}w`%)9yGKi}2OlWpgTs7o=6VFnn~9W_$e?D8G!7@hsB|a)n@U?vFQr!T!a}k;yyHwwP z$@PvS0g&hQ!^atiNSqtEfN}_v$%z3@en!A4XW{8g=CtP^-blabL8^G@rkSVI7cyd7 zLu}qq;+l*=CqLhA?p5*}}PU zIYALDT83CLwlegK?uzYyZy8GF7g%ckR0E6Je3OtAs*;!r1O57yH zq7?d0EZV^d?V|q>N?N7UZ8q*)=z|f=eMK)c4uR|PHA2>eQe;SKxt0-O>_|>*_^V>S z4~tqMx}-cx{b1d%qft%V7}xmC03`J&7YGv5U)FSmE*Ej)=W*g{M&0g^mN{(U!=ZJV+ERy=M0B{=S`x&++o9Fv zEHCwllkMsf?lNlch}ASZBD*Kqjs1jiV(6g^hnp$3GD*kZZS{32gm*zXaz*Yff}w5M zC#^8Csr2t)=K-3yIlBLYX%$W1{xN5NNhMrkbU1OE>Mh)ZA#h6;mcliK%U_Nst0FSn z=c}4L`*=lZM0?tlaQ>OO*xQZ<*#uQPW85ad0TEP=ChI)n5+iJmHOJ*{H42 za%SiT86oOk1Hbl>Uwc=6oaB??ClVVT#wZ^+5x8gW{KdPPha~+qUIl{9A#|7lfyrS{ z|6b1>P$2D;+HZ67k|T*q*=dG}@N`dWI@!1Ry??QEnN z>#nJimXct84kAe5pV40F{)3(Rvvk&8ke>|aX2}aRWGWv4oF%x$ z|MP-6UM{~E0RrD=y*6@1PQ#-F^`9?;!3E`o7zhx|+b#o%$4NAZ63Lh4#6SH{sUCb) zl+3ouj5Ap4UQ$03jNXTt(uo_84HwX2gE82yr3-(lE-5i6fI~d8Zh;-Xy;1P}Rfpc1 z`M3?h04%PT zpwKB?-leeK1+KKEQ391EI-z5cf#D|>*gYsn<8>XU;bGM=E13s%21hXfh>z+UqC}3* zgxmhr3oe^XKzs?4g#s^8A=}@D5mwuRN`w8p5DxFFfJdKjV&dXJ7i4}c8vWyR?2_4>Phj?$o!kb8>ct?JLnnI(9lpn z$PfRB_=SdxN1JJpHmadMo)vK;L6dmji3SBuv}wwWOWh9CmmlOFSv9QZ8^$zFKU*xL z;*mYK8}DZA(|MPq`5#@$GV%(1-&OtJ)AUFO5=RczoxTiQF0GmveaBn=vdfZCU(>r$ z!Cx(V0@WpLA^mSQtL!(ZlTT~!9z^l;%vyLqQHwj&Jb!soUa9JE|fl2pN{D22%-( zX_Qu`=7)drFV7oqJVaDfd$lvQA>-rYLvB;k!a8;{zb#u@2n6E|XkF?4Kg({(n;h%Ye69Pm*fjKR!jJofS z>*UVKz}P$04Nwd9^9Ro_fe&6P&`-|MNsSy16ej*9+T%+5R&5C(zylv|gNE|^CzF+A zrhbLmz?OGwx4SKs@X)VF1K6_oYg{4&3St-+qd%v8M;Y|L00>Fl`9^vOw?bL&25!VM zkwa%wy@1KwB$^PToR}gnc{l#hF7kUF0-p?m!^d9}Gr;llxP=qETEiVE+w?8yG883A zH7}C#$gYe3j4!E;dinNFLS_e37@c-2-z~xO6i&Wlv8;OcDt%)98Zj`px6MWisk4h| z`22{+)I^56V}L!e4_a_~+gfbC|?x_>F9j5Tr+ze)orf-%0g^kk04TA zj6o&S3q(6;kmx2ne0>TUjrLn&u5Xy<4EI=Um6B_$t3}=Mt?@^V954v@>(KAy1?{3S zt(+!#8O#_T4iF0@Zi`J@;gP~hl1W4#RDoahA|#XKc0fcdN!%?i45NEIQjK6p70V5Y zLR8rAvxsT@2#wJh%^zNt91=LU3U2l4Rp-Q91aT35Xej=xPu^shL{JI8$wybGh|o0N zD8_vEE%FOF!^fC@rzS0)pc1g8gpquWr5$B+l;)c+erB8jcrH?TDCv#ZkYLGYCAV4| z&dul~d4u_E&{g2*Nu$3fFA_2D>E){RlD#Cxd$54@1f$~^9Yk74c$2Cmwa9v_-C!_J zR8VQu#4Aa1#10@kmE8>`CyQ9pDc>b=1&z8X(y`nsqg3g>==T-GGcYJ`FdSr**xbIk zCZ45KKMM9=q7M1_rzvmsA$N+{lGJ?Yd$8w=jlX{sdjswzy}xU)IC?OPD|bGNkxt5{ z-6+rH$coQPGJ5JFT`2)Kwci;M9*+b~eieLODP_Ahe&35~33)IF0k; z_U!s6c~d&P$IkQEX)R_T_HoF#nh`dO9t=d^=Jb(u?#dG*JU$W9J)HQp%m+uTFtv4% zi27O@=zFC&?&HmLuS1LN8#)5SCkmIfiLZW!tKGgZESIZhVBHzA1LAXGRRl{$LCC@t+UChcmj)~FtK#6beEX?d zaKF@l#nA)QdQtFzi$CP`MOduV3r-`613U$@KUo-{{O*^{;suaq6GXfA(prt*9fj@u3eFoK431XM%U!AGeNT!<KRrf+=%zR7;6F6mDaro{ENv8^c0=lLs8g;L{o!AjR5Z1OUy0F7PXc)E@ya_YLq1CQ3q2>z4ug=#%;2l8NWozj6itTrjErsKJd$X`WCJE)C{q!(tB~!u3mt%vD($o8q^kF;!a+M#0Oy_ghtn2(NJ_ z&TGx2z)>Y~Y0;#}POcxz`sDD|nTNYI@TwzBLx;xXHq7~pn~b*v9}JurBqTH-x^4iv z929_LuTXgk;x8u5!UDPhU&D1xJFd=1jzx~j(BP{j*Pt2DmqsSsO1P=V-^Dpt7>l^t zhk5dQCe#K}_e}YWmz4EWh#NT%1|Tn|?{$0fgsb;=Q(!DfLh2tK&5S6}k0p=1Z5?0G;zDU9fuF?s>yXdOGk2QRhY zRyRcoR1Um-mD7^l%?qtB7j1m#(NcY58)kpEa)&mi9I_A`4=F>Ii-ONTxXPU#RdWbT{){}P*7BYz2rXa9-oF%BfvM$${~>H zN9jz;dR}=nrx>uB6+mg<>-fN|B`#{%llj9@xw&0s-#V1!H^1Ys6OTZdV0t$ z83RXcOh945@PrE-#%F)5)-NAs{l^r)ASb~Tu#Kgal<&$p1(uY+uIl3P3EQg`N5$F1 zh%Hy`&i0|v*fH38sY{N5IkYC=B_<}8awD)6J!qx267NT`75a85QM=d4mV9{nkw)#O z;HCQN>K5+Zb|$4u8b0uFUrIR)QO&Xm!QK49x-{nfl>&R0=1_&!TT4|>_bMLq`SKdw ztay=EtyHFtPn6pdGL>VYZRp=41B$*3bG=;0q1C8PQpuYIwE%FsgHxiwT5^^n2@)Y( z83veaWa4*oVrw6+T@DN3&d(S(4FKAHo7$&?2;fFq^nQ#JOpdtAR#fX!P>7S*i#Wi~ zg$4B}d}m|A_%57+q_JKeCC4xwaiiQSjmrQSKXC)}Azx+kPC&t}<<>29>4MU(!X2@K z7R`l~w+l`8r47!G5qK+#VoJbMs!i;fGT7#2fmpODKyjC?7Jd_>L)8;TuG}vx_fX** zk_~#v)|7pzA72n-ye%jzjQ@U!-LEr9SOkIL@27!k?#=R5>a6h|cAzkv1^FeVw5z@! zra)Utc8VB++uU#_d}MmWJ(@XaNVP`~ zsu+?EJY6i7`l*r2j4UWj8PyHe{h5HfjEG}S%+y0Qe-?}rRf|FAv@FHb!{Uv52Fwem zp+aVM_tbJ5Q-#qTcvD4+jR0|P8|E&}N1iwz#GAUf}! zKK+dY8>i?7vP|<;9lLikQv5VRcV8dV$q%HG@~?7!P^t+h@lm4L#tK|8NW+u01uxo5 zl)7%50k|n~_3Fokd3DP|WcsiQIUE4Sw?#z}AH(Amhyk2f-4CIVX*4mnG*JAY{lB)F zExGt}enUXN)ctqbF#0NU8=IQ+r&D^oe#HBL)D~!P4KAa}5|uG1GYt#}=@&&h0~~N4 zR2U?8Mh{Q7&~O86)Yygj^=kNF587Y0Y2zhR&|#ZMsVFKauAa58i`;{G9!=0D^9`5L{V zpGp&^>IsILA8LyNS$ zl3&=dS|@&?JeF`#Ms_ug!gwHImA34FE9?|7DKyfcVNTK>QNBx?J2jUNmNMFcn0t@D zs>&shTt~<;A%-gleKzWhVO4|8P?oO`i-vNT891HAiZJKB2~}Eo4@%aH0@Wgt$EF}- zn}!Eft@RD@!}!x~GNZ#@(b5C&M5aYI{&CVjK=o)S!8hl>A;_R0>lpBizxOAKWoTGjtS#Y>=Or;-%nzx~+=GPaSz466=JFU+dq?yB zkQ(EOVte8>!5lS_9p-~1=MP-?is}x+nUMn^{E(3Hxc8N6GPT{ndnnrdBW{1pb-)mi z>qbTh`)hloCo7gZoU=+k3I``f+o)Hvquz%3rRm!dz|9^zU!`2{Gi+)hF&Ox`h-Y;{;9yUDgQ82_m9r*NY1miOg9ZyYBMz2I_ z--z}4o*ppYC@PA8@tmHly$Hxoj9j+b+rB!0%(=NS6IZuutrWl2CP_KI2XQS?cLTAH zf#C+|$`iOlEg!L-ViClSH4ScLNZ3*JG49M6&N=$r+$c-C9{%mApFoaSY2*}qo)D2n zQ`>G;PNUnkqohsZC!==n`~ znw?G{*If&kRM(`?qgox1aUu?FJqK2(A6vofP2EXo8qcM_%~C4>;baU&89T03&V7=5 z&`lBo5Y|d#^8i_0!7KJw>Hp^;D$@bc6Ikc#I~RLYe#d!OJc3>9uz`N>*iG||FM`2O zivCYX2GQ(SFMU@VrZ^d|j3^Y%G4hEOf}juVhI!|KvL}yEFGGzQ67txcek;}>1b2l4 z3p|kgL+B?0NQCzaP7lF>0654Kr3+|xNR{->@S{hu!e z`SOU2gOW-_?|w5tRy6Zv?WCVlN(!W%`%e5F5+q&~BY}|Z{7x`lUGd3BN#D!ahBn=~#^G>m5%Q2p|wN$1Iz2o{8Ergg0R z%~9!&9}E7^Hg3ywH~5w{qJOrdae^?A?TOoYA4WWmkUj<()KfJL8RpsyEV|H^&V1FaRl@q8_$cDll_`KnAi`yTKd~}H&6l$|%IPn)M zQ>>jU3>wXMyT|joawp8rer4OjJE@5lS(oypQu^YA&B=qC;m6G4*Z2KWQqKe#*tC;f zS;2S@qu&boCz0bX*r%~DnpPkJMi}stsFn?tT_l7G!bp_QZ2ka6ro_9Yu}~1oJJ<=X z7IR-0d*R^#m2$vI3beDud`CgZd*UNcohP6Bt-E4 zl;m}%+q*l5G=}u_bk6aclSl5+nl6qh{>Swg2EFzq29T@vy|vC(a)o&`C67~Jw6)aM zWp6N0^E1k!`(A->*uy%z$yn9zn`gzgtw#DVH{(a|xKX?JtUQQys`i@;Q>SxEfp(_= z^%mL0m+04BBpe%QrPD6^`yy9aO?Og_PYuIO1)P`>eDH4_uTBh+dvnZQeEdHLhkuAT z&?(cR6f5F}(V-P7eqza*vwsEn5n?=_-;CGeN&H`}oo7@N-Mhs>S|~ySgx-rFy@M2i z&?8lPZz4tMAV`xEdR0I`dQSj>AW~FN0!UNog7l*FA{{~C4(R*dd;eeW&B~{&tYprt znP<*E`?sG{LtDhb^?aI!r|*-`s;B)%If0vn*z_0mB{j+4ZxY_^ zRLUj!y^l1^GEFv?;cSM3f2Jfz zL;y&wp6CMs@)?#R0<&w7*M_*cMpJr0O2R1il$}=jclb$4`zM;6jD)qTk_0zp{Oq>N z+gO&7y}C~zNu5T3U*|pq(|ozGeGO3kwg>)x<6?Ig<-I8iS|h)Cc`$zf+ksR|#V3v|Q+n0Y*YocSOKPHMT$LAjU4-6G#I?I>R^2k+#Q?6!*V~i<5{# zPJ8YAL%@JZlaJt6Lq%H^ zLOoRnPqE{&OM#0jI#*%h?cw}$(*jSs6u;TvMgxukK%uwUBG%5-U@d3t9Q z?$2p)!@dt+0-CdI8Q>2q{T!*)kJWB7Gt^06{is*fFJbFv5T)*VE>DRHADLdI7jIKN z1U6W|UQ7jSocm*CjR-oTOW)<4X%jQ7puaWAZuad?$=ZS$mz+prKF`{0kIGaJbg%p0 z<>?adGGS;LgUiaJ0aLR|`oxTA{`g zqR*^&6NcWn_W7=^5HWmFz-KhN8J*pZ@RVbv`C7>aaS#zrI?puA`rHdiCfkj2a}}dv z)+5i(h-*41ZJ)&pDEUIFJeP*+37!7bVr`ri6Jqb_Y`Lma#&(t|tUa>$@Tohe_S;oU zzKO*o4PObohnuL>6Tct^#g zdH*&lp4}MdX-($fG6G_{bX%obd=R@=SneHIiWMTEZ?t%7+IqikE_^`Hh#fZ`vN8y@ znz6EDmPZ8JuN_$t9Bac6<|3FMKA99x6^4Uf5Ek#S(Jwz{^Cc>6C$?hKVp_CETRIy) zVnAc{b*-TNQ=(a)C@s#5NSl1esAGHSR1}^IGxbQ)E&+QdnI-AA6jsYR5yq*y2+)qf z<<^v6$jaW!>3>T{QntC?USI5c_af>r9JBiueqg^$7hlqTCY;gQHHXMTVRS{Z%x7b)%Arfeatww)v;>3mK!WI~mg2P= z#hP3CdSA-W2&N{;1s zWEzS3a(A&EtF$|8dPU4tUO#wO4X+vQt+svT;O&R1Jjpn!O^bs7qhSBkDQ!;$5f zPw4gQek{QThtSFni%gI>Nl@cGFx_xP)pHoL#HkSZdJqia!<%$^bpZMf(`mg6TN1v8 z7rRUiKCYLi=o4c&o>GfS;xYAa$Z^26kxixA-z{~X*GVG!XuEYk*Ut@IDN7Q4KL8+X z1m=)>0fMNZ{3q}=N40FtL8WRkZ59p(8anxA1>dAegFH_2u z-YrFnQy?<`gcyqVF3GpKC{PC0G9&lbd&OF}_Wn5h-hTgWaJN`BXfkZ!ko7AlyN}+S zoN^PXJ?8Y>sv+fGM{tFI^v4{az{d@G;ai}9d)0}m;~s+k{--QFT7=c27IQKZl!f-e zoi%2H~8w9cICL3c0qE~#|j=jcH3vj_o0AuauecNLl%*d0k-V=%eQ6* z(f}w-It$+|A5gT&BevB&UEAtP$kC8c$Ch!CJ6EK%ZJDt$-u7zc(|RGAYQJM*jKn6w z>&(6d6 zfr-lj2G1n-oOfyei^6_6D$4(A&EVf{el(K=Qe~D${(IzLJ6#BMF1QxK`ew zVV$4lZ(e`%{EG299tELOW?pA3*{xr39`*!R-TGb?B|0>0-_ zsk&Cjb|QhLvkLs?jC5az?0uasyNxQG6)_>2)Vx)kGxwdC>8gk$Z%9lQlt?G=`m@=* zm~lSz9b#kuBOl29mj#J3?}7p9)}?mjpm`w(0~U~5tmY z>#+Wv#0Sm&lD0qdpAltX1DbIabk^gqndAD0GAK)+e(}WScO&NCG zJXNTC`xYyuDyzpQI~#y0E#V7wPZH{t=c53f00DT4HrV!@58j5D)lR74RwGc#JWP4D> zf_{pRa;E#hPvboU5;@*GBI(RITC({9@-wRpr6Sy0?7R@j-2djHEK7|Tcw8x5o*(m# zCQ^!=Srrc8f`&Od47^M6PuM~+q?PG;5U9c7{urHv@AtKlha1=UvgWQ+U)qT0vKEm6 zgeOjfu)wz;(3V(bNq*68I%QrVdn@l|6^8<`J$!vAVUFZ^+f_;*TYC?6rKqBtn5TuE z6UxbL6kfTo{ce$)gMbM}Pm2Upr#PswSPe#`shf^GpboY;hK|+<+0rJs1HbCX#HsLJ zD(LvpBLw@8%|zqxm#h*&O?s(JlxEzYS#xW4y~@?5yHu;fDc!0SM?w<8f-0FPoLh<36xW_CCKb)A)^X0qa`)mS=D zcLDsF1VyJnPR%w4b&{`D)`@uTZ`kd7!J1-kLbDcVSP)Uy7gSGerY1er<(!As3vYy_ z$A8yhgZyf!<15W$fC%^`g!~mTy`-0e03bmu0HP={uCeMB#DR{v$L5ff4EvP1Z6SaB zoVqJ!O&T#apx%+|(H1ZI+@_uH+mdzp3Q>HT^=PQsu;G#a9sgL;p5ZP#)A%evp_N4; zbiE!fCxsFUkN<3ASI>pUe}{2aQ?fSQ=dj%_yNIZ{4VlN0o7-z>YvDEKH3%z~2?|DV zbN>|>+6s!cuA>}(u1g>^0>ov5kEZWFRRGv}w*l6Jc9m}M`zZoElbmz?3}eP(VG2TP zhijWKbdWs|eLcps__`+e8aKU-l`kKDswZ7*l>8}JOeTdWPtHY1!?`pfZyQhfOp`fA z=pg=hO<(kty5X@vgcddH3EXK4L9UCPozx0Cv%z^(or^p98m$h74VFf88BAD=@0VcD zmBmY1Y$Bd}_(DsoqfSb^z<n8WzkF?P0IKTIM%~^EZT4K5cU=y>@%D6 zh-E3mGIvy!rU(VZK0AFCQ5Muv-K|hh2xyen_pM;*T3jk&f1En*ZqIk4)x3?fM}r~z z{5&VLb03ZNV)hVS2%j4ZpW_~)6~9fsWB0b>m#x3wtC#>Ma#-bb^W8>ZP_i(-cj91* z(FpXtEgP}EVOV9U3YYv`8PHYrgYiO&EhRw!|1KXDr}g00%^Bcdz20jo8Cu9wv)up(w&o3l=||v+|||YO>YZ;YJ)7nu0SV& z8*EC;Nd=~rXm3}f#`wJP@OrkzjJ-KlR(X#9ULw;L_%4!D`CUR!AEc2zmbVTb@`lV1)4@Z zBYs%KbH)cZsbb{Y3zjQ%dy#P9^YxLt(*_Xt;v#3`2cBA2Kh!<~{{OKoPz^S!Mn27>F>ds=HqN2y3&GSDe`x0;y#$;bdbo~$DO*^Dvb^h~REeZ^tR;qNRK zm=gW7hvO6n4V{=%D7~Z`6<+Sd1g2TV{^8WqXo}^1J_bPOUA~u?!Z-Zt7JKrz-sWwb z#-)(5$l1WJ+Uq>OX!c(AxHvm%|9ZHD16J#&WZg^VmCASmi zYoxFkwX<$(S4Xtd6 z&PZbFk~jY}3R$Yb{&rDqX?m~kgg-Phg zjSLR<$jHbh{~b27dMDPm9Nk)|1uW2?aRD+`%PCZ}nRze;cg>UqZR6-z-tpX4PYBG( z$=MY2tImL7e{yO=<4?5));|Mv9kC8>4BS)u9%X=bY*R1&x0Oledx_C=5BjV634HyD bc<2SeC&iPOyV1HREa0V~qODvFvkCt%Z7K|9 literal 0 HcmV?d00001 diff --git a/static/images/integrations/bot_avatars/airbyte.png b/static/images/integrations/bot_avatars/airbyte.png new file mode 100644 index 0000000000000000000000000000000000000000..1c563d017dad624310a027c6dcec70820ae24b99 GIT binary patch literal 3137 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 W0Df2iS5+GgW-0CrYQuj{Zhitj2DlLb literal 0 HcmV?d00001 diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py index 6c94734bb4..a8274eb413 100644 --- a/zerver/lib/integrations.py +++ b/zerver/lib/integrations.py @@ -343,6 +343,7 @@ EMBEDDED_BOTS: list[EmbeddedBotIntegration] = [ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [ WebhookIntegration("airbrake", ["monitoring"]), + WebhookIntegration("airbyte", ["monitoring"]), WebhookIntegration( "alertmanager", ["monitoring"], @@ -712,6 +713,7 @@ NO_SCREENSHOT_WEBHOOKS = { DOC_SCREENSHOT_CONFIG: dict[str, list[BaseScreenshotConfig]] = { "airbrake": [ScreenshotConfig("error_message.json")], + "airbyte": [ScreenshotConfig("airbyte_job_payload_success.json")], "alertmanager": [ ScreenshotConfig("alert.json", extra_params={"name": "topic", "desc": "description"}) ], diff --git a/zerver/webhooks/airbyte/__init__.py b/zerver/webhooks/airbyte/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/zerver/webhooks/airbyte/doc.md b/zerver/webhooks/airbyte/doc.md new file mode 100644 index 0000000000..488384a035 --- /dev/null +++ b/zerver/webhooks/airbyte/doc.md @@ -0,0 +1,28 @@ +# Zulip Airbyte integration + +Get Zulip notifications from Airbyte. + +{start_tabs} + +1. {!create-channel.md!} + +1. {!create-an-incoming-webhook.md!} + +1. {!generate-webhook-url-basic.md!} + +1. In Airbyte, go to your project settings. Click **Notifications**, + and toggle the **Webhook** button for the notifications you'd like + to receive. + +1. Enter the URL generated above in the **Webhook URL** field. Click the + **Save changes** button at the bottom of the page. + +{end_tabs} + +{!congrats.md!} + +![](/static/images/integrations/airbyte/001.png) + +### Related documentation + +{!webhooks-url-specification.md!} diff --git a/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_failure.json b/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_failure.json new file mode 100644 index 0000000000..bb8a76f9d7 --- /dev/null +++ b/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_failure.json @@ -0,0 +1,83 @@ +{ + "text": "Your connection Google Sheets → Postgres from Google Sheets to Postgres failed\nThis happened with Checking source connection failed - please review this connection's configuration to prevent future syncs from failing\n\nYou can access its logs here: https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720\n\nJob ID: 20441143", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Sync completed: " + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Source:*" + }, + { + "type": "mrkdwn", + "text": "" + }, + { + "type": "mrkdwn", + "text": "*Destination:*" + }, + { + "type": "mrkdwn", + "text": "" + }, + { + "type": "mrkdwn", + "text": "*Duration:*" + }, + { + "type": "mrkdwn", + "text": "1 min 23 sec" + } + ] + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Sync Summary:*\n1400 record(s) extracted / 1400 record(s) loaded\n281 kB extracted / 281 kB loaded\n" + } + } + ], + "data": { + "workspace": { + "id": "84d2dd6e-82aa-406e-91f3-bf8dbf176e69", + "name": "Zulip Airbyte Integration", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69" + }, + "connection": { + "id": "aa941643-07ea-48a2-9035-024575491720", + "name": "Google Sheets → Postgres", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720" + }, + "source": { + "id": "363c0ea3-e989-4051-9f54-d41b794d6621", + "name": "Google Sheets", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/source/363c0ea3-e989-4051-9f54-d41b794d6621" + }, + "destination": { + "id": "b3a05072-e3c8-435a-8e6e-4a5c601039c6", + "name": "Postgres", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/destination/b3a05072-e3c8-435a-8e6e-4a5c601039c6" + }, + "jobId": 20441143, + "startedAt": "2024-10-22T20:27:59Z", + "finishedAt": "2024-10-22T20:29:22Z", + "bytesEmitted": 0, + "bytesCommitted": 0, + "recordsEmitted": 0, + "recordsCommitted": 0, + "errorMessage": "Checking source connection failed - please review this connection's configuration to prevent future syncs from failing", + "durationFormatted": "28 sec", + "bytesEmittedFormatted": "0 B", + "bytesCommittedFormatted": "0 B", + "success": false, + "durationInSeconds": 28 + } +} diff --git a/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_success.json b/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_success.json new file mode 100644 index 0000000000..d573f8b63d --- /dev/null +++ b/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_success.json @@ -0,0 +1,83 @@ +{ + "text": "Your connection Google Sheets → Postgres from Google Sheets to Postgres succeeded\nThis was for null\n\nYou can access its logs here: https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720\n\nJob ID: 20441143", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Sync completed: " + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Source:*" + }, + { + "type": "mrkdwn", + "text": "" + }, + { + "type": "mrkdwn", + "text": "*Destination:*" + }, + { + "type": "mrkdwn", + "text": "" + }, + { + "type": "mrkdwn", + "text": "*Duration:*" + }, + { + "type": "mrkdwn", + "text": "1 min 23 sec" + } + ] + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Sync Summary:*\n1400 record(s) extracted / 1400 record(s) loaded\n281 kB extracted / 281 kB loaded\n" + } + } + ], + "data": { + "workspace": { + "id": "84d2dd6e-82aa-406e-91f3-bf8dbf176e69", + "name": "Zulip Airbyte Integration", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69" + }, + "connection": { + "id": "aa941643-07ea-48a2-9035-024575491720", + "name": "Google Sheets → Postgres", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720" + }, + "source": { + "id": "363c0ea3-e989-4051-9f54-d41b794d6621", + "name": "Google Sheets", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/source/363c0ea3-e989-4051-9f54-d41b794d6621" + }, + "destination": { + "id": "b3a05072-e3c8-435a-8e6e-4a5c601039c6", + "name": "Postgres", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/destination/b3a05072-e3c8-435a-8e6e-4a5c601039c6" + }, + "jobId": 20441143, + "startedAt": "2024-10-22T20:27:59Z", + "finishedAt": "2024-10-22T20:29:22Z", + "bytesEmitted": 288179, + "bytesCommitted": 288179, + "recordsEmitted": 1400, + "recordsCommitted": 1400, + "errorMessage": null, + "durationFormatted": "1 min 23 sec", + "bytesEmittedFormatted": "281 kB", + "bytesCommittedFormatted": "281 kB", + "success": true, + "durationInSeconds": 83 + } +} diff --git a/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_failure.json b/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_failure.json new file mode 100644 index 0000000000..5df5ad4aed --- /dev/null +++ b/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_failure.json @@ -0,0 +1,3 @@ +{ + "text": "Hello World! This is a test from Airbyte to try slack notification settings for sync failures." +} diff --git a/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_success.json b/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_success.json new file mode 100644 index 0000000000..1323fd41c0 --- /dev/null +++ b/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_success.json @@ -0,0 +1,4 @@ +{ + "text": "Hello World! This is a test from Airbyte to try slack notification settings for sync successes." +} + diff --git a/zerver/webhooks/airbyte/tests.py b/zerver/webhooks/airbyte/tests.py new file mode 100644 index 0000000000..1a997bc292 --- /dev/null +++ b/zerver/webhooks/airbyte/tests.py @@ -0,0 +1,70 @@ +from zerver.lib.test_classes import WebhookTestCase + + +class AirbyteHookTests(WebhookTestCase): + STREAM_NAME = "airbyte" + URL_TEMPLATE = "/api/v1/external/airbyte?api_key={api_key}&stream={stream}" + FIXTURE_DIR_NAME = "airbyte" + CHANNEL_NAME = "test" + WEBHOOK_DIR_NAME = "airbyte" + + def test_airbyte_job_success(self) -> None: + expected_topic = "Zulip Airbyte Integration - Google Sheets → Postgres" + + expected_message = """:green_circle: Airbyte sync **succeeded** for [Google Sheets → Postgres](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720). + + +* **Source:** [Google Sheets](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/source/363c0ea3-e989-4051-9f54-d41b794d6621) +* **Destination:** [Postgres](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/destination/b3a05072-e3c8-435a-8e6e-4a5c601039c6) +* **Records:** 1400 emitted, 1400 committed +* **Bytes:** 281 kB emitted, 281 kB committed +* **Duration:** 1 min 23 sec""" + + self.check_webhook( + "airbyte_job_payload_success", + expected_topic, + expected_message, + content_type="application/json", + ) + + def test_airbyte_job_failure(self) -> None: + expected_topic = "Zulip Airbyte Integration - Google Sheets → Postgres" + expected_message = """:red_circle: Airbyte sync **failed** for [Google Sheets → Postgres](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720). + + +* **Source:** [Google Sheets](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/source/363c0ea3-e989-4051-9f54-d41b794d6621) +* **Destination:** [Postgres](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/destination/b3a05072-e3c8-435a-8e6e-4a5c601039c6) +* **Records:** 0 emitted, 0 committed +* **Bytes:** 0 B emitted, 0 B committed +* **Duration:** 28 sec + +**Error message:** Checking source connection failed - please review this connection's configuration to prevent future syncs from failing""" + + self.check_webhook( + "airbyte_job_payload_failure", + expected_topic, + expected_message, + content_type="application/json", + ) + + def test_airbyte_job_hello_world_success(self) -> None: + expected_topic = "Airbyte notification" + expected_message = """Hello World! This is a test from Airbyte to try slack notification settings for sync successes.""" + + self.check_webhook( + "test_airbyte_job_hello_world_success", + expected_topic, + expected_message, + content_type="application/json", + ) + + def test_airbyte_job_hello_world_failure(self) -> None: + expected_topic = "Airbyte notification" + expected_message = """Hello World! This is a test from Airbyte to try slack notification settings for sync failures.""" + + self.check_webhook( + "test_airbyte_job_hello_world_failure", + expected_topic, + expected_message, + content_type="application/json", + ) diff --git a/zerver/webhooks/airbyte/view.py b/zerver/webhooks/airbyte/view.py new file mode 100644 index 0000000000..f422d0b144 --- /dev/null +++ b/zerver/webhooks/airbyte/view.py @@ -0,0 +1,97 @@ +# Webhooks for external integrations. + +from django.http import HttpRequest, HttpResponse + +from zerver.decorator import webhook_view +from zerver.lib.response import json_success +from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint +from zerver.lib.validator import WildValue, check_bool, check_int, check_string +from zerver.lib.webhooks.common import check_send_webhook_message +from zerver.models import UserProfile + +AIRBYTE_TOPIC_TEMPLATE = "{workspace} - {connection}" + +AIRBYTE_MESSAGE_TEMPLATE = """\ +{sync_status_emoji} Airbyte sync **{status}** for [{connection_name}]({connection_url}). + + +* **Source:** [{source_name}]({source_url}) +* **Destination:** [{destination_name}]({destination_url}) +* **Records:** {records_emitted} emitted, {records_committed} committed +* **Bytes:** {bytes_emitted} emitted, {bytes_committed} committed +* **Duration:** {duration} +""" + + +def extract_data_from_payload(payload_data: WildValue) -> dict[str, str | int | bool]: + data: dict[str, str | int | bool] = { + "workspace_name": payload_data["workspace"]["name"].tame(check_string), + "connection_name": payload_data["connection"]["name"].tame(check_string), + "source_name": payload_data["source"]["name"].tame(check_string), + "destination_name": payload_data["destination"]["name"].tame(check_string), + "connection_url": payload_data["connection"]["url"].tame(check_string), + "source_url": payload_data["source"]["url"].tame(check_string), + "destination_url": payload_data["destination"]["url"].tame(check_string), + "successful_sync": payload_data["success"].tame(check_bool), + "duration_formatted": payload_data["durationFormatted"].tame(check_string), + "records_emitted": payload_data["recordsEmitted"].tame(check_int), + "records_committed": payload_data["recordsCommitted"].tame(check_int), + "bytes_emitted_formatted": payload_data["bytesEmittedFormatted"].tame(check_string), + "bytes_committed_formatted": payload_data["bytesCommittedFormatted"].tame(check_string), + } + + if not data["successful_sync"]: + data["error_message"] = payload_data["errorMessage"].tame(check_string) + + return data + + +def format_message_from_data(data: dict[str, str | int | bool]) -> str: + content = AIRBYTE_MESSAGE_TEMPLATE.format( + sync_status_emoji=":green_circle:" if data["successful_sync"] else ":red_circle:", + status="succeeded" if data["successful_sync"] else "failed", + connection_name=data["connection_name"], + connection_url=data["connection_url"], + source_name=data["source_name"], + source_url=data["source_url"], + destination_name=data["destination_name"], + destination_url=data["destination_url"], + duration=data["duration_formatted"], + records_emitted=data["records_emitted"], + records_committed=data["records_committed"], + bytes_emitted=data["bytes_emitted_formatted"], + bytes_committed=data["bytes_committed_formatted"], + ) + + if not data["successful_sync"]: + error_message = data["error_message"] + content += f"\n**Error message:** {error_message}" + + return content + + +def create_topic_from_data(data: dict[str, str | int | bool]) -> str: + return AIRBYTE_TOPIC_TEMPLATE.format( + workspace=data["workspace_name"], + connection=data["connection_name"], + ) + + +@webhook_view("Airbyte") +@typed_endpoint +def api_airbyte_webhook( + request: HttpRequest, + user_profile: UserProfile, + *, + payload: JsonBodyPayload[WildValue], +) -> HttpResponse: + if "data" in payload: + data = extract_data_from_payload(payload["data"]) + content = format_message_from_data(data) + topic = create_topic_from_data(data) + else: + # Test Airbyte notification payloads only contain this field. + content = payload["text"].tame(check_string) + topic = "Airbyte notification" + check_send_webhook_message(request, user_profile, topic, content) + return json_success(request)