From 507aa3ce5e772eeef590234888a509a9be8525f4 Mon Sep 17 00:00:00 2001 From: "gerasimchuk.dv" Date: Fri, 3 Feb 2023 03:10:01 +0700 Subject: [PATCH 1/2] add client for rocket chat --- .../notifications/clients/ClientFactory.java | 4 ++ .../clients/rocket/RocketClient.java | 50 +++++++++++++++++++ .../allure/notifications/config/Config.java | 3 ++ .../notifications/config/rocket/Rocket.java | 22 ++++++++ 4 files changed, 79 insertions(+) create mode 100644 src/main/java/guru/qa/allure/notifications/clients/rocket/RocketClient.java create mode 100644 src/main/java/guru/qa/allure/notifications/config/rocket/Rocket.java diff --git a/src/main/java/guru/qa/allure/notifications/clients/ClientFactory.java b/src/main/java/guru/qa/allure/notifications/clients/ClientFactory.java index ef79da8e..305ee7ca 100644 --- a/src/main/java/guru/qa/allure/notifications/clients/ClientFactory.java +++ b/src/main/java/guru/qa/allure/notifications/clients/ClientFactory.java @@ -5,6 +5,7 @@ import guru.qa.allure.notifications.clients.mail.Email; import guru.qa.allure.notifications.clients.mattermost.MattermostClient; +import guru.qa.allure.notifications.clients.rocket.RocketClient; import guru.qa.allure.notifications.clients.skype.SkypeClient; import guru.qa.allure.notifications.clients.slack.SlackClient; import guru.qa.allure.notifications.clients.telegram.TelegramClient; @@ -32,6 +33,9 @@ public static List from(Config config) { if (config.getSkype() != null) { notifiers.add(new SkypeClient(messageData, config.getSkype())); } + if (config.getRocket() != null) { + notifiers.add(new RocketClient(messageData, config.getRocket())); + } return notifiers; } } diff --git a/src/main/java/guru/qa/allure/notifications/clients/rocket/RocketClient.java b/src/main/java/guru/qa/allure/notifications/clients/rocket/RocketClient.java new file mode 100644 index 00000000..957436ef --- /dev/null +++ b/src/main/java/guru/qa/allure/notifications/clients/rocket/RocketClient.java @@ -0,0 +1,50 @@ +package guru.qa.allure.notifications.clients.rocket; + +import guru.qa.allure.notifications.clients.Notifier; +import guru.qa.allure.notifications.config.rocket.Rocket; +import guru.qa.allure.notifications.exceptions.MessagingException; +import guru.qa.allure.notifications.template.MarkdownTemplate; +import guru.qa.allure.notifications.template.data.MessageData; +import kong.unirest.ContentType; +import kong.unirest.Unirest; + +import java.io.ByteArrayInputStream; + +public class RocketClient implements Notifier { + private final Rocket rocket; + private final MarkdownTemplate markdownTemplate; + + public RocketClient(MessageData messageData, Rocket rocket) { + this.rocket = rocket; + this.markdownTemplate = new MarkdownTemplate(messageData); + } + + @Override + public void sendText() throws MessagingException { + String body = String.format("{\"channel\": \"%s\", \"text\": \"%s\" }", + rocket.getChannel(), markdownTemplate.create()); + String url = String.format("%s/api/v1/chat.postMessage", rocket.getUrl()); + + Unirest.post(url) + .header("X-Auth-Token", rocket.getToken()) + .header("X-User-Id", rocket.getUserId()) + .header("Content-Type", ContentType.APPLICATION_JSON.getMimeType()) + .body(body) + .asString() + .getBody(); + } + + @Override + public void sendPhoto(byte[] chartImage) throws MessagingException { + String url = String.format("%s/api/v1/rooms.upload/%s", rocket.getUrl(), rocket.getChannel()); + + Unirest.post(url) + .header("X-Auth-Token", rocket.getToken()) + .header("X-User-Id", rocket.getUserId()) + .field("file", new ByteArrayInputStream(chartImage), ContentType.IMAGE_PNG, "chart.png") + .field("msg", "Test launch report") + .field("description", markdownTemplate.create()) + .asString() + .getBody(); + } +} diff --git a/src/main/java/guru/qa/allure/notifications/config/Config.java b/src/main/java/guru/qa/allure/notifications/config/Config.java index 2116e8bd..453d1fe9 100644 --- a/src/main/java/guru/qa/allure/notifications/config/Config.java +++ b/src/main/java/guru/qa/allure/notifications/config/Config.java @@ -5,6 +5,7 @@ import guru.qa.allure.notifications.config.mail.Mail; import guru.qa.allure.notifications.config.mattermost.Mattermost; import guru.qa.allure.notifications.config.proxy.Proxy; +import guru.qa.allure.notifications.config.rocket.Rocket; import guru.qa.allure.notifications.config.skype.Skype; import guru.qa.allure.notifications.config.slack.Slack; import guru.qa.allure.notifications.config.telegram.Telegram; @@ -29,6 +30,8 @@ public class Config { private Skype skype; @SerializedName("mail") private Mail mail; + @SerializedName("rocket") + private Rocket rocket; @SerializedName("proxy") private Proxy proxy; } diff --git a/src/main/java/guru/qa/allure/notifications/config/rocket/Rocket.java b/src/main/java/guru/qa/allure/notifications/config/rocket/Rocket.java new file mode 100644 index 00000000..77f9cbc0 --- /dev/null +++ b/src/main/java/guru/qa/allure/notifications/config/rocket/Rocket.java @@ -0,0 +1,22 @@ +package guru.qa.allure.notifications.config.rocket; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +/** + * @author GerasimchukDV + * @since 4.2.2 + * Model class representing Rocket client settings. + */ +@Data +public class Rocket { + @SerializedName("url") + private String url; + @SerializedName("auth_token") + private String token; + + @SerializedName("user_id") + private String userId; + @SerializedName("channel") + private String channel; +} From 76eff8310166e02ebff0bf2acaa4b4892bda021c Mon Sep 17 00:00:00 2001 From: "gerasimchuk.dv" Date: Tue, 7 Feb 2023 21:55:31 +0700 Subject: [PATCH 2/2] add allure testops integration --- README.md | 18 +++++- build.gradle | 2 + readme_images/allure_testops_en.png | Bin 0 -> 46817 bytes .../qa/allure/notifications/Application.java | 1 - .../qa/allure/notifications/chart/Chart.java | 13 +++- .../notifications/chart/ChartSeries.java | 21 +++++- .../notifications/clients/ClientFactory.java | 3 + .../notifications/clients/Notification.java | 9 ++- .../clients/rocket/RocketClient.java | 18 +++--- .../allure/notifications/config/Config.java | 3 + .../notifications/config/base/Base.java | 2 + .../notifications/config/testops/TestOps.java | 23 +++++++ .../qa/allure/notifications/json/JSON.java | 14 +++- .../model/summary/Statistic.java | 9 ++- .../notifications/model/summary/Summary.java | 19 +++++- .../notifications/model/summary/Time.java | 4 +- .../template/RocketTemplate.java | 21 ++++++ .../template/data/BuildData.java | 22 ++++++- .../template/data/MessageData.java | 8 +++ .../template/data/SummaryData.java | 38 ++++++++--- .../notifications/util/TestOpsClient.java | 61 ++++++++++++++++++ src/main/resources/templates/rocket.ftl | 12 ++++ 22 files changed, 288 insertions(+), 33 deletions(-) create mode 100644 readme_images/allure_testops_en.png create mode 100644 src/main/java/guru/qa/allure/notifications/config/testops/TestOps.java create mode 100644 src/main/java/guru/qa/allure/notifications/template/RocketTemplate.java create mode 100644 src/main/java/guru/qa/allure/notifications/util/TestOpsClient.java create mode 100644 src/main/resources/templates/rocket.ftl diff --git a/README.md b/README.md index da5a956c..c833fb4a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

Allure notifications :sun_with_face:

-
for telegram, slack, skype, email, mattermost
+
for telegram, slack, skype, email, mattermost, rocket

Languages: :uk: :fr: :ru: :ukraine: :belarus: :cn:

@@ -8,6 +8,8 @@ ![shakal_screenshot](readme_images/telegram-en.png) | ![shakal_screenshot](readme_images/slack-en.png) | **Mattermost** | **Email** | ![shakal_screenshot](readme_images/mattermost-ru.png) | ![shakal_screenshot](readme_images/email_en.png) +| **RocketChat** | +![shakal_screenshot](readme_images/allure_testops_en.png) | | **Skype** | **Icq** | | Done | Wat? lol | @@ -19,6 +21,7 @@ - [x] [Email config](https://github.com/qa-guru/allure-notifications/wiki/Email-configuration) - [x] [Skype config](https://github.com/qa-guru/allure-notifications/wiki/Skype-configuration) - [x] [Mattermost config](https://github.com/qa-guru/allure-notifications/wiki/Mattermost-configuration) +- [x] [Rocket config]
CommandLine options
@@ -68,6 +71,12 @@ Here you can find config file structure for lib configuration. "token": "", "chat": "" }, + "rocket" : { + "url": "", + "auth_token": "", + "user_id": "", + "channel": "" + }, "skype": { "appId": "", "appSecret": "", @@ -85,6 +94,12 @@ Here you can find config file structure for lib configuration. "from": "", "recipient": "" }, + "testOps": { + "url": "", + "auth_token": "", + "xsrf_token": "", + "project_id": "" + }, "proxy": { "host": "", "port": 0, @@ -97,6 +112,7 @@ You only need: - to fill needed options in `base` block (please, be careful, `language` field is required!); - to configure desired destinations for notifications (`telegram`, `slack`, `mattermost`, `skype`, `mail`), keep in mind it's possible to set multiple destinations at once, if no destination is set, then no notification will be sent and no error will occur; - to specify optional proxy configuration in `proxy` block. + - if you need Allure TestOps integration, you must fill field `enableTestOpsIntegration` in `base` block and fill `testOps` block If you want the project logo to appear in the upper left corner of the chart, add file path to logo parameter in configuration diff --git a/build.gradle b/build.gradle index 37093d74..bb265a70 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,8 @@ dependencies { implementation('com.jayway.jsonpath:json-path:2.7.0') implementation('commons-io:commons-io:2.11.0') implementation('org.apache.commons:commons-lang3:3.12.0') + implementation('io.rest-assured:rest-assured:4.4.0') + implementation('org.jboss.resteasy:resteasy-jackson2-provider:3.0.6.Final') testImplementation('org.junit.jupiter:junit-jupiter:5.9.0') testImplementation('org.springframework:spring-test:5.3.22') { diff --git a/readme_images/allure_testops_en.png b/readme_images/allure_testops_en.png new file mode 100644 index 0000000000000000000000000000000000000000..ab3538fc88f67e3ffbf991bec31f64b3bd5f76da GIT binary patch literal 46817 zcmb5WbzEC*yET}WwzS2H7car3P~4$~V8Pv`#e+LRYq)#y;_d{u;_j}+Az1O^-{yJV zGjrxQ-^`r(gG~YRj9Wnh8j%58kXcbieqemP=5XXJfrsnaZ-#q|>02^R8xyA-der zOzueyrf^RU9crzB^xh+^N0J5NC%>iib_J1e%6y4~U4twCq;>g|2l&8=!d z+2o>a%OZjMsqo?TxyO9Y;QVvo?^V6G$!7vSmo>iO1Wx1i|K#XtNzh$Xf18xcSUMT+ zW1)R^)~F%H%I$dldtI;PwE@S;nKha_c;tEr8Fgl|e0yGVsJVW!)CgZYo;bWebN7rK zMgv8xpN~14JS8hbp4;A?VC8sg3ZJCddR4x69`oGNSr$}uZ&%_z+1+-%`-73O24*O& zbv;@GV=sDN@4J@N@6MeP2K&H3pr)_51qy2Vg}NS&lm$`J8*@p(Go1mB5<3|j!uXpB z5Ge7hn330IJepFti5qaoG!tGPSn6)ybv_l$FWe28ABq%QQ1a=~X|`aF49M8MTbK?O6Hz9&1Yz5f-Ko9WD;=aN+e)tqZyp|gpo|}PQGa!I|?_w7Vnk9`YYx6||E94X%!OsiMC^N=|tfe}? zZ3-;fd!LXLo6INHkX|1op+)QXt!dPh7F>@P>M$*V+MW5O^5S6GRQNEQ@w!0ZRj<)i zM1oY81d7?2ufX}?&sMF9^5W4ITyOYpYLglb1X53%KTFFSaNCE;C!~kSt^bZskFjN5 zD{NS-D;~WiTq_EJ5PFlX`j{3M&APZWUG}0*E2Mhdt{+z4{xzFAPEc4r^(s4{3l~v6 z-bq?3q=4Ibd79=JPcnjoB)!M)@3uPH3IYQHOohG1370Yu5Hn$+gX=d&PurR`+WNvN z!VEkol+@)pr8#Hszvia(k)%~xdT1vP3&JlNQjI9aMs#lXvU@~)*L2RZwUT{cfqz-G zF<2jiUgLMh+!xcz-!ri$@3>%NM;4k%1qMTm%jfx^zh+}=r67&3UNYK81~7}J(XBD? z`RgzxIFjzbpf%%up_?+9YxWsYeNEL|nctM}U+hdwa=~&l1<81p8$Mii>V9aa%euo= zzW@DNjgL_#226VG3ap501*u!QtYOAevN#CDbiQj?9(XPv-ZdC@+qQ(rEP4WO$LxLv z?YDA_FhGh>rVs}wS-Uu1HL2<_PT>yfHWfA5T_Vwydgq+uOS=Q1Qww5PKl%gBCy~xh z!}T~Bf7J*eGOJtRE$2x`M2suNIssEcr`uvV{f8~*S4UqI)(Q{iZ$}m(5KdryyI0qm ztLm%sliig?oHO1_67c)TTe2Tt6JMU(Hj7Icp|rj&rWeh0Yt^1Gk|DSYJA`HB@Jm9rVs29c+4-Vr23d4_i7yLeAq#$?oZJIl zLp?{9)CSRk0jm`yqNAVZJVO*b30N660s?A_CpZ7jl+Aq&c{U^wnyVNd6do=fn9*L8 z{hETJ38E`4eHCQM#V10X&82(QgkEAGjUgGm5(u2#RH;s7qN(X>mAl^M6{67GVU=ow zalIdsW}pP~9*)B(444u*t?W*-sxAGV_gyEtd|j5@Mp&8S)+P?Kt?ZCyxEw1mAg}R= zPYF+aRQi^+F`JvehKFK-_}rr9G00dM?}eJ)lxk_O6&M`V?bir_H2I?@=Ql9uP^qwM z@kVto?b2kNuqFKEl#^Zjn0e?Hn0Jvn*c&!{Q^CGn4 z&_f1{aU_&X>gfB}w&;&Hq}Ld4S)4OxyXC5=AbQF)EWs8(hDacP?U zI}Y2o!-3mv9VXg{X3xoi$^JBhK$SoXuik+z!m*miA}w|ne&#~=AZ%=*Ki|kUo=$x9 zs5B?L$ifsG6k9L88*#GG;ybz~C9yF2MjwboM2D4q_6;t|GoyPu5;JI8{mWMU)y?l&z_)HyZ0kwS1 zw2n+JDlNnD6k2M{1X&GFut?+AUJ_7*683iqKgU`W%9<4#uK5Jh@zsFvaD5!oYK+@2 zGFQF6qqxdrVGcLMYSx_N+4Pojar7_LF2}^{3JToY|Nfx?Gv-%m*w0p^p>3R0Hct=d zGz(sSxVJ<;PrUGPzVSLcTTT%!F*ev~D>cyI$n#4J3>b?`$Y?Bz;pPtb^zBM?!RN-8 z^ww8t?kbDEB5i!o{Ryb2!E%P}jeRTO#@ zev%#%V0CgN2n-nf5MsLEk*`%va31%=VC973?aJ&UP#(Q@Xw7GtyE|MNJ^!10j^ZUq3&qdFw0(0vX=ncQ#7IpZ`>C3Mf7QT&t<}=M*)W)<;rL z=I~PrHV8x@gQY)Si1d2msybP^q8P~R~oSO6OgL>+o9drhS zftNjT_}(V&Kk!H38Nxae*rsF z*R7%UeNifG>_F6_=RqJ)zrGTJ*u*Dyir%T#5krA1At_HUV!{P%=8B4vo?gP&B@PB3 zeP&gHS<=7e9SAeV8z9@52NzqC5+Q_SGnzeHW7eY1XLlG_v8uT z?`XM+$10PX;7U1>!1)W(^Ky2X+86}54{S*{y`|HVUEPz%isZ$i1%qY8z{>7d*Mg9B zB!7FVITd-COW9{AWP&8Ld@Z()g8WzH@%u-&m$Wg9a?FnN6GHUxfPfB(q!Lx&f&=U2 z>Givps31c=rrJ__yW7tljOIS&N(C4%3BbW4Ti95CZ!l~Ga%D>tT>}5k z_`oUg@SOZBJ`^_?uhzvJ^vbreRSO)VKTEE-=P@$IeoQHrZzQIFe7Y-f;()3EL*Z2lm1BBN`TR)7Bs3G++|x#PNON& z$ysfl|LSeZ!jK%mVh>A(hP1^s^<%U_k0Z~%Mi zhou}A2vi%3zv~_fNo|g3rE7T|Sroco$x0x+rQXF0V#f7#R|<& ztVdmR36ml};FXY^u%eXN&L7N&$n;D)cUf=EU{Y~eF`?B2I%Ji@ehn!>ER-z#BTCkj zb8{d@EX=cCxnQeo3|I`;0yE%&5MhginTecrK4<4C<&>)!S{*?q=GiBoHXtFPZF~$I z4FP}D9)oOznBcE5x9oP^N8LV*6?+V*?SI-OFT4#t{zH=B-p1?y6YV>2S%q0}uvx$R z;S|!F2_nM8N1S_(+7i-#b(ALGO4YEx%**!}8A<4#U6kIXKKW{cI%H3sPfV<#@Xh9J zFMNbNuFzYTC}~fKmWZ)TWcx3y`|h+_X^B?w1)CBdvSWQR{ofb7WJ^PXa=@4ZbgWqHjO_ ztRs8m-!(8X$_bN+R3aobWy>j!6R$77a90VN?vv)E7vf_6>4zGVY<9TvNWGTrZ?*`6 zR&dN_hL{LDFaWGl|1^4Xvxwk2HZsG+RPT*4<|$^ViC%C*KnDuUiS-B0=B$Wbg@%=} zQUXw@8TL&W!cO~lKe3e z=Q5o=&f@fAxV8|fI}vQp2P)gc7Z+8Ne#UseDXb>eoA2O~o03U7JF!j`dus75O2i6j zqo`a&bt0J7bU_wJy+Imo@nvLkJk4O~*fWGi6L(hQA1I;;U3~=7j0^&Enr2h@McanQ zr=ykqw1PD#kh!j5J`?@*nO&0$JUG7q4XxS88e?M2Bb-6L+kjie$eo)}nNwrIVVkbs zVM)fdlO?HCE-dVyXKz7vGW2&Nyi7=5HAdc~Igi?<0(2QQv_GyaBUB>*3?8Wlu87oC zp+%+NE0cM0ZebV9y)!)Pe-u1BX@usC3xLIg@wnbs05lQq19rA&sQe3GA_Rl3ExClbMey}yI@BE}QUC0%$UsGND59aSRQAaU!9 z8+n(MeB=z$eB3z)bDC5u$NJgRTh*iv0h|}|b#R2B zdmDIL_z1o-Fk-l>G31@4Q)5$BLJD3Y;@srJ*phPuFRIZmgP6IjiCjk4`gs$BU@IcO zIT?1QFk5=_F`ro=$tUz{T>j=B(`!(1&lQn*C#T!JCky#U`b4G)H-b{9wpoCa<;O7{H`%`^Q@wjKQX&L#Ek^E~+p z1K|=MsSN2F`qY!zUFGj?AKJ=DKhvlcrAM+FeGw1y9k+3HCQ~9LniB2|2>AQVNQ3nH z%>k*YmSz~0Pc92nQpF!uCON&Ak!v$sKRb}EYWpxS81_J-EcDgglv7n+fS|=QfgLE? zN_~OE^B!1OWg2Z%mmz)F%oWSIRZdS)>98o&z>vcgqjvWDH5hkN_m$+R8E zJ+|#u6$s)craMuZrBV%?K!)jfmgWQL7Ot^TR9<5D3=+MRP_6D60nV;Z>FlCeJj9t! zF@LGU@*Y`1sKG(9JJ&oyTtz9lC7>eo!WG$i+M;^ziuyJ?f_+izobv<+Vr%J@xk!j* zuSz?KhQ+yjhbCF0m=U{~t=^y@RZJ>7w064i{jTNyE+vRwQOf-%Pvip|@iY8JxFq;{U zYyDIZbN&ufb_nlz#U=I&!dQD@=jCru{Tz*}?%hW9>lS`I<8+o^lN@-@>k!EWF~S#$ z&w~RqH95ryYRWF~LCjU3$o$61L!J|)2K=RPZRqekwqS9b{W{(EX7Lri#^To$d8@sm z*D&Yvnm}!KjuSg9ld`B>R_ZFhtIJC={7>c-xHJ{Zm1Xbae`Av-e^kT*714{2!tk)n z9XYBdG7{g=3@sJd@^Ct1LCKL`{w7j<-A2&duq`%|*T}C51uEmkaeget3EMlvjxqjo ztx!(3`K*a*2lpj0$=eG44l&ZeR|e5*Z;syk8<=h(L%Txc{>*lA9FF`*UJ!Ok=r?tq zM{D{%tK#?@0@*TRPhiBcrPuIfC}lq`cnf020?NwxarNGWUpsx1`xnJ`KhbYatCLl` z7N1|~Chv(<|HcYVcS##7PLO)Wf2oVg(1=?UA5AvDoZ|)C;<9U>cTkoo3>qsN5b!@; zNeGQ{K0CE!tw$D2aQ*~%Fn5pdesbOQAV~_i#LDt26tFqkk{456zgVEH+mG3@R$#0f zk$Lm7`xq^H%P4S#16js^z&s4?3$#_0TO7O;{z6$-U6N`Of_S8v6U4)bV<}hgO$HbI zVXDYV7M;eVEy{YUF|2DK3%H?U`6ewfW(V?CoaY|-)+3%0c?JHJSmuZ6^+O-We9edz zSEHppl zR2=_PO{%7V_mW%IdHb+#?60YrplG+}r3<|W*5s^bIi#~7Kcd7OqzQRYq1O4k4=#EL zo^v6Va!a3jAgGYwacow2ubVgljGRyEQ++8uP@9(7y(J*VIKeQ9e?x<7B_q+XM@yQdx z1~^`Q+)~UmuCzF!`k#X3+G7`U1c5yTM}bWhG*cacs+6`x49NEJ@i=24wMYMv zwWSq4epO}-iu3Z2n1%Z}^?nu2vEo+_!nDy%Q(`d=9B*?vpn%^WTqLCvs>S7uMZ0kY zXv#d1lT|p2W5UtX;E3*8l85p?Cyp_nb&AGW@9u|Qnmg48-gf!DCm9oC)Ca3?jPT@Z zjDSn`a5K!nbe$qx3%)rC-hSbnH3@G$XD#};#U39i=&XMc#KSMHYAP?C?0%KpD0m~G z-*5E@G|&)BNUWzPIb+t8_(^SQevQOk*H4_ZJ!(+f5*P9o#iD0qQTZ^%WhZZG;1&Iv!mlFn6+Og1ohmGq!B!G=(&VPvKRi(5Me2dn0^qH{Yiz|Bli z#II8|y)dRizx<+Ul(YYNjkFb?LAJ9wZv+^H2ppi81Y%W9V7$d%>T@_d~Nf zV!v98{B#{mbQ}-bzkZrq-?#d1r2YGhoFA#-^O3E=R-ex9*o0J0TxGS;t(8$+Q-B;~ zc!V!*o}3v|(vdYNgaK{u(*SG+7tCCX@@zV8BzrO>xry(>tZDC1irRKE#713i5rdh& z0^QDEa1zjPQ-w;t!z8j6vLu4l_4BWrubxAAwqLn1V)r{3wVNh|83WPMI8WjVPU5Q0>J_`3QmLE=Oxb@Z)pBz@$M!Dw{~MBpa!s{B~a5R%1hkyjX~`B^ph*Q;`D zG2fbRR4Nta;ZrI`=`)bItUYzycm^221Isd=hdz})&3-|tw*}yHj!QMerCQw6SV5={ z$u;6x*0yU(&5UNvZJ$z`BVg7q2h5`@*)}f_o(i~%^YV0 z|H7lirxKo4SN_UY;EyEa7SqiXc)VvL9_DW{uH{E)UwCRNn+CdFo#oGQOaI38!3KTq zL>!A!5&5OFf0P!<$kKafbw*ww!}+64`OyotZvn<8i*jlJK)+VHoil)1 z`;LpVOIq9hoSNv1F|Nwo<8hK{OcT|3LHnAJ;-gR>6ae3{@cs0J#ELOl!gT;+W0!tx zgqIMe+EX52WRHub8CpIxPGP6q=iWn}K9ZY&0%G=gfXuW#YaAVRFj#9=8Rs2ZNeRUX zfKy`_PG_8dDwrJu!a$0;L7beM#OPEpU|EPsCZlndhKWg%X+7{KGWip*68DVa%)qP`m8#zbkO^4QSV;~kFj9M zJvIvBRghwcy1uy+z-$S2V*!86vUo68XH&l=#z?b5%X0Sk`&$7^YxRw^3YW}}h$~%9 zC3R;8@eR$R&uUESvmI)OE*5A{`|sqvFaVhyCGLr&RDoDc8Y~W3ZHPZqw!Fc7=JdV8 z5rh0T5A)BrRTQqog+EK`Y7 z!Bq7o71a>Ke~hD3*rpdrLw-7*j- zeQDjegjq8ogPia0q^ZaFAEULZO_oPZXnj7O@o0hxsyoEOj-vwrb1KJRKV(lT>%Gve zm0jqCVoJ%hBB|1qFiCwPj>kFNi7>;);f$1xTPb$R#bC3`Oy{k_3bq$Yxn3Su$8XG* zfo4v6Ba=rU8!sl?W51e>b)!dg3LStT{n^BP9nKJq+c?U=V-G#lv(!W;NAkN z{e0dR*e~P)j(|l=-3ikO_}k;T?yVXDpF3c@(k4N6xSPRsyK_B=Wcu z>HdRA-lnm5<3?rsbxJ0I`E7Oy!dRa78@*?_Gl{V!-=EAAvmqf!@70 z|GyuTNt{Q+2Z9Z2-TfxiK2#LN$wK}yK&4tY=J* z3cSMRIwodKdIPRV5fF{)Oey^jVGO>o<5uu#&0i|`BdE&`YJH1U+=Z9gtXMCOXC|hT zL-mjKR%A(9AoDd3?KAUB;I$T?3O~&jk=?PU|CwZY1PYRmtR@D~I{R##_UThJZ1JqU zNQ)1>QSTJ!F(Q;)okp@igx1uBHZvw7Dm3oet|O-7o7O^|3KJc zMu^1^#wgU?tmem{vWlX{k!_^%RK3})4+0tF=P4H6U-mC_S$~y!aJV(6^|drdm7|?& z8FcWK^(d6iU5B#iGk_9^a414p4NCosQoSsa>h(^tazO`hP5-&iJX^RLIRgE{d*5g9c31BqPq}bfHug<*tX6^GU7Iw1_hDRIPg%$*{_K(W|m z)geZ%7i;-w!k8o-xij_2IteiCv0o^(0~!UM9*N$rm{4YHbV3X?+yCVuDt=KoN}7o> zqR!{y58aD`%CS>w9Fnt4Wh&t{MPhsEljmw+c8hX#`V&`{6%|0H#f8svTQ5~lI0FPj zQ$qdIgLHd?_RyICq5O}IU3E&Siis|Kp8)0c1`qwaU;SpIuwO>L$2;K3gm}m!kRc^r zXNGEiMe!}(QMN&&V)uimpT_89UzjoVkIA+xrMD~QQ)uFoNCpDgZ~;tWOZ2uVO&b}+ zj8&3Kc^0|Bpx|kZBzKy(sBL$7E+hE(JvPJG2t9cjBS!N-u^f8v%Is|A%ADGuxF-U& z4cK-CS#o-kdz=eO;kC~};yF@cM)F-Dg?op_Otx-UlbxwR^5hM*qGrX53}OSd6;a3H z`f-bHZfC6eebPul=1?^>7y16v`ud^m>B2tJ30#EQ#ptv_1O5KqzY0x?S|qkm#z$!> zE=3=lF>#V7ndA&G>g?=C!bK#zKH6^}s!7I@gV2 z6G>m?%i+ZM#tOi!r?hqa>%W$B@Zl7CD0fWH$O8uCAZAzqSXf=b1>ZAth1Ug)Al98M zp#@|N%3e?#)l-_}OZKpwjI5A041gar-?Y31tY z^p5Rz0b#={$)Fb~o#a70F<}DqROb1TY1KD8a$e$MWA~QZHT(ZD(M1M3@U}7j5{Rys znv^X%xBZo4Dv@KH7~~$U&%m+v@d?PTA9y2r@*a`br~kzzey#$WU#RaPxe_qctvRD4 zvJv~6`kE$k#@O$1VK)ywR_W1mtFMM#qfDy-QNq@WU5n;M#!>u_bC-B?Am;iJK;M*% zSY{{I$ys{w@<{==1$VLaA%vM>u-~16?Grx81`aS7v{h@q{E!B0&Bs&u=$?Ez1H&pO zg#g#ZLzg0_$?o-*BNt9299o&WxEGfvAHoxv^Fr2=xV{72j|F5vdW_qlkdrFe1l1}0 zv?=OqqK?aaWwoCg4Atxlm8!I7J1f(dgn*uLR?RAXHGnV7_MwFuzkZ+#^FxqY7$Le2 z0Lxi+pN1dkg24+n2p@&Yv`4Kz$%g{+#RPBt%o=fwutrBf)6My=8{5XcF3RLqLnBS^ zdv*ZLlK4a?tu9D|2?}ca?|qE_9Y6T*T;YH7cx@ps*n9mRG?sJlOA$CV%=0XGbAIq! zA2-@kv$P>P1(7M;Cm=;n%Y3G?cYYMFZ}_r-3L8Z0x^J zw**}6=y-ntB-Tng!15s@Fxi!zq)CpBt#9=`c3s(garhe3;bslrX+W(G>RH7S0HwG1 zgOL94OB905a4S|HPY76UIytu+@B#S>S(y`>#~{N;_!i~^y+7<@Z&R!`FcexUB=E~8 zQ=Yt(;r^hyR>+uaTQccpSpO%P*9cO<9Yq;Y7>h2GqJmLuRV)egHToVCmZYYuqe@a= z3p|sxIdcecR=MbHW&mG~sR_)R0Co$li@u65&tuSUf(jhmf(Okb6bY<@i8MGu6c9Zt zV*fFEh>YILwU6fp2NRJX2q>|a0xBE)V|>GKOSf4hHEOsS@&qD!z~(5PAxUn(@*61T zu6h$R?5v`njQ>$${_qskX|dr8bc?W^y$k`pqiuP@z(fMXhR|jB{AhqCC2fG!N6{p; z%iN;HAC4=_Ig4tf*g&%MBd=nvoH8NLxrxF#(17t$t1IANd#s1owcXpEvmGTLSn-+N( zAEN6R=9hp(0X8=FmR)7Fc7tJv&!prNF5!*WY$W80b7rdnI3)emNbY`b3ITnxPBB_X zT*PEW*+c#V2&E|c5!lnB)gfa%ev-{NjFnY6HYgT)zm*yVgyft1QBMNtXP{cH2Sz|g zNWkBzxDqPAq4Iz>L;EDf1HX$YXpI97eg}iJ$1~mLt89{iJfP>)}GZy=!sxJ3~oX6B$5#YW8+UBf7LXRLO zpps`d=dyq0Ia7eE>CPrKdlhy~qNZR<5R%wB@Vv0_YiMAkQQ&%UB9Hq0pa~|p0l!h@ z8VvqZq?KHpO91+40GbXeG>>LP_LPS{y6$1~A`+$diHeo6+!KKesgRVu=x zqyn`r`2bU};=FLioa20hA?E>mwDfzH#?-F4a6inAj7-#%3q2;$q6%-Cmf8QHM^Vr= zP6wBouB{Rf>f4nUi>{S}F5jJ#7gQ2GF2+;hSF!yP$OZyk0_K+Ht73ytLd|i1@uylO z8X21!S_G=ckev07(x*Zgtzi0Mrj0jntDcchC*=GEn90}`!s&AP1?)MhFUDQ~`er2C z^FiK7Px}{GO>TUjl!IYJJ9MTd>OLeTg8n-cb<{?={3-)t2`p^2oXq$Uid+FwQxh!w zw!y@taI>)(bg?rW{E-|(+8%F%47CCe^wb&?v#~PG#oDBl`naOK!_%EoP10U|Q9!fw z{718(7)Sg#PUDs-N;(d=2Tth3jHdcQrBwW1^ohYg(I@TyM~_?lgO;Hll8{{yN9Ov=ORR5a8X+~n{xv-*!t%^F-XZ)`1sn!K=@XEc=UOyd3f zO5UUDtMU&bErN&t;Z@Lkyrj#m z{Et{4HY|l8GA@JglT+U(<93ppT<;0i4)S-kj=5yu!{_Hhf1i8Z?0u=d5|y?zH3tPwK1`F$5oK6c938^t%-NwlDuAaxF#-)-joK#v?rgV<2Y-Lx;}5p*Vor4s;nV0VnLWAr4u-z9JQ`qavI#8_ zkrwqFUpClHpf+x3K6AJWpu(mb-YP#`8?$`iczhWHSQ&a*4b%U$oaosr_6}z~XLO7* zdb<4-NT(B_2kyT$BRe}y3LG>T=oA{~zUR<9)X@_AKL(}>seR!0TsV;oheZFVShzp zfOI#`^^w`zpKoM{V#yp)H(mQ=3oETrXe83otJ}o{gBdS{73NIBYCMS}5OPODR~;K^ z{C|$t2i|JN>_^?UrH&kn9rn&>OHe3x4(d#SS@UYVsrXK2@en0f@C|@7+-5gq0xuk*fWoT9yS3$!?mb zvoZY_cKoQ4O(K;kqM^Jc7O+V^v+Hr)DJtzj>cMFs4*PxR7_)rT0W<{5%r^S#S6_Sq z<;;??*d6se0VW@Y?7X6_Nt5W9j+p*aPs^v0kgCg0P^93|ayRsTra3V{%I%N%7vpCO; zHL4zMZ#%R?zGrXI&rC-ERRwq{0A_D{QD+z7p)c*#euS`6m#`cK9h}q0N>g1}4_t>- ze!{Nmfp(yo)z8*N-%BJnIiKgQJzYe`Ca*~)3heDncXxKY90T|`x(}xJNidg28Y0DG z%H?42xit>sIZhBY5WqL`u)DNgjB&50AkR~Uvb zjMV#G_dTmBtX&KI5XWW|Z#PZ#C8$XYxa4l-h}EDD`|S^dsRDr-J91w z@mzMmSKt{h?7M9onmru%n^6UJE1SL2))MJ>n3lC=Q+OD|R#aUOT}aK)pSa(L>-6U$ z>@=#i0*7+<&>QP}=Z$?i)mi7X(BP)|DDJUh-BduVx zq^F=LsRwXxBQKhiTwJ2-0_m}hz*HG=pd#R{tdSqooa=DA03$LGC03QgzzF@|*v@}Y zyKqv_8qD7^|6@M{_#SL9mV>sY*I_~EoJTqMx2zz|odmW)@c~X0>pPz!(lhG_cYh%- z=<6^k=prO=pE+H7?Ey8#D^}|=^fe_1K5)U5HB-rtd zaz;y9bXr8>7pH{OveG*Ul#@pRL0!OjEuyA>(WQS@nUmV}CoYe3S^OASscMa{-6C$s zo41_R`FyAr|Lq0QKqjfRJyqrm(VGp>D z`R|+u-GNlh%W9ZM8P-(Coo!i5w5xg1?8mJDS$q}^aeT=voku_ByzZXWq!^|BXqxMG z|K$I8+u?_{*IaC6<*|hZ7urA|jqe$sGx{F`WC1iNyoKlOJX7jHQm=4F3`Q%P=Tz;KszWculwUHV ziPe{(j3Ez8S!#tRI5pc{hepO6CbcLfT(P8H>^~badNkN$y{E+?2|Ols_X%i%vC9~; zM;nJ|a3dlVJ4P}2Sa5<12Rojd5j00`&->Wb0ndX+%u>NRJ0I8tG|G?7kUC!r-ReJz z67jCF3oY00U$Vqihq}%Q+hACEv$ew5k1Fsyv`6%0_SVd-q3Y+Ct>2avh!`7d4u47h z-TfkOl*lu!6kG!Amc7FT_>X^L(_27VOL1x?AKD)hD3|APE@5P|syQJ|MxAey5}oza zoc{iw=?qETGaC)uas@B$15O6|O;!d-Hf49!J>|ntM{QP8bZqYe#MtWXhVWWj|GkXZ zhir#FgP&#g#pMZgAuUpOka0I75jyu~wVeXl#|HKwg04!ZPDzmCm>r8{g8!vT!tvi&2MVxx4~$-XUH}+Gz;{HC=vqAm4flp82XTPol1hVoaP9I9mh?rxd+lY-UVGhpoTkq*@*N37H_sS79 zW@dRJPBR~w5zd^^WXwtR-Iu@@yB5s$LMwFjHr4gc8{(PIY=Jy^R6U{jnEZgN>dsEp z^Em|5a35dgdpfRj5rCd4K+AEI8Y>+|P19(f(TE`Euv+u?7^huNvgUN6ujiM|%6E;m zW4H4Gf)x+nQlav7(swTnH6w0FGa27|Dcj?O;#hxl{o0;ZZ{;SX{^FWUfz$Z)=V*!E z3&eeX6NiSmbHc&>5pea#$)c=onk*8S5cqER7E3shrBQuK?i4Cm@_ik|lH00@hsr0z zG{wdEG3*(<(@HXVuf?c<2^IusXa;>OH+A=YPNzHd?kV1k# z<8Vi;WBtQTt$(^@s?@Vb`&Sm|#Imap`^@pGfyYdb3bB1%P4j$cSAN{habz3Eep)=w zYF2A%v@ts1D2N;e8@oMuxK!W`TOiK0Z^DO}i1LpXq9Z}wRj&We5w1AM%-1y%%8+md zLH;0T{d%rz_J1(!X8)g?F?D{!I z^4_&3uDhkG0ks>|{7K*fX&CpTRVh3*9;d3$&D`JbKOU(dDaGvIz15V9-+`NqNN&nq z70NgJ|5I5QXxNEfD!9O?B|Pi2<)sT*M3So&G<>oF_uFH5sd+F7Cu#MbN;HXhLLhFL z!Z#pv1E~6P=%A(f*pqZ8;6g+B29f1fPT|L7yJ2+o6NfOUm?F_)G!RQNac^tZzBC=F zR`*z@?RA+@W^De>m>CvNxiq_MYF%{5W%|aX#rWn=ynl8%?1S{pj)NZ%$!oBW(-$62 zvn0$)9C$J8lHOi4gvmLSYh3k_Hfwhr2g~UPDq>`k&Cp>hUU6SY*LD<15y}4vV<$B~ zHz_QEk7L$@cguQUEd+@EG*sqlRxUR=T~A8@v3cmc`%teGwKgCQq=SV-$5u zk&WIeefOQ%_LB2m?SACv9c(DET4cf)We3bzXyo#Ut~iO~&}(h-D%{*oC!>Mw_^49r z?)^UQFiz8tZL&u4?Zya?=sPDrfJUDvCD<4~5G5DqG^s8A%H^Q*KpsnSPY46ux#?C% z{giSw&9oT4O{2S2Mui&D@B?B-S6g_6=3;u@c%65>0aO_4^`Uz`qlZux%COfIVq@p? zoHY?3y7o}BJY=@}KKfMb0=zO1Enm6&x8hjiuv_kaCf&0dlNp%ulF^I3R5KA+{=&cA z!FJ_7VJ&h)i@_E9pF5kWxw4b~zLkdI?I{{=GRBI@FVsHA+LS6rE*O8zR2vsseX^-H zB)n+y$Tlz}h%5bNSw#xJ9ZOw}1X1XV;E# z&ouSG=Fw`Q&HZM6pXJ$xL87{s&>{X<@Zf?lFMOJ{YkI)bYyN90(9?Xc@`poJWASd# z=kAv(FoBJxnyw0rmE2N@(2F_gaxQD>RW$E40A>kUook8)kL^+|Fkt1=6o&5rG7Q8| zR`e=3p8Et0<`n39oB+Qo7~X|%BfSd7_PU;TEn>YsSWP1Yy^;QvUfz7u@0-`nF1Qnf zptiT~RlorKWZ>mwak^hU=3@YXiexK^{2U7%fMoVOA23Ra#Db&omH~5Nt`msU zp@RuvjNA1GZcCRWszUg1j*Rs;BcC&;kKMo!z#LNeZFYIoa zNrF497uSAw$)`xc%JqnaDHE*QmWp!~QVb5(*bZk`Zj@-et~MsN;sNF!m2O$^pr>ueU0Y!jve%NUH}w-nNN#%nKi8=Oe{(}AkeP}BU05WsT7-*G>ntyQ_d=YA56Rgxxnan-wQeSI`sw84?qymM{1s6g(M zw?59(ts!#!`|9t2Y;S=Y1TXw1=N_0M^;HAf8H1izIjoK1mVrZ_?h6+mx?XQlI_I1Px&!f|6&4-myfWHDC?Qn@81N&;jXJGGZf7=5gpFM<1hmKJe^ z+MU;@q?LiUGSl*nL}1ZJ6d2t0QcpiDqXx6jb9=i0&+XkgZCv&U&r8TmXnIIoc)1m| zO_RRe{4+MLxnNb9G=7*pr(B+DzKJy1_wCm(3xH0QG#b-2>GvH z51x4BOw3p7*qFG$y~8|&7R~ubSb?31VD8?bY)o$gvxUnFbgDBc994( zpt?+yh2X_I;|D#P*fjE7t#p%Skc4zt7jt)a`%MxUbJ}sk1by9AJw2JBcx0w;JOBRO zrdfqT$24>cdoM@ni&w@DP3&%=Fy2ITg#pDEHQtMflbjs5xs$tn)1`Xi(YO>08k7dK zS2sb*Cmz*Sn&MS=>1jfD#_e7-5SoR=5E`>%yOYSo9oI^ySAJfs@Gvi6i*JMu2WHR* z$s>JM-Xccpnl67&l_^}~gWg1ENaZ0ba@^%iae?#YUZ(*A+~&9JqTDQT0+3^g6g<~w z#ZC$D?Z~zGgv2Cy2;KZ=){Fqc5$3)z71x6cjTl*_QF_tcnTtDGo>igW>xiSIDx?^i z2@lvp+Yqq%HcT%k|G4I9xxcBNU=sjB2Exu6_ z4^1#PU1cA=lJ$MKo#TWtX#ke2bL_{%JSzXvW4HFPWq(f;?y1S9CyZPOFAMP0U;qQ~#6mE=1r|1brfm5egQ0!48!9R-H zLNCF28B@0MJ1i4?VhcN1vI+5VxNXA~QJ^mwOu|aei=yg{%s-l{j$V)-l;D-40MDO~ zDn=PmHp@CR;|^RN#dnbj$=_5BEzy4Sf{y6#{~bh;PDWa~I9{I-nBDp6_t^bQ(CodU zUw{&)&1}^vaRB0d?CAHCN$$D|Z{G6fHm}t?Utcc`M98&%a|$?p7je5MK*?5p%cK(# zL;U=Th^;hO=b28w!a&@G$C<#zr6`ub{hH6?p$L%<8ZkBJ?kTn& zYQ8$_MpIh1EIG-{+k&a+WK3*EfDqven-~C24Qls7Td69|l=F4?<9hD+JUc>GpPQix zG{cO0L9e0Wkga8kL)Gii_&uKp=)OT znrW6(mHh;8dP2mx53@`X8R@Ajx6T=or{0I7><^+hv;>6=e7dp|+tbCZMSi>9y8D+}ZATAo*B%d(Vay4QR*)ZTEY|kd+{-^CFdUN!^4=Tk+*(};xDY^sQLc$UH(bV_CVb?j zn>l^;aI&*l42}-TUUWUVsAzkLsi-m#_&3;es5L;0*#1m0H*0joRl;JSb)rVi3T&BS z_7fSc5R3!2?FF8Li}KwWzeVTUvX>FXDLGuO9_O}!O&3ctl3#%dP~acNJu(|qTpyL>NS8$CFVk=@8g` zH57AumCA_1SbMKa=ytSlwWjLucB^H1P4&&ENnM@0{@I5U`9|06jq6~+eEH#4chM_; zo>iBd6OZfd`5Jcop(ag;=T8cDCVcmdoC$Es09KG6w2~NcV^&3+BT`R5|{Tusj{O6zlT#RiSRkR+I9w{8;JQRSj5`4Q>=YGGo z_}skwZi)F}x%|SG>^39fZc=bVJ)l>n)uEU0@Z}BdjrMqNnH<{sENRBG)^y_rV5w7J z&te_D4oW?5{y9U6#TDYhiG!_0azyfiQpsDPsCL%+uNSW#)%baG*f$AA6AqE9CnwDY=?W+j0_!j z&{w$BwmMlxtsmZ28utneDjfK)3f|vZ7rP&y_NEKUNq@cV(7Er_IsEj{|H329L6bUr zfLCU{Tww5R7p=3twD#Tn!0q4@NTS~gT9`d*Hww@G@ojwH%ABzgvz2SLUHj@=iO2T_ z?)-JE1-JY9%~F;S%wMw}gOn;kZu9C9n=oue4?0niny!P)9MWOx(Q~6?$GNv2g1gez4~LP$AMX`{<*hPdop1crx&f)2Of@BO|w(bQuRAB?bbzL%6d4U zp`<*0X*hSb7(uAU4H5eM%!&h?Fu+xM?F^2#y319sjZcV^(`iU_a#OM3VOWS$p>c#K_Ys;0YR+R~JE9pbR)X5>&JNX(9(NV!vN9VobgzxSeSFf`t zFWyly^MBb_iVZBRcCzdaD{d2hM+JeGcz+7>iE(%>#=qe>HfI0glrH}uv!3dkrm5=s zq=5{hLxT1;N{H1aJFgg%>?WV=yzudT5 z2~Q6j;}Zr28ll`ybKU(*7u3V--I{K5H`?3p8%k)bm)!XHJEJV)+{0_R2=b%-+4Hc+ z1IOIYX3-Vn-uINd-w>{vSMISmF|oB&2=|j|q~46Z!cmV^n*D;cn8Aw(qkR|uXLmO+ zFmQ3&B=0C7sp%=$YBg!5E-f6&H!LpDnT0u_f*9086I|SJtJl^R3kAMwjTsZMVm;$V zLCmdZKidg8P{+mJ-ABM_G?E=U8>9J@TTfT3j0Ijqx7}PaAG!;LgroL6L?CHsHiIbt zOXaFcHN9i?1A=ZAD0S1Wu4^AIh5stYP45i0X>JXf{Xb_TRPGa=w|q@acT&Q;fGW;; z@U2=s*snEEWna=^Yc(e>D+``X9f?PUDeOY5&JL(0S6!R^cS)#Z}3jVVe7poQRsee8In&d#@hXfCiEYg8n``ODJ@d8FWz*yS4VfpyBRY-)d*OM3A!1*b%d^wlg7p*` zAZk1&92+@5RB|{MGbAsnfgzz@{D1dz<8{Y+0H)*0=YP}@^4L3~fPoeK>63=@qFc&A z^Xc!rGH?<8PRH$RvLM6V&YtJO&htHMH8D0J0)#w-_ptf2^@el3%>!Ig*SxiU7#4)v zi-X&|y^eV}^I$A|@)4u-k!x}C{hz(TU?6}0tuoGVzux}+Tkbpp@4X<9@Z?@MSZcOwj#wLpEWM<8WPGPOkNOr+D#Z;eym*n(<(^6AIC31>Y%b6G% zZ#v2DkItrb9v@SzQhI&8Ti$lL5+X^B6Va<$uH9ld7;nEjWhgkRs^iAN&wuxeYteO+ zcd@~4ePC_vVR#{}_nrFu4bnrpagv)(^!s!ENIy>msvFuT!o^c&wx&8NF1s}gGgHp9 z#iivs=99B7pZ47*YZDgC2x|yxl!m?)y$BnMktBPQ(P;^NLN>7Nsuo10Nd9m%=42r0f^|JxS4KaOBZ z*xmbE#hD^oFizp?@9*E(*y!DnIchT0*QdY(mr|m`d*&y?5cj>QiI+4YAt9kjv+VH} zjEpj+Dql#Z7Z=M_itTx{92^`xJlY1=2m1P8Ot;t=D7?J9;)+8 z+S*po1v|IgpJqI`y1To(xZJ$3ZXO*Ofj;vCKQ%So_Q4WRQ&V&FD_=i$yuZI_yEs3e zv8dhHFshqPOG){pr8Pe>@j+R+lVzyVaIp0_%-V!KIgQuh;7`TsP|_Qc-oN|j;vod3 z8Z`|3{PTww)^qh0EiLm_bq@CSH8nNcXV)LW!q93W&?gBJ17j2WUZc)RiyRthc;)eM z;n8-{a`^GX2X;Z*18Z4~mg;o7>{3k1VM#d0{98@gQxkmfqc`IuZlT6d= zzs4DH)cyP4ia#)@$KPCEYrCC&jOq7c$<1Ap!(mxbQUAbzw7mSo&EkXmzpc^r z!GziU^T&<28X=g@>rghnT~b(I5CF4Ou{Yf)5K*f%_!KyBI^{<5W| z<#cmcUPi{;+}!1CQZ?cAr}N9zh?_h2P%z={Z5f+a_xJbH+AjNP4h#K#eROd6-qsd3 z_|m1N%;>JOv$M9lee0^KsxNkSsdBGm$J5f%%9~FHz(yc*-l8aG~B#UG!277yEwdtOMf<8;ME8FF`V z<3IBgxLoq7t*r$Cgzs!ZIfDN@Q9AE>SLb2p^a_$$TYC<^_8T|J;`LOWw)1x0WU*=$ zym*dou(EUc@+6_{7JQE2NgvghjiD&&?X~_G*y`!*;qGoRSl)Hki>+=q$;ru-QGqAN zZ@+9bAN@qvxf)Q7O&WTY?znQbnZf+}H6}7*SXfxoxBZzqYaK(wbu9NIpNzXb68$jEsyK9`{$^cd`o$3)|W}E|oi5!NBaEU6+)UyvlH`EGgLo&%Qa|ubSOo zyzJp}Kd;Jg12N2&n}p2m-)NR-fWNhEgXUu>cwEeehK5?V#`s|pV9LPs-Ys4%xRiJ6 zZSU>5I0!-zgk)qw77sH7J=|4P;#G=Y)6w0tl$51FM?>=j zf-G!eVgeO^Oax_xg)Aw*>K2@L0i|D;pPvsl<@&a+wDiEg?Qt!p>GpOZBcuG`%_hf+ z_uAT|vg5>I`I(vRChTCQQ4qbrEHbP8Am`_I11G||TpYMa0?SWX>?dfcAsr3PBq^!qrP;SoG}Z`zdXwnxL5o|vd|CW46kk%pLw&8v^cl8*ZZBS zVz|+&#}AWD2WNAqdcy~ke{C?%nb&B%)@#WsbHyYjpo1yNqN7OhaixJ>5tbzLI%U$8_Dh;suxYE^$VKYeo9`@@oU~zu+MLzJ z?=;?~rY5jW9VAPG^5&fJ;~K}^~-cTKQ1A`dZv2Yq3vcK>=EJ@FH%xc9+6ge z2%(OlPr?T^wR*?nmAjjZa-Fu;ii)UJ?seAJ!qAK$uyn#%F!ZbbK0`-WR#vXj*axXB z-Dx9f@pgl&?XsI0Dgq+E+g?R)>~T`Rf%8^YfLc7i^S?rg9QA6GsLFM0iVsRkAQVO1 zt`N5UY4~ESufMu8S$ya2!2*k=mU3c9N=hnvFPJW0s!~u;05X#y&x8p(_!t9YW5yN` z>6w|BI8%NB>@m!LF$W-1!1eWYX8;z6`u`e}7KP znJ(Kokk!F0>MZ`AB>8A~lPkz|{M(%mh)U+)lz?3B>fAfF@QQ>!3$5r~RV7TTLp|(p?T3KmtL4^xFFP_|V2|-x>!VAhXhs5~Q3uUwqCTd&34aD5ge;SDZa;^BmWA0r>r zQxiW07gm(PmjD0Xz^o9bi{>-YAX_|ZA50$^4XPl-_)suHv!JXO8pp6O;DL`PPT|{A zHjOMyjRfuvAsU3qz#tT26dLg~+Tq7ap}6|lLr>WvD&YE0k$OB4I&Eh1UW8C0Fp)-J zarr@&1d2(%WT_Mr`F~cA{|xhm_Nc`h{==HyoWwysRtK-892M>72}PvVr>FJJ){9Kw z4mHGJWA3KBRnWxvq7)mG-HeAz;}w%_%L@N273-+D!J-SPZ&?cp&-Y4C2d78C{PRuM z+Xk4`;wMem8RDdiC(UX!){!xMp@WqG(HpAMz?YkaXwdZd$VVmL{3wuFc8w7 zeugue_DWTdPh&yQ6gEfbb(#x!|B&E{!Um_CA|McN^2k%Hztre`AEcGzk?Y$F#`z(}kP zO>M2O=16cwrkl)FyLDN%6a;Cm1g~0#gsU;LEg@)XxUhkC%_og|5Nx#<3Jef(1aO-G zaGT|0yHs4N?5>;Dbb`gGvMl9PyrzY;Mih407Z@ltHWG>@FR5TZkkt^&Q27pjwtr(8M!fkYwCg#AnCB%kxkZ6Si2yJ`5VAh~ z$<05J|LX-{l3>aFouyoQKhsgj%j8d^PLeWDnA2rcwH<{TbU)ns!OO6n%8Sk;tq#;GK@;dh9eY6a%eJUYcD@W7*!Z zvcmf*-ybP_068Z(n|$L5eY7sF9myqkAVr|PH!Uul%d6rx#xiP+2n>xccm?;*@ZLN$ zzeLW5<8YxI<^|kkB)0f6hqmZ`y$Y7VUL_2P%ghfOo`L#)YruALfP+^^qY>?b5Tr{7d=!|T)&=IO|_h2Fu3 zia`UxQ`lLnmWL{X7^BzR5e>IhVsCf)gksLOTy?Kj?%TaPU#uvs7~8EkdelV!nLKG< z+BVQ0Q4mtW#1il2#A)&c0(F8&K~?{Q0)1*Yh61#{zfpGPI^Luosk@6gnTd4m0!8Lo@ zbMxwYXMEV8LJf+h7Mwkr%-BPYth)v4nui`DpIWfIM}V@x z@V$K$9eNe?-z|v+UT7W4g}n^v&c?DfOq#LwRKf=<;lbHQSOevTS7S|V)f%N5rFyrPThH>8W6%fNvD(|eE!_yg z5cS`VXk@BY-bIl!31vss3mCwku^on-rJs>mV@*6+MVh|orxY!!4i)Z%O@zwH?tZ9N z={AXrFd_0uo-&0Y$rOLACV*5_C*Sv)$;${cK>8}-Qqj?V$BqDgcCXr|Z@vv*b<&0+ zhp*~YB$A`SJWkk3RT%pyH;}ffiVJA*33Ru(yBdWwpZY6}7(m3I#p3>#5zCN>WIhY9 z!0fp&r@lUs*Ge!fM2ifbGK00ma}=4T#d9Q1%7xHQ=6-`se#6g;tH|X$$%Tp|$ST7{ z5`JNUaPXEUQ_V*l^g`5 zMJ#@6Z?~sv8(w8l6Ar0&-Y^M3GU%)I*pVODqsnt(O@Z~^ zr+SIveL$KA>lsU$ohgwp@y(OPv;(tD%f`c4DI zyvME^Q$$kq1$$^mW~Ew9*;mo1LA5?528_h457Gp*1isS~;BaE~DGPvCjUW)s=ftmk z#CUW}jo$U+Hp5T4P}ErCo)lk(#rKQdCGp@!PQ0Zuw@i{W3quIm#` zIQGrGz;sId+Nus^9cSmC)!dmd)Q7*R4wm?LHVo+kk%c`;R|i24N-4|o6nAyd+JUd>*iG)FLbJ2&u3IZ0c|B4irvdC1QEd(rgZmoF7J@-FJ-RdebV6{p?;V&Ld)VOT--OuSH%$w~fsOAPq`8$uMzf_}SAWF+yHRNRQY27>9TV3ehF2eD z7xBl?lU6dsfQ~jYN3uSW&~s6Bc;}K8G@!lW**_W~bBT*V>@M!d3D<(zM3RLDi#GAz z??t#AnLPKT6Du;&`Yup6(ZDju_YzWpTW3I*Eu4l@ROj=BMl8AG#sMt@5gvYxX1B$C zBp})Ha_D1jIti;`lnJ#2HVVmxtB>NvK{%Jbh{*@lghCU`@VsV9!yx4ZS&HCfc6)6) zOqj7}7RJi_$SENjDcM;$Y}Yfr&65Oamok&KPa%ao+N|>SlDvlugPY0!?s$R!rFGA{ zH-tpean@<-QI%a~gSpr;6lSrlBWJ!QBSyjtbzWqitrzWe9$!@}PLM|Gp0c3KiU={l zGO$dZ>P>(tmw19gg8P#{Yr`$gy4!bz?qg#XcFbo6`Y%kNG0LG|e0=L;7_3U2Au6g)R8 z@ttU}Hsd)eSC@+ZEO!RTr}2DLS9j{f3bn|&h6hcJ8DEWUL+aDXl-smIyu8~W^ zH*53~$4o*ey^?NN({wVwq8cU4#}dV=?gp@D+Xn}~fBg!R$dQ$mg{@wlZjy1^=x&eY z0Zu4|!<;~z#yX#Hv|YoCRpgusYU@%oY`b(z^+f{lg)PY#Rm=Uw`ge z_A9&ET8BXsye46^^br_C5nh>D^ZWD{46rdGr=tecHX zwz0RjUwpW~h-u5iVa=3&(u1+{^=Ln!T^M>Rb&sQl*ouX60nux3O)VLq$PctVj`2$* zF@Se>9|=vkl^E|TG&67So2zwNOz(UZ%@R-P2`}iR{B|%$6ea=cNT+mENSt(BQj+?o zPfcz&=X-m5$^~+O%0TuJZhPbwVsDw{O@$IrL!bWZW~qPqQ&x&eJ)CCA`LoR{2x9Lq zmVUA=9Apq_DAf(%=xm1(7aWcb&%5TNbR1%=;0*HWsfNjA+4ieum}XzToC0D1l;CI1 zZr0Z2!<(zzW!c$3EjVSO*G{j#?fp^I(z*j0!V3(4P}C2n^Sj_VdkL*Wde9h9FE50b z(9on_R_bG~@Gs3`Ruhz*f&~;q3LgBFC7uR;soxT|(9)6>zjm-|Om8nf5Dougnw;fH zk9gKhd@#Dvw zk0s$sKCKWKQsH=e9a*+Ew64a?4NT4 z_kVgVMKO^3O(hrJUpF*SG_?A~T0&cU0o44sL1Lgv6dnHx9^0V{fvCsNTRDKLbSzIM z;w7&UJMsRvo28{CItNrQ9?X(c;dRdreyEwGQJ9^&rwQ*r59ClH_9T|{o&)44&Bq5v zSFA<2fN(y8bSA|Yo0$KH-A%jbi*V~1O0r&0C)j-<}$S8_$6_3y-Xc3jw-ADl2Al%GOl8u6=8D_;bv`c~xX zAd5|MT1F6q10~fkY<)Hn&y4SS8`jd^NSAk)d}TKsP6wrkK#qF@V3JVom|N~ zH8B+7Cr?7d0HE!3*SOuR@o#QVJo7xKcht zTk#znf2D|7S>hPPCp#bdoM9N_G4bJQz6*8a3mj&>`q*9ek6?}yHrR}3P0T&<(oAZA zO5&wC(-)@H1I=~?hUkzSECh4EIcv{W7SlmvZY&H7qz?J~k3mvC;Gvi-OZ9mCyMj|M z&7=no+%fg(WYjCsD^;o?75#JBh8&6((3wq#2q>lwT^5q( zi~(L~-q4^_ok#=(OV8UwZyp-cvjyh|9tL9xoUXISk8xIu?(VtgcZ)iYB7_z|Kc-jK zcL}I=ZpDl>6d^szRIT@C&!Ei5w%^^rIdQAQm)B6QMiL)3%@1^u#Da z%lf|QN9{|r46vQ#=i{lz1EQ47^{4|&;L>xI8?xkdUm$q5?fnFO!TrWEq`T7N;eKjr zDl01sQ0wJt6)RmqxW9h=dKWY@m_|)HL9>}A$)cYyafxAH#SFg%~ zLk)em-XH}avJUrslHpiO;I^tX)iNMG(|GEA1UA1rXd1Gzu*ju*2Kk-NZ(jA+B-zT! z3UJSDsZmkFig<&S5b|I>a_EsGAK!37Y^-LZxaiy5Im887a%72_w{R~9+#j4Hgkc#z z`L7stN~T}(r5%BTuLWMsD|C(Rteu;UJ$^dATxj)W_@y5ISm5-Qxw^jLj$J~QjVmb| z$M2#K-(3z{5&#Kv3t0jpDiCYj+XQK7y7#|53d(@+1R@JiuQ_2DObI~tv0VP?hlGTL z(fP-M5EF|1-U)~MgAoFJZk zYzIP1hHT{9r|6dV`kw_lJOVoD#8i#v(k+=@1_a>9qoHM;h9n|EdbnS|-uFT!1!~YA zplZ$T0|8jA+GG@yYmPj41u%I)XWaXC^U1o!utuZp$dCDhii-9hy3!}p2|{Q>ThvQY zvH^Nen>|o#(1)t}+HF64hCgM?uzAfOMbQ6dnUQ>$IB1ym^RdB$fX5t_zNxS5$9$$i z&V=tKmP$Ml5FueX-B#1s_|@H`R_s^Lh0z_g|GS?BS zIxa4*;S9ltjTCFnr1uaOZtg;r;?1F?n1lpXB_%r`A#`+f0GEJPt$3H`W8vyGrFEWM zOpQ$b2ckJFIZF|^>&s_OT}I915BQufUe4|YesWNeD$U}te^(fJ(khGj^#BW6UuNk8 zrMuq%#D?bqoq81`?3fUvs)ou+rGjzOni(J#fy|*&{N>A+@(DV9l-^(D7$SwA4fF*t zvUo-aeRy6c$e)Jq~|H|pDd#tV}bP6*(L!Y z`$X+>RsT4ME?bh(P@*k3+5HPhKV9Tv!{LPpAj-}UibJ<^wK7hGoLY1lfLt2P_+!__@ zCyMs#ebZa<44IgDV%#T|kW-RCd*P$Yz!_{Ls40RdkqKM$#2dW{3g024FF=Zd_;xNk zfUO$Nd!P$LfdCb$)@t4v7$clcH!uMqPJryS%ALNMGg>%rrMht?uK2-BHt{`e$}b5; zyk1){gz<&}omu1CP?1Nn^4^5qj4i1|rTAn@y-2ykHg#}aDOjVga_?IcLS{<~KMbR1 zRbR0?9So6_TI>_W4GoltP=g3Tkq?a(CXM<6qTdMQ;f=08h7f)uX&wg7Jk3D3BT^_p zN@7VQH%XS0kpUJKj-Jb!X>CS&`UbFSei?n?BVDYuOp(s3R1^3(7qwbHGcyB(?!OkC zig>2U+EqqjD#dp|u?}W6Qm-v7-3R(8C&ekDI^jC4d_Xh+YBY$+c;cT! z2L}cSm^A+^E_xg-0r(sN%{F!ZTWKg)a2nxpn?n~2)KVbC@$$M@+>`W)q1rx$c_!%1 zXBp`AAbRD+hKTP?Z1~ zM{4}vLW|4lu_GuCG&E+eR8ROk*7~9zWfFE-bN1xb-UwjF7~MSGpREJpV{U$a`r9u? z5BGOKhdyk*v0^8#(I^GW&r7KN@ZxZ$rnsTuuU>^%j$+;HuhU!L9|5!ZU8NX+by3ky z;FKDloIIOdyq_{*=OGDKpre+en%8iP>nn_^M-DZVB9O;)g%1XKP>lulvk(|36+Et? z=mCWMXMwdM)o#e8x`EXqmw0~&XiN3Gws6I50lsI5GXpUlj7dw28$jOZ1aM%sId;@c zngsgUKsS6aK0bZ{tgt9a-~8=InX7KjPR7tMPo?;y%CCRt=fT-qHrxOONJvPSnikDl zp+a&-VL@V!>BC?;fRl!p*v!IWZ)az0`>Qwk*4Z_%ajb7_06iCcjdH;_2%?x7oNs18 z#3cOb`Ieqw4dwc$v28Xg289sultNN%>e`C0pO7GC=o^ZuCsg#QgZ7}Dgjab{(nQi? z2t%jJbvz#KE>hmp`i_j|ymLxF+}pzl=mf$VP<>m0L~3Ve7aPJs9M-L;xO2U8c5MTK zK>$DLaC^I_O0lJt6{v7(2iKEp&$i+AQFRcF5TFwG-7Uo}6QUVGT zpr2(a6lP^1=#xbB%E-vvxj!7~_f=O{+c+A?be0hZNvw##^z#xgB{?lvVi{&NQYFD|)5T`f0^LQvGYr26G=MPl`AeRECxgt4E}TH60(GE%g&H-EBEdY` zPeJp2hVd-0Fi3jA2;~(NFvhv)IIZJX{RLZ*Mhe($fZ7=JDm}1wby(oWLSH|pa(W9C z;=p0!0rX;XGc(}d@dvstFr$@w-0fm?rgGb67ZkkW;HUv$r@VaFs;;iKmI1XQ3<1;? z1-k8Q1mf!4geH>p*uIg%mZf&{-hJQif-t?Zkl|e6UzY%vG4gZ-A2CTJ5iM4{02CI_ z14N0+Kv#ggdfd#!M6OD40+W`8ni{t&|L`fW&VZ<=56iErIt6bPO-)ZjlLz7l-ZS8O zQHRF^DD{%}+Y#TP4fojCSY>4;wbb|0(mXmd;0FU{3_(FbUfxf5-5{C)p9@h~4-mca z2?!Rx%~}F65%@QL_w|{Wn?K5{@q^)<|9t>Fz)#3w>%ACEx*j`z%pfU0<-Q?7{XHsJDxJ$v zaa$9UEuiL>fBzobyE^Kxe0&Lqs(A@Rvb3A%VC9Vh6ox~{r$Z-QK$>cFD%>wkdpL1P`g|QIBuRK$Yuxt@pgODgBlnxK-?5~paNbN zV4!PmzP-iD&&_@GzC3M?i~=C>78eDsFBR*TZX@$E>_;{2WTY`TC`Za?%7^pCL=m*D z$8I?gMH-a+6GXbHNMDI{-*a-yvB3v7*4IsIVl2b(!RpU2fIn&Q0&-Dh>ZK1&&Wr$+ zJ(0M7XiN|%nT1T|CbJC(V?6egz$em}#Dp*_$UH`5*vR1%`B!Vj?Ixsm2RQogiYLo7 z8%~@rD6c>~c<_ZtRoZYWB-o&1ziT)hi@6^c-;E@UnotbI?39Ed2CDTR6} ztDB4&i{GsMr|HG>J?~a+8^fQL7wwgwddg;FNV1`EM=lCVBbNh@u&f<6SP4s@yjNT4 z-6Q%iC30iIX3DuuK4ReIM&!t)b&ey8qGpA>%cr}yv>y<0-g*CWWEUkAK_BATN?Ir= zlFh1ap)nmgvEGa)v-holDJ4(xs%MaLRMJ)Cr#JHf`0Ku}Tn$)7`Z6om69@XVd!zzu zJhNcUgdTIxYa4XSP~vc}-Ht{DxPPEFlR}Xr#Uh7qw4)6JC3NW^MuXy|(CMM?Qg6(a z1e|7V_o3O6Z^IeE(`;PhzjKj-vj37;E^521~4$u6H9RcO%iYGdYPj#e?yy z6_#|=2lB4$%<%}LcTe?l!COEeIPuMRmMT?{VFzTfWLlc%9aV?w) z5e1fT3yp#(2>e<}`xvF&578){`ZnGmLOcxEz@*92 zJ=%)dkqdB{hh?DppaurV*+%=-!-Ai6bR87XtCUyne4JYQ76Nnlz#u7wbcdEN>Vv?o zj!MN%mO|bGNm8H@itdIUUUWO-3iwI)CR=C_hV{)%^gpLyQP)-B*jr4}MfZlZ3I|FE zTT7Y^=aVi%zNX2zULL&n35DzS<3`GW^4Zfj@5jqF|k=*;g6ZL5d)mB^-1 zs^J?L_(-Q+I?@pPzj-Ce9{zzF?WOT&@(z#MNLAz&wBJjqdwsL6h){Axf}gJ5)`F9v zQ<;^OcuqHOITY{`;gM+xj_6S=>OrERU#N)-crer=8|q;Mh7=V97z`=0DRM^21<)`Q zqu)}u-@H)E12EJBk3(;{Q+BA;F;4HHT!}X55;gN#gJQX8zbCSkI;(@btb1XO&ndIk z>;uW8L&a7w9&7Opqaq>*LtO0U(J9~S#XXPHHbHJhP(~&@$mfoyuu?))u;Jy#txlWzglSUQ@O$&^$@9j>H^F6Z!yrv2bMc8 zaGKLLGgsld@57g1)+@)3Zf^X8m5&Zc(_~QPrEpp%bC}1=6(D&Y92@}O8Zfe)oY*zZ zADlVQ&dl7px4{R2Wul~H2!NfW(|(-Sp|-03{bPXvN9|yLKWoY_(ujh*JaN2kU=r)m zdz#+Kha$-?XF725K`wMz3`>lou*9f_s-cCF7UJ*k9o=P}U>N8tjN?UtAKOEy9#0h& z*l!R`9h7hC(_ynSQM9bMn)Jrl+1dHl-2>3i04&G9{sb6sb@0F0+1+DDQo!f{PxY7# zGaze$TKqgf$8q^djYe8V#=`((`5Z|8kN);`W{-YbP4G*-WF94CQr#Vj64|!WF$NTr z7~Q$Z)6Xb{7&MCW?fdufSvd{huUgkp6f?OzrJuNZ7bGIYQ$#pnbNpW~05L)gO~-7| zXx5~eH9$*`X3O3Pvf|Z?mznEx4Y_`hV8Y(74}Uytf4Phjr_fksD|& zaLLL5%Pf`_Y!~DlSMRDK28n{t{{n}CWZz5u3Emm-{ryeSVn8_lGo4VjZF80>4>R*2 zz#aDo4sDFVE5N=FbV2$ZoJ9s;&@-yCoU1<$<5~a&5qxmr%P@au2EKR&nDD&3bpX7w z>7?<*eIsZ0e-90@r~HCM#>Bj_nqGng_0Y@pu)Pvr9rFwMQ63%7T4<6MYG`KXm&4ZL zdKLwIR8tqI)7LaTRLNc?np-=bUEjwTM-$`rw6Upj-WTqC1)$~}p&j63;Qm!9Zn+!) z*3-I0_mu2H;GwQ4F9+qBz}XTJg+bk{b<=+B;@yD*z;y&6PJHP=*{A`?^MwdyRNoQb zKDU@kp`5K+EVs7w(TfkV<~56@r1D8Qn3S)9TUDnQzctOVJ_#-Rp^X?Ql zA0mUaQ}vrA?Iae*iUvid7`(?1`CXV9n7A-J4yX8nfy!pUpa34+^{iM26cw#M<0{nl zKv#xNm65EJloX)irKI|SVbtR!CWD=WL+gM*Dq1d;`}mi&`xgMw#`9&10ZR{uHv^Uc zlr)0eiH;oEyC{l}ahqM5@*j=+A9Pk}lVm11{Pyp#`#I<<5xo014h-FYg7Fz+*NcKd zM@e=zWxOG%epSjeoxqN>cH0HL16mjJ4mx1lNl5enx{2g@@Ar?zB`UjsJ+lYC#Y6Xt zCNH5SRKZJO(1`G6m=Bb)?$_fPK}{e_m{N z>()Um5go6V&J#gRhIcfIj7+1vmsHu6ILv|;LC+`0_ zRD!CLWqo~JUhuA(4)g^9N15wjm?%g@?8KumeSuMv592%>Z@{#ZzO`GM2GKtJC2tp_ zN^3mzHfW8Q2W$rLW`okxs^x47&|c-!O~5(}HjL$Bs}?AZ!+OA6Eq~$;FD@!_y524T zof_KZ9+%4jswHZlIaB1*`L2NVJ})b48PLlz^73_Ra3?1!?ompL7hG0z^|n-ll{{$& z0P3d$x2Qkni^is=C&Hk+VFQGGV9Dj>;h8Gaav3c}3*CqcBX*IPa?txg?h6Xo%A5zi z%?NU4CnCTrXAX6Yv0K(?3_rj-fpm|o<{}Q_Xbv3;LGA})qKpYf%2brl)6x z3B2uVZEJg2PtXA>@!0O!V;BVV!7n3F9Re%*@k+Pg?J}0b{><5L-a}w~eZ|G)07ng9 zwsE|>K6|ubpPrsBy5H;rdvx*S&aopfle-BlE-mQ-Gk))C5~y%!tRzgtc4Z7PM$vSM z`jTFEU5bJ)=~Roa2&}mMo1pznv;u=;xy}MqL(bryAUB#>S#5C3f##pNxCKUwKR-Hl z&j2Cuj?~Ejppj1NY)q6+;Iy8fnyLlg^SCy7zHR~f6a^v-1#khpYd6c#KpbNNvyMRX z0&pCs{MrC5Hff;21lV=~-~fb5QIWm_-B)U0?#t=!?gr&Ds2IU!1YIH7bW+id>6=ci zO-(7ASJBafdj|z{3PoO<2xV@p*zpu3)D4oM@@qGCAgL+F;?7rM3Q;&`Cj`y{a3Jq~ z5~Hf^1lC5!_SXf_m;y3U)6X-|o&xBvW5;ID-2<8>Zu@DH^NoL}s%vSLj&B1#4G`wB z5i-%#Xd=Mf3dR_CbBA?)JlFm!0c1~L`*-B?64GVY-dLyO9g<7qu?M5j)6;W*58VC) za5Ya>&>{k4#PV|aoKXu<*8}=0A<;OVdSn+jRk<7H+tqe~ruQLAaW&RoSkCbjB3gk5 z44k84?yHM4GtQv9Mh#vIG)`vMW%O@Nccpa;jX%#ovmFTx7HQb<{rk=e{=P&`#g88> zR8*@VMt15e#2W&B#RJ5l;A}NbO;_6#_ck4(0!rT=N1||v94Fuo2h;#yX&479M@@3+ z9wQ$I$49(wVCV+TQw%x%v9Wj=!xvY>9{0)@`b5<{LO~eB2;?0a2);kl5xM4&vygY~ zh1^*SX*U&F&~j%4g2ecNC2^YS>jxNclqg~%h5lcmS2fn>mwmSFJ)@7YTM*FU>w zfOqot_8!#Ybj}{#0(8No*%@f!(9lTk&>h+S3IqXwI|#*D!8bsC|NZ-#oDW)x1Yd^1 zsImV^2;m8Ne>o9bVXIvJk`chjMAU?n^OUJ-#2e+n=G)y8TIeEhDtVF#EtR|38hKx@A+&UrI zXap}5=teJ8g`O`;rY=C7JI@ZfazQBBiLRjjtSwzKFm0fypkO0A?ZQV&L!2hJNt2_P zGfIa=u3wS3=&^0a3`nj=Q#K!|ys~l(8~3Lgvq`E5J|#s(TaM#YO+ZQRZf&*0m_hW; z&K4nh_Jt%NDao`#4e)xP$&Vb3GvzfG*9FdnIXg=<*E=9H+Xex2C6RunS*$>p%a;aB zTM418angBW`Q&H`v#4W0Py`tB%2i;!jy4APCAZF|3lwg$#Y28B|Dcv~Zqfc*g9!nA zD2Qq~qaaIwW{XFjx4P4IbXy9Nt^%c8@Tla@Y6n?nSVltWy;CcAw`0!Ib20%GbW5{Jzb`^bpGx|WFsc*pQ8%r0}ad-dmk^$&e8ThjZ1--oyqA({SX)U0z6nEfF%}r#!${Nd!AU@jCVukmEs?XZet&=eN{@&0@;a~Q{e0f{ecjh}y`F7DD8WWpVH@_r79h?=v8xZz#G=0O zD`SiId**TcT2=Ph`MPs1poeoN8&spqCD6;*|4e)#-Cf0oBQ&3apPJ3aSmTq(2`*F; zB|i#bTzqL=8gDw{OkO&CC2?J|3{&uZYnW!{M|y)!=P0S-{hSu8}|z zGY30~PK^FN)ufld&yzOWcu($Ke~{$j(n9!E=okh=$?z!e1O?736~$|!paK{+aT!`a zfb3LKULK2LZqItdS_qOpsLANRf!YLSeikRS=H>i=D;ta6ifw_s{Kxh9Og7PWY<|ZH zw4PN}1J=N3rHY>L_{krb%|svkb^4YzWr~99B+LCMb~s+rg5_o3;WkVbhMxtYftold^S$&L z5cI~(KYJVdZ65OA<=@Nm7MO|bNw)Z%?1G-UXo%wdig0mgwro(YLIig1jn2l?>!7vR zEpdxze?fNRj(I$Yq{{s_9r>H~F$WY#B(s0|Z+SqW2FVMK37EhHF-}6Ia9wKu*U8o7 zq@=?i!ap(hoUOG2I=~CK4|Kz(D9A~@8tyuc{`&o!Il@|Az60mFqyVPqJpNsFP43;0 zoGVA`t;- zPGGWi3f~4)WDNnA&oSzYH>^=Ru(dGaC_Ex+;$(+(BuGMXq3={dtdCoLni@bhOm8~{%06f#4^Zix!3WTC3^d% zXkC(oKWb+brYyFNCRJVV(unx_^>vgy?Z>XI@q<6EVg}{rnXt2ylf6UipA)zHfj-s( z3U_#PhoEyC8L=0k<>unT6t=()dtXpserA5Z)BunJOi*JIHnhM{z;?N1j5)m(wCUWe znUQvxZcIo>_@g61fzvsAz}g%Wn)Ah4-68CMFbedk#TMslvCsE&^Hr07i=0ChxG`BL zEF*IXZ4rpRK`q?d(}VNknPf9)8R4ucj_uUq!03y7y56wCdA}2xJ-CL-y5nvw!X9e- z`gR}s<=gCNV?5tP7$8dDmomdOK$jbz7}Q?PzHl;hP#SZ@@ES)GS?-y zzo_4x8l0YL@)-LAflLY=H9$FHM{!$@iWt^LWxws{rYI+6ZN zo;3o$M_-?ax$`Z|rMK#q&HZ-dZMmw;%3^Xp#NXB&9vP9y>;qcm^1=eK1rMSPNEXjw z=~dtC0~Jta=PLzr-X-AQ#Ely_pp*4j?(>Ih0EpCUXVUWg97<)W#_}gn6$0{B3iapu z`aj5h3A;LfTP`m!>Op{YUNW3nj(>UrL<;E2ON5>^4F{lE?qfO*vRvCJTJ}~PZE-=a zZb^0Z4^L#7IR!$W(bxB}-HIl@W=Nk*F%qegUsP5$49cNW10RuJptXgnwrUKX6ZBrx zgdvz&$RE!HR!*n|pfds~58R9YX7$Kc{W@RG#5qjaphyRA02uxLg?1E{1t7t*?e^=5 zv^p4Nu@MhcVX$5d0cV2k0v7+qN80{umzgzydb=7xATLkPv$JIxXvK0k;Aw%r76@H} z3do2dA%#1cvzi>*OFo=jJ;@wwDr(Ou@wQ^Vj?AX;u-wB*Mc(P24D~Y)G>rmo#6yq) z!)o5Y->S&qKluB%5PdYr3xViTTPsEz{kf**?^J^!XTk^i-X=f-_xJbD&ioptq@l5b zu_>_KA%p@hFQAkxD&oQtbDR4LJppqZ;^!U=^2Ldv)B$N5H`@m&0vA5=j;g}reelZq zNn%>RU;HfUhr7iN zxdc1}tQTNi@UqoXgcc0bnnBwMBjTffQ1i(tGdB z2+QWKN0mlA4%NPpu0ldW=tP~we0KAC?TGMS0)djU1D^g9ku+EU!GJ^%@wpYX8x@cW z=R95@u;qz+oS{;|*g;@zc5va(KAxky`Zzj5f0Z}Yz~n+i^-(=LmsTSpPJsBZU6z5^ z*Q$ac=>BVI<6#6BVE7imQS!qN{uOmYI78#T`|gTi^KK#>)K24{IB&&5`B;;ehpLOR zN`XTdXgR=k11@rEs?UUTO;@EP{cE^;3sAtWudQjkmAnzz(G2t_4B88GQ(z-N{6aMi zinB%ck39ciA8^UzL*tb{vYx0DiO7bEtNNi-r^$0=k&Da2t&O_Xy$hP-DnG-`hNG!Y zs_RH*-Of&W~4)dJTy8bRK3_6P{ z>By-uk`m)b?KCSTJ>s=kE&h8bxh%&ilM(WQ^>5Y))#rRg3nF6|sdf9}`HXPALnWqQ zw7%<6PHO*;{D>a!7tyag%qy#FMN@g%Dmk<-*gJw034J@>DIO~ErK<2ZT|+g_iCuA+9kt+MDlQ?~!{XMZ6ZDK(u|3roOXkwD+f*n8x* z#&_3pvj4{?yMi7Iay6<}iiGtbhtc`NV9C-=?2uH23iN#u^ixVAa&36g!eHq{;$ZU6 z^i@5#VSYut#v+zmmg=s3p6_1|+h1#V_V>tolJntMPGY9K64_)Ew&OB^Iv8oM zFbCJ?%XTy=^chbp@lqGi`Abo@b34S_WJvrdb1J?rhp=HL;wz^w+thcKSD}cw_29>c z?Jf74%qr^qk97m@3cbK(KFNEfNT|ajHhHlt;iu52#z%=B`cHz1_U_%zRP-`Mh;nC( zCI4kIS@=dWKvnKVx8+~s7W)|zHp*UUL)b*gGyh8@q)}e?lD22%GMXh6rH(P`@78!E z1K(5Lb;xJjYVu}@2Wd1GQ?QslxxUR-O!wB7fP{VC?^DU*4+B?4Ny8+%=F~d84RKxK zYiJ&b1|3|>A!F2=e#d+Az~Mp1`r};%2X^n?`b**NR)yADT>;^}6YYV1ZpSMuJlY*B z_hHSZF;PgGgChR^@t$`BYN7k#uM)K!F;2@5Q?*YLM@-ah%k?IMcxu}ngadP#zSI+x zYj0l-o1p*b>*8(ey_~Taz2J$)y%&?7vbsW~?=-R9ogFia77M>s5Sz_qSZP>{kwIiwVQ*G#E&8)r+mU`RXyj!0|V-41k+Ua&!zIssGDV*wt>|h z;yp>^@u}QHPG$!xf7o3#ua!GQ+C&*A*%}+zdVAA{d6-YAj;mP?5}nb=eXxkBKK<9B zGEykC^++YWe_wonbLepV%R{S4!3}E#3!_Fys-lK?ykv`TRRtmSd5q3OOmM?DL_<Cwa?Sf*gied_AOJCSlL&@(PF1SWQh ztAbgA`-qBH_MC!VKb=R-2_;9?r&ia#%j0h{&0sW>Rxq__7ivWeQyvnMq?Sif(1wCv zR?{gFN1_UhYo5c@W8@JVuhTSER;ZvhuD5Jq><8YiIT6LgM!uVN9Yi$+LhV!8=h#Wo z|KSXXKqpi1{{78wHy9Y8lh%|zSpZrRv}JU3bWhD%z|uljQ!2674j5R8fq{V-_!Kj0 zrJ<(gGpipQ9fk6y_(HyQN??n!ExL;vCjl_W5wYm9Ctzs9n8KutTC+kAV$|d!+`Hsa>)Ehg2I&+ zZ}uzBq+i7A_IHUPmp)H!; z^lZmkD0LDB_Cl#J5W+x#)*q2fteF}N5?-K{X#{7rB-^gak|*O3jL_^-#C{743)?%9 zl;fa|+?;8d5$SDhy$>`o8^GhIVOUKoi6`)mP@1L_MJ37Gy!l)_l-tbpr~GBZn$_my0|>D>nUZ&$So*Tp#;3hD5O zKC54!y!p#^QkJKO_Qnk#aKZv6gH>9=leAQ|PPdF*%^Nn-S=Vh!iW~w00)VwA_x_KV>`;AK8XAVe?e!E@vZ+(EX{Px~c zhiA{Akp?WOWyS^B2}63Y5Cg&j>`zQ%PcQENsPruXQVTEZI%LUe?%L&tyJ~%E`hIdo zU3{hyEvxY?(&D>?R{DJ>1Ps~@JV#hq)P~Ba`+9ya1gF95mrbVN3AmkHfaC<46`7MR zO;f()qN1NNmwwV&{D~?sRS_hfhsUM-exOiq4UB~15IkESr2338$F6x-5_R#D+wZrpq;HZlzU!qByikDBi|ZH~+8Y}b z>cO~Ci(}H9SrHN5VGV8o!9cDp)N`l5O-bk@c}q^oXZF2hU}bIXv!x$5zPZtVD=aS` zc`8MUU<~f|NQ#WqZ$fb6Hz{m~ptyR1ig_z^Ka@Re^W0i9y$D@RkgiT^uPgyQhfpUB zP)Oxs=A)vco6dH7fRXW2a!*YF`N_HA>;z_o7(+vV`0fFe6cBI;!LYQnG`!|w=V1e- zU!936%~G+6|AUHObAOBXWRu-qW+#09^a)eKy15C)ccm1h_S*GCwv)-NdV+D@<%Ye? zzYhNAOv=Au7yVxS#M1-U_Psb+R?YwISl(oCtIv(z9=!1!9Eyy1%xhh<3k#K#79R3YbIep{(&MK;V2YsGApjaKbjkNJ`xf1S`17!M`HFA!ESiI? zChWR~ZTX|jj))l!@#W=Zf^(G;*sP>GhyUdlJbB*I0=v3Vi#O|MmehYHHv^O9tb@h^Xbyl4K(Q`#g3-}}%mxPIV3UZze|h3}0b4$YgXG#ZfZM^j3Lr9% z_V)Jo8kyaT?(hTn+aZz2Xa4|i1Kx?eHjM8`7^EB7C9Iz?QktDBmX>xF07LpH>+JBM z#dmgA(l~1-mpoyG#0H$Re7+^A0i+3=Dx&4`3%h zKf4y?BB(k;t?0o)3PKX3`oKs5mqlm+fjsX8a3!z~4pxtsTbhGN)6&zMzD>-3E^Fpu zaPQre*=Cem$-g|889tR0egDSnSLxY$&BcE@S)~Q+WO6InOMjh+7+fRoi`n_!oO?yn zyOElj?857>*Skg+tfk~EIeM1)`3s+z?ej%7P0fI_oo`elju-{#N1L#svhVgZ6Q9e0 z;<{1w^Q}z4#o=8D%q*~I!VJ1GxV4Jp%9PZ}egI>zwB%jS*qShJj%_x|R$ihqgG-J4 zwY9G_O9b=dwe4JI8AJvoijx(QhVhpfJ+TIX z*|Q5aech@~!!pQNYzvK?UD>IApUna*nj79+=x?6;*OxD{n4i%$P7Q5by>iE193uOB z?|xWXYFTArN(1j2IuX}UosZZFmGB!@3B!@{ehzcZbI4QX#j5odzZ`KbG#G{EHSAqu zO^OaFEL5_Z>W|SyVkYWegT^cAb005;QT%EKz! z*Vs=_m{kZ3>2b;-5;x{LLhj-+d*dCoT}gNRZZ%9Gd}%M_QP*u2WsCDPR^F6XAhPjZ zcRKg*DVHek@gUX~RV#{7$6`P3LqqAD@VCp5ni>1q zJFVuvG5fGIn_lVbtr!^)#`Wc+tEDiM>__)mD@};ui-#n1a;48@VC+Ob; z0ew6d@5e4L-#47{1+|x;tmfR$Hp73|5`6Y3Y1|#YSe+cYzHF|VyyW;M5~@GVl^0yk zEZgx%MP@zj)w%?0wa{-OHim!XqpWhehn?&?iRQ%u$>eSZUO~p0h|}-9F-z&0IevU` z(9x!+j{lLWwW{WikydHgrC3F(tVqzmP%`Sm-wW%#>8m-3#TRFk>8V8HynW)!B`g)` z;TRtF~k0Ho1`)46!j9eoF`(|;{>O|6L zD^s*>Uy2IPhnVne3D|k#A8B#5t(abO`x^h>j(=8vRTm-}JDXO}4#X~5z|lR_l52B zk8=$zy<2ByV_bb4ZGGG+qa)97o^ezK*E8>-o6t#op=23NV5Ggjh$vvzUz0LROXVBG z>oqNER`1MY^S@|on|W%oPPlcg=-TU;#6KA(KfWhWqw_-1d0|D9Pdo^Ch{~qF8=mII z%JK#E62kq2Shup^nVd^R8eAx>m1sxqb&AcU)p>GW*!6eAZQk*%u~M^K+i7t_a25Wd40T(ZSIs@vXK@}PYG&y>rqMSGf*SG zoZ|7LwRochIz%=cHezoTd3X;Bd_j88jUN|wUEWEO&f?^i4gbd9AzqVOrQj87@X@i?ZUY<)gm!|(?bi7t=DF1$*qrxM820D=kFaE&ZyAPL4vzlCD?a4iBa zq~2z07$0}J^+rdt{ckf#)ue}797P%JG(w5jYrbb692lR2Vei8 zbR3oL{92BJYQ&4+NG{d7f(qg~EgPjJ?=m{%`EDnexmLTBrjM17;-$^yW?P$Oj)2wZ zy_Z`oatODWXINP6z zapsSzz#PFXl?@k;xJh|Iyg26T?>Z3$x5Nj7eHqXvTMfDapEGoN8FDObXHtDXx;>eg z4*fhW2{vi(v1iZ*z0ce8n7EC zl!{Q@--^09ZRSl0`|u8Fw52?$+N4fJf5WC@=FeuS9T~^k#WM}jDhP1)s`i_$Qxdsh zat;)&>Eevyb4j{f59>TUF1@+_>+m#iZ5v`cC0M&~w%@Oiee_+vVh9!?QAgt&{-l5H zW~%T2|EfXrlCTwmsq^O4Fgi~gs z7$#h|u5IY(vXIUGS2TiiYBthS%$}r%_x&d=WJ}zs<^2ZzN}}p}E-R72Lj_X@7VC(O zP`EBNT-O*$5lhLMB0au)o?T~{tU|>7$v%q(E!ZyfBPY>>N5gkfDZRXf`_l*u{tiC9 ztu3TG$EDxTmVTp_+^5wNc}h>~Q!I+Q8l3GnT&v18sZCwcyrY$n=^|KC>QNS%@8^qo zHQ#12MA38_x(ie^o=D}VrbgXjFgfMO6a94JU5$S#$V^GJ-blCL8s_oMHNwr=Pe>wz zM}wxM1ifhZ=e1;%oBeFyW?b01l-cV@%gC3Haf<^j*T|c&pZrr9pfIGKN$oc091EfH zM|EZfB?5q)|GQ1j>dawx>hbX6^Fq0I=?%xJa9U>HaIZ* zYW;J;1g)GB8M=4Tc=J z_>Pm2K_wAeLxoXBj*%8_tRYDQDvP;RzjdAb{1)eITo9iiBT>-{`B3B>J(HS1)3-jh z_@ldI5}V%K)!~+^JvAn&fsb0+lFA{i;<$=twQ*y) ze5qPlwB-wml&Atcxic`HE((#U#rH1ILe-n zJyO`wsN^`_SXr^fY>AXbEu2GJp7o6qRd^9DMQXjvQ5lMvLarxn&UNXGo0iu=DD%x~ zf|FgEMwh!van`B#{cuu`Y8N7g$JaLuaARdL4_q2AhpgmA)Kz)b52CO1c2u%WQX}_H z((5xV*4Ix_b%?pNqiqL-%^bO(b6{p^^F_=o4q`{$%Z?PQ8oM7cjBFj`#k&`D9d`&O zOzmTn2cM*uw-Z0Ge9XU!il~1$j*SD*XweEA-6k*K750<_?-+08ZLttZ!s8T+!C1+lyd;BOEGg>(5%;50CoC9QWI+3$`d z?T6@#(K_q4SH24!DloY0+xjJ~zddPw7Z47ndaf`pJT;*I3*CVklZQ!t8Cuv2Inf?#RYfin`wmUt0^HHmk zh})CzWQm1bnaclvRi13FHyvA0MJxAftcq?pM0|q<;|e1whROWiaew+d|J4NWbsym; z_t+5M8{Qec#xu-p@HaWm#&=0pdKAa8eujFAH4Io)#q&g>hk|`x^06BcH|xYMPahq+ z_jjlMc=6(lGN@2#@%8Uo-m^rz89-Aa^w<$e+1OfF;ZxIOkO*G5*?XUl{Idx|7sxc$ zc}lzFIoY+*v&y})lnY--wmI)tO!sTz>*prFA14|`yeN9rj4V;d(4eZca<$D@>mRf;HPf3 z20{l>9Qn7`(I!dv;kv4cGjm*;zdzI+r;D$6YEd~~_?}}qKr}f?B5mZi8G~t?CakwU zN8QT$L856FCY*o#&|*BIJp~TN!y}|3W)%=83ra@L$X7SbxOjgoy6{l5CyNdm70aSp$Q`#mVYG4X zuE$uZNXQiw#u6&niV)?(uE6Azr9eSW`%s=*`6A2Umnsj8oZV|}#Ub0La;W7UoCGBr zx3|5vKry~CAq*bsyytB%TNwW#2Z4^IO!&}EcKelOlwc6=4XhG;Sg@;B!JO2jauKuC zPgH$21m6wc_5F_?O;hSrK>0I?+}K9Yo|X5TQSl}NxqhQNYn~|VBy&z4QstZoMZ!81 zg96eDB@j}G!ZKh$v5fu@=)}HQP;<_rJbZN}sIYxAs7|k$|roC~ zL3G3^Q4--2k?Y&GvT4ib6sei<_21UUgTgQILlNeIye_0;`XSzqq~HM$eEj-XSS2{H zMBMdN_UzhRr2V{jV#~+?A#z9Isz)@Q-1cJi9V$`cqE%uFQDYV&rx9druPoIB*s;mj zie91O@bnAN^4nT!&US8J;r)6aUs#26Vy#o5Sg=;AY*NVe<(V5$4D%$4^lrg|3Wyp1 z9^Cm2i`Fi(He9P9P9`~@X05zk!V40t7vq7CacB3e)NqMyNv@1WSSk^~E8dLobIATL z|JVuBQ`#d#d3rRZ&lw#3nc!N(ob3Wz=bh=%_*k?E!y90DI|QQO)eZl(NTJu) zOzMi^Ej3u88NGl(zz+_H|JyzMAHTx?|M8_uoN)GARxcyUl;PH}l;t&0Wpb85{}0@> B8QuT@ literal 0 HcmV?d00001 diff --git a/src/main/java/guru/qa/allure/notifications/Application.java b/src/main/java/guru/qa/allure/notifications/Application.java index c8093cbe..24d024e9 100644 --- a/src/main/java/guru/qa/allure/notifications/Application.java +++ b/src/main/java/guru/qa/allure/notifications/Application.java @@ -3,7 +3,6 @@ import guru.qa.allure.notifications.clients.Notification; import guru.qa.allure.notifications.config.ApplicationConfig; import guru.qa.allure.notifications.config.Config; -import guru.qa.allure.notifications.exceptions.MessagingException; import guru.qa.allure.notifications.util.LogInterceptor; import guru.qa.allure.notifications.util.ProxyManager; import kong.unirest.Unirest; diff --git a/src/main/java/guru/qa/allure/notifications/chart/Chart.java b/src/main/java/guru/qa/allure/notifications/chart/Chart.java index 7e213e0a..42e2916d 100644 --- a/src/main/java/guru/qa/allure/notifications/chart/Chart.java +++ b/src/main/java/guru/qa/allure/notifications/chart/Chart.java @@ -1,5 +1,6 @@ package guru.qa.allure.notifications.chart; +import guru.qa.allure.notifications.config.testops.TestOps; import guru.qa.allure.notifications.exceptions.MessageBuildException; import lombok.extern.slf4j.Slf4j; import org.knowm.xchart.BitmapEncoder; @@ -9,6 +10,7 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import javax.imageio.ImageIO; import java.io.File; @@ -19,6 +21,9 @@ public class Chart { public static byte[] createChart(Base base) throws MessageBuildException { + return createChart(base, null); + } + public static byte[] createChart(Base base, TestOps testOps) throws MessageBuildException { log.info("Creating chart..."); PieChart chart = ChartBuilder.createBaseChart(base); log.info("Adding legend to chart..."); @@ -26,7 +31,13 @@ public static byte[] createChart(Base base) throws MessageBuildException { log.info("Adding view to chart..."); ChartView.addViewTo(chart); log.info("Adding series to chart..."); - List colors = new ChartSeries(base).addSeriesTo(chart); + List colors; + if (base.getEnableTestOpsIntegration()) { + colors = new ChartSeries(base, testOps).addSeriesTo(chart); + } + else { + colors = new ChartSeries(base).addSeriesTo(chart); + } log.info("Adding colors to series..."); chart.getStyler().setSeriesColors(colors.toArray(new Color[0])); BufferedImage chartImage = BitmapEncoder.getBufferedImage(chart); diff --git a/src/main/java/guru/qa/allure/notifications/chart/ChartSeries.java b/src/main/java/guru/qa/allure/notifications/chart/ChartSeries.java index a07afb06..61e5dcbd 100644 --- a/src/main/java/guru/qa/allure/notifications/chart/ChartSeries.java +++ b/src/main/java/guru/qa/allure/notifications/chart/ChartSeries.java @@ -1,28 +1,47 @@ package guru.qa.allure.notifications.chart; import guru.qa.allure.notifications.config.base.Base; +import guru.qa.allure.notifications.config.testops.TestOps; import guru.qa.allure.notifications.mapper.LegendMapper; import guru.qa.allure.notifications.mapper.SummaryMapper; import guru.qa.allure.notifications.model.legend.Legend; import guru.qa.allure.notifications.model.summary.Summary; +import guru.qa.allure.notifications.util.TestOpsClient; import org.knowm.xchart.PieChart; import java.awt.*; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; public class ChartSeries { private final LegendMapper legendMapper; private final SummaryMapper summaryMapper; + private final TestOps testOps; public ChartSeries(Base base) { this.legendMapper = new LegendMapper(base); this.summaryMapper = new SummaryMapper(base); + testOps = null; + } + + public ChartSeries(Base base, TestOps testOps) { + this.legendMapper = new LegendMapper(base); + this.summaryMapper = new SummaryMapper(base); + this.testOps = testOps; } public List addSeriesTo(PieChart chart) { List colors = new ArrayList<>(); - final Summary summary = summaryMapper.map(); + Summary summary; + if (testOps != null) { + TestOpsClient testOpsClient = new TestOpsClient(testOps); + HashMap statistics = testOpsClient.getLaunchStatistic(); + summary = Summary.getInstance(statistics); + } + else { + summary = summaryMapper.map(); + } final Legend legend = legendMapper.map(); addSeries(chart, colors, summary.getStatistic().getPassed(), legend.getPassed(), new Color(148, 202, 102)); diff --git a/src/main/java/guru/qa/allure/notifications/clients/ClientFactory.java b/src/main/java/guru/qa/allure/notifications/clients/ClientFactory.java index 305ee7ca..31d6e6e0 100644 --- a/src/main/java/guru/qa/allure/notifications/clients/ClientFactory.java +++ b/src/main/java/guru/qa/allure/notifications/clients/ClientFactory.java @@ -16,6 +16,9 @@ public class ClientFactory { public static List from(Config config) { MessageData messageData = new MessageData(config.getBase()); + if (config.getTestops() != null) { + messageData = new MessageData(config.getBase(), config.getTestops()); + } List notifiers = new ArrayList<>(); if (config.getTelegram() != null) { diff --git a/src/main/java/guru/qa/allure/notifications/clients/Notification.java b/src/main/java/guru/qa/allure/notifications/clients/Notification.java index 41e51c98..1511e5f4 100644 --- a/src/main/java/guru/qa/allure/notifications/clients/Notification.java +++ b/src/main/java/guru/qa/allure/notifications/clients/Notification.java @@ -1,5 +1,6 @@ package guru.qa.allure.notifications.clients; +import com.sun.mail.iap.ByteArray; import guru.qa.allure.notifications.chart.Chart; import java.util.List; @@ -19,7 +20,13 @@ public static boolean send(Config config) { try { log.info("Sending message..."); if (config.getBase().getEnableChart()) { - byte[] chartImage = Chart.createChart(config.getBase()); + byte[] chartImage = null; + if (config.getTestops() != null) { + chartImage = Chart.createChart(config.getBase(), config.getTestops()); + } + else { + chartImage = Chart.createChart(config.getBase()); + } notifier.sendPhoto(chartImage); } else { diff --git a/src/main/java/guru/qa/allure/notifications/clients/rocket/RocketClient.java b/src/main/java/guru/qa/allure/notifications/clients/rocket/RocketClient.java index 957436ef..5458c1db 100644 --- a/src/main/java/guru/qa/allure/notifications/clients/rocket/RocketClient.java +++ b/src/main/java/guru/qa/allure/notifications/clients/rocket/RocketClient.java @@ -3,47 +3,47 @@ import guru.qa.allure.notifications.clients.Notifier; import guru.qa.allure.notifications.config.rocket.Rocket; import guru.qa.allure.notifications.exceptions.MessagingException; +import guru.qa.allure.notifications.json.JSON; import guru.qa.allure.notifications.template.MarkdownTemplate; +import guru.qa.allure.notifications.template.RocketTemplate; import guru.qa.allure.notifications.template.data.MessageData; import kong.unirest.ContentType; import kong.unirest.Unirest; import java.io.ByteArrayInputStream; - public class RocketClient implements Notifier { + private final JSON json = new JSON(); private final Rocket rocket; - private final MarkdownTemplate markdownTemplate; + private final RocketTemplate template; public RocketClient(MessageData messageData, Rocket rocket) { this.rocket = rocket; - this.markdownTemplate = new MarkdownTemplate(messageData); + this.template = new RocketTemplate(messageData); } @Override public void sendText() throws MessagingException { String body = String.format("{\"channel\": \"%s\", \"text\": \"%s\" }", - rocket.getChannel(), markdownTemplate.create()); + rocket.getChannel(), template.create().replace("\r\n", "\\\n")); String url = String.format("%s/api/v1/chat.postMessage", rocket.getUrl()); - Unirest.post(url) .header("X-Auth-Token", rocket.getToken()) .header("X-User-Id", rocket.getUserId()) - .header("Content-Type", ContentType.APPLICATION_JSON.getMimeType()) - .body(body) + .header("Content-Type", "application/json") + .body(json.prettyPrint(body)) .asString() .getBody(); } @Override public void sendPhoto(byte[] chartImage) throws MessagingException { + sendText(); String url = String.format("%s/api/v1/rooms.upload/%s", rocket.getUrl(), rocket.getChannel()); Unirest.post(url) .header("X-Auth-Token", rocket.getToken()) .header("X-User-Id", rocket.getUserId()) .field("file", new ByteArrayInputStream(chartImage), ContentType.IMAGE_PNG, "chart.png") - .field("msg", "Test launch report") - .field("description", markdownTemplate.create()) .asString() .getBody(); } diff --git a/src/main/java/guru/qa/allure/notifications/config/Config.java b/src/main/java/guru/qa/allure/notifications/config/Config.java index 453d1fe9..e00b1da7 100644 --- a/src/main/java/guru/qa/allure/notifications/config/Config.java +++ b/src/main/java/guru/qa/allure/notifications/config/Config.java @@ -9,6 +9,7 @@ import guru.qa.allure.notifications.config.skype.Skype; import guru.qa.allure.notifications.config.slack.Slack; import guru.qa.allure.notifications.config.telegram.Telegram; +import guru.qa.allure.notifications.config.testops.TestOps; import lombok.Data; /** @@ -32,6 +33,8 @@ public class Config { private Mail mail; @SerializedName("rocket") private Rocket rocket; + @SerializedName("testOps") + private TestOps testops; @SerializedName("proxy") private Proxy proxy; } diff --git a/src/main/java/guru/qa/allure/notifications/config/base/Base.java b/src/main/java/guru/qa/allure/notifications/config/base/Base.java index a6ee2ca9..e6de33da 100644 --- a/src/main/java/guru/qa/allure/notifications/config/base/Base.java +++ b/src/main/java/guru/qa/allure/notifications/config/base/Base.java @@ -27,4 +27,6 @@ public class Base { private String allureFolder; @SerializedName("enableChart") private Boolean enableChart; + @SerializedName("enableTestOpsIntegration") + private Boolean enableTestOpsIntegration; } diff --git a/src/main/java/guru/qa/allure/notifications/config/testops/TestOps.java b/src/main/java/guru/qa/allure/notifications/config/testops/TestOps.java new file mode 100644 index 00000000..f9b1b0c9 --- /dev/null +++ b/src/main/java/guru/qa/allure/notifications/config/testops/TestOps.java @@ -0,0 +1,23 @@ +package guru.qa.allure.notifications.config.testops; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +/** + * @author GerasimchukDV + * @since 4.2.2 + * Model class representing Allure TestOps client settings. + */ + +@Data +public class TestOps { + @SerializedName("url") + private String url; + @SerializedName("auth_token") + private String auth_token; + + @SerializedName("xsrf_token") + private String xsrf_token; + @SerializedName("project_id") + private String projectId; +} diff --git a/src/main/java/guru/qa/allure/notifications/json/JSON.java b/src/main/java/guru/qa/allure/notifications/json/JSON.java index 2b404f3e..60a38c85 100644 --- a/src/main/java/guru/qa/allure/notifications/json/JSON.java +++ b/src/main/java/guru/qa/allure/notifications/json/JSON.java @@ -3,11 +3,14 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.google.gson.stream.JsonReader; import guru.qa.allure.notifications.exceptions.ConfigNotFoundException; import lombok.extern.slf4j.Slf4j; import java.io.FileNotFoundException; import java.io.FileReader; +import java.io.StringReader; /** * @author kadehar @@ -28,6 +31,15 @@ public T parse(String file, Class clazz) { } public String prettyPrint(String json) { - return GSON.toJson(JsonParser.parseString(json)); + String result = ""; + try { + result = GSON.toJson(JsonParser.parseString(json)); + } + catch (JsonSyntaxException e) { + JsonReader reader = new JsonReader(new StringReader(json)); + reader.setLenient(true); + result = GSON.toJson(JsonParser.parseReader(reader)); + } + return result; } } diff --git a/src/main/java/guru/qa/allure/notifications/model/summary/Statistic.java b/src/main/java/guru/qa/allure/notifications/model/summary/Statistic.java index 03731d00..39b1f3e9 100644 --- a/src/main/java/guru/qa/allure/notifications/model/summary/Statistic.java +++ b/src/main/java/guru/qa/allure/notifications/model/summary/Statistic.java @@ -1,14 +1,19 @@ package guru.qa.allure.notifications.model.summary; import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; /** * @author kadehar * @since 1.0 * Model class, representing test statistic from Allure Report. */ -@Getter +@Data +@NoArgsConstructor(access = AccessLevel.PUBLIC) +@AllArgsConstructor(access = AccessLevel.PUBLIC) public class Statistic { @SerializedName("passed") private Integer passed; diff --git a/src/main/java/guru/qa/allure/notifications/model/summary/Summary.java b/src/main/java/guru/qa/allure/notifications/model/summary/Summary.java index 08dcb960..4ac8721c 100644 --- a/src/main/java/guru/qa/allure/notifications/model/summary/Summary.java +++ b/src/main/java/guru/qa/allure/notifications/model/summary/Summary.java @@ -1,17 +1,32 @@ package guru.qa.allure.notifications.model.summary; import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import lombok.Data; + +import java.util.HashMap; /** * @author kadehar * @since 1.0 * Model class, representing test summary from Allure Report. */ -@Getter +@Data public class Summary { @SerializedName("statistic") private Statistic statistic; @SerializedName("time") private Time time; + + public static Summary getInstance(HashMap statistic) { + Summary summary = new Summary(); + Statistic stat = new Statistic(); + stat.setTotal(statistic.get("total")); + stat.setFailed(statistic.get("failed")); + stat.setBroken(statistic.get("broken")); + stat.setPassed(statistic.get("passed")); + stat.setSkipped(statistic.get("skipped")); + stat.setUnknown(statistic.containsKey("unknown")?statistic.get("unknown"):0); + summary.setStatistic(stat); + return summary; + } } diff --git a/src/main/java/guru/qa/allure/notifications/model/summary/Time.java b/src/main/java/guru/qa/allure/notifications/model/summary/Time.java index c0960b3b..4bc702b3 100644 --- a/src/main/java/guru/qa/allure/notifications/model/summary/Time.java +++ b/src/main/java/guru/qa/allure/notifications/model/summary/Time.java @@ -1,14 +1,14 @@ package guru.qa.allure.notifications.model.summary; import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import lombok.Data; /** * @author kadehar * @since 1.0 * Model class, representing test duration from Allure Report. */ -@Getter +@Data public class Time { @SerializedName("duration") private Long duration; diff --git a/src/main/java/guru/qa/allure/notifications/template/RocketTemplate.java b/src/main/java/guru/qa/allure/notifications/template/RocketTemplate.java new file mode 100644 index 00000000..6cb9963e --- /dev/null +++ b/src/main/java/guru/qa/allure/notifications/template/RocketTemplate.java @@ -0,0 +1,21 @@ +package guru.qa.allure.notifications.template; + +import guru.qa.allure.notifications.exceptions.MessageBuildException; +import guru.qa.allure.notifications.template.data.MessageData; + +/** + * @author GerasimchukDV + * @since 4.2.2 + * Utility class for KaTeX template creation. + */ +public class RocketTemplate { + private final MessageData messageData; + + public RocketTemplate(MessageData messageData) { + this.messageData = messageData; + } + + public String create() throws MessageBuildException { + return new MessageTemplate(messageData).of("rocket.ftl"); + } +} diff --git a/src/main/java/guru/qa/allure/notifications/template/data/BuildData.java b/src/main/java/guru/qa/allure/notifications/template/data/BuildData.java index 253d9c06..45362118 100644 --- a/src/main/java/guru/qa/allure/notifications/template/data/BuildData.java +++ b/src/main/java/guru/qa/allure/notifications/template/data/BuildData.java @@ -1,7 +1,9 @@ package guru.qa.allure.notifications.template.data; import guru.qa.allure.notifications.config.base.Base; +import guru.qa.allure.notifications.config.testops.TestOps; import guru.qa.allure.notifications.formatters.Formatters; +import guru.qa.allure.notifications.util.TestOpsClient; import lombok.extern.slf4j.Slf4j; import java.util.HashMap; @@ -14,11 +16,17 @@ */ @Slf4j public class BuildData implements TemplateData { - private final Base base; + private final TestOps testOps; public BuildData(Base base) { this.base = base; + this.testOps = null; + } + + public BuildData(Base base, TestOps testOps) { + this.base = base; + this.testOps = testOps; } @Override @@ -27,8 +35,16 @@ public Map map() { Map info = new HashMap<>(); info.put("env", base.getEnvironment()); info.put("comm", base.getComment()); - info.put("reportLink", - new Formatters().formatReportLink(base.getReportLink())); + if (base.getEnableTestOpsIntegration()) { + TestOpsClient testOpsClient = new TestOpsClient(testOps); + String launchId = testOpsClient.getLastLaunchId(); + String testOpsLink = String.format("%s/launch/%s", testOps.getUrl(), launchId); + info.put("reportLink", testOpsLink); + } + else { + info.put("reportLink", + new Formatters().formatReportLink(base.getReportLink())); + } log.info("Build data: {}", info); return info; } diff --git a/src/main/java/guru/qa/allure/notifications/template/data/MessageData.java b/src/main/java/guru/qa/allure/notifications/template/data/MessageData.java index 032772f0..c2db5938 100644 --- a/src/main/java/guru/qa/allure/notifications/template/data/MessageData.java +++ b/src/main/java/guru/qa/allure/notifications/template/data/MessageData.java @@ -4,6 +4,7 @@ import java.util.Map; import guru.qa.allure.notifications.config.base.Base; +import guru.qa.allure.notifications.config.testops.TestOps; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -28,6 +29,13 @@ public MessageData(Base base) { this.phrasesData = new PhrasesData(base); } + public MessageData(Base base, TestOps testOps) { + this.project = base.getProject(); + this.buildData = new BuildData(base, testOps); + this.summaryData = new SummaryData(base, testOps); + this.phrasesData = new PhrasesData(base); + } + public Map getValues() { if (data == null) { this.data = new HashMap<>(); diff --git a/src/main/java/guru/qa/allure/notifications/template/data/SummaryData.java b/src/main/java/guru/qa/allure/notifications/template/data/SummaryData.java index 97127c17..0d84fb48 100644 --- a/src/main/java/guru/qa/allure/notifications/template/data/SummaryData.java +++ b/src/main/java/guru/qa/allure/notifications/template/data/SummaryData.java @@ -1,10 +1,13 @@ package guru.qa.allure.notifications.template.data; import guru.qa.allure.notifications.config.base.Base; +import guru.qa.allure.notifications.config.testops.TestOps; import guru.qa.allure.notifications.formatters.Formatters; import guru.qa.allure.notifications.mapper.SummaryMapper; +import guru.qa.allure.notifications.model.summary.Statistic; import guru.qa.allure.notifications.model.summary.Summary; import guru.qa.allure.notifications.util.Percentage; +import guru.qa.allure.notifications.util.TestOpsClient; import lombok.extern.slf4j.Slf4j; import java.util.HashMap; @@ -19,30 +22,47 @@ public class SummaryData implements TemplateData { private final SummaryMapper summaryMapper; + private final TestOps testOps; public SummaryData(Base base) { this.summaryMapper = new SummaryMapper(base); + this.testOps = null; + } + + public SummaryData(Base base, TestOps testOps) { + this.summaryMapper = new SummaryMapper(base); + this.testOps = testOps; } @Override public Map map() { log.info("Collecting summary data for template"); - Summary summary = summaryMapper.map(); + Summary summary; + if (testOps != null) { + TestOpsClient testOpsClient = new TestOpsClient(testOps); + HashMap statistics = testOpsClient.getLaunchStatistic(); + summary = Summary.getInstance(statistics); + } + else { + summary = summaryMapper.map(); + } Map info = new HashMap<>(); - info.put("time", new Formatters().formatTime(summary.getTime() - .getDuration())); + if (testOps == null) { + info.put("time", new Formatters().formatTime(summary.getTime() + .getDuration())); + info.put("passedPercentage", + new Percentage().eval(summary.getStatistic().getPassed(), + summary.getStatistic().getTotal())); + info.put("failedPercentage", + new Percentage().eval(summary.getStatistic().getFailed(), + summary.getStatistic().getTotal())); + } info.put("total", summary.getStatistic().getTotal()); info.put("passed", summary.getStatistic().getPassed()); info.put("failed", summary.getStatistic().getFailed()); info.put("broken", summary.getStatistic().getBroken()); info.put("unknown", summary.getStatistic().getUnknown()); info.put("skipped", summary.getStatistic().getSkipped()); - info.put("passedPercentage", - new Percentage().eval(summary.getStatistic().getPassed(), - summary.getStatistic().getTotal())); - info.put("failedPercentage", - new Percentage().eval(summary.getStatistic().getFailed(), - summary.getStatistic().getTotal())); log.info("Summary data: {}", info); return info; } diff --git a/src/main/java/guru/qa/allure/notifications/util/TestOpsClient.java b/src/main/java/guru/qa/allure/notifications/util/TestOpsClient.java new file mode 100644 index 00000000..28afa732 --- /dev/null +++ b/src/main/java/guru/qa/allure/notifications/util/TestOpsClient.java @@ -0,0 +1,61 @@ +package guru.qa.allure.notifications.util; + +import guru.qa.allure.notifications.config.testops.TestOps; +import kong.unirest.Unirest; +import kong.unirest.json.JSONArray; +import kong.unirest.json.JSONObject; + +import java.util.HashMap; + +public class TestOpsClient { + + public static TestOps testops; + + public TestOpsClient(TestOps testOps) { + this.testops = testOps; + } + + public static String getLastLaunchId() { + String url = String.format("%s/api/rs/launch?projectId=%s&page=0&size=10&sort=created_date,DESC", + testops.getUrl(), + testops.getProjectId()); + + String jsonString = Unirest.get(url) + .header("Authorization", testops.getAuth_token()) + .header("XSRF-TOKEN", testops.getXsrf_token()) + .header("accept", "*/*") + .asString() + .getBody(); + JSONObject jsonObject = new JSONObject(jsonString); + JSONArray jsonArray = jsonObject.getJSONArray("content"); + String launchId = jsonArray.getJSONObject(0).getString("id"); + return launchId; + } + + public static HashMap getLaunchStatistic() { + String launchId = getLastLaunchId(); + return getLaunchStatistic(launchId); + } + public static HashMap getLaunchStatistic(String launchId) { + String url = String.format("%s/api/rs/launch/%s/statistic", testops.getUrl(), launchId); + + String jsonString = Unirest.get(url) + .header("Authorization", testops.getAuth_token()) + .header("XSRF-TOKEN", testops.getXsrf_token()) + .header("accept", "*/*") + .asString() + .getBody(); + JSONArray jsonArray = new JSONArray(jsonString); + HashMap statistics = new HashMap<>(); + Integer total = 0; + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject element = jsonArray.getJSONObject(i); + String status = element.getString("status"); + Integer count = element.getInt("count"); + statistics.put(status, count); + total = total + count; + } + statistics.put("total", total); + return statistics; + } +} diff --git a/src/main/resources/templates/rocket.ftl b/src/main/resources/templates/rocket.ftl new file mode 100644 index 00000000..d1209581 --- /dev/null +++ b/src/main/resources/templates/rocket.ftl @@ -0,0 +1,12 @@ +<#compress> +**${results}:** + **-${environment}:** ${env} + **-${comment}:** ${comm} + **-${totalScenarios}:** ${total} + <#if passed != 0 > **-${totalPassed}:** ${passed} + <#if failed != 0 > **-${totalFailed}:** ${failed} + <#if broken != 0 > **-${totalBroken}:** ${broken} + <#if unknown != 0 > **-${totalUnknown}:** ${unknown} + <#if skipped != 0 > **-${totalSkipped}:** ${skipped} + <#if reportLink??>**${reportAvailableAtLink}:** ${reportLink} + \ No newline at end of file