From 85688120722f482664f7620f5e38baefe43112d7 Mon Sep 17 00:00:00 2001 From: joobisb Date: Sun, 1 Sep 2024 22:55:25 +0530 Subject: [PATCH 1/8] feat: turn off notification via email --- coderd/database/queries.sql.go | 17 +++++++------ coderd/database/queries/notifications.sql | 1 + .../notifications/dispatch/smtp/html.gotmpl | 1 + coderd/notifications/enqueuer.go | 3 ++- coderd/notifications/render/gotmpl_test.go | 9 +++++++ coderd/notifications/types/payload.go | 17 ++++++------- .../NotificationsPage/NotificationsPage.tsx | 25 ++++++++++++++++++- 7 files changed, 55 insertions(+), 18 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index d79d101688e51..b83c58c987062 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3487,6 +3487,7 @@ func (q *sqlQuerier) EnqueueNotificationMessage(ctx context.Context, arg Enqueue const fetchNewMessageMetadata = `-- name: FetchNewMessageMetadata :one SELECT nt.name AS notification_name, + nt.id AS notification_template_id, nt.actions AS actions, nt.method AS custom_method, u.id AS user_id, @@ -3505,13 +3506,14 @@ type FetchNewMessageMetadataParams struct { } type FetchNewMessageMetadataRow struct { - NotificationName string `db:"notification_name" json:"notification_name"` - Actions []byte `db:"actions" json:"actions"` - CustomMethod NullNotificationMethod `db:"custom_method" json:"custom_method"` - UserID uuid.UUID `db:"user_id" json:"user_id"` - UserEmail string `db:"user_email" json:"user_email"` - UserName string `db:"user_name" json:"user_name"` - UserUsername string `db:"user_username" json:"user_username"` + NotificationName string `db:"notification_name" json:"notification_name"` + NotificationTemplateID uuid.UUID `db:"notification_template_id" json:"notification_template_id"` + Actions []byte `db:"actions" json:"actions"` + CustomMethod NullNotificationMethod `db:"custom_method" json:"custom_method"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + UserEmail string `db:"user_email" json:"user_email"` + UserName string `db:"user_name" json:"user_name"` + UserUsername string `db:"user_username" json:"user_username"` } // This is used to build up the notification_message's JSON payload. @@ -3520,6 +3522,7 @@ func (q *sqlQuerier) FetchNewMessageMetadata(ctx context.Context, arg FetchNewMe var i FetchNewMessageMetadataRow err := row.Scan( &i.NotificationName, + &i.NotificationTemplateID, &i.Actions, &i.CustomMethod, &i.UserID, diff --git a/coderd/database/queries/notifications.sql b/coderd/database/queries/notifications.sql index 983d0d56e40d4..fa916c95179af 100644 --- a/coderd/database/queries/notifications.sql +++ b/coderd/database/queries/notifications.sql @@ -1,6 +1,7 @@ -- name: FetchNewMessageMetadata :one -- This is used to build up the notification_message's JSON payload. SELECT nt.name AS notification_name, + nt.id AS notification_template_id, nt.actions AS actions, nt.method AS custom_method, u.id AS user_id, diff --git a/coderd/notifications/dispatch/smtp/html.gotmpl b/coderd/notifications/dispatch/smtp/html.gotmpl index ac0527b9742d2..10ef6f0796c7c 100644 --- a/coderd/notifications/dispatch/smtp/html.gotmpl +++ b/coderd/notifications/dispatch/smtp/html.gotmpl @@ -26,6 +26,7 @@

© {{ current_year }} Coder. All rights reserved - {{ base_url }}

Click here to manage your notification settings

+

Stop receiving emails like this

diff --git a/coderd/notifications/enqueuer.go b/coderd/notifications/enqueuer.go index 2915299ef26d5..517b69d2c0509 100644 --- a/coderd/notifications/enqueuer.go +++ b/coderd/notifications/enqueuer.go @@ -123,7 +123,8 @@ func (s *StoreEnqueuer) buildPayload(metadata database.FetchNewMessageMetadataRo payload := types.MessagePayload{ Version: "1.0", - NotificationName: metadata.NotificationName, + NotificationName: metadata.NotificationName, + NotificationTemplateID: metadata.NotificationTemplateID.String(), UserID: metadata.UserID.String(), UserEmail: metadata.UserEmail, diff --git a/coderd/notifications/render/gotmpl_test.go b/coderd/notifications/render/gotmpl_test.go index ec2ec7ffe6237..25e52cc07f671 100644 --- a/coderd/notifications/render/gotmpl_test.go +++ b/coderd/notifications/render/gotmpl_test.go @@ -56,6 +56,15 @@ func TestGoTemplate(t *testing.T) { "url": "https://mocked-server-address/@johndoe/my-workspace" }]`, }, + { + name: "render notification template ID", + in: `{{ .NotificationTemplateID }}`, + payload: types.MessagePayload{ + NotificationTemplateID: "4e19c0ac-94e1-4532-9515-d1801aa283b2", + }, + expectedOutput: "4e19c0ac-94e1-4532-9515-d1801aa283b2", + expectedErr: nil, + }, } for _, tc := range tests { diff --git a/coderd/notifications/types/payload.go b/coderd/notifications/types/payload.go index ba666219af654..02b505caceb65 100644 --- a/coderd/notifications/types/payload.go +++ b/coderd/notifications/types/payload.go @@ -7,13 +7,12 @@ package types type MessagePayload struct { Version string `json:"_version"` - NotificationName string `json:"notification_name"` - - UserID string `json:"user_id"` - UserEmail string `json:"user_email"` - UserName string `json:"user_name"` - UserUsername string `json:"user_username"` - - Actions []TemplateAction `json:"actions"` - Labels map[string]string `json:"labels"` + NotificationName string `json:"notification_name"` + NotificationTemplateID string `json:"notification_template_id"` + UserID string `json:"user_id"` + UserEmail string `json:"user_email"` + UserName string `json:"user_name"` + UserUsername string `json:"user_username"` + Actions []TemplateAction `json:"actions"` + Labels map[string]string `json:"labels"` } diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx index 532d457656c3e..a27487047803a 100644 --- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx +++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx @@ -18,7 +18,7 @@ import type { NotificationPreference, NotificationTemplate, } from "api/typesGenerated"; -import { displaySuccess } from "components/GlobalSnackbar/utils"; +import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; import { Loader } from "components/Loader/Loader"; import { Stack } from "components/Stack/Stack"; import { useAuthenticated } from "contexts/auth/RequireAuth"; @@ -28,8 +28,10 @@ import { methodLabels, } from "modules/notifications/utils"; import { type FC, Fragment } from "react"; +import { useEffect } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQueries, useQueryClient } from "react-query"; +import { useSearchParams } from "react-router-dom"; import { pageTitle } from "utils/page"; import { Section } from "../Section"; @@ -60,6 +62,27 @@ export const NotificationsPage: FC = () => { const updatePreferences = useMutation( updateUserNotificationPreferences(user.id, queryClient), ); + const [searchParams] = useSearchParams(); + const unsubscribeTemplateId = searchParams.get("unsubscribe"); + + useEffect(() => { + if (unsubscribeTemplateId) { + handleUnsubscribe(unsubscribeTemplateId); + } + }, [unsubscribeTemplateId]); + + const handleUnsubscribe = async (templateId: string) => { + await updatePreferences.mutateAsync({ + template_disabled_map: { + [templateId]: true, + }, + }); + displaySuccess("Notification preferences updated"); + queryClient.invalidateQueries( + userNotificationPreferences(user.id).queryKey, + ); + }; + const ready = disabledPreferences.data && templatesByGroup.data && dispatchMethods.data; From 5309f0a75abb986c16b8c8e7b59491666f2e7bf0 Mon Sep 17 00:00:00 2001 From: joobisb Date: Sun, 1 Sep 2024 23:43:46 +0530 Subject: [PATCH 2/8] remove unused imports --- .../UserSettingsPage/NotificationsPage/NotificationsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx index a27487047803a..73acf82f7f175 100644 --- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx +++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx @@ -18,7 +18,7 @@ import type { NotificationPreference, NotificationTemplate, } from "api/typesGenerated"; -import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; +import { displaySuccess } from "components/GlobalSnackbar/utils"; import { Loader } from "components/Loader/Loader"; import { Stack } from "components/Stack/Stack"; import { useAuthenticated } from "contexts/auth/RequireAuth"; From 254f4dce98fde018dbff5a7a53f40a571e9d5a00 Mon Sep 17 00:00:00 2001 From: joobisb Date: Tue, 3 Sep 2024 10:00:47 +0530 Subject: [PATCH 3/8] addressed the comments --- coderd/notifications/Archive.zip | Bin 0 -> 36113 bytes .../notifications/dispatch/smtp/html.gotmpl | 2 +- coderd/notifications/enqueuer.go | 2 +- coderd/notifications/types/payload.go | 18 +++---- .../NotificationsPage/NotificationsPage.tsx | 44 ++++++++++++------ 5 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 coderd/notifications/Archive.zip diff --git a/coderd/notifications/Archive.zip b/coderd/notifications/Archive.zip new file mode 100644 index 0000000000000000000000000000000000000000..60e0ea451bc67d838f1880edb9b86ee77f1ffb1d GIT binary patch literal 36113 zcmb5VV~}N0wk@2tZQHhO+qP}nww+mN+m*IaX`7YCtGfN(?Y{lpAN`$(b=Hm(ab~Po zG3J=qV=71kgP;KX{mgh^YyQ{6|NR07AOLVOwKFkwqE}Uc1pvp%n>K^ZpEmouyLvza z0D?RL0s#EipB4Vg2m}Dc|1k0a3;=-ePb22`F18LfbmsQ|ZH?-WrT@iRuCi==HUk3h zfj*J=aqQH;O8(eG&w(}bF`cXgo?QYEHA|FK9 zAVJkaLm9UoHaMoVk`>HKtn7t_yB2Zdq{BosjwvZI0AlXDauDvIj%>MfXY2`nidl{G$JSV>yr)A% zLhS+0XgOdP@hg%ilsFpLo@4_(7}~T{2~l>x23M)NVX5#R#k+g*Qt8n@nHh$d4_RCbQM=q;Mxn%MewJcqfQ??U@=OFS!`l1H$ z*x-xi3%5E9Zy(==lg3>S^H#I755v)G;v}$~&`1*LyA{U?Qk~D-v5$lvyDW&wYdH?1 z(Xj}+<7QyGy$`v;lCQR~=Sdk~K2c>A$D3F7lp3O?abY>U^G#`{%~0w&{E`suZp=F5 zgES5{<&-wTwf1fUB;8c7H=6S-CXiKUi09Y|)5nF%LwyW1fW4u&Y!36#JHwnS33{@# z3pv{9u^D>TN=on#IoSl1z9)DWaIf2%;Qx+?%c=NMWHP1%{6_t0@=Ar!r2d4iAPo+o z1iMrR4Fmx27f$~>&;-E&08sxOX!N8$X+fvT3}B2@3i0&Ff`jOCIF3p@|?UJD>>c`qeFnH#+jKlv^vsE zN?}6>a~RDKEC>B`=}T~1H;J4x)zS`Dqme!Td~mh8?MMWRKL{zS+Il{x`E^*ikU|XF zru2ggw&t63v6i}8lPBiDvl!$TQd;~Q+GN!(8aUdv>uK^Pq0Rmn+3K&`qJAR*%ESUB zv@EP?(Q#8iWrNI_8U0m$9DwlA(p84c!B1dByAW4ZqMypch##eQLEe~er=wLL)_>#$)J%i^@aMRf6GN!HOebz-5n4+5$E zlE_2V0A`2KfjFl;!)%`@v}0NrVN=P9)gi1T1AkdJ10LU!%WZ`<#LXVRIkj=KOz!Ee zk>@X2cgFXKthO%*+A4oqW2(#%1D&mZ!FI}9D4mtkIl#;-b4yYl%N7{_@$SF{zc&gT zz1B;>UAFusf4%1#h;EyA9t9oJl|9KjKKc$9haR3#Z;zz^G*6f`hjl|D$fS1=gC`mG z=+ks3eoeO;Cu4Gk0u6Qy$;NBT}^JxZh!tMEx`vCis__z@# z+KHEs3axw*B9+)~TxF61?1u68cQGY`8cJI_615g36t5hpAyZCnRCBeox^ zO#w!|*=5Mj(I6NT7FGQpNJ??s&KB0Iqx|>xcY_M#1%@SLGMHT zra^sSQ7hv)EH0)CIoo40k2~)B4|^_GdXWMISAVwg)Lw@R2LyR+QzkcI^N@uk-V?xsc-_V(8Qf%E06+1hWiA$-|< zM*RpdRVtjbt-lHmgt52;xtkjnyQ&3_B1$FIIwnellKcr5_4SOaPtwOC+w>MJk{|bc z|I1l?#nLSs4aKSKT*dqcfq^P*Nf^axXf(L2YDBb*gDm?74dtxJjK}=9I;0(_5|m|2 z27|nutAD90F4#-Cf!8vP1X-0Ip@`yw-(vl*3e+MQPr(qHx9qPnRwJLIq{GMFIw?h* z4S`jax0BCE?L%KW7VSowWf_O%RrnUzPN^eXx)mHh8v&)bo7>S|6+dh+M})`s+dw)s zpYiDmKZ7uK`23=JLW?b zf*#H{DTA|uH&Unvh<;2Zgf}~zzYG0Jqp(X*tlGY4fX7mWdr`e?^RpH6nRS(#A5<7N zPBD&{6Z!k$Q_w4dJ0Tt#_b_!9fBUHxo3Ahh25XJn-Ur}El$LZR;>11m`3T|z_lL7G zF~oy3Isu4t%e>tGwnjH?=3~JdsvvEOIVWe3?H^>WJ7)q+?$-1SF^Uk2u{(mv16zj=_Cfxx($RhFVKHK_~APVf!JL zxgEgoGd9WNAwW>P*6@V*6RAnL5{eM^ZopIHO;Wz#9PKMiL9osvi_?WH=3$See*BviA| zNQ;_wk0|B>P+D5FlT_rywjq;<@r=tz!|b+~@K{?Yg-o8yf^~Ly7p7W8@N-4#0Vx}r zNR$$Wy!gSLeKq;@#fZm;8DGBEoW1zY=FN<+yE`+!Zif8kgt4Ky=R&C|89e(r*ZB!> zRzqJ}d1u(SugHt^zEZ(U8nGm%BQT@KeA+4Upd!j2Dp_VhMcMu3Ed}1OfiPKmDou*1 zm{**h%mG;P34!WAmL(YPa{p{@Qa_nuTz+NJ&qa>J*ABHngao~1&S^k zsgv(KO8<)zLV9ym4L|*a^`d=Bi=O3O|`?LP;|HkuI>GFSaB8(CJa!tm$s?26d9Wu=gR7DL? zrDf@nwZc^<+wfYW5{3u}Mj}aOSVBj<(XD4xw=4AZUTOHnV-k8A*0Zu)EmyyUSHX|& zx^D_MBLITw0(D>pfB+$(9)Qdu4Ym?OcM(FzIDoP_@SC;0ll6|2l$7g)q88~r-nCo6 zkH@zb96P~AMjrIiduBD9roA>H`=Bpr>;1Jn{Te^=jkGTVZgJ;kiP;Tdyw=ZJe2?kt zN0zteDCbeF&ugu^E59{Hyq?|KotCXH7PxwEUqjFL5jSrhoBRGN&+O7ewfgkRB~1a3vGLk_crFi3shq zK;ZE~w8=b#2><*t?T^)%wBTP{10weL;qUqIsxK*dh8KNWR}Oc5^a;%qdZ!2ZDlfE6oq^FUZ=vb)G^0HmN67WM~gf7id^&Da3d*xbO#;8s>f?G|1wLLmSs0RxyE0UNLZ8zJ}w zRbUE0d5@q!0GJ~z1Vk_sl|lFP?|Hx>pdREvoNZkk z^j%#nZT`~&;YwrYZ;s;E`V|!&0G3oZzFyBa70lSI*#*|LNg&w(E2=n$bmUMd8e4*d zE&BDmrMLu7E{A3FGw&E&nk?RZXUmrdPq*PRs)plmP-V(2J)ef14Pv-cX+sDlW>ChS z`Uja>76XQFP(ocg&*-WG#T#8mY4~~?CU1?#14Dm$YJK`|rs*ZG!BrjX!ijMyU*n9# zJ>o@c?G8&)%?_zlJz2J7*i|h{7JTpl6Cm^l=P`~V>X1V+*xF380%RS2*IfrAW9#YZ zVF%x7*DOoy7>jXh9YB0Oy0$d_&fO^cU_?LIqxtqNHJmtA%?MPC9~V=DCkBs2t?AHt zx{XR1ErI2}d_E$eMLK);GzY0iuaYy;)R%05Zil=@lVSJ2F-i+d?cH*6}D@IG$9Mn z!#5Aky7Tpk9N#nHzvQ4&=i`#>c0N%hWSkYX`XI-UFuNJ5F+AN7>5A%_-KOn$xGL6> zggIIRr&`CsQ0~pa??J3*1No5-oTjM|N~pNRbtX;|z?jHLnV?hCtR_eX-Wry2wjHWG zo=<=10@>?L@BFj`Cf0HzI|!_z2xhd6L2*oF6uI7-CEaBc=LtD2=QF2>13=bb<0`N3 zmMuVOeDEIK%_&+0A&ct5fJLEK#_#{Um~G!uLa;-nDa`(-b%5*+G3dPrOjgpt0K&wXLVKJ!_h z!&0M{7vLI0hsN>fU(1Awdo!NG1KPyT02R1d4*Va>;rieeT7Pr@Md9NM#Dn zd|zN~sx5neIBu=OKzK30?CR0aHcp7CZ9OE8^hL!#-x(Tn0IR7k8C_+|_E)`3j`^FV ziDHFF&2mh3e7Q5EU$=*xRja-TO-PK=iL*G)ng0YHnh$51`(4M3ziJ`L_T(}+2j+* z$f2*3^G4KmsN1$osX;~B(1r2t)?`V4+nGKSDk$%s9g8;2uP4I43}lB@9e$L>8d znS(w{!&lJlwM27{?<>TZK)Txu+ZbgzqGoEmbdCDhIyw5gESsDq_nl%K-tK4A&p5S1zeU?~w|M|REif3;~PCEA+M%r+qYLhtI>)PL8#j(ka z3d2V1zaH$ywqV9rlj4T`{QVw>>U_K>LgF_3VUceA!F>|l+?yy+Ck=zCw(`;P?0IK5 zq_ctNUmn^229jr77K^<<+0@@~@xKR>AAtYpX8w14b9OK_{trrARRzJgUlk`%87No}j*g5J zJc{zkbE2D&=$lp~gfeCAK+i&%@#4yB>^DNl7cI4{JAbD0DpM~S#pqXyKET`1ym zMF|qlQ1ym`s2nd*M1dzVrVM~;0viK{0f7LjmRl)2EH>k5ZqvqtK!qtrR^0u&JJO+1 zF8*{0ov1^RRK6-2j^UX2p{xyagqLhW%=<)3g1e=lX~_XT?lP%Td#Fz-Q7|CmGl^pv z>HOc-^S;I3elbUuIla4&i+*?w!K!7Os8xwieto*y7Te?p^$0MGbh^tvdglK~DbynM zX<`1{<=^}K-*38|=^yR9f8O-pko_OF^R~3Koj2Q2{nGqEi!#l|MGBK--W6P#n&O-v zs;_>F*gX*@k1LRnG?YQm0dA=Ae$o1#X@BeZ3-vhz=(#kdPLj#-Mv4G}gM*)Nvj>a~ zozmn;A(C^!%d)&%l(22tG0mzPhOAyajzF{gzC}Qd{#6kudA2G(pZw4eQ)$e^{+L=t z>1&$gepFLGNWme7VAyc4}DqVlZo`|a6Zq9r#|Mp=}$ zGHTGJjK#V;d3es(2yIEZqJcGPrbNM6(BEPi+V=3nT#tb=;4K!Z=zO$<|Df+mD4%Zh zJ0Y>Kc2Tnk^eZzHr8ZM6UbWjHcoSu53a98+HAzaEL6S~ZQ-U{Qkf3o;6d?(6V{2}Z zbKG2V=5VlaO(2LbApxnzGir}n5!1o}B~Nnp@#kmf(P|Ft@wq`(rBE*ola#6yqN8mQym- zzYSWbfrzY7q8)Np?&DLXYAo~c;8rAE@H~hy9wzrq1@2$etCkb_g@|& ze$uriSlO9KfY_Gh_HpCmMonnb-poWkEEQJxthi|mT$KwuwZk2yDyEsBHWymB2!P16 zlueRw!!MH95ew{pu0^53i{8wO7A9!-)x>RnDO$Eq%`mwpwk&CNN%5kJC&>>`2n`oz z;1=z_6c%no&0>Y^0927gt0*dLSCzc*^q;djK;;rZer6EN@1PY>G{I?mwrDvS&`sk5 zV<<0U9vnK{ATx;x`$8j-TY9($a5$K*E2w~KeRq{1sm-|kuorX+l+sKsOM4$(@~U%x z{{5MdETk{OT-w}h*C1Vg7Ts0-+GTO$ddjKBJ(|m^6icfhcLL6|9k-x6aIpDf%kx** zQjVr?7M zzVN{$nA|%n4Jj}sWV6lo9NKT=EQGeTtYM`rQ$95fO+HCRJ5{-KhuAyeJNM}BEOX=nTl$FW07Sm8on6t%k z;#?W#k8Q)qOeVKC6VudeGzuhdyZY`quh@J;f*0*J#CfLV8EUPKdmh{`&moaheYl0p zG(;I6m$bttm><)&#vmH3L)y)JTwC7W=ULeC=S_vnzKR)%oujZA)N0rqU%w$@qex;c zMRF{dK)AJ;ON!=9QO08-T1nr}Z?|@L^f60j#zI0Zaje(7_<)7wQfy^~_?56JIOmzm zPnh!23}f(5BsHiT`_On>#iCF_T0x??y8V%W^o4t-r@GGCj$)?gF8G;xeF3GYA-0fC zs~q)#v%1c3*aO=SGkH)`mf79NOQ32y`xd}Z7f76No} z6h26((%`7FO5`kDu=r7{FAE{NG*sSeSg)%F=*dxPsFGE+z`9T1V;2w-zr^j@u+APU zEYLNeIhQj$3>|d#zR?S1u(+ZtZesefE^`_cB=De}X#&|}CG&-;_72vl z;m!zw4el`_(mk?$D0%xxNrRT{dDlQMBZ7_-#SkpK{tj0HiQG-gJSzfdtu>dL{yUp4 zce#s>fxubvvg!vLpo{fkX<;;$>!!V-kr=Zo1~*7@|7B2>2KsDBmpKZLIu$8qHLLcy zBSx-d1{WD;8v5)NeI4vxUX!!;N4NwAXv>*;d9@;mb46DJ8#zcUY}JavFoCC^zvUd8 zboE;s#$ih;zQPrH()q?*p3FRH(YeRDQ5peVVRbC>AVQQb#N~U;)XSylVK?XSP?<4` zZq75VYd#QZCB=Sa90RZD*KEhyh$n+XJy!yWC^dnHU0)AJev&I=B8 zX?<&S1qPP4?^vxVSTMBm6h8g122qk|<$`SK%<5xAWX}#-dp0KvH5yWkS*akx3=c+1 zd|AhM}vb{s5w|lU20ScUpPO>4xW0Yr_o3v~JkVn&{>e zIBeReG#MR22?ANuB6emne7Kh)9DO8Yx`?NGWcvEBcxX(t-3Hr_#Zmch9oSB@j)YVt zZ^s{tm zBTk8;I4*j{)a7x}l$2}UgIc*%4JOMCISJHq!RS=Uc{^+iXgW3}EFHpAL@rm$NedyD z&9wOejIpFB)S_@d6B9vgtvx*9aMKTevjihY*%@(;7d5-tVD;sb6+Ba=*Y$Mhi!$?o zFAGZE(#c5EjMkE8G8B8yg76GGhNlm4lc^o$aHAzvRWY=rPUMe?S4H%*!WhnwQ?nhp zZdN0DE1gOo<$bs5o=gaQtvfp^tQj_PL^kEe#ELy)L5ii*x#i8bC@Y#Ge}px!SaAzH zQO^uLs#eI_V$M#Ii1L9wFiRlNRYaL{JIv+?oGn;8m_TnKZ`!Fp%;?b^%l0SlHc)3% zQabVms~lG~3^~v!Yr9UJ)tXQHlYWupcQ3miRO(=rR*oxT`HTvdVfq+&H}-q6Xvar;B4E%W+-(IV>JY?DcJDb}LT$-qy@ zcPufjQ?pP5 zVsgpGsIexciQxnOZw=NrPjuM^4FK>r|Nq}BgJ-^fufhIQ2LC|~w#;qqyvdQ2U!xzW zsA@z-syt&tmaMqqe(}=qRD=6&W9rn>q)ZnPLXt-rL?}UBRsH?$$_{{3Lh;Jjq~4{> z8V3Qi73=2?EaT}gSsoOcDCLPa#g%K3H$h{rXa0j*?^wunPn;lG$7lM?yj8}TbIkaN z$M@6m+1#VsP?r%u8evD~n8g=w`lo^3uxHABHP)-rm`;c#8Yw6KD9|W!Vzxh+Gv3c9 z_I+9AOlh2i5S5>b1!qc}0vYG|lO+BAj(jv(<&3kCc5pA9xG#*c_nM4Pz?}FI9b+ma z$pb3a6txJMStpd*+~9g{7X)QWa>k4lQ4}`anU1W{K$)fONv(8^GG;8&BRrXJOxbX~ zaJ>ulL>oa255kV@3Jp1Ik+3EdOb^f3-k-!cBuw#Siv7+6im*(H{T9z>&=00k)j1EB zY$?$pyfC}-?J6FdSC3q|+0H7H|uO)TV{zy z{W8$CvnFA}icaAIt3rJs7aK6RS2vM!@*+6WlR;=~HnR?ct*>GKlKxCGcRYrkJl??cE9X)Krt5>ZkZfZ+Sd z>iI813;>;%Q>F!7c1uyZqB#KX5I07n=jRjdi1HZ&#>LE6Ay_Dw=H__x8r1;~P>~_M zG7vaM?=c-2CW!G4ga^k`5$flE?)^*%e25;nP0qpn`u*tR;Ooi5!L!rH^&-zU`Je)8 z_*#iAtY#l0odJAX56d5qE3y9GP_$?z>PINIR zk>Yc#>68f4ui)Uq_Z7rWu#B?HcyaS_KmACapOCnEKii_F zb3VW~Sf{s09!mefJH8 zupu1;jFl{n6VDWeht$jxV5_yP#s>o56R_L3hu5^d)YrTc2zT=<#*4Jj;OBe@ns3`^ z5`|b04fI}vKGq%L7xP_bm_)Gsm>x}M2nxWKd-G2}fh1ilIk6LS*2gqXUgIC_`wb=B z*7>5IXLJ!Q)Yt;TIguss3lu2=Wj~T;H-bQiJ_9TulF$;Cl~F{Mx33I9Pr*)*F?XP3I{heA6$yvCXUb4PZe1_iF+&oKZSH0ZW4BLGqBVoQ)>*1dX+yHhO2Qt8_G0y08 znFGo*VlZ$9Qb-(NSb6ZSwKIYR>Y-rvzblDgih==T1)51g=Ajh-RDYQsDT2a9u!ahC zVB~vS?T>dZaE>Uz*ffMTEEKFz>c~;vRU(>I9KWh|$h`GL0m$qaJ60@`1nzvr1t^xi z^vB8-Fo7}SiCogbv~pEgN`#XYD-EDDIUwBxx6hjO1fpZ!RFffio|+LAJ8T)|kmxx9 zs;*T4zOKfPG3LbUe%*T?*Zw+~nZ&$h=0KVA^`koq2lYd4Cc1MX1 zY)Y7su1rEM-*t;;)CyYz6m-S74*GCQAjVPQH%*Ssa|4y?F7VI9k3qukNfn(B}p1 zSwM-=pnKc<2^WSx$SEC;0+EJLH{B=~@mv697?CWLXN+Wph>cir3fM>qV+bcLA~+O0 zvwtd`oJ6Aw4OyqY!ZT4rya|B$2Q&&GNSx23QVr|HaEGQ-f^VzMPARp3)oRog0U8gb zlKurNr&~RhEioEx)WG)*(S#y^ELY5%@tak%KhQBe9;6F(XROSeoEW@KjM^V!~wS9v=720$r7zvm?Vsd z0^}#$(+?9VjL%}vEc%+Nmh2|lJ8Y?42w7Chv_&o;p&060gn7@PdrYtecj`;cxC2#zDklw}raM6p8?yHpa+@a0;Vy8^OcHDuQk zf!t07OZ$<746I347Ld8|_ZYu8QiReh6NVlzj6@i78z{uP*`xsR@>UZ>7d7J;W96A4 zhPXA%Y0VH#<|Xn(B{7ePf~xU|fIRI%GYOR~6Ga>6CP4YnrEZAGQ8qjMf39<683n|C zhUe<8lo3$p%`|M|02N-r0m|f6c$TEhQ!#JX@vIZhz5-XuaU0rFsIf|lOE9nU_n)eX z(&VCO2rwHErKpRPWf8g3S8iVWQ;-21Vh2%g9(!E|O6E6FtR_zoKE`{kS@#P?37*&s zl#@UF;xCi?7zS1%5GYm-p04aO_aC*0!81SjNOUpVEF1G5r?}JU3UlAu7UNhoRWAl9 z6KAVs(k;kcL_q)Q(2*WzR&7k65M(6no?e4@^|VSqXbAjPr0dS}r)lDx<;CjY;`8`? zduR9Ggo&n#CE3BGRSwX+$CIK+0JZv^>oL2gfhIZDbfhQhtef!#&U^s4FiXg}{VK{% z9Ddo-7rhHnqFL6Ht||i*C;bUuAv+7B107=YyM1dXzPUvOElL+^r?**_z(YsnsvHcw zd0l5PKs^uSwS5O!NXA9&QtVGNlv`YqKv-euI>Gniys0ery#B0LUmsK`D6-|d_sp53 zihYN9jEsq5?wKz(&VBpr=wk2+*fiV?sGVO*Zxh?_P`Z~U0XNo}X|w>Hb>2@Cqw?zw z+Y6$OCIvsiwySNih;%<;$rTflpoQ=HjKk%+4=sEJ(dsuQZs6!73@%LzbNv-~Hq5U3 zR3VaF19cTN#8dJ2#y6lhsX_JS`xgH8`EqMlg9goq#33?Q*d!U?SA5gQhqd0>eqR=q z5%OVvMI>ptHtXGMl!Z5YpD=u6vwtIA?N3D_&G|5K1D#QCp>=?@AdlRG+VFe+{>Wt3 zSjdgeVcbB2u8DrZTSX)##7I#!1c1n7A~OwG2KxI@_;vk8<tq9FN8_;J9+jtjWw@;&V@1y(~mQ=eaIdRx563=74>y75k z{;W*5CO|(6M-nl9*EQ=J=Bkw}!T_b5g;}E1ua)nQ7iroqc1TntL>qU^S-?osCOxb0 z#2GgWcSSMH)}WcBc^>vpC*r%dat5ZZPmpEs`PR5f8WM*w?!<^{>O;N>g*{@T$YZBG zBSr1m;gxJ@A*)(%nANRUsi}_k%z#%#R+i{Ns+0{Zm#~sZ#pW~G*w4Loomw9pazV9_ z$dM0QnM1n4A;{!kYqU!h##W8huL9_8E4F~a(AKd=p=+2WB!@H$y-RYBE`E5m*3*d2 z#Ep@z$J(GQL#c|y)||@6&Co^g5&;+I1zS#mxETe=Zs?);R8CyWy+2{J>Np*>8rzVM z@@~#UpfmGCPFe{us7Z>Vg5h?aDC@1v?GkAXp$cIdifBfTOQT+s6A#} zljJ>eWx5MUam*-=6LAF$mX8}b{u&c zE98l^m1D?}1P=p#ru5h=AHi<0ZdG#^ZQ^|Irbb$?Z;A7vAH$4m;W{FWPKLF#N;6{~ zD5&TAZXiPWW0~~hX5E5(8GLg@{ag%Y+S=h<(~taF(H%I}kLLFo>jB*7%8h9R+H0|2 zx6Pjn0n#Z1)<58go=|2Lf5#GfxKcdI=NGy-V^=W77EhZ2YExmn(vEhgwJwYR!HKLo2WFL2WE>l2vk#tMppcN>{(T#s#L z)A~-3-T|f22$Mj#p`b9?hlXZLgPh&O9u=yu5TtCX;;eD_^DY6x25C3%f=7CE9i*Vz zLL=%B$Z8)H45SG(+xx)VKi>sbpYn1<4=>jn@P?P^fD}}W;lggSWcV{2^du|8RdZQG z*snoC2W^nY#0dUp`=nm4P51W?4X!$NXwC`;KG~VtRSjZG;WE~A{;6b}t*9g5c35b? z@+`nSRn!ATH6roKPxw=;g02upd1ufxUd;Rs?-)+6M3krxkOE>2r2dWB3E2xKxB5t}JlPhs$lWlEnTEGD<)#dvaLIG(?_;KyD zr6S5)Ie(@hRfc6o7&I#FF5z>r(o$aJ4!Hx0X9!x8HyCRY=a&fMVhNZ?nF4lA zT~Q*gGdD6_5vdL(=*&<#pJw6hjR%RM1(ucwjRVKq-cYBw(?oMcr8&yo9ze4~Zn!tW zfEM8Q0P+k1V>KVoR{yv8U(ghLTu@-QI)I_KfQR*dT%{~A4G{69nCc~N)^jDP zeqRrRTpHl&dYL>E_n#&+09`qG14uVh0yd~Np_#)y7`LWKw*80>?**d&skjh@t~$|` z`1>NxPxC|Fed<&AtmD~7EgKk%TeCnIXqnH73>renm_-#clwwF~p+5nK$IyOlJN87A zA^Lil01Di2g#2};yC0Dh`Ax<=yyDmT)=#ybYldIStWP(#AgIjW`mq7cD;NV{tnRhU zcD2pP=e5%ls-^Vx;O}U7H(t62e(wp_*3U0BOo_e5YR_`Ms}yc3sl7|OUlXRdJcDjH zJ(fpme0VoXD(dp5e&?ik%^CL5GyNgF&M>`fCT?9ncq9=e4y4P>iVT#oOg!Z{#zwVPd!p)xod^p{yIc1+rdI0*7SmRj#7JkF^ALl)`cQ;ajo<=S=2*G z(q5J-FOvIqG$SgUiQ@6<$u%-I#0yr$b5gtetcsf_KNQl* zO}=$AV{^%|Rs`nKG7qw71&Z>@3aqc1XX6`L+Z;8_@A=a7!wsOdiIug;X z9xr3aoi?Vzt0B?VQL7_0ZSo_S&E*w#S3&DCqPm7^__|g>VFrXz zrrm~X&l<00Obx5HoV~?5g&<{_PP#j0;xeX$O(SuUrEb;cvYG;h^+kZNqEBu`uPKJouH=HS5Drn!*;0RFb${?VBGBew(a zar&Q=1aSYZvirw40Snul8QY)rcmFrp-#=~rt77l3?f)TLHM8_^adk3v{^uAP%>SP; zG!-}i0E~Y<+y5O;_)irD|23n)0rPeF0hJqweMzL+3G_@Va5J>xMqNuUyr~P15hmDh zHmol0M9OKacG!ncIUbj6M&DNCU=vA%pXej8Lezz>Y~X2WW!SWi4m|<~cGRu5(P)D3 zlYluj-HGdMl>+lXh_UUFcp%PfW#!ZTnE>|6sF>QTlvHI;Z<2Eu*e5g1ZAHeT?6|m6 zX%R|HR{8kuIIaN;Ar+4k_NNm`44|z)N$Fl8WlLM~QPxR4My3=ez^DClMiAR|Dd_fM zj5;{4P%^7T7wo3}+sXc1-yv}&f>0);HkGAVjlTQ&=a*tv^}xq4XY!7}e$@5y8WM3L z9GECEvs*N^J3}U=YraM+`rweg@)J)4%{M`F1yu!7N<7o>J`-VMY0WO$u`4M}Ju%v* zrX|MLCMHAMc+!cvA03$jrXEF$R_ULRTH&*fyU8b>{b`JRw~SA`>CF5|kd}(XiH*;A zg5U=@VlWcsI*k1p%O?;YtND!HcMN`q&_?Ht*Ms7W!JHm#H;awa)KS522v!Kp_m001 zX<4fhvyWVu>k?wGZ@E`!nfGe~3ZEK85(;+jUF0NtDhZi5a%Cl;+el|bMJwRT!?)M^ z6|JKXYchiPqkSIEt<9UNhc3!MNM{e9KMxlk(=kpzxRO!9prZ!1-0=q6WJ9)NV#_p=;42R8-lMy+vabk6i5bGUN9g z-}T6kHUw^bv3Vg2wQv}V%3;WHn}`3MMbe1ORlI3W2=H9Q#-)|#2>h8Bz`|iej+vaM{{D8ceQZn z!)(6aLce{)k@Pw8$uHPye_w*bUFIK|Ojddv&ZO$mIrerp46+@c)7u|Jci}J4OOWOB z$)~*pALZ;US&XAeRucw00dl@?4R1;;;&<2{AsGj+UPevpJ3r9blI3=%hXX>OQ38bCgjgi}DV+NvjGf z*+(aQMd}=bF5faX98E}z3`QB$yL@e*m7fPkBy<5r(wZ5lG<2A9x((~T;%GHi#%6@l zPZ&QBJ(p+W`e5}5gj`qA zOa(W^Z70*$Xz81KsMdl{^WJgpQaZ*SOPp`BK8M_n^J=M$H27-IXFM@)b1br@e>t}r(YcmQ=Pxb5#4|TMSuewuuEa1_pxZjZHCi!JLmB{dm&sjwO}B_;g%Z` z*#}>n?@Av!Q(b~G3Ilq}_h@kiun$%8oC2QW2Dcrml?xnb(XG>mZKx<|NZ*6VCd?Ri z1`!29$vnKCct)W+npZhBb(zb&8`#mO=41$7EFzs0zk(?Fz+=W$Xkb>1E~;cSC9c=e zONJ+X;#cieF>&z9?hptfOeQ&9-_YHv@(l|lGamFKL~isO{oVg9EahsNs^F}z*cLcqK_LCo4eA1g014t2 z4D!AmA#AsHAaC#L>f$D8?*d5&*g2f;Zex1AUzzHzYUxz_uIRD9x^B9ubv`6148RDu z8LdFh!`cl8#uI>HU~+A2YGVaqrlJl6sDH5}7L&*qi|N(B%0&P~=%Jcmg3pX( zS~u{mnsOBE5ZE^JrgC~*oNRSPWIZmQa7XE1$@Y+%Ade##{GRGBqp=nQD6Qi4h|K-T zio2r)f``zc+>X!`D3&?pox`voG2|Lu3S{Q;+QztZ}@Pdy4uS%L9zvRVA zHtThpxn|_g;c8B~Goay=GW1ZMbHHp|LB^5(2uZU5aid;kTGE2=<7jZymYFQMq@>1l z4Kw}ZQ&787NioLCRyy7rD15TFJVBzVK7qXPHt7v4JA0`2`QMFHt z^8DnK8h4{qIgqa8n37$k(;@v#jSovGx!AaV7)Ag=m<$CB>a(3Vu`P)V#?DZ52{(QG zz&T&smBL^`MXX5%h>EZ-aluWXGD|^zr@{nuK|X@_wQlIrd0ga|YhE^~>xGP8-J#c1 z(a^nTs`z9fi!AcfDT36W4RF#8tIE9#O!5Mlv|Urggs|U~S9lF)o_PAg!+rW7WLf`-(Om$kq4?`5IqQOAv@Ao-|OB z)9Pv>WrQiW*U|#;CW%272vjM`(1^UzyUtX$)aE>&!-Ha!?OfD=?+UEm!*H-mWPK6* z7{Qfc=j1%*xCtM*t}&#sPxsf_A_o*n2$UW@=QBzL^E1ZtmR>IF;`l2QJfU>VFUoGi zrO8COjwJKx`wU-7n<8=5x~Xvf{Pue?V0rec_NMzDYB!;1qSwsntro28!qng5>8>nW zRl2CC{@@0s3bR!h%2F@Lp2<9>zfb7Q7AIdCsvzJ$i2=$Fl`l)eB+1s zhD$lq1XR5&=G=fLfOG`uph2^Bi_0s(b<4#mH?UjWUy}^h6^V6`53&Y`cNPCAF0!Ef zk*Q)j&KwMHYklR)6=?I=^s0tLqEsR-OFD<4$Sm3bIZ#1_l_-fuI!>Q5f>B6C=_ez8 z*ugm03|irCj00o$GtC>8oXv}07J5i<=aGJ)ZK6)yOqTX0u$y{c%N|t+IP%_gSIMXm zHecEp^!p0fb6_@IBK&z{ZcXB?i94+8BErNrEq;G5W@*L_IM7Vj`7#NowR?}Ec~ot< z7a+)KeGm=1NUJYCA;HY4r1wx7UR7cU5rvl|8s(EzxPr4XAH094Q3Y}e87pO$CIgoe zc)O1lN>>gayQwe{ly0a_KoTuBYMe%_0$qh+c*{r5E4vcjp)DdhJ6K~niC|?^jl4&~ zuRE3%zoC-f5odmAYej~sJH0{E1RC3}zC#(|lskC{!O{I|#7l(2`?nsSn9U{ao=7m+ z3@Iwyuf3N;!?coHYt_`8>TC0_p(?FqXipl*DqkYW``Zz7Z}k+Rz#3~yXdu;NFFcy* zTfMrH5gAlalqA+)vpQ^5;Gt#2H3$_i`o1eB{7JxmHm1ejD@AlfLAD{1nU8nIBGk-G zvCowcxI}NctrG1F=}7!zY|Houg}?VVQ)7mqn=+~5Yo}8k z%|DUq^d^9x8K|YE&u{RZ+_CT6NN8u@O5@&h->p{;Ks$jnYlqYVb0h2M`)vj#8wtsS zYj#W=OQX}V$>4PYd!s|1xBm`IA?e~a!7})|xIGs{-&T|N6Iv&oukF$ZkO=DrKSBn# zp`p8`4da2(9$@DPlO|8o`c*hYe-ijjmLmoKXnxgnuC3+$M-L^dSR++3_>ldHHiLaG zjE_M9{2mz#0!TQ4!BU+Bge0`HHz#tus8ps$H6c~-2W-d9 zmUVG=LU4Bp?w$m9cXxLuSdifE?gV#tclY4#65M%k&dXdTcjn$X?=_3H*bAtyckNx( z-BtB}rFXT7tQ6NxSU^ZJ=Dki#BMtqbD9d;^AELK{=KUuAaIAQSWIjxo$1#0_+u|WX(zk2HE!sqPGk00I;ul`@p*%tfrC7u;UwRsIB9>H%6Euj z_(RA#E5^pYGFrt;y+43Yy%O3xW<&lCQxUFO1vQSb`Uv9iny&ArLm`Vdk%-U_e}tAsri)a2+5Q=SJcv{@^X$ z0&Ec|h4p$Rg%>-EqD9+aZ`OrMLI1}e0(^)4@M1J!sf_u3>Y4Jbk#GnklIssJl1M{6 zvWJ{gZR`yb-+9z)EK@!qWJ~F2fFwU?3B=2!Z=i<^MzJAcN~A#Rdlbe@X_qbdDse#8 z?pbIj@<=dtrAIIriYja>8VGsE(F-dou!Wv)b;`=1S>QNuc@oYkd;Gw1Ra<37NF8uF zRW}Kuw)$n_u07}~SB@dDO-R8MlDm{RF!0uL$zDQdFAAK#09Q^O)bHjG2y=4+eAqHP`5 z6IT)on!Qs~T~nXyl}pzeMS{;4n=KX0Uqw#D!9BmTD_rI(mCkKRG|uJy%%PPQ7E5W+ zb)zfX>4LHnx{_!~A zDWb6;T@mk>vf;5nc=anoIY(g|Rn%A2Ts6Z&ue)sTKpvOyoS^XH8Z+E$y)-d>fKF!t z6>!b&6-l&l8E6xUy*3?(NnHqVJNSV05MY$k-OUK@(0XZ$YLW%24qcb=nX`a$uFeoNF?=6h%#f ztU@Fhxb3s4=7B$e*$Nww*Ap8AZ_}tq*1a~+`StMQ^gT&%zwrdkNGz z@z;AOCLmI_lB{a(mGQcy6DbNy3tZoGcAZV}`@UQ`l*tncZUjTyWY6qx!O)`-vD(Cn`mrm<(ceP9tL~Q%?zzBp z`h51Tzy?KEvW4ngSJ{R?T^7N&rjV?x4Q0BC*Cr#0{o+Jk8d@NkP_15*?;45#wHfY7 z?Z+@S=k^G|^@rRV5j!f%Q0Wq@Yi`yZBF=C;B~Vc!L;Br0loX;X76&lC!G#ig5Mh1n27bzY_t`Q?xAk{KGVFu~55X)=$E zhnuU-ZD+&qgew)|D$|WpuZ1S_BciH;I z=avf5TWI;#b7s~#cYZqz41^kBiOR=Wa5Tx_pj4(F@>NMVBR>q&D4`D9Z|^}Ka}bqpJ26rjth z?Mz=*ZYp^gb0beE--0~VhCixA7IBY?2xE-2TYY3s4T zg*6#bODln@CmrlVc)sBek|T5@fS;q{pr$SyP)7L)NNvBTg8uyB6M})xcO({sJr$T@l6+z#C=p`%Yfi7kJ1sM?tAu8o-_PT&4ax=?u>(@xphSre_k$0`KO zaFvL=7k8ej7!NnTZXcoRO4|!LSDeb)?Y0HAVv@whVHJltjuAK;-0XWt9R_%rDm{ox z@Su_kvirQA>p^Hk4lDmYFpD}>SHW5Z$(0bM-lLk&7mS@3%oQy4Hm*9yDJ{TM4n7W=vhE%;HGB97D5}0X}w)L`4*pc5i{n%zztFp5^m>0ibh+8*)C2UP8 zFPNqUul(HOxrTvOKGasT36dBBLo-YtO9vQ(>xU{J0lyg&(@Pm}1qQFyyXz{_<&QJA z5x8I%DUMye7he2+4d^S2Mx#BGM?MlB^PR_WJ=91KHWWb&`;@7nI8o<5Kj@NhjE*j! z1y4*WbPpGoZm8Q_99L76LnYdh7b2G9!Q#(4%TGorW$T@wj z(o9id@KarAyNkeS%3v+o8%qS^h|S&w?{JFLQfqetfy(hgLd{VXT%pXRzBC$ z1@1lOQQ-(7y>a_)j+JoZyQU%Fij<$G$y$SM4UwbFp|oIEDkuq>qhIaqnN5^uN(tKc z(z~wPrE{Ii(Xo|ER$!nCAh!*d5xe9>sdEQ|xt3c48UySqG_n;5(`<|LDk)X`ACvpZ zE&)epjk+`H5;nYhFk^bb9wbA@`|&sNe!3DryW*1$qV(r6B!;e5>(7#`z|RSOkddB4 zi8TJe6-R80S9inTX-s0uOJ^r35ShwZegENh$h6>4R>+%rEa}+m%uJ!5eIh?Nq}je1 zRJ2uIxf=_ZL(T{r(cG~_<{bgh_EWb|bE8pjbe-4;hdcpruiX5?Aw(KC<1UAYj|pIm znM@0v3b8bAWBjIsEU1toma2HS0^F8;$#y@dk!zUQ%0$kpHl6R?hoxiRz~H+wcTc<| zXni9 zWg_plIWs=Ci%m$eU%uNV>x2yPo)Jm?W?I70>(i;SixYaOL0AGSG69>ouOv+uMQx!j z+xt<~ICa{u;on(oMQ&3q?oF_wlmKie3U`1A=-aCen);1ePyTZk{`pLxtc;7Maruv&v`=efYk6)}`O@o6U zj8;@@#T=qObRmt{_psg{%*ycVQyW*{zEm_|jxn?xd}1ZG#!6tROrK@t5v3#>JTToh z(6hAIW6T?lE644YJ9q@}m`S22lS!t-d-eg|wTVLj$r$Bf=B$0tXlm*rF25TyT&Z3> z+sy_bHS_2Af@Ez|OGloXlPV0IX*w71og<1;#Jr{?+JlnW)nYySkf2iEqKJAV;h@%S zsOBIdGrCHD-#LYY9Nd~9t{!fZ?jaaHC=qYOzd^w7#(o_H&|Sq5Fj2bDusguO*Z&M41Ay!Xn68QGNf+E7l+ zIOwSBhgyG+MpPkeTr$K;smRLojbdjGoKt$_HY3<2E#fX+O<6{N!A_j2m$`U% z?lcA^%4#5Y@gE{_vCMB^7;MThjUjAqt6tKyLO<*_Nvpp;q*utc3p-+M$z^HS<}f-U z0VTO`W=9^T?sqDH2(j2*OnF<(oum!-<*ZL9m2X2}rDGJdJy8JV z3Y!g>L*HT^g1L{W9#-8pe-N2=K70CbNWsl0t+tmDq(CGMSDtA1aj)%;>E4T!3snAA4k0=o{5}c4E$2cn=X@IFb*X(~?%EP7xhKCJF3! z!o;)(bdM-KY|m9> zKr1;kB{z(=YtgiN9-7GdR-Zyfir%MeC#Q&5k&4rPmv?>tskYVA=!Y7V)rs`Ij!}jt zZl>{BX4Zk|wVzhL9FE%A4wlDzTNU>H5q%bG%$&)Cn4hZ6r%7vx2ud)U`qu$cUtAqA zfKl{FhcUUB1_?Oxl7Se{xe5xZnshVO_{MUrn^v_(2_2QTEsE<^yh0#!$LXTcA-=%j zeT3JwKnKJr5uL_W))Fx3wPlG~ zIIUF2(k8xAVN8bauJ|+B^YFD3N!q)-H?|qI!v_mr=CHMGw&q4rh(yYkjqFTK9$V>){A;(voY-6xF=nVQD#G%gkg42I?7=H-y6#tbW_p1eEwx?`%Md zFY3IyHDx9j{jW;X^>1cPr1GiHKKEU0jMJ2CbwO9GR z^nMN&X8Pd=tnMJrER{hC8CAbJ)|)tLB{1if8?st|^#}Ii@5zeo0uX6x_azl$y*}+0 zFger77|m{M?G2_$tbNys&XWVo`AmUZLJj^ab(*!&wbrbn*)+R3>zEe-8Nu?LpZyxe zJt85B)g^Cru5SWCHu^mqF*c#y#)A1VxTly>Tr91nx{Z+#dnH zj7h{^Mk+Y@LSG@U73~{Q`s#xWGq$2vs;oDlBh*>rVMArW#W-Vsn5dVF#+d$r4V+5t zS~f3X^##mBf_ELi4aJYZccCn}x^far-VlynWDK&IY{+B z!=fl=Jqmg;LZM(}m%Y|Z@Q2IwN&R7s#g}lFL@F$sfa86Y0z_8*7-EIC+$G4Yz}8SA z%2ldKSumtuDt@&1=SS9NH!1Pr68RyhY^_0gOm+WBX;tItymSXbkiJKW$@UvSgT9AE z5>pWg#HkX$VwNK@U+}|4@i|O}4yILvntLH_HgL!+Eh=R6w38(c zU5?@AYa?X9*A~Lx96?b?b0~A%bO{g!*s-g`uc=EmcfYHVNHV#tDKq-`?3E{ou2H|wWc{4HVA4xT z*>G1_gS_B^T)DsMgM-RI|8pn7{F{yUnOUi*)-tj|GGm>ZuDr!|zyYh?u?Oho{n~JM z>&A(h&N2e>Hr|+q(T~|;`5=#(+2?%W(J`kY3gBy-{nbkf!=m_^&#lW;@FQk|_~Fjw zR^tayOj+*`MPn@l`+q;9EqrHtP!K~&p6qlov!GO0t5GNIhH+agz1 z07vx2l-BfHh{zyMUi7ltV2heTGHqcKGxT0pF&UhQc7O1)pN#&g%cY2Wz=M38o`Ob4 zp4A&VIB3e<>NnC9p39j_q+AwX&8}wCIfH@M;Qli)ObCU#)Zzl8(4@>Fa$@RxwQ)oI zdBDaa!%XZ<=Z?;TyvaGNtSDq&DFyobZZxZdyE1wojR6|jwX$*38){kZAzVaGBg>i( z0wO0|@<%RT99QTR?d>9;#kBD7=+d9=sUBB)BgU>rJ!qAmv9DHYo;Jq7AHnC$Xv%T5 zpP#ZH4Zq^{lAw7I?0$QC2JtYYd`ejBZJ``ULvzpqc(y}3XSfM#&g3}&{&o|z^=wag z{qpwt+QR04_6o9| ziF5!wNS>JUFg&1P&^=#3WY2j%e~5Sz`>9*>=@FS9f_W9!a6E0?qbh{w+1Jk+;FQF3}CzHdHI3^`8+&iAVs*#Ner1=36^1GyvtFa zLyb7%bBb_9KZ&9!s3iKrMuP3g5Nlyv{&op9B2;Zm5^CW#aAf|Mql`0V8UO{*iYmIk z#Z28cXalN-YRCJdf!0$zEL@73?&@0P8!3|w&Q%pQxy=VW8O0*mMaWS1MpnQKA7rt+ zScZTDWd!W6Y->ao?hl@;iNs{)R)&R}95&!Q9wi;eRe-2hy9xmkre!amAzO}3Q2UOS zNgPcj?mM8$K}hS~Q}bzt?xu)}_^ze~u9kGn?NKDTO^|%*rP6DqN>$Nr4`g{+s|3-a zwTbw?P@-xwDcIQ6B05*F&`a=-P zL}(81N+#lbq+QgHu2YIc!Jzp@Vxr)c9RWp(oZ&+PU35(s*J7D+4g`zfx9IPtr=O-mw6c2UXBI>r^OmKox0(7@NHIE>)jH_2<*lsacM*3^tQXg3pS z!PG1{2IJUg-7`X*G9Sdiat64xB3(`^*uI&vU*G9*B~}UqW;`bL5b$+o#&ZE%4B0N9 znX%yb^^IphW#jR^uy1L=dTxa9Xi*yU`rJ=?9%Tn-h9q!ne&d2)*`q&J5|c%9VMWkj zIQCIMD8;`SOS}pXwV@@0F)$6IK2-N!D4pG;mm@~0i4OLY`yMMtz|@M%bbJ_XcUZ+e z5&Ad;b2J|!I-5amW3Cj%2$q9_V|lg)=v7JHA8QcSfMWOQgB~)MRE`JU1r9IeegyuZ zadI;Uk7iR^==1|XO=GJO|2*`x`Ec0qvJSoM);8>{e)E?sBvooi*3#gN$d&0n7`ol7 zg`beC-qBi#tnXnaY^yt~IIbH<>slZyofOg8s4Yef-CJ0@Hy!nVGEW|aR27ybdp?Qh zhCfM!?k-3flhV6rFW@|w0>sIjx{{6Iv5Her3Jj|s479OQU(7#!vywY*SMwb{9JgdX zbgylFF-35Q)Qq`M#uo6ME65Bq#2eg7Ns z_rel|v2$TDkzw+gAjtvAe7Wz%Tk8cS3S=TfqLT7b!eZ3@L!+`~74l=YAPhktp*+u| z(ET;i<70p#wT!~y<&;e{pu$1_HRVFmabm%jNxe?&uc!PS3;+P@?Ua9&iO|^U+c@ak zP@C#I|G`%OH{AF7F||mAMlimvsXDIPaOj`QyCoUfdf{kElJwRmOC1;F^QEn~CnHAE zcv{!_l}Yh^U4HaKm}x^WuwOF(iBzlKA_kn?@>ZUuPR?W(yHt&Fx&QWrIyc9iQbPAA^4lh zT&YX}?$jA~vtkvXSEd){pqKY(N##XAb%-HC z90zAS4c56`1ETN-=G`pBHRoovn#O4g>lWv5ywMkxuMi5#okd2um)=$iw>-KmQ?c|l zJ0kad(%6MPSr;8deIe%@Ve4N^yq77o)zB{~zo#zN#$LAH)X5WJM=cEEB z61nT9T9GPn^xIrNZF|QrC=7|a95@t#DErXz&_AW1>dY^{8@k(+mKoqG!VYlPq7a;O; z>4_Qb8a(+uC_6k#kc5@hzl+*Rju>bg8CDM*fgJUKRFjcnZ32hTH6AVu+_lYW6)4A1 z#EG>Vi4lAxJgF+S1Pj=$2bl~2!>`~IWLf<3lminfMkPsup<)Pjt-xRv+`$h1jefk{ zK+U5U&qMZ*Vk}EY3C-1(%ZF8EA#c*={41%m%)9IH_m!oRGQp*1{r&F!Izve)rH3HQ z$+-rSmsJ65g-7_KQ=IrbH*(Xv@leLKQAIy?ko=b?2`gsw3;OlS7#coo7;pJFl0=wD z5pb9-Jys?T0TOrPFA{&pA8g5C7&$0PiXn{$9apWOqt9f9?+-0%mg#hIYuE;s=e-QM zf`u_}a6G0zLNF(d{$#t)LPehCHhT*#>she1Y!O6;hkEiU(N`O&I$V=OBi zG_EGC7TLT8Y^&-r{H*5^Iou9B+o(@arJqfuqXkEmaK`J^&@V~RAr8kT4zol*2`DP$ zWOX6>4Jm3=E#%S1EB5jwN<$i+?Ol<4^lw>9UEr=X)A68hg7o?+`%C(~%TNcnc%dL$eAWADRIQNn&_-SPLB-`PWnDJ80xPO}@}ita4ni zHYtAZ9$PLRt|UZ0m`qoCbZ)MUdDIgoc05<^{;PmIIKk}{@1e<>lEYA6c@bJhWp6+t9d_RlEf7P%GJYk}o6LY&s$QBduC-Li z_>5#5f9r0*O>+H?yBS)pnz_2}vbsfA9%4DO)c$SIo`{_>r$^utO{MZ;kR8Kb= z3$%-)oqh<7p>wu1O+O|uZ;u}b@FIsh(i&Gk+p+uL%(%ILK!WklmM*A0VJN_xvIsiI zwNCo_dI%jawu{SYLPB_|OE)`K1fC<*ZWeZVBB6P*WTA`!&EjL!H8g^WQP=Kc>;bSq z)L_4)W|>_sBU_PwT@*J}i7TOwz5(KRXZg{HO2^Q;lvg`J$GRz4u*(~K%Esx9_Q zDzmd5FE#cx=?4cB@dIhs6{z9V;maM~s4=UvDJVrcH*=w20k~FugCn3 zN#eCsoddHC4c-0SKfNq!!l*oVi9+Q{{M#p@M3#9-{6I$bBIm(+hLM#9-Ko-jt7Pl| znPRg-kO?;voaB5h&nOJ`%<-5|vRq*MTaCnaz1>denhA(acXQ+KW4MFVsyVYZgoVDP z`sA3&6O^SDW9UEJo)beb^3du4m;;@4r_9jCxNrR-Nv#IdQ<}9w^LwnXEXqkOJWVB5 z%sYVk{M*xSOhNb_I)cf! z)(V_2jFLQ%S3C(1aFO~cbfc!6eTU}e5|IJ(2NB56piNN?? zbMl`^2hkFF=}W(p3ihG}aR5YuutfuwT)Ay@RBX^tK4Xh49=$qQSwV>STC9msj}uD= zf^)Y;&?~5Tl6R9tOXrsjj_-sAfmKFAOfM!y?s*lV6i-_;27b(oY)gso(c&w6lo)C$ z1fR?*D+Run$_w38h`wV#+A(2F+XLyb*m!NM5+w8FrDD|+6B^RZjWwAhnzCpY1VhA9 za#)l1YFa;IjvsffSed?f8T#-JWn`~q909c#@%D7%ca6X@9KKaNbO21Gwzc*!fbBa+ z5D>o{C=kqfxsm-4?z#J>zN&pAnuL8d3-!nWh;Qx^^~b!Cn?;D0RRy^FGV%omU8P3l zhMzMUtvg74J;cA1p4&Jx`H2N*+&Y&h!3HKd2zuAf!-B7V;!LpZEl~BJV7`vhn7<=; zH`^hR-`qY5<#0sxdsLCIJyhFf`}R}zs4`SV5}#L3NCu?z+P$7ij-J_teuf4kc%0CC zZwE~=VkFiuKSyk>PD(F>T7)@gxp=T+bt#i8UsjVy!C+Lko+BtvrZQJH=H$mR7peto z7&0+#aGQb*Jw##k2N)O1$wR*$1?52KS)7&oMP#o4BL3^Z`I0WSz)^QYjil{*W5X7ydRxgXytUE;Dkke6TAwdPz1S-?`r7N7FVK9OdlnyQYFP*_ZaW*Jm3Rv^vQ{_9xWnMwHJn ziMYdM5y!?90_$RsG)ZpMD+u|7$=>>LPmc7L>!|k2)O^ev`=Tq0Klbuz+T~%{%^OZQ zR|Evu?2$!4U{SQ)f!T^rGC^cDO-x?@sj?_AD@rOr8sxB84A5v?5kb=wZ)RZto(nww%gkLJGAvb9GyYi%Mk(aAVA8NjS&!XHg61{ zBgDGI*vn*%I#2n;v+S@~;7}5C;nn`NC9^sBoc;><`u*3_xqtc1E3^N9-;y~nXuclk z|H+n2J*3?P=p&5Z$@#l;fWe7N88Rxn^7niZ6FH=a0UXvLBL;erdli-|D(reWgxWaV!{DcUFQ|H@9mCCiyQPsF!IOgWqm)WbT z`{<1PR8-(kJ*GAwB3{r*TcD11T-sPRQrg&LoXCWGRy`7!rI=_Ik}K^8skSsdW=FbZ z9Ovc6gA|yZL}(s#vD|j6Cr+_HSwpv4r_T=DrG_;Iz3?Q8-J~* z_?M0C{}@F0C)+Z)!HNPQgvE1|N)`*euTX>TKP?bIK^%YrJ^j!8N?TKH4Gb766ZYf< z<@Na$3>_ZtF4@A#5--6C1Qny0K-A7q9=fH(J0pV-cA3%9m>}qffuR*<5UJ<(qnX!9 z;)*Wt?fguYI{Z_lSoVb);R8Uibm+$bH$ai2zP|Jb6I@Nnv=t_)0>CzieclslC+xoX z9s=Ss=ZJ;k4Gsrq2nXjI5^Xo&Ss^tET1=ODIo z&HO>=QxDqc^}q%C{+_hSfwWekKtT{q=V>oJoDBc#~4*R*qu(PUd2HX;FFSR}a(7oH~&jSdBA4m|7= zY_p#f@{AxcTRVd3*=v+%{yYoR*4n6{U%!g5O@~b| z4U9C#jLB$6b$!swufm?2R>zL65?xG}7=5}Ij=hO+vRb54CYp7vqqHkrh?&o%5p%=z zF<5aZYGQU%6;^nv$4YaIN(v%^6}p%6yZ`89u_F3>M&2H{w(Q#IU zkmCnv6BONPEQi$=LQ__-k%AYH!DM9~e0V$HjEj%iuqBUp?-on8o5Y*)oFFP^t7 zySxj4A=}I;;e5VTV0oAv4h;)B#Q|YD)aTUOVX0W(FH^m;o32Vq9}}l=iF3xvtty)1+*V|m zOuopAGF;Cw;w zk8RuEoP&qu&0pI0AUqvwT4y~9k&W3O6wrSd*p)+R=QFy_C8zI4Yyu^%#3q|?e;5(= zjFvH;%r;*4-e^2;<{HHo9F@rZlqy!A%HROPjS(LJLado?kczo@!E=d$Z|T-+-?`lU zh$)Lrv{N!uYHdyGO zN_+WxV*_4eoH5d7$lQ+IAdCCkde~L+qWW5E{$yFch=EL7s!cErTt(3KEqxD0Elu7v z_?rQOjF50^>I$7)i54bwnPPmy1QFC|O?bTlkzQ~OS*kEG z?#7|CvDCjiM(5nv>&e0CoBb~no)9B2@^a8umr%1MH8(CPnVP`PvkdUdt zZ^wS)WU&nzbD{zkrJLN2t$B|CeD%DqVvnN5| zDaV5fN$x4$jD-N{`i`DDp!NHGGa0$096Hlh?7>#WkCI>27eY|xzJ1GA43=T~90t}X zIQeA@?g4xQD?kY?b#8BqL~)L&ZwsxG6Nw)~eDINhpvZJ}!_5jss?wEp8F&C@i!ZIc$3r z7xSS)D6AoBF#r4g^dBzAyGLe^d`zy8Gq#kAm}eqWV&EDrEhK6OFZ2w3~g4z@WbV3?WE$GLcUzJgYf-mnkHmLLDKVF z$rDJ+Ll}OqM>h||l)ba99iUL#yMa+THSz;NJtc;C9g=Gamz`9&pFl1F7c?GGuS#KyW4LCr4KMe@Frzd` z_3ge%nT=K0 z`Sg>V7%!!q6xzf@jywBy{QS(VYBK7mn3X@wpmxRAq;E;*_~dQ=f_1$zj{jM zv(3_zTAZFBt^GA_&g3sadmmdgT?s99XaHx9-PvQZd(M}dL|ftfRCk2dx|hVsT3*q{@ zc_Bdo^y%DY!5-E4$EZZAlrV3=W^bX(UgR0nSacBBP{+nX^0=Vt<@sR2-$HkCX|*7* zTm>3HrMdQAWg(A#vi(9`QpKX$)0-2hy|#%CcdwQ=C2yTlEn5bo4gJ6-&|KEa>@1SL zpp$~7*DDA=T8;<@ahv`u5?r&M{KM|)Gyc10J+>-XAJBs)RD#gK&Z(4p^Mgtjqp--@ zjV%8HXGly5(l0abKa%p=Nitkgd|F`zKZFaZJlaa8A4W3%-h@&ma@_>rzi}WQ45$K} zc^$>qu~e86+0qqlJ5~BP}Xw%BXw^c-4EThazGtB1$Gk2S1VZz@!OH>_ggR^ z+6`qO(&^9Ec)mX$(NaDp zT%D)05N4r&;ummVoLRKE0|HnPIj@~<8BI^WR}k)#s1Z6_T+TK-RjSn_eIflWT+n@F z@0N#@uN1)RoBgud{@IeU?yB{~rq0NuDlop=#e`7keP-*S)POXjKrxspPR2>S%Qb4n zW}c9iL+1WZ*gjv(**JBsjz+o-!B5QCnhTBl+BM2?h`oJjjXL^yLmy-tqt1K~s z8jpQCGwkRy?FAroiv^2bR|pv;0Ffo@y*u9a_}SLx#H&VqAMEMX!|n{m?uN;%`mV?` zwyY?FSjb3`58fM@lpgR!to*b|`XVPbK1Fq`CDp#f7++o1UOat#Pf=}RS|VftQaeQw zH`;v_UN?`kgLGm*D~}vACM~V@{u(s%LY(B71b@MN#`19K3z|kh?)3)&PFzKZyxNg# z)XIU{2@+$2md2?fmS<*DM26;;!}o&QmCAGVlS}cZpDU;r1*tJ=dr>n!@<=a}qBByd z`Ye+?&K+StYHqeJMu_jd7qn4V6=eKB<#t|t@}K&^zg~mA1QP6=t^S`$deJa zvQ`SYtd@((b09HR|8oMv%wX}m-5jQL(RC1R4sv!aL4rU!#2BDP&^~o&4XvWxTZcfa*lvsVucXX zU1xn2Vy56njco(PHEkn*7fp>C+>_eqzn|V@x6k6AC$BQMS2B0~(dni@pww5CG++C? zXC^sp*4wQqzhHb_iywN*t^>!y$~-ewoJQ<%7$tgV4k5EDR=kVezF4}zgG}TH&d}1c z=V#093Dc*MgnC*d=bm~Y1_mm{(d-R+t{CJw)iMC`8)^?@fDj918^zwATZ;a>j+g%m zjgUJS4k*1WzOT>buNPp0mnRSN$5Yd_)BXofj8aH!L_$nbo-ChA=)?9pkTev2emy8# z0C9e3KXJmXGgLP*e@;I!)U;YMcYts{8=kv?M#HC13fAtaF*0yMV&q;y42*q(9|6TH zsvwBu2mf6Q(!Xa?AoDVt*BMEP0s_4Qe)saze}7NGUuOGj9DRKK$wuEoPv7RhxTpRm zvGz+S1OUXpUjDaT>tA2oUnAR#|G$L(yCmixDCKwQZ+m;ccE0~Lvb{=P9>)Kj^1r43 zQ<|$^5U--%QmMRNzcvZ~HI6=RzgYVpLBW5WMSrym{|hpeUt)jhm?RSW`1a*JDfjENSApQemdsX+_+SAwTSCYG5(R$AZ)N{+1pFiP^S2au|CayD+}=_py&i>rjcl)>xAOl(O1!uFfAtk_>vsSA zh}$^dXz15n{1XzUw-B!n;cfll>*@H{$o5K5c?0pEmIJ?qc{RDWHG{9kjK4;<7o*1S zF#l<36MgwhtbZ7_`yJ-(x$4g^Ue8On^FJ2)GPh k$o3NS{59i$cG8d%1%26a0s!E?d_lkf0Gz-4KLEi00jS}*CIA2c literal 0 HcmV?d00001 diff --git a/coderd/notifications/dispatch/smtp/html.gotmpl b/coderd/notifications/dispatch/smtp/html.gotmpl index 10ef6f0796c7c..78ac053cc7b4f 100644 --- a/coderd/notifications/dispatch/smtp/html.gotmpl +++ b/coderd/notifications/dispatch/smtp/html.gotmpl @@ -26,7 +26,7 @@ diff --git a/coderd/notifications/enqueuer.go b/coderd/notifications/enqueuer.go index 517b69d2c0509..3a2cdaac687ca 100644 --- a/coderd/notifications/enqueuer.go +++ b/coderd/notifications/enqueuer.go @@ -121,7 +121,7 @@ func (s *StoreEnqueuer) Enqueue(ctx context.Context, userID, templateID uuid.UUI // actions which can be taken by the recipient. func (s *StoreEnqueuer) buildPayload(metadata database.FetchNewMessageMetadataRow, labels map[string]string) (*types.MessagePayload, error) { payload := types.MessagePayload{ - Version: "1.0", + Version: "1.1", NotificationName: metadata.NotificationName, NotificationTemplateID: metadata.NotificationTemplateID.String(), diff --git a/coderd/notifications/types/payload.go b/coderd/notifications/types/payload.go index 02b505caceb65..fbcec19bf76ed 100644 --- a/coderd/notifications/types/payload.go +++ b/coderd/notifications/types/payload.go @@ -7,12 +7,14 @@ package types type MessagePayload struct { Version string `json:"_version"` - NotificationName string `json:"notification_name"` - NotificationTemplateID string `json:"notification_template_id"` - UserID string `json:"user_id"` - UserEmail string `json:"user_email"` - UserName string `json:"user_name"` - UserUsername string `json:"user_username"` - Actions []TemplateAction `json:"actions"` - Labels map[string]string `json:"labels"` + NotificationName string `json:"notification_name"` + NotificationTemplateID string `json:"notification_template_id"` + + UserID string `json:"user_id"` + UserEmail string `json:"user_email"` + UserName string `json:"user_name"` + UserUsername string `json:"user_username"` + + Actions []TemplateAction `json:"actions"` + Labels map[string]string `json:"labels"` } diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx index 73acf82f7f175..58016ced4a27f 100644 --- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx +++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx @@ -18,7 +18,7 @@ import type { NotificationPreference, NotificationTemplate, } from "api/typesGenerated"; -import { displaySuccess } from "components/GlobalSnackbar/utils"; +import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; import { Loader } from "components/Loader/Loader"; import { Stack } from "components/Stack/Stack"; import { useAuthenticated } from "contexts/auth/RequireAuth"; @@ -63,24 +63,38 @@ export const NotificationsPage: FC = () => { updateUserNotificationPreferences(user.id, queryClient), ); const [searchParams] = useSearchParams(); - const unsubscribeTemplateId = searchParams.get("unsubscribe"); + const templateId = searchParams.get("disabled"); useEffect(() => { - if (unsubscribeTemplateId) { - handleUnsubscribe(unsubscribeTemplateId); + if (templateId && templatesByGroup.isSuccess && templatesByGroup.data) { + disableTemplate(templateId); } - }, [unsubscribeTemplateId]); + }, [templateId, templatesByGroup.isSuccess, templatesByGroup.data]); - const handleUnsubscribe = async (templateId: string) => { - await updatePreferences.mutateAsync({ - template_disabled_map: { - [templateId]: true, - }, - }); - displaySuccess("Notification preferences updated"); - queryClient.invalidateQueries( - userNotificationPreferences(user.id).queryKey, - ); + const disableTemplate = async (templateId: string) => { + try { + await updatePreferences.mutateAsync({ + template_disabled_map: { + [templateId]: true, + }, + }); + + const allTemplates = Object.values(templatesByGroup.data ?? {}).flat(); + const template = allTemplates.find((t) => t.id === templateId); + + if (!template) { + throw new Error(`Template with ID ${templateId} not found`); + } + + displaySuccess(`${template.name} notification has been disabled`); + + queryClient.invalidateQueries( + userNotificationPreferences(user.id).queryKey, + ); + } catch (error) { + console.error(error); + displayError("Error on disabling notification"); + } }; const ready = From f9ac6d20af67e7ea6e141f8d6dfb527e8ae25fb9 Mon Sep 17 00:00:00 2001 From: joobisb Date: Wed, 4 Sep 2024 11:24:14 +0530 Subject: [PATCH 4/8] chore: refactored notifications page --- coderd/notifications/Archive.zip | Bin 36113 -> 0 bytes site/src/api/queries/notifications.ts | 38 ++++++++++++++++ .../NotificationsPage/NotificationsPage.tsx | 43 ++++++------------ 3 files changed, 51 insertions(+), 30 deletions(-) delete mode 100644 coderd/notifications/Archive.zip diff --git a/coderd/notifications/Archive.zip b/coderd/notifications/Archive.zip deleted file mode 100644 index 60e0ea451bc67d838f1880edb9b86ee77f1ffb1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36113 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 diff --git a/site/src/api/queries/notifications.ts b/site/src/api/queries/notifications.ts index f8557637e72f7..ae9ec1cb1c763 100644 --- a/site/src/api/queries/notifications.ts +++ b/site/src/api/queries/notifications.ts @@ -5,6 +5,7 @@ import type { UpdateNotificationTemplateMethod, UpdateUserNotificationPreferences, } from "api/typesGenerated"; +import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; import type { QueryClient, UseMutationOptions } from "react-query"; export const userNotificationPreferencesKey = (userId: string) => [ @@ -136,3 +137,40 @@ export const updateNotificationTemplateMethod = ( UpdateNotificationTemplateMethod >; }; + +export const disableNotification = ( + userId: string, + queryClient: QueryClient, +) => { + return { + mutationFn: async (templateId: string) => { + const result = await API.putUserNotificationPreferences(userId, { + template_disabled_map: { + [templateId]: true, + }, + }); + + // Invalidate the user notification preferences query + queryClient.invalidateQueries(userNotificationPreferencesKey(userId)); + + return result; + }, + onSuccess: (_, templateId) => { + const allTemplates = queryClient.getQueryData( + systemNotificationTemplatesKey, + ); + const template = allTemplates?.find((t) => t.id === templateId); + + if (template) { + displaySuccess(`${template.name} notification has been disabled`); + } else { + displaySuccess("Notification has been disabled"); + } + }, + onError: () => { + displayError( + "An error occurred when attempting to disable the requested notification", + ); + }, + } satisfies UseMutationOptions; +}; diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx index 58016ced4a27f..f04f9b9e1eb46 100644 --- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx +++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx @@ -8,6 +8,7 @@ import ListItemText, { listItemTextClasses } from "@mui/material/ListItemText"; import Switch from "@mui/material/Switch"; import Tooltip from "@mui/material/Tooltip"; import { + disableNotification, notificationDispatchMethods, selectTemplatesByGroup, systemNotificationTemplates, @@ -62,40 +63,22 @@ export const NotificationsPage: FC = () => { const updatePreferences = useMutation( updateUserNotificationPreferences(user.id, queryClient), ); + const disableMutation = useMutation( + disableNotification(user.id, queryClient), + ); const [searchParams] = useSearchParams(); - const templateId = searchParams.get("disabled"); + const disabledId = searchParams.get("disabled"); useEffect(() => { - if (templateId && templatesByGroup.isSuccess && templatesByGroup.data) { - disableTemplate(templateId); - } - }, [templateId, templatesByGroup.isSuccess, templatesByGroup.data]); - - const disableTemplate = async (templateId: string) => { - try { - await updatePreferences.mutateAsync({ - template_disabled_map: { - [templateId]: true, - }, - }); - - const allTemplates = Object.values(templatesByGroup.data ?? {}).flat(); - const template = allTemplates.find((t) => t.id === templateId); - - if (!template) { - throw new Error(`Template with ID ${templateId} not found`); - } - - displaySuccess(`${template.name} notification has been disabled`); - - queryClient.invalidateQueries( - userNotificationPreferences(user.id).queryKey, - ); - } catch (error) { - console.error(error); - displayError("Error on disabling notification"); + if (disabledId && templatesByGroup.isSuccess && templatesByGroup.data) { + disableMutation.mutate(disabledId); } - }; + }, [ + disabledId, + templatesByGroup.isSuccess, + templatesByGroup.data, + disableMutation, + ]); const ready = disabledPreferences.data && templatesByGroup.data && dispatchMethods.data; From 18b3f9a1229ff71f1b5996e16722a44b07d7ba6c Mon Sep 17 00:00:00 2001 From: joobisb Date: Thu, 5 Sep 2024 17:38:38 +0530 Subject: [PATCH 5/8] refactored and added tests --- site/src/api/queries/notifications.ts | 21 ------ .../NotificationsPage.stories.tsx | 73 ++++++++++++++++++- .../NotificationsPage/NotificationsPage.tsx | 27 ++++++- 3 files changed, 98 insertions(+), 23 deletions(-) diff --git a/site/src/api/queries/notifications.ts b/site/src/api/queries/notifications.ts index ae9ec1cb1c763..036d461ca3e28 100644 --- a/site/src/api/queries/notifications.ts +++ b/site/src/api/queries/notifications.ts @@ -149,28 +149,7 @@ export const disableNotification = ( [templateId]: true, }, }); - - // Invalidate the user notification preferences query - queryClient.invalidateQueries(userNotificationPreferencesKey(userId)); - return result; }, - onSuccess: (_, templateId) => { - const allTemplates = queryClient.getQueryData( - systemNotificationTemplatesKey, - ); - const template = allTemplates?.find((t) => t.id === templateId); - - if (template) { - displaySuccess(`${template.name} notification has been disabled`); - } else { - displaySuccess("Notification has been disabled"); - } - }, - onError: () => { - displayError( - "An error occurred when attempting to disable the requested notification", - ); - }, } satisfies UseMutationOptions; }; diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx index 78e3778a24c75..87678fe2bca1b 100644 --- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx +++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx @@ -1,11 +1,12 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { spyOn, userEvent, within } from "@storybook/test"; +import { spyOn, userEvent, waitFor, within } from "@storybook/test"; import { API } from "api/api"; import { notificationDispatchMethodsKey, systemNotificationTemplatesKey, userNotificationPreferencesKey, } from "api/queries/notifications"; +import { http, HttpResponse } from "msw"; import { MockNotificationMethodsResponse, MockNotificationPreferences, @@ -76,3 +77,73 @@ export const NonAdmin: Story = { permissions: { viewDeploymentValues: false }, }, }; + +export const DisableValidTemplate: Story = { + parameters: { + msw: { + handlers: [ + http.put("/api/v2/users/:userId/notifications/preferences", () => { + return HttpResponse.json([ + { id: "valid-template-id", disabled: true }, + ]); + }), + ], + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const validTemplateId = "valid-template-id"; + const validTemplateName = "Valid Template Name"; + + window.history.pushState({}, "", `?disabled=${validTemplateId}`); + + await waitFor( + async () => { + const successMessage = await canvas.findByText( + `${validTemplateName} notification has been disabled`, + ); + expect(successMessage).toBeInTheDocument(); + }, + { timeout: 10000 }, + ); + + await waitFor( + async () => { + const templateSwitch = await canvas.findByLabelText(validTemplateName); + expect(templateSwitch).not.toBeChecked(); + }, + { timeout: 10000 }, + ); + }, +}; + +export const DisableInvalidTemplate: Story = { + parameters: { + msw: { + handlers: [ + http.put("/api/v2/users/:userId/notifications/preferences", () => { + // Mock failed API response + return new HttpResponse(null, { status: 400 }); + }), + ], + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const invalidTemplateId = "invalid-template-id"; + + window.history.pushState({}, "", `?disabled=${invalidTemplateId}`); + + await waitFor( + async () => { + const errorMessage = await canvas.findByText( + "An error occurred when attempting to disable the requested notification", + ); + expect(errorMessage).toBeInTheDocument(); + }, + { timeout: 10000 }, + ); + }, +}; diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx index f04f9b9e1eb46..5effeb9af1b85 100644 --- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx +++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx @@ -71,13 +71,38 @@ export const NotificationsPage: FC = () => { useEffect(() => { if (disabledId && templatesByGroup.isSuccess && templatesByGroup.data) { - disableMutation.mutate(disabledId); + searchParams.delete("disabled"); + disableMutation + .mutateAsync(disabledId) + .then(() => { + const allTemplates = Object.values( + templatesByGroup.data ?? {}, + ).flat(); + const template = allTemplates.find((t) => t.id === disabledId); + + if (template) { + displaySuccess(`${template.name} notification has been disabled`); + } else { + displaySuccess("Notification has been disabled"); + } + queryClient.invalidateQueries( + userNotificationPreferences(user.id).queryKey, + ); + }) + .catch(() => { + displayError( + "An error occurred when attempting to disable the requested notification", + ); + }); } }, [ disabledId, templatesByGroup.isSuccess, templatesByGroup.data, disableMutation, + queryClient, + user.id, + searchParams, ]); const ready = From 025c633300f979f5bdcbfa280d299ba14215ab97 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 11 Sep 2024 12:34:41 +0000 Subject: [PATCH 6/8] Simplify effect --- site/src/api/queries/notifications.ts | 5 ++ .../NotificationsPage/NotificationsPage.tsx | 52 +++++++------------ 2 files changed, 23 insertions(+), 34 deletions(-) diff --git a/site/src/api/queries/notifications.ts b/site/src/api/queries/notifications.ts index 036d461ca3e28..22810f716dc2f 100644 --- a/site/src/api/queries/notifications.ts +++ b/site/src/api/queries/notifications.ts @@ -151,5 +151,10 @@ export const disableNotification = ( }); return result; }, + onSuccess: () => { + queryClient.invalidateQueries( + userNotificationPreferences(userId).queryKey, + ); + }, } satisfies UseMutationOptions; }; diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx index 5effeb9af1b85..9c4d3193c1bd6 100644 --- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx +++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx @@ -63,47 +63,31 @@ export const NotificationsPage: FC = () => { const updatePreferences = useMutation( updateUserNotificationPreferences(user.id, queryClient), ); + + // Notification emails contain a link to disable a specific notification + // template. This functionality is achieved using the query string parameter + // "disabled". const disableMutation = useMutation( disableNotification(user.id, queryClient), ); const [searchParams] = useSearchParams(); const disabledId = searchParams.get("disabled"); - useEffect(() => { - if (disabledId && templatesByGroup.isSuccess && templatesByGroup.data) { - searchParams.delete("disabled"); - disableMutation - .mutateAsync(disabledId) - .then(() => { - const allTemplates = Object.values( - templatesByGroup.data ?? {}, - ).flat(); - const template = allTemplates.find((t) => t.id === disabledId); - - if (template) { - displaySuccess(`${template.name} notification has been disabled`); - } else { - displaySuccess("Notification has been disabled"); - } - queryClient.invalidateQueries( - userNotificationPreferences(user.id).queryKey, - ); - }) - .catch(() => { - displayError( - "An error occurred when attempting to disable the requested notification", - ); - }); + if (!disabledId) { + return; } - }, [ - disabledId, - templatesByGroup.isSuccess, - templatesByGroup.data, - disableMutation, - queryClient, - user.id, - searchParams, - ]); + searchParams.delete("disabled"); + disableMutation + .mutateAsync(disabledId) + .then(() => { + displaySuccess("Notification has been disabled"); + }) + .catch(() => { + displayError( + "An error occurred when attempting to disable the requested notification", + ); + }); + }, [searchParams.delete, disabledId, disableMutation]); const ready = disabledPreferences.data && templatesByGroup.data && dispatchMethods.data; From 8af93fd2d36281b5abe287cb4166113aa18c8253 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 11 Sep 2024 13:42:29 +0000 Subject: [PATCH 7/8] Fix stories --- site/src/api/queries/notifications.ts | 7 +- .../NotificationsPage.stories.tsx | 122 +++++++++--------- .../NotificationsPage/NotificationsPage.tsx | 4 +- 3 files changed, 67 insertions(+), 66 deletions(-) diff --git a/site/src/api/queries/notifications.ts b/site/src/api/queries/notifications.ts index 22810f716dc2f..c08956b0700de 100644 --- a/site/src/api/queries/notifications.ts +++ b/site/src/api/queries/notifications.ts @@ -5,7 +5,6 @@ import type { UpdateNotificationTemplateMethod, UpdateUserNotificationPreferences, } from "api/typesGenerated"; -import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; import type { QueryClient, UseMutationOptions } from "react-query"; export const userNotificationPreferencesKey = (userId: string) => [ @@ -151,10 +150,8 @@ export const disableNotification = ( }); return result; }, - onSuccess: () => { - queryClient.invalidateQueries( - userNotificationPreferences(userId).queryKey, - ); + onSuccess: (data) => { + queryClient.setQueryData(userNotificationPreferencesKey(userId), data); }, } satisfies UseMutationOptions; }; diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx index 87678fe2bca1b..41e8e431b9f21 100644 --- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx +++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { spyOn, userEvent, waitFor, within } from "@storybook/test"; +import { spyOn, userEvent, waitFor, within, expect } from "@storybook/test"; import { API } from "api/api"; import { notificationDispatchMethodsKey, @@ -19,8 +19,9 @@ import { withGlobalSnackbar, } from "testHelpers/storybook"; import { NotificationsPage } from "./NotificationsPage"; +import { reactRouterParameters } from "storybook-addon-remix-react-router"; -const meta: Meta = { +const meta = { title: "pages/UserSettingsPage/NotificationsPage", component: NotificationsPage, parameters: { @@ -43,7 +44,7 @@ const meta: Meta = { permissions: { viewDeploymentValues: true }, }, decorators: [withGlobalSnackbar, withAuthProvider, withDashboardProvider], -}; +} satisfies Meta; export default meta; type Story = StoryObj; @@ -78,72 +79,77 @@ export const NonAdmin: Story = { }, }; +// Ensure the selected notification template is enabled before attempting to +// disable it. +const enabledPreference = MockNotificationPreferences.find( + (pref) => pref.disabled === false, +); +if (!enabledPreference) { + throw new Error( + "No enabled notification preference available to test the disabling action.", + ); +} +const templateToDisable = MockNotificationTemplates.find( + (tpl) => tpl.id === enabledPreference.id, +); +if (!templateToDisable) { + throw new Error(" No notification template matches the enabled preference."); +} + export const DisableValidTemplate: Story = { parameters: { - msw: { - handlers: [ - http.put("/api/v2/users/:userId/notifications/preferences", () => { - return HttpResponse.json([ - { id: "valid-template-id", disabled: true }, - ]); + reactRouter: reactRouterParameters({ + location: { + searchParams: { disabled: templateToDisable.id }, + }, + }), + }, + decorators: [ + (Story) => { + // Since the action occurs during the initial render, we need to spy on + // the API call before the story is rendered. This is done using a + // decorator to ensure the spy is set up in time. + spyOn(API, "putUserNotificationPreferences").mockResolvedValue( + MockNotificationPreferences.map((pref) => { + if (pref.id === templateToDisable.id) { + return { + ...pref, + disabled: true, + }; + } + return pref; }), - ], + ); + return ; }, - }, + ], play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - const validTemplateId = "valid-template-id"; - const validTemplateName = "Valid Template Name"; - - window.history.pushState({}, "", `?disabled=${validTemplateId}`); - - await waitFor( - async () => { - const successMessage = await canvas.findByText( - `${validTemplateName} notification has been disabled`, - ); - expect(successMessage).toBeInTheDocument(); - }, - { timeout: 10000 }, - ); - - await waitFor( - async () => { - const templateSwitch = await canvas.findByLabelText(validTemplateName); - expect(templateSwitch).not.toBeChecked(); - }, - { timeout: 10000 }, + await within(document.body).findByText("Notification has been disabled"); + const switchEl = await within(canvasElement).findByLabelText( + templateToDisable.name, ); + expect(switchEl).not.toBeChecked(); }, }; export const DisableInvalidTemplate: Story = { parameters: { - msw: { - handlers: [ - http.put("/api/v2/users/:userId/notifications/preferences", () => { - // Mock failed API response - return new HttpResponse(null, { status: 400 }); - }), - ], - }, - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - const invalidTemplateId = "invalid-template-id"; - - window.history.pushState({}, "", `?disabled=${invalidTemplateId}`); - - await waitFor( - async () => { - const errorMessage = await canvas.findByText( - "An error occurred when attempting to disable the requested notification", - ); - expect(errorMessage).toBeInTheDocument(); + reactRouter: reactRouterParameters({ + location: { + searchParams: { disabled: "invalid-template-id" }, }, - { timeout: 10000 }, - ); + }), + }, + decorators: [ + (Story) => { + // Since the action occurs during the initial render, we need to spy on + // the API call before the story is rendered. This is done using a + // decorator to ensure the spy is set up in time. + spyOn(API, "putUserNotificationPreferences").mockRejectedValue({}); + return ; + }, + ], + play: async () => { + await within(document.body).findByText("Error disabling notification"); }, }; diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx index 9c4d3193c1bd6..49f01f1f00936 100644 --- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx +++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx @@ -83,9 +83,7 @@ export const NotificationsPage: FC = () => { displaySuccess("Notification has been disabled"); }) .catch(() => { - displayError( - "An error occurred when attempting to disable the requested notification", - ); + displayError("Error disabling notification"); }); }, [searchParams.delete, disabledId, disableMutation]); From 7f0a831be932fbe62eaeaf43a00fe71fb2993931 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 11 Sep 2024 13:50:08 +0000 Subject: [PATCH 8/8] Fix fmt --- .../NotificationsPage/NotificationsPage.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx index 41e8e431b9f21..cd37bcbd1fdd2 100644 --- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx +++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { spyOn, userEvent, waitFor, within, expect } from "@storybook/test"; +import { expect, spyOn, userEvent, waitFor, within } from "@storybook/test"; import { API } from "api/api"; import { notificationDispatchMethodsKey, @@ -7,6 +7,7 @@ import { userNotificationPreferencesKey, } from "api/queries/notifications"; import { http, HttpResponse } from "msw"; +import { reactRouterParameters } from "storybook-addon-remix-react-router"; import { MockNotificationMethodsResponse, MockNotificationPreferences, @@ -19,7 +20,6 @@ import { withGlobalSnackbar, } from "testHelpers/storybook"; import { NotificationsPage } from "./NotificationsPage"; -import { reactRouterParameters } from "storybook-addon-remix-react-router"; const meta = { title: "pages/UserSettingsPage/NotificationsPage",