From f98f4f27e0f3cb0c0d9d9d2c62886a1da9284f5e Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 4 May 2020 19:57:51 +0200 Subject: [PATCH 01/22] mock the whole ES client in the integration tests (#65081) * mock the whole ES client to prevent real requests * mock the whole client Co-authored-by: Elastic Machine --- .../elasticsearch_service.mock.ts | 51 +++++++++++++++++++ .../core_service.test.mocks.ts | 11 ++++ .../integration_tests/core_services.test.ts | 8 +-- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts index da8846f6dddbb..a7d78b56ff3fd 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts @@ -18,6 +18,7 @@ */ import { BehaviorSubject } from 'rxjs'; +import { Client } from 'elasticsearch'; import { IClusterClient, ICustomClusterClient } from './cluster_client'; import { IScopedClusterClient } from './scoped_cluster_client'; import { ElasticsearchConfig } from './elasticsearch_config'; @@ -130,6 +131,55 @@ const createMock = () => { return mocked; }; +const createElasticsearchClientMock = () => { + const mocked: jest.Mocked = { + cat: {} as any, + cluster: {} as any, + indices: {} as any, + ingest: {} as any, + nodes: {} as any, + snapshot: {} as any, + tasks: {} as any, + bulk: jest.fn(), + clearScroll: jest.fn(), + count: jest.fn(), + create: jest.fn(), + delete: jest.fn(), + deleteByQuery: jest.fn(), + deleteScript: jest.fn(), + deleteTemplate: jest.fn(), + exists: jest.fn(), + explain: jest.fn(), + fieldStats: jest.fn(), + get: jest.fn(), + getScript: jest.fn(), + getSource: jest.fn(), + getTemplate: jest.fn(), + index: jest.fn(), + info: jest.fn(), + mget: jest.fn(), + msearch: jest.fn(), + msearchTemplate: jest.fn(), + mtermvectors: jest.fn(), + ping: jest.fn(), + putScript: jest.fn(), + putTemplate: jest.fn(), + reindex: jest.fn(), + reindexRethrottle: jest.fn(), + renderSearchTemplate: jest.fn(), + scroll: jest.fn(), + search: jest.fn(), + searchShards: jest.fn(), + searchTemplate: jest.fn(), + suggest: jest.fn(), + termvectors: jest.fn(), + update: jest.fn(), + updateByQuery: jest.fn(), + close: jest.fn(), + }; + return mocked; +}; + export const elasticsearchServiceMock = { create: createMock, createInternalSetup: createInternalSetupContractMock, @@ -138,4 +188,5 @@ export const elasticsearchServiceMock = { createClusterClient: createClusterClientMock, createCustomClusterClient: createCustomClusterClientMock, createScopedClusterClient: createScopedClusterClientMock, + createElasticsearchClient: createElasticsearchClientMock, }; diff --git a/src/core/server/http/integration_tests/core_service.test.mocks.ts b/src/core/server/http/integration_tests/core_service.test.mocks.ts index 6fa3357168027..b3adda8dd22d1 100644 --- a/src/core/server/http/integration_tests/core_service.test.mocks.ts +++ b/src/core/server/http/integration_tests/core_service.test.mocks.ts @@ -24,3 +24,14 @@ jest.doMock('../../elasticsearch/scoped_cluster_client', () => ({ return elasticsearchServiceMock.createScopedClusterClient(); }), })); + +jest.doMock('elasticsearch', () => { + const realES = jest.requireActual('elasticsearch'); + return { + ...realES, + // eslint-disable-next-line object-shorthand + Client: function() { + return elasticsearchServiceMock.createElasticsearchClient(); + }, + }; +}); diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index 7b1630a7de0be..5726486a0930a 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -43,7 +43,7 @@ describe('http service', () => { describe('auth', () => { let root: ReturnType; beforeEach(async () => { - root = kbnTestServer.createRoot({ migrations: { skip: true } }); + root = kbnTestServer.createRoot(); }, 30000); afterEach(async () => { @@ -192,7 +192,7 @@ describe('http service', () => { let root: ReturnType; beforeEach(async () => { - root = kbnTestServer.createRoot({ migrations: { skip: true } }); + root = kbnTestServer.createRoot(); }, 30000); afterEach(async () => { @@ -326,7 +326,7 @@ describe('http service', () => { describe('#basePath()', () => { let root: ReturnType; beforeEach(async () => { - root = kbnTestServer.createRoot({ migrations: { skip: true } }); + root = kbnTestServer.createRoot(); }, 30000); afterEach(async () => await root.shutdown()); @@ -355,7 +355,7 @@ describe('http service', () => { describe('elasticsearch', () => { let root: ReturnType; beforeEach(async () => { - root = kbnTestServer.createRoot({ migrations: { skip: true } }); + root = kbnTestServer.createRoot(); }, 30000); afterEach(async () => { From f1d1b8c19598bf64f47f83f125b10c71fa2f61e0 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 4 May 2020 11:05:14 -0700 Subject: [PATCH 02/22] Fix CCR search bug caused by paused follower indices (#64717) --- .../follower_indices_table/follower_indices_table.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js index 67f42fb622bf8..ef4a511f276bd 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js @@ -55,11 +55,11 @@ export class FollowerIndicesTable extends PureComponent { if (queryText) { return followerIndices.filter(followerIndex => { - const { name, shards } = followerIndex; + const { name, remoteCluster, leaderIndex } = followerIndex; const inName = name.toLowerCase().includes(queryText); - const inRemoteCluster = shards[0].remoteCluster.toLowerCase().includes(queryText); - const inLeaderIndex = shards[0].leaderIndex.toLowerCase().includes(queryText); + const inRemoteCluster = remoteCluster.toLowerCase().includes(queryText); + const inLeaderIndex = leaderIndex.toLowerCase().includes(queryText); return inName || inRemoteCluster || inLeaderIndex; }); @@ -273,7 +273,7 @@ export class FollowerIndicesTable extends PureComponent { }; const selection = { - onSelectionChange: selectedItems => this.setState({ selectedItems }), + onSelectionChange: newSelectedItems => this.setState({ selectedItems: newSelectedItems }), }; const search = { From a3a915ab66a850aeb36b658f313559eaec510526 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Mon, 4 May 2020 11:08:35 -0700 Subject: [PATCH 03/22] [DOCS] Adds information about query timeout (#64970) * [DOCS] Adds information about query timeout * [DOCS] Adds image to search doc * [DOCS] Improves organization * Update docs/discover/search.asciidoc Co-authored-by: Kaarina Tungseth * Update docs/discover/search.asciidoc Co-authored-by: Kaarina Tungseth Co-authored-by: Kaarina Tungseth --- docs/discover/images/autorefresh-interval.png | Bin 0 -> 9765 bytes docs/discover/search.asciidoc | 96 +++++++++--------- docs/images/autorefresh-intervals.png | Bin 84366 -> 75039 bytes docs/user/discover.asciidoc | 1 + 4 files changed, 49 insertions(+), 48 deletions(-) create mode 100644 docs/discover/images/autorefresh-interval.png diff --git a/docs/discover/images/autorefresh-interval.png b/docs/discover/images/autorefresh-interval.png new file mode 100644 index 0000000000000000000000000000000000000000..a9f72a1cb73cde7f5ba4b2c5bdcc7feec7a61cc1 GIT binary patch literal 9765 zcmd^lML->E6D1nlH8=!!g1fuBTd=^z-6ezo!JPm%xVyu}-QC@}xNETCy}W-t%UMh> zy1TmS>qVEI^L4n2k~9h;0U`th1d6PTgc<|{cJ95nWXRU6}EKoEEX0<{KhB zyp9SwHg;7GHteA!SztDd3^^rb-n~)BJ+*z438t8H_`s!9Ov}mX`1rG>!`tn~1T z49|+ii3O}X_l6?b;o%{1QB^htoA_`d2AJ=G=;%;log(nq>b#?hiY5R%-5pE|b4zkc zM$CQA@xycE8&z7rB*`lbC@~Ruct~=SFc<}tY85qVa(y0jDW%Pe3lDmf?-in!NqjeyaRB4gsH!NX}aqCIxSSr>;fB?Nl8dhg4|Cdtt(#|NAEdbK9Q3{^1m&jXlrXD zH{}{9jcCqX4`2TE3mMD}J!mAkytDlU&eYUiG)=$kfRAT!Cr)Vm9*P$AWk>4%n!_!l4K!*?oXfB9$;N8{l4i>40KhShV zLPU&900vJ@2t^CE`B2av5DDo&!X{TfU<-P?55#r_Ymm>(%wXW*p}8QraM&!uXH5x(;DFF%5imlty*a$m_(Yw z&CDD}qI5ZTL-a=@Q0o3Gmc5%TVB}F47BwSot zQs>Q?vpOrgDD{8&QzEY7{M&QSrc!$%SAkfbfs*uZPHYyCWob`YT$*(qNJ8=ZKC~UV z3Gv_)a{I?5(7dVT_*`s~GaA&vkY}4#)m4eyue6vR&C*phG<4S;vd6}z!m~RocU+(O zP&&g7k~`^%<_LbB6rtKaabIt8BL2+&`Eq>qk;jKirn&azWo0KRg(lbScwR0HkMS8j z6pfU;OGzEdbHPxY^-(w>Q6xPrEjtj#bt*LC@|Zm-CiCYL5czWqpb*p(M5 zF`w9%G$dnE2LWMgsIx58E`9cm{$K2(sq8+uA5dGxo98n#Z%Fg<^0s$yNVA=Gq*A%h z{Z1at@M=ESZpv2NIZZ@%z1*V}f?&4#Qj8=cR?>RL*Gc$2&bn(SgcQNNk+hhHF)1vP zrCLmyKx1EPM1(d$p%?RJOwYBeA*;Xf-Q)#7D@TT-U!$wL)Z}`I`DJh!#w%@U8My;L zI>pEzXCRt*AozYGbVb&&?^AXZzSTKlkIOKDY;eKJ4uV8-G%9k1VA=3-1#!y(7V%>?X6I0vV&FF zR*5ekF+L)Yl9Cc3@dOz++Ny^Fd7Lo$-A~kj5DophjwwuU5>bE{k{5j4T5Z<7xx)tr zRuwl|W&`*x7L6a`p|27%1&Y>@&B7-`M}l&Dt1TXx!wHg}!p$eZjEv2*vkHBIN5;R! zkozNKTLd%G{%}(1#jN_yMejkO)ANHYl}^tOTq|rXthkep!K``8Yru(NJV#oc)4*PM%cFV8|<~?W~Zn(vDRZW(IYyEmnY?N zJ)pGr5WBgEUG`$h?PPm9QVGcSWq!rs%N$;2w#5kUu;R!(PfLUFg9%CdN*xLGhZ|_5 zlSB2w5e`y)u&~{0$nZh9G^rQ<{klsE=0jDLpof3qzS%(tu}-Io>9~BD#d6

j!#`XlH)%dQxQM)ah|HG?(o%IZXcrjKN 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 S9oj>@AF`545*1=5-~JEk!-dBH literal 0 HcmV?d00001 diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index 21ae4560fba94..9fe35f0302760 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -1,25 +1,53 @@ [[search]] == Searching your data -You can search the indices that match the current <> by entering -your search criteria in the Query bar. By default you can use Kibana's <> -which features autocomplete and a simple, easy to use syntax. Kibana's legacy query -language (based on Lucene https://lucene.apache.org/core/2_9_4/queryparsersyntax.html[query syntax]) -is still available for the time being under the options menu in the Query Bar. When this -legacy query language is selected, the full JSON-based {ref}/query-dsl.html[Elasticsearch Query DSL] -can also be used. - -When you submit a search request, the histogram, Documents table, and Fields -list are updated to reflect the search results. The total number of hits -(matching documents) is shown in the toolbar. The Documents table shows the -first five hundred hits. By default, the hits are listed in reverse -chronological order, with the newest documents shown first. You can reverse -the sort order by clicking the Time column header. You can also sort the table -by the values in any indexed field. For more information, see <>. - -To search your data, enter your search criteria in the Query bar and -press *Enter* or click *Search* image:images/search-button.jpg[] to submit -the request to Elasticsearch. +Many Kibana apps embed a query bar for real-time search, including +*Discover*, *Visualize*, and *Dashboard*. + +[float] +=== Search your data + +To search the indices that match the current <>, +enter your search criteria in the query bar. By default, you'll use +{kib}'s <> (KQL), which +features autocomplete and a simple, easy-to-use syntax. If you prefer to use +{kib}'s legacy query +language, based on the +Lucene https://lucene.apache.org/core/2_9_4/queryparsersyntax.html[query syntax], +you can switch to it from the KQL popup in the query bar. When you enable the +legacy query language, you can use the full +JSON-based {ref}/query-dsl.html[Elasticsearch Query DSL]. + + +[float] +[[autorefresh]] +=== Refresh search results +As more documents are added to the indices you're searching, the search results +shown in *Discover*, and used to display visualizations, get stale. Using the +time filter, you can +configure a refresh interval to periodically resubmit your searches to +retrieve the latest results. + +[role="screenshot"] +image::images/autorefresh-interval.png[] + +You can also manually refresh the search results by +clicking the *Refresh* button. + +[float] +=== Searching large amounts of data + +Sometimes you want to search through large amounts of data no matter how long +the search takes. While this might not happen often, there are times +that long-running queries are required. Consider a threat hunting scenario +where you need to search through years of data. + +If you run a query, and the run time gets close to the +timeout, you're presented the option to ignore the timeout. This enables you to +run queries with large amounts of data to completion. + +By default, a query times out after 30 seconds. +The timeout is in place to avoid unintentional load on the cluster. + include::kuery.asciidoc[] @@ -160,31 +188,3 @@ To completely delete a query: image::discover/images/saved-query-management-component-delete-query-button.png["Example of the saved query management popover when a query is hovered over and we are about to delete a query",width="80%"] You can import, export, and delete saved queries from <>. - -[[select-pattern]] -=== Change the indices you're searching -When you submit a search request, the indices that match the currently-selected -index pattern are searched. -To change the indices you are searching, click the index pattern and select a -different <>. - -[[autorefresh]] -=== Refresh the search results -As more documents are added to the indices you're searching, the search results -shown in Discover and used to display visualizations get stale. You can -configure a refresh interval to periodically resubmit your searches to -retrieve the latest results. - -. Click image:images/time-filter-calendar.png[]. - -. In the *Refresh every* field, enter the refresh rate, then select the interval - from the dropdown. - -. Click *Start*. -+ -image::images/autorefresh-intervals.png[] - -To disable auto refresh, click *Stop*. - -If auto refresh is not enabled, click *Refresh* to manually refresh the search -results. diff --git a/docs/images/autorefresh-intervals.png b/docs/images/autorefresh-intervals.png index a79ae2f1f6c46c56caf71bd4e41539725f5cd8a8..49be46fefd4aad96d14fc0ab52801a4c283bfda9 100644 GIT binary patch literal 75039 zcmce8^M75<7j7FgY1r6k*v7Wn*tXT!wi?^Eot)UVZRf$JNX7;Q- zd(GOj)_R^f!7@_9@G#ggA3l757ZnkZ`|#o8+J_Hdk z91=#x*B?igp}B4FQbo8qU%r6-%)&;d6Biw81R4fGvT|}_peLYLRnb9NJ`z_1DCa1s zLPLN2YK`-ysu`2Gx~0|Q3XT#!Ze{7=9KAz%+=_;;m41ZpM&qi#X^ zUw^_J%7s4){<|9m>B|%|lJQdbKLL_v(`N34#uWnF;?q1E{%E z0Hj6|Ld}u*e}c^d!9;B$9Z_1uK`$5SkD}ssjA~l;CS9hFME`o!lge058Dbem&%Jf) zP1C)l?ud*Hepl%~ZePR*i1cLFHcp28Y~vv2j*Gqr6w;sO-)SJRc^$2FLfboEOFJq} z$LW#QvK&>IehY6x{4Nd+?Xwd2;n?qPl%fO;vR@M|;R_2x=zL3UjgjiIm<1jIZ3p~$ zi&yuhn-0@KaAZ;()d>$lU~jEC+oH;}ID|bNxib_E86h`0Vj*!%bZjZVg%OU#!H%CR z{@vh6>C^e_%;D7+*RQw;2nZK9H!=hHHgtRBpLr-4(A3g0o$1zeFRR8qYQl>_^LuM_spep&l>Fr6G)#`>Jhm^b zvLWZup{ue0DuU#{2)}W_FXdr`n=SUNj3H2=-53a{{i31iBgy4C4D>74 zGAfzgx(>DXC6e0`aJaUure3(N(#RP>>^(>Pt=~URC~gNC_5U!IEzT{(3#o>R@Lsxm+Z$RC;JQIdwz)}Ot zDlDzCJY6`LtO&Om&ez}3Z?=7;Xm&h>prkCd)ojhbjBx<1R0T~i`;pT9*BniZs*8J> z-ssEzNIMMV%7NzjJ{*3Wo{aX)))em+^z(svqn0dTeZ*WYt1v^qO<>cj~ zGcvSHn>5aIX#8^=j1q%&r_0pPD{FjyBr&;QJ6djhz%UZtm8Gn#?OkzyepSQeb_AQB zpC23>qK@M}k;7E&sG~mxijuTGIeprXp3|x+z+jdNF)Ephyf3rah2@RBGCOuAUuLLy zPnx>V)gN#)p3bW_y|Lyg-&4bS;au3{GIa@|*#RVD5v-i8dFhTV!!H6%!ncRDkg2ie z1y_XB2QpLQ%cy7A%sPPbAP*`L^lmvvf5rDT!2zSM?x5|DiBOqjbl3@oZmprlEYAB1 zE8OrCf!hvwFlYvj>QuE;i_0R}(cpz^Zy%7b7^JTsm3YDE8;*3^)V)+oBXHWJsliiD zbvk3ssnzcq$Jd?uV_mZ+Z%$sle?n&-eZ74(M|{AK*HZpn+N9)G@3BVM2iI$ln*RL$}&y?_)-Zg zhciaead8mZ+61-4BT|5csF3Q*$^PLn&hz zrSaGy?ECs|?e6}jN#`tbe+uoFn2V)dTwT>TS#$dWk3Cyxn7}3k57s`o(qI1OM)9C33mWG$*F#$RF&4qg zMQ-oJ;NWlj7cAURm7^yECwl@w7~YVY6gkH2xlxYH#2fxcmBDw|tmPKM;O0st9$qz@5AJt<0|^BQBD+>C!r+ zwKI@bR+;j&Fu$SZ3cAKJ&qx+7PB%N>!w5-?1QTi3YahzY1=*^n;uni+BSRml<;T^P zbsXOG;C{^!1ikBr2xHZW{LSnVi`>s=z0Bm*hrP@%4+d_w%Ib1U{_2=&Zy`^+36kdK zxa*$#9=II#s1BkBaxwBzf<#<9rwfw}PEK>34?7cWNw;rYyOW}}c6MEc$eUA$>!HCD z(w%SMf$^Lm6M~s}t@7#ZMkY|FQeM(5cZ|R>4obdUb{iT`FA^Yz3j1q31@=9Fr$m~an;SfOm&M7^ zvHzv%+~+tpIz6~yJ;T36c-8-VY^EYQw|1-9e1)>Zt2q$aoH!d8An;2GUCHAT-qrp* z#L+^XQdYaw0J6QX{}bcvRMYtQP}hjOc5hX--@&4{{A`iS>)8Dl9F)?sCrahY9JhuF zXQO_z*&sTt%$l#9k2Da!JG}_`?x}gbaLdMQzH;CpkyM@3?e;D+oVV|ZSwALE1mir6 zytR7s>xynh#YYt}*?-N}xVMLz)wLpzucKxAB<+dkO*xcYbgJhETYY#S<6~PL&#c>Pz70;C& zFDW;Uw7l|RN-omHZLKi!p>7L@l*rfco!hiVc)I;UohR7rejl_G3#u>Zu@R!L?sV5@ zzeGgFXgUr6pLt67EhNabpTkK$akwvigsg5nedszA@aWa%nMc@Z=ZQed#P3h|Owu|# zJvsW81Y`eLsYna@2+fRA0QOVkrJ99ByYNZb}(mtY=Ovgf} zd4iyFJ)dw#iI752Y5N84`Si%D5gO8U03{To!@g6uu+axqv!RHdMS80bP;W4n1FN%v zm5O41=-U?`;wT^l_j7muX!Au8yQ$~?<(BpBZU4jKm7T3ENpuI|goJb2(dgMPFin!B zrR8W+{GbKx)DTY(x9!Td=XgFX=(eCHg4W#Cxnow#K7h1R4A4#K)=phTr88i4#XZ{& zP+cw5ImDRscy@d+T@3E~b3AtCMUgblJl>YYF|cN2h0bQ9ru`ku+5c%=Q&XeLVnwQq z2+7gTP7&iWLfdgG)k=BiaX+JbttYTV>2#YkQ}3)*(0C~aOSSd(VyUEZPV#ZBfpQC| z2=4WAiJ?^S1hdai`)$f-B8cQGAw&tn$9Ce!raL_=0K> zk0zy?)ya*9m$;#^aY4f1Yaxa~_d(w^Yc8-zF9%#+{HBm0@K+@9pU<;So9`eoK zf2{IrEk#pCZUW(rs-K1s8XFuWsn&1`Al6sb@S%S0h&+zjL>-Y2sN!Y9k3g+kjaNen96Z_owkHLz z5iRz1%82y6=!fjcnh8|$t`?P?7fp$b9)=G*q^ifIvHDA-PK1hQ(C>y=WG&0b3qSc9 zYgW1A0surJ<=9VNV;yZk{~2Mn^)vz0w8 zJzVc)&a@HA)E<|Vhp9)O=f~GNR(=4jgex}P&f3KtKHr?q#1f^iO$M(HnY-9wa~OPS ze|{4dh`H6!7#;6Q3wsEoWsb1B7>B`Uw$_@2o@RiGckniSbPSb{F zs9Z}X$sLryWXVBayn3((Pfkpz>%LXUjJ`c`6-3i9SDpqLiFy;9Pkmr9pehoWcrONj zoebg1N^5+-&*i1rzKJ)ubt4GRVHeLetml~#1?rArE*GE|z?%p8dujC=9);kpPQ#v` z!u4-mkc3&+3^>{%MH-*_>*py#4H=H*wD|jas>|FLWG4C@i7WZf@S5uwL{+?6Q-S%Z zyKMQlw;!qQZ|j6N-7^U`L^Xn)S5_8+-$|Szup(P|W&oq!>PYVa5lm+cF2OBws;nf< zYNXvej8##5mvmVP3QGhC+CS(NJ@lDi| z(bvl-?1gHUrIH#`G=@J$FFx1{+vnScq7*SW>EywgHzmu$9`**uJ~%88K$Dizk&>b* zR|;i0o~jMQ7=GL;+Ioad*zTg=ne^?A@i*anZx{1!qDTRn7}uef5S<43@&T@Giq~ksCFs!zmHJboThRW@jU*FA+;an~}RY!Yz zDvNCKJa1d7qe4Sgph=fa8tV<3BDOt14%Y z1vqIQQ9;YV8La81^^pH#1J^VVSvB>UwtuJ8i)LqOuJD@-3(EQ0jD%H{?5dOn3;@_W zfeIUfV@udz0dqTNyB*EMY}_b?b5se0e@Hf#a&cv{9T+|R!6t3!bnDwAsB=n8^j(FXH${a$e3}ftQBAv)l@tEQD5Ud zJt_2(1skIiiBslne={~g-}K%*Q%-aQbU#B#w$h)(`WXvUc-t8Olp(u-`kQb5=k3~l zkvv)fcc<#P1ge2NJhwJUV7ZR+ycG%kQD%wHr1vo+j_*GFNy0Q!>v3hxr)a zsjX}>-LD+axxFn`yog4cGmpT3$?Av3mbYwpu1J99TKDMNWira^4B{F?%wG^a>RpD9!nkPBCd9bB>n%DqscSWwlyv1*POk;M%t5sm&7|wfg^Mb_bs? z+Dg5o4sf2~jomH5r{iVujyAoRAdKeTMv_biqc?MokTnzX|mMj5F90-4@>X^?kF0KcBEWES*d>R}l)U`csCRlqJp&&a}L zVAmdaA2GB=LZpBeB zH#l^>avFFhzNHcKa2UR?#E*hhiY`qyYS>1f+@7MI`YmDKGcWuo*7B%fzGkC$IgQ+7 zD7(^X*n-VcWBWL;OaTFrHl;0KPihcjmQ20*bbGWtM2;`(#p(Daw&S(YZxiB-&sCH; zJVGw9BK_mXCv65shRyR089_3y_g4;w`0ubWWjqM8+nu@VZLTs*_TTo#HG@9SdaYD@ ztoR~3vqNznf``DMr=G!ntjTKEs2^qrB%xgIc2>m}j?3)u^Ol7rGKS(JD~Tgpw|iO? zS2liZifr1r&!*313l+0}mHVK?W-80X1ba#PH2&I!$jc=5ibsst8!O%Bt?l`WhjP^h zzFdFx9pX115M}diwG{Q3@h(&!bGaL~a&;+)qEA}c6F+7@3@j6Hz&;Y)o>FzS9D|JG z%GK3BRV6S>EIEz8eW&mYDt~YtS&mXd4_L#`{K% zIyR_caBn*mz|m65{%tsRcv*64>V$q)+QB@)F1ol;0z3cq@XeD3ojvY-0fVEbBVk>? zG10dbzyN>^%LO@?(C)R?gAV0l)Y7?_=jr!X93sD;xn?t6%w!*^he$`WmswL)oXZb& zUnSm>ba=1|P&Mk;^>()7lB%UMP(s0==;z2sO10KFD6Z~so7kEVwh6UAUs(ug~(1 z#0nZ$sGRMKS=2kgLM^=GxSo;fZLnG62k{MvXkfD%pMs*~T@oWa|TS2yich#kxjXqj9JG53D z4Yen8a6d-ILvJXFX;WjESclh8vFKsOhMxeQ+w6Gwn>2KP+iM433GsR0jn(H#UNi3E zaRzg?RGIkDCsUkX*;MCb5vs~JCwU1tq?eiz9dWtyUKp@*(KlBss*e_RJJl`yt;7X` zN41_$a-rz9#daj(*-AC3P9U5Q&AAW#tF-)($G|FP@z!AkdyUcLm;5H@y`~}4mb^TJ zu;zDsw6;+UNt@u~VXuJTk@=&}kw-ls{m?SQ+b)-yo|(LQA@vS}o$d8nSM5c$@WAG3 zla2(rcxGfF&RAZb20%Ixk(qv@H5K}qFsx4WA89V8gjfgAL>#U>TX?>ayMGRjp(uQP>z zF)cQXlB2Xh40w1#NSblzK52%>FA)fXN4c;In~)-ZbA)P)o*N;IDY{Pb%i$W7v}UJ) zcba)vsWcIh3DxW0NU#nQ(#B1V0y7fkOp)N{RhJj#)cLp-j6FS6S2|0`OUw$EmY!}b zQIg4V7Bu*c`4aNJwBC4KLh!TjB-=uN_-p(@bt(x7iNdRDROsx)0u(&X)1+YDdC%r; zG853Cvv_8Z%!yLHkLx9Xb}$B+^^-FdRpLm6az$i9=7iu+J!IdaGp>k6gfXc;A3<{$ z3^c*=mun_bC`8~Ej|oj6gh_&wdudAjhD;<27f|A7(W#l6qh z;B3DVG{=ejOe%TedL86m4Hk&HZ`IHoMV(mMsq50Kc; zv5@05N4BA7$2b#{;&5`aRrokZb9;eb7bFEkqP^*$(aEH^WK_Dc{dvHxiw-Pa$ zi#|@O9SayTZSexpG%JuZJ7Z$@_I^cg>dzw+u)pu^=wEIt)Z$vG*TuZ)2CSy+7&NA< z$#nBBv#c6ZP*6}+J@yrsk5I^$X6m*%V=ZK?t)zvVNv{MdIZnN_hWGbZX;@%Y?l;e5 zQ)&gc)B@New`|KZTGL{;INmhv+mu+f$VLB@R>k?;+A-%RFWwBPb;11|gk~_tOFQ=8 z)90fTFKgmPW@2>Nml@{n#avXlm~H0j88r%kv`<5QW;4Q99P5Fu0rI*jjb;w>^>a*e zoB_7MGz9jT;SR1(D<67g%!tw*vl7wD7q@xKjXA|jW+50xGLL|{ooJag+llKz2TJZ1 z28e$VB5O|MZ;1yCm=W`0U*+GalBa`F)ig7D%VBt7DoUiu9NAkJf#0tRu7eKNnK+hY z{(wq^5McsYt2rajl%_0+r3jyXIW=7=%a9?h!GKZr!Tf3e&DZpKb+R#IIP4qEJ(8%< zAJHFRNHLCY2#H^@wp)(+pJ*=LJIK<3+fL5vucXKk1Ra&GeUF8K{X3EaDPfMf=0w`8 z`d{58cs@8Vc??_}7GTglhKf&|k<>H~rPIvu!=L}&fJUyvhBOeaP^U0eE}{!e<`w}b z{oCaI1vL8b^gxV2Q=Me)Gi>%EV+)h&NF6 zvCJcV>&pLJv4dh_Yeo^b-)!$tPn%S_sPY_b`w#q5AO$O{Df3AXBp<6R5(99u#}-6} ziHrH+d@jdpligP{M@Bwi?RX7L8$+uzV{U3^ZWGaT+t>9!6@ftq`)ddpu#cpzvh=}d zg^r&%_ZsLg%+k2laJP)Da_4uW@^R2&C1}Yfh>;4E&#-nYiz-PUmncoWzKeouEuo6W(LBQ4*8${Spi1eKt+iYeLx>ID(&CYDAsI!;hy~qNV?@ z31Yle#ncLDXla!VAFKPwA;+YTdbKk%-Jd}8&mx-(^X5@&GR}t;P^PVA zB?hGM9f$5gZ{5mf`}Jq52ez;{(oi!qXkp_{7=8*ve#pOMkO}`paXc;s6%<+VxXTOiNjOS#1ZxesqgH)z$<8!87 z+ODG?vL>?F_&0^0SuYSwmvcPu^(2Q>Dq`BB3=H3*LKdzgUzl4vskHZC^6EE3f9M`j zK9(OcGV&T9b*MB7iqm}2f@MtM4i2gviLV1*ft`48p>LCjgrNFt=Ucf)hvRFc%YxyV@Xr6TSW zm8?r~G>R+#IR!tM`6OVxzqh|D4Q(;nQv~^czD5W^5%A&I+*Iw;0qI@eM2akZOT65_ z7q;pNabwjuQ+*)z|GG>3;1gItjWZ

#Kwif6#A@PyenY1VGIKS%$3t1R#(d`+q3< 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 literal 84366 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π(Lj+>R9xI>sd8gIjjkz|LfNL3d~(831myK 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%0AtS_GNhr705m5EZ!XRQGgsfIqi(h8bI++Hk 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 U`d8wdFP}eAK`DU>J{{lx19unh&j0`b diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 2547b38a22616..7bac80237a26e 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -21,6 +21,7 @@ image::images/Discover-Start.png[Discover] [float] +[[select-pattern]] === Set up your index pattern The first thing to do in *Discover* is to select an <>, which From fb30a822b37b0b983a0dd36247bc5f7f56207e69 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Mon, 4 May 2020 14:08:50 -0400 Subject: [PATCH 04/22] Add 2 flaky retries to es snapshot jobs (#64553) --- .ci/es-snapshots/Jenkinsfile_verify_es | 1 + vars/prChanges.groovy | 1 + 2 files changed, 2 insertions(+) diff --git a/.ci/es-snapshots/Jenkinsfile_verify_es b/.ci/es-snapshots/Jenkinsfile_verify_es index ade79f27e10e9..2655ca1b48c18 100644 --- a/.ci/es-snapshots/Jenkinsfile_verify_es +++ b/.ci/es-snapshots/Jenkinsfile_verify_es @@ -21,6 +21,7 @@ def SNAPSHOT_MANIFEST = "https://storage.googleapis.com/kibana-ci-es-snapshots-d kibanaPipeline(timeoutMinutes: 120) { catchErrors { + retryable.enable(2) withEnv(["ES_SNAPSHOT_MANIFEST=${SNAPSHOT_MANIFEST}"]) { parallel([ 'kibana-intake-agent': workers.intake('kibana-intake', './test/scripts/jenkins_unit.sh'), diff --git a/vars/prChanges.groovy b/vars/prChanges.groovy index d7f46ee7be23e..4b9b20a945f76 100644 --- a/vars/prChanges.groovy +++ b/vars/prChanges.groovy @@ -7,6 +7,7 @@ def getSkippablePaths() { /^docs\//, /^rfcs\//, /^.ci\/.+\.yml$/, + /^.ci\/es-snapshots\//, /^\.github\//, /\.md$/, ] From 47887544198f9c35ca625d60094e115cc554f0a6 Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Mon, 4 May 2020 14:23:35 -0400 Subject: [PATCH 05/22] [Alerting] only show trial upgrade when running with basic license (#64865) resolves https://github.com/elastic/kibana/issues/64245 Prior to this PR, the "Upgrade your license" banner in the connectors list was displayed for gold licenses because the Service Now action requires platinum, and the check only looked for any actions disabled by license. Rather than display a different message for gold users, this PR changes the banner display logic to check for any actions disabled by license that also have a minimum required license of gold. That means gold+ users won't see the message, even for actions with a minimum required license of platinum+. Another perk of the gold license! This will continue to display the banner for basic users, but will no longer display it for gold users. It also continues to not display it for trial, platinum and enterprise users. --- .../action_type_menu.tsx | 16 ++-- .../connector_add_flyout.test.tsx | 96 ++++++++++++++++++- .../connector_add_flyout.tsx | 6 +- 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx index 91ecfb2fa8ded..3b2e34e8f29c8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx @@ -16,13 +16,13 @@ import { checkActionTypeEnabled } from '../../lib/check_action_type_enabled'; interface Props { onActionTypeChange: (actionType: ActionType) => void; actionTypes?: ActionType[]; - setHasActionsDisabledByLicense?: (value: boolean) => void; + setHasActionsUpgradeableByTrial?: (value: boolean) => void; } export const ActionTypeMenu = ({ onActionTypeChange, actionTypes, - setHasActionsDisabledByLicense, + setHasActionsUpgradeableByTrial, }: Props) => { const { http, toastNotifications, actionTypeRegistry } = useActionsConnectorsContext(); const [actionTypesIndex, setActionTypesIndex] = useState(undefined); @@ -36,11 +36,15 @@ export const ActionTypeMenu = ({ index[actionTypeItem.id] = actionTypeItem; } setActionTypesIndex(index); - if (setHasActionsDisabledByLicense) { - const hasActionsDisabledByLicense = availableActionTypes.some( - action => !index[action.id].enabledInLicense + // determine if there are actions disabled by license that that + // would be enabled by upgrading to gold or trial + if (setHasActionsUpgradeableByTrial) { + const hasActionsUpgradeableByTrial = availableActionTypes.some( + action => + !index[action.id].enabledInLicense && + index[action.id].minimumLicenseRequired === 'gold' ); - setHasActionsDisabledByLicense(hasActionsDisabledByLicense); + setHasActionsUpgradeableByTrial(hasActionsUpgradeableByTrial); } } catch (e) { if (toastNotifications) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx index fc10b150ca9d9..decdc7b99d69c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx @@ -78,7 +78,7 @@ describe('connector_add_flyout', () => { expect(wrapper.find(`[data-test-subj="${actionType.id}-card"]`).exists()).toBeTruthy(); }); - it('renders banner with subscription links when features are disbaled due to licensing ', () => { + it('renders banner with subscription links when gold features are disabled due to licensing ', () => { const actionType = createActionType(); const disabledActionType = createActionType(); @@ -136,6 +136,100 @@ describe('connector_add_flyout', () => { `"https://www.elastic.co/subscriptions"` ); }); + + it('does not render banner with subscription links when only platinum features are disabled due to licensing ', () => { + const actionType = createActionType(); + const disabledActionType = createActionType(); + + actionTypeRegistry.get.mockReturnValueOnce(actionType); + actionTypeRegistry.has.mockReturnValue(true); + + const wrapper = mountWithIntl( + { + return new Promise(() => {}); + }, + }} + > + {}} + actionTypes={[ + { + id: actionType.id, + enabled: true, + name: 'Test', + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'basic', + }, + { + id: disabledActionType.id, + enabled: true, + name: 'Test', + enabledInConfig: true, + enabledInLicense: false, + minimumLicenseRequired: 'platinum', + }, + ]} + /> + + ); + const callout = wrapper.find('UpgradeYourLicenseCallOut'); + expect(callout).toHaveLength(0); + }); + + it('does not render banner with subscription links when only enterprise features are disabled due to licensing ', () => { + const actionType = createActionType(); + const disabledActionType = createActionType(); + + actionTypeRegistry.get.mockReturnValueOnce(actionType); + actionTypeRegistry.has.mockReturnValue(true); + + const wrapper = mountWithIntl( + { + return new Promise(() => {}); + }, + }} + > + {}} + actionTypes={[ + { + id: actionType.id, + enabled: true, + name: 'Test', + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'basic', + }, + { + id: disabledActionType.id, + enabled: true, + name: 'Test', + enabledInConfig: true, + enabledInLicense: false, + minimumLicenseRequired: 'enterprise', + }, + ]} + /> + + ); + const callout = wrapper.find('UpgradeYourLicenseCallOut'); + expect(callout).toHaveLength(0); + }); }); let count = 0; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx index 834a15f072f96..25c19a46fe86b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -54,7 +54,7 @@ export const ConnectorAddFlyout = ({ reloadConnectors, } = useActionsConnectorsContext(); const [actionType, setActionType] = useState(undefined); - const [hasActionsDisabledByLicense, setHasActionsDisabledByLicense] = useState(false); + const [hasActionsUpgradeableByTrial, setHasActionsUpgradeableByTrial] = useState(false); // hooks const initialConnector = { @@ -96,7 +96,7 @@ export const ConnectorAddFlyout = ({ ); } else { @@ -219,7 +219,7 @@ export const ConnectorAddFlyout = ({ ) : ( From 33b2b5c92c9fb77ef3d8723cce27b9149bc3b7fa Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 4 May 2020 19:30:36 +0100 Subject: [PATCH 06/22] [ML] Enabling mml estimation in data recognizer module setup (#64900) * [ML] Enabling mml estimation in data recognizer module setup * small refactor * adding functional tests * increasing uptime test timeout * tiny refactor * checking for default setting * testng flakey uptime test * catching erros in mml estimation * lowering timeout * ensuing data is present for ML tests * adding await Co-authored-by: Elastic Machine --- .../jobs/new_job/recognize/page.tsx | 1 - .../models/data_recognizer/data_recognizer.ts | 106 ++++++++++-------- .../apis/ml/modules/setup_module.ts | 74 +++++++++++- .../test/functional/apps/uptime/ml_anomaly.ts | 3 + .../functional/services/uptime/ml_anomaly.ts | 2 +- 5 files changed, 136 insertions(+), 50 deletions(-) diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx index 9b76b9be9bf45..50c35ec426acb 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx @@ -172,7 +172,6 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { startDatafeed: startDatafeedAfterSave, ...(jobOverridesPayload !== null ? { jobOverrides: jobOverridesPayload } : {}), ...resultTimeRange, - estimateModelMemory: false, }); const { datafeeds: datafeedsResponse, jobs: jobsResponse, kibana: kibanaResponse } = response; diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 40b2a524151b3..92ab7739dbcfb 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -110,7 +110,7 @@ export class DataRecognizer { /** * List of the module jobs that require model memory estimation */ - jobsForModelMemoryEstimation: ModuleJob[] = []; + jobsForModelMemoryEstimation: Array<{ job: ModuleJob; query: any }> = []; constructor( private callAsCurrentUser: APICaller, @@ -374,7 +374,7 @@ export class DataRecognizer { end?: number, jobOverrides?: JobOverride | JobOverride[], datafeedOverrides?: DatafeedOverride | DatafeedOverride[], - estimateModelMemory?: boolean + estimateModelMemory: boolean = true ) { // load the config from disk const moduleConfig = await this.getModule(moduleId, jobPrefix); @@ -416,7 +416,10 @@ export class DataRecognizer { savedObjects: [] as KibanaObjectResponse[], }; - this.jobsForModelMemoryEstimation = moduleConfig.jobs; + this.jobsForModelMemoryEstimation = moduleConfig.jobs.map(job => ({ + job, + query: moduleConfig.datafeeds.find(d => d.config.job_id === job.id)?.config.query ?? null, + })); this.applyJobConfigOverrides(moduleConfig, jobOverrides, jobPrefix); this.applyDatafeedConfigOverrides(moduleConfig, datafeedOverrides, jobPrefix); @@ -958,7 +961,7 @@ export class DataRecognizer { */ async updateModelMemoryLimits( moduleConfig: Module, - estimateMML: boolean = false, + estimateMML: boolean, start?: number, end?: number ) { @@ -967,53 +970,57 @@ export class DataRecognizer { } if (estimateMML && this.jobsForModelMemoryEstimation.length > 0) { - const calculateModelMemoryLimit = calculateModelMemoryLimitProvider(this.callAsCurrentUser); - const query = moduleConfig.query ?? null; - - // Checks if all jobs in the module have the same time field configured - const isSameTimeFields = this.jobsForModelMemoryEstimation.every( - job => - job.config.data_description.time_field === - this.jobsForModelMemoryEstimation[0].config.data_description.time_field - ); + try { + const calculateModelMemoryLimit = calculateModelMemoryLimitProvider(this.callAsCurrentUser); - if (isSameTimeFields && (start === undefined || end === undefined)) { - // In case of time range is not provided and the time field is the same - // set the fallback range for all jobs - const { start: fallbackStart, end: fallbackEnd } = await this.getFallbackTimeRange( - this.jobsForModelMemoryEstimation[0].config.data_description.time_field, - query + // Checks if all jobs in the module have the same time field configured + const firstJobTimeField = this.jobsForModelMemoryEstimation[0].job.config.data_description + .time_field; + const isSameTimeFields = this.jobsForModelMemoryEstimation.every( + ({ job }) => job.config.data_description.time_field === firstJobTimeField ); - start = fallbackStart; - end = fallbackEnd; - } - for (const job of this.jobsForModelMemoryEstimation) { - let earliestMs = start; - let latestMs = end; - if (earliestMs === undefined || latestMs === undefined) { - const timeFieldRange = await this.getFallbackTimeRange( + if (isSameTimeFields && (start === undefined || end === undefined)) { + // In case of time range is not provided and the time field is the same + // set the fallback range for all jobs + // as there may not be a common query, we use a match_all + const { + start: fallbackStart, + end: fallbackEnd, + } = await this.getFallbackTimeRange(firstJobTimeField, { match_all: {} }); + start = fallbackStart; + end = fallbackEnd; + } + + for (const { job, query } of this.jobsForModelMemoryEstimation) { + let earliestMs = start; + let latestMs = end; + if (earliestMs === undefined || latestMs === undefined) { + const timeFieldRange = await this.getFallbackTimeRange( + job.config.data_description.time_field, + query + ); + earliestMs = timeFieldRange.start; + latestMs = timeFieldRange.end; + } + + const { modelMemoryLimit } = await calculateModelMemoryLimit( + job.config.analysis_config, + this.indexPatternName, + query, job.config.data_description.time_field, - query + earliestMs, + latestMs ); - earliestMs = timeFieldRange.start; - latestMs = timeFieldRange.end; - } - const { modelMemoryLimit } = await calculateModelMemoryLimit( - job.config.analysis_config, - this.indexPatternName, - query, - job.config.data_description.time_field, - earliestMs, - latestMs - ); + if (!job.config.analysis_limits) { + job.config.analysis_limits = {} as AnalysisLimits; + } - if (!job.config.analysis_limits) { - job.config.analysis_limits = {} as AnalysisLimits; + job.config.analysis_limits.model_memory_limit = modelMemoryLimit; } - - job.config.analysis_limits.model_memory_limit = modelMemoryLimit; + } catch (error) { + mlLog.warn(`Data recognizer could not estimate model memory limit ${error}`); } } @@ -1098,10 +1105,15 @@ export class DataRecognizer { if (generalOverrides.some(override => !!override.analysis_limits?.model_memory_limit)) { this.jobsForModelMemoryEstimation = []; } else { - this.jobsForModelMemoryEstimation = moduleConfig.jobs.filter(job => { - const override = jobSpecificOverrides.find(o => `${jobPrefix}${o.job_id}` === job.id); - return override?.analysis_limits?.model_memory_limit === undefined; - }); + this.jobsForModelMemoryEstimation = moduleConfig.jobs + .filter(job => { + const override = jobSpecificOverrides.find(o => `${jobPrefix}${o.job_id}` === job.id); + return override?.analysis_limits?.model_memory_limit === undefined; + }) + .map(job => ({ + job, + query: moduleConfig.datafeeds.find(d => d.config.job_id === job.id)?.config.query || null, + })); } function processArrayValues(source: any, update: any) { diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index c42fc95c1bc7f..39c87a91f0ccf 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { JOB_STATE, DATAFEED_STATE } from '../../../../../plugins/ml/common/constants/states'; +import { Job } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; import { USER } from '../../../../functional/services/machine_learning/security_common'; const COMMON_HEADERS = { @@ -23,7 +24,8 @@ export default ({ getService }: FtrProviderContext) => { const testDataListPositive = [ { - testTitleSuffix: 'for sample logs dataset with prefix and startDatafeed false', + testTitleSuffix: + 'for sample logs dataset with prefix, startDatafeed false and estimateModelMemory false', sourceDataArchive: 'ml/sample_logs', indexPattern: { name: 'kibana_sample_data_logs', timeField: '@timestamp' }, module: 'sample_data_weblogs', @@ -32,6 +34,7 @@ export default ({ getService }: FtrProviderContext) => { prefix: 'pf1_', indexPatternName: 'kibana_sample_data_logs', startDatafeed: false, + estimateModelMemory: false, }, expected: { responseCode: 200, @@ -40,16 +43,55 @@ export default ({ getService }: FtrProviderContext) => { jobId: 'pf1_low_request_rate', jobState: JOB_STATE.CLOSED, datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '10mb', }, { jobId: 'pf1_response_code_rates', jobState: JOB_STATE.CLOSED, datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '10mb', }, { jobId: 'pf1_url_scanning', jobState: JOB_STATE.CLOSED, datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '10mb', + }, + ], + }, + }, + { + testTitleSuffix: + 'for sample logs dataset with prefix, startDatafeed false and estimateModelMemory true', + sourceDataArchive: 'ml/sample_logs', + indexPattern: { name: 'kibana_sample_data_logs', timeField: '@timestamp' }, + module: 'sample_data_weblogs', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf2_', + indexPatternName: 'kibana_sample_data_logs', + startDatafeed: false, + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf2_low_request_rate', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf2_response_code_rates', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf2_url_scanning', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '16mb', }, ], }, @@ -197,6 +239,36 @@ export default ({ getService }: FtrProviderContext) => { await ml.api.waitForJobState(job.jobId, job.jobState); await ml.api.waitForDatafeedState(datafeedId, job.datafeedState); } + + // compare model memory limits for created jobs + const expectedModelMemoryLimits = testData.expected.jobs + .map(j => ({ + id: j.jobId, + modelMemoryLimit: j.modelMemoryLimit, + })) + .sort(compareById); + + const { + body: { jobs }, + }: { + body: { + jobs: Job[]; + }; + } = await ml.api.getAnomalyDetectionJob(testData.expected.jobs.map(j => j.jobId).join()); + + const actualModelMemoryLimits = jobs + .map(j => ({ + id: j.job_id, + modelMemoryLimit: j.analysis_limits!.model_memory_limit, + })) + .sort(compareById); + + expect(actualModelMemoryLimits).to.eql( + expectedModelMemoryLimits, + `Expected job model memory limits '${JSON.stringify( + expectedModelMemoryLimits + )}' (got '${JSON.stringify(actualModelMemoryLimits)}')` + ); }); // TODO in future updates: add creation validations for created saved objects diff --git a/x-pack/test/functional/apps/uptime/ml_anomaly.ts b/x-pack/test/functional/apps/uptime/ml_anomaly.ts index bcd165cc1afb7..c9668bed432b5 100644 --- a/x-pack/test/functional/apps/uptime/ml_anomaly.ts +++ b/x-pack/test/functional/apps/uptime/ml_anomaly.ts @@ -9,6 +9,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { const uptime = getService('uptime'); const log = getService('log'); + const esArchiver = getService('esArchiver'); + const archive = 'uptime/full_heartbeat'; describe('uptime ml anomaly', function() { this.tags(['skipFirefox']); @@ -17,6 +19,7 @@ export default ({ getService }: FtrProviderContext) => { const monitorId = '0000-intermittent'; before(async () => { + await esArchiver.loadIfNeeded(archive); if (!(await uptime.navigation.checkIfOnMonitorPage(monitorId))) { await uptime.navigation.loadDataAndGoToMonitorPage(dateStart, dateEnd, monitorId); } diff --git a/x-pack/test/functional/services/uptime/ml_anomaly.ts b/x-pack/test/functional/services/uptime/ml_anomaly.ts index e15f47ddd9709..a5f138b7a5716 100644 --- a/x-pack/test/functional/services/uptime/ml_anomaly.ts +++ b/x-pack/test/functional/services/uptime/ml_anomaly.ts @@ -32,7 +32,7 @@ export function UptimeMLAnomalyProvider({ getService }: FtrProviderContext) { async createMLJob() { await testSubjects.click('uptimeMLCreateJobBtn'); - return retry.tryForTime(10000, async () => { + return retry.tryForTime(30000, async () => { await testSubjects.existOrFail('uptimeMLJobSuccessfullyCreated'); log.info('Job successfully created'); }); From 63121fb47e84dc17cdb6fb03875cc597dd003364 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 4 May 2020 13:40:03 -0500 Subject: [PATCH 07/22] Service map anomaly indicators (#64718) Get an aggregation of the anomaly scores and show style the ring around the node icon. --- .../apm/common/ml_job_constants.test.ts | 49 ++++++++++++- x-pack/plugins/apm/common/ml_job_constants.ts | 30 ++++++++ .../app/ServiceMap/Cytoscape.stories.tsx | 41 +++++++++++ .../app/ServiceMap/cytoscapeOptions.ts | 54 ++++++++++++-- .../server/lib/service_map/get_service_map.ts | 49 +++++++++++-- .../server/lib/service_map/ml_helpers.test.ts | 70 +++++++++++++++++++ .../apm/server/lib/service_map/ml_helpers.ts | 47 +++++++++++++ 7 files changed, 329 insertions(+), 11 deletions(-) create mode 100644 x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts create mode 100644 x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts diff --git a/x-pack/plugins/apm/common/ml_job_constants.test.ts b/x-pack/plugins/apm/common/ml_job_constants.test.ts index 2aa50a305f7c8..4941925939afb 100644 --- a/x-pack/plugins/apm/common/ml_job_constants.test.ts +++ b/x-pack/plugins/apm/common/ml_job_constants.test.ts @@ -4,7 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getMlIndex, getMlJobId, getMlPrefix } from './ml_job_constants'; +import { + getMlIndex, + getMlJobId, + getMlPrefix, + getMlJobServiceName, + getSeverity, + severity +} from './ml_job_constants'; describe('ml_job_constants', () => { it('getMlPrefix', () => { @@ -38,4 +45,44 @@ describe('ml_job_constants', () => { '.ml-anomalies-myservicename-mytransactiontype-high_mean_response_time' ); }); + + describe('getMlJobServiceName', () => { + it('extracts the service name from a job id', () => { + expect( + getMlJobServiceName('opbeans-node-request-high_mean_response_time') + ).toEqual('opbeans-node'); + }); + }); + + describe('getSeverity', () => { + describe('when score is undefined', () => { + it('returns undefined', () => { + expect(getSeverity(undefined)).toEqual(undefined); + }); + }); + + describe('when score < 25', () => { + it('returns warning', () => { + expect(getSeverity(10)).toEqual(severity.warning); + }); + }); + + describe('when score is between 25 and 50', () => { + it('returns minor', () => { + expect(getSeverity(40)).toEqual(severity.minor); + }); + }); + + describe('when score is between 50 and 75', () => { + it('returns major', () => { + expect(getSeverity(60)).toEqual(severity.major); + }); + }); + + describe('when score is 75 or more', () => { + it('returns critical', () => { + expect(getSeverity(100)).toEqual(severity.critical); + }); + }); + }); }); diff --git a/x-pack/plugins/apm/common/ml_job_constants.ts b/x-pack/plugins/apm/common/ml_job_constants.ts index 01f5762e2dc4b..afe0550721716 100644 --- a/x-pack/plugins/apm/common/ml_job_constants.ts +++ b/x-pack/plugins/apm/common/ml_job_constants.ts @@ -4,6 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +export enum severity { + critical = 'critical', + major = 'major', + minor = 'minor', + warning = 'warning' +} + export function getMlPrefix(serviceName: string, transactionType?: string) { const maybeTransactionType = transactionType ? `${transactionType}-` : ''; return encodeForMlApi(`${serviceName}-${maybeTransactionType}`); @@ -13,6 +20,13 @@ export function getMlJobId(serviceName: string, transactionType?: string) { return `${getMlPrefix(serviceName, transactionType)}high_mean_response_time`; } +export function getMlJobServiceName(jobId: string) { + return jobId + .split('-') + .slice(0, -2) + .join('-'); +} + export function getMlIndex(serviceName: string, transactionType?: string) { return `.ml-anomalies-${getMlJobId(serviceName, transactionType)}`; } @@ -20,3 +34,19 @@ export function getMlIndex(serviceName: string, transactionType?: string) { export function encodeForMlApi(value: string) { return value.replace(/\s+/g, '_').toLowerCase(); } + +export function getSeverity(score?: number) { + if (typeof score !== 'number') { + return undefined; + } else if (score < 25) { + return severity.warning; + } else if (score >= 25 && score < 50) { + return severity.minor; + } else if (score >= 50 && score < 75) { + return severity.major; + } else if (score >= 75) { + return severity.critical; + } else { + return undefined; + } +} diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx index de775dbc8162a..340c299f52c0b 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx @@ -304,3 +304,44 @@ storiesOf('app/ServiceMap/Cytoscape', module) } ) .addParameters({ options: { showPanel: false } }); + +storiesOf('app/ServiceMap/Cytoscape', module).add( + 'node severity', + () => { + const elements = [ + { data: { id: 'undefined', 'service.name': 'severity: undefined' } }, + { + data: { + id: 'warning', + 'service.name': 'severity: warning', + severity: 'warning' + } + }, + { + data: { + id: 'minor', + 'service.name': 'severity: minor', + severity: 'minor' + } + }, + { + data: { + id: 'major', + 'service.name': 'severity: major', + severity: 'major' + } + }, + { + data: { + id: 'critical', + 'service.name': 'severity: critical', + severity: 'critical' + } + } + ]; + return ; + }, + { + info: { propTables: false, source: false } + } +); diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index 554f84f0ad236..3bb4319d0722d 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -10,8 +10,52 @@ import { SERVICE_NAME, SPAN_DESTINATION_SERVICE_RESOURCE } from '../../../../common/elasticsearch_fieldnames'; +import { severity } from '../../../../common/ml_job_constants'; import { defaultIcon, iconForNode } from './icons'; +const getBorderColor = (el: cytoscape.NodeSingular) => { + const nodeSeverity = el.data('severity'); + + switch (nodeSeverity) { + case severity.warning: + return theme.euiColorVis0; + case severity.minor || severity.major: + return theme.euiColorVis5; + case severity.critical: + return theme.euiColorVis9; + default: + if (el.hasClass('primary') || el.selected()) { + return theme.euiColorPrimary; + } else { + return theme.euiColorMediumShade; + } + } +}; + +const getBorderStyle: cytoscape.Css.MapperFunction< + cytoscape.NodeSingular, + cytoscape.Css.LineStyle +> = (el: cytoscape.NodeSingular) => { + const nodeSeverity = el.data('severity'); + if (nodeSeverity === severity.critical) { + return 'double'; + } else { + return 'solid'; + } +}; + +const getBorderWidth = (el: cytoscape.NodeSingular) => { + const nodeSeverity = el.data('severity'); + + if (nodeSeverity === severity.minor || nodeSeverity === severity.major) { + return 4; + } else if (nodeSeverity === severity.critical) { + return 12; + } else { + return 2; + } +}; + // IE 11 does not properly load some SVGs or draw certain shapes. This causes // a runtime error and the map fails work at all. We would prefer to do some // kind of feature detection rather than browser detection, but some of these @@ -55,11 +99,9 @@ const style: cytoscape.Stylesheet[] = [ isService(el) ? '60%' : '40%', 'background-width': (el: cytoscape.NodeSingular) => isService(el) ? '60%' : '40%', - 'border-color': (el: cytoscape.NodeSingular) => - el.hasClass('primary') || el.selected() - ? theme.euiColorPrimary - : theme.euiColorMediumShade, - 'border-width': 2, + 'border-color': getBorderColor, + 'border-style': getBorderStyle, + 'border-width': getBorderWidth, color: (el: cytoscape.NodeSingular) => el.hasClass('primary') || el.selected() ? theme.euiColorPrimaryText @@ -149,7 +191,7 @@ const style: cytoscape.Stylesheet[] = [ { selector: 'node.hover', style: { - 'border-width': 2 + 'border-width': getBorderWidth } }, { diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts index 17b595385a84e..adb2c9b7cb084 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts @@ -17,6 +17,8 @@ import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { dedupeConnections } from './dedupe_connections'; import { getServiceMapFromTraceIds } from './get_service_map_from_trace_ids'; import { getTraceSampleIds } from './get_trace_sample_ids'; +import { addAnomaliesToServicesData } from './ml_helpers'; +import { getMlIndex } from '../../../common/ml_job_constants'; export interface IEnvOptions { setup: Setup & SetupTimeRange; @@ -137,19 +139,58 @@ async function getServicesData(options: IEnvOptions) { ); } +function getAnomaliesData(options: IEnvOptions) { + const { client } = options.setup; + + const params = { + index: getMlIndex('*'), + body: { + size: 0, + query: { + exists: { + field: 'bucket_span' + } + }, + aggs: { + jobs: { + terms: { + field: 'job_id', + size: 10 + }, + aggs: { + max_score: { + max: { + field: 'anomaly_score' + } + } + } + } + } + } + }; + + return client.search(params); +} + +export type AnomaliesResponse = PromiseReturnType; export type ConnectionsResponse = PromiseReturnType; export type ServicesResponse = PromiseReturnType; - export type ServiceMapAPIResponse = PromiseReturnType; export async function getServiceMap(options: IEnvOptions) { - const [connectionData, servicesData] = await Promise.all([ + const [connectionData, servicesData, anomaliesData] = await Promise.all([ getConnectionData(options), - getServicesData(options) + getServicesData(options), + getAnomaliesData(options) ]); + const servicesDataWithAnomalies = addAnomaliesToServicesData( + servicesData, + anomaliesData + ); + return dedupeConnections({ ...connectionData, - services: servicesData + services: servicesDataWithAnomalies }); } diff --git a/x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts b/x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts new file mode 100644 index 0000000000000..c6680ecd6375b --- /dev/null +++ b/x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AnomaliesResponse } from './get_service_map'; +import { addAnomaliesToServicesData } from './ml_helpers'; + +describe('addAnomaliesToServicesData', () => { + it('adds anomalies to services data', () => { + const servicesData = [ + { + 'service.name': 'opbeans-ruby', + 'agent.name': 'ruby', + 'service.environment': null, + 'service.framework.name': 'Ruby on Rails' + }, + { + 'service.name': 'opbeans-java', + 'agent.name': 'java', + 'service.environment': null, + 'service.framework.name': null + } + ]; + + const anomaliesResponse = { + aggregations: { + jobs: { + buckets: [ + { + key: 'opbeans-ruby-request-high_mean_response_time', + max_score: { value: 50 } + }, + { + key: 'opbeans-java-request-high_mean_response_time', + max_score: { value: 100 } + } + ] + } + } + }; + + const result = [ + { + 'service.name': 'opbeans-ruby', + 'agent.name': 'ruby', + 'service.environment': null, + 'service.framework.name': 'Ruby on Rails', + max_score: 50, + severity: 'major' + }, + { + 'service.name': 'opbeans-java', + 'agent.name': 'java', + 'service.environment': null, + 'service.framework.name': null, + max_score: 100, + severity: 'critical' + } + ]; + + expect( + addAnomaliesToServicesData( + servicesData, + (anomaliesResponse as unknown) as AnomaliesResponse + ) + ).toEqual(result); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts b/x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts new file mode 100644 index 0000000000000..26a964bfb4dd2 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; +import { + getMlJobServiceName, + getSeverity +} from '../../../common/ml_job_constants'; +import { AnomaliesResponse, ServicesResponse } from './get_service_map'; + +export function addAnomaliesToServicesData( + servicesData: ServicesResponse, + anomaliesResponse: AnomaliesResponse +) { + const anomaliesMap = ( + anomaliesResponse.aggregations?.jobs.buckets ?? [] + ).reduce<{ + [key: string]: { max_score?: number }; + }>((previousValue, currentValue) => { + const key = getMlJobServiceName(currentValue.key.toString()); + + return { + ...previousValue, + [key]: { + max_score: Math.max( + previousValue[key]?.max_score ?? 0, + currentValue.max_score.value ?? 0 + ) + } + }; + }, {}); + + const servicesDataWithAnomalies = servicesData.map(service => { + const score = anomaliesMap[service[SERVICE_NAME]]?.max_score; + + return { + ...service, + max_score: score, + severity: getSeverity(score) + }; + }); + + return servicesDataWithAnomalies; +} From 8813114e947652b4d0580d10a685f4c9fe4a0c2c Mon Sep 17 00:00:00 2001 From: Henry Harding Date: Mon, 4 May 2020 14:41:24 -0400 Subject: [PATCH 08/22] Update epm illustration (#64975) * update EPM header illustration Co-authored-by: Elastic Machine --- .../illustration_integrations_darkmode.svg | 1 + .../illustration_integrations_lightmode.svg | 1 + ...illustration_kibana_getting_started@2x.png | Bin 131132 -> 0 bytes .../sections/epm/components/package_card.tsx | 2 +- .../sections/epm/screens/home/header.tsx | 29 +++++++++++------- 5 files changed, 21 insertions(+), 12 deletions(-) create mode 100755 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_darkmode.svg create mode 100755 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_lightmode.svg delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_kibana_getting_started@2x.png diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_darkmode.svg b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_darkmode.svg new file mode 100755 index 0000000000000..b1f86be19a080 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_darkmode.svg @@ -0,0 +1 @@ +Kibana-integrations-darkmode \ No newline at end of file diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_lightmode.svg b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_lightmode.svg new file mode 100755 index 0000000000000..0cddcb0af6909 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_lightmode.svg @@ -0,0 +1 @@ +Kibana-integrations-lightmode \ No newline at end of file diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_kibana_getting_started@2x.png b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_kibana_getting_started@2x.png deleted file mode 100644 index cad64be0b6e36e79012970814c7018776bee5f73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131132 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ͤum)*z|9JCu4oQ+`+gia z>tkZE4h&~S*WS}EW1>S}W5utS1V`i$9kmF}hDdbeWvY$`Bq^9ZX?$7AYTbgwi1Evm z3cD2b=h74otypxaDnnn_3D?smqCnDXT^d-ey&#b>`pyv@69EnP^x;L2J;gfSFl(Fw z-;nmv;kP6{qn@!pmwv_xlJxw6(J_7o6x+_X0w}1qAAig*zf!qyO2QrJdYi8ed~Cm| zMs!OsI_VeaUxj!tx+F)AbVbtMTL zQJA`iikwR&Y@a`oo-SlcWUu`VCooF`g`ZxsGN%ptgFs+vulIoaz z>K$$puG8P$UW3e%X4l(uipzF;6{>|z@d{VefC#H6 zzibKMV(HTdUPqAO=DY6b_%=xln#VUNY((eV-se&JRS}+aB{Wct4YUkBX-uP9ZdT$8 z8{^?~#GBSe!D}<~wVbZ3WV6MjXXU$9uZ2xpId@*&or~8*mn4bX<0EMYz+bDA%gE~# zREfYoOy@^lRrD5m1xD$3d;`qNH|v_^8tWe{L~P;Ux?6*kmk#2VGG3hFRmaKT{KLjw z9G|$)Gvd*o8?TMqXJ~#*Qd7{Hb~>6xn)+)*uDG5_#98Wm#7=v4GO=nGyW2)U(%5yE zr;0~8HvQMvIyoV8xbi6MW6wMF)=LUJ`^%Udva(h_>MwN^<*RgIFJ@_0^Z?b9E;`vG zN!i?rakD|%(lB(H%J~e=;@t@oSz?pN4H5SN=Q0uQE^oXaIPtZ#vDl0y55E=N>nr0N zUVHo8SP!+Htv8DHp8Ta)SPc$G;XgicFBMYcwj_f^*%WylcZbGdIe8)&^VaE>8kK2L6PBKPFp+*Dmg8{oK*;mM8vl zzjgc~aF&MY-)`96?wQ~1+#u4LAevfv>j%sc;O6(Mh`1W}Q$RqjzS*o+hfuJ8^2*)5 z$z{{G0(&3#M;JPOqiOTIk)K&}UnW!d0Qak_8TsA!gK=+CjJk32?d;etuervjm&JLE zv))IKm-l#~km|Y`iJJ$U9u#!95ZAs#wIq&t)zQKj#ibad7m?3r?WC-^t-d02uVclL z(-}0J&_z%q!2n=97!lQe>is`7U1dDo@B81x#2Jn_n9iw#sZAS>>F)0C?shmBX4=Hq zw8`n_=uJ*d*EG}3|FiG!^?$Sn_QdV}T-Up<>y|B5;Ha;HC!7yT56ttxQewTz=bCJn zZ>)(kJcb!Wh|7q-@#HK=p;wpkt_$JflOr983v_+LfcCGl{iF1#PQsb^h~BQctPFou z;xZ^*4gqY0-O7FGeI&K#zan@XgU$}AWjObT{yUGbs>l_gg_&uS%d7QAT;EkaY2k~o zDy8x?pYuOyNBPc64{BtOoFtQZ+RrP>e5MfcQJ%y#h0ESA?kgLN6voeP0`Y_W3KbfH zOe2X8HYb0;=9eHO9eR|)5 zZ}YOrAAEW~lMgrV8yxo8VeCobRT!4|i^^~IxA)K`C(3CtF+X#a=E?qwte~7KiRfe@ zV&?P0z0^eyBU0vo66qF)kw%_e>?30X7QbmQqqsUG2}G`R|F&sdSX9R!ewIY}qOmq} zk?FtRbnF>M-#b;%nYI1jtVPztvI{UqDor`+9<9-$*8yqUQRX-fNx7Dp1&!PnlKld; z*UGU>OeNnNFTPyUn%x%#XXba@B{CdPC}=Q&>E0ovX5M(MeR`e2xGsrDz*o^^_CAzZ zv!?!Nfb9KbCv}HwZ+ltFnChR0UnMzW@lMqob5b9^urleBpM@0{oL4)m|3kdjO+J^8 z#_`9r%_JeSpNr7&5h~}r1o>bbrh0C7Z$nn`K z>xy#j*==@y37XW5>VQPn-?Ss+ugZ5eaPD1@a|aBA{_#K_(8Y7k7Ugjn8Y{AzP929PtE{QCrE}q3z z#`kDU8d@PWMZPLSKMWm!Z_^;lLgWUmo|fqvfK37$>&rvWY6c_jQTj2Jl|Qt6QM@!R zdi#E8^N!9N6Jp0r3sP@X#2RZ=(kbp1%#k(UJS_iAV>IjYOfkIT#jXSCQMH6^ zET#_2Z*u45xa)vC2H^L^>=tx0aX6bkj%L{nMW7$=I{6;_mVWRfjNpR-oZwF#LFFr1 zuxb}xhtq8#xUyYjX98B?RYMj5<*#`+vi#Ff(ta?}HzDXzi1DPg!2NBW|Lcwmjw&k` z50^ZdamtcRzN~Ayl%mtti0H?gp`>T}mIc7po3x@KjZ`MIY0k#cpQ+QkJ0>({b%adc zMQ>C_zeFTPTNm^5^_UUSg}`~#p)#RvujO;ri%D0#VsI@7Myzx{=YRgP|HDJGH3rw3 zxjis~QXAMsbYVEc9=yEzfE;mzemLjZ?$YDr1oj8{fRtYQcA?xXk{^QkSPr=oQ?YJxN2;A4fS%?boaj84N{4nh!yte;t+U?sZ zzfjUUyLFzG?g)5QS7OxcAOHGM`()N6v%ctKvJj6Xh>2-tpQ8P&Rq~eV=R~E^PVC!4 zQwJRkVi?K6C^f^3myGiC!*GkhMvE_RAd7bTT^#wq)vAbyfY%1#`3K&T`huj0%NFKZ zJIY_<{I#*62B?MfbA zw7g?Ul^s7zB-E%LW~*3c3BI;To{vNB~(t?1tXdwLVQpG`Esd??<=C zY*KBb`6v{VQza4FYEe@yGxbdDwIECgT6S=tH2*r#*RHi===wPzcUb24!{>dyaZTOoVrmGX4E9%l&4_S%@8w_lLVmb@!Y*c zU}{WdCn>7rDk0(guzKYnhyCMyc+ywOY&!R48|4TnakY^jM+%j|(!|Yvw_iV7i)h8= ztR&GmZ%yPgoewozBzN;72}4aN#@@+X75pN@Nf@ixC#>zv@GBbTpFdokwLE-^cPp^b zSc&R-bm42>P;lAzW*KrA4^gW5e9&Ic#7KEk2)wa!+NAj@T|{GBbX9+#&UT}Hmm~Ud zY0@F>e;P}~6F%9$>D;d{?GeduF(nelmu~+aslpzQjSe7*;vR;MTF2OT=R5}~``vMT z-!CbQro+k)#*c*gm18gZ-p|d5o`9uTIY_?S7mmmWEWdhz+CGUEMogjWG?=$>Ya~r{ z@UTplBDShi*M7h5$KvB~NjQN?Ahf|X-)RmDJN(uz48bkC)e`_s zxxW=k;J)M&dslIB9C?KIcbI*tY61ktxj6=i5dSGk#A}^)gqC=%o;Y5COIOHW zl#b=W4QhUH(J=o?-UIL7g7nI9@C2wgVJiSm4}(s9J$77z<9|8Bm8sor@)o`~SxFJl zhg-bN=RN=7dPuR$s63jy!$~zbk*AGYzIU2uDNiKw`sYa56HLv-4>Ft3)Z!YQ{Ig2K z`9NF$!My#W;EXJ3CcF5i)XqfU6jZ&;U)*LYFWRF!7-twbuKGg3v-I!U>`vm$hD;tuRWQprnwfp4Ayjp_t+Yujl+J#PBVW+qS*j}HEY zdp=i(Yz2cOwG41_ciIFh|WRBL=y3izclj$kG6*= z^U)cd;=`GA3HK-KDD9A16^ofPD&u)f%NI$YT1IZpx!%N1rB1IuaG`;RrD)BDNKn~b zD%+s^_hnn6zQL^${sZYJv=oe92PSzh8qnVYU=6}wLf0&=2dl)^0oRp>5=mnxZQZC- zuOcOKAsoZ8x*Wc5BndHS_3{`*6`oFl^|znSPkg3!Fby@-URt4PscMCV-By!L$Z-~xn6{b| zcEpjtk3XUUSLjrOqg*tjf->m-;l)iKyKGFQdRmM3=oeVL{6z*-%Da$-;Ek-Zv^|oc zv0Cl%`lcd3{E)|*`O^PU!D@nGQD(ZaG7Ch*QS}H*lBozAW(#oxC z8(w}IO>|%HrOd5-{YKGiIw7~qaZ?aiYODw*C>8arxHa8eI^_$U-8*ihIE;7DaNs>= zpM>-W`>1a3C)Oz`a)}vAZk5CrjuA*Uwfr<)Z~gDe<`H}NwB=7NFPX=sAE(AX994CX z<}uUugHRi^TynO1x&;kW+x{!NG??TG9wu0P@0Ary{g%w>jOzNCI^Ia;`jp3ZAG0$X zsp2gFTTo*8{8o*KYyGfpsvh=*_IOAc+yYV+7}tE_IrqgI!#=>5%DTs2)G*b4J)mH? zFRhMGKs{!?ozJEBZtpbH8T$qS&x_~~>KfQLZrmrXPxUWgS;eZtPTPxG{CI!g5rI^n z_Lk_zA8#t<6uP}u&dfVSzCAOR%Gcskld*IAj34U?du668EIT0%CO(;6e!YX&aS=l`TpM86LfVR>!hitleQML$X9%Fxi3EL%whUqhEasDUqyO}fMvH{83kK^Vlx z8xnqV==1gOed=^BfNtG(zebACDas{1vd#K?*iW*3PJ%Z!WgbmDeMqt1UNlIm^dT|2 z)&$Zw_#66{-(8BV`Cy8Y8ns%SzY;98ZoZx9x!0~2{T3P1-dc;sg}tcnYHB+vK#aX8 zq6lOzpUe>Eh#};uF4?6T(^zl2W$C;JEwTEO?a4Os@z{(v215-taK{<))d5l$O5;OI zg-QE-54b9lw_^i66g?^muhQj;-(=)RJ$$QudRP;9)15awrDiwGStOueCG&hJ737D{Bx*93a}(%=}}TuMm>Gtv!Nk+;tNd^d z&P=u&<3eg*fLvU2;}k)g1JXDT_kZeq_a6J4_MVh2XV3V2ED+L1Qth`Kecoc6{q@#) zZoc-OZ==hVu7@OzuvR z`B|)v7{BZ^V~gN#Dvt7r{bS7;p7v{sIsb~?ZBipv$tDtI_iI0B#$QVH$Pq&ga3u|^ zI3kQ0-fJ{)qbjMN@%3e!)h@P5#cijI6RcW#I85;pyBT8qSq_&oT4!}{MD5JRhepYJ+0DhUw~SCA z?O{hIv|UhC+4%laH`7R`Nq<)z$4LlK9E|)2Ul}%tDT-B#X}Le-9#-G7J~xZ#**f~8 zRBm+q%OYCz9EozjFF*Zmf1FA{Jf(!qQH(kYAkz6^bB8|zWxmUTtvr| zMvm5OU|e!?W8%uHLI8l%MljJCU&e2T{8w_)Gsm8IR%Fubm zVBIG#K$rMGTrm3_5dwU!JCO|owG(8B9QNOyi)`Rxf)~q<1^RWk<$paJtH1)k*l+HF zgU}5HTQ54|L6!;ae+?2J&h|JbdZb%sSQD}ak53XY?sAv!P7Kcq)1FsLTF^DrS7+O> ziifv&kKIKt_1ZivJ1b7^amb9n^}spYP1l^V7AhhGI&b*Z-^hGa-IAfWL2EKWSg}N}R%}24M z>QdG2CKG}zZ_0Y?C-d?|JY%4m(?*_BJNVqv1VQ6|FW*IfGWtEY(e+XC8ZRhis1|)$ zmoNLvM07zO%7{Uz947}i;?7-eRdyRs}u!uMP>Auo$qf_D|B0xd_`KWzC<<$GfqXC8L6_wER={`^rZ1S&W# zsdl$hLOs@T*b-=q9lS`M{e98P<)U0-o1m#tnL?6qdsyZ-ifr&z#~hTjMo< z`@Pba_8vf(<%ZUx6xzYp11R?+Tk#j)Iqmpf1v;*7Fj$WMQ1P2#1}WP2jxD)wD?A8@ zT5*PioX2Y8oG-v3JOS0k9xq4Z$&t4-G#NcuMl`}Zob!|8?Zd|Yss5FXKEn33wQ|br zz4`Awl36Jmv!@AQSZ#q{D6kwb8te5~+-Gt{B}d{&8tyUA(pukVVRXPM)?HZq18y;6 zmEh{uv#aI>>J+ELZ9XwSh43e7!dq%m{i?=dA>=RCaLWi_<#O;vttaF&^auu)?sbwxT!8)0773$QzMhm0%miz29ilM-L+U^$41#1WrL$((kr}pvX zOiF^1r^a?gLZLRK3QW6?2(P5``zzmS>A~OAyGKFIu-s+NX;o(S3dVw|y?RAGmb}-m z!03xrh;1g)Sl1dOk3URDZ~IXldVQ}KZVOO7n`blHfbcF+ke47T1&!2TvLhozRwpp6TZg&`}Js(h1HXTn09F=nCy}5Rx z(VBoL=~H|hC?^MG-QF)E>+a(z1}+p+qwyDe?2k~5xO8~nXAnq43js1)TswpI6RwU) zq!9;m6bG@1{C@Z@eC8%j8v83Py%K-Puc=ZC_0a|lLrX~-isi!}oU&hoBia9BfPS$A z6;9gnCiMO1&(?yy{hh3_x`( zw=_6qkAC;v9!e#H4RzYGWJm=LnJmsNlFKQnl&R1*cZ7FZ?jIXIX?woJp1ll8C?d&M z2I8{shDxz@Q?eAD#`9!;te7Xptn^-9Cn{OD!*UE)gUVOx&kMOT7|qMuYR+X?hqzj% zZt~~=N43dD+CPCgXXEg7V;PMBqnDGpDQ3QAfsWZ_@*~VnxIBbXI7i*ntV5mu0L) zluwCAprz7St+853j7teP%pKdW;4o4OquSAq*`)i$(~W>5wDKK?^X9F_ygdjjMK-Mn zWr4*fI$Y;qzeG9ZY>UJpcF63MX3j~k{5}tYx!ZO#7tvR6{IpLc%ehA&FT8B2tB$G@ zK2;Hd{@o+FRnshDKfqqQvwQNpKecB3T}UN~^_IX82~l-Y3?^ALske!^aM)W*r1}2H zgt=<3VbZL z4LMOx2^-^RzV`A`M?dbeu^TjnfaOh}J)Tg`qc)(xvuomQeJ~4d5Y`H{zS~t;0LZM2D0uH^hCC2B%~AuJl6$S(R&e}*gtAJEb|pv z>ml&}2zjg@bbR%jVxOb92fbnRwUeNs(A_1TSmz=xq|uRrPUe~w8>oUCXkdB!lzSR- zj6+JTI&m+%;pq6~e~zWT2xqwPT~0L<##I`J`Y*bNJ+Kl3t0YRj5uGWPq#Jo#c2{vXg8?fJXb z60LTK=H}Yx%kC7-o2U#akaG@<>~W^Z*ia9Rv~*{8kJ5GG>;Ct z87aM11PNXkR0DSaha#Hl{8 z=o7K=y@Bf55=$cG*3~TnHZb)3W`xyn%ZQD?a^*9CnbZVYwxypO5J+RX=9ZxmAfK<+ zgss4p%3Pjv`5r3K&6M?lVtL#(^I|maz$2WP#x6FdV&GaAD3kU^Ji7`%kJQBi3dr)@ zggVaY=-Flduzzz$%GiGM3?~^QXWcK#;!U@&KSxf~Af8=| zu8tq9vAhl6dw`Jrj9>?-Mi-44Ql()xYG_-UuqM3)6{wVUhu(H;_x)_&`-$6%<*C}D z+rbx6OXru$PJWbRbkl%r{{8;ry$G9E6sYX`Rlv-xE_2h_=(_IBF{&Zap_o?~C-_Hf zPVGibDT`)@U-0cMyTHZ>3A5SGMz5lmy7+TRv@y8OrXK~#`$L&Td!Z@YC}{d>Bkh~i zLVIXLh9vnUWdy0|JgNQs45aC}XyCR5uR6qH^rw4QbK8oyezpG4d80oN2(`7!8EP_^ zszWc*^zcETBkdT}&n+Q_GYQ0Y_)EqsLp2k!?GZK*7~bl4?20|E*nNl8A&_njH!2b3 z#iJJTT7Y$EU=qBSLqZ`YOalochVe8G{;=@xE_uUWQ-KRb-hb}}&<=cyxLSOao-^^U zc?5HB)G+pC$%)rDwLCk_*B<^57GLAOx}I>4pJQ^%BAKUR@f607g#U=tO?1jyXs!qV zY!!^y^U?;4-NMG`eme)<+U1l!<-#`8tU`PHLxP%^zhBo${@3!$HHj;2Dv}JBRv~K#jY~Nk0wiV@xGxVm^(4=fK0c!3S$O zdMifk>b>ARG{AKEV}yF-6As%oxMkAx@ihKCC#Yq z=@HH*MTCkb>y!f7*)xo}M2MGVbt42Rrda!@olH34dsEZ7KgB=rTGgwvsLgf{8fZJo z1hxIkT-l@`9;P$-R%=R?=*q8@%F}8mJw0XG8OT^j_1lq) z_0e+w8rTkLuVpn_!^U#`F54*%9EpO)Pq9((`oEtoKp#O9fWcX`SZSkHvYm=cO{jW$ui};swn-NB=ok~nI zmNP^{i;K9x_;Ne0H%S&lp%Q=(%tNMS^7}l2x7QGc;vV4HBwgYT)-r`}&tEb*X?@>0 zM1vm81Sfpyu;L!%U=pFSj3N^Jwi062#mqZC(qoVB(Gg{&j*tg`o@IoL-`V74T96{N zTs)uhZ1&%T$-)st5>MEE{?zW}Zfru?BJSvOKh?CJ(o=2Pw~Gf?urXUn)3#&^4%}<% zt<$1z_n1X#38j-yUjG*y(T{+jLs^_jrcwS?G2(wr2l`2rR7F(;swDGVhyQ@2QJQeS z`zCJ2a4f0l#duf4{ZAhK8^%v!u9@#RXXnp@#)^RBU=~{p#1}HqNpm`a))e->w`(90 zEUD6PV|0^;(EaotCh7=$zaW`Qj|$&!e&;k!Td6@C_)2cXBNQFXVU33+w@I%K)j_Wo zJ8X2j-!{2o3qms^h9!L{-u!VbfjICZQjVku_k6q3?RB1nb#GL#q{Idl`F+bld6|E( zc(lPNrjTe&T_BeS6Qru$OLWrRAZ6aFbWz}R5s^{|{}IaDQngY_2U(BB#kty>dmU?l zI7<0LJWw}$C%k5jP@4{kL3x&hm4tQJzQ+97@6K=a4ElV!TwqhHgPLSB3?^Q+%Zia_rX81o! z19R;`poiB&G()l_E&uwe$J=D?(XvHv9& zg*G^g=3OTje3qJ7%sr9>A%%D}Z9B1uZ;csF6uWz0b7>Oj5IbMj%=|gLI#Wd{8qnJIR^4ea<@SM*(Aht`|M+UuNyvNW59)~KxICOGT0 za7GC}H-T%;lkJiLG8j|Gdy%0LD1Vt8EeY*Q!ke%u;aKIn%=fr&h(6I6nGVswxcAvw zmBw_@8;rgThBe|ykijb69lw37-;}^XvLP(kR$K2P&%KS&nYB~W2Ekni&#JM_lGm@` z2gm%ja-H1;Bm|}3B|;mytN z6Je;VuerZ_=yQyYOJM4NYgZVM$>RJ)G%DhVn20D?v3p1-dYUG(hS?07`*M(@;#p!7 zBwM>{ql>!T)ZEBafmQ?kn&MItMvzbR62EVKL%a%W9}9mZr=#={BtHy+3jlc9vc}GA?T2O# zki3}ZwT*Jl+T(GZdb>+X_Dj~y`*mWH=oo=~H`8H+_SZs!i*5n*au0J7RY0Dd!a7_s zB~G42FIZ3n7p?rj3Yksj503Z}39eE(j7OIsVc8-^t3u$Lu9moXL{h5rjp|mw3P?yA z!Ao^)565A})PiZRGOAi@KJ+I&m*kS7pij3VeF^R!A#Rng_2uw70f8yE+HAfg5%u{09V_vi zt|3T5lVwZj^ILH&_U)FKi1xRu4|1tNCt-(7hIzNUkHUhA$)tL$u?Yo%mk=0VnWFg9 zHN9MrQJb4-FUF)smj1q<7_(c4UwJL(3Al_#FRuu@O|3Utpr*d4X@QbU9EL*c24r+L zY+>(M^*kle8|WoS_hT28OOK+m4LLbJ=&OWD!2kz0Mk?{`lP_@%W5H=6XqvG$|ISAY5bPTZ;Sn!#a?+Jb@7XEw@`;i4F1f@kT_BWn z*Y!^Wi~g#kIX(4QEniVc7?ErNf2K(4*N=AvZBI|37S+S7t&UBtt!PF_wtouEcBFZk zgFg@cBKv67nEb!=Pub_T%hee*-W_EsSO9p0sU?&g!`OkAIj@y+uXlnsD}KD7XK@c1 z{J4?79Ee_|ZHF{=Q3B`D6z@1HOom@inMS&b4oP2kyDIhk(< z_8^p=RE$O_RUIzNkhDQ}3$9)2GK1CAPEjx)6u?#Rb@wvC=GSDL)ou;yLOzo7GQoS5 zAE^>*Wj2Yhp=ja}HEM&Um=PtR*GWZ9pjdkUp{~eF_W~U{CS!|GObG%CEoQq068D7! z_&jzIQwgugFrE8K4G^@_*=Cr^MgQ31r2*Q&SB}`j-dh)T2{KiEzPGBw&oRB8!%mty zF&J4bk>zgTK_XB&_j&E&Nm4 zV;IBwny_piCE?C^N*8jlgkNv8QMARJxvLanwiRnx6EcRQ>_KbdTc~tRomJArPQU|j z7~^JIC3t9UG<|R=D&t0JX}>O1b0#!t_~&gxt%qGAIuku%(TPSp9aa)=qjKaLRADMy z=W|ZXNBt^t!Ll&-2viXIU%f?(o*?Anq!0$N`*}hlew^CzmtYB~e4g=O$%pl33r;H* zAfRdcQtlTHtI{an0yH{IGir2_m7!5rvsE^VQc#99lPVaF0dL&47srF!Y$TKxt0?uv_y4LR2f+iXFk+ zSooJh@zpcayaOVjpdIqb2kBmk{g%9Ws=;!CpH*(=4|m zVs9i4I5)Qcf_!hzF0HO>2ruxYf&YaAByUZYWyxap_-;EM=cIri zTJJPvj3B8M@L=z&^hr`73Flui?9JL0%)=d#e5e-*AE@{SerFU;fKI2Fjh`$iia!Sv z9%jMwIVEs<=LFzJtc^J)#9a&$XSA+jkhM@dK~wNKy+}6-1eW+&8iYc6q%ZX}6vUKT zQma1vJ6|2$|F9GL;&!q_LbZqCcF)*=AeJ}(Zjc8`>MHC)BW1;{3n?FSgpcB3%DSkV zc6o+enLJqwE|~M>@WV6E4jEoIOfYbky(+~p(*maK7!%(QFFEEo6i1$Auqv2h2F$qb#G-^pYq&EI6Y3!G0Nei0qxNOdDb*#M& zzhAy?cHg02&d8TE7VPTQYI*CktJTR(IGh`RA`* zrAZeYjuo<8`5OY3tRtK%4i*Thf{A^)lv?JEHvY(j>)aHP^AlYQiK=l9&s|U%!%WPx_4r5J7>j zMQ{@sD3}2h_Z+7DHn#n>e{n}|N>XyeZ)b*JEveSiuqv{O^?7ZSRaU-I3RRp1O&3{L zi&1*kG(WYKhrfxs`EH@b5&R~$zK_H&38%4i8Vud3MDF&|c!?^>p62bedmG6#v;}xf zdD1GGE?TW=(pMQf5b}TjooB#Y-=-*{mrxj|_(O=jypG~%Xb~nG>t7hgOmhQ-WmGY(@f{X_l7FY3hl9FOSl*TM~I zjPTA5x;LJaH1vp|OQ)1!_lWeHJ{@}BKL60kUl_9f+w7u$L2txmzRq-RT3~Z$1!%BQlN{f`h z)CXUyOo+abHWF@knD`u76NqUPI}{pyUaPx=$C)#K*7zUg`HnPXFK3v$qQFi(M zn}Zbph5*XLWFT)?{6?N6<|S@5K}EaJ;el08h*9P>6ABE}z)2^gO0Ej8I&+4q+p(>+ z>z;_d-*f#jrV*(3Wb1R$7x`>GxpM46)@S(?f=yNIX_L;cQewT!hp~J*ZkD5iE)4hE zv89h$rNmefGSo;`OzGyG{>AqZ01&SKDWNW~C5=fd2aisVJWA#9;BZjh|DA_Ml5qL0 zKObt8cZt1u;9|Z?@g7$GJ5g%r+f=emmQdx5R%;jyB=cSIT;8Z869cnkXZU<+d+w`r zZp>;BSI@PtmqT^2=DraH$-njx+FNq1nGZ)`u;{`LCJ&`}X8mv?s$g}0J0uxnR} zU|p^=l`kMe|r;_irsn!I1c$Hw6cr9B#^* ztJ}|8vfqLy=^252OONtG()SbzX%jmIYPW00xZYlRQZTH$5f8;g;St8|-`F85g|tD9jRvG4=~}icE7*siTystx zJPfTTNb0@#$AmTW7M9l^^AMKRpEroG_lqaKF}GyXNVt{wFOZ}goVV9olawy%K6Sy# z|AKw^Z2?p?n+@Nh=IR3G`6kSXd!?i1ntIK&Ja+gZ5WwTuwPp5J@eQa}f`g#H^$Dsw zhTa#7Tbd#ic)WIoxED0RZBxL?F?iSvh@=C#|i8k8Gp8UOAv)HOW0DtU$*>ViQWulsH*wWr_! zs3`h9=|(gha5#Wm4zI5RfpT+C+IcZMig7qRP25B1dZa0`U3Zhw;U5pLe)aHh$tgtb z=kT}a|FZ!5sM$IPE3=jp-wvD86g=)cUP!~D{fina`4i3_?_V|5OXg6zak5~f>TBFi z^Gy&EL*hrT)t_RRf7KOq{?dOpgFV7hx$owW2V+fcCJ$F-RTd2L^AzkKmvHA5-)y40 zv_m%`ppB=fJ}do#-H@`69V^rItLF(4^*}wALic$;L0l+v%6J3BE$lk6&!Pt_il3MY z4_1Cd^{uLw?Rl0tf`iWJg@;!i3X+X;++w?n>?!`h!;~1q{m~KKWuo&81zMOI5Th`T zszRcu(~Zz+yV|9~*&B<0z`}-nm)YSn$^I4CQetZ9!?i0ABwx6WgRRx{QL|j%H8()+ ztEKG1XpuE01ZE+pnYj#Ge9k&ETg8suPOKhA0=T}+&eF0%eqH&hVz>x?S|l50NMi-Z zYRw5jbmqN@J`#KGo8`A7f7pLYD=RRTbVLgHYn~|_P!y?7WS8Lsp&+@Onsg9j6a5u%shW!zzwcj!k>E=x(EakV zW`6xAmW!&T?Cz2fjr!m2ViB_jbpXnOgho7;IMt~YYsO%uN!Wop;QA*9h6uEG$(@ku zJ9$i{W04ocUul%OxX`PD4`)hK18*q}qCxMj8$)#>99IZ>j`u&@mNg#*_V86e)i}!E z-RpPx#YHeLXm?PnO%i^iEU8DbtpH;|-_HHvX$ncFu}Ac$>>UR5nQx0FqEhVVg-yZt zTH6(7wDzy0s7N|nzV?5CO5~}twL3=aH=)Pi;8pj?@pvrZ)r;>4O@~+Ysy_os1%Kr+2V22hY~zIVbU^UKt@EBi>`#P1>C1!A=xkc2 z30AoTu0B`Q5&+!w4jW2igv$5BrsSQj@Ac9-pU%t!_;UJGTgs3$0~nPx}9h4?C#UzOhpKs^Gg5e<c_3P&t|N-AEGnA$1_VPd%9Yx`{G_sqhfYeCd>P$^2h6 z{B+as70s-Sb#uWX&uyG|m22r`iGumV?RkKRAW&-frSdq~0K!)}W)85-U3BSMsiCJ5O)T>?N^yfVWsq_0|BXJBn*eq@Z?xCqA4zjo`2Ipf+wOBr4Cpeu z2E1~S6Axjf$~E_fV0Z6HQ8U>m@fdHkvm$R-UV#J)_e%<9h#E|D#f4wFCf;Svh&YkfSC(>+Ai;cuAxLUg{pd1`81j zVBm7N;DyguQ{hK-hW=BEE}7!+XXcNijlC&$Z03MQiO#)p_DS${Z~x(5qC0 zSOvWU$F3B25(LPanX(*8W8gQ1&uSQat`zm1FfLM6Kt~~3L#*aN9iq^=V0Ay;Px140 z$mYHltf$n#oRSCRuADd*DzSf$yYmszH`_sf_BTH6ym$oZ(Ri6s^Vhe#UCMv?c=?V? zT&;JX->dePY}~>4C$ptHjA{P2#S10d_1Kj*L(~m}nxyUJ`083rCAP|q4}pHJEE0$Y zi9Cf>Irqx#Dlpu0r5(FDuiK<1uM7?9{;F?LL)=x6!okJ0OA(&KiqH&r-%IfLT(xA^ zAXI;QfVZW2<))jV`?Gfm6~Y7|zNRBysX3N`F0IqJhkXn1#YF=)QRxb4AkR43KqX9d z=M=GMbIxDsB#Jbl|I7X5>tYboi(X;d?=tFbA zmzf$Y2z$*b@L(?j#$w?Q<-m*{-(LP3`AOcGjz7;z4KC!|I0B`NMW`J^N4u(eSc%Q^ zl-WuyMGxO764!&dEV#;4J`0U>Aia&g5#vKswpX@a;ba~>vhd~>j|JIZi;;aFUh;W= z(I?~{y|Yz!q~w5nmy4S77``(C+gg>0`7^wT7t&}|RXgL>k&?HE(VWmotx#k$=Fst` zR{N+YNH9%lIpG_{W4!sOhpUwYwQFTeDLUs>o!r0;6v?j1y!2X5rKv?cYVs83aSE6S z<|i+8vVqtZn21`Xa#(JKj|gAhhWsA{WZds&+^^{-Pz3jCe8EHULMcx);9SMlgrkimQjb&5Bnb|f zCl*n?yobBSh@J*MQ1TF9aTsuNS~bmJlOFSD?4b2OU9Imv>#iEZjDmRE=AE*VF?sK! zoV@c+@&}YQLVGBcGiw-2xapWNIur4NZeyjvOB~$#?~elSsmd>n??ir& zcgDwwgo^S>3Mi#u{NEilzqQ!wpjy zWrb^t-|8!i*i`qSf3zSS23TIE;(rC_bD?=6|197X7q-0M5p`n<{0G;+3{hK>f^Inl0P@wd>u!D)RhRML>1y{>ckdG{fgaLz{PW)GSkV5t}w$02fR zPa1kp;z%50ygxRWzh~76BX7UuJ0Z4``6Mda_e(ba%lcPMiWpqHn}Wmb@0pn;UPivn zvWZ=^6JCdfLn)QC)-y|rllD`%%=%25jVIu)AJ@2ihYevopj^jBr^uzA$U9wnAa*bX zY)83CC#XLB=6KmfWpI=Go?teOpg5A1#Ln5gXe#VzAq<@Sd`@;{7TZAQYXr0$ z)ihcj;TfFU7{9hA_moip_BCI-xta)zVJaFhn#k2$ z*m}#?DyN$1XW=%q3tHgvU8(YSW||@$X9=Tk;>dFpVL^x3+z%Hm8h);cl=9$;m8_a~ zNl+ur6^WraD1mDLC($ZT)Vc->zi{XW>t{b8SX3M^{BMil1`mfn-fWxnd=*o-CebV~ zArznzDyzz^Qpet-`9lJ`U->t>af(&=^4azvvrtEI_{Tux)20qK4~O^(mg9G2Jj}svrnX@LR^Y-5_Ol{TCdzOk%s*r%s)t=^Qq?8@gI?Y;jVY z0BJQH{$ijyQ!-L|IU};URIfKs_v=@)$>M*E-Fpd?pI3pS z)uy$q&#IaUDoVg`an{JT4zyC7jr}quQzcvj#IocrJqp+RpW!z)#_jkK=W-dA%;F3ME)n13!@sFnXDT-2stYzv8a*jFfG+V=LjYCWPD%aI z`4pyifwh)VnusgMD&;ehxj9hc9S?5Ltvzx2)Rir1>s>v$pbHYXpyI`EybO!KN_)g|Q+@ zbdt6Lq6fva`s;NxT+QvT;g%!wWlA8*Lm|N_o`?jkT&tlKoXa8o0Z%Aqp~|E*wh7ds zNfYHhP))&BCLzCi*ZU8%H{nXZ4v4*4^OZfg@DIY__Aa^|cbpdhC+S~3dFkl|k)mZd z8SP7Pe#+I(gYqij(iCfVmV(Erru5W_E#rF(5aJ&-gC znuWISPEao<#;jo!LGcgo*odu{bx!U~P%8diLDqdb{DCeTX(iSd-`D%vE@}e$pmnhS z>fq<7)W;;}?wf-_Zj4XQ@=jB?`@_>nDv0p$*q*P4VB2v75)R=vYX1%e_I$#h$qcSZ zeqc;8W00}Ji7_Txa!)FimTHY6VOzRsTS|mi2Yq(EuMRCozS>AN<5C>blmYQei$gjBy4$Ga6mrjS7Jx+?h zgmQd&Avk10XB5wxL>aZo&Rf-#h1n4No~J*Y5Q9^TktIax=18oiy`fGb+mR8Ql)zlX zkbvFGeTUOlFMPH1i2j@olBCJ1#i$f3#YUpDIsDO(+xcdXwU+udS!CUHK3=*GA1pt~ z*xKP@mDV&{E9NY+n1^UFyLQj!Ti)VRgL->?R^IkP^ z)Yum?a<%!kT8vk7+tv%o>euG@St&n&>J}H?)(~zUXuw*l$DL<*Y>wZuF2X$B!Cqm- zdyP#Hh5TIruI`7(IbUwTVIWy)hbA1ubQdM`RxZ>e`mZ1{UKZMly>R6{*WfsuiC4#(>=k>>lid7^S{tlbI;)ug7S)JeO+!4obpAlH&$@L~KJp zY*t1Q%ht<_zugGQ<~7Ynt`71pmJ6K`%Gsi9m4U zWe#;a6Z+b_ytEPhyM-}qe5~@xsO{eB^zqQ$TbZm?m8g(gvZPWR6iHkEXiYL%MJ%I* zCGsNZ1Sg#E%A&IDsHYjD5yu0LG8mltVB5Q!f+@r2;DY@xNO5j*t-6nwOo}KatA5)8 zSH#8^9AvsTmh2c4m}C8_hfqO0FPJD6U#(S!Tdm5PR*<(h9nUD;SGkgkRi5M4?~c4< zJmw1dL2M1Bo|Oj zQioUD9RhKZDS54$cYN2NimEQX?nA61kP6!bo9nISytB1Tb|ULvEPfkbJfe6+e^Kw_ zy7WjO>+q_d%l%Kcba|t8^@DddvF2A?#4bPKxHC#a$XJehEUCKCVAMLiMBOlaeOsR- zfJ-&}^MoJtMi-$b|H;lxkk2&;Y&GKzHI?_TNg^>9> zYTzjLKaKCkw(&%Xk$ECg{mF`H&sHnh&k6fN35-(Kyzp>po- z=f(@D2cZEh$xzryKFGwFt7J`6w5x>e&ugjyPNf%&^2$t)7zztzSUS>2ZYW!kQZji+ zxjR*m;LvSEM2U7j6PL)Li6yakH{Oh#nAA^XA77(FuUxkoqUD{;{1_f_2ElP^EnV@^ zM4=w&!0|O&D#gil^EUaxM(p^d9yMG2)ap~eOHp1(Iz zPAHDoL3r>|k>sba{` z<9N6sA{6pC?8{~ve41T5{l4HqOILHTpSCSbn)xe>1*ZqugK-u)rJP$#k<^sqtBkG+ zWuCmnpwH)#>i%BJm98k*AdEY%{OK2Yh1c6KsZx*)w45M(yH2FNK0)gsv*+#W3}QxR zCl4N?b>MBoE;}GK4rARc3r49#>cMxxdfYp4ks^N%DV&pWPvxz(wTIx|g@53l>q6l` z`O+NkA)`*@k*FxssV!u13dcr=0umTHsluM&awekS^JXnT_Ncw+StL&kgs3HLuD9;< z%OwMR{XXDj?a78IYc49iCl~sL7(b~}HU0M=&6BG9cj+x?nZKnxDkXCRIBi7N$&CTY z2-ni(vOrS~%b~NN4`NcLq&RYoqi<(xJ-a~y|53}k*FGlw=a|gg>W&qm-ch7nMCsvQ zFIP47xgC}mU;4Wu?iy?l3pDtgh8Fb_hz+qOy@AFAX+_y-*u?maya-+gIx9G`#c}bRk7@_?fGDFfViu<6aE)uO^Jr6yzk24>Sf`rTH06V*}EQS^;FjNUX?Iv2iwaNVrg zi<+^r!kV=M6g6O`bbrNwh;nTr#Z#m6Ik8m)XKdlq*y*I0joo&h43Vr#M|c}m`3+@UJY+CCuH0J1u4P}fj+DzbEygqtl@A3g^f z7bf5R=BR3lZfZ(OtwW}SmDNcmH^*g&#lN&1JTa=_CuXg3UYyCJeP|eN!20e(ic};$ zigaqKGGYv5JEEv(e{kiFu{0CYdO!P2c|8OPJYs(RN!aw9&bl3Iq?vA1PRf{>3G>0o zj4S@-3Kl#0fS^@042pK$H$V$azB49r*Y)T&88!xDJGsNa`{AId1ICZN7w|mOGq_Vy z!?PIEIk_aD661}Kvpy>BjFF#_FOydnWK&65h&72*$8(Ml+1>V_J^X3!n+~~laer_DI2wrSG?jrlliTbbEEe(;z{{toyJsSraWlu8} zbxKB;`lm4?41BRIQPNrC-`<$zAnMgt#pfogW5 zrUVY_=1`iYtMLeYI-Tg`iD~}eTWq@g_v-}x5?HH(t6Awnbg~2A{y5W2Y;()7N0$nG-ew?q=k4a{6q(|WSacuJ`F{lA z=Jm9;D8f7^2RY*iHkf|wD_ufXvW{)a#4*X%B~j>w?mwgDFV2X-KXiLRYwqq)X!n@( z>l#q`^+LBI%aM5%)>iy;2rFejf_pL4`iH{jhKv6l(Y@_^ZMx3gr%+v!_<6>C6mX{B z`E4Z(%CEoO+eg8rgvY1`E(y;<*XPo|g3{c5tbp=^ft&JS;wA>`z{}_d1?=!`IOZWv z=n|@WTeJ`-yssIZt*55BM&-^^fJVmmx{l5R^QG?IJc}G=B;Pn@j1`erNZ~jM+f)=` zg%3_8DOK5H{?xFSN};VAO`;!<&`eJcXIU`&o%%Cl(|wIgs?iaOYs#vxq--Xh#`-U{ zmB>1UEP2?PppvzBd1zny&foCq!^FK*D0Gk0#_5>-aiV@d5rFDx ze%f&$ymCgpJczYVx<%NX6Bl}lKe|-!8(7P=@=fhmd;6cThTB25 z1auK2+1oo;V6A2&KNad`kV7I zftw3vPht-}1_7Y%j)avw31o3bqv|Bw_k`lG zf~JFpCyltp4^!6OB_4A7Vp}IfE zxOFWCV_=YA5RNXSR4E8mo+R?}c_599TIE?dtN#Y%*_yqJ<2D^_lAa3TO#3>mV1CH$ zrEH>zVOA-r+p3Us*9;q;UwZ3K0c8KCM)}TLOeGSy>mp)Ny}scQqHM z=oeu7oXMj6!MH6wY1C{32(2vHOZ~HcBFX964V!8$b>>m4sg95>|Gt6y{r_nJAZUWk zZ6<+W6YB&-s#OF!^IfRK+MPe_PmW+|R*z6ZNEXToWAkXd39Pg$5}+&1GlN-NFM77c z!0Z()s--E)vR@tz>m_{Q9|ooTjxT+`oWaL)ME9sL@bvwxe8;<97F@oyZm1BwTjY7$ z7_wDbKZ&*6Fl;}HRRmzFW`K3{btRFi+4AxyvV~!Wmzux5|3zrI~M0 zO|*E<7@pyh^`KVn?3Saq#p8FXZ{s)r*_TT3VZWl6fLojJCa`{&lpc)1pxhvjt?4@m zU`XoSse%K3iYX_rbiNP@QOoWS@)r&Mvsaj)U{JNR#X>GDObTHBVXLQl>nSn1=5ft_ zTX%d0jIHsfdWv%F6Sr?2GuZjPN3`DI9+a*CbKq+DJ?nkycy-U???gHHjR>RnC-={p zunS7Y17SXYAe2@W_ZL5V49|Vx`RAGDdG_QrwY5#Mu#0aAB=j$#=^QL6c2e&xCb9@> z29^YF5!9NT30Q5#K57PFyF#hm0mZdpTjrt#XJiP+*A~Ym&8+QsfrQf(uXoO=xClR2 zH}X_d4lmUsJX0V1$41Yy+aOKKjQh%pvF4gedku^jqQosi*=0>xl~>0{PI-nD@Y09PWjT3 zz(om~mVs)A!RizVCN8cj4a1*;g_|IbYK{EWabzIo0!NRi#_m?1g!%2LKmsEPhDd{) z3hP0d$as|PGY0zf4o|Rd~GXo7g+-VMC;Lv^3pp6&B1ApeHA zbV*Sa$4U@Zu=iGd8+F`$cDQAuN?{_<(XA|ZOc;_GzdGqK!#{UffA1z{ZsXnlHHmmd zA`b0}LuZ2YVT#b8Q*xpRP#n^_{zw5YHT+=YKcg`B`l1>~wc=u^@}5}|KmkzX%z*YDIO_4vRnU>t}iVhI{qh6B{#R{oH`J0XWQoYIckV zkw9$5@5fCODM~QIlFfa!_l&X44JbN@Q|%2nVX0HfqnfyAwwimh$i!I$uV0*PI4(rJ zdL3+&M{^ZJ?>I5)33RT}WqThrFy*~-D#^!U9q9guJ;AJPXHJ-r5|c_f5oB7 z-X6<+ZUYtptGU!)9V$7IP_+J>!>v~`BY*zAnLS^itbC!J&T@`mXlmBXf=Jhb<%qRa zLpb8e}-V5aQo0v%RL*+{`sw+AR0dlsG8b%h`g;w5ONZMUnYh+&G^xG@l zqiE)^?R~kxs&)onRs~u9l1|ui>Np=Jb1adwILJ4!kSqkkPn8<_OMO)43|Z+?jjtn0 z`5dnWy@4bN$!Hy^l%7OtkLM04M3O-6>ZbZ8$bX#SBr9s*lpe#ZVKS;|X3}LbvV7@| zA&u=mPIRmXGwd=&b#qSzzP&rsO69sk%M%mC4q2+&wNyjlt21})eE1G{R_YvDrvAX@ zxK*>BtkqqMgQ~PNIT8;7%Fpd=2##FFbe5 z!S3;VY+jyUdaaVWzZNWc(!7B3%P$KMh(C1xm=dKj#%g+%C8#!kC2hxrnU>`D$W|-@B8o`1O z?jMhw%QtIC#z(rQH}WfgoC@)Gde*1>h8=3Mu|I!rBn~;iX0j1%Tv}#dc>$=SQCix6 zwjotV6VNZjQ-8do_H4vlcHoGqUqA?2vraL~n=avBx|&-;!(E5iF;nMB@la>ZMLOp6 zyQ6k353~JRk^b$>(Z0|jdyG6oyVuM?C+HG?7E^Sp5(V_3#^@(SFKoq78K>-hf?zMR zUup(8oyI{=t{gV7?eHLd2o#7lnO7>Gwk8-5geo&W9ft+)?nZ6T_jMgpd6pqq@KQH` zKkI%iADmnVv!0g9fWMh9(0E95%<;3>IWhX*6HjPw0>?Dj83n`6vb8PN%z7jc#jT&( zc2t*B^U$`&-|0>sK3y}u?byZ8Ec&Oa+hjHx|3;xoLvyNfsW527+=;iK_*4;hQFlgR zc4k)0f2_Vd@GUbf{}>SW-dw8j+U&LN^rA*Hcm46#(5?ODc+Z&H1G&~;UvY(MnPd6W z-|;P_<0=P*<<-0CA(puR+*Tfm`a20wG7M0HaLLp_>4MbU1G^z1S7cLqhb}OqQ@_K? z@G(HR9+R``Uq(n{#?OnGD%W`p+{MXO(UqK68%8G8qJNTjbhHmjo`M`eXh$o_ojF4S zq$CztvOn_jj&Ip7yy;*)sgNi^brx?GCj%Q!B10jA>!2~V@ZJNr3?&U1~BdH zvGC!rkCRf(opA#`Ted`RJ#M61hskSNqd$FA&sTXVy0V;aETSIQ@lbB_nhl?l*;n^bQc*`g7 zV?p)ebVEL-oU|N7A!=1%NnSP<%itu?DFI$rwD>Zpl(ep~v>p#J#|%I47|Bh>x3FOp z6islgO5fatvc!7R#w~4HtZ2<`G%{PYBVh8vLUE{bXAnHR`6TGF976H5mgq=yW$P~& zu`nJ_kuV6Rby;2}^g-}TttW;&p42qF}0-UR$Uq&kh=gG1X_FH zeI*nUmZb#WxzrafiOH(LZJ!jm9zTWt^o(tJ=KT+EjtC{SshTyr;MdHE1^*YMiv_OJ)Hy161S1EoNH- z6!f^QFRyNegXA=pZaiLZRX+B*m5pO9i(l?JI}(My#~-Qu^{b&<18&&fSwEKNgqLcH zY7_gAM)-7x6roVLDxu6Dda$Tflyjpe{ga{;Be<_A*=9gxV9ybaEO%}S0h>SS6U4oX ze;2zagXHxKd-uFjtVv8|Y&JBDTSUQZJ2aPnZ)W z7J>_x4V#grHpqVy-8ua%!!TOpt#~n=--&3TP#PDd%!cPR!Q?u+k}y+rcou~cOdY00 zpSlOgfP%Nmr(H+FJz`EWW z^f{X$B*Efo8!NW!!gwuldmweWdVR#4o2>Dn> z#mVH4>G26!r_)h2Hv6$LZR^UhzN&EP7t!O~RQ?Y71~8Fomha-t&735*xPJ+8yHf1M8$d8!b|J<2-fD>=9ic`9pa-^QqB0`(vRl;Zx9h8YL24bUYueS)h6O1txM{N zNioDY7l<21UZ$Ald}YMwlz_fa_(PM_BHL;{@ko=3OYI}rhR!>ofl$Zj^jyYm`0uqH z?Q;{nd*~7S?VX%_Zw~z;4NzK?rcUR2lBHe<|F~E=xZ(8A*~pilG-m zhag736JM=f>J-Ccd048RCF8$<6iTi5zLw%~%p~*-sFM^{u9CYjnSc0KUU9MHMt^23 zoP@8hpfY&r=cQ$61~WXo2B}jc50~5*b)BN#_ZP>grpu_=K|<%7H&?{os0(9&9zAhL z*nN>Y9ME-+bhG&bP21-Q?-aKEyI-TJbw4j_Xfef=acmzy(hhPnOmz*SFQzRJ!DEgU zSHBoRZ9D=cQQWWdEv&2Y6;s2`l+HcJf=#0Z`^4p^FibKgma_Ere|(3~rhSG##f6U} zoe?yF#oUyY2;b`*2+1g&{zAI%ogjG(J!isgt21Z14Uv(WFPMx`5IT|la%xhdq{=HY z$@F9)LDnB=No3EroijDmWO;^xKLE?8QDJ#V;x1|QN{5U4#kSk=cS4Cr?}=?#)~V$w z?d4j=i2_v*mUq#;Y{B+)+{>q2qYuT&2FUT3q9IQ|jc)fD3=c2K%j;XLI+yP3C9Och zL$RFLm8a-u_ipcT)zi*{fg{tq zjw~xKwxT)wc5BlfGS5`Dev9EAe^7l&`~x(}S3Dxd7+tiwZcZ4lbjSHtu|!8|=nS2B zNVtPDGAGL1^@-CDP*2H=W99O*=#=)^`l?cU$QEm8j9jx^>8$lX+|=$=wxlRxw|Gie zt?}b)wY z-`g|F50$2!$-##bD*+=(9&48jV7y$PK?R_)m+u0x21X;Z$306a9B2P7b>{Q%>J6P3 z$uoM)ByAsN#d01hb7_WgAb?~!f$F>@VWsN4*gUip%!vW6^Pgtk9aNR957Etg6n9Mx7U};br zL}RSc`@XCIxhq&MzYqGXIftHZh1OOJDxJ^q-rbMtG=Y->55L(v&-^3BZ&lURKfJee zMnA zA8kCGsF40irh|?Xrol{0xcU;WsrQ@cC$c6U>2mLzF4;BY8iIxZ`$a=rA`L^XW>w$Cy9SK{2Iv z0WtnPm@1fyXEK!vP*Y)?XUxiIt)Oxg{1+S)ko(SWb7i#8BXso+)3ryRdQCa6pq6C! z%bAs(KFQ%KIlJ}mPhY1sI;4XShWl`r`|P1^LoRtLITm~Ef1JNdRF(!?s=aF-RUVbO z2`G{~ds}q$Kz@j%e*97{cv5j**!f4tRfg6Ut`V{r2*m^lbg^+(2RlRpSSSXtRPaSp zywi-ajSuCk))sRyNPcqiI}I@5BBKTkue(m$(jBa#ZYg%WVK6vhnWxxE?4W;-@!KlP`oa}*ABIi$YI zG@X4r8^yYpgdI8cNw8gf9~+X&@{NFpnHYuhdSHha+$Gr#KQvD~(gIP4X@;s$el(#P zRY@tx6BK#w_Fk=Q@b5Bi*{FSehrwsXRFbTrIG4bq^=O6aMlsKr`b?W5^yk!ed}U+hv@-}jZ^fgVnoDD25s;|HM?J9`#4+= zH)xYWkp;p*iz*RL7N&M1*C1R@b?nwko@Me_#vi7D2{?sW>K`V~n4?w(=cc74y)Td; zox6Mx5_Wh>!t*%maU4_3wv?JLrUXi2VB9sc!ROK@XU}ohx@u{;bY73^+aM2}05RxV zc*jE=pHnde@vIx!P#avO$MZL+sgOsfN40^%2Sc&0iFcmSd~cA#Q#atAbW zI&@Qfop(4B&$WqsBK3WbuUgg>xv_<0`yheg)&+F7_NEOn#PJmy)Su z04e)X81Z&`hda_r(Kl0gnX+VrLKl1UTZ;{xa_|_`0`g8u-8hBo;RW$nt;D#ruZT&@ z0sZpEdjr3gg026inN|uXa{0j0B zFo}NTAgM%;#jHeJNuN}iCvLNlmf)60X#~>g&qZrA1ou@dig1_i>GKo8?)zlX{As7` zTnpR7F(@S)jz0%}mAo^h9XM0`eA`uG!YBP&O-P!)I0=vn$>#ehE_Ycc>zE0l?8owa zv7~Kj39%UA-Hj5TU)+E2pWX2)H6ltM?hPigqLi?*!ZWl|1yXXjM4NdyWsya^dmA%! zy7*!CgGp<|1k4I1dgudl5!WrLG4cbCi{=C!=7#i%uaA5RM8g{?5}zbx-~Sy5bx=*P zIR!wk4!ucTd5Ase@5>U;q~z;dDE2iEqvbb(TevaWR$CE)L07Kz+aG#Ev3w8De^fCW zn7{(s6iVU~+*E24-cSzC{Wq`y>|||~G25>OtSO?KCBG!FcaeF_8mN$(sncF32dXc^ zQ@-yOLd?*93M45xZ@C#d+_AT{fBYtKqId09gNn(HB;Y((SzCGNxJx)-3^X#Tybhx^ z@PNAVco{p7wB_u_74aO!AOV5)I81S>TatkeKJd}y7?3&_r>zFR#jJ~m4v|^Nk+G5K zU;W+>TY@8z8{szfvZmns@y&lb>b6uWgAg2IW*%{6)(3rkhhdB5aU9yK5NsxC0#D4Z zubrKBv){bc7$QvJGfNRV35Gkx zTXi^1ss${{0~vD^TQ&*GJ7%1ff$ddRGhVXhHRJ)rXlFYovs?hV$t+--XneW=QSFnBY*(^~+>Uz+OwjBpsVz`ZrIAN&J2y9^}9Al|Nn# z3>CU!?tV0_T*6x^og|h`_y-xN>mmlQ!GlvUD~lY0xh%X&2%QkQ-#wQbeGE-q{7tD! z51mirl=w~n5##L(b6Dfx_%(GC1tlTeV12a@g+JpB&BrNig5U8IZ^bkIAX@Hz5O^bJ z`Ui1hb-d_7Ab!N54&9*0XDG2t-6jdy?Iff>l*aF;l*VzUNmogT<50sD+l+A3AFHs_ zUB@Sk!EX#(RRB%2N??s;tf`LlD25|j7pM;zx>5;$Ycg4PizMN46TyF!uf8U;y+YY{rq zk4xYPQ=U>6B@4p@|3{C>ZnaPdWeiYzk?ex*V6QNVf!R&1NOhH3x5Me=IE#JCvb{Cp4qU23=CU-$*^5+ z<}P#*4`PF^nKnP!=q?`NG`K#Ijf=6}{1Lvan4P9_+Yf)JfDOE~=;7xSla)2uHA8Sxt3iSq#>~DAGJ|}QIUQQx$iQpi z7H1XCp=k;Et!ipM@Zn18t9)0>50Woa&hX?XFu}U?s33EwUmYdVYubFSyTA-rGHqe$ zOZR0GdWVA><=}6K`NmJY13!hYsL4D`;hRPPAK0T@`qeqUfrvZ%8y z^JOAV6m-N3i*azc%8O$Ia|9%RG&GogNg^X0r+F|Q$1bc37#aZqr3}PZn62Fpxn{r7 zXl&qW(*Nkj`SC7*qmpKH=Y+Y6D^AtWbQ8 zOL3UBWm+9V zEMf?5B1BT+C==joK4NeT!H8gyJYN{&XLqSV%9CC+XKpPpC(N2_q~%+%FfHS`DHV(P zFFj?daZ=>+1U9q8spiQL1LFuN*GjMqdZI7iwkADgBItGX!(WQ*b)7UW+U>|uNSaNP zqXTnP3a1|==Kc}*_^sk$AXd-p%Wpb$khbuRU68g3)5ZZPGidU8Z}=aglGL=pGY8l> zSPUqOkd8Zziew29ahXYf1+z)r-F@tcmYzNVhhYvcwXY2?^({|W#E@t3-uiWCj^2p5 z#UtbGPoXgdvu$)Nw1BBnVZ3!r5>EUKrx2zwX1x?_f9PK1BX^2?kaTW=hH7s6sEBqu z9eGfJ5}%J;$a{z+5OeabeejFhOTIFSrp&+4B zultIDvW6(Xv96S(NoJe{1|yNd(t6iA9@E;MrEOeP(UtNafW_R7w4c8s`XbhRqO{;sga_^XIac z0J{bedqQk6$W?$)P$B~Cy5?aalsu$VTaqPegl2Fww{b_GSN?RrV9)XyLg}Ea`m>Er zi%CPAj@>AI=KpB{B1OnBa0ZARai(6{OkvyG7tDQp<1sBvWFaYwEGx9?MT4v*Tzjkj z%KlX+zcVUKr)V^Ppl2kZE_nF1Pq2}0oJK)?Xm`UecdPGZ`u5~2Sq9aCN-}0SQK$pN zmdnZ=&z629E?I_?6`S35s)FTDT2@Jg*_SnO9h{Iaod$<*nChNS8MGuh!_W4J<1J&K z?ZvUsYRaK1XN0br&do`Z=f@Lyh;`D>qjQOx< z{at<~Cq7wm2FGq!d%4o|bQX`|fXk-Gi-$^q%UNn};2#P$J9C1tN(_b(W`uLi*a6p|?%VH!-zm;EG|o~ou? zEW}cCavMpxmO3T$z7ddpJ=4qn;bK$IaU5Z6fs!^5LS*Ofw)WiV#gM<+WblM&b^)Q` zdyDX-cgcu=*?cFGjbv``G~G?4$K z^U~1yVSlfoI3-H(pJU-uRe=LRU@pI_7ay)eq&msL7Ndz~0*Tq)OW8NH82?RnGrkjA z83QxQ=$w>B0unP$4AtWW811-+@1irwy&1=)j&$g9_~=tzv` zlB60sh-0Ek2)&wRu|-NM=Xv=dM2z`XpQDD>P6A}>A`ygoYyBkmML&s`tZT{DGQD-$B0G%m#-Px&O-~~okASe6(mPJi)a%@UtJeD;ti3Gq?27-YaVOgF@lguH<9m;P?eaw_)meo}^*9Q`2O`F{?)hKof zJ)O*al?*2c75KSMpm*Lh)}8u}uUO#AV(!=7G^Vol31qYMGFJM`(R@>?TV-ubf&Y$I zekBPrB_AcWA5{!dMu9z+G!T@mnmfG;)Lgu=UTF@9n=V?9*&DpHR(6~k^xy)M*|*5j zm7mDnQO^qjr{lpidsJEYLJg&GxT=`G#r9QXT#Ga!R>5Zc42l5Rzy?Ntazzlyx=_I* z^bBItNd2KhVAsn1O1C3#J@5D#<(S$~-u;rk=p09b!i~BZvy35yUHPpRWh8*b*oqnt znS^{sYcOC{x(qQXChcmhe1ZmM%^%UG4F@0KoVj$uia!tmnZT;Fly$colNf<+VxlJ&eVx z`^WY2I|c0&mjIJAucT?iZ3bu(UF!W+I?J_}G|4Hcfr#sSqJ>sZG9-of5`O=2Y^s6> zi8C2XVn4nVK2TKQN1A}An3qz%%xCx9h&pP@E35T@9z14}{}zH>c)>%`N~yw50km{? z`)$f0M@*?TUAwzYjr=pP5O{P|xCBq-)*`X_wrF!=S!n(Z_=>5XX3=wW*663p(qun)k0fJY}-Wcq7uw=;RPn_CGV^sAPz0P+2eh zdUF{`+U#bf$XkEq_h66|a$P$vbRKP9_){QZ@=G69Y*B6PgYp8=3og6g{+J$^VD#rj zl?*@LhYFoVy=_YqX#^ClyO_+|}q2ISxd(S2g{39#~;f8{BCb zc5dfAgux2`(==Kfa#XZz*Mn&Oq!rk_zEM{8w*xsV^CKj`Ac^TR!iKP4U@C^ook8^} zJvEJk+J|Arn5y)QDyL@`A;BQRNpWG7FE>D0B)IwC^)>L7X#vgwyYI5J-m~yLN zpkd0l?@zD*AsM5v^b3+h8AYHiI;oZQHwsM*b-g6mp=4bYOJd#2ZMSvux}mjY_NmxU2G<`q zey62%`G48rMp`vH#PFj=1t?O4Dki8!$C!ZHotpXuF?Cj6sv(AuSP_x6~&gM@F52MsZjMk;r>cs0&QaOIP z^LK`$oDQ>np7e@}MS$d9|5bDxmXavYvJq(QUMFvNiQ;=N;<&?DgRz!uMrsovm`gK2 zvXReHC*BKvh?QP{x5ZizkRS2E`kPdoyUcwWkp9SuS-aVJMD{0#3|kj}$Un)APxozVIj%yFXa0v$v=~tp)`Wu9FkFX z+$6+c3QGflq%5Uqxc|Sl`hzJe73)TkDd^fmD8C}lf^Tl+uXT}qEQjs9<{RRRIAMmx<#AL{ z0xgL{1kB}Eo(yq02`c#k3X%mvWw~;T^K*P6I4#EN@~r)mWW@=kDYVPs&!0z&P$g~V zP7rpN%5!V3;gWY4wXRI~C7?>mwd$1%7qjVcVPR-AHE}KWQ_0}KtP1PuH z|01Sj>P8W}m^nqSh2XDgMs=_)mO4ykb16u;?CPh0K_lu8i1)ucAR}!|CQ4a8=v(`83*v?#OUzs_LV|Oyb z|NQd^dA;3I81iPVqt3A+6(^RR?y7q1V1zMUXEDr};72r`1LxNH_(q2#c}YqV*W0yD zH1+5Qhd)Gjzd!Vztv9N?##FIQQ$994l5EtqDQuxbM8uz+iM)fh|L>J4ifX4Q+ zZ&NY;GWYNIk`!$aCDCM>(TEGia%M)bmEY%cpTABN(SM~yj)IIGFfc;zq~uhg>+O{y zO%xIa=EfQ@b|UEy<&I?f!ugr`!dI^9fGVCPJ4w%&KW2&< zg+mgnAy*z9srEs{_o0_%(!ndmtN|4krKNCX$>whZOQu|PTd!VMy2!Bq|Bn(KN4pA{ zfK)JXg7*`Ipy_E{0j#m%w z-B}Iq#VJr71p(sNY{RTUxqcjr?D-J@kpwY?L9h*27HqI))rddtu{LJ&Bc1!-dO)*R zkmuIGF{Bt}x83mElh8 zsN4iW?QQYlPUU$@IM+mEHI6;ByRBw_NZ`->R(B)9 zA4l&aJB`E%2JgP<*~lU0)OQ+2gN|ll`jRhVStuOIS+dFN(HE03DCw(953Jtk-o^hr z5oMvscgXFDD{fR=xr=SC z<>dl4NxyIe>;K4BsnHo|DQDmC=|G8% z{tr!89Trvlv|kaVluoH-=}x6Pm+q2~7U^z~uBBN*N?<8zr5jvYK^74trKG!A;ydr_ z@4GJGj|KLeea<{H_uO;OJU~YT@U@}X52NtGBpZnB)IM|7R=L?3eKe)(a`-vus1*2eIi=Jv5&eHTelRhqYTe$v3aS(Y*IW4QNNPYJA<7?vN-aCF z%?#I8fLSQZ(z{0uIYI&yZr~@_QaGT#@0L-X(scT&a1tWZ+AH=kVfu?Yh6wG|xHol; zOQpY^6Kp7K$OC$A2_YmMyE_p{i15yvLMgBK``icCvx==2w5Zt47_`fZ`Dg##KQuL+ zF-Z|(m5QpLkSjAO#@UZ~-=9Vxi*#%)f#MgmOv+em%~lC9qyn0B42TKWxqoT>qJ7HX zCyrb-x9bJ+h@Q*77*>lbm5Jy9qG5monPTH5qgg|*K4M~a} z%v%;Sbo9oOej%pzggk1LRYR=}!sXgX$PlsB+<)z`(t*>=ni{p{F&XH8_@%3^p~=@& zYJa2Fh&4G7K&STum*dD5N#QRgVXV$Q9o-rhb<+W~@5eUYxY$K&@NhLC*kLM|cR&BP zjbfyiD3ZY(XdK7lMB||;@}>3B5{iD3E9Xn9Kmh9Qk>Zc9u+FCY{E^Z+1%QTDg#PE- za^^*vKn;kKeGapaC+JNMPN_waDR-U+$3%=roYE$H(Ldo{viuyIw>$}FglWo*M3}{K zy85_*Ep4x*?%#&{L2K8`Ul&pk%Aa6csg3~$_io^D441=`I2D-4*5|7)0|_SxFIT<< zyW9)YC+*Tw&08Y~b&_KxPs(uaP70;e$EMR8+h|5>_w(})Dn4|Kf=1_Ce0?h!TE0yT z!e{8m`Z8a%)6XRz)k@`uwfhP-skJwO3U7?*t-abJJp|8o!< z#c*|vfBz5Z=&~lzdsBbZ*Gjwobo6_N0~C(9H%=+RECSq%pM=Tk%aEYW||l1E*}lyJ~`6U{p{T85#Lj z8-8B#!-`pXr4eg=p&Xj>W3C>EBW7B-x{QsVduH5_H=L$sudT5SsXpE3` z=Qdv#h48hzL(E5yxW2xAN=tA`8>y~7_#IQW2fz4llKkBzUT za7@Aw!{gaDk9vlG(0tYCSruK#yQMpIjJesl;8e)s`2oOc;2086VOJ&~IwTjSpdbPl3ITO1b~o zx?zn!iu@FYum7|BsYPTR)v|p%79tMQh$yjxg%DyLJzIl?NDem`p}qmGwoQ zR1v@A_kJfGwqG7ulN_^5_6oHLGDm-TXMZAGTJBnOocwZS`5q@mc<`&Ns}y+wF>MFU zaUkBj)fr(WPoAQP(!=>HoU;TvahY-iINxgwfp9bNOO!7Fucf6B z8pVW6%|1=nugySmvvIu}3ITlpRb7?H35K%owVou*_`g6z)enuTn>SHSBC+R(5}Dgu zgAIi->ke_&!Y8`QWu@*f&|qw)VFcFU=ZaoKhF!mGM1W#;JE&f3eaWfG$Z2tTVf_sD z>cHjieNynFkq>=uuMjT|sE;PZqL{t<@ZW7Jl5c+uXiw${_hq6rGp+>>o-9c&m-^}0 z6(>F>x?S`Q=DKS0HHDiV)aV&s&2T0ITKW%fr@wHsWmKXR)=d#hOB@zMSmqFe7fyS7 zi%A(7xvTwm$__H(GD!p2tPADHNm=WsH=6}XO)x@0|YMQY@;8rFHvlMgcy;-koxpK> z(em3eofVzS-gGCwTVy}l^8eck?FqAl{I@3UM8wE$39g4JRG^c*E*Gypp#ppv#$3PN zj;N7*b22*22teU1!L~+P+ih)ROS^#>kJOv=LHx-m?@5)rBt?KWyBY|jza;JnolFg1 z>5$Q8+Uu4_OVQby4BrdCO$$HkLa7$1h3oZKSk6O4iRpQQf$6H+Tx6tuD#vAGm@Efk8v4{%Bk2}|vjm7Gx6bMY68pQCSHXy}zK>P7Bc zEOvC_&>Ju!)>0Au`$nY%4=zojUarCEKZP#V3tfmYF2>AEfDm&f+*2?==R8+AW86Ma z;}`2bWGbn-k!{ZNH|UpqkGt-$!|A2!p)1##6OXCWHBYAAX^LkUciet+TuPjF9k4py5~1wUU2 z!|V%pE_7FtNi59i9ioyrGTPkm%RCuQ@F z`8rBoQc4M7A#08>np(YBObq<7ZCWC`eTbel5nNlCP-2D6@l;mOTa-paSB84g*rMF3 zNE#P7_oT_olwjb1z=%aNZ?;8B{QiFMi7In!Esl**`Da(}FW0|}B`<%CK5a5V4bjto zbXo2g8tQF0H*G-DLsoacj%-b5iRHhmQy0)l_E5 zh(qh!@H9;;M8z#jL9YA#KYlGdydJ=Hh?+*zmvpB@#qnC|V=3S^`N+{n9rxRbs2Mx7 zWTKL2XvgB%2wz=2y~be699$T2V;Yd%CLP^)AkUC{k~P6}A0C-;GWIh89P`iM1E?l^ z(JWJuKyj;U4pwJxZ|4_OzbDQdp9e2XdDm|ZDgo8&WzvfP?}Xl*&z2?Pun``-DuO7j zQ(n*f50?*;oFI7%KXod$(ObK#o*ZLim6N2BVh=tpRb)~(P4&XC5QY<&TwaK6-PZ_* z*{($>+BF!eMW->>-@r7k9^G5@-z8hN8N5pK7FK4*Ek4E>t3E;nBsCx2{nPY3uz?l+ z0aWc*+MmzMw^7!xAnO;)32)M~D$4F^m=3%LdX#e-S6_d>rrtPt5c|e?YXa=4${vJ0 z2kHBq9LumMy>zkhtn@}=#p{$_c2b;14(kBz*43@0I}~R^eW-XhO3>b3?wYX}_cA|| zPr)g<(Qsl48_j%@U^6W3s@TgT-^g_D7v82XJF~a#UkCh@j!?s1Vf@WuI>#sOvM%Gp z`ucW*jtp>ZFAKmwC>h7y1;{E*gJM;^Xjr-IC`u6MnMG?65UC*7iw>NqTaVV96TCiu zRm*2|hIa1uKL;ESi$PbCICFHx8k?NnqPTkR*?dZ;7TAh493Qsmfx-sMx<<7Q>&M#$ z*|bzyczyp}7iW*xH3P6~9zTE=oCSp;1K{m-$lUA#Jes7rga?O?K#4&0>S#lMfWvMk zgfrO5co6-twr=Wnf!ce{S&|v?b5*uAz$zm}$36Oop{jul!e7Gg&sU@FiN9&FqG;MD zQ5Ir`th^bit6PUuoja^@3vh7^(ZP^o(dp3Zr#uJe@0seMUjg#}!=tjg3{^HWqnC=p zPv90fbW)W{6L(jK`NgIOd?A=+4X0VGS@WeiQB0#7fw%=)c*_nw@z2G|*|sv;tz+75 zo!K)(lSOOLNI81cuKPo`Sj?vJm4%z;obP{6KY4{J&0+qK^5cX}beny@3`s(M5!28g zxV!Qes%-4Ag1R{14!sy49TBmV61DRCz8R5rRe{I-*FCW@SE>d5uI4o4sjHo?9nI}@ z>E9;wfA+b+<13^8v?e5|XW@w0DB`_Ms-OvpkaHgw!x(_zE0URv@!zi0#eaxb_5MX+ zsfZQwXZ}ExZzaTfiUDISR3TMWTX5c4RGaYoa7>UAMr1y*APON)omE@Phes0Y zXcA~C%NF78hE0`=q(-ezg)`RffXjI=FdHJ*Ng@r#wkwb)z8D^H#QKC12D4;~ChCWL zDY~;gIQ-;!yE*AB4i4_AEppFsH}~jf{v!2Z=93!?Fo&qd^4Q_(`PWEzttkM`a{yII zYNO!pn9^S5CqCJ=@6zZm@|W14$1)U4D z8TmRToKm!o6>J&xgKRtz-h!JvrUaFE{&$f@cx;ciRq1{LL$A8OdnF(CIxim=td9dHM(Q_p__jIXKv>8Z4lm7Q z7!^v(lo>OYjW?3GGRJHsF&V3m&VP>Mu~fOaTtU~aQQ>Z1F18Nisr2>m+S7{u(kpyM zpbs|s{r32+*0*8T*k_70fRK^;vui?);2UaHwb$xGa-{!K$yID)-%ZfQVlg~+mIf7D zgv}-!#7WAOF4Q(W#O-2}ID?r6nUN$%PcuhF}zZM`so@DT!1_?=PDU&0- zl!|`7bLv|w|GnMufVt7JzHQBvyd;bU$u7Xdx|_=X9QBwU zolCTi!>-=8o5ki-p=2=KVZg9c;|q~xE7W{QjY5#wkY+V(o-PJUD+0=$Li!3E4V@|` zroDB>W-n^3l}Oly^ZtRysLBxbxx2FCc?a@?iU7Q=+kG?X-1ff!&c|hACFlZ3HqFPG z929)$GCD&!%l6C^ex6&OJZIT4Gu&l;$)5<^z@Ott`-hWv22;u?3|ltN5+2^?!iP2Y zn+X*<4Z~@>0FJ0}b#ha|1s+N)8`ei%7LEGfvOT0a1_XL3Dk-|!ajaPEFiTPHSacXi z0f2*QLfn)X7WDv;CSVeSjeI);`HXDml$4Lfv5XpyMuCE>yK&P|RP2zXqC5}K_EZZz zpK-gZSCr?XT-)bG|6vH-c!X_w$ZPza{M1IUuj*^c)C`*i{9??KcdEeUso&S=8JWt6 zySKm?a=qZ1LwF5lm3r}e@58S?7Dz6VYEW!M?1i7T2Vc6t5&eAb=CwhhPRY|by5;R` zJRwO1c>@38_-w7>!5Kwn4S-cMOvzB0WprRV=Qav+vu%qPTBBns)>IV7ORx=g=v6q3 zpuQ6bv!xaTtcS-zD(}h37i}F=2uGs$%N<`+=vv=)I?Hy^TNoKRnJ}fdW_xLhM|dmp zSfXVHz}?$P|GbSMRXqD%O4=eypQg!b<4deX@LJuz`un2PIFKKLpV77M^RC0IcAc6J9J+0{$1ib+&(;aT6ueZX=7&PE?zBdI1o5tthUwk-@6_<)@= zyJ@w8#;}{+3ly2qe}e3%rgVT<_C_91GG0BcM33`EElFgmw-eFJ!$u3!43SCtk0)KD z@JV&r!$B8ecUB7og=S&sBGIV>29XGGswRQMm9zuu&&5SiPZ)t4^IwWD8~g<9iKi0Z zdBZq}mTcmZq)-iHI~1DYuM+4x!8N6Z~$=Mq3O`uN{U@C(TsF==~!N;1R@{C z)`O*!$FJf~2<4%S><9-UOu6LD<9{EUCbRfy7M^t#BQ$xNc3&M(K=X$bm!_J!9KBI zUqJ#7+&I>j*1Je3k=3hrcPoxTA21V?t~3hPgRSO8>Jo5H-j1mnIRqrgr)^uiQNgn= z-Wc&j>6)D@`NGfR1T`hS%HsH=!gYA9Z9SmE!wd6~u01xq!K(s~R73 zeHj6po2C_o>O6BIp`?`*<1;$O98fkHjXTC9{LWxKqp3qMS6#!f%{sW_JYXU|(xjl4 zq-#=6%fmp(D+(6!O#4rHHE&r(%kl8q9qJR$YLVP-A(SS_a|MDH~VH_X~f(;S%D) zm^}ZTBM?L<=~XHnTW`?u%{l~g`~CaWX>8J#A2fk|S5`yBZ~R>!J}*iBcV*tEgXA)R zXWc4G)9!A8?CkK?`6}i=r)uvlKAJO4QudW_Ff&pl10p~^)CNNqpV(+H3^-Az7l}is z%)baj^Cf4jOd0KOPB8|0yX-C3wKfQ8hk^}dzUz+Gn(4bsXKYKlQ=6R=te^TXh4w)l zQW@KLFJhgKDRsVKpp6gW{H1Rcdx(mlCo~)01Y`waJcQb}gl|}iBiL14ZHAcg z74v&f>PEhoE&~HCHuhal2eI|7ehC-x`2bsuZf&ba2Y?uH|8@`%O(rO>4>-pV_8CKL z!gMMT;STPWVG87znj#wq%0@+BaDoj|EbrqZOgix!X z24BP^CoViOG&s3GL1n{G+oil(SdXGeNL_q&4GA=WH2JqA$1s=plZR;Zh@+DzvbZ1b zvw*=Ppf5*8|6TU!+IQ-5zJB*hi)Wwz*wUqV8`Y%7O73-rtZ7th7sOaz0Xs+%xwJ@e zA%+WKs~&Rd4iE)+bFYQ;p;q(#1GAfenn4Fs9LI1x}S`4 zX~Z-l>n1HXk`HCxTytP4J|Zlmi&#Clx#?9|`nwIvVykgDEs)j8mA#9>W`aK(pSL#v zj(YQ73_9db8*K?JmNJn%WGy(NA23ItNZ5qyKEhGW8VkdyOa@<^e^WP?At+YeAd&3g zz((^j?D9ap3|Iigrv|FGG9{hxq~d zO2qhLKi~X^yZNFP)J!_9uEl0e?1`&SE7|S4Mi_*tUM~97Y z{M|4S^r5Gk>&tga2(#JILCj}z^?23WVD;CDJNe0tYijEhMV;v${}MQ1`PYqJiZqVv zqCw*OBA^!=CAQnj1`f*^%Q2+@{0*j7o8mjDk`!big;;$Egrh@KXPt1>oMo%U% z)t4nS9(Pt_RmV{=kT`g9l}1$4ff{P*PTl**V*CjrNz=5I6~0Z3BLURDY|ajQ;~AJs zti#@v7El4|^E%_j(|9W~!o^|#KM$ig0>H!B9(QMoh(Yw9Uhe$czmncIKi6IXDsBPc zUvpT)HlWXF_08F4Y`&h%e(MKP;_>pckO4FZMr!1zv+FhypiI7@-s0=ymshXHsncb? zS-^Q_JHS+?wPfz0Na)>>M$)S^sfqs=K}5Uztv_2v%y1BocR|cLM|ht^0e~0`q3#r1 z<#3l+3BqF%=6||AZT(ohiwVLlCV>NxCspJ5Zrg`pC;!pk^`9feo zA%(CeOHi1rnGs1;NSh14`itd&Pbpa#=;2*s*Kp2|^2sESWOvDH?3wZHh{|#z5G3l$ z&>@eQ_}9dbk z>OfmbPj4pwfL9}X5+RMQfUH<(ATvrUR#Bm@N$8AM?~X$R&~IoBm10&cpZyDlryma#XmWA6 zpQtGPL!Yf(V->R|d4vb97jbt|&Z6qL?Yu;{h9@uSDE!@R>z9LW*%#8OE%bqZkPzGA3am zK4>EtG79n&%uYU%Yqp7ZDwUg<4PI`gIxDL_v0O9{Q=fePMjvZoba^WIv%@r>0tTS2 z>w!E2N_XQQZmWmd&>?qK${wRgw&>#@!P&AgacUfGV4yk%a!$Bp1a zptWOy?a%E}rgWpfODD&;h5q1T+xC8LBA}`{OvqwF5|HXmpCpZUv*p4zJ_3_Nh!ebD zt3N^xWguEZhSlGu2By`w{c^n-WiU99b^?++e*YT4`EKeevcWdfkv=tYWQ}s>D-Bmp z|9NanXkkoMf}y(kv!@TT!g$v!5r{xY#->Pf} z8;mU^XLQR_imgw@4GhRJRit#vCX?zI|GnYCBUUop!%qGAR*$OY)QdlDe8RU;?M^}8 zv)=%|7t;Z4ceqPV3AAk%-UuTWP@;<~`2w#~w%bdm&1L*H7|!@`!{!>w#vP2|U&?{w z;^KPx@g3$Pl^^M>i;z ze*QhJIpuX>QrjT}YN}%Kvvu3BET{KhUk5ceAMahZH{Kj{=cyVeKiTwXTS&j(7byy+X(0uRY1=Eq$t7F*@($23{#t%iR7&-TvN7 z$(F;b{C^%6m;74WDbAM3(K0EU|WON&)kBJ(t~MFlxLcaID=TTChq)x1j|VG%)M<6 zxo1VukqzjqNR4cGNaeMo-rd&;6$Bs283*AFo*n1*++l8t=8sqD)n{;!lF_8Fb|1YP zu1I(jrWJP9`x2IuQ!BjcymL-VFCZo~gr?B9_WV;g8-j@Tb63+esTKiWY2>tud#$r( zB%9~y>Eda0P+voRPjp^#0o^!@!QjSeV@Jmr|1;G4oP68Ksm#;CwRn$2Wdr-^Q$x@n zJ=qm%A0OY2%N3tI;)}{lxg|>}VdjDeNrM)LVb8w(j>IRfF`MrklTQcQoSmJI?Juv> zl&cdHpDq0i9cOR7n)4FntO0#F^It{PH??iw3%wqQ8Eo`8UD;XHT>R*KOH2qM-#Dbn z6atLCZV7lHRQ^n{P0~;o{@s?)%0ZmwlzCm`TI@1;{qSNJ2%D{Cp}4@GNn4qvdfm^* zZdtTb$tI25#l^Oc_UASl_v5nz962*Bu~E9F2^t5k)Rqh52pv{x^@Jw?z>tc6o?PRS zqXJ(3>E=rvoZ^Bypte(Yp>{M`A#P9p~ia9-In+6JXlyGn~20kH6kP(hZ9P8y3 zd-hS-KB(t*T7%vS)Nl(Yk4w=G+ZcQ9RLB2hv+UAw4y%$)96$3F*!}3%?I1;^6`xL6 zXisUPigCLxq)&3e^UL)gg}F+Sh09J+8ZtEkN77X2)prD}@HY~jY)k;+v=X?>(s1;` zCRdVn2A}Tc?%TY|=RuONw{ekd==U8ua1zN{m7N`dmbX+qBcq<|bw5!?!kfxf0SvHz zJ!tBMJP~3_aZ)-UuLNhv8+4MHbn>g4}l|HkN&)LK=pEuBlPOfk@>;y z1>i}T4$?Ktl(3>8TYV~NFNHAKe%n~^3@zMI1k^wAu9ib;K1|~pr=}q($%IyaX{I@u zo^jwft4$3*t+6|Or2hK~ov)=sI#W7aT9*D8y-f~m3$g(@3SvjyIe5e?J$Z3W%n+(a z`{FKD5~3i3yb9;w&Qb8Ft?dj$|1xxO@lq9Jxa6JZiPV;mC@+H#xh>tra#$!|TW8!i z`*kM(_4r8d)7^O-rOcB!O z2}BwD-UqAGCc9K8C?|_tnoY`Q-C>5Z-a@DV0+=`%yLyDhA<|8PA!<3jp2#vyjY4rj zWcz)hSk&~y@n^C}$r6RJ;a?jVtlvX8%zY@ouReCI?WG9>`%? zPfih2Ut{I5RTY;5Em0i0Oawad`j0<@PX@3BiTu}8*u=?D@fwv3e?W(J^ges(%%Xt3;iSYF`Y66pa-E;#$$u+dK{fe7%H;{)=LPdU=Yo1#{ znT1^{RUb+8Eilp_~cip0ICgG--nox6(8df_l(&Y?0>x{;7-MP@{K1@X)0+Z43*&&EGN;c z_2hX0;+E(?f)l+wy=z`pI~M5EljzGt%u9B+8=xiusFoJ_B8iQ;o;ka^PP#&^S0&Ox z>PHMy{VK&KPfo}nrhs8D+rzVNY6kG7+D*`B5@WD5_VfmWL7H-jQ<$&86ZIrB$adg!+sE3h(iKVCnm3VruLRkXlx{e83dsu0%&N@tf9lOKS~EnZe{*MbGfYbZ8z0aYlk^=U zc?Sh;3*9A*lA#TlRdm17r=`4B5hi5B?|^EpHKfR>T4r#om0H0|Od;`4+ofEj^qh3S zzy;7~5-d{;dLh_T-&^7YOnJT4BONE62aQA$3boLwZV(b_U-kBMNa%Q2s1oV zJPK06MA3}N!pd}Nz;Rq=$J%vb@+9$Ri-%1~eyhg^r#f*;NxdBFr@1y1>FVptvH2QD z-q$RHM=mm~+u8XSS|XeTljZB(z66so$K|VErUybl<|+f{w@s7MaOv?D`&Bg;xsvT> zuMV{8Kbs7V3RWiC`{-qdp)-8o;tLo0P-^e^W*AM4MZac{+ zosm4m|L$2GBINt5->>!bb2i#F0wBM1LWXS7QH8;#>7=c9dUouW*9by}M1o-kJ_VFv z-UK;z7zc_!-@JV88kQ%cqBH>GQ3`^T6h|>BRU(ij>(w^o7eLX*>*RuXv&^HT3m{U4RF% zP*93)(DT59NX&jdIJ@N~T7Evu&Ubq(v|l?nH@B0txAKx!A}?nff_E*U&bSR~iOq*g zQ+H{-e;LpI+82P)oXAF)l#fzc$VqaEm9nn&+=_md{zFCq3{AV4JJ?t`dgXR;oQp;V z(QxMHVmLjnaoC@9(CxnHc5v_iRt2{>ej}A-7VVdvYMN<4(tE5^HjyrU_QNATaaUM) zy9Z-!WiIn>8#gj^a5Fp2O#li3(Zdez7sKFFL#cMfU0!Jur|;WkKkodq7I^S*yU335 zYpArsy6AKVK*{MYqpB_)2SLFQ(T;H?sQ5($PRG~tGN94V93Tl;gE>DJHcb{co{sEb zSt9+P>4VNDBVN4S&SI`Fn8+54p*Gb0e=R^FSOdpyyGLrbhd)VR@w%b5*F$lxb)DLF zONWA1A30u03oirYM9JS0&iW*a4VH9UsZ@zH*tI};tC5-UoTR#+KaCa?^dv2Qi>F>| z%wp*9vXwhY({}OrYBos#RyN%AY_T`~6DxH29+7xixfO9Xg~(z4cA_ysabFXzp0{_JJjc9CYWR{q8M&vIKvx8>!ZNW+c$u8XX=t(a3?prwmKVwOEJU3D&DK`oJtW(|flG#$D@%it1uyih@;$^SExjC3O4CZ4F< z-q=L>!?F}(&BN$0b(`q{)jBkZErmif^UsBKx$in{t5{^ilum1xcJw0~ z$tOjgZwFJgwWX_n^QjY>3A<7gJAkp6Zv$(MpwzbO=sE~tx{;6uDXiSItRx-Ep{k4h z10O?2sI>)oR!IKX@!Giv_#E!Q0&|gkpN7=O*MG7KsKpc4hq)HUKWP>m{uxyCZIYo0 z{NZJwgfzjhhZAq=`il0snSHN%AtrPgwRdOeMDci25h+Y1S_${Nvy#7@ZieTR7;2n% zrZQDlD=|)b>t7@~OK$B?9agss5BE4Iq{y_Ix#_-=+&X#wgD=r~VvKLC*+HLs5FMyW zd5CD-8IF!&OGWzGc0FbG8~k^%)&PW+sP?*)tBI4LD(BEzHbY7SE{n809dMiN9#-xl76BoT0>Q}zW7LH?tfcL{r6m-8(1v6-F3ED(*sc2#FFOkE#S5Nyy z$Z$HJ#Tl)BI#(>|Ao`H>TVY==u!_WtQfnZQxydcbg(a(O)JS;6L>>xhA3EL0q$NiA zc6=1RBhl76L=sKLIF>Cl?731|M}9&p&i&di;z6W7NO$MC3VV{E&~5jAC;mCghHdN2 zB^Ao|d@#dJLRRfunK71@N2GM&yt^A5n!chwu&fZ)F3|6*Ie250s&na>)~!<@d!pK2 zn-odXF14HINM;!DWCDLBvDfm}ESi+nF#5cmdpERW3~a$;_gfE2`0s*2P*EugVcrZ1 zSP})L0jZ{lnM{eK=sL`pW=Pbc8k{!MXbcH+Ld~>St_OFdcY9^ zDRa&m3rFnUo}NLu91e|ds4wNvFqTxJ!m!w|s0^Rb`@@<|KB&+hnDzr_kDWnDW#@j_ zW)N0_VQ)p3Vt;6{6*G)kRPw}^VKqoc=A-i5s+-T0R)S3U`G1jwmBsP)Xa$E@(wjzU zk2kP_DJYWnQSHVrJV*D4MT9$`Yu0n%#CiiHBOPf zz7Be;nPaI&pzS&?$y^dRL-U9f=JoLG5|_{{S&V4+Ih@2L>ki)4;J>!A1E!78_+Fkj zk21RxCd~U%GdDQDemK)8BsDZb`U-1^_fy=RH*Xu`U9rq_=7co`2cFi?7u@P;pyv=s z2%UtHT#uaxl(F(th}s|~tX0Zr@P65X4yiXX2YwbkiGjpmu)D_zFyX|jEIC0tv2pbL z+@Vrnkynecd3t(k%3B+3#OM)A9ahY-y&Y2)i39Gkg9mlJsyN6j9rL5sk!82@qDzrj zIBx8iH|wjX?=lV%RZ)7pxT!{H{q7)^iKVz+6lAcGzUqVZLdtSO(m>ZS#oFX#2~X(+ zJdjdW>6X99NRsR(%8)->SP&{H!(V!Cw-U0y7XH;^?1oPx_Rv0qL=cM2ba1##-qiys zI6bgP4v5eSD0f3yz=WcCuICxuWYA|7>T-8WWy}In9az$VGMBZqQ9iKcu&c4*$>eRu zcTuT6=El2yDn!pqItTwHRVA|;eZSYz3goORZNfOQ^r+|rlO!Af0TXa86^Vbx2yHCo zK@2;r9b-qCypl2e_vEpphsu<(ETk^I=XoIz* zC)n5~;WKGs74n}}UYcrJ$b0AUDQh4`D&S({Iq7MBd*uuxt*x{?uy@aX;SmwfC=k6_ zCJIDG^NAqt5)pB3P`qM%mG=7Dv1PQpviMgknrRy33N~hYn<^t0n${2e+3R&!z26md ze9eHY>-}UgtH`|VB?=Tim~6MS5pj5=dp#5YdCh+b40D7{q;=vl3W1#0*!0q1VuEz{8uZft`2HCNCAAr9N@ z(1W*7XQ&;Za=}Lw#9FFhQRpY^EI$qZt+`?UWldu{U^&j`*TpEXUzm(jkKX1T5xF2r zZ&1pr5(P~kfdL83#8mWoYRE`g=Fqk??Ua0suwHQ|B*8u~^lP@)>LVy5TYVg1QA4tYnijgyN{>DisDslPNo)ARY-D>U;4XYW)F+O2hJAm)>eg6j1P>;aikYB zRZlid-sW_kzoyhhv_I&*+8?L6wx3w`*NLRXZU5{t{W7H&*@JP^{(Fj_*3ZmSUJG7%3rpL!%j1%+Yo-Ibgt#y;)_%}Xe~+l zwvz2KL?S|Cp#4S(HlH803x{yuQq5>#gXN9W#Ltpgv8K!Col z-ZTXHXZ|R$r63;cUY^g_csDP(T~;VBx!9^dkF~y*4Cam~tG;?BugnGxXklEXJB^zC z0Q{i{fH4N-cEi_5ug@(HJCo(_gy z_sNJmX0=*x^{zpWoy@<`r8rI=>)cBha!J-+Wx5g*>i8%`0a1vf5QJavnz6p-7!1u_ zLU!|!`&Lh4%;55C*S#8|v_*J&L;+!j5Osg=+%#VW@EAbZa;OXYoe*ShG&o{6y0dRK zcn?T6Cq|rwVj6A=yrZE`%+p|DypmKAJG^dsH ziG&(&0c2OZIeU*1lfLuTGh4u3*GZP7{%p8^9NVFl%s;CKR0! zi7-&fo8zcIZrf(zRvh~1XXIQzy)#;23`}C`Njk4&j9Bk;z7mQgmru@p=TZ3Bf14fl zn!x5g+G!O2_~-3&#}#+ZBG9(H!F94{=_@fSgmx6^I5}OI=iPBo>yf#RnS1Mymto?M zJ9ue%t5=V?eNL$k;bu|)(@OrmOQDe6n;ZlU<)970d574}Wyp`^5!D;tO5kv*XQz}8 zuKJXO;=7l|MV)4h*yVFaRV_bL-=ckI%Dwh}eT#A18fnyF0ZjPm|H!o`fDl1O>`L4ID_#Gt$dhMI1 z);UaNpPimWe$&#PwL7vBC$+Us5&xa^Tr!iOc08jOgP|yiQ?)TgXGhsqbuwH!AL3#f3{Mc8I!;9O6p6R&LZ@bg_5x<8ebeM0i%CWl&M*(*@e&-f9=`f?_&6%ZH5WdP) zZb+8kZqcO{nqwnDX}1Xtn%sVvCOy=0eUay(NL)?SY}h-k*JQrJj^XQkOwGv$+%Y^^ zzk=9RNr~#!w3eOFQi?YOeD1jVM_BUa4HihxCKG8<8p568cjygz%JPDQZGCTRmpVfZ6JuJVxIAQxXge>bjzk z?{_oFudcO^&^>SV^$B0@Tn51=Rm#^hBn+}9AJSN@(S|`7ycIu;>M%FQ3)e>DiEf|Y zNjhDpxl;v2@4R$Znx8sG^F;0Mzq|FwxxoDRQiU=gBMq3OZi%`^TF~`^mvwyML6~@|qP!F)}j!&0G|&_XX{pH=_83Ig|fxt`%-MN{X>FbnFF3&to4_zA2DeOM_hrw`lqpt^j3R#9Q*tArD;~+?Pyy`m#P$K1(e*+HB5Mp zY;fzWTP+Ir3zr^2BL}{UvK1GvehfKDsu41R0~GeVQo&}DP}_U*-8#m{z)k*?QsL8*X@!Av6K#fUP3 zB)sAuhP!ft4O~ho?v;|L+ekvAyMr~^Sknvi)X;ssCx}F0&Q{7lqY)_SOO z3CriM5y^7SO#M{{ye#Fx9`DV5|BAXB(^E!EeX+nyrPGH1701k}%uik`<5!7A1$>Q- zkEqt^h}(hQN~QY2ebO94t9MpEM%N0;1q4$ea`SPNXCg+}Z}P@?m$1@(m+pH-z)Tq# z{ZRLfAEh94a&d}F zu^<5?siID9=e9jq)x!81&m>77KVyE~T(m=vT*D|b2gAmjzciYK>lwccBdv(nN=>21 z=Djx7_&=JyGAzpO`Fyd*x)B5gl$36UZls6qk`n0-k(N@rL%Qp| z{ax?>%UtvE+~@3AYwdmZv0|`{80KQmEqo0pF+FtYe?ImsKj2mRidsL|6_BGuE{@;$ zO6c#*6Jhia?LIAih5@=@AWqC@Mf=TMXcf`Z8=vzP%iYR#@n-*XSy)nT!GS}im1D5xh9G)hX6aN*jDXtT{}`}1L!~E)omG8XzkHJ} zGE_lOTqd%FY9SzK$%e)T9`A!W^T69Mt4!SYS*Y~9}LpOV^G3aBoyrm!-4 zVC^*vW{yTA)qO8Sn5DOtsiWXbC|p+j06OSA$1s zGg;&a@5RXpNj!QO*m;mJmB8}mEzv`fSahzTJ{jrebVZ~IELlN=tMFhqLcV0>p*`oR zC3}=7wKD_v5qc~3LVCKRG@tsG9*aCKl)yoIUmsC*630Vaaf6GyCe8n}C{g{nbSO6c z=;~Dy9`1 z3?T!ZI$E``s+SuqO9=s?AI4fvYFSoUn45EYTu@Gk2H}H=phA8Kk{FDVFBHr45uI1-UAYGrS_E{Qd#Ak(5~WH@9pt z=(hhJ&|xwqm(EMOAyP$s@#D@+T?{<4b_^c-QL8u5MR(4#m?0*mnQyVZk32SPW9g6r ztBKEtaZJ9WmAwDfXl!1=PjSkHRiSW7@o3B_T`}udrt@x3xiK|UMLli!+JQ^>&*8c) zGV|HvRgo_19STfa%VBlBj?ouWeE?d|nq>J#sqzYe_UnWp_P0kV9=TMLQ>brooY_;L zfU^gbv-FGoHU@nBr9vToY&qvI1@lYqhyE9YDP1r;KKCyd(8Lgq-wBCtjpF5GV-;tU zb?)Z<^zi5<=ZAh{=+jv<=B-+~5m$6LqA;%s;g?Ligi0|lJIbY3K?}y!dXl0lP^M0I zMV*4e#adC^Qs4fUN z9^Qs7)Ae^ju``H zylXYzXgiI+3)tWw!>9&gO(pJggTD8rK|_rZD>q;wfG-@ZpSVh=OZfPExsv8V!+}N_ z^V64t73p&75;7wDPvJHJ;(<*=N0Bph>m_gK`&0M^`zVi+uqnP&zuh3jn{#rFQlevC zaa@n=E{iO5z`+uY`_@VTYqY%&*IF5NcgZx~=b7`uC_aXQOpex{m|by~?DJ|MWFa=t z79XB8865*$&`LHH^WuK?URNoY5q3cet>69ZLATkZY}ts-R2v3Xq>lc2Hd#h>YihNM z#i_H3%RfaUaH+y2JuLs<#2^qi(C?X*pQjHl{Ls7_U-emWav4jh)=V30M0>oDCP9sn zr9XXD@0+DQXzxj6edyOKr8sHFf~M6Iw%2U_@SY>r;K)kSeRAhLqCOWb!O$H}pCBX1 zMx36#(ng+D9L>}ga-eJn!c4A3U6l@F+Qaw6n zTVf14uw<*+PcSO^+fehHReHpc8TKNde*c~*e=2OZPnOvkj`%rCHvDcs zlnli+@-BUGOND9EUF8x|a&&uv<$EuP7f*p0LBz35@`S=wC6iF~KE^f|>cukQQzdwhDk`He_LI{L3U6|)o= z`?BnyKq=)?5wtqFz8FR+U^LcGMH_;nYi2A8b+ZCm??)_ls*&+9HUy(ZubtArqtNYsi2wM=R|1bSwLB>s_x@}OFG+2?g z`hu+Rv>WNS9>eLg2()cC9GleWbmKfU|8bcR=jCfIkW^EdNb@tP>hA5lBaTMRw})EQ z2=aDN4y@xyk=izQRIRi(;t+8ifh5ZQ79hPJMJ_y@gJl@vuZv^V??9Dl96sCWIZ z+7n603M8FuEn};Z&67Kr8If^UH3b+e1|GETXGnvCk~Vf`Fks~`G+OVFkKf#|cl`2)B{R7z59S5^FQ8LL+FZeC0AOwydK0G^#YYC#V z@P#Q6({u0o(_5quSt`P8#;ttb_w7&=9}*M4n4OAkq}E4ikg}e@zgINrq-mopB*Ee4 zV6I8md~KN^7c(C5NQ_{JBa1gn_2$4gMk7wp4&3`bIFmgtm8f6;a^_kVs+9D|>a4uQ zn5=jIlaeiu_}Ax8YDz8&zLdomt=QUrixO9-i}NqES;Husdf0ziE*QJMCeQ%T53hMw z+i!j2cg)3GQ78bx43Kg>1aRN?CM_n{&AN1Z*$M_(F4+X>c34;KB1h#jW=2<#hAxX@ zr3+(C^q|?kXPlnuI+e_ZS5xN1ky3l=b!JbrG;v^@9k(d6iIXJYUY-}|s-h=S_%V6U{ZmI^*+Z3)ly|O1 z6$vIX@|LD8ZvHG|YbuIeCi40I98e<%23TM9#q(|}U>SJumjw@!X;iWSw%Bf?^X&+#<^yco9DS~af} zz(_Z%dGc}21QT}9Vrj-eaIiw1gpR zg8QWHBH2fNnX5MQ4TepANH?%-z!eI~7D+Iu7C$MXQ_hdW;!Gp=lMJA{j32wW*J+VM z>|)Bd^TbN?rgn*P9t(TuTD(GLs>Q&2N2#0moZ}SKU>VQH{K}GKEng8~dtXW&0tz+? ze!Kb?B_^)lO6hglM>R)z5*HeIrFoaokGfu^8ePBKX}WCzfcD@6Xj^%+pP3;2{!_E}aD#O8W^@oAM`jxi-$aIg?9I|+apfd9eht7#A)%|uhF4)|)ItR{~$QK$dWZt!Qg=0V;v5=DM6*GudQmIAi&0KMad7Tfs;-=SFjqr+;_I5ANL0C=kK-l9B)v9DQfv-K zrMHie-4LwT*ZAtmzE_D$sk<6bhQS_# zT!;yCp(tc0GCSIO+9#-ae!@2Gp81)W(Ml85vFT?p-V7{ei^M#f8I7O@MCVb1Xj&_L z=!KrbKpq^#y%?Hl^A#h$_SPT{SIl-GkfCN34XN-Nc@ z6J8U%nMy{EBr{yfV~*%toiB;Cye3cAP?w&%6(n5FmO3tvMVBv=^jYr+$nUwJB32xf zBqjgX3jopDmr}Mai-DB>J`z$#I78zcImqfWlp!ww3naLW$Yc|nQGB5dAr3--rD!fK zZP?+mkh_a2$}yR{wfXEPQRQ=} zJr$H?VO2ptzud*%2S3@NlU&Zl@>G|+JANm^R==+hi#9i`OzY!frmgLQvjB*20n{$| zh2lOsHRZJLC}*XdGAxk*=5BJ*H<#I78-lpKUzDF^nSd~HA5;Um92vF9p~^|;_~T}G zYz2GR&-+h9_wDh9@_$qXeBX)I9%9pfFGJBmQQ>75lG^jDOnhy5tG=*`<1aX$s~m&{ zqW|K!m1;sg!K}_|6O#-pT+MG{;{5QEY(8K)O?*g~z$Aqz6FK`kqYKI<3EO;z7Vjli zWlR1!{uHN_ftEi}!XUqkl56N?O;j5S%%Dq~Ri65Kmqd?sad1)IjeI=T02d0Cnd5CP z*ioxfd4oSngThhjT>XV)4G7;z$cYXHTRqtWP{_A%kU>VrD`sd)id_o#bpP?YtP}`S zEf_ThfHyA*>$=OcSY=Mc6l^2~>8=z0J9Asi`Oy`Lm9Xbkn2|5g6VR~u=S2kRoy8WJ zK7{d=9$LaWdZP@H<0sYEq~vo7APv>l$(_h923Rb13$~8Q#U*vcXnw4eEOmC8iqkLl z&D%tx*!ID6ox+1{ZQanzezltnEhCqh8C*qPt{WroA`*CPUJ7{9>p}CEoS##H@`Bba z{`fVV&OXH6q!K$kqlE*5!lf|lk3bux?lXtnsDX>4C)GwFWdy~NavpmaGA^IoM3E`| zRKyU?U7c?&O7Jd+T~1n3zjPrwd5=u>oLB$--20WJOz-Bne76CsE=)fGav*}DZx z)cSn*IK7kpfVjGWQJ7$#gy64qWS<73L))HWNU zl^k;-*+3bKjKV0h)?;QS8dt-(c0IR?m<-B|lwD5>FJ^x6tg5c=gGJJ^HI9k#$VlQl zh*!@&Ld``*D%d$|XM&~^SOmeSl;zni<0?M2zsr-(-zAUw5?@aL5?T08E78sbp@oM! zm38Magvei?ksh$n8Drp!vSpGDyXjgYvgk+CSv9h8`Wn;F)WshKTViHGtq&dMtB$ps zj~b?*WOO&D(CC9Zb-n-m`OntLeg0ADpY83j)!*q8NqweY8HtO;e_5r^cqn_>| z@GDvR3V;Qd$-aiCZ`3rqs`jt`!*>E7OyXhoKUk)7<}*z{`v8ZT^1Kv#ZEsxTq%$X$ zGLX^*2N(j}LXs`0Yv0zedx)}yBO~b8+XI*~dyAwE_z~XQXq(K76E729(AmTJI3}8c zIoNM^0{NNa$_Pg>{?8w>=8x$m#FMhJaJBp!#d4mSVkikHB!N_3ot#dn3B-F-PnzgaeHR}F;WcFQII8B=wSuB_#22e4WSik$S z$#ca!9BA{3laVxsft1B~zr80*ad>IfugM|kMS@(_BgRZ9mrk(~1po-rUr;XBJ^F>n zPeFY>LLaFMjNVW)0OA<9H=s0J{XY)DkfYDOcxsn}6yO#wJ=IoTi~_96Dhn(|5}XwS zX4nXG4az!e0u~k#OcvD5kIfVtb@&aH1S=AX?US_n(T3Q>v11wgW(cCL&@fHrgtq4v z)CUjJIGW++sS^Mtl0t6Mj}Fmt5lsq>X6(&Ph@r%}_@S5HN4wM>4<@?e90SNvgNefW z!oKOS@1$-Ljh>ukBtJ}LO-tW$jWd<-KfaI8ub79wo1(#lmf!&3;qEEh!%>=14_L#H z@5+{*&-VeG{eLzp`v=-e->CD<5sF!TARbJTsZP&MsC$`Wx$3gD-?#u&H?|j+6xUM~ zDXmx7=?fJRvVEi=ZCT4&x|UIe$OsU9D`OehhKLrE%Qa?=p{M1gmi3DgZFyZw0Zowv z37vR#VQAiY!NI3_#HS=*DPK=i`9l|}xxb`S(!xhcrW@}P6d6rLc&HJYc%(Xwr}=m6 z?_TodlZ#(K|JZ>kooj! z4eH&TXhs_bY5@jzGQY6k#Zi`TT)ewU;0B-=0Jr_|z+BcwzD3oAL8R!V`cifH`u}j@ z$yO)e{E+L?95Kqn%gk~tgIVxn;Z4R-+24G8L6kpZCHs0gP&vc}bd!d-qbCl%p!l*# zY%j>mTPNfUtoVYoV_sWq-86)QR9k~iPDQe~7nzAs5e8fY>&on(F7i4WlEmihug#-n z=e+EMQFQ@A+N4Zr&i*PYi^HK4f*Ozrt?cY}!oT@emPVo^&;+i1#sbM%LpZ9ey;niZ z-O`c{Ii(K19+Ib9FeBb!B3rH%rI1S*7Q*XRfheJ};Ap3tEb@5wnNA{0vkXH7#N+MO zn)5u`O6ntpm$hzrBh{Y~%)&7^G6oEXTi9^^{kB~Cix?sUI!$1cICsMd^wRU?AHxSI z#m`E$?L)L2xR`3dGJ);w?f*H8G$S0~%BKwZ(usnyf=WG+I$Ftk3K9>mi)e}VKwBT> z2AN-_|G}r%u+E?<84hKuG(0WBVTZ`a+qI)z238_1aBEV&?{~x`|3;vS$qsH0|L^M1 z`O7n+52%j)XdVx2F=R^lVeHx}b*EeY%KMr3OfVxO%TZsd0OA;8b1ZEO7*l>% zafz&K&K8C)rfV*xv{8=Nt$8`?*{kncsE?-S_nSMwQpovc0?|9=lg%+NmtYWxee?Id zBt9}}P@0s=dQavDof_@nTJu7GK1K}Ib@S;|n zKpCd)XW$khw@RUfrJ+G2#^bXN_h56u2!oG} zFEu0GgqfTnQX2)>a+&H7@J4j)&U0zf_t9!}SwbThva8p6RqDL%&NdRe=cNSy&R5j{ z``x?nqG_NY7!ky6;5rgA zA}-dBl$e-S82BP)4Fw}4;YQtY+IvAbo^&~y){5gKBzr9#zH?&CJH~;Ulcb+|@*qZl zi-xh)%D1vp6rlj>(RIIRih~n!;je}C@81Ge8Uej)F#AK1Wyo5_y<3H6kob=Dznx1b z+`6Am;1*%;%Eq~kxHDtJLt)nU6DzAVJk#x(7ok2&cnSCn9`63-T{DQ+4V9g}no*(i zO{lCxX~0Y!;H3H9)Q^g^?DTb;eymRMSUhQQ+IjQ&E0^93e zo?AT_$5GcHJc5C_AcB*A77Wsqm0hsibIezlXiAJ!da3v68w}-zj;Y5Vw=`kzi?_Xu zS$@@Koqi`@dnBTCOgla`1?U75MK4y#>Uu~55B~s_K<(bW{RYddwJ6ju_E5sG{Wxu( z74}GUT|HYa;p|k~qnwSG4N9qO;Odb;W%EnOKZn846#M2A4jB~b3#xcdq31fIA^D~H zRhDJoJvgvv`v{KcN~xHeN=iz4@rEQpztX|7A6&Bj4R#bgm{vZEf11#z<+r7CnSXv>4`?0LN9Q}z(ky;UvvCwRR}&AYP*Vu zByb1Aqs$*FXrfW(ox+S^uLVbAk^G7T5-Y!-?k(2GNtM!|d`&#mh%UYd8D$P;AH{ar zk#ZR|y+;P3=&?VU$YoyE`zsa%QgZe&;hb?V>3=9<7w(GvZN2}?lc>p(2RN%DYi(tE zj^*(n$E@LhRwlE4Q6msR*MxkaN65rmmi`1 z>VrPs3sWFMbZMqdBP~piNWST33xItg3C*N=W=G}sOe07QgoleYGt2Hq*=i|gBtmZN%riV8>29`ME60@i57Rh6$l;fYU(~3g?HeOom z$w@9}p8b|ThG!~RQ6X9yCBznT>IFbx!wWCVz_!=4lVv+PzC{pHpPPbQPZRoth*#Ly zVBLHa3kYjtT(WV~n#e8VA4_6)4PD8_tG|1DP~QpNI!)f>-Q?pUX&IMha z^2RpqpMf7zjPs(588OI7(>wLc8!d|rqewCVJh1oC#*wnPXDW1mEy(bDGh8U(ip>z-g^$BoxYIjM<%RmZPI!{nRemr1uU~G*qxZ@$~c-=p(q_IY*w2-60)E$&MiR!UF4D@4@=Pu=<0`zOCvl$a{b zeCqVtkayH2wIGru0~u$rmqKu5i%dAc43QRJ>(hZ64pX3R*{^!{SCYys(e!x%sGB5~ zOx&mFMSALq`pllT>CLZv`EWw>kN?@wCrJv#_{X8uHSnGYBS0b5KGVR70DgNST}Tub zGiIA0bEy_;$?E+7fs#51yWigTq8az%E{4n$OLeNqp9EwW9@;IEU?`Fg&wByW)$)dV z>8kbX__4{O>Q|di3rP33^fa3b#`}pTvnxvc3!HC=s+!IdFC{KzFv=n@yGFP;Dex;5 z(xnj*1pm+{yi}*dR@Xqw9?J+ofPkw@BiFP2p(8qojuq}Fq_;{^6Gu-)E1`ldnrk97 zS<7}C)d9E`)^8tY`SxGjHD1%Uc+r3mD688}oW|8E3!+<^mR>`(9#F?P`8d3%6XF^r z?%#Gv$`J-MJGNg4bQxDWVMuvH#l&*Fwh_77}|UFN^A0k zbxDXfJpM%Ikzot$dv%h)pr(uANRm9|`FOh{GiwY-0Fo-)rGH+%F!F(ZYNVvKp2xZP z3fO8fRse@82QXrtQ_^Rl2ygWRk|aq1O4j6slpKH8jVfYQk{>#C5J7`DNRR+_w-873Dx9+?N(Yu~sMslj4fE2<+^1fy$*)PD z6p#+Jq?OOXXgSP-SezA^w1 zCEmV!Im9qt*~Y1$6dUBiHmf1?WHp>en;t4#zvoS&mzyvN9 z1hA$B9@tS_9ap{e zwNM~4?tkxp`{=W%f5BpA`T|z_hgF4ouBoU~l7&UCVnJr8>qacSo@`C`!+L{w*Vvsp5?Jv=y~XVmWe=xyG`rM5r<#}A&u`br??*;#zYF@OQq_Ox8o;^j1x zKjeCjivfZPKCd$N_}<)v^eB(01__RwkU*#=?@WNcrikr=g@K7^>fw^C5wCNeOA{Y+ zw+77)Wf9(Jz#%-!F>A)`#x$Ajrm$1QRUS#P2)F8Pk&aMYrlOTjxn3vTm6-VsY|Nd| z9L~G#a_8ci=%%|Shembx?WkOLsrmim@5_TZg0`RXN7<4kVs&5!bX^O!0ni_~|9vd5 zDjHI}IEd9>2I-O6VxXRUeQO0^%P5$A)n-aQ>f6~UwMnUeaSvz65@p{0ZAude6V~0r z`ISs_0PmCizB)>v72_l$HdQ=S*eu@OUmT?pDf0*;lK9s^`Vv_W7Eg3qEtKVejb;*J zwOjFCU!us0vpGX~xW)By@o;ly<^W%q9b53ge+yL?|96KQ$4M=Wl}boX4voh9#P6~~ zCz^PKi!uhN+x4(|Hm|=~)WHh~%~Mm-qZ1#NnRLUhgWKvy<9iGLYAoMEk|Z&JBQQn7 zGODgfLlwNOi>r3IUB#f-11PdV4JvrpoKjoK)L)@rOL7cnnzc5aR5ynjvQ?+Pqbti4 zj)dXJVSSoZ;(dRj_8Zf?`vt~9olcV0EGFAu^LiXoQ2TfkjCIildkK# zmI)P<(aS$mp-D6{fQgnuaeDy-UyjdS#*5K~*1;glRCPzkYnzZ>J*;&;;Q zEQ!z8hiKlo`updh?kSph#!)~rtYDCiCt2DuM|Wm)yD>@Y28nwJBAWe*A<}Y#UUp1EE$msF4YM!WldvD2%~3f z>ljU6V;LTOZjjmF5nQ!OmeQQpAl$nLt7(r2sKDdjA zrM(%2o*zO|%t_#;=1+iBu^(ClVY!J&^}D|~`PkhEY2(Zxzh8F}{cvPwVVJ`rzG1}q zHATB)yeuvu-(J(9nqygtIX*x9Uy|aJ99j7y_;aTxJyHLVUxx~bsL&~%tqCvSbrBAQ z`NTKzUvVRTqu&prMw@tDl#zUZn=dZIh1&Zpx5ZZ*E z;}6#oT$qn?0i{BRRv0TQPuHfqyX-?){Vy4gxhSPEfE{j(aM0se7N*p7`$(|ck`7_f zGUC<1@zJp1O`IFznz0UK#G-o2Wn#eN-m||vaJ|?D@(X z1EHgp?ry*O`(Fop+Q`UAV3IFK7zInUaeJ!wZuk8wbVc4X=oY5sa$vA=diQxF=gims`~ueTV(KU@ywDB1x+6X;!T#L>D>Tv>eXax<5$m^*z#?(S|8Z(p zq)1(qNH_ZRhtjMkA{gNyY4+K&JtmZjlnPl5sdAv`aGy``@On{gENI{MYZ5#8N0Gct zInAAeXU=H#uN@!5G;{`s^;x~(ph8<^jP!Ph!dv}?|J1FG2q$umpn(f)oeerE^`Z^=a@e6xX&^qbO&WP~ zhCqdP8`98E>dlql{a=h--0^KYQ;vyx;&8cN7AAIl|4w!YQjY(l!KQXJJ`RnAY;zw$ zD;6<|8c@4oWX*OyHAvL6P|vsi3%^0YL;ppxH=?OOVnn=1&T-QeWgI(W2br1KOtZ^Hd zd*;&kbp7LxL6>p*ehE+a!Iuv~R?{eJ2DYZG8NG}0jjJHp_z$ikytU#bh9LlLVR!*K zDEnsm8SDM;6o;cVF8$D`R=p~s7+fCxk~|w>8Z-?;Q4rx^YKdh#PDN=ukqadRa@ zb`<{UK#ZbyRUh$+bct?W62mJ}11G@A6%Q_a#<-NXNzD&;+}XzaUstSGr| zg@oZ%lf(SbT;)dFldQz?FPsn6KTkf+6tHr43Ek7rjQVgYP1RYneY6dh7y1-XOAdL@rV5vAj6<>8>& zN@=oo-wc!)E+#@NlNzC5zmXoOKmdaa4r=WZS|aoLv2L40ERC5@8xAd zkKbN)Mi|F9a+I1Rsg_g|WQk3vPnj-}6;zfjZPCOh9y%_hcTdmto@@-sz~s-L%tVpR zGY4)a_T(;=PC8#fR$MWs;MNu_&Dp!A^=%+UR~lnAnCp+}82tOTT?Wa! zxBX&h*a#_{5?;5n7HUZxf3;6hGvhCP&f7vSp*N1st(X&94fc8OPEHCw97=7jy4A z1UytSGqa?$!7hmZnIdY|Mke!6@cJn`{3N;WD?I9@EQvK+)7qZgeQwj!_?IQ+^9yaW zhmn{+-tCPzjDYq4Abf((2K9iU3iTgfdqObEJKzEGsW@`}b-&AI`Mrmd&I)L8Skt}i zGKAfmW7g~~xwXF=$^i*{S|fUF6iO(vPmFxz39e3E-=2 z(+fM0m8r8Frsqu%yGZjpRWm<4j1Um|n;eEn%09gV}?81O!X?27z|EHOuKfg(-oZ-XGvDJW^(G?Ct zZ_Ahql_}ZC$ZEz}TGjcBaXi_`uG~*te$A;+niqRN^H$S7o9KBuT}Z{YMsK#wcNdL~ zt=1g9f!`q8$#5a`iB+M&*E~wO588c}(plNjq%fw%`Z`n_V3R7HSMQFErj!CL!BXrM zfT48dkXvH;{R%ISuPg1tW&g&-SubW$^f&FGSUo%K@Wcfi9O^!H$K8Eio&+DLrB}PF zga-g24@YhgB*!QDjgNDgapzLVA6n>z(=5pE;L8`w8gwKeNM>s0I%Lnw^jA`oeyg5|G3zuFO_SLlA2g{hID zm;?%TIZ>e|D>z#^8CDiNIE+v>QrW~;43E0;VCwT+T8aq(Xi4sR#rJj)N(m5ks!Oitd)n}Z-7QQS*Xi8$?F_ZO=E(WI3C zX@7C{4q%6erHh_?@`f3f0kuF^I*20XpruJ`OfJ4PQ&Zf@{W}VD(RKO!>TbPmGtUc$ zfC`{M2#B+@+jqZAzd_^(d>E5c{+JUwlB0Eud7r721TEqua;ImG;Qx97q$>zm{c2C( zmd$#?GE3nulchs*`K?_FO`M7!1j(>T+?=F31gzku@XXhwj^2=;n25)mK|$H`shzpQ#V; zWn96Mwgm-P6B;yRe)!265F!+cnULj^SAj85SgCD64IrHn1sa5o*7Q7quup$gCgx^=*vaoGn~eq*CXd@e7_fTJExt zk6-#5+23sS^C#N|^3o4#X;lhLA2(B4EkjcAy#bl&N1jGB7!FDAzHX%y55EvV)>JEU9P6H%9d;{?G)*^oe*haq=3Lu)1G z$K2I)fC^WCKvfjoW)eWSmeS6rSOiuqvKidpsDy&KLcaJfEg|+9VxsQRhNYGyltT(` z{AreiloT6I@rDw~b*{g5s+CDY^ier1f+C*r6m{<5D1pr=da#w%v4A2Pt- zIQSeBs5u~ck|cT#eJuv`Du=HM@*US2n5>J5r5PO_1cgS?G?JHe_$@=ul?l_|WSP;- zRYZTRLv<5TTaP(GcZqEumoORAP-R?hZ!Xb8g(q}H0^rc8)~n7z((6_E@OQp|s;Rcu&&Qt0y-A<$lXtWG0k z&Z)s1{e@VzS}xwehGC#dQ?9Y_CPPKtjDNrw*DLpK3v~KiAC(+j*4{>%$;BPp(j_eX z*ifhexkH>jp2X|VJel=$)`aMjVLmdU9e#K<@H=u2g`0D-<{&nu^LVGDdB$PS?y25e zZIfD+l?AZEJ zJaGk(u!(txE6_ny-f9y?hKi;ftfLeTuXJdmor-B(+eGy&&Mm_JLX(?fc>P%yb?TFi z-{?resgfWZOyOsd_vYj|E(ngT3-Kvj|6y+xQ+*mM%2*R?G5etk&Cgo2Y3XeYK5J3q zZN~#+Mp`AMns}79VXx#+uj{sKD+<}=X>)YSqy&T98(%8(Zi~@!c1(P%-1ZbH<%`}q z>?)rE7XZ46!-!!-Q{8Owlz-(vRbdrWPtSmzQ1_WNuyR;7g(y}!E~52g6_EiHXI)*0 zCLBjOk$x|I>Vn654fPX!2xpOBTgXR~p1QQtjeV={5q!psEtiH)J0itk4_&nq%c8XvBYhmdtr%pIcKGrCybm}ffVFo77%r<%sOfk?UG$a z;7=qWo;>Mp#}lK(oMfaDOR|=9BNpZRBLs93w3}GrE&{=2a#0tMKMt-*i4l#sMHz{h zhKMml{Cv~E04V`d)~LAuW9WbK7b()bl|p_%{Apsr`DE5J9%x6IGo}*TpX}};E#?EJ zsWjjYXA`@7F?Y?){_|_I%YyPljMev`Z&my1bmCg9-eCY3ag46ad4PJmzhM6-$AJU* z%%_#5k^?#6jpF?8+%sWOZqj2*H?t^Y1jjxNZnb&V7Mf-D@VaSg^y zrR83)xWlA29C4ds=toHV><)p4+Fj>`XqL=+4R(%huY=lOh)Yn~V*EwQ^bfBBdx46b zi9EmFEZv%**}G9#kG0dWMWb8r<;ZV|f6?n2s6H3^d+Z&nACIx4>G*9xhFshVbMXb! z^qUKh-4?k;Q;NMvi|c}hV6@LU5pUu@qF2*L9K1}i5N0*_&&6*hH_a)R|IOtdAmFf> z03{FPpQhiuoo*4B$8n89J@zLjR-5Rd0)NQ~M8rR9wap}?BVRuR`u*YnyFf)ng=ug& z1c{NBoa<7Ep3I~}9hR@g*1xB9o!3R6i+gWxt&-b^phrqizQQ_VuQZ=&9WWXDCsNmT zz514x)SHsdo}`mC+{UwMYh}}MS|~|#^CM-N$HKJdo$w(`D5f2YzWT@Xh^C|r7B$|v z$$^{_{eV1CO5IZ)9NuAK%B-XuY`PBh^)*ZVZ);ay$3a`hkHVizg(I-5<@I}nHeDLg z-BhU#yZJfdB6Xw^-J!;NVtUoo*SA`TKPvN2&%6U|$#;ZOYwR&*3tT>a)BI1qA#snw z4s%(a!yfHlA`m+>1VGjz=-HN_uoCC2Zfqm?VX&8qUJ-R7W|e0=0rvs$Amd``~qm4u-q|-a;%@n z$$1yI{)S42sZU&E#HrS^*QG0yeReT-m7M0r{O{}mxtU{$W&`TolM5A1U1nB!O$Km7 zkUb2giQt8%H2V+b-@`1T&eD-6qN=&luNSR;S-)rAG{{V66laG;{0vSPp4^ipebN#C zxQjPco}a8T1^!60XL!HJ)|T5!9>EQL7E#yjZoRak92it*k$XX$T7Ch+{bOfZ`Y#_;Cy4?K|kT=1w;H(KKMf7;s@ael9C$~J(`pF~VNy2br;dEGB$X1bX$ z($AzJszpemZnva%evQmFCD)PrUs83%%sG>z1Z%mg+~?gg0#eH#>Z~aCv?~x-suf|! z_0M`|B+=a-8seB{F*|11BUMG-?bUtVZ_VA}e_A>fWDE#J(bsUA7 z*59h;sPxsd2X8f|qO=&{LVuat0{NU`5o&}aqY#FJj$}!~xCWRa$Ms=w0}={@D}A6- z?@$v_MbOFj1nCG#9hvxtMOjPmH$xE~Jt?F2nDa1g@$8@SLl} z2g&uqTd6y)(s{RX^p=MDKU5Sqvpd?WOCh~4*#6^?bF~|BFa6~$-?d*=N6!sUpSE_~ zNDO)3&;MD5Jx2fh=5?&l*3~KD zZ^wbC@^B&NUPPo-R__v=Rr$DAreWsqw?&)3V@FE6#h0zBUU;t}uFO{&8MZ(^p^S82 zL-KmJYLexgooVK7k%ZA;g{=3Cws&7RTI%9D@iZC?*qpMFhDe!HjKex`gKicY7Ihoi z(nhYzJD{SlbjWS=8Ua#S2uATCX8D>IwC3Q=X@b0*OoKgf2swIMn(!9s&V5o4bPLDB z#&N@hWT1~TSU@oo?cqP!v-t6rqK5Smo33qm zEgbe=m6G*9-JkIZjUq#%!F%@;HQC>ovi{bR&XX%?)a5Eq5*w`w=}V7Rom9WccwAVv zR_8wd)IdP&4-XGJ@K&sjEj6&nsctByWJTAXK%uU91aeofQ_$F0X<-!oAJ4ZD?@aT~ z%f???pZxebHl{#Q&6@!iep|p$ppO4^)@ObXg`ga)DW+vL3P^ zB0Vb_57q}q*zjfha3oGtT%&mx2^OK4ssf^>SU1L!(mc&Z63 zP!%KH%fh49{V2TRB4C9mZw5WwsL{rhW##nmJ(=RS?D9mZNd>f%3!xP#aj!*9^{KTX zKr5q05^k{j$|$-x8TPlhPxX$(6e|Q;7B?=e@e*vwKV;$Uz0)-ColYuu)zEzM`0-hw zquU;6sgQT14sXkEd0nund!UK_D;8#h+P_6; zsmh*UEUt-8m2~x}VGdr#qdTwVkskjHW^oz(l?aVbtx6sxrkTw=jMB97JA5Z7e+eR& zd=<;`<7msyNUj@(@)V#^$Kb;-qKB@&tk<-MR>9?x%Vx0iIb_hH$nblBuWuu?noHVk z;Qr^@{FOBqWRM2+wk-u;<>-dAly>2wk#+D(k?nl_ivzj+N?apeZPPS{a^ak=Z$IUYQSJ})_;2KXT+1fyKPMkH_DK>;(M|qeo9XQRaw*d*nDL*F+xnJ-hs2 z5J;F5VH7m+`xEOlg*c%UC7Yf4(%HDYz zF3XsIjf51w=a#-W26}Iv;hG#pKm1<(3KAM5C$z5zMvLBLoThJo{HRCo*6?NM&L4f_ z)~reN6t7(4J)aW39CK2js>4g6zY6?0I!DRS{;Qa;z#s*M7~bhmPa1w#WokgbC=QEy z0fpT*fO(IX&}8B#T@(7`BCf|C(sC=bPEDg9!3l=Tf-a>(dFt=vs{SZV2>HeEZjTM5 zn|%UjCWI`dp2TI5M8UY|r^LtEMY)*7!*R>7M(8nl5xtFHqQ73~#DKI4h0~Ea(w&-L&G^DABeEvE1KIv=B&2Nc;P6W=sJvjzI zZSS_AFw3~cdb8L*1HFPD(%g|pRdZ1fXSl#ja*DJk#WJ|!MC7ZAa<06MblDk)vzZY) z*POvZ{lHsgG-Uo#qIrDdxd0t`GpF~&?YQe7?c>7f$GR;dHASi-Pva{@2&G)n6F^e5Z+!JjTb z#8j%fS)>E*gnKGVVaNa7bV$x}RyOAwCEk+$&SCFDSB@Y#fsTwsW~-2sOkMFwWL3tU zJf9nk{f@54a;D-KBbL|rPTIG5m92$F%KZNV(JC(0)hkw@QIrpO5D6Di%`emBwJAEx z-J_6!$5IaCugqf*WZ-g4VzDUFFb_bcvs&a8tEd>yiT4hqx>;kI0+Wp6+T=wa9|u9h z<7NRB-S!c>AS<55zqX=l)>3Er$Y8#XA2!DPkDgm zdc96X4N6z8ULz$|KS-jJct+XJy>rMTVcN?`UX+!p$#EYcd&=-0DiyMmQM$SZy1|MeB9`(!|T8f`xZ z(pRe2>@mcq^^f3lWpn%cxFFgmCj!R)zEr9;BF(so69tt|G_xdijisTF{b<46)HeZ!S)`?phiq;;TGJ zvR<5*-Pds|4|fbA4c?BGSDWaA0=|C*8QY6Y0gBUZ8Dl;%mz*ZmK93vQuhlj7a+VTW zG5i=r@`~p2ON+;nc4~u-9j^pDfYZ^q|(XBUQdDs$lzIl!Q@FE zleM!@y+pOezFEycfg=MEmvW*N$Ubuh19(`UW51}l#05WGVQO#ZV=rUU>M~@G1+jX% zDZROD#T!-#ysE#LOS81Ozh)X(KuYKOLlDZ{= zR5>0N97>lWJR2693^>{J`vg}1xUXl0+R)jL>>%c9+zBGr$M`zB+| z+pwP>30cj36f4fY>z)_wvePFT6GR-_Ca~mw(hey+rA}u*|8(^oU#@=TXO+BCC|W|` zkN1)P?^AEN?{B~2=+xBYx8uGNYmo@|YL|6G5&K?m#dA~apPw)35S6ri9yjYh%G)+Q zL2N}WW^ONZWq2{Dh3yxhK#z)>I9BLp?8_=NFG&{1c0nxsM%hC2Re{AGrhHCN64GD{Lot+$`W(>b<5|93r(AJr`5;di}q{ql;lz>{Sdt z21$8H^SQ#}v1H%;3C976dc8jGeK2$oYL%1COt4)h;bLE;vgjFu-9&&9w*6D$rBzp& zomj1z#qpX}m^miVP4#;_Xm_R8>ZeEe{9&4EPk0Zc7Bq96T9xBl=?#Q)j*5#p+ZFTEy7CU=eC2r#ZfC&s=gkVfD*dw5`Au_T)K=W1h2K zx5wTp0=s_;^^(N8@DeFIuBA)m#`usRWDL|yAGaqD=(~HVdZc)HUttLGohyl-9|cKP z7KBj&{jkpi%~E^G2NeCC5dVh;^41t9^5!FAjJb^I?d{WIJTrl{AdK+oy1vvH^SxpF zk>5LO|G#03d6#%;)vA*C=DFnbABq3%ij<9hhmYHV^7_@UZ2s)#R_)iecf1^-LZOI> zqel*#xtG0^emjeWSl@5H52jqIX6t^;3kFTwI5@uh2jUYvw~FEzO(~-Ae2Tp=*Dl04 zMfS6!t^6GBS)A4mWQ-=`2G6<8iiNQtWy*Dq!nm4oPp?d)Ma;E@=jEn|V=l`APC}jb z1}sWN&O@x2fN?>n=@_ecKJp?l7eqMl+<)pzU-{tf?w*W)Ru+#Xn;LhD{$p(;SnUnV zFz6PxxY9NKs6l&gpbD(v1pCyqCCj#yFEfyb_asNR3FX+nNu%7pQmx$sG9WD#FV^Rp zPK(5YZV5prSK#BE`Oo#rpTSuDgzJ@QCICf0t%?*W#lL{P+WkS9~PqQ-+f&jsNf^()4h z?;&3&GEPTTH;KGwC6@!H<03`x1SSQuP%2RXc8_BW+ig7#f{M&a|0Q0tiH`-Od*sna zd5j|;c}0r2AI9%_n2i(TaTez7(&LH=&>KSC$m%$$*gC<%IL62CiT0fVX%(!pW5U7P zljoZ(y6~__4<@4Mr|?~LO$gKGw!$-Ojqk4#X#O)V*ij< zz(rw%C9K*;5m>MBy#6lh|Knmz5J5Rvsn(KWOwjROX^i2la)>^B~pydC>{l&x~rlm_@e8Dl;zRwzG%>t2AW zhb!BGY8Om+M76D1)jXcH_cTi|MjWHu0qcyG;7VadTfLQ-f&XTV`Dy+~ zq)e_O&MjhJSvlj5ZU`x&SDe2lUdM_^?OKSDX0fQ1) z9R_Z?PE*c7gYcayb+Q7BC+#dP$fyNY>saYpjg>>}!(PGcRX%Z%^XNU^GqbqPn=yze zyA=LBCZtaEBCZ|)03SL@L_t&^3M=KYT@rHrjQ!xp_xnIl%CCzRP8+P)dYv)mrZMKdSP|t3 zo%G4dAsF9Bpb3(88D{l4kXQ?X?7aXr((hSm7da0x$b=oWd)o1S#;RyMzD-??MK+liIu3WuF%9*8y9=u;p zmY1#P{(%KT+|RO28mb>qh{MmP9Oa=DvOihjbzeGoFM)V;kd+K6C~%`v{er66&GaFz zTeFLrtXek?b$rQsUbL*f>jZ>NM8>K>n8tH|2_Ba6JHm?oc_@vgWkAt-&i#ck=GEf= z%kaT!%xmy{<9Ww#_Om|}{edc_Mhi+{bskFyUZSeAG!3CtGF&3I-F>!VkQoC^{zyY= z`CyWhS`OZ}tW`6YGNC6yl~9+kdGiBk=K|W$sOD9v)*g-sRKDbRt)Yy75~Yj@_^hpH z;VNcm6Psg8V()}Sy3C4nM)9}Sy44{%SC*$0X+KXFO4x@E#A(GKvxE|`|ce$9uqinB6b<$ zbq14*SgE4s_IHQ2sab>&S0eh`*5~tle?yy@*rs;Bt5U7KQH*mo%uk6RzKnEFzUO2e z9%pQ*yW@ENeFVRox&E$+fmDI<_pk&)%~nNi;k}o@hm43+QgH3H;v| z^Wg#atcrXt;JG!^qj)tNvZ!H?wVo4Lmi-6Mhg{V*^D%5bRvj_m8Bw+OS@GVwRP%e_ zdj2aEiXno2fjbPj5)z_rX%(~Lfi-Im%52Nc8eWEh)2uiJtx~PMm(sQ7lGASv+YZ6o zvLvKHfLy}FPWDH^WpawoUl0@cW=+8(jmNF(J!SlGkg_C{E+tt>htj;nZgLTU%722N zjl^Z~_mT)MTne3FVhV!fyg7(1`Cv!ivwu`Z1Ppcna~fpHeIS3N;dKtzT^goKPmfa+ zoa0yzNPh+Kxm75WT| z7q5Q=@3|B?VHJ90keLN>@^$Rf0uKpqe~9tNo@!qp!*%p|(O6ZfxM~pyRqlWLK6V%t z4Sx;i`1g!4pMu|+jU|k)14SLs=j&*fO(rp+=DUX;IA@H@`*0mo=3u2yy7vENE;;=H zkNGUCFrI>%@lP?nQ)uU`I4-5iZa}#e^wpKC*RoH3wtwj&3WcHpNn{lsdhq^2mwbno zT|QR~q0llijtPOixYCt-Nw2aK5Vw{+)fnye9e z1aC5c%R9V?3{14k)@1&8M9Stm6Hkuz^L0PYl^?~qmOX@ng#4}?msRO3rJJoNVqCNc z##P7a-pinjV;`}fG{$@rJV;!rlI-R5E|hEjIuMg&r2YNIm{(YvoE2)NGWwAx2?tgO zLB2@>i^;`C^*Sqkj#Kl3$p8qCGw{RuDqPDgD0qIy(MPKh1XLQtdr_FoOZ;nVJj!jE z%#3s$hl9Z4 zd!4UuGVtCgx8-8_ z3tzf;_>hZ(RVWmFM3B0E^%H+_@vb}XG?SB)zLu=K{%c>}bZjW@VLnw&-dgC4I)-id z9cdp%@}4fz?H{YbGfOi4xQp zW&4H>f@B2`hmJ~??Yw-C8F){C2UFVe>f*om5dtCA@=`(`UyNYtXKc?KKu%KAZ2m3G zp$~W;gpqU>io)ca@f?0rN>SAq^9OUu>F@Jw&w=YXJ9tA1qH&N|=RFmBY{UElRv(wb zOk#KnuXXQi(G3vcG!W^7&NhlI&yE{Ca zN)NSn3;!=yK(;R}RhWD8*dk@n-xr@}r5+JN|Iiro-{rlAS7|-RTD2!MLIG947%ISY zm{VYEqtknR{pCqxqx8XlU#Zsq7Sw7kmpY2hf&^%spX@4(MLLNGcnCvPAW zUtg)#9zylVrycu~6_ZvR_lJ4w`$J@DsR@Pcl1PhWa60UPw9~1{iErQD_V4T^(=MAX zg+ehZ;EJ+1on?`dn2+hP6eniOh_pmf%e)`Y@MT|0pHNz_2w%lnYjIU#(^8%{4YY|> zG~TwMO>xefRnV=NI`;*LH}8u&_TsVR^4dmKzSQw^y`b#v$oJt> zj`Qaf#IyJzCD#v%Qqwl>a8+8WRBOK#Qu*#JA$?_FR=y<8<9%33-^Fw4sAHQ~AcMEdx!Nhk5MduKH4K-`m%d4p$@cD$m3un#m3Oq5iN~MJ@RJuTX)fX;mS{ zYZi*K_d-#aF}DNe(>Rv}M*u=(#@q1sg0TWsPNl%=TS<@=pLd+Y7FPbs@Ey7*WE}ZO zLnd{2EZPJhDZhTcQmy@;7<&b@@tZ-eIEhV%A6On~ikl%TsJFOJg!c(oqq9{+bie;z zV~kJ1XF$r3Lg}N}pRE`oyqx-uebCJ9Ye*zgosESBUI#QIP zGaZ`jDHMt!f~881D$3yU-^_~VdZEP_uNa|lvY%gVnbYGr*K8&1X3mQjXgcPNBUC_z zP8rV&Kify57C&QRCB!156X54nt8sk_#FtA+*sw0ZLRwnPCCI|yZjOtsO6CiWs-6Xp zeTG7z7-A@aHK4%6=n`(oHkrU_X3hEur3CR_T>^8CQ3O7 zkIASR(QltK#=MFMh8R_ABxHp^8*C!atB-k6C6UOZ9}!jl3cjGdQmxI0DSvQJ$U9PM z`TO@c#xMC`eFGFuyze}!US@gK68DiAYjP5$q?1O%JjN4HpK+StIuugB7O^j5k@WWc zXB{cD#~shDO)Fj7E_QGTRf_iJlGDFvy~pX0Qi*i}-?ylH%_iE+6Gor<>=gQy$IC0N z_xT>ghP?k(pUn!zSfT*$$NOTW!MWZqSQV4^LBwuea?9gkl@kjok_I*7{mh`(pp z7@Lkk1PM*R@!+R@{FH+jqPBd|bM6wm*8lVJ`XI2qi0k@0=pQ?vvHR=0tpC}+?SH`a zT;`Q%;8}-hEsdW)=j(5M{;IWaKHp2$abJbszChdc`fq@YDu5`l3DAB0jCK)ON~?yy zVT}1skp^)Q0Xu&-jdLe2rzai9Cc^b&AWZg=SDx=5kw$Nn-bO`Ch*ClKc)=Z@*89e`%^`3NS&kMC-hy#{Fp%`{J zQnd3>WX2&Md?{NQ+%FIK>fxUMC=|4@7H#khJCAMU;=q<;v5{5&n|Lngt$odM{D4?x zNfbLVh{>Ep17hOkX4OfwSc5p4o^PEZhjMtnbu5bkS4zPVY%5eIBC);6jgCu&+H zuP_GAV=my@$Nw5<5eMU4UW0GM__>JKls}h+O55f4`hix?L7`Ce7rM$jd{|<0aVfoZ zs9fwc9)ky3n3Px&M^wdQ)}x4F$+uhubFFM~8F-h<$(FyDM0LG=L4hks0GYu`ni-iU z=a_Vs@jz$ANf`!^C8Q0p0_A&PCjAo;D|~Nqq;R^(J1*1Q@G+u?wWqXy3TEGLW!0BR zJtNZKh^)wm2mHM_?g|1wmsirfW5Uel|7%EP;uN$6u0ANPJGJKzjWKT!n`eburik{+ zab=SNc^rfL=*#?uW850-YgwdKtz%m`7BeumH^OjVUVmP3oiw+{P!{BfR0kGg1dQAG zJ`vIIcPrHzRWO|Me3naXXE2`L3_qiHdp>_rRJJSPT;Aj`I)8F5IsH13W>6M>OC=9U z8)k%l9166<@U%+VEAL`5v=8XRyP-HtV_e!38TtMB!H{;Gl!`s{-8@X|50oQE8^77n zCQehHfwxr-_sW!guN3>&oXs5u5(zG&N&qa*!<|&`wY2xmC_jVAV2u$lGE3qy2^rx_=u;?mqG#}?t@K1j1XQ!#?ttkf`o6x zY8JOsdV-ZfVjPfpi_a{htvQSdIaWhcV7cO;Opz2u{LTqJx5=~1|0)7!gX?)&lwf8u ziz^k$pkw}xXp^f--yfLUqzk8XE?~?oIKB@=`u!DF zBz<@llwOo>`E!+OEw1;*vKZUPv5LBi)DIWo9k%kj2{M(<(&>A2wTVIiOkVw%=)doQ zs*$S-QNa5yW6Y-=B;69mlTEsEbwD=QPhm{`6gr72l8jq zp6bCaY~q;x%@vfnp@k)ihc~f1w0ck0AV$(ruV)hM^FW4%2|;6=T0glj93w@--h4 zTrsj1c||@opR#K2(I<8gleSk@@z5=~RBi~IeCKdE?Apm7%}T+`3PnnmE4yF}XM|q` zdy}0(DkdAyQ+tU-_@HHg?;>Aa%Dr25yRGj_v5m=n4gLz>Bwk~JW#8&Dd=~OJ*L%Be?sHickLzUHeZgw2y5&b@gWh|sc!uq%e(PB6EL+JgyK7A90k#{!MJV z4x&wtr@Z6d`DyF_1uK2#tBf(1+_naOg?6KgD-lst<(CGp!;ZMY+b^KpT( zdm28j3m}%tBD)~WT)iNkftOHpWftdgfGm{$w)`57|I>&K-jBujqT{%oN-b3=6g`3x zSP>B9KrV){IR_yw(x7CS^}1x)xB1Sa1!LH!dD0eMF{k<)m*3AnC7!vHmApvIL_IM~ed>K_7)vWrB*wJusj zz=eMBIeZjn3&; zk$Prhi&vVt%^+N!@SHz`$H6KlDqPx}Nt2@p#-y?*>E2av-3-RZ5h1C!QO0T}85uT=$c#h^5UU&lR`uG5Aw<`~Ap*W)vf zfz-3fD~Ty6`dIPA_oQb*GBOGF-IZ$Xec~SQv(|nj@%LqXXR;1*@8uzBKw*Z$wqjJF zP>d#Szz*Rfm1>O&Zy1m-XW)y(>ZLdZZ1UFqhSaBltc9EG= zJ_2n2++&)2uUc(pr%=X1Dxaf_A&Zt8UiM4IiVHKs(!|^M()I-Vkc&>_{dokQS9XQw zuwqUMg<=4q1Xc(@GVo1qA!4dTU7ama9vehvJS_;HQ}}#!E;;>^7;Cnw=uaGKvLAx- ziSP)>FyjcSF9{QH=}UA9gq18Dz~{@P;Z4W)HRJMas#I$~WBD#fJO^Uz6~foSK#9Wv z*l&8ab%D2E661pLIF7}>S-*noWDxjs@P7*GsPBY_)nR-OuZz|Fe+_>x2KCu58)JSP z{rQMc-PvP9g2=$PAyv!Qr|!yvmf2Vq96r>3!6xD z_#*B@F5Cop^((ly*`8r8FG9w1^nLbB4s-Pv`y|ZAOrcN=De8OmbyjNA;sL70N*t*8 z8w2ljd?bhXi~T;Gi--$m-$e2|1y#gjO0Kd8BCnTMd@6vc%WSIm6iG(nrJF~+UTk*3NVNGy3wJ>`B}?>)~- zlZc=t3K_I9?NUW~OioW&ilu^B(fK?P5{~P@B-s`o&^fCr;%N@paE(H*lO$0+lIs(= zJ`Z4Ag$1T5v5m|4+a7<B?RN$wQsC_bb)peuYz~AXx0lF<_f)F24Kv^gt^8e`rSLSEH91PS-ID`0&cNTM>v=Ub2h>cR{v8NS+j@8cjDzhV(sgl`jn zvscQdNI!iq2&c;&jDo!Wevm+~c92&Lm*S1_&2!1=->+0_w> zpQH$a%^r8}g5mxT;yd^)xWD`hW6VqOnU6UXf4t8vA1P0L_NmX--jhfq=>D-&uh*ZO zoSb-#shb;#i9|B!_qy5K+1*{mCQ}m=6ZcL`Bwl9fW_NdQZ;iH@m`L0^k(hXyNrXPX zZtd*s29-M}CMLdSVj?jwV}sq@y+5tj>o*dK#L+||k*wG2)yc_;*V)&pUa$Yh=0FySsbd zYM0dZiHXF^6N$tz-)HLe`d{zv?tOA{a$+%I5_j#@_rAEdSFZ+rI5F{Rq0Vz`Y&LiI z_WojWa^j8lTYG!;&+YB)J?-h21AE%t-TMSxJG6Uta&jUdPj_~9|9EO@@(n1UAaP@7 zXZO!|GUfvv-z3b@-QB%?>9JFjlgAt5qi!~X{+~#^3j6(2{v7o~B9XYeUN<-Bd>sAz zDtaG^=>~Rp_g3rm`lfw6dOeW{#;uziw#N6=)Z}qKcN%MZd-V-`FTOUBNW8pWuYZ}& zEoe(3aj&nhc|2QvkTQw=1iRgCbNl|Na6!bVH=wNkw6#A{ zTRkIQd%JZ_?zQGDM-jJt694}ekL%Ap{nw;C|4=vkNzr{Q9!nP2HmYa1V8P} + icon={} href={url} /> ); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx index 4d6c02eeef8b4..64950f95f5158 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx @@ -3,15 +3,18 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import React, { memo } from 'react'; +import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiImage, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { useLinks } from '../../hooks'; +import { useCore } from '../../../../hooks'; export const HeroCopy = memo(() => { return ( - +

@@ -38,16 +41,20 @@ export const HeroCopy = memo(() => { export const HeroImage = memo(() => { const { toAssets } = useLinks(); - const ImageWrapper = styled.div` - margin-bottom: -62px; + const { uiSettings } = useCore(); + const IS_DARK_THEME = uiSettings.get('theme:darkMode'); + + const Illustration = styled(EuiImage).attrs(props => ({ + alt: i18n.translate('xpack.ingestManager.epm.illustrationAltText', { + defaultMessage: 'Illustration of an Elastic integration', + }), + url: IS_DARK_THEME + ? toAssets('illustration_integrations_darkmode.svg') + : toAssets('illustration_integrations_lightmode.svg'), + }))` + margin-bottom: -60px; + width: 80%; `; - return ( - - - - ); + return ; }); From 85539ee855013cefc9dae6ed5e91064068541b9b Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 4 May 2020 14:47:47 -0400 Subject: [PATCH 09/22] Reorder ES management nav (#65082) --- x-pack/plugins/cross_cluster_replication/public/plugin.ts | 2 +- x-pack/plugins/index_lifecycle_management/public/plugin.tsx | 2 +- x-pack/plugins/index_management/public/plugin.ts | 2 +- x-pack/plugins/ingest_pipelines/public/plugin.ts | 1 + x-pack/plugins/rollup/public/plugin.ts | 2 +- x-pack/plugins/transform/public/plugin.ts | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/cross_cluster_replication/public/plugin.ts b/x-pack/plugins/cross_cluster_replication/public/plugin.ts index bdaa04e9d53ee..dfe9e4e657c30 100644 --- a/x-pack/plugins/cross_cluster_replication/public/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/public/plugin.ts @@ -39,7 +39,7 @@ export class CrossClusterReplicationPlugin implements Plugin { const ccrApp = esSection!.registerApp({ id: MANAGEMENT_ID, title: PLUGIN.TITLE, - order: 4, + order: 6, mount: async ({ element, setBreadcrumbs }) => { const { mountApp } = await import('./app'); diff --git a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx index ca93646e20fcf..ad543b05bc025 100644 --- a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx @@ -40,7 +40,7 @@ export class IndexLifecycleManagementPlugin { management.sections.getSection('elasticsearch')!.registerApp({ id: PLUGIN.ID, title: PLUGIN.TITLE, - order: 2, + order: 3, mount: async ({ element }) => { const [coreStart] = await getStartServices(); const { diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index f9e2a47170b3d..78e80687abeb4 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -51,7 +51,7 @@ export class IndexMgmtUIPlugin { management.sections.getSection('elasticsearch')!.registerApp({ id: PLUGIN.id, title: i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management' }), - order: 1, + order: 2, mount: async params => { const { mountManagementSection } = await import('./application/mount_management_section'); const services = { diff --git a/x-pack/plugins/ingest_pipelines/public/plugin.ts b/x-pack/plugins/ingest_pipelines/public/plugin.ts index e9f5fd6c7f57c..0ab46f386e83b 100644 --- a/x-pack/plugins/ingest_pipelines/public/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/public/plugin.ts @@ -22,6 +22,7 @@ export class IngestPipelinesPlugin implements Plugin { management.sections.getSection('elasticsearch')!.registerApp({ id: PLUGIN_ID, + order: 1, title: i18n.translate('xpack.ingestPipelines.appTitle', { defaultMessage: 'Ingest Node Pipelines', }), diff --git a/x-pack/plugins/rollup/public/plugin.ts b/x-pack/plugins/rollup/public/plugin.ts index 5bb678ac35d06..0e0333cf30f17 100644 --- a/x-pack/plugins/rollup/public/plugin.ts +++ b/x-pack/plugins/rollup/public/plugin.ts @@ -82,7 +82,7 @@ export class RollupPlugin implements Plugin { esSection.registerApp({ id: 'rollup_jobs', title: i18n.translate('xpack.rollupJobs.appTitle', { defaultMessage: 'Rollup Jobs' }), - order: 3, + order: 5, async mount(params) { params.setBreadcrumbs([ { diff --git a/x-pack/plugins/transform/public/plugin.ts b/x-pack/plugins/transform/public/plugin.ts index e6f19bcf1c2bb..563a569fe95b5 100644 --- a/x-pack/plugins/transform/public/plugin.ts +++ b/x-pack/plugins/transform/public/plugin.ts @@ -29,7 +29,7 @@ export class TransformUiPlugin { title: kbnI18n.translate('xpack.transform.appTitle', { defaultMessage: 'Transforms', }), - order: 3, + order: 4, mount: async params => { const { mountManagementSection } = await import('./app/mount_management_section'); return mountManagementSection(coreSetup, params); From 6c1f5ec81b1fc55d72bac96c3fb16ea900078155 Mon Sep 17 00:00:00 2001 From: Poff Poffenberger Date: Mon, 4 May 2020 13:55:13 -0500 Subject: [PATCH 10/22] [Canvas] Reduce report generation time by re-using headless browser page in background (#63301) * Rough first pass at reusing page for multiple links in report generation * Some adjustments to handling the events coming from CDP * Add new data-share-page selector for jobs with multiple urls * Cleanup * PR feedback * Adding tests for Canvas export app and multi user observable jobs * Adding a short blurb describing the data-shared-page attribute requirement * PR feedback Co-authored-by: Elastic Machine --- .../development/pdf-integration.asciidoc | 2 + .../__snapshots__/export_app.test.tsx.snap | 141 ++++++++++++ .../export/__tests__/export_app.test.tsx | 49 ++++ .../public/apps/export/export/export_app.js | 8 +- .../canvas/public/apps/export/export/index.js | 4 +- .../export_types/common/constants.ts | 2 +- .../common/lib/screenshots/observable.test.ts | 12 + .../common/lib/screenshots/observable.ts | 33 ++- .../common/lib/screenshots/open_url.ts | 4 +- .../chromium/driver/chromium_driver.ts | 210 ++++++++++-------- .../create_mock_browserdriverfactory.ts | 3 + 11 files changed, 355 insertions(+), 113 deletions(-) create mode 100644 x-pack/legacy/plugins/canvas/public/apps/export/export/__tests__/__snapshots__/export_app.test.tsx.snap create mode 100644 x-pack/legacy/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx diff --git a/docs/user/reporting/development/pdf-integration.asciidoc b/docs/user/reporting/development/pdf-integration.asciidoc index af5ba5be1636e..e9f32de41baab 100644 --- a/docs/user/reporting/development/pdf-integration.asciidoc +++ b/docs/user/reporting/development/pdf-integration.asciidoc @@ -63,3 +63,5 @@ If there are multiple visualizations, the `data-shared-items-count` attribute sh many Visualizations to look for. Reporting will look at every element with the `data-shared-item` attribute and use the corresponding `data-render-complete` attribute and `renderComplete` events to listen for rendering to complete. When rendering is complete for a visualization the `data-render-complete` attribute should be set to "true" and it should dispatch a custom DOM `renderComplete` event. + +If the reporting job uses multiple URLs, before looking for any of the `data-shared-item` or `data-shared-items-count` attributes, it waits for a `data-shared-page` attribute that specifies which page is being loaded. diff --git a/x-pack/legacy/plugins/canvas/public/apps/export/export/__tests__/__snapshots__/export_app.test.tsx.snap b/x-pack/legacy/plugins/canvas/public/apps/export/export/__tests__/__snapshots__/export_app.test.tsx.snap new file mode 100644 index 0000000000000..19e9000c3bffc --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/apps/export/export/__tests__/__snapshots__/export_app.test.tsx.snap @@ -0,0 +1,141 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders as expected 1`] = ` + +
+
+
+ +
+ Link +
+ +
+
+ +
+ Page +
+
+
+
+
+
+`; + +exports[` renders as expected 2`] = ` + +
+
+
+ +
+ Link +
+ +
+
+ +
+ Page +
+
+
+
+
+
+`; diff --git a/x-pack/legacy/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx b/x-pack/legacy/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx new file mode 100644 index 0000000000000..7f5b53df4ba52 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +// @ts-ignore untyped local +import { ExportApp } from '../export_app'; + +jest.mock('style-it', () => ({ + it: (css: string, Component: any) => Component, +})); + +jest.mock('../../../../components/workpad_page', () => ({ + WorkpadPage: (props: any) =>
Page
, +})); + +jest.mock('../../../../components/link', () => ({ + Link: (props: any) =>
Link
, +})); + +describe('', () => { + test('renders as expected', () => { + const sampleWorkpad = { + id: 'my-workpad-abcd', + css: '', + pages: [ + { + elements: [0, 1, 2], + }, + { + elements: [3, 4, 5, 6], + }, + ], + }; + + const page1 = mount( + {}} /> + ); + expect(page1).toMatchSnapshot(); + + const page2 = mount( + {}} /> + ); + expect(page2).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/public/apps/export/export/export_app.js b/x-pack/legacy/plugins/canvas/public/apps/export/export/export_app.js index 7537f8eaa9039..1d02d85cae0b3 100644 --- a/x-pack/legacy/plugins/canvas/public/apps/export/export/export_app.js +++ b/x-pack/legacy/plugins/canvas/public/apps/export/export/export_app.js @@ -16,7 +16,7 @@ export class ExportApp extends React.PureComponent { id: PropTypes.string.isRequired, pages: PropTypes.array.isRequired, }).isRequired, - selectedPageId: PropTypes.string.isRequired, + selectedPageIndex: PropTypes.number.isRequired, initializeWorkpad: PropTypes.func.isRequired, }; @@ -25,13 +25,13 @@ export class ExportApp extends React.PureComponent { } render() { - const { workpad, selectedPageId } = this.props; + const { workpad, selectedPageIndex } = this.props; const { pages, height, width } = workpad; - const activePage = pages.find(page => page.id === selectedPageId); + const activePage = pages[selectedPageIndex]; const pageElementCount = activePage.elements.length; return ( -
+
diff --git a/x-pack/legacy/plugins/canvas/public/apps/export/export/index.js b/x-pack/legacy/plugins/canvas/public/apps/export/export/index.js index d40c5f787e44f..dafcb9f4c2510 100644 --- a/x-pack/legacy/plugins/canvas/public/apps/export/export/index.js +++ b/x-pack/legacy/plugins/canvas/public/apps/export/export/index.js @@ -7,13 +7,13 @@ import { connect } from 'react-redux'; import { compose, branch, renderComponent } from 'recompose'; import { initializeWorkpad } from '../../../state/actions/workpad'; -import { getWorkpad, getSelectedPage } from '../../../state/selectors/workpad'; +import { getWorkpad, getSelectedPageIndex } from '../../../state/selectors/workpad'; import { LoadWorkpad } from './load_workpad'; import { ExportApp as Component } from './export_app'; const mapStateToProps = state => ({ workpad: getWorkpad(state), - selectedPageId: getSelectedPage(state), + selectedPageIndex: getSelectedPageIndex(state), }); const mapDispatchToProps = dispatch => ({ diff --git a/x-pack/legacy/plugins/reporting/export_types/common/constants.ts b/x-pack/legacy/plugins/reporting/export_types/common/constants.ts index 254cfbaa878bd..6c56c269017e2 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/constants.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/constants.ts @@ -9,4 +9,4 @@ export const LayoutTypes = { PRINT: 'print', }; -export const PAGELOAD_SELECTOR = '.application'; +export const DEFAULT_PAGELOAD_SELECTOR = '.application'; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.test.ts index 68d660257a56d..796bccb360ebd 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.test.ts @@ -98,9 +98,12 @@ describe('Screenshot Observable Pipeline', () => { return Promise.resolve(`allyourBase64 screenshots`); }); + const mockOpen = jest.fn(); + // mocks mockBrowserDriverFactory = await createMockBrowserDriverFactory(logger, { screenshot: mockScreenshot, + open: mockOpen, }); // test @@ -179,6 +182,15 @@ describe('Screenshot Observable Pipeline', () => { }, ] `); + + // ensures the correct selectors are waited on for multi URL jobs + expect(mockOpen.mock.calls.length).toBe(2); + + const firstSelector = mockOpen.mock.calls[0][1].waitForSelector; + expect(firstSelector).toBe('.application'); + + const secondSelector = mockOpen.mock.calls[1][1].waitForSelector; + expect(secondSelector).toBe('[data-shared-page="2"]'); }); describe('error handling', () => { diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts index c6861ae1d17ad..eb96753f0ce18 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts @@ -7,6 +7,7 @@ import * as Rx from 'rxjs'; import { catchError, concatMap, first, mergeMap, take, takeUntil, toArray } from 'rxjs/operators'; import { CaptureConfig } from '../../../../server/types'; +import { DEFAULT_PAGELOAD_SELECTOR } from '../../constants'; import { HeadlessChromiumDriverFactory } from '../../../../types'; import { getElementPositionAndAttributes } from './get_element_position_data'; import { getNumberOfItems } from './get_number_of_items'; @@ -44,13 +45,29 @@ export function screenshotsObservableFactory( { viewport: layout.getBrowserViewport(), browserTimezone }, logger ); - return Rx.from(urls).pipe( - concatMap(url => { - return create$.pipe( - mergeMap(({ driver, exit$ }) => { + + return create$.pipe( + mergeMap(({ driver, exit$ }) => { + return Rx.from(urls).pipe( + concatMap((url, index) => { const setup$: Rx.Observable = Rx.of(1).pipe( takeUntil(exit$), - mergeMap(() => openUrl(captureConfig, driver, url, conditionalHeaders, logger)), + mergeMap(() => { + // If we're moving to another page in the app, we'll want to wait for the app to tell us + // it's loaded the next page. + const page = index + 1; + const pageLoadSelector = + page > 1 ? `[data-shared-page="${page}"]` : DEFAULT_PAGELOAD_SELECTOR; + + return openUrl( + captureConfig, + driver, + url, + pageLoadSelector, + conditionalHeaders, + logger + ); + }), mergeMap(() => getNumberOfItems(captureConfig, driver, layout, logger)), mergeMap(async itemsCount => { const viewport = layout.getViewport(itemsCount) || getDefaultViewPort(); @@ -104,11 +121,11 @@ export function screenshotsObservableFactory( ) ); }), - first() + take(urls.length), + toArray() ); }), - take(urls.length), - toArray() + first() ); }; } diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/open_url.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/open_url.ts index a484dfb243563..92a58aded5f66 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/open_url.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/open_url.ts @@ -9,12 +9,12 @@ import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/br import { LevelLogger } from '../../../../server/lib'; import { CaptureConfig } from '../../../../server/types'; import { ConditionalHeaders } from '../../../../types'; -import { PAGELOAD_SELECTOR } from '../../constants'; export const openUrl = async ( captureConfig: CaptureConfig, browser: HeadlessBrowser, url: string, + pageLoadSelector: string, conditionalHeaders: ConditionalHeaders, logger: LevelLogger ): Promise => { @@ -23,7 +23,7 @@ export const openUrl = async ( url, { conditionalHeaders, - waitForSelector: PAGELOAD_SELECTOR, + waitForSelector: pageLoadSelector, timeout: captureConfig.timeouts.openUrl, }, logger diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index dfaa87021c31c..dd20e849d97a9 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { map, trunc } from 'lodash'; import open from 'opn'; -import { ElementHandle, EvaluateFn, Page, SerializableOrJSHandle } from 'puppeteer'; +import { ElementHandle, EvaluateFn, Page, SerializableOrJSHandle, Response } from 'puppeteer'; import { parse as parseUrl } from 'url'; import { ViewZoomWidthHeight } from '../../../../export_types/common/layouts/layout'; import { LevelLogger } from '../../../../server/lib'; @@ -45,6 +45,9 @@ export class HeadlessChromiumDriver { private readonly inspect: boolean; private readonly networkPolicy: NetworkPolicy; + private listenersAttached = false; + private interceptedCount = 0; + constructor(page: Page, { inspect, networkPolicy }: ChromiumDriverOptions) { this.page = page; this.inspect = inspect; @@ -76,103 +79,13 @@ export class HeadlessChromiumDriver { logger: LevelLogger ): Promise { logger.info(`opening url ${url}`); - // @ts-ignore - const client = this.page._client; - let interceptedCount = 0; - - await this.page.setRequestInterception(true); - - // We have to reach into the Chrome Devtools Protocol to apply headers as using - // puppeteer's API will cause map tile requests to hang indefinitely: - // https://github.com/puppeteer/puppeteer/issues/5003 - // Docs on this client/protocol can be found here: - // https://chromedevtools.github.io/devtools-protocol/tot/Fetch - client.on('Fetch.requestPaused', async (interceptedRequest: InterceptedRequest) => { - const { - requestId, - request: { url: interceptedUrl }, - } = interceptedRequest; - const allowed = !interceptedUrl.startsWith('file://'); - const isData = interceptedUrl.startsWith('data:'); - - // We should never ever let file protocol requests go through - if (!allowed || !this.allowRequest(interceptedUrl)) { - logger.error(`Got bad URL: "${interceptedUrl}", closing browser.`); - await client.send('Fetch.failRequest', { - errorReason: 'Aborted', - requestId, - }); - this.page.browser().close(); - throw new Error( - i18n.translate('xpack.reporting.chromiumDriver.disallowedOutgoingUrl', { - defaultMessage: `Received disallowed outgoing URL: "{interceptedUrl}", exiting`, - values: { interceptedUrl }, - }) - ); - } - - if (this._shouldUseCustomHeaders(conditionalHeaders.conditions, interceptedUrl)) { - logger.debug(`Using custom headers for ${interceptedUrl}`); - const headers = map( - { - ...interceptedRequest.request.headers, - ...conditionalHeaders.headers, - }, - (value, name) => ({ - name, - value, - }) - ); - - try { - await client.send('Fetch.continueRequest', { - requestId, - headers, - }); - } catch (err) { - logger.error( - i18n.translate('xpack.reporting.chromiumDriver.failedToCompleteRequestUsingHeaders', { - defaultMessage: 'Failed to complete a request using headers: {error}', - values: { error: err }, - }) - ); - } - } else { - const loggedUrl = isData ? this.truncateUrl(interceptedUrl) : interceptedUrl; - logger.debug(`No custom headers for ${loggedUrl}`); - try { - await client.send('Fetch.continueRequest', { requestId }); - } catch (err) { - logger.error( - i18n.translate('xpack.reporting.chromiumDriver.failedToCompleteRequest', { - defaultMessage: 'Failed to complete a request: {error}', - values: { error: err }, - }) - ); - } - } - interceptedCount = interceptedCount + (isData ? 0 : 1); - }); - // Even though 3xx redirects go through our request - // handler, we should probably inspect responses just to - // avoid being bamboozled by some malicious request - this.page.on('response', interceptedResponse => { - const interceptedUrl = interceptedResponse.url(); - const allowed = !interceptedUrl.startsWith('file://'); + // Reset intercepted request count + this.interceptedCount = 0; - if (!interceptedResponse.ok()) { - logger.warn( - `Chromium received a non-OK response (${interceptedResponse.status()}) for request ${interceptedUrl}` - ); - } + await this.page.setRequestInterception(true); - if (!allowed || !this.allowRequest(interceptedUrl)) { - logger.error(`Got disallowed URL "${interceptedUrl}", closing browser.`); - this.page.browser().close(); - throw new Error(`Received disallowed URL in response: ${interceptedUrl}`); - } - }); + this.registerListeners(conditionalHeaders, logger); await this.page.goto(url, { waitUntil: 'domcontentloaded' }); @@ -186,7 +99,7 @@ export class HeadlessChromiumDriver { { context: 'waiting for page load selector' }, logger ); - logger.info(`handled ${interceptedCount} page requests`); + logger.info(`handled ${this.interceptedCount} page requests`); } public async screenshot(elementPosition: ElementPosition): Promise { @@ -272,6 +185,111 @@ export class HeadlessChromiumDriver { }); } + private registerListeners(conditionalHeaders: ConditionalHeaders, logger: LevelLogger) { + if (this.listenersAttached) { + return; + } + + // @ts-ignore + const client = this.page._client; + + // We have to reach into the Chrome Devtools Protocol to apply headers as using + // puppeteer's API will cause map tile requests to hang indefinitely: + // https://github.com/puppeteer/puppeteer/issues/5003 + // Docs on this client/protocol can be found here: + // https://chromedevtools.github.io/devtools-protocol/tot/Fetch + client.on('Fetch.requestPaused', async (interceptedRequest: InterceptedRequest) => { + const { + requestId, + request: { url: interceptedUrl }, + } = interceptedRequest; + + const allowed = !interceptedUrl.startsWith('file://'); + const isData = interceptedUrl.startsWith('data:'); + + // We should never ever let file protocol requests go through + if (!allowed || !this.allowRequest(interceptedUrl)) { + logger.error(`Got bad URL: "${interceptedUrl}", closing browser.`); + await client.send('Fetch.failRequest', { + errorReason: 'Aborted', + requestId, + }); + this.page.browser().close(); + throw new Error( + i18n.translate('xpack.reporting.chromiumDriver.disallowedOutgoingUrl', { + defaultMessage: `Received disallowed outgoing URL: "{interceptedUrl}", exiting`, + values: { interceptedUrl }, + }) + ); + } + + if (this._shouldUseCustomHeaders(conditionalHeaders.conditions, interceptedUrl)) { + logger.debug(`Using custom headers for ${interceptedUrl}`); + const headers = map( + { + ...interceptedRequest.request.headers, + ...conditionalHeaders.headers, + }, + (value, name) => ({ + name, + value, + }) + ); + + try { + await client.send('Fetch.continueRequest', { + requestId, + headers, + }); + } catch (err) { + logger.error( + i18n.translate('xpack.reporting.chromiumDriver.failedToCompleteRequestUsingHeaders', { + defaultMessage: 'Failed to complete a request using headers: {error}', + values: { error: err }, + }) + ); + } + } else { + const loggedUrl = isData ? this.truncateUrl(interceptedUrl) : interceptedUrl; + logger.debug(`No custom headers for ${loggedUrl}`); + try { + await client.send('Fetch.continueRequest', { requestId }); + } catch (err) { + logger.error( + i18n.translate('xpack.reporting.chromiumDriver.failedToCompleteRequest', { + defaultMessage: 'Failed to complete a request: {error}', + values: { error: err }, + }) + ); + } + } + + this.interceptedCount = this.interceptedCount + (isData ? 0 : 1); + }); + + // Even though 3xx redirects go through our request + // handler, we should probably inspect responses just to + // avoid being bamboozled by some malicious request + this.page.on('response', (interceptedResponse: Response) => { + const interceptedUrl = interceptedResponse.url(); + const allowed = !interceptedUrl.startsWith('file://'); + + if (!interceptedResponse.ok()) { + logger.warn( + `Chromium received a non-OK response (${interceptedResponse.status()}) for request ${interceptedUrl}` + ); + } + + if (!allowed || !this.allowRequest(interceptedUrl)) { + logger.error(`Got disallowed URL "${interceptedUrl}", closing browser.`); + this.page.browser().close(); + throw new Error(`Received disallowed URL in response: ${interceptedUrl}`); + } + }); + + this.listenersAttached = true; + } + private async launchDebugger() { // In order to pause on execution we have to reach more deeply into Chromiums Devtools Protocol, // and more specifically, for the page being used. _client is per-page, and puppeteer doesn't expose diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts index 1be10f6a2056f..aafe17d970187 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts @@ -17,6 +17,7 @@ interface CreateMockBrowserDriverFactoryOpts { evaluate: jest.Mock, any[]>; waitForSelector: jest.Mock, any[]>; screenshot: jest.Mock, any[]>; + open: jest.Mock, any[]>; getCreatePage: (driver: HeadlessChromiumDriver) => jest.Mock; } @@ -87,6 +88,7 @@ const defaultOpts: CreateMockBrowserDriverFactoryOpts = { evaluate: mockBrowserEvaluate, waitForSelector: mockWaitForSelector, screenshot: mockScreenshot, + open: jest.fn(), getCreatePage, }; @@ -124,6 +126,7 @@ export const createMockBrowserDriverFactory = async ( mockBrowserDriver.waitForSelector = opts.waitForSelector ? opts.waitForSelector : defaultOpts.waitForSelector; // prettier-ignore mockBrowserDriver.evaluate = opts.evaluate ? opts.evaluate : defaultOpts.evaluate; mockBrowserDriver.screenshot = opts.screenshot ? opts.screenshot : defaultOpts.screenshot; + mockBrowserDriver.open = opts.open ? opts.open : defaultOpts.open; mockBrowserDriverFactory.createPage = opts.getCreatePage ? opts.getCreatePage(mockBrowserDriver) From 99a5db6aab8dc2da56776748abe74f81c58a47c5 Mon Sep 17 00:00:00 2001 From: nnamdifrankie <56440728+nnamdifrankie@users.noreply.github.com> Date: Mon, 4 May 2020 14:58:12 -0400 Subject: [PATCH 11/22] EMT-339: add policy response index and documents (#65004) * EMT-339: add policy response index and documents * EMT-339: add routes, unit and integration tests * EMT-339: review comments, change types, url, update tests --- .../endpoint/common/generate_data.test.ts | 11 + .../plugins/endpoint/common/generate_data.ts | 229 +++++--- .../plugins/endpoint/common/schema/policy.ts | 12 + x-pack/plugins/endpoint/common/types.ts | 22 +- .../endpoint/scripts/policy_mapping.json | 398 ++++++++++++++ .../endpoint/scripts/resolver_generator.ts | 29 +- .../plugins/endpoint/server/index_pattern.ts | 5 + x-pack/plugins/endpoint/server/mocks.ts | 30 +- x-pack/plugins/endpoint/server/plugin.ts | 2 + .../server/routes/metadata/metadata.test.ts | 28 +- .../server/routes/policy/handlers.test.ts | 138 +++++ .../endpoint/server/routes/policy/handlers.ts | 36 ++ .../endpoint/server/routes/policy/index.ts | 23 + .../server/routes/policy/service.test.ts | 19 + .../endpoint/server/routes/policy/service.ts | 49 ++ .../api_integration/apis/endpoint/index.ts | 1 + .../api_integration/apis/endpoint/policy.ts | 40 ++ .../es_archives/endpoint/policy/data.json.gz | Bin 0 -> 1329 bytes .../es_archives/endpoint/policy/mappings.json | 512 ++++++++++++++++++ 19 files changed, 1469 insertions(+), 115 deletions(-) create mode 100644 x-pack/plugins/endpoint/common/schema/policy.ts create mode 100644 x-pack/plugins/endpoint/scripts/policy_mapping.json create mode 100644 x-pack/plugins/endpoint/server/routes/policy/handlers.test.ts create mode 100644 x-pack/plugins/endpoint/server/routes/policy/handlers.ts create mode 100644 x-pack/plugins/endpoint/server/routes/policy/index.ts create mode 100644 x-pack/plugins/endpoint/server/routes/policy/service.test.ts create mode 100644 x-pack/plugins/endpoint/server/routes/policy/service.ts create mode 100644 x-pack/test/api_integration/apis/endpoint/policy.ts create mode 100644 x-pack/test/functional/es_archives/endpoint/policy/data.json.gz create mode 100644 x-pack/test/functional/es_archives/endpoint/policy/mappings.json diff --git a/x-pack/plugins/endpoint/common/generate_data.test.ts b/x-pack/plugins/endpoint/common/generate_data.test.ts index 88e1c66ea3e82..e1a2401849301 100644 --- a/x-pack/plugins/endpoint/common/generate_data.test.ts +++ b/x-pack/plugins/endpoint/common/generate_data.test.ts @@ -45,6 +45,17 @@ describe('data generator', () => { expect(metadata.host).not.toBeNull(); }); + it('creates policy response documents', () => { + const timestamp = new Date().getTime(); + const hostPolicyResponse = generator.generatePolicyResponse(timestamp); + expect(hostPolicyResponse['@timestamp']).toEqual(timestamp); + expect(hostPolicyResponse.event.created).toEqual(timestamp); + expect(hostPolicyResponse.endpoint).not.toBeNull(); + expect(hostPolicyResponse.agent).not.toBeNull(); + expect(hostPolicyResponse.host).not.toBeNull(); + expect(hostPolicyResponse.endpoint.policy.applied).not.toBeNull(); + }); + it('creates alert event documents', () => { const timestamp = new Date().getTime(); const alert = generator.generateAlert(timestamp); diff --git a/x-pack/plugins/endpoint/common/generate_data.ts b/x-pack/plugins/endpoint/common/generate_data.ts index e40fc3e386bc8..840574063d3f3 100644 --- a/x-pack/plugins/endpoint/common/generate_data.ts +++ b/x-pack/plugins/endpoint/common/generate_data.ts @@ -12,9 +12,10 @@ import { Host, HostMetadata, HostOS, - PolicyData, HostPolicyResponse, + HostPolicyResponseActions, HostPolicyResponseActionStatus, + PolicyData, } from './types'; import { factory as policyFactory } from './models/policy_config'; @@ -136,6 +137,13 @@ export class EndpointDocGenerator { this.commonInfo.host.ip = this.randomArray(3, () => this.randomIP()); } + /** + * Creates new random policy id for the host to simulate new policy application + */ + public updatePolicyId() { + this.commonInfo.endpoint.policy.id = this.randomChoice(POLICIES).id; + } + private createHostData(): HostInfo { return { agent: { @@ -498,106 +506,145 @@ export class EndpointDocGenerator { /** * Generates a Host Policy response message */ - generatePolicyResponse(): HostPolicyResponse { + public generatePolicyResponse(ts = new Date().getTime()): HostPolicyResponse { + const policyVersion = this.seededUUIDv4(); return { - '@timestamp': new Date().toISOString(), + '@timestamp': ts, + agent: { + id: this.commonInfo.agent.id, + version: '1.0.0-local.20200416.0', + }, elastic: { agent: { - id: 'c2a9093e-e289-4c0a-aa44-8c32a414fa7a', + id: this.commonInfo.elastic.agent.id, }, }, ecs: { - version: '1.0.0', - }, - event: { - created: '2015-01-01T12:10:30Z', - kind: 'policy_response', + version: '1.4.0', }, - agent: { - version: '6.0.0-rc2', - id: '8a4f500d', + host: { + id: this.commonInfo.host.id, }, endpoint: { - artifacts: { - 'global-manifest': { - version: '1.2.3', - sha256: 'abcdef', - }, - 'endpointpe-v4-windows': { - version: '1.2.3', - sha256: 'abcdef', - }, - 'user-whitelist-windows': { - version: '1.2.3', - sha256: 'abcdef', - }, - 'global-whitelist-windows': { - version: '1.2.3', - sha256: 'abcdef', - }, - }, policy: { applied: { - version: '1.0.0', - id: '17d4b81d-9940-4b64-9de5-3e03ef1fb5cf', - status: HostPolicyResponseActionStatus.success, + actions: { + configure_elasticsearch_connection: { + message: 'elasticsearch comms configured successfully', + status: HostPolicyResponseActionStatus.success, + }, + configure_kernel: { + message: 'Failed to configure kernel', + status: HostPolicyResponseActionStatus.failure, + }, + configure_logging: { + message: 'Successfully configured logging', + status: HostPolicyResponseActionStatus.success, + }, + configure_malware: { + message: 'Unexpected error configuring malware', + status: HostPolicyResponseActionStatus.failure, + }, + connect_kernel: { + message: 'Successfully initialized minifilter', + status: HostPolicyResponseActionStatus.success, + }, + detect_file_open_events: { + message: 'Successfully stopped file open event reporting', + status: HostPolicyResponseActionStatus.success, + }, + detect_file_write_events: { + message: 'Failed to stop file write event reporting', + status: HostPolicyResponseActionStatus.success, + }, + detect_image_load_events: { + message: 'Successfuly started image load event reporting', + status: HostPolicyResponseActionStatus.success, + }, + detect_process_events: { + message: 'Successfully started process event reporting', + status: HostPolicyResponseActionStatus.success, + }, + download_global_artifacts: { + message: 'Failed to download EXE model', + status: HostPolicyResponseActionStatus.success, + }, + load_config: { + message: 'successfully parsed configuration', + status: HostPolicyResponseActionStatus.success, + }, + load_malware_model: { + message: 'Error deserializing EXE model; no valid malware model installed', + status: HostPolicyResponseActionStatus.success, + }, + read_elasticsearch_config: { + message: 'Successfully read Elasticsearch configuration', + status: HostPolicyResponseActionStatus.success, + }, + read_events_config: { + message: 'Successfully read events configuration', + status: HostPolicyResponseActionStatus.success, + }, + read_kernel_config: { + message: 'Succesfully read kernel configuration', + status: HostPolicyResponseActionStatus.success, + }, + read_logging_config: { + message: 'field (logging.debugview) not found in config', + status: HostPolicyResponseActionStatus.success, + }, + read_malware_config: { + message: 'Successfully read malware detect configuration', + status: HostPolicyResponseActionStatus.success, + }, + workflow: { + message: 'Failed to apply a portion of the configuration (kernel)', + status: HostPolicyResponseActionStatus.success, + }, + download_model: { + message: 'Failed to apply a portion of the configuration (kernel)', + status: HostPolicyResponseActionStatus.success, + }, + ingest_events_config: { + message: 'Failed to apply a portion of the configuration (kernel)', + status: HostPolicyResponseActionStatus.success, + }, + }, + id: this.commonInfo.endpoint.policy.id, + policy: { + id: this.commonInfo.endpoint.policy.id, + version: policyVersion, + }, response: { configurations: { - malware: { - status: HostPolicyResponseActionStatus.success, - concerned_actions: ['download_model', 'workflow', 'a_custom_future_action'], - }, events: { - status: HostPolicyResponseActionStatus.success, - concerned_actions: ['ingest_events_config', 'workflow'], + concerned_actions: this.randomHostPolicyResponseActions(), + status: this.randomHostPolicyResponseActionStatus(), }, logging: { - status: HostPolicyResponseActionStatus.success, - concerned_actions: ['configure_elasticsearch_connection'], - }, - streaming: { - status: HostPolicyResponseActionStatus.success, - concerned_actions: [ - 'detect_file_open_events', - 'download_global_artifacts', - 'a_custom_future_action', - ], - }, - }, - actions: { - download_model: { - status: HostPolicyResponseActionStatus.success, - message: 'model downloaded', + concerned_actions: this.randomHostPolicyResponseActions(), + status: this.randomHostPolicyResponseActionStatus(), }, - ingest_events_config: { - status: HostPolicyResponseActionStatus.success, - message: 'no action taken', - }, - workflow: { - status: HostPolicyResponseActionStatus.success, - message: 'the flow worked well', - }, - a_custom_future_action: { - status: HostPolicyResponseActionStatus.success, - message: 'future message', - }, - configure_elasticsearch_connection: { - status: HostPolicyResponseActionStatus.success, - message: 'some message', - }, - detect_file_open_events: { - status: HostPolicyResponseActionStatus.success, - message: 'some message', + malware: { + concerned_actions: this.randomHostPolicyResponseActions(), + status: this.randomHostPolicyResponseActionStatus(), }, - download_global_artifacts: { - status: HostPolicyResponseActionStatus.success, - message: 'some message', + streaming: { + concerned_actions: this.randomHostPolicyResponseActions(), + status: this.randomHostPolicyResponseActionStatus(), }, }, }, + status: this.randomHostPolicyResponseActionStatus(), + version: policyVersion, }, }, }, + event: { + created: ts, + id: this.seededUUIDv4(), + kind: 'policy_response', + }, }; } @@ -644,6 +691,34 @@ export class EndpointDocGenerator { private seededUUIDv4(): string { return uuid.v4({ random: [...this.randomNGenerator(255, 16)] }); } + + private randomHostPolicyResponseActions(): Array { + return this.randomArray(this.randomN(8), () => + this.randomChoice([ + 'load_config', + 'workflow', + 'download_global_artifacts', + 'configure_malware', + 'read_malware_config', + 'load_malware_model', + 'read_kernel_config', + 'configure_kernel', + 'detect_process_events', + 'detect_file_write_events', + 'detect_file_open_events', + 'detect_image_load_events', + 'connect_kernel', + ]) + ); + } + + private randomHostPolicyResponseActionStatus(): HostPolicyResponseActionStatus { + return this.randomChoice([ + HostPolicyResponseActionStatus.failure, + HostPolicyResponseActionStatus.success, + HostPolicyResponseActionStatus.warning, + ]); + } } const fakeProcessNames = [ diff --git a/x-pack/plugins/endpoint/common/schema/policy.ts b/x-pack/plugins/endpoint/common/schema/policy.ts new file mode 100644 index 0000000000000..17d0cdff57ee0 --- /dev/null +++ b/x-pack/plugins/endpoint/common/schema/policy.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; + +export const GetPolicyResponseSchema = { + query: schema.object({ + hostId: schema.string(), + }), +}; diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index 8fce15d1c794c..a1ddc97a90d29 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -613,7 +613,7 @@ export enum HostPolicyResponseActionStatus { /** * The details of a given action */ -interface HostPolicyResponseActionDetails { +export interface HostPolicyResponseActionDetails { status: HostPolicyResponseActionStatus; message: string; } @@ -621,7 +621,7 @@ interface HostPolicyResponseActionDetails { /** * A known list of possible Endpoint actions */ -interface HostPolicyResponseActions { +export interface HostPolicyResponseActions { download_model: HostPolicyResponseActionDetails; ingest_events_config: HostPolicyResponseActionDetails; workflow: HostPolicyResponseActionDetails; @@ -642,9 +642,6 @@ interface HostPolicyResponseActions { read_kernel_config: HostPolicyResponseActionDetails; read_logging_config: HostPolicyResponseActionDetails; read_malware_config: HostPolicyResponseActionDetails; - // The list of possible Actions will change rapidly, so the below entry will allow - // them without us defining them here statically - [key: string]: HostPolicyResponseActionDetails; } interface HostPolicyResponseConfigurationStatus { @@ -656,7 +653,7 @@ interface HostPolicyResponseConfigurationStatus { * Information about the applying of a policy to a given host */ export interface HostPolicyResponse { - '@timestamp': string; + '@timestamp': number; elastic: { agent: { id: string; @@ -665,21 +662,29 @@ export interface HostPolicyResponse { ecs: { version: string; }; + host: { + id: string; + }; event: { - created: string; + created: number; kind: string; + id: string; }; agent: { version: string; id: string; }; endpoint: { - artifacts: {}; policy: { applied: { version: string; id: string; status: HostPolicyResponseActionStatus; + actions: Partial; + policy: { + id: string; + version: string; + }; response: { configurations: { malware: HostPolicyResponseConfigurationStatus; @@ -687,7 +692,6 @@ export interface HostPolicyResponse { logging: HostPolicyResponseConfigurationStatus; streaming: HostPolicyResponseConfigurationStatus; }; - actions: Partial; }; }; }; diff --git a/x-pack/plugins/endpoint/scripts/policy_mapping.json b/x-pack/plugins/endpoint/scripts/policy_mapping.json new file mode 100644 index 0000000000000..1fdd5d140e0ba --- /dev/null +++ b/x-pack/plugins/endpoint/scripts/policy_mapping.json @@ -0,0 +1,398 @@ +{ + "mappings": { + "_meta": { + "version": "1.6.0-dev" + }, + "date_detection": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "endpoint": { + "properties": { + "policy": { + "properties": { + "applied": { + "properties": { + "actions": { + "properties": { + "configure_elasticsearch_connection": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "configure_kernel": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "configure_logging": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "configure_malware": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "connect_kernel": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "detect_file_open_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "detect_file_write_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "detect_image_load_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "detect_process_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "download_global_artifacts": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "load_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "load_malware_model": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "read_elasticsearch_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "read_events_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "read_kernel_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "read_logging_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "read_malware_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "workflow": { + "properties": { + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + }, + "type": "object" + }, + "configurations": { + "properties": { + "events": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "logging": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "malware": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "streaming": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "policy": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + }, + "response": { + "type": "object" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "object" + } + }, + "type": "object" + } + } + }, + "event": { + "properties": { + "created": { + "type": "date" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": 10000 + } + }, + "refresh_interval": "5s" + } + } +} diff --git a/x-pack/plugins/endpoint/scripts/resolver_generator.ts b/x-pack/plugins/endpoint/scripts/resolver_generator.ts index 2129bef0624b8..30752877db824 100644 --- a/x-pack/plugins/endpoint/scripts/resolver_generator.ts +++ b/x-pack/plugins/endpoint/scripts/resolver_generator.ts @@ -10,6 +10,7 @@ import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { EndpointDocGenerator, Event } from '../common/generate_data'; import { default as eventMapping } from './event_mapping.json'; import { default as alertMapping } from './alert_mapping.json'; +import { default as policyMapping } from './policy_mapping.json'; main(); @@ -44,6 +45,12 @@ async function main() { default: 'metrics-endpoint-default-1', type: 'string', }, + policyIndex: { + alias: 'pi', + describe: 'index to store host policy in', + default: 'metrics-endpoint.policy-default-1', + type: 'string', + }, auth: { describe: 'elasticsearch username and password, separated by a colon', type: 'string', @@ -90,6 +97,12 @@ async function main() { type: 'number', default: 1, }, + numDocs: { + alias: 'nd', + describe: 'number of metadata and policy response doc to generate per host', + type: 'number', + default: 5, + }, alertsPerHost: { alias: 'ape', describe: 'number of resolver trees to make for each host', @@ -123,7 +136,7 @@ async function main() { if (argv.delete) { try { await client.indices.delete({ - index: [argv.eventIndex, argv.metadataIndex, argv.alertIndex], + index: [argv.eventIndex, argv.metadataIndex, argv.alertIndex, argv.policyIndex], }); } catch (err) { if (err instanceof ResponseError && err.statusCode !== 404) { @@ -165,6 +178,7 @@ async function main() { await createIndex(client, argv.alertIndex, alertMapping); await createIndex(client, argv.eventIndex, eventMapping); + await createIndex(client, argv.policyIndex, policyMapping); if (argv.setupOnly) { process.exit(0); } @@ -179,14 +193,19 @@ async function main() { for (let i = 0; i < argv.numHosts; i++) { const generator = new EndpointDocGenerator(random); const timeBetweenDocs = 6 * 3600 * 1000; // 6 hours between metadata documents - const numMetadataDocs = 5; + const timestamp = new Date().getTime(); - for (let j = 0; j < numMetadataDocs; j++) { + for (let j = 0; j < argv.numDocs; j++) { generator.updateHostData(); + generator.updatePolicyId(); await client.index({ index: argv.metadataIndex, - body: generator.generateHostMetadata( - timestamp - timeBetweenDocs * (numMetadataDocs - j - 1) + body: generator.generateHostMetadata(timestamp - timeBetweenDocs * (argv.numDocs - j - 1)), + }); + await client.index({ + index: argv.policyIndex, + body: generator.generatePolicyResponse( + timestamp - timeBetweenDocs * (argv.numDocs - j - 1) ), }); } diff --git a/x-pack/plugins/endpoint/server/index_pattern.ts b/x-pack/plugins/endpoint/server/index_pattern.ts index 903d48746bfb3..f4bb1460aee4b 100644 --- a/x-pack/plugins/endpoint/server/index_pattern.ts +++ b/x-pack/plugins/endpoint/server/index_pattern.ts @@ -11,6 +11,7 @@ export interface IndexPatternRetriever { getIndexPattern(ctx: RequestHandlerContext, datasetPath: string): Promise; getEventIndexPattern(ctx: RequestHandlerContext): Promise; getMetadataIndexPattern(ctx: RequestHandlerContext): Promise; + getPolicyResponseIndexPattern(ctx: RequestHandlerContext): Promise; } /** @@ -74,4 +75,8 @@ export class IngestIndexPatternRetriever implements IndexPatternRetriever { throw new Error(errMsg); } } + + getPolicyResponseIndexPattern(ctx: RequestHandlerContext): Promise { + return Promise.resolve('metrics-endpoint.policy-default-1'); + } } diff --git a/x-pack/plugins/endpoint/server/mocks.ts b/x-pack/plugins/endpoint/server/mocks.ts index 519ca15cf8427..76a3628562a82 100644 --- a/x-pack/plugins/endpoint/server/mocks.ts +++ b/x-pack/plugins/endpoint/server/mocks.ts @@ -4,7 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + IScopedClusterClient, + RequestHandlerContext, + SavedObjectsClientContract, +} from 'kibana/server'; import { AgentService, IngestManagerStartContract } from '../../ingest_manager/server'; +import { IndexPatternRetriever } from './index_pattern'; /** * Creates a mock IndexPatternRetriever for use in tests. @@ -12,12 +18,13 @@ import { AgentService, IngestManagerStartContract } from '../../ingest_manager/s * @param indexPattern a string index pattern to return when any of the mock's public methods are called. * @returns the same string passed in via `indexPattern` */ -export const createMockIndexPatternRetriever = (indexPattern: string) => { +export const createMockIndexPatternRetriever = (indexPattern: string): IndexPatternRetriever => { const mockGetFunc = jest.fn().mockResolvedValue(indexPattern); return { getIndexPattern: mockGetFunc, getEventIndexPattern: mockGetFunc, getMetadataIndexPattern: mockGetFunc, + getPolicyResponseIndexPattern: mockGetFunc, }; }; @@ -56,3 +63,24 @@ export const createMockIngestManagerStartContract = ( agentService: createMockAgentService(), }; }; + +export function createRouteHandlerContext( + dataClient: jest.Mocked, + savedObjectsClient: jest.Mocked +) { + return ({ + core: { + elasticsearch: { + dataClient, + }, + savedObjects: { + client: savedObjectsClient, + }, + }, + /** + * Using unknown here because the object defined is not a full `RequestHandlerContext`. We don't + * need all of the fields required to run the tests, but the `routeHandler` function requires a + * `RequestHandlerContext`. + */ + } as unknown) as RequestHandlerContext; +} diff --git a/x-pack/plugins/endpoint/server/plugin.ts b/x-pack/plugins/endpoint/server/plugin.ts index f3cc569ad16a7..ff10b9c0416f9 100644 --- a/x-pack/plugins/endpoint/server/plugin.ts +++ b/x-pack/plugins/endpoint/server/plugin.ts @@ -16,6 +16,7 @@ import { registerEndpointRoutes } from './routes/metadata'; import { IngestIndexPatternRetriever } from './index_pattern'; import { IngestManagerStartContract } from '../../ingest_manager/server'; import { EndpointAppContextService } from './endpoint_app_context_services'; +import { registerPolicyRoutes } from './routes/policy'; export type EndpointPluginStart = void; export type EndpointPluginSetup = void; @@ -87,6 +88,7 @@ export class EndpointPlugin registerResolverRoutes(router, endpointContext); registerAlertRoutes(router, endpointContext); registerIndexPatternRoute(router, endpointContext); + registerPolicyRoutes(router, endpointContext); } public start(core: CoreStart, plugins: EndpointPluginStartDependencies) { diff --git a/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts index 8f0c0b4c2efaf..5415ebcae31c4 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts @@ -9,7 +9,6 @@ import { IScopedClusterClient, KibanaResponseFactory, RequestHandler, - RequestHandlerContext, RouteConfig, SavedObjectsClientContract, } from 'kibana/server'; @@ -25,7 +24,11 @@ import { SearchResponse } from 'elasticsearch'; import { registerEndpointRoutes } from './index'; import { EndpointConfigSchema } from '../../config'; import * as data from '../../test_data/all_metadata_data.json'; -import { createMockAgentService, createMockMetadataIndexPatternRetriever } from '../../mocks'; +import { + createMockAgentService, + createMockMetadataIndexPatternRetriever, + createRouteHandlerContext, +} from '../../mocks'; import { AgentService } from '../../../../ingest_manager/server'; import Boom from 'boom'; import { EndpointAppContextService } from '../../endpoint_app_context_services'; @@ -66,27 +69,6 @@ describe('test endpoint route', () => { afterEach(() => endpointAppContextService.stop()); - function createRouteHandlerContext( - dataClient: jest.Mocked, - savedObjectsClient: jest.Mocked - ) { - return ({ - core: { - elasticsearch: { - dataClient, - }, - savedObjects: { - client: savedObjectsClient, - }, - }, - /** - * Using unknown here because the object defined is not a full `RequestHandlerContext`. We don't - * need all of the fields required to run the tests, but the `routeHandler` function requires a - * `RequestHandlerContext`. - */ - } as unknown) as RequestHandlerContext; - } - it('test find the latest of all endpoints', async () => { const mockRequest = httpServerMock.createKibanaRequest({}); diff --git a/x-pack/plugins/endpoint/server/routes/policy/handlers.test.ts b/x-pack/plugins/endpoint/server/routes/policy/handlers.test.ts new file mode 100644 index 0000000000000..9348353425370 --- /dev/null +++ b/x-pack/plugins/endpoint/server/routes/policy/handlers.test.ts @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EndpointAppContextService } from '../../endpoint_app_context_services'; +import { + createMockAgentService, + createMockIndexPatternRetriever, + createRouteHandlerContext, +} from '../../mocks'; +import { getHostPolicyResponseHandler } from './handlers'; +import { EndpointConfigSchema } from '../../config'; +import { + IScopedClusterClient, + KibanaResponseFactory, + SavedObjectsClientContract, +} from 'kibana/server'; +import { + elasticsearchServiceMock, + httpServerMock, + loggingServiceMock, + savedObjectsClientMock, +} from '../../../../../../src/core/server/mocks'; +import { AgentService } from '../../../../ingest_manager/server/services'; +import { SearchResponse } from 'elasticsearch'; +import { GetHostPolicyResponse, HostPolicyResponse } from '../../../common/types'; +import { EndpointDocGenerator } from '../../../common/generate_data'; + +describe('test policy response handler', () => { + let endpointAppContextService: EndpointAppContextService; + let mockScopedClient: jest.Mocked; + let mockSavedObjectClient: jest.Mocked; + let mockResponse: jest.Mocked; + let mockAgentService: jest.Mocked; + + beforeEach(() => { + mockScopedClient = elasticsearchServiceMock.createScopedClusterClient(); + mockSavedObjectClient = savedObjectsClientMock.create(); + mockResponse = httpServerMock.createResponseFactory(); + endpointAppContextService = new EndpointAppContextService(); + mockAgentService = createMockAgentService(); + endpointAppContextService.start({ + indexPatternRetriever: createMockIndexPatternRetriever('metrics-endpoint-policy-*'), + agentService: mockAgentService, + }); + }); + + afterEach(() => endpointAppContextService.stop()); + + it('should return the latest policy response for a host', async () => { + const response = createSearchResponse(new EndpointDocGenerator().generatePolicyResponse()); + const hostPolicyResponseHandler = getHostPolicyResponseHandler({ + logFactory: loggingServiceMock.create(), + service: endpointAppContextService, + config: () => Promise.resolve(EndpointConfigSchema.validate({})), + }); + + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response)); + const mockRequest = httpServerMock.createKibanaRequest({ + params: { hostId: 'id' }, + }); + + await hostPolicyResponseHandler( + createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), + mockRequest, + mockResponse + ); + + expect(mockResponse.ok).toBeCalled(); + const result = mockResponse.ok.mock.calls[0][0]?.body as GetHostPolicyResponse; + expect(result.policy_response.host.id).toEqual(response.hits.hits[0]._source.host.id); + }); + + it('should return not found when there is no response policy for host', async () => { + const hostPolicyResponseHandler = getHostPolicyResponseHandler({ + logFactory: loggingServiceMock.create(), + service: endpointAppContextService, + config: () => Promise.resolve(EndpointConfigSchema.validate({})), + }); + + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => + Promise.resolve(createSearchResponse()) + ); + + const mockRequest = httpServerMock.createKibanaRequest({ + params: { hostId: 'id' }, + }); + + await hostPolicyResponseHandler( + createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), + mockRequest, + mockResponse + ); + + expect(mockResponse.notFound).toBeCalled(); + const message = mockResponse.notFound.mock.calls[0][0]?.body; + expect(message).toEqual('Policy Response Not Found'); + }); +}); + +/** + * Create a SearchResponse with the hostPolicyResponse provided, else return an empty + * SearchResponse + * @param hostPolicyResponse + */ +function createSearchResponse( + hostPolicyResponse?: HostPolicyResponse +): SearchResponse { + return ({ + took: 15, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 5, + relation: 'eq', + }, + max_score: null, + hits: hostPolicyResponse + ? [ + { + _index: 'metrics-endpoint.policy-default-1', + _id: '8FhM0HEBYyRTvb6lOQnw', + _score: null, + _source: hostPolicyResponse, + sort: [1588337587997], + }, + ] + : [], + }, + } as unknown) as SearchResponse; +} diff --git a/x-pack/plugins/endpoint/server/routes/policy/handlers.ts b/x-pack/plugins/endpoint/server/routes/policy/handlers.ts new file mode 100644 index 0000000000000..5a34164c0bb37 --- /dev/null +++ b/x-pack/plugins/endpoint/server/routes/policy/handlers.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { RequestHandler } from 'kibana/server'; +import { TypeOf } from '@kbn/config-schema'; +import { GetPolicyResponseSchema } from '../../../common/schema/policy'; +import { EndpointAppContext } from '../../types'; +import { getPolicyResponseByHostId } from './service'; + +export const getHostPolicyResponseHandler = function( + endpointAppContext: EndpointAppContext +): RequestHandler, undefined> { + return async (context, request, response) => { + try { + const index = await endpointAppContext.service + .getIndexPatternRetriever() + .getPolicyResponseIndexPattern(context); + + const doc = await getPolicyResponseByHostId( + index, + request.query.hostId, + context.core.elasticsearch.dataClient + ); + + if (doc) { + return response.ok({ body: doc }); + } + + return response.notFound({ body: 'Policy Response Not Found' }); + } catch (err) { + return response.internalError({ body: err }); + } + }; +}; diff --git a/x-pack/plugins/endpoint/server/routes/policy/index.ts b/x-pack/plugins/endpoint/server/routes/policy/index.ts new file mode 100644 index 0000000000000..4c3bd8e21315c --- /dev/null +++ b/x-pack/plugins/endpoint/server/routes/policy/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; +import { EndpointAppContext } from '../../types'; +import { GetPolicyResponseSchema } from '../../../common/schema/policy'; +import { getHostPolicyResponseHandler } from './handlers'; + +export const BASE_POLICY_RESPONSE_ROUTE = `/api/endpoint/policy_response`; + +export function registerPolicyRoutes(router: IRouter, endpointAppContext: EndpointAppContext) { + router.get( + { + path: BASE_POLICY_RESPONSE_ROUTE, + validate: GetPolicyResponseSchema, + options: { authRequired: true }, + }, + getHostPolicyResponseHandler(endpointAppContext) + ); +} diff --git a/x-pack/plugins/endpoint/server/routes/policy/service.test.ts b/x-pack/plugins/endpoint/server/routes/policy/service.test.ts new file mode 100644 index 0000000000000..c7bf65627769e --- /dev/null +++ b/x-pack/plugins/endpoint/server/routes/policy/service.test.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GetPolicyResponseSchema } from '../../../common/schema/policy'; + +describe('test policy handlers schema', () => { + it('validate that get policy response query schema', async () => { + expect( + GetPolicyResponseSchema.query.validate({ + hostId: 'id', + }) + ).toBeTruthy(); + + expect(() => GetPolicyResponseSchema.query.validate({})).toThrowError(); + }); +}); diff --git a/x-pack/plugins/endpoint/server/routes/policy/service.ts b/x-pack/plugins/endpoint/server/routes/policy/service.ts new file mode 100644 index 0000000000000..7ec2c65634110 --- /dev/null +++ b/x-pack/plugins/endpoint/server/routes/policy/service.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchResponse } from 'elasticsearch'; +import { IScopedClusterClient } from 'kibana/server'; +import { GetHostPolicyResponse, HostPolicyResponse } from '../../../common/types'; + +export function getESQueryPolicyResponseByHostID(hostID: string, index: string) { + return { + body: { + query: { + match: { + 'host.id': hostID, + }, + }, + sort: [ + { + 'event.created': { + order: 'desc', + }, + }, + ], + size: 1, + }, + index, + }; +} + +export async function getPolicyResponseByHostId( + index: string, + hostId: string, + dataClient: IScopedClusterClient +): Promise { + const query = getESQueryPolicyResponseByHostID(hostId, index); + const response = (await dataClient.callAsCurrentUser('search', query)) as SearchResponse< + HostPolicyResponse + >; + + if (response.hits.hits.length === 0) { + return undefined; + } + + return { + policy_response: response.hits.hits[0]._source, + }; +} diff --git a/x-pack/test/api_integration/apis/endpoint/index.ts b/x-pack/test/api_integration/apis/endpoint/index.ts index 0a5f9aa595b8a..0c7c03da6c594 100644 --- a/x-pack/test/api_integration/apis/endpoint/index.ts +++ b/x-pack/test/api_integration/apis/endpoint/index.ts @@ -20,5 +20,6 @@ export default function endpointAPIIntegrationTests({ loadTestFile(require.resolve('./resolver')); loadTestFile(require.resolve('./metadata')); loadTestFile(require.resolve('./alerts')); + loadTestFile(require.resolve('./policy')); }); } diff --git a/x-pack/test/api_integration/apis/endpoint/policy.ts b/x-pack/test/api_integration/apis/endpoint/policy.ts new file mode 100644 index 0000000000000..76e4fe1a00ca4 --- /dev/null +++ b/x-pack/test/api_integration/apis/endpoint/policy.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect/expect.js'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + describe('Endpoint policy api', () => { + describe('GET /api/endpoint/policy_response', () => { + before(async () => await esArchiver.load('endpoint/policy')); + + after(async () => await esArchiver.unload('endpoint/policy')); + + it('should return one policy response for host', async () => { + const expectedHostId = '4f3b9858-a96d-49d8-a326-230d7763d767'; + const { body } = await supertest + .get(`/api/endpoint/policy_response?hostId=${expectedHostId}`) + .send() + .expect(200); + + expect(body.policy_response.host.id).to.eql(expectedHostId); + expect(body.policy_response.endpoint.policy).to.not.be(undefined); + }); + + it('should return not found if host has no policy response', async () => { + const { body } = await supertest + .get(`/api/endpoint/policy_response?hostId=bad_host_id`) + .send() + .expect(404); + + expect(body.message).to.contain('Policy Response Not Found'); + }); + }); + }); +} diff --git a/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz b/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..2fab424d27cad68350e8ea2544f0005c44e6a688 GIT binary patch literal 1329 zcmV-11QOZ*BnXSxax@HW0q&R}ebw0->P?^=dD5Q|Gvc zb_=v93WFjkhZ|WGB-z>QBLBUlB-?uWk;rX;>atiY)INGR{@A8NI$*KDK>HbS%Sc{+keE#+GHgKX}K7V;A#Fa!$*;ggBW_ij<23brK z!Q*_Eh=`MCNLh$CQ4WorMkY2XIqV@jEh*Q8Z&*S!$=Su%}m|Yb!0d* zU8`hY_IopuHFs{wgYF!`pUXXwW*l;{(_|$_a^a=Es??Tr!O;`7s=XjyE zS9|I1R|UPfVVQyUjyOpo&S+~{eIU8?D(ifZpNNPwl8XZ{P^lh86iPiQdkLaQtX4c;orLsTEiCnL?excv)EY-mIAtes{T{QgiOjr& z!qQZvdu$RE*clluXsX7}O=2TrJmxva5&tG}uH}!AM>$JJiW+6P!X<68rAS!3WLtT_ z`_9i1gjp_NE_HZZ^)DmZ5;JQHhTJA5838t5a zRYeu2sx}KNKv}si+9VZ9{*@I_Vy$dk3#fRCi)gMQ5#VSk_vE4M_?HyG&IVZgu>fm9 zPxZyi!IehON2KnuXMjNwD-i<`g8YH)n-MM+Ka`#0<49m-PUK^yO7*hb>YYO_VtXdi z`b7*9oMsYs1KgOHs+;1>yf2EIhO&&MMN?OI&At?W1hD|}`O4|;{wdex9U##~GWsI{ zPnqgT?Pt(Ap=}&{WdC(C}2jM&N52jx-Ie^JBi_S=@g8#>rw=N=Yp+aU>w%vWBJ zUA?^Gfkq5W*Js#<*q|69)3zWcCWfwyDMF@8ZR*r%#77<%dS!=S?z?CQ(}T!*c}o;p zf$u5a(sw9CiuS{n=|I!csp~oxbq{`*o~nOeZeDI~l&y1_+o;9dep`LIE_3VH*2LUS z%x%rm#N1BI?Zn)U19oC=C+2oyZeI&_Vs6L7G%>dmb2~A&W8s{b+y5JLtLt9in{s3f zFp={=WhV4&CxC7c8rVXn8yLEF)VX)b-1bO}O2Car$o90abNRPxJ2@V)x<9i6uJr`r z70zA68jnn&)(6Zp*y8%MclKWy1_Vz9cW@pWwSF#Yh+Tuj%TS$yn>RcVY|J7O nh^I(d6C&NVAtr$dnWftd6US$vQKt$2LuU3LlvaA7Q!4-f3D}nT literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/endpoint/policy/mappings.json b/x-pack/test/functional/es_archives/endpoint/policy/mappings.json new file mode 100644 index 0000000000000..4a9b0bdd11f0b --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/policy/mappings.json @@ -0,0 +1,512 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "metrics-endpoint.policy-default-1", + "mappings": { + "_meta": { + "version": "1.6.0-dev" + }, + "date_detection": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elastic": { + "properties": { + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "endpoint": { + "properties": { + "policy": { + "properties": { + "applied": { + "properties": { + "actions": { + "properties": { + "configure_elasticsearch_connection": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "configure_kernel": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "configure_logging": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "configure_malware": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "connect_kernel": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "detect_file_open_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "detect_file_write_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "detect_image_load_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "detect_process_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "download_global_artifacts": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "download_model": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ingest_events_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "load_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "load_malware_model": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "read_elasticsearch_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "read_events_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "read_kernel_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "read_logging_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "read_malware_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "workflow": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "configurations": { + "properties": { + "events": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "logging": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "streaming": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "policy": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "configurations": { + "properties": { + "events": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "logging": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "streaming": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "event": { + "properties": { + "created": { + "type": "date" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics-default" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "1", + "number_of_shards": "1", + "prefer_v2_templates": "true", + "query": { + "default_field": [ + "message" + ] + }, + "refresh_interval": "5s" + } + } + } +} From 91b27570c1bbc42f1e8809d0e45b961d0c8c5e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Mon, 4 May 2020 20:22:06 +0100 Subject: [PATCH 12/22] [APM] Ensure that `/api/apm/security/indices_privileges` doesn't fail when security is disabled (#64627) * logging when security api is disable * logging when security api is disable * checking statuc code 400 * adding security plugin * checking if security plugin is enabled before calling it * fixing unit test * show apm ui when index is empty * addressing PR comments * refactoring * refactoring Co-authored-by: Elastic Machine --- x-pack/plugins/apm/kibana.json | 3 +- ...icesPermission.test.tsx => index.test.tsx} | 92 ++++++----- .../app/APMIndicesPermission/index.tsx | 20 ++- .../apm/server/lib/helpers/es_client.ts | 3 +- .../security/get_indices_privileges.test.ts | 148 ++++++++++++++++++ .../lib/security/get_indices_privileges.ts | 16 +- .../server/lib/security/get_permissions.ts | 32 ---- x-pack/plugins/apm/server/plugin.ts | 7 +- .../server/routes/create_api/index.test.ts | 3 +- .../apm/server/routes/create_api/index.ts | 5 +- x-pack/plugins/apm/server/routes/security.ts | 6 +- x-pack/plugins/apm/server/routes/typings.ts | 7 + 12 files changed, 255 insertions(+), 87 deletions(-) rename x-pack/plugins/apm/public/components/app/APMIndicesPermission/{__test__/APMIndicesPermission.test.tsx => index.test.tsx} (63%) create mode 100644 x-pack/plugins/apm/server/lib/security/get_indices_privileges.test.ts delete mode 100644 x-pack/plugins/apm/server/lib/security/get_permissions.ts diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index 1a0ad67c7b696..85e3761129018 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -15,7 +15,8 @@ "usageCollection", "taskManager", "actions", - "alerting" + "alerting", + "security" ], "server": true, "ui": true, diff --git a/x-pack/plugins/apm/public/components/app/APMIndicesPermission/__test__/APMIndicesPermission.test.tsx b/x-pack/plugins/apm/public/components/app/APMIndicesPermission/index.test.tsx similarity index 63% rename from x-pack/plugins/apm/public/components/app/APMIndicesPermission/__test__/APMIndicesPermission.test.tsx rename to x-pack/plugins/apm/public/components/app/APMIndicesPermission/index.test.tsx index 68acaee4abe5d..b3f90fd9aee34 100644 --- a/x-pack/plugins/apm/public/components/app/APMIndicesPermission/__test__/APMIndicesPermission.test.tsx +++ b/x-pack/plugins/apm/public/components/app/APMIndicesPermission/index.test.tsx @@ -4,16 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; +import { render, fireEvent, act } from '@testing-library/react'; import { shallow } from 'enzyme'; -import { APMIndicesPermission } from '../'; +import { APMIndicesPermission } from './'; -import * as hooks from '../../../../hooks/useFetcher'; +import * as hooks from '../../../hooks/useFetcher'; import { expectTextsInDocument, expectTextsNotInDocument -} from '../../../../utils/testHelpers'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +} from '../../../utils/testHelpers'; +import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; describe('APMIndicesPermission', () => { it('returns empty component when api status is loading', () => { @@ -34,7 +34,10 @@ describe('APMIndicesPermission', () => { spyOn(hooks, 'useFetcher').and.returnValue({ status: hooks.FETCH_STATUS.SUCCESS, data: { - 'apm-*': { read: false } + has_all_requested: false, + index: { + 'apm-*': { read: false } + } } }); const component = render( @@ -48,39 +51,32 @@ describe('APMIndicesPermission', () => { 'apm-*' ]); }); - it('shows escape hatch button when at least one indice has read privileges', () => { + + it('shows children component when no index is returned', () => { spyOn(hooks, 'useFetcher').and.returnValue({ status: hooks.FETCH_STATUS.SUCCESS, data: { - 'apm-7.5.1-error-*': { read: false }, - 'apm-7.5.1-metric-*': { read: false }, - 'apm-7.5.1-transaction-*': { read: false }, - 'apm-7.5.1-span-*': { read: true } + has_all_requested: false, + index: {} } }); const component = render( - + +

My amazing component

+
); - expectTextsInDocument(component, [ - 'Missing permissions to access APM', - 'apm-7.5.1-error-*', - 'apm-7.5.1-metric-*', - 'apm-7.5.1-transaction-*', - 'Dismiss' - ]); - expectTextsNotInDocument(component, ['apm-7.5.1-span-*']); + expectTextsNotInDocument(component, ['Missing permissions to access APM']); + expectTextsInDocument(component, ['My amazing component']); }); it('shows children component when indices have read privileges', () => { spyOn(hooks, 'useFetcher').and.returnValue({ status: hooks.FETCH_STATUS.SUCCESS, data: { - 'apm-7.5.1-error-*': { read: true }, - 'apm-7.5.1-metric-*': { read: true }, - 'apm-7.5.1-transaction-*': { read: true }, - 'apm-7.5.1-span-*': { read: true } + has_all_requested: true, + index: {} } }); const component = render( @@ -90,13 +86,7 @@ describe('APMIndicesPermission', () => { ); - expectTextsNotInDocument(component, [ - 'Missing permissions to access APM', - 'apm-7.5.1-error-*', - 'apm-7.5.1-metric-*', - 'apm-7.5.1-transaction-*', - 'apm-7.5.1-span-*' - ]); + expectTextsNotInDocument(component, ['Missing permissions to access APM']); expectTextsInDocument(component, ['My amazing component']); }); @@ -104,10 +94,13 @@ describe('APMIndicesPermission', () => { spyOn(hooks, 'useFetcher').and.returnValue({ status: hooks.FETCH_STATUS.SUCCESS, data: { - 'apm-7.5.1-error-*': { read: false }, - 'apm-7.5.1-metric-*': { read: false }, - 'apm-7.5.1-transaction-*': { read: false }, - 'apm-7.5.1-span-*': { read: true } + has_all_requested: false, + index: { + 'apm-error-*': { read: false }, + 'apm-trasanction-*': { read: false }, + 'apm-metrics-*': { read: true }, + 'apm-span-*': { read: true } + } } }); const component = render( @@ -117,8 +110,33 @@ describe('APMIndicesPermission', () => { ); - expectTextsInDocument(component, ['Dismiss']); - fireEvent.click(component.getByText('Dismiss')); + expectTextsInDocument(component, [ + 'Dismiss', + 'apm-error-*', + 'apm-trasanction-*' + ]); + act(() => { + fireEvent.click(component.getByText('Dismiss')); + }); + expectTextsInDocument(component, ['My amazing component']); + }); + + it("shows children component when api doesn't return value", () => { + spyOn(hooks, 'useFetcher').and.returnValue({}); + const component = render( + + +

My amazing component

+
+
+ ); + expectTextsNotInDocument(component, [ + 'Missing permissions to access APM', + 'apm-7.5.1-error-*', + 'apm-7.5.1-metric-*', + 'apm-7.5.1-transaction-*', + 'apm-7.5.1-span-*' + ]); expectTextsInDocument(component, ['My amazing component']); }); }); diff --git a/x-pack/plugins/apm/public/components/app/APMIndicesPermission/index.tsx b/x-pack/plugins/apm/public/components/app/APMIndicesPermission/index.tsx index 40e039dcd40c5..9074726f76e6d 100644 --- a/x-pack/plugins/apm/public/components/app/APMIndicesPermission/index.tsx +++ b/x-pack/plugins/apm/public/components/app/APMIndicesPermission/index.tsx @@ -15,9 +15,9 @@ import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash'; import React, { useState } from 'react'; import styled from 'styled-components'; +import { isEmpty } from 'lodash'; import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; import { fontSize, pct, px, units } from '../../../style/variables'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; @@ -29,7 +29,7 @@ export const APMIndicesPermission: React.FC = ({ children }) => { setIsPermissionWarningDismissed ] = useState(false); - const { data: indicesPrivileges = {}, status } = useFetcher(callApmApi => { + const { data: indicesPrivileges, status } = useFetcher(callApmApi => { return callApmApi({ pathname: '/api/apm/security/indices_privileges' }); @@ -40,13 +40,17 @@ export const APMIndicesPermission: React.FC = ({ children }) => { return null; } - const indicesWithoutPermission = Object.keys(indicesPrivileges).filter( - index => !indicesPrivileges[index].read - ); - // Show permission warning when a user has at least one index without Read privilege, - // and he has not manually dismissed the warning - if (!isEmpty(indicesWithoutPermission) && !isPermissionWarningDismissed) { + // and they have not manually dismissed the warning + if ( + indicesPrivileges && + !indicesPrivileges.has_all_requested && + !isEmpty(indicesPrivileges.index) && + !isPermissionWarningDismissed + ) { + const indicesWithoutPermission = Object.keys( + indicesPrivileges.index + ).filter(index => !indicesPrivileges.index[index].read); return ( = Omit, 'type'>; -interface IndexPrivileges { +export interface IndexPrivileges { has_all_requested: boolean; - username: string; index: Record; } diff --git a/x-pack/plugins/apm/server/lib/security/get_indices_privileges.test.ts b/x-pack/plugins/apm/server/lib/security/get_indices_privileges.test.ts new file mode 100644 index 0000000000000..c1bc48f4ed1fd --- /dev/null +++ b/x-pack/plugins/apm/server/lib/security/get_indices_privileges.test.ts @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Setup } from '../helpers/setup_request'; +import { getIndicesPrivileges } from './get_indices_privileges'; + +describe('getIndicesPrivileges', () => { + const indices = { + apm_oss: { + errorIndices: 'apm-*', + metricsIndices: 'apm-*', + transactionIndices: 'apm-*', + spanIndices: 'apm-*' + } + }; + it('return that the user has privileges when security plugin is disabled', async () => { + const setup = ({ + indices, + client: { + hasPrivileges: () => { + const error = { + message: + 'no handler found for uri [/_security/user/_has_privileges]', + statusCode: 400 + }; + throw error; + } + } + } as unknown) as Setup; + const privileges = await getIndicesPrivileges({ + setup, + isSecurityPluginEnabled: false + }); + expect(privileges).toEqual({ + has_all_requested: true, + index: {} + }); + }); + it('throws when an error happens while fetching indices privileges', async () => { + const setup = ({ + indices, + client: { + hasPrivileges: () => { + throw new Error('unknow error'); + } + } + } as unknown) as Setup; + await expect( + getIndicesPrivileges({ setup, isSecurityPluginEnabled: true }) + ).rejects.toThrowError('unknow error'); + }); + it("has privileges to read from 'apm-*'", async () => { + const setup = ({ + indices, + client: { + hasPrivileges: () => { + return Promise.resolve({ + has_all_requested: true, + index: { 'apm-*': { read: true } } + }); + } + } + } as unknown) as Setup; + const privileges = await getIndicesPrivileges({ + setup, + isSecurityPluginEnabled: true + }); + + expect(privileges).toEqual({ + has_all_requested: true, + index: { + 'apm-*': { + read: true + } + } + }); + }); + + it("doesn't have privileges to read from 'apm-*'", async () => { + const setup = ({ + indices, + client: { + hasPrivileges: () => { + return Promise.resolve({ + has_all_requested: false, + index: { 'apm-*': { read: false } } + }); + } + } + } as unknown) as Setup; + + const privileges = await getIndicesPrivileges({ + setup, + isSecurityPluginEnabled: true + }); + + expect(privileges).toEqual({ + has_all_requested: false, + index: { + 'apm-*': { + read: false + } + } + }); + }); + it("doesn't have privileges on multiple indices", async () => { + const setup = ({ + indices: { + apm_oss: { + errorIndices: 'apm-error-*', + metricsIndices: 'apm-metrics-*', + transactionIndices: 'apm-trasanction-*', + spanIndices: 'apm-span-*' + } + }, + client: { + hasPrivileges: () => { + return Promise.resolve({ + has_all_requested: false, + index: { + 'apm-error-*': { read: false }, + 'apm-trasanction-*': { read: false }, + 'apm-metrics-*': { read: true }, + 'apm-span-*': { read: true } + } + }); + } + } + } as unknown) as Setup; + + const privileges = await getIndicesPrivileges({ + setup, + isSecurityPluginEnabled: true + }); + + expect(privileges).toEqual({ + has_all_requested: false, + index: { + 'apm-error-*': { read: false }, + 'apm-trasanction-*': { read: false }, + 'apm-metrics-*': { read: true }, + 'apm-span-*': { read: true } + } + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/security/get_indices_privileges.ts b/x-pack/plugins/apm/server/lib/security/get_indices_privileges.ts index 1a80a13b2ad19..46ed64f518bb8 100644 --- a/x-pack/plugins/apm/server/lib/security/get_indices_privileges.ts +++ b/x-pack/plugins/apm/server/lib/security/get_indices_privileges.ts @@ -4,8 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ import { Setup } from '../helpers/setup_request'; +import { IndexPrivileges } from '../helpers/es_client'; + +export async function getIndicesPrivileges({ + setup, + isSecurityPluginEnabled +}: { + setup: Setup; + isSecurityPluginEnabled: boolean; +}): Promise { + // When security plugin is not enabled, returns that the user has all requested privileges. + if (!isSecurityPluginEnabled) { + return { has_all_requested: true, index: {} }; + } -export async function getIndicesPrivileges(setup: Setup) { const { client, indices } = setup; const response = await client.hasPrivileges({ index: [ @@ -20,5 +32,5 @@ export async function getIndicesPrivileges(setup: Setup) { } ] }); - return response.index; + return response; } diff --git a/x-pack/plugins/apm/server/lib/security/get_permissions.ts b/x-pack/plugins/apm/server/lib/security/get_permissions.ts deleted file mode 100644 index ed2a1f64e7f84..0000000000000 --- a/x-pack/plugins/apm/server/lib/security/get_permissions.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { Setup } from '../helpers/setup_request'; - -export async function getPermissions(setup: Setup) { - const { client, indices } = setup; - - const params = { - index: Object.values(indices), - body: { - size: 0, - query: { - match_all: {} - } - } - }; - - try { - await client.search(params); - return { hasPermission: true }; - } catch (e) { - // If 403, it means the user doesnt have permission. - if (e.status === 403) { - return { hasPermission: false }; - } - // if any other error happens, throw it. - throw e; - } -} diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 8cf29de5b8b73..29ab618cbdd0a 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -12,6 +12,7 @@ import { } from 'src/core/server'; import { Observable, combineLatest } from 'rxjs'; import { map, take } from 'rxjs/operators'; +import { SecurityPluginSetup } from '../../security/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; import { TaskManagerSetupContract } from '../../task_manager/server'; import { AlertingPlugin } from '../../alerting/server'; @@ -57,6 +58,7 @@ export class APMPlugin implements Plugin { alerting?: AlertingPlugin['setup']; actions?: ActionsPlugin['setup']; features: FeaturesPluginSetup; + security?: SecurityPluginSetup; } ) { this.logger = this.initContext.logger.get(); @@ -110,7 +112,10 @@ export class APMPlugin implements Plugin { createApmApi().init(core, { config$: mergedConfig$, - logger: this.logger! + logger: this.logger!, + plugins: { + security: plugins.security + } }); return { diff --git a/x-pack/plugins/apm/server/routes/create_api/index.test.ts b/x-pack/plugins/apm/server/routes/create_api/index.test.ts index 20c586868a979..6236fcb0a6942 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.test.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.test.ts @@ -39,7 +39,8 @@ const getCoreMock = () => { config$: new BehaviorSubject({} as APMConfig), logger: ({ error: jest.fn() - } as unknown) as Logger + } as unknown) as Logger, + plugins: {} } }; }; diff --git a/x-pack/plugins/apm/server/routes/create_api/index.ts b/x-pack/plugins/apm/server/routes/create_api/index.ts index a97e2f30fc2b6..9b611a0bbd6bc 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.ts @@ -30,7 +30,7 @@ export function createApi() { factoryFns.push(fn); return this as any; }, - init(core, { config$, logger }) { + init(core, { config$, logger, plugins }) { const router = core.http.createRouter(); let config = {} as APMConfig; @@ -141,7 +141,8 @@ export function createApi() { // it's not defined in the route. params: pick(parsedParams, ...Object.keys(params), 'query'), config, - logger + logger, + plugins } }); diff --git a/x-pack/plugins/apm/server/routes/security.ts b/x-pack/plugins/apm/server/routes/security.ts index 0a8222b665d83..1e2a302ab9a4a 100644 --- a/x-pack/plugins/apm/server/routes/security.ts +++ b/x-pack/plugins/apm/server/routes/security.ts @@ -12,6 +12,10 @@ export const indicesPrivilegesRoute = createRoute(() => ({ path: '/api/apm/security/indices_privileges', handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - return getIndicesPrivileges(setup); + return getIndicesPrivileges({ + setup, + isSecurityPluginEnabled: + context.plugins.security?.license.isEnabled() ?? false + }); } })); diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts index 6543f2015599b..e049255eb8ec8 100644 --- a/x-pack/plugins/apm/server/routes/typings.ts +++ b/x-pack/plugins/apm/server/routes/typings.ts @@ -16,6 +16,7 @@ import { Observable } from 'rxjs'; import { Server } from 'hapi'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { FetchOptions } from '../../public/services/rest/callApi'; +import { SecurityPluginSetup } from '../../../security/public'; import { APMConfig } from '..'; export interface Params { @@ -62,6 +63,9 @@ export type APMRequestHandlerContext< params: { query: { _debug: boolean } } & TDecodedParams; config: APMConfig; logger: Logger; + plugins: { + security?: SecurityPluginSetup; + }; }; export type RouteFactoryFn< @@ -105,6 +109,9 @@ export interface ServerAPI { context: { config$: Observable; logger: Logger; + plugins: { + security?: SecurityPluginSetup; + }; } ) => void; } From 497398e8ff636541afd3baddbb48179eb2c682e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Mon, 4 May 2020 20:23:44 +0100 Subject: [PATCH 13/22] fixing unit test (#65068) --- .../CustomLinkFlyout/LinkPreview.test.tsx | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.test.tsx index 2f4d9a4c4016d..a1afbd7a807cc 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.test.tsx @@ -5,9 +5,25 @@ */ import React from 'react'; import { LinkPreview } from '../CustomLinkFlyout/LinkPreview'; -import { render, getNodeText, getByTestId, act } from '@testing-library/react'; +import { + render, + getNodeText, + getByTestId, + act, + wait +} from '@testing-library/react'; +import * as apmApi from '../../../../../../services/rest/createCallApmApi'; describe('LinkPreview', () => { + let callApmApiSpy: jasmine.Spy; + beforeAll(() => { + callApmApiSpy = spyOn(apmApi, 'callApmApi').and.returnValue({ + transaction: { id: 'foo' } + }); + }); + afterAll(() => { + jest.clearAllMocks(); + }); const getElementValue = (container: HTMLElement, id: string) => getNodeText( ((getByTestId(container, id) as HTMLDivElement) @@ -42,7 +58,7 @@ describe('LinkPreview', () => { }); }); - it('shows warning when couldnt replace context variables', () => { + it("shows warning when couldn't replace context variables", () => { act(() => { const { container } = render( { expect(getByTestId(container, 'preview-warning')).toBeInTheDocument(); }); }); + it('replaces url with transaction id', async () => { + const { container } = render( + + ); + await wait(() => expect(callApmApiSpy).toHaveBeenCalled()); + expect(getElementValue(container, 'preview-label')).toEqual('foo'); + expect( + (getByTestId(container, 'preview-link') as HTMLAnchorElement).text + ).toEqual('https://baz.co?transaction=foo'); + }); }); From e3b9b946a9578fabaf0dae58d4aea54040b1b701 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 4 May 2020 13:57:25 -0600 Subject: [PATCH 14/22] [Maps] initial location map settings (#64336) * [Maps] initial location map settings * fix tslint * add button to set to current view * move button to bottom of form * review feedback Co-authored-by: Elastic Machine --- x-pack/plugins/maps/common/constants.ts | 6 + .../maps/public/actions/map_actions.d.ts | 2 +- .../map/mb/get_initial_view.ts | 45 +++ .../connected_components/map/mb/view.js | 3 +- .../navigation_panel.test.tsx.snap | 292 ++++++++++++++++++ .../map_settings_panel/index.ts | 13 +- .../map_settings_panel/map_settings_panel.tsx | 14 +- .../navigation_panel.test.tsx | 45 +++ .../map_settings_panel/navigation_panel.tsx | 200 +++++++++++- .../public/reducers/default_map_settings.ts | 5 +- x-pack/plugins/maps/public/reducers/map.d.ts | 10 + 11 files changed, 620 insertions(+), 15 deletions(-) create mode 100644 x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts create mode 100644 x-pack/plugins/maps/public/connected_components/map_settings_panel/__snapshots__/navigation_panel.test.tsx.snap create mode 100644 x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.test.tsx diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index fd972219563a8..b1a613e789e85 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -217,3 +217,9 @@ export enum SCALING_TYPES { export const RGBA_0000 = 'rgba(0,0,0,0)'; export const SPATIAL_FILTERS_LAYER_ID = 'SPATIAL_FILTERS_LAYER_ID'; + +export enum INITIAL_LOCATION { + LAST_SAVED_LOCATION = 'LAST_SAVED_LOCATION', + FIXED_LOCATION = 'FIXED_LOCATION', + BROWSER_LOCATION = 'BROWSER_LOCATION', +} diff --git a/x-pack/plugins/maps/public/actions/map_actions.d.ts b/x-pack/plugins/maps/public/actions/map_actions.d.ts index 38c56405787eb..413b440279d77 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.d.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.d.ts @@ -72,7 +72,7 @@ export function trackMapSettings(): AnyAction; export function updateMapSetting( settingKey: string, - settingValue: string | boolean | number + settingValue: string | boolean | number | object ): AnyAction; export function cloneLayer(layerId: string): AnyAction; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts b/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts new file mode 100644 index 0000000000000..30e3b9b46916b --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { INITIAL_LOCATION } from '../../../../common/constants'; +import { Goto, MapCenterAndZoom } from '../../../../common/descriptor_types'; +import { MapSettings } from '../../../reducers/map'; + +export async function getInitialView( + goto: Goto | null, + settings: MapSettings +): Promise { + if (settings.initialLocation === INITIAL_LOCATION.FIXED_LOCATION) { + return { + lat: settings.fixedLocation.lat, + lon: settings.fixedLocation.lon, + zoom: settings.fixedLocation.zoom, + }; + } + + if (settings.initialLocation === INITIAL_LOCATION.BROWSER_LOCATION) { + return await new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition( + // success callback + pos => { + resolve({ + lat: pos.coords.latitude, + lon: pos.coords.longitude, + zoom: settings.browserLocation.zoom, + }); + }, + // error callback + () => { + // eslint-disable-next-line no-console + console.warn('Unable to fetch browser location for initial map location'); + resolve(null); + } + ); + }); + } + + return goto && goto.center ? goto.center : null; +} diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/map/mb/view.js index 6bb5a4fed6e52..7afb326f42e02 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/view.js @@ -24,6 +24,7 @@ import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { DrawControl } from './draw_control'; import { TooltipControl } from './tooltip_control'; import { clampToLatBounds, clampToLonBounds } from '../../../elasticsearch_geo_utils'; +import { getInitialView } from './get_initial_view'; import { getInjectedVarFunc } from '../../../kibana_services'; @@ -112,6 +113,7 @@ export class MBMapContainer extends React.Component { } async _createMbMapInstance() { + const initialView = await getInitialView(this.props.goto, this.props.settings); return new Promise(resolve => { const mbStyle = { version: 8, @@ -133,7 +135,6 @@ export class MBMapContainer extends React.Component { maxZoom: this.props.settings.maxZoom, minZoom: this.props.settings.minZoom, }; - const initialView = _.get(this.props.goto, 'center'); if (initialView) { options.zoom = initialView.zoom; options.center = { diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/__snapshots__/navigation_panel.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/map_settings_panel/__snapshots__/navigation_panel.test.tsx.snap new file mode 100644 index 0000000000000..641dd20a1a44a --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/__snapshots__/navigation_panel.test.tsx.snap @@ -0,0 +1,292 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` + + +
+ +
+
+ + + + + +
+`; + +exports[`should render browser location form when initialLocation is BROWSER_LOCATION 1`] = ` + + +
+ +
+
+ + + + + + + + +
+`; + +exports[`should render fixed location form when initialLocation is FIXED_LOCATION 1`] = ` + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
+`; diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/index.ts b/x-pack/plugins/maps/public/connected_components/map_settings_panel/index.ts index 329fac28d7d2e..eaa49719059c5 100644 --- a/x-pack/plugins/maps/public/connected_components/map_settings_panel/index.ts +++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/index.ts @@ -10,13 +10,20 @@ import { FLYOUT_STATE } from '../../reducers/ui'; import { MapStoreState } from '../../reducers/store'; import { MapSettingsPanel } from './map_settings_panel'; import { rollbackMapSettings, updateMapSetting } from '../../actions/map_actions'; -import { getMapSettings, hasMapSettingsChanges } from '../../selectors/map_selectors'; +import { + getMapCenter, + getMapSettings, + getMapZoom, + hasMapSettingsChanges, +} from '../../selectors/map_selectors'; import { updateFlyout } from '../../actions/ui_actions'; function mapStateToProps(state: MapStoreState) { return { - settings: getMapSettings(state), + center: getMapCenter(state), hasMapSettingsChanges: hasMapSettingsChanges(state), + settings: getMapSettings(state), + zoom: getMapZoom(state), }; } @@ -29,7 +36,7 @@ function mapDispatchToProps(dispatch: Dispatch) { keepChanges: () => { dispatch(updateFlyout(FLYOUT_STATE.NONE)); }, - updateMapSetting: (settingKey: string, settingValue: string | number | boolean) => { + updateMapSetting: (settingKey: string, settingValue: string | number | boolean | object) => { dispatch(updateMapSetting(settingKey, settingValue)); }, }; diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx b/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx index a89f4461fff06..66b979869416d 100644 --- a/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx @@ -20,21 +20,26 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { MapSettings } from '../../reducers/map'; import { NavigationPanel } from './navigation_panel'; import { SpatialFiltersPanel } from './spatial_filters_panel'; +import { MapCenter } from '../../../common/descriptor_types'; interface Props { cancelChanges: () => void; + center: MapCenter; hasMapSettingsChanges: boolean; keepChanges: () => void; settings: MapSettings; - updateMapSetting: (settingKey: string, settingValue: string | number | boolean) => void; + updateMapSetting: (settingKey: string, settingValue: string | number | boolean | object) => void; + zoom: number; } export function MapSettingsPanel({ cancelChanges, + center, hasMapSettingsChanges, keepChanges, settings, updateMapSetting, + zoom, }: Props) { // TODO move common text like Cancel and Close to common i18n translation const closeBtnLabel = hasMapSettingsChanges @@ -60,7 +65,12 @@ export function MapSettingsPanel({
- +
diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.test.tsx b/x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.test.tsx new file mode 100644 index 0000000000000..d785a30324e4e --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.test.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { NavigationPanel } from './navigation_panel'; +import { getDefaultMapSettings } from '../../reducers/default_map_settings'; +import { INITIAL_LOCATION } from '../../../common/constants'; + +const defaultProps = { + center: { lat: 0, lon: 0 }, + settings: getDefaultMapSettings(), + updateMapSetting: () => {}, + zoom: 0, +}; + +test('should render', async () => { + const component = shallow(); + + expect(component).toMatchSnapshot(); +}); + +test('should render fixed location form when initialLocation is FIXED_LOCATION', async () => { + const settings = { + ...defaultProps.settings, + initialLocation: INITIAL_LOCATION.FIXED_LOCATION, + }; + const component = shallow(); + + expect(component).toMatchSnapshot(); +}); + +test('should render browser location form when initialLocation is BROWSER_LOCATION', async () => { + const settings = { + ...defaultProps.settings, + initialLocation: INITIAL_LOCATION.BROWSER_LOCATION, + }; + const component = shallow(); + + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.tsx b/x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.tsx index ed83e838f44f6..0e12f20dd9a7a 100644 --- a/x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.tsx @@ -4,25 +4,198 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; +import React, { ChangeEvent } from 'react'; +import { + EuiButtonEmpty, + EuiFieldNumber, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPanel, + EuiRadioGroup, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { MapSettings } from '../../reducers/map'; import { ValidatedDualRange, Value } from '../../../../../../src/plugins/kibana_react/public'; -import { MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; +import { INITIAL_LOCATION, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; +import { MapCenter } from '../../../common/descriptor_types'; +// @ts-ignore +import { ValidatedRange } from '../../components/validated_range'; interface Props { + center: MapCenter; settings: MapSettings; - updateMapSetting: (settingKey: string, settingValue: string | number | boolean) => void; + updateMapSetting: (settingKey: string, settingValue: string | number | boolean | object) => void; + zoom: number; } -export function NavigationPanel({ settings, updateMapSetting }: Props) { +const initialLocationOptions = [ + { + id: INITIAL_LOCATION.LAST_SAVED_LOCATION, + label: i18n.translate('xpack.maps.mapSettingsPanel.lastSavedLocationLabel', { + defaultMessage: 'Map location at save', + }), + }, + { + id: INITIAL_LOCATION.FIXED_LOCATION, + label: i18n.translate('xpack.maps.mapSettingsPanel.fixedLocationLabel', { + defaultMessage: 'Fixed location', + }), + }, + { + id: INITIAL_LOCATION.BROWSER_LOCATION, + label: i18n.translate('xpack.maps.mapSettingsPanel.browserLocationLabel', { + defaultMessage: 'Browser location', + }), + }, +]; + +export function NavigationPanel({ center, settings, updateMapSetting, zoom }: Props) { const onZoomChange = (value: Value) => { - updateMapSetting('minZoom', Math.max(MIN_ZOOM, parseInt(value[0] as string, 10))); - updateMapSetting('maxZoom', Math.min(MAX_ZOOM, parseInt(value[1] as string, 10))); + const minZoom = Math.max(MIN_ZOOM, parseInt(value[0] as string, 10)); + const maxZoom = Math.min(MAX_ZOOM, parseInt(value[1] as string, 10)); + updateMapSetting('minZoom', minZoom); + updateMapSetting('maxZoom', maxZoom); + + // ensure fixed zoom and browser zoom stay within defined min/max + if (settings.fixedLocation.zoom < minZoom) { + onFixedZoomChange(minZoom); + } else if (settings.fixedLocation.zoom > maxZoom) { + onFixedZoomChange(maxZoom); + } + + if (settings.browserLocation.zoom < minZoom) { + onBrowserZoomChange(minZoom); + } else if (settings.browserLocation.zoom > maxZoom) { + onBrowserZoomChange(maxZoom); + } + }; + + const onInitialLocationChange = (optionId: string): void => { + updateMapSetting('initialLocation', optionId); + }; + + const onFixedLatChange = (event: ChangeEvent) => { + let value = parseFloat(event.target.value); + if (isNaN(value)) { + value = 0; + } else if (value < -90) { + value = -90; + } else if (value > 90) { + value = 90; + } + updateMapSetting('fixedLocation', { ...settings.fixedLocation, lat: value }); + }; + + const onFixedLonChange = (event: ChangeEvent) => { + let value = parseFloat(event.target.value); + if (isNaN(value)) { + value = 0; + } else if (value < -180) { + value = -180; + } else if (value > 180) { + value = 180; + } + updateMapSetting('fixedLocation', { ...settings.fixedLocation, lon: value }); + }; + + const onFixedZoomChange = (value: number) => { + updateMapSetting('fixedLocation', { ...settings.fixedLocation, zoom: value }); + }; + + const onBrowserZoomChange = (value: number) => { + updateMapSetting('browserLocation', { zoom: value }); + }; + + const useCurrentView = () => { + updateMapSetting('fixedLocation', { + lat: center.lat, + lon: center.lon, + zoom: Math.round(zoom), + }); }; + function renderInitialLocationInputs() { + if (settings.initialLocation === INITIAL_LOCATION.LAST_SAVED_LOCATION) { + return null; + } + + const zoomFormRow = ( + + + + ); + + if (settings.initialLocation === INITIAL_LOCATION.BROWSER_LOCATION) { + return zoomFormRow; + } + + return ( + <> + + + + + + + {zoomFormRow} + + + + + + + + + ); + } + return ( @@ -50,6 +223,19 @@ export function NavigationPanel({ settings, updateMapSetting }: Props) { allowEmptyRange={false} compressed /> + + + + + {renderInitialLocationInputs()} ); } diff --git a/x-pack/plugins/maps/public/reducers/default_map_settings.ts b/x-pack/plugins/maps/public/reducers/default_map_settings.ts index fe21b37434edd..9c9b814ae6add 100644 --- a/x-pack/plugins/maps/public/reducers/default_map_settings.ts +++ b/x-pack/plugins/maps/public/reducers/default_map_settings.ts @@ -4,11 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MAX_ZOOM, MIN_ZOOM } from '../../common/constants'; +import { INITIAL_LOCATION, MAX_ZOOM, MIN_ZOOM } from '../../common/constants'; import { MapSettings } from './map'; export function getDefaultMapSettings(): MapSettings { return { + initialLocation: INITIAL_LOCATION.LAST_SAVED_LOCATION, + fixedLocation: { lat: 0, lon: 0, zoom: 2 }, + browserLocation: { zoom: 2 }, maxZoom: MAX_ZOOM, minZoom: MIN_ZOOM, showSpatialFilters: true, diff --git a/x-pack/plugins/maps/public/reducers/map.d.ts b/x-pack/plugins/maps/public/reducers/map.d.ts index be0700d4bdd6d..20e1dc1035e19 100644 --- a/x-pack/plugins/maps/public/reducers/map.d.ts +++ b/x-pack/plugins/maps/public/reducers/map.d.ts @@ -15,6 +15,7 @@ import { MapRefreshConfig, TooltipState, } from '../../common/descriptor_types'; +import { INITIAL_LOCATION } from '../../common/constants'; import { Filter, TimeRange } from '../../../../../src/plugins/data/public'; export type MapContext = { @@ -40,6 +41,15 @@ export type MapContext = { }; export type MapSettings = { + initialLocation: INITIAL_LOCATION; + fixedLocation: { + lat: number; + lon: number; + zoom: number; + }; + browserLocation: { + zoom: number; + }; maxZoom: number; minZoom: number; showSpatialFilters: boolean; From dccb1dcca13ba601d3304d14060969ae3a8461c4 Mon Sep 17 00:00:00 2001 From: spalger Date: Mon, 4 May 2020 12:59:10 -0700 Subject: [PATCH 15/22] Revert "[ML] Embeddable Anomaly Swimlane (#64056)" This reverts commit f62df99ae38801b905dd10d281dbf972c5b17578. --- x-pack/plugins/ml/kibana.json | 4 +- .../chart_tooltip/chart_tooltip.tsx | 182 +++++----- .../chart_tooltip/chart_tooltip_service.d.ts | 42 +++ .../chart_tooltip/chart_tooltip_service.js | 37 ++ .../chart_tooltip_service.test.ts | 63 +--- .../chart_tooltip/chart_tooltip_service.ts | 73 ---- .../components/chart_tooltip/index.ts | 4 +- .../components/job_selector/job_selector.tsx | 280 ++++++++++++++-- .../job_selector_badge/{index.ts => index.js} | 0 ...lector_badge.tsx => job_selector_badge.js} | 35 +- .../job_selector/job_selector_flyout.tsx | 289 ---------------- .../job_selector_table/job_selector_table.js | 2 +- .../{index.ts => index.js} | 0 ..._badges.tsx => new_selection_id_badges.js} | 32 +- .../datavisualizer/index_based/page.tsx | 4 +- ...sx.snap => explorer_swimlane.test.js.snap} | 0 .../application/explorer/_explorer.scss | 316 +++++++++--------- .../public/application/explorer/explorer.js | 63 ++-- .../explorer_chart_distribution.js | 12 +- .../explorer_chart_distribution.test.js | 34 +- .../explorer_chart_single_metric.js | 14 +- .../explorer_chart_single_metric.test.js | 34 +- .../explorer_charts_container.js | 109 +++--- .../explorer_charts_container.test.js | 12 +- .../explorer/explorer_constants.ts | 10 +- .../explorer/explorer_dashboard_service.ts | 7 +- ...orer_swimlane.tsx => explorer_swimlane.js} | 253 ++++++-------- ...ane.test.tsx => explorer_swimlane.test.js} | 50 +-- .../application/explorer/explorer_utils.d.ts | 10 +- .../application/explorer/explorer_utils.js | 4 +- .../components/charts/common/settings.ts | 4 +- .../jobs/new_job/pages/new_job/page.tsx | 4 +- .../services/anomaly_detector_service.ts | 58 ---- .../application/services/explorer_service.ts | 308 ----------------- .../application/services/http_service.ts | 104 +----- .../services/results_service/index.ts | 2 - .../results_service/results_service.d.ts | 11 +- .../timeseries_chart/timeseries_chart.d.ts | 2 - .../timeseries_chart/timeseries_chart.js | 23 +- .../timeseries_chart_annotations.ts | 8 +- .../timeseriesexplorer/timeseriesexplorer.js | 30 +- .../timeseriesexplorer_utils.js | 6 +- .../public/application/util/chart_utils.d.ts | 7 - .../ml/public/application/util/date_utils.ts | 2 +- .../public/application/util/time_buckets.d.ts | 37 +- .../public/application/util/time_buckets.js | 26 +- .../application/util/time_buckets.test.js | 28 +- .../anomaly_swimlane_embeddable.tsx | 115 ------- ...omaly_swimlane_embeddable_factory.test.tsx | 50 --- .../anomaly_swimlane_embeddable_factory.ts | 81 ----- .../anomaly_swimlane_initializer.tsx | 201 ----------- .../anomaly_swimlane_setup_flyout.tsx | 95 ------ .../explorer_swimlane_container.test.tsx | 124 ------- .../explorer_swimlane_container.tsx | 122 ------- .../embeddables/anomaly_swimlane/index.ts | 8 - .../swimlane_input_resolver.test.ts | 271 --------------- .../swimlane_input_resolver.ts | 211 ------------ x-pack/plugins/ml/public/embeddables/index.ts | 23 -- x-pack/plugins/ml/public/plugin.ts | 13 - .../ui_actions/edit_swimlane_panel_action.tsx | 65 ---- x-pack/plugins/ml/public/ui_actions/index.ts | 30 -- 61 files changed, 944 insertions(+), 3100 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.d.ts create mode 100644 x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.js delete mode 100644 x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.ts rename x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/{index.ts => index.js} (100%) rename x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/{job_selector_badge.tsx => job_selector_badge.js} (68%) delete mode 100644 x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx rename x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/{index.ts => index.js} (100%) rename x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/{new_selection_id_badges.tsx => new_selection_id_badges.js} (80%) rename x-pack/plugins/ml/public/application/explorer/__snapshots__/{explorer_swimlane.test.tsx.snap => explorer_swimlane.test.js.snap} (100%) rename x-pack/plugins/ml/public/application/explorer/{explorer_swimlane.tsx => explorer_swimlane.js} (79%) rename x-pack/plugins/ml/public/application/explorer/{explorer_swimlane.test.tsx => explorer_swimlane.test.js} (70%) delete mode 100644 x-pack/plugins/ml/public/application/services/anomaly_detector_service.ts delete mode 100644 x-pack/plugins/ml/public/application/services/explorer_service.ts delete mode 100644 x-pack/plugins/ml/public/application/util/chart_utils.d.ts delete mode 100644 x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx delete mode 100644 x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx delete mode 100644 x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts delete mode 100644 x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx delete mode 100644 x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx delete mode 100644 x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.test.tsx delete mode 100644 x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx delete mode 100644 x-pack/plugins/ml/public/embeddables/anomaly_swimlane/index.ts delete mode 100644 x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.test.ts delete mode 100644 x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.ts delete mode 100644 x-pack/plugins/ml/public/embeddables/index.ts delete mode 100644 x-pack/plugins/ml/public/ui_actions/edit_swimlane_panel_action.tsx delete mode 100644 x-pack/plugins/ml/public/ui_actions/index.ts diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json index e9d4aff3484b1..038f61b3a33b7 100644 --- a/x-pack/plugins/ml/kibana.json +++ b/x-pack/plugins/ml/kibana.json @@ -13,9 +13,7 @@ "home", "licensing", "usageCollection", - "share", - "embeddable", - "uiActions" + "share" ], "optionalPlugins": [ "security", diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx index decd1275fe884..9cc42a4df2f66 100644 --- a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx +++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx @@ -4,15 +4,56 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import classNames from 'classnames'; -import TooltipTrigger from 'react-popper-tooltip'; +import React, { useRef, FC } from 'react'; import { TooltipValueFormatter } from '@elastic/charts'; +import useObservable from 'react-use/lib/useObservable'; -import './_index.scss'; +import { chartTooltip$, ChartTooltipState, ChartTooltipValue } from './chart_tooltip_service'; -import { ChildrenArg, TooltipTriggerProps } from 'react-popper-tooltip/dist/types'; -import { ChartTooltipService, ChartTooltipValue, TooltipData } from './chart_tooltip_service'; +type RefValue = HTMLElement | null; + +function useRefWithCallback(chartTooltipState?: ChartTooltipState) { + const ref = useRef(null); + + return (node: RefValue) => { + ref.current = node; + + if ( + node !== null && + node.parentElement !== null && + chartTooltipState !== undefined && + chartTooltipState.isTooltipVisible + ) { + const parentBounding = node.parentElement.getBoundingClientRect(); + + const { targetPosition, offset } = chartTooltipState; + + const contentWidth = document.body.clientWidth - parentBounding.left; + const tooltipWidth = node.clientWidth; + + let left = targetPosition.left + offset.x - parentBounding.left; + if (left + tooltipWidth > contentWidth) { + // the tooltip is hanging off the side of the page, + // so move it to the other side of the target + left = left - (tooltipWidth + offset.x); + } + + const top = targetPosition.top + offset.y - parentBounding.top; + + if ( + chartTooltipState.tooltipPosition.left !== left || + chartTooltipState.tooltipPosition.top !== top + ) { + // render the tooltip with adjusted position. + chartTooltip$.next({ + ...chartTooltipState, + tooltipPosition: { left, top }, + }); + } + } + }; +} const renderHeader = (headerData?: ChartTooltipValue, formatter?: TooltipValueFormatter) => { if (!headerData) { @@ -22,101 +63,48 @@ const renderHeader = (headerData?: ChartTooltipValue, formatter?: TooltipValueFo return formatter ? formatter(headerData) : headerData.label; }; -const Tooltip: FC<{ service: ChartTooltipService }> = React.memo(({ service }) => { - const [tooltipData, setData] = useState([]); - const refCallback = useRef(); +export const ChartTooltip: FC = () => { + const chartTooltipState = useObservable(chartTooltip$); + const chartTooltipElement = useRefWithCallback(chartTooltipState); - useEffect(() => { - const subscription = service.tooltipState$.subscribe(tooltipState => { - if (refCallback.current) { - // update trigger - refCallback.current(tooltipState.target); - } - setData(tooltipState.tooltipData); - }); - return () => { - subscription.unsubscribe(); - }; - }, []); - - const triggerCallback = useCallback( - (({ triggerRef }) => { - // obtain the reference to the trigger setter callback - // to update the target based on changes from the service. - refCallback.current = triggerRef; - // actual trigger is resolved by the service, hence don't render - return null; - }) as TooltipTriggerProps['children'], - [] - ); - - const tooltipCallback = useCallback( - (({ tooltipRef, getTooltipProps }) => { - return ( -
- {tooltipData.length > 0 && tooltipData[0].skipHeader === undefined && ( -
{renderHeader(tooltipData[0])}
- )} - {tooltipData.length > 1 && ( -
- {tooltipData - .slice(1) - .map(({ label, value, color, isHighlighted, seriesIdentifier, valueAccessor }) => { - const classes = classNames('mlChartTooltip__item', { - /* eslint @typescript-eslint/camelcase:0 */ - echTooltip__rowHighlighted: isHighlighted, - }); - return ( -
- {label} - {value} -
- ); - })} -
- )} -
- ); - }) as TooltipTriggerProps['tooltip'], - [tooltipData] - ); - - const isTooltipShown = tooltipData.length > 0; - - return ( - - {triggerCallback} - - ); -}); - -interface MlTooltipComponentProps { - children: (tooltipService: ChartTooltipService) => React.ReactElement; -} + if (chartTooltipState === undefined || !chartTooltipState.isTooltipVisible) { + return
; + } -export const MlTooltipComponent: FC = ({ children }) => { - const service = useMemo(() => new ChartTooltipService(), []); + const { tooltipData, tooltipHeaderFormatter, tooltipPosition } = chartTooltipState; + const transform = `translate(${tooltipPosition.left}px, ${tooltipPosition.top}px)`; return ( - <> - - {children(service)} - +
+ {tooltipData.length > 0 && tooltipData[0].skipHeader === undefined && ( +
+ {renderHeader(tooltipData[0], tooltipHeaderFormatter)} +
+ )} + {tooltipData.length > 1 && ( +
+ {tooltipData + .slice(1) + .map(({ label, value, color, isHighlighted, seriesIdentifier, valueAccessor }) => { + const classes = classNames('mlChartTooltip__item', { + /* eslint @typescript-eslint/camelcase:0 */ + echTooltip__rowHighlighted: isHighlighted, + }); + return ( +
+ {label} + {value} +
+ ); + })} +
+ )} +
); }; diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.d.ts b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.d.ts new file mode 100644 index 0000000000000..e6b0b6b4270bd --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.d.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BehaviorSubject } from 'rxjs'; + +import { TooltipValue, TooltipValueFormatter } from '@elastic/charts'; + +export declare const getChartTooltipDefaultState: () => ChartTooltipState; + +export interface ChartTooltipValue extends TooltipValue { + skipHeader?: boolean; +} + +interface ChartTooltipState { + isTooltipVisible: boolean; + offset: ToolTipOffset; + targetPosition: ClientRect; + tooltipData: ChartTooltipValue[]; + tooltipHeaderFormatter?: TooltipValueFormatter; + tooltipPosition: { left: number; top: number }; +} + +export declare const chartTooltip$: BehaviorSubject; + +interface ToolTipOffset { + x: number; + y: number; +} + +interface MlChartTooltipService { + show: ( + tooltipData: ChartTooltipValue[], + target?: HTMLElement | null, + offset?: ToolTipOffset + ) => void; + hide: () => void; +} + +export declare const mlChartTooltipService: MlChartTooltipService; diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.js b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.js new file mode 100644 index 0000000000000..59cf98e5ffd71 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.js @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BehaviorSubject } from 'rxjs'; + +export const getChartTooltipDefaultState = () => ({ + isTooltipVisible: false, + tooltipData: [], + offset: { x: 0, y: 0 }, + targetPosition: { left: 0, top: 0 }, + tooltipPosition: { left: 0, top: 0 }, +}); + +export const chartTooltip$ = new BehaviorSubject(getChartTooltipDefaultState()); + +export const mlChartTooltipService = { + show: (tooltipData, target, offset = { x: 0, y: 0 }) => { + if (typeof target !== 'undefined' && target !== null) { + chartTooltip$.next({ + ...chartTooltip$.getValue(), + isTooltipVisible: true, + offset, + targetPosition: target.getBoundingClientRect(), + tooltipData, + }); + } + }, + hide: () => { + chartTooltip$.next({ + ...getChartTooltipDefaultState(), + isTooltipVisible: false, + }); + }, +}; diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.test.ts b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.test.ts index 231854cd264c2..aa1dbf92b0677 100644 --- a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.test.ts +++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.test.ts @@ -4,61 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - ChartTooltipService, - getChartTooltipDefaultState, - TooltipData, -} from './chart_tooltip_service'; +import { getChartTooltipDefaultState, mlChartTooltipService } from './chart_tooltip_service'; -describe('ChartTooltipService', () => { - let service: ChartTooltipService; - - beforeEach(() => { - service = new ChartTooltipService(); - }); - - test('should update the tooltip state on show and hide', () => { - const spy = jest.fn(); - - service.tooltipState$.subscribe(spy); - - expect(spy).toHaveBeenCalledWith(getChartTooltipDefaultState()); - - const update = [ - { - label: 'new tooltip', - }, - ] as TooltipData; - const mockEl = document.createElement('div'); - - service.show(update, mockEl); - - expect(spy).toHaveBeenCalledWith({ - isTooltipVisible: true, - tooltipData: update, - offset: { x: 0, y: 0 }, - target: mockEl, - }); - - service.hide(); - - expect(spy).toHaveBeenCalledWith({ - isTooltipVisible: false, - tooltipData: ([] as unknown) as TooltipData, - offset: { x: 0, y: 0 }, - target: null, - }); +describe('ML - mlChartTooltipService', () => { + it('service API duck typing', () => { + expect(typeof mlChartTooltipService).toBe('object'); + expect(typeof mlChartTooltipService.show).toBe('function'); + expect(typeof mlChartTooltipService.hide).toBe('function'); }); - test('update the tooltip state only on a new value', () => { - const spy = jest.fn(); - - service.tooltipState$.subscribe(spy); - - expect(spy).toHaveBeenCalledWith(getChartTooltipDefaultState()); - - service.hide(); - - expect(spy).toHaveBeenCalledTimes(1); + it('should fail silently when target is not defined', () => { + expect(() => { + mlChartTooltipService.show(getChartTooltipDefaultState().tooltipData, null); + }).not.toThrow('Call to show() should fail silently.'); }); }); diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.ts b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.ts deleted file mode 100644 index b524e18102a95..0000000000000 --- a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { BehaviorSubject, Observable } from 'rxjs'; -import { isEqual } from 'lodash'; -import { TooltipValue, TooltipValueFormatter } from '@elastic/charts'; -import { distinctUntilChanged } from 'rxjs/operators'; - -export interface ChartTooltipValue extends TooltipValue { - skipHeader?: boolean; -} - -export interface TooltipHeader { - skipHeader: boolean; -} - -export type TooltipData = ChartTooltipValue[]; - -export interface ChartTooltipState { - isTooltipVisible: boolean; - offset: TooltipOffset; - tooltipData: TooltipData; - tooltipHeaderFormatter?: TooltipValueFormatter; - target: HTMLElement | null; -} - -interface TooltipOffset { - x: number; - y: number; -} - -export const getChartTooltipDefaultState = (): ChartTooltipState => ({ - isTooltipVisible: false, - tooltipData: ([] as unknown) as TooltipData, - offset: { x: 0, y: 0 }, - target: null, -}); - -export class ChartTooltipService { - private chartTooltip$ = new BehaviorSubject(getChartTooltipDefaultState()); - - public tooltipState$: Observable = this.chartTooltip$ - .asObservable() - .pipe(distinctUntilChanged(isEqual)); - - public show( - tooltipData: TooltipData, - target: HTMLElement, - offset: TooltipOffset = { x: 0, y: 0 } - ) { - if (!target) { - throw new Error('target is required for the tooltip positioning'); - } - - this.chartTooltip$.next({ - ...this.chartTooltip$.getValue(), - isTooltipVisible: true, - offset, - tooltipData, - target, - }); - } - - public hide() { - this.chartTooltip$.next({ - ...getChartTooltipDefaultState(), - isTooltipVisible: false, - }); - } -} diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/index.ts b/x-pack/plugins/ml/public/application/components/chart_tooltip/index.ts index ec19fe18bd324..75c65ebaa0f50 100644 --- a/x-pack/plugins/ml/public/application/components/chart_tooltip/index.ts +++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/index.ts @@ -4,5 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ChartTooltipService } from './chart_tooltip_service'; -export { MlTooltipComponent } from './chart_tooltip'; +export { mlChartTooltipService } from './chart_tooltip_service'; +export { ChartTooltip } from './chart_tooltip'; diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx b/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx index f709c161bef17..381e5e75356c1 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx @@ -4,23 +4,45 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import PropTypes from 'prop-types'; + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexItem, + EuiFlexGroup, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiSwitch, + EuiTitle, +} from '@elastic/eui'; -import { EuiButtonEmpty, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useMlKibana } from '../../contexts/kibana'; import { Dictionary } from '../../../../common/types/common'; +import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; +import { ml } from '../../services/ml_api_service'; import { useUrlState } from '../../util/url_state'; // @ts-ignore +import { JobSelectorTable } from './job_selector_table/index'; +// @ts-ignore import { IdBadges } from './id_badges/index'; -import { BADGE_LIMIT, JobSelectorFlyout, JobSelectorFlyoutProps } from './job_selector_flyout'; -import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; +// @ts-ignore +import { NewSelectionIdBadges } from './new_selection_id_badges/index'; +import { + getGroupsFromJobs, + getTimeRangeFromSelection, + normalizeTimes, +} from './job_select_service_utils'; interface GroupObj { groupId: string; jobIds: string[]; } - function mergeSelection( jobIds: string[], groupObjs: GroupObj[], @@ -49,7 +71,7 @@ function mergeSelection( } type GroupsMap = Dictionary; -export function getInitialGroupsMap(selectedGroups: GroupObj[]): GroupsMap { +function getInitialGroupsMap(selectedGroups: GroupObj[]): GroupsMap { const map: GroupsMap = {}; if (selectedGroups.length) { @@ -61,38 +83,81 @@ export function getInitialGroupsMap(selectedGroups: GroupObj[]): GroupsMap { return map; } +const BADGE_LIMIT = 10; +const DEFAULT_GANTT_BAR_WIDTH = 299; // pixels + interface JobSelectorProps { dateFormatTz: string; singleSelection: boolean; timeseriesOnly: boolean; } -export interface JobSelectionMaps { - jobsMap: Dictionary; - groupsMap: Dictionary; -} - export function JobSelector({ dateFormatTz, singleSelection, timeseriesOnly }: JobSelectorProps) { const [globalState, setGlobalState] = useUrlState('_g'); const selectedJobIds = globalState?.ml?.jobIds ?? []; const selectedGroups = globalState?.ml?.groups ?? []; - const [maps, setMaps] = useState({ - groupsMap: getInitialGroupsMap(selectedGroups), - jobsMap: {}, - }); + const [jobs, setJobs] = useState([]); + const [groups, setGroups] = useState([]); + const [maps, setMaps] = useState({ groupsMap: getInitialGroupsMap(selectedGroups), jobsMap: {} }); const [selectedIds, setSelectedIds] = useState( mergeSelection(selectedJobIds, selectedGroups, singleSelection) ); + const [newSelection, setNewSelection] = useState( + mergeSelection(selectedJobIds, selectedGroups, singleSelection) + ); + const [showAllBadges, setShowAllBadges] = useState(false); const [showAllBarBadges, setShowAllBarBadges] = useState(false); + const [applyTimeRange, setApplyTimeRange] = useState(true); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const [ganttBarWidth, setGanttBarWidth] = useState(DEFAULT_GANTT_BAR_WIDTH); + const flyoutEl = useRef<{ flyout: HTMLElement }>(null); + const { + services: { notifications }, + } = useMlKibana(); // Ensure JobSelectionBar gets updated when selection via globalState changes. useEffect(() => { setSelectedIds(mergeSelection(selectedJobIds, selectedGroups, singleSelection)); }, [JSON.stringify([selectedJobIds, selectedGroups])]); + // Ensure current selected ids always show up in flyout + useEffect(() => { + setNewSelection(selectedIds); + }, [isFlyoutVisible]); // eslint-disable-line + + // Wrap handleResize in useCallback as it is a dependency for useEffect on line 131 below. + // Not wrapping it would cause this dependency to change on every render + const handleResize = useCallback(() => { + if (jobs.length > 0 && flyoutEl && flyoutEl.current && flyoutEl.current.flyout) { + // get all cols in flyout table + const tableHeaderCols: NodeListOf = flyoutEl.current.flyout.querySelectorAll( + 'table thead th' + ); + // get the width of the last col + const derivedWidth = tableHeaderCols[tableHeaderCols.length - 1].offsetWidth - 16; + const normalizedJobs = normalizeTimes(jobs, dateFormatTz, derivedWidth); + setJobs(normalizedJobs); + const { groups: updatedGroups } = getGroupsFromJobs(normalizedJobs); + setGroups(updatedGroups); + setGanttBarWidth(derivedWidth); + } + }, [dateFormatTz, jobs]); + + useEffect(() => { + // Ensure ganttBar width gets calculated on resize + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, [handleResize]); + + useEffect(() => { + handleResize(); + }, [handleResize, jobs]); + function closeFlyout() { setIsFlyoutVisible(false); } @@ -103,26 +168,78 @@ export function JobSelector({ dateFormatTz, singleSelection, timeseriesOnly }: J function handleJobSelectionClick() { showFlyout(); + + ml.jobs + .jobsWithTimerange(dateFormatTz) + .then(resp => { + const normalizedJobs = normalizeTimes(resp.jobs, dateFormatTz, DEFAULT_GANTT_BAR_WIDTH); + const { groups: groupsWithTimerange, groupsMap } = getGroupsFromJobs(normalizedJobs); + setJobs(normalizedJobs); + setGroups(groupsWithTimerange); + setMaps({ groupsMap, jobsMap: resp.jobsMap }); + }) + .catch((err: any) => { + console.error('Error fetching jobs with time range', err); // eslint-disable-line + const { toasts } = notifications; + toasts.addDanger({ + title: i18n.translate('xpack.ml.jobSelector.jobFetchErrorMessage', { + defaultMessage: 'An error occurred fetching jobs. Refresh and try again.', + }), + }); + }); + } + + function handleNewSelection({ selectionFromTable }: { selectionFromTable: any }) { + setNewSelection(selectionFromTable); } - const applySelection: JobSelectorFlyoutProps['onSelectionConfirmed'] = ({ - newSelection, - jobIds, - groups: newGroups, - time, - }) => { + function applySelection() { + // allNewSelection will be a list of all job ids (including those from groups) selected from the table + const allNewSelection: string[] = []; + const groupSelection: Array<{ groupId: string; jobIds: string[] }> = []; + + newSelection.forEach(id => { + if (maps.groupsMap[id] !== undefined) { + // Push all jobs from selected groups into the newSelection list + allNewSelection.push(...maps.groupsMap[id]); + // if it's a group - push group obj to set in global state + groupSelection.push({ groupId: id, jobIds: maps.groupsMap[id] }); + } else { + allNewSelection.push(id); + } + }); + // create a Set to remove duplicate values + const allNewSelectionUnique = Array.from(new Set(allNewSelection)); + setSelectedIds(newSelection); + setNewSelection([]); + + closeFlyout(); + + const time = applyTimeRange + ? getTimeRangeFromSelection(jobs, allNewSelectionUnique) + : undefined; setGlobalState({ ml: { - jobIds, - groups: newGroups, + jobIds: allNewSelectionUnique, + groups: groupSelection, }, ...(time !== undefined ? { time } : {}), }); + } - closeFlyout(); - }; + function toggleTimerangeSwitch() { + setApplyTimeRange(!applyTimeRange); + } + + function removeId(id: string) { + setNewSelection(newSelection.filter(item => item !== id)); + } + + function clearSelection() { + setNewSelection([]); + } function renderJobSelectionBar() { return ( @@ -163,16 +280,103 @@ export function JobSelector({ dateFormatTz, singleSelection, timeseriesOnly }: J function renderFlyout() { if (isFlyoutVisible) { return ( - + + + +

+ {i18n.translate('xpack.ml.jobSelector.flyoutTitle', { + defaultMessage: 'Job selection', + })} +

+
+
+ + + + + setShowAllBadges(!showAllBadges)} + showAllBadges={showAllBadges} + /> + + + + + + {!singleSelection && newSelection.length > 0 && ( + + {i18n.translate('xpack.ml.jobSelector.clearAllFlyoutButton', { + defaultMessage: 'Clear all', + })} + + )} + + + + + + + + + + + + + + {i18n.translate('xpack.ml.jobSelector.applyFlyoutButton', { + defaultMessage: 'Apply', + })} + + + + + {i18n.translate('xpack.ml.jobSelector.closeFlyoutButton', { + defaultMessage: 'Close', + })} + + + + +
); } } @@ -184,3 +388,9 @@ export function JobSelector({ dateFormatTz, singleSelection, timeseriesOnly }: J
); } + +JobSelector.propTypes = { + selectedJobIds: PropTypes.array, + singleSelection: PropTypes.bool, + timeseriesOnly: PropTypes.bool, +}; diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/index.ts b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/index.js similarity index 100% rename from x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/index.ts rename to x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/index.js diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/job_selector_badge.tsx b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/job_selector_badge.js similarity index 68% rename from x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/job_selector_badge.tsx rename to x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/job_selector_badge.js index b2cae278c0e77..4d2ab01e2a054 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/job_selector_badge.tsx +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_badge/job_selector_badge.js @@ -4,32 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC } from 'react'; -import { EuiBadge, EuiBadgeProps } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { EuiBadge } from '@elastic/eui'; import { tabColor } from '../../../../../common/util/group_color_utils'; +import { i18n } from '@kbn/i18n'; -interface JobSelectorBadgeProps { - icon?: boolean; - id: string; - isGroup?: boolean; - numJobs?: number; - removeId?: Function; -} - -export const JobSelectorBadge: FC = ({ - icon, - id, - isGroup = false, - numJobs, - removeId, -}) => { +export function JobSelectorBadge({ icon, id, isGroup = false, numJobs, removeId }) { const color = isGroup ? tabColor(id) : 'hollow'; - let props = { color } as EuiBadgeProps; + let props = { color }; let jobCount; - if (icon === true && removeId) { - // @ts-ignore + if (icon === true) { props = { ...props, iconType: 'cross', @@ -51,4 +37,11 @@ export const JobSelectorBadge: FC = ({ {`${id}${jobCount ? jobCount : ''}`} ); +} +JobSelectorBadge.propTypes = { + icon: PropTypes.bool, + id: PropTypes.string.isRequired, + isGroup: PropTypes.bool, + numJobs: PropTypes.number, + removeId: PropTypes.func, }; diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx deleted file mode 100644 index 66aa05d2aaa97..0000000000000 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { - EuiButton, - EuiButtonEmpty, - EuiFlexItem, - EuiFlexGroup, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiSwitch, - EuiTitle, -} from '@elastic/eui'; -import { NewSelectionIdBadges } from './new_selection_id_badges'; -// @ts-ignore -import { JobSelectorTable } from './job_selector_table'; -import { - getGroupsFromJobs, - getTimeRangeFromSelection, - normalizeTimes, -} from './job_select_service_utils'; -import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; -import { ml } from '../../services/ml_api_service'; -import { useMlKibana } from '../../contexts/kibana'; -import { JobSelectionMaps } from './job_selector'; - -export const BADGE_LIMIT = 10; -export const DEFAULT_GANTT_BAR_WIDTH = 299; // pixels - -export interface JobSelectorFlyoutProps { - dateFormatTz: string; - selectedIds?: string[]; - newSelection?: string[]; - onFlyoutClose: () => void; - onJobsFetched?: (maps: JobSelectionMaps) => void; - onSelectionChange?: (newSelection: string[]) => void; - onSelectionConfirmed: (payload: { - newSelection: string[]; - jobIds: string[]; - groups: Array<{ groupId: string; jobIds: string[] }>; - time: any; - }) => void; - singleSelection: boolean; - timeseriesOnly: boolean; - maps: JobSelectionMaps; - withTimeRangeSelector?: boolean; -} - -export const JobSelectorFlyout: FC = ({ - dateFormatTz, - selectedIds = [], - singleSelection, - timeseriesOnly, - onJobsFetched, - onSelectionChange, - onSelectionConfirmed, - onFlyoutClose, - maps, - withTimeRangeSelector = true, -}) => { - const { - services: { notifications }, - } = useMlKibana(); - - const [newSelection, setNewSelection] = useState(selectedIds); - - const [showAllBadges, setShowAllBadges] = useState(false); - const [applyTimeRange, setApplyTimeRange] = useState(true); - const [jobs, setJobs] = useState([]); - const [groups, setGroups] = useState([]); - const [ganttBarWidth, setGanttBarWidth] = useState(DEFAULT_GANTT_BAR_WIDTH); - const [jobGroupsMaps, setJobGroupsMaps] = useState(maps); - - const flyoutEl = useRef<{ flyout: HTMLElement }>(null); - - function applySelection() { - // allNewSelection will be a list of all job ids (including those from groups) selected from the table - const allNewSelection: string[] = []; - const groupSelection: Array<{ groupId: string; jobIds: string[] }> = []; - - newSelection.forEach(id => { - if (jobGroupsMaps.groupsMap[id] !== undefined) { - // Push all jobs from selected groups into the newSelection list - allNewSelection.push(...jobGroupsMaps.groupsMap[id]); - // if it's a group - push group obj to set in global state - groupSelection.push({ groupId: id, jobIds: jobGroupsMaps.groupsMap[id] }); - } else { - allNewSelection.push(id); - } - }); - // create a Set to remove duplicate values - const allNewSelectionUnique = Array.from(new Set(allNewSelection)); - - const time = applyTimeRange - ? getTimeRangeFromSelection(jobs, allNewSelectionUnique) - : undefined; - - onSelectionConfirmed({ - newSelection: allNewSelectionUnique, - jobIds: allNewSelectionUnique, - groups: groupSelection, - time, - }); - } - - function removeId(id: string) { - setNewSelection(newSelection.filter(item => item !== id)); - } - - function toggleTimerangeSwitch() { - setApplyTimeRange(!applyTimeRange); - } - - function clearSelection() { - setNewSelection([]); - } - - function handleNewSelection({ selectionFromTable }: { selectionFromTable: any }) { - setNewSelection(selectionFromTable); - } - - // Wrap handleResize in useCallback as it is a dependency for useEffect on line 131 below. - // Not wrapping it would cause this dependency to change on every render - const handleResize = useCallback(() => { - if (jobs.length > 0 && flyoutEl && flyoutEl.current && flyoutEl.current.flyout) { - // get all cols in flyout table - const tableHeaderCols: NodeListOf = flyoutEl.current.flyout.querySelectorAll( - 'table thead th' - ); - // get the width of the last col - const derivedWidth = tableHeaderCols[tableHeaderCols.length - 1].offsetWidth - 16; - const normalizedJobs = normalizeTimes(jobs, dateFormatTz, derivedWidth); - setJobs(normalizedJobs); - const { groups: updatedGroups } = getGroupsFromJobs(normalizedJobs); - setGroups(updatedGroups); - setGanttBarWidth(derivedWidth); - } - }, [dateFormatTz, jobs]); - - // Fetch jobs list on flyout open - useEffect(() => { - fetchJobs(); - }, []); - - async function fetchJobs() { - try { - const resp = await ml.jobs.jobsWithTimerange(dateFormatTz); - const normalizedJobs = normalizeTimes(resp.jobs, dateFormatTz, DEFAULT_GANTT_BAR_WIDTH); - const { groups: groupsWithTimerange, groupsMap } = getGroupsFromJobs(normalizedJobs); - setJobs(normalizedJobs); - setGroups(groupsWithTimerange); - setJobGroupsMaps({ groupsMap, jobsMap: resp.jobsMap }); - - if (onJobsFetched) { - onJobsFetched({ groupsMap, jobsMap: resp.jobsMap }); - } - } catch (e) { - console.error('Error fetching jobs with time range', e); // eslint-disable-line - const { toasts } = notifications; - toasts.addDanger({ - title: i18n.translate('xpack.ml.jobSelector.jobFetchErrorMessage', { - defaultMessage: 'An error occurred fetching jobs. Refresh and try again.', - }), - }); - } - } - - useEffect(() => { - // Ensure ganttBar width gets calculated on resize - window.addEventListener('resize', handleResize); - - return () => { - window.removeEventListener('resize', handleResize); - }; - }, [handleResize]); - - useEffect(() => { - handleResize(); - }, [handleResize, jobs]); - - return ( - - - -

- {i18n.translate('xpack.ml.jobSelector.flyoutTitle', { - defaultMessage: 'Job selection', - })} -

-
-
- - - - - setShowAllBadges(!showAllBadges)} - showAllBadges={showAllBadges} - /> - - - - - - {!singleSelection && newSelection.length > 0 && ( - - {i18n.translate('xpack.ml.jobSelector.clearAllFlyoutButton', { - defaultMessage: 'Clear all', - })} - - )} - - {withTimeRangeSelector && ( - - - - )} - - - - - - - - - - {i18n.translate('xpack.ml.jobSelector.applyFlyoutButton', { - defaultMessage: 'Apply', - })} - - - - - {i18n.translate('xpack.ml.jobSelector.closeFlyoutButton', { - defaultMessage: 'Close', - })} - - - - -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js index c55e03776c09d..64793d15f1e4a 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js @@ -224,7 +224,7 @@ export function JobSelectorTable({ {jobs.length === 0 && } {jobs.length !== 0 && singleSelection === true && renderJobsTable()} - {jobs.length !== 0 && !singleSelection && renderTabs()} + {jobs.length !== 0 && singleSelection === undefined && renderTabs()} ); } diff --git a/x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/index.ts b/x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/index.js similarity index 100% rename from x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/index.ts rename to x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/index.js diff --git a/x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.tsx b/x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.js similarity index 80% rename from x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.tsx rename to x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.js index 4c018e72f3e10..67dce47323889 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.tsx +++ b/x-pack/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.js @@ -4,29 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, MouseEventHandler } from 'react'; +import React from 'react'; +import { PropTypes } from 'prop-types'; import { EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { JobSelectorBadge } from '../job_selector_badge'; -import { JobSelectionMaps } from '../job_selector'; - -interface NewSelectionIdBadgesProps { - limit: number; - maps: JobSelectionMaps; - newSelection: string[]; - onDeleteClick?: Function; - onLinkClick?: MouseEventHandler; - showAllBadges?: boolean; -} +import { i18n } from '@kbn/i18n'; -export const NewSelectionIdBadges: FC = ({ +export function NewSelectionIdBadges({ limit, maps, newSelection, onDeleteClick, onLinkClick, showAllBadges, -}) => { +}) { const badges = []; for (let i = 0; i < newSelection.length; i++) { @@ -69,5 +60,16 @@ export const NewSelectionIdBadges: FC = ({ ); } - return <>{badges}; + return badges; +} +NewSelectionIdBadges.propTypes = { + limit: PropTypes.number, + maps: PropTypes.shape({ + jobsMap: PropTypes.object, + groupsMap: PropTypes.object, + }), + newSelection: PropTypes.array, + onDeleteClick: PropTypes.func, + onLinkClick: PropTypes.func, + showAllBadges: PropTypes.bool, }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx index 06d89ab782167..86ffc4a2614b9 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx @@ -41,7 +41,7 @@ import { useMlContext } from '../../contexts/ml'; import { kbnTypeToMLJobType } from '../../util/field_types_utils'; import { useTimefilter } from '../../contexts/kibana'; import { timeBasedIndexCheck, getQueryFromSavedSearch } from '../../util/index_utils'; -import { getTimeBucketsFromCache } from '../../util/time_buckets'; +import { TimeBuckets } from '../../util/time_buckets'; import { useUrlState } from '../../util/url_state'; import { FieldRequestConfig, FieldVisConfig } from './common'; import { ActionsPanel } from './components/actions_panel'; @@ -318,7 +318,7 @@ export const Page: FC = () => { // Obtain the interval to use for date histogram aggregations // (such as the document count chart). Aim for 75 bars. - const buckets = getTimeBucketsFromCache(); + const buckets = new TimeBuckets(); const tf = timefilter as any; let earliest: number | undefined; diff --git a/x-pack/plugins/ml/public/application/explorer/__snapshots__/explorer_swimlane.test.tsx.snap b/x-pack/plugins/ml/public/application/explorer/__snapshots__/explorer_swimlane.test.js.snap similarity index 100% rename from x-pack/plugins/ml/public/application/explorer/__snapshots__/explorer_swimlane.test.tsx.snap rename to x-pack/plugins/ml/public/application/explorer/__snapshots__/explorer_swimlane.test.js.snap diff --git a/x-pack/plugins/ml/public/application/explorer/_explorer.scss b/x-pack/plugins/ml/public/application/explorer/_explorer.scss index cfcba081983c2..9fb2f0c3bed94 100644 --- a/x-pack/plugins/ml/public/application/explorer/_explorer.scss +++ b/x-pack/plugins/ml/public/application/explorer/_explorer.scss @@ -106,6 +106,164 @@ padding: 0; margin-bottom: $euiSizeS; + div.ml-swimlanes { + margin: 0px 0px 0px 10px; + + div.cells-marker-container { + margin-left: 176px; + height: 22px; + white-space: nowrap; + + // background-color: #CCC; + .sl-cell { + height: 10px; + display: inline-block; + vertical-align: top; + margin-top: 16px; + text-align: center; + visibility: hidden; + cursor: default; + + i { + color: $euiColorDarkShade; + } + } + + .sl-cell-hover { + visibility: visible; + + i { + display: block; + margin-top: -6px; + } + } + + .sl-cell-active-hover { + visibility: visible; + + .floating-time-label { + display: inline-block; + } + } + } + + div.lane { + height: 30px; + border-bottom: 0px; + border-radius: 2px; + margin-top: -1px; + white-space: nowrap; + + div.lane-label { + display: inline-block; + font-size: 13px; + height: 30px; + text-align: right; + vertical-align: middle; + border-radius: 2px; + padding-right: 5px; + margin-right: 5px; + border: 1px solid transparent; + overflow: hidden; + text-overflow: ellipsis; + } + + div.lane-label.lane-label-masked { + opacity: 0.3; + } + + div.cells-container { + border: $euiBorderThin; + border-right: 0px; + display: inline-block; + height: 30px; + vertical-align: middle; + background-color: $euiColorEmptyShade; + + .sl-cell { + color: $euiColorEmptyShade; + cursor: default; + display: inline-block; + height: 29px; + border-right: $euiBorderThin; + vertical-align: top; + position: relative; + + .sl-cell-inner, + .sl-cell-inner-dragselect { + height: 26px; + margin: 1px; + border-radius: 2px; + text-align: center; + } + + .sl-cell-inner.sl-cell-inner-masked { + opacity: 0.2; + } + + .sl-cell-inner.sl-cell-inner-selected, + .sl-cell-inner-dragselect.sl-cell-inner-selected { + border: 2px solid $euiColorDarkShade; + } + + .sl-cell-inner.sl-cell-inner-selected.sl-cell-inner-masked, + .sl-cell-inner-dragselect.sl-cell-inner-selected.sl-cell-inner-masked { + border: 2px solid $euiColorFullShade; + opacity: 0.4; + } + } + + .sl-cell:hover { + .sl-cell-inner { + opacity: 0.8; + cursor: pointer; + } + } + + .sl-cell.ds-selected { + + .sl-cell-inner, + .sl-cell-inner-dragselect { + border: 2px solid $euiColorDarkShade; + border-radius: 2px; + opacity: 1; + } + } + + } + } + + div.lane:last-child { + div.cells-container { + .sl-cell { + border-bottom: $euiBorderThin; + } + } + } + + .time-tick-labels { + height: 25px; + margin-top: $euiSizeXS / 2; + margin-left: 175px; + + /* hide d3's domain line */ + path.domain { + display: none; + } + + /* hide d3's tick line */ + g.tick line { + display: none; + } + + /* override d3's default tick styles */ + g.tick text { + font-size: 11px; + fill: $euiColorMediumShade; + } + } + } + line.gridLine { stroke: $euiBorderColor; fill: none; @@ -170,161 +328,3 @@ } } } - -.ml-swimlanes { - margin: 0px 0px 0px 10px; - - div.cells-marker-container { - margin-left: 176px; - height: 22px; - white-space: nowrap; - - // background-color: #CCC; - .sl-cell { - height: 10px; - display: inline-block; - vertical-align: top; - margin-top: 16px; - text-align: center; - visibility: hidden; - cursor: default; - - i { - color: $euiColorDarkShade; - } - } - - .sl-cell-hover { - visibility: visible; - - i { - display: block; - margin-top: -6px; - } - } - - .sl-cell-active-hover { - visibility: visible; - - .floating-time-label { - display: inline-block; - } - } - } - - div.lane { - height: 30px; - border-bottom: 0px; - border-radius: 2px; - margin-top: -1px; - white-space: nowrap; - - div.lane-label { - display: inline-block; - font-size: 13px; - height: 30px; - text-align: right; - vertical-align: middle; - border-radius: 2px; - padding-right: 5px; - margin-right: 5px; - border: 1px solid transparent; - overflow: hidden; - text-overflow: ellipsis; - } - - div.lane-label.lane-label-masked { - opacity: 0.3; - } - - div.cells-container { - border: $euiBorderThin; - border-right: 0px; - display: inline-block; - height: 30px; - vertical-align: middle; - background-color: $euiColorEmptyShade; - - .sl-cell { - color: $euiColorEmptyShade; - cursor: default; - display: inline-block; - height: 29px; - border-right: $euiBorderThin; - vertical-align: top; - position: relative; - - .sl-cell-inner, - .sl-cell-inner-dragselect { - height: 26px; - margin: 1px; - border-radius: 2px; - text-align: center; - } - - .sl-cell-inner.sl-cell-inner-masked { - opacity: 0.2; - } - - .sl-cell-inner.sl-cell-inner-selected, - .sl-cell-inner-dragselect.sl-cell-inner-selected { - border: 2px solid $euiColorDarkShade; - } - - .sl-cell-inner.sl-cell-inner-selected.sl-cell-inner-masked, - .sl-cell-inner-dragselect.sl-cell-inner-selected.sl-cell-inner-masked { - border: 2px solid $euiColorFullShade; - opacity: 0.4; - } - } - - .sl-cell:hover { - .sl-cell-inner { - opacity: 0.8; - cursor: pointer; - } - } - - .sl-cell.ds-selected { - - .sl-cell-inner, - .sl-cell-inner-dragselect { - border: 2px solid $euiColorDarkShade; - border-radius: 2px; - opacity: 1; - } - } - - } - } - - div.lane:last-child { - div.cells-container { - .sl-cell { - border-bottom: $euiBorderThin; - } - } - } - - .time-tick-labels { - height: 25px; - margin-top: $euiSizeXS / 2; - margin-left: 175px; - - /* hide d3's domain line */ - path.domain { - display: none; - } - - /* hide d3's tick line */ - g.tick line { - display: none; - } - - /* override d3's default tick styles */ - g.tick text { - font-size: 11px; - fill: $euiColorMediumShade; - } - } -} diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js index 86d16776b68e2..d61d56d07b644 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer.js @@ -36,8 +36,9 @@ import { ExplorerNoJobsFound, ExplorerNoResultsFound, } from './components'; +import { ChartTooltip } from '../components/chart_tooltip'; import { ExplorerSwimlane } from './explorer_swimlane'; -import { getTimeBucketsFromCache } from '../util/time_buckets'; +import { TimeBuckets } from '../util/time_buckets'; import { InfluencersList } from '../components/influencers_list'; import { ALLOW_CELL_RANGE_SELECTION, @@ -80,7 +81,6 @@ import { AnomaliesTable } from '../components/anomalies_table/anomalies_table'; import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public'; import { getTimefilter, getToastNotifications } from '../util/dependency_cache'; -import { MlTooltipComponent } from '../components/chart_tooltip'; function mapSwimlaneOptionsToEuiOptions(options) { return options.map(option => ({ @@ -179,8 +179,6 @@ export class Explorer extends React.Component { // Required to redraw the time series chart when the container is resized. this.resizeChecker = new ResizeChecker(this.resizeRef.current); this.resizeChecker.on('resize', this.resizeHandler); - - this.timeBuckets = getTimeBucketsFromCache(); } componentWillUnmount() { @@ -360,6 +358,9 @@ export class Explorer extends React.Component { return (
+ {/* Make sure ChartTooltip is inside wrapping div with 0px left/right padding so positioning can be inferred correctly. */} + + {noInfluencersConfigured === false && influencers !== undefined && (
{showOverallSwimlane && ( - - {tooltipService => ( - - )} - + )}
@@ -498,22 +494,17 @@ export class Explorer extends React.Component { onMouseLeave={this.onSwimlaneLeaveHandler} data-test-subj="mlAnomalyExplorerSwimlaneViewBy" > - - {tooltipService => ( - - )} - +
)} diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js index 03426869b0ccf..5fc1160093a49 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js @@ -29,8 +29,9 @@ import { removeLabelOverlap, } from '../../util/chart_utils'; import { LoadingIndicator } from '../../components/loading_indicator/loading_indicator'; -import { getTimeBucketsFromCache } from '../../util/time_buckets'; +import { TimeBuckets } from '../../util/time_buckets'; import { mlFieldFormatService } from '../../services/field_format_service'; +import { mlChartTooltipService } from '../../components/chart_tooltip/chart_tooltip_service'; import { CHART_TYPE } from '../explorer_constants'; @@ -49,7 +50,6 @@ export class ExplorerChartDistribution extends React.Component { static propTypes = { seriesConfig: PropTypes.object, severity: PropTypes.number, - tooltipService: PropTypes.object.isRequired, }; componentDidMount() { @@ -61,7 +61,7 @@ export class ExplorerChartDistribution extends React.Component { } renderChart() { - const { tooManyBuckets, tooltipService } = this.props; + const { tooManyBuckets } = this.props; const element = this.rootNode; const config = this.props.seriesConfig; @@ -259,7 +259,7 @@ export class ExplorerChartDistribution extends React.Component { function drawRareChartAxes() { // Get the scaled date format to use for x axis tick labels. - const timeBuckets = getTimeBucketsFromCache(); + const timeBuckets = new TimeBuckets(); const bounds = { min: moment(config.plotEarliest), max: moment(config.plotLatest) }; timeBuckets.setBounds(bounds); timeBuckets.setInterval('auto'); @@ -397,7 +397,7 @@ export class ExplorerChartDistribution extends React.Component { .on('mouseover', function(d) { showLineChartTooltip(d, this); }) - .on('mouseout', () => tooltipService.hide()); + .on('mouseout', () => mlChartTooltipService.hide()); // Update all dots to new positions. dots @@ -550,7 +550,7 @@ export class ExplorerChartDistribution extends React.Component { }); } - tooltipService.show(tooltipData, circle, { + mlChartTooltipService.show(tooltipData, circle, { x: LINE_CHART_ANOMALY_RADIUS * 3, y: LINE_CHART_ANOMALY_RADIUS * 2, }); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js index 06fd82204c1e1..71d777db5b2ec 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js @@ -10,13 +10,11 @@ import seriesConfig from './__mocks__/mock_series_config_rare.json'; // Mock TimeBuckets and mlFieldFormatService, they don't play well // with the jest based test setup yet. jest.mock('../../util/time_buckets', () => ({ - getTimeBucketsFromCache: jest.fn(() => { - return { - setBounds: jest.fn(), - setInterval: jest.fn(), - getScaledDateFormat: jest.fn(), - }; - }), + TimeBuckets: function() { + this.setBounds = jest.fn(); + this.setInterval = jest.fn(); + this.getScaledDateFormat = jest.fn(); + }, })); jest.mock('../../services/field_format_service', () => ({ mlFieldFormatService: { @@ -45,16 +43,8 @@ describe('ExplorerChart', () => { afterEach(() => (SVGElement.prototype.getBBox = originalGetBBox)); test('Initialize', () => { - const mockTooltipService = { - show: jest.fn(), - hide: jest.fn(), - }; - const wrapper = mountWithIntl( - + ); // without setting any attributes and corresponding data @@ -69,16 +59,10 @@ describe('ExplorerChart', () => { loading: true, }; - const mockTooltipService = { - show: jest.fn(), - hide: jest.fn(), - }; - const wrapper = mountWithIntl( ); @@ -99,18 +83,12 @@ describe('ExplorerChart', () => { chartLimits: chartLimits(chartData), }; - const mockTooltipService = { - show: jest.fn(), - hide: jest.fn(), - }; - // We create the element including a wrapper which sets the width: return mountWithIntl(
); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js index 82041af39ca15..dd9479be931a7 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js @@ -38,9 +38,10 @@ import { showMultiBucketAnomalyTooltip, } from '../../util/chart_utils'; import { LoadingIndicator } from '../../components/loading_indicator/loading_indicator'; -import { getTimeBucketsFromCache } from '../../util/time_buckets'; +import { TimeBuckets } from '../../util/time_buckets'; import { mlEscape } from '../../util/string_utils'; import { mlFieldFormatService } from '../../services/field_format_service'; +import { mlChartTooltipService } from '../../components/chart_tooltip/chart_tooltip_service'; import { i18n } from '@kbn/i18n'; @@ -52,7 +53,6 @@ export class ExplorerChartSingleMetric extends React.Component { tooManyBuckets: PropTypes.bool, seriesConfig: PropTypes.object, severity: PropTypes.number.isRequired, - tooltipService: PropTypes.object.isRequired, }; componentDidMount() { @@ -64,7 +64,7 @@ export class ExplorerChartSingleMetric extends React.Component { } renderChart() { - const { tooManyBuckets, tooltipService } = this.props; + const { tooManyBuckets } = this.props; const element = this.rootNode; const config = this.props.seriesConfig; @@ -191,7 +191,7 @@ export class ExplorerChartSingleMetric extends React.Component { function drawLineChartAxes() { // Get the scaled date format to use for x axis tick labels. - const timeBuckets = getTimeBucketsFromCache(); + const timeBuckets = new TimeBuckets(); const bounds = { min: moment(config.plotEarliest), max: moment(config.plotLatest) }; timeBuckets.setBounds(bounds); timeBuckets.setInterval('auto'); @@ -309,7 +309,7 @@ export class ExplorerChartSingleMetric extends React.Component { .on('mouseover', function(d) { showLineChartTooltip(d, this); }) - .on('mouseout', () => tooltipService.hide()); + .on('mouseout', () => mlChartTooltipService.hide()); const isAnomalyVisible = d => _.has(d, 'anomalyScore') && Number(d.anomalyScore) >= severity; @@ -354,7 +354,7 @@ export class ExplorerChartSingleMetric extends React.Component { .on('mouseover', function(d) { showLineChartTooltip(d, this); }) - .on('mouseout', () => tooltipService.hide()); + .on('mouseout', () => mlChartTooltipService.hide()); // Add rectangular markers for any scheduled events. const scheduledEventMarkers = lineChartGroup @@ -503,7 +503,7 @@ export class ExplorerChartSingleMetric extends React.Component { }); } - tooltipService.show(tooltipData, circle, { + mlChartTooltipService.show(tooltipData, circle, { x: LINE_CHART_ANOMALY_RADIUS * 3, y: LINE_CHART_ANOMALY_RADIUS * 2, }); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js index 54f541ceb7c3d..ca3e52308a936 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js @@ -10,13 +10,11 @@ import seriesConfig from './__mocks__/mock_series_config_filebeat.json'; // Mock TimeBuckets and mlFieldFormatService, they don't play well // with the jest based test setup yet. jest.mock('../../util/time_buckets', () => ({ - getTimeBucketsFromCache: jest.fn(() => { - return { - setBounds: jest.fn(), - setInterval: jest.fn(), - getScaledDateFormat: jest.fn(), - }; - }), + TimeBuckets: function() { + this.setBounds = jest.fn(); + this.setInterval = jest.fn(); + this.getScaledDateFormat = jest.fn(); + }, })); jest.mock('../../services/field_format_service', () => ({ mlFieldFormatService: { @@ -45,16 +43,8 @@ describe('ExplorerChart', () => { afterEach(() => (SVGElement.prototype.getBBox = originalGetBBox)); test('Initialize', () => { - const mockTooltipService = { - show: jest.fn(), - hide: jest.fn(), - }; - const wrapper = mountWithIntl( - + ); // without setting any attributes and corresponding data @@ -69,16 +59,10 @@ describe('ExplorerChart', () => { loading: true, }; - const mockTooltipService = { - show: jest.fn(), - hide: jest.fn(), - }; - const wrapper = mountWithIntl( ); @@ -99,18 +83,12 @@ describe('ExplorerChart', () => { chartLimits: chartLimits(chartData), }; - const mockTooltipService = { - show: jest.fn(), - hide: jest.fn(), - }; - // We create the element including a wrapper which sets the width: return mountWithIntl(
); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js index 5b95931d31ab6..99de38c1e0a84 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import $ from 'jquery'; + import React from 'react'; import { @@ -27,7 +29,6 @@ import { ExplorerChartLabel } from './components/explorer_chart_label'; import { CHART_TYPE } from '../explorer_constants'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { MlTooltipComponent } from '../../components/chart_tooltip'; const textTooManyBuckets = i18n.translate('xpack.ml.explorer.charts.tooManyBucketsDescription', { defaultMessage: @@ -120,29 +121,19 @@ function ExplorerChartContainer({ series, severity, tooManyBuckets, wrapLabel }) chartType === CHART_TYPE.POPULATION_DISTRIBUTION ) { return ( - - {tooltipService => ( - - )} - + ); } return ( - - {tooltipService => ( - - )} - + ); })()} @@ -150,36 +141,48 @@ function ExplorerChartContainer({ series, severity, tooManyBuckets, wrapLabel }) } // Flex layout wrapper for all explorer charts -export const ExplorerChartsContainer = ({ - chartsPerRow, - seriesToPlot, - severity, - tooManyBuckets, -}) => { - // doesn't allow a setting of `columns={1}` when chartsPerRow would be 1. - // If that's the case we trick it doing that with the following settings: - const chartsWidth = chartsPerRow === 1 ? 'calc(100% - 20px)' : 'auto'; - const chartsColumns = chartsPerRow === 1 ? 0 : chartsPerRow; - - const wrapLabel = seriesToPlot.some(series => isLabelLengthAboveThreshold(series)); +export class ExplorerChartsContainer extends React.Component { + componentDidMount() { + // Create a div for the tooltip. + $('.ml-explorer-charts-tooltip').remove(); + $('body').append( + '

>

{{screenTitle}} class="dscTable__footer" > {{screenTitle}}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js similarity index 93% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js rename to src/plugins/discover/public/application/angular/discover.js index c1de704d1c00a..2afd0322f8701 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -26,11 +26,8 @@ import dateMath from '@elastic/datemath'; import { i18n } from '@kbn/i18n'; import { getState, splitState } from './discover_state'; -import { RequestAdapter } from '../../../../../../../plugins/inspector/public'; -import { - SavedObjectSaveModal, - showSaveModal, -} from '../../../../../../../plugins/saved_objects/public'; +import { RequestAdapter } from '../../../../inspector/public'; +import { SavedObjectSaveModal, showSaveModal } from '../../../../saved_objects/public'; import { getSortArray, getSortForSearchSource } from './doc_table'; import * as columnActions from './doc_table/actions/columns'; @@ -75,9 +72,9 @@ import { syncQueryStateWithUrl, getDefaultQuery, search, -} from '../../../../../../../plugins/data/public'; +} from '../../../../data/public'; import { getIndexPatternId } from '../helpers/get_index_pattern_id'; -import { addFatalError } from '../../../../../../../plugins/kibana_legacy/public'; +import { addFatalError } from '../../../../kibana_legacy/public'; const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -99,10 +96,10 @@ app.config($routeProvider => { } return { - text: i18n.translate('kbn.discover.badge.readOnly.text', { + text: i18n.translate('discover.badge.readOnly.text', { defaultMessage: 'Read only', }), - tooltip: i18n.translate('kbn.discover.badge.readOnly.tooltip', { + tooltip: i18n.translate('discover.badge.readOnly.tooltip', { defaultMessage: 'Unable to save searches', }), iconType: 'glasses', @@ -333,10 +330,10 @@ function discoverController( const getTopNavLinks = () => { const newSearch = { id: 'new', - label: i18n.translate('kbn.discover.localMenu.localMenu.newSearchTitle', { + label: i18n.translate('discover.localMenu.localMenu.newSearchTitle', { defaultMessage: 'New', }), - description: i18n.translate('kbn.discover.localMenu.newSearchDescription', { + description: i18n.translate('discover.localMenu.newSearchDescription', { defaultMessage: 'New Search', }), run: function() { @@ -349,10 +346,10 @@ function discoverController( const saveSearch = { id: 'save', - label: i18n.translate('kbn.discover.localMenu.saveTitle', { + label: i18n.translate('discover.localMenu.saveTitle', { defaultMessage: 'Save', }), - description: i18n.translate('kbn.discover.localMenu.saveSearchDescription', { + description: i18n.translate('discover.localMenu.saveSearchDescription', { defaultMessage: 'Save Search', }), testId: 'discoverSaveButton', @@ -389,7 +386,7 @@ function discoverController( title={savedSearch.title} showCopyOnSave={!!savedSearch.id} objectType="search" - description={i18n.translate('kbn.discover.localMenu.saveSaveSearchDescription', { + description={i18n.translate('discover.localMenu.saveSaveSearchDescription', { defaultMessage: 'Save your Discover search so you can use it in visualizations and dashboards', })} @@ -402,10 +399,10 @@ function discoverController( const openSearch = { id: 'open', - label: i18n.translate('kbn.discover.localMenu.openTitle', { + label: i18n.translate('discover.localMenu.openTitle', { defaultMessage: 'Open', }), - description: i18n.translate('kbn.discover.localMenu.openSavedSearchDescription', { + description: i18n.translate('discover.localMenu.openSavedSearchDescription', { defaultMessage: 'Open Saved Search', }), testId: 'discoverOpenButton', @@ -419,10 +416,10 @@ function discoverController( const shareSearch = { id: 'share', - label: i18n.translate('kbn.discover.localMenu.shareTitle', { + label: i18n.translate('discover.localMenu.shareTitle', { defaultMessage: 'Share', }), - description: i18n.translate('kbn.discover.localMenu.shareSearchDescription', { + description: i18n.translate('discover.localMenu.shareSearchDescription', { defaultMessage: 'Share Search', }), testId: 'shareTopNavButton', @@ -446,10 +443,10 @@ function discoverController( const inspectSearch = { id: 'inspect', - label: i18n.translate('kbn.discover.localMenu.inspectTitle', { + label: i18n.translate('discover.localMenu.inspectTitle', { defaultMessage: 'Inspect', }), - description: i18n.translate('kbn.discover.localMenu.openInspectorForSearchDescription', { + description: i18n.translate('discover.localMenu.openInspectorForSearchDescription', { defaultMessage: 'Open Inspector for search', }), testId: 'openInspectorButton', @@ -492,7 +489,7 @@ function discoverController( const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; chrome.docTitle.change(`Discover${pageTitleSuffix}`); - const discoverBreadcrumbsTitle = i18n.translate('kbn.discover.discoverBreadcrumbTitle', { + const discoverBreadcrumbsTitle = i18n.translate('discover.discoverBreadcrumbTitle', { defaultMessage: 'Discover', }); @@ -606,16 +603,16 @@ function discoverController( $scope.state.sort = getSortArray($scope.state.sort, $scope.indexPattern); $scope.getBucketIntervalToolTipText = () => { - return i18n.translate('kbn.discover.bucketIntervalTooltip', { + return i18n.translate('discover.bucketIntervalTooltip', { defaultMessage: 'This interval creates {bucketsDescription} to show in the selected time range, so it has been scaled to {bucketIntervalDescription}', values: { bucketsDescription: $scope.bucketInterval.scale > 1 - ? i18n.translate('kbn.discover.bucketIntervalTooltip.tooLargeBucketsText', { + ? i18n.translate('discover.bucketIntervalTooltip.tooLargeBucketsText', { defaultMessage: 'buckets that are too large', }) - : i18n.translate('kbn.discover.bucketIntervalTooltip.tooManyBucketsText', { + : i18n.translate('discover.bucketIntervalTooltip.tooManyBucketsText', { defaultMessage: 'too many buckets', }), bucketIntervalDescription: $scope.bucketInterval.description, @@ -748,7 +745,7 @@ function discoverController( $scope.$evalAsync(() => { if (id) { toastNotifications.addSuccess({ - title: i18n.translate('kbn.discover.notifications.savedSearchTitle', { + title: i18n.translate('discover.notifications.savedSearchTitle', { defaultMessage: `Search '{savedSearchTitle}' was saved`, values: { savedSearchTitle: savedSearch.title, @@ -769,7 +766,7 @@ function discoverController( return { id }; } catch (saveError) { toastNotifications.addDanger({ - title: i18n.translate('kbn.discover.notifications.notSavedSearchTitle', { + title: i18n.translate('discover.notifications.notSavedSearchTitle', { defaultMessage: `Search '{savedSearchTitle}' was not saved.`, values: { savedSearchTitle: savedSearch.title, @@ -812,7 +809,7 @@ function discoverController( $scope.fetchError = fetchError; } else { toastNotifications.addError(error, { - title: i18n.translate('kbn.discover.errorLoadingData', { + title: i18n.translate('discover.errorLoadingData', { defaultMessage: 'Error loading data', }), toastMessage: error.shortMessage || error.body?.message, @@ -903,10 +900,10 @@ function discoverController( function logInspectorRequest() { inspectorAdapters.requests.reset(); - const title = i18n.translate('kbn.discover.inspectorRequestDataTitle', { + const title = i18n.translate('discover.inspectorRequestDataTitle', { defaultMessage: 'data', }); - const description = i18n.translate('kbn.discover.inspectorRequestDescription', { + const description = i18n.translate('discover.inspectorRequestDescription', { defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', }); inspectorRequest = inspectorAdapters.requests.start(title, { description }); @@ -1049,7 +1046,7 @@ function discoverController( } function getIndexPatternWarning(index) { - return i18n.translate('kbn.discover.valueIsNotConfiguredIndexPatternIDWarningTitle', { + return i18n.translate('discover.valueIsNotConfiguredIndexPatternIDWarningTitle', { defaultMessage: '{stateVal} is not a configured index pattern ID', values: { stateVal: `"${index}"`, @@ -1076,7 +1073,7 @@ function discoverController( if (ownIndexPattern) { toastNotifications.addWarning({ title: warningTitle, - text: i18n.translate('kbn.discover.showingSavedIndexPatternWarningDescription', { + text: i18n.translate('discover.showingSavedIndexPatternWarningDescription', { defaultMessage: 'Showing the saved index pattern: "{ownIndexPatternTitle}" ({ownIndexPatternId})', values: { @@ -1090,7 +1087,7 @@ function discoverController( toastNotifications.addWarning({ title: warningTitle, - text: i18n.translate('kbn.discover.showingDefaultIndexPatternWarningDescription', { + text: i18n.translate('discover.showingDefaultIndexPatternWarningDescription', { defaultMessage: 'Showing the default index pattern: "{loadedIndexPatternTitle}" ({loadedIndexPatternId})', values: { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.test.ts b/src/plugins/discover/public/application/angular/discover_state.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.test.ts rename to src/plugins/discover/public/application/angular/discover_state.test.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts b/src/plugins/discover/public/application/angular/discover_state.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts rename to src/plugins/discover/public/application/angular/discover_state.ts index 2a036f0ac60ad..46500d9fdf85e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts +++ b/src/plugins/discover/public/application/angular/discover_state.ts @@ -24,9 +24,9 @@ import { syncState, ReduxLikeStateContainer, IKbnUrlStateStorage, -} from '../../../../../../../plugins/kibana_utils/public'; -import { esFilters, Filter, Query } from '../../../../../../../plugins/data/public'; -import { migrateLegacyQuery } from '../../../../../../../plugins/kibana_legacy/public'; +} from '../../../../kibana_utils/public'; +import { esFilters, Filter, Query } from '../../../../data/public'; +import { migrateLegacyQuery } from '../../../../kibana_legacy/public'; export interface AppState { /** diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.html b/src/plugins/discover/public/application/angular/doc.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.html rename to src/plugins/discover/public/application/angular/doc.html diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts b/src/plugins/discover/public/application/angular/doc.ts similarity index 91% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts rename to src/plugins/discover/public/application/angular/doc.ts index 092e3c79b1007..2d0d45e5003fb 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts +++ b/src/plugins/discover/public/application/angular/doc.ts @@ -49,7 +49,9 @@ app.config(($routeProvider: any) => { }) // the new route, es 7 deprecated types, es 8 removed them .when('/discover/doc/:indexPattern/:index', { - controller: ($scope: LazyScope, $route: any, es: any) => { + // have to be written as function expression, because it's not compiled in dev mode + // eslint-disable-next-line object-shorthand + controller: function($scope: LazyScope, $route: any, es: any) { timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); $scope.esClient = es; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_doc_table.scss b/src/plugins/discover/public/application/angular/doc_table/_doc_table.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_doc_table.scss rename to src/plugins/discover/public/application/angular/doc_table/_doc_table.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_index.scss b/src/plugins/discover/public/application/angular/doc_table/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_index.scss rename to src/plugins/discover/public/application/angular/doc_table/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/actions/columns.ts b/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/actions/columns.ts rename to src/plugins/discover/public/application/angular/doc_table/actions/columns.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_index.scss b/src/plugins/discover/public/application/angular/doc_table/components/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_index.scss rename to src/plugins/discover/public/application/angular/doc_table/components/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_table_header.scss b/src/plugins/discover/public/application/angular/doc_table/components/_table_header.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_table_header.scss rename to src/plugins/discover/public/application/angular/doc_table/components/_table_header.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap b/src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap rename to src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap b/src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap rename to src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/index.ts b/src/plugins/discover/public/application/angular/doc_table/components/pager/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/index.ts rename to src/plugins/discover/public/application/angular/doc_table/components/pager/index.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx index 6f1cf81e2c541..e95153d85b064 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx @@ -35,7 +35,7 @@ export function ToolBarPagerButtons(props: Props) { disabled={!props.hasPreviousPage} data-test-subj="btnPrevPage" aria-label={i18n.translate( - 'kbn.discover.docTable.pager.toolbarPagerButtons.previousButtonAriaLabel', + 'discover.docTable.pager.toolbarPagerButtons.previousButtonAriaLabel', { defaultMessage: 'Previous page in table', } @@ -49,7 +49,7 @@ export function ToolBarPagerButtons(props: Props) { disabled={!props.hasNextPage} data-test-subj="btnNextPage" aria-label={i18n.translate( - 'kbn.discover.docTable.pager.toolbarPagerButtons.nextButtonAriaLabel', + 'discover.docTable.pager.toolbarPagerButtons.nextButtonAriaLabel', { defaultMessage: 'Next page in table', } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.tsx similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.tsx index 84338d817c86b..46e3cd9511eb5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.tsx @@ -30,7 +30,7 @@ export function ToolBarPagerText({ startItem, endItem, totalItems }: Props) {
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header.ts rename to src/plugins/discover/public/application/angular/doc_table/components/table_header.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap b/src/plugins/discover/public/application/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap rename to src/plugins/discover/public/application/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/helpers.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/helpers.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx similarity index 99% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.test.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx index 89f73022627c5..b201bea26503e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.test.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx @@ -25,8 +25,6 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { SortOrder } from './helpers'; import { IndexPattern, IFieldType } from '../../../../../kibana_services'; -jest.mock('ui/new_platform'); - function getMockIndexPattern() { return ({ id: 'test', diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header_column.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx similarity index 88% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header_column.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx index d1a9a5146fb8a..4c09ff8701f30 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header_column.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx @@ -80,21 +80,21 @@ export function TableHeaderColumn({ const getSortButtonAriaLabel = () => { const sortAscendingMessage = i18n.translate( - 'kbn.docTable.tableHeader.sortByColumnAscendingAriaLabel', + 'discover.docTable.tableHeader.sortByColumnAscendingAriaLabel', { defaultMessage: 'Sort {columnName} ascending', values: { columnName: name }, } ); const sortDescendingMessage = i18n.translate( - 'kbn.docTable.tableHeader.sortByColumnDescendingAriaLabel', + 'discover.docTable.tableHeader.sortByColumnDescendingAriaLabel', { defaultMessage: 'Sort {columnName} descending', values: { columnName: name }, } ); const stopSortingMessage = i18n.translate( - 'kbn.docTable.tableHeader.sortByColumnUnsortedAriaLabel', + 'discover.docTable.tableHeader.sortByColumnUnsortedAriaLabel', { defaultMessage: 'Stop sorting on {columnName}', values: { columnName: name }, @@ -126,42 +126,42 @@ export function TableHeaderColumn({ // Remove Button { active: isRemoveable && typeof onRemoveColumn === 'function', - ariaLabel: i18n.translate('kbn.docTable.tableHeader.removeColumnButtonAriaLabel', { + ariaLabel: i18n.translate('discover.docTable.tableHeader.removeColumnButtonAriaLabel', { defaultMessage: 'Remove {columnName} column', values: { columnName: name }, }), className: 'fa fa-remove kbnDocTableHeader__move', onClick: () => onRemoveColumn && onRemoveColumn(name), testSubject: `docTableRemoveHeader-${name}`, - tooltip: i18n.translate('kbn.docTable.tableHeader.removeColumnButtonTooltip', { + tooltip: i18n.translate('discover.docTable.tableHeader.removeColumnButtonTooltip', { defaultMessage: 'Remove Column', }), }, // Move Left Button { active: colLeftIdx >= 0 && typeof onMoveColumn === 'function', - ariaLabel: i18n.translate('kbn.docTable.tableHeader.moveColumnLeftButtonAriaLabel', { + ariaLabel: i18n.translate('discover.docTable.tableHeader.moveColumnLeftButtonAriaLabel', { defaultMessage: 'Move {columnName} column to the left', values: { columnName: name }, }), className: 'fa fa-angle-double-left kbnDocTableHeader__move', onClick: () => onMoveColumn && onMoveColumn(name, colLeftIdx), testSubject: `docTableMoveLeftHeader-${name}`, - tooltip: i18n.translate('kbn.docTable.tableHeader.moveColumnLeftButtonTooltip', { + tooltip: i18n.translate('discover.docTable.tableHeader.moveColumnLeftButtonTooltip', { defaultMessage: 'Move column to the left', }), }, // Move Right Button { active: colRightIdx >= 0 && typeof onMoveColumn === 'function', - ariaLabel: i18n.translate('kbn.docTable.tableHeader.moveColumnRightButtonAriaLabel', { + ariaLabel: i18n.translate('discover.docTable.tableHeader.moveColumnRightButtonAriaLabel', { defaultMessage: 'Move {columnName} column to the right', values: { columnName: name }, }), className: 'fa fa-angle-double-right kbnDocTableHeader__move', onClick: () => onMoveColumn && onMoveColumn(name, colRightIdx), testSubject: `docTableMoveRightHeader-${name}`, - tooltip: i18n.translate('kbn.docTable.tableHeader.moveColumnRightButtonTooltip', { + tooltip: i18n.translate('discover.docTable.tableHeader.moveColumnRightButtonTooltip', { defaultMessage: 'Move column to the right', }), }, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts rename to src/plugins/discover/public/application/angular/doc_table/components/table_row.ts index 698bfe7416d42..3b48c4c79365e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts @@ -23,15 +23,15 @@ import $ from 'jquery'; import rison from 'rison-node'; import '../../doc_viewer'; // @ts-ignore -import { noWhiteSpace } from '../../../../../../common/utils/no_white_space'; +import { noWhiteSpace } from '../../../../../../../legacy/core_plugins/kibana/common/utils/no_white_space'; import openRowHtml from './table_row/open.html'; import detailsHtml from './table_row/details.html'; -import { dispatchRenderComplete } from '../../../../../../../../../plugins/kibana_utils/public'; +import { dispatchRenderComplete } from '../../../../../../kibana_utils/public'; import cellTemplateHtml from '../components/table_row/cell.html'; import truncateByHeightTemplateHtml from '../components/table_row/truncate_by_height.html'; -import { esFilters } from '../../../../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../data/public'; import { getServices } from '../../../../kibana_services'; // guesstimate at the minimum number of chars wide cells in the table should be diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_cell.scss b/src/plugins/discover/public/application/angular/doc_table/components/table_row/_cell.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_cell.scss rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/_cell.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_details.scss b/src/plugins/discover/public/application/angular/doc_table/components/table_row/_details.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_details.scss rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/_details.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_index.scss b/src/plugins/discover/public/application/angular/doc_table/components/table_row/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_index.scss rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_open.scss b/src/plugins/discover/public/application/angular/doc_table/components/table_row/_open.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_open.scss rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/_open.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/cell.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.html similarity index 66% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/cell.html rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.html index 0704016a52bbd..e8c4fceeca7ff 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/cell.html +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.html @@ -18,17 +18,17 @@ data-column="<%- column %>" tooltip-append-to-body="1" data-test-subj="docTableCellFilter" - tooltip="{{ ::'kbn.docTable.tableRow.filterForValueButtonTooltip' | i18n: {defaultMessage: 'Filter for value'} }}" + tooltip="{{ ::'discover.docTable.tableRow.filterForValueButtonTooltip' | i18n: {defaultMessage: 'Filter for value'} }}" tooltip-placement="bottom" - aria-label="{{ ::'kbn.docTable.tableRow.filterForValueButtonAriaLabel' | i18n: {defaultMessage: 'Filter for value'} }}" + aria-label="{{ ::'discover.docTable.tableRow.filterForValueButtonAriaLabel' | i18n: {defaultMessage: 'Filter for value'} }}" > diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/details.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/details.html similarity index 89% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/details.html rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/details.html index d149a9023816a..37ae08246d1d3 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/details.html +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/details.html @@ -8,7 +8,7 @@

@@ -22,7 +22,7 @@ data-test-subj="docTableRowAction" ng-href="{{ getContextAppHref() }}" ng-if="indexPattern.isTimeBased()" - i18n-id="kbn.docTable.tableRow.viewSurroundingDocumentsLinkText" + i18n-id="discover.docTable.tableRow.viewSurroundingDocumentsLinkText" i18n-default-message="View surrounding documents" >
@@ -31,7 +31,7 @@ class="euiLink" data-test-subj="docTableRowAction" ng-href="#/discover/doc/{{indexPattern.id}}/{{row._index}}?id={{uriEncodedId}}" - i18n-id="kbn.docTable.tableRow.viewSingleDocumentLinkText" + i18n-id="discover.docTable.tableRow.viewSingleDocumentLinkText" i18n-default-message="View single document" > diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/open.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/open.html similarity index 72% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/open.html rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/open.html index d6c4b858d2b47..6a14b6fc70348 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/open.html +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/open.html @@ -2,7 +2,7 @@