From 551bb077aae3a96d2493b9a8b8e70b0070824598 Mon Sep 17 00:00:00 2001
From: Maja Grubic
-
+
-
+ {permissionsError === 'MISSING_SUPERUSER_ROLE' ? (
+
fPxMw$()FJp9}b>k@Ix0C+qg`ZxH^HST_hr0^za-6=s z@4fFohCHSLG`cwX%f({QuQvDiYBJ6BIp}YB?%HpESp3rAWq*+0)@GWukdP9k;cVem zXMS?FdPBpcX3bPEF^fv;z)DmFBMR#kw=4xC6<@xy$(iS~Af2}grHITI?WtXPNZu>W zv{g~@oJ-QRmMN`yKYiJ(=E*0?qGMijo`lZixVaoqQpd<*=*`}6k%F+I6s0^9-m@Ys zW`|63KNnZujysr?)<(@h2WAp;20ajIDd!q<_0|FC_Wt*=$(- z_piUc8jnuLet7bdhQ{RAU*|^Ji#Y9n$aFpAUw?fueElIVp`(7%n!)SGu{KV;8`|I9 zXt~@({o&=^IQuRNmdKL`G-((iGxeC&88ep5cub5hw8RR1{{3anrS6;d 6oeIL ln@EPUbRXwZ)z*MH;p_`)$Ket;j4W-IwU zdjT(AND20PfiEPNFJG7!>nEIl^`~biKU5O-FHRr5sa3 B)Jp8 z{N?1QG-FL4GMw(xIbLwhqyy#Z68^x+V$q=hmqG^NN$Fzkp#Xl6#|s}f9HpzlgI3zp z$(~uZY3af1ggL|F+R5N446i^q_;gZVyqwMWg5Tl?lP~=JZT_3;jX!!XYq)z)-Qv&l zNqxYR`h$h )Z3b*XlFBy?dl((`R&j@#*$o|ML3hAO5yz72PB9zaIO9JG=_1^!#wm z!^^j|o!`!VO#R{NXEodWKyCBgv1J1 jGurwGS7|u^Y=yCo$iJr%(bKeqo zcXo0;CL3b>d`D%`OqpHgYrpu@HJlgq%xiuaEz{9?Ug2RH4Sjz$TK@j?$%}vf{O5oC z^p~H1S-cGbWIi3&+a1tMU57T;!pg^9^Ld0!KGHxKE H>iy+O z9QQBpje(!?<-o^$;!o~I@5$`zPCh-`AC}*pFY4g=JPuc`-{0ly{9;na@vCuv=*J&_ zuAlbm{w}{;c-L=p|N8ghsQJ{HuYrRCZkH5sX9`U3K6<#gj*VV%HCcfA+D^Z^yqiAQ z{14Z@@%aw!`QY~Lyo38{bRo9x;2K6WeA>Zfj2Y#afRc&_8qtZE931cx3prJSi%^~w zGvht?AB~wwlUdYS0oKKeGT8#blNlOh4w{-r6}GDti|T50!_EqKYl=iPVva78q3jDy z&Q?co7#+lHL&{`71dC73%ZjF6-9v6So_bBHldesWsH+vErPV~7L)Ks!kQ<^nHQdof z%`gnl?E9PX`wzRw^`G-bgzn4AxrrLB23HSuk(LJt&PZT6`XW=dPKHK=MuZ25FdE`@ zxs~c-y!ANx9Bj52M<2Z01yuNPLxm+SbS?;JgaPTZ0dFw_=Ya?3gp9dCr9tIEsHE$g zcURMC+Wr!vIbLlODsOfH6+CXJATp5%AP|&FAeox2N+fedIj+1UgC)BLl?Igupb{q2 z_ZP!kd40NlsC?noHlZ^3cA@e#I9kTPW&B&le}6On7+E$k*7%?%#nU7Uo+(|Ael0Zd z(LmB-yBU9W_Y2niasO-mg!89d{MBH(SGj(}gfs~`s8Sa^W6a m5_d@n86|8=?k<@P+_Tz|96Tekn ;8?Q#YlA9Zo$X(SOUw4RZ2v~i z?M`xg; ?)+1(f zQ}GrfX2YH2_T+Ix#b$0?-D#-^3?K_VopUylo312xsIc(dpwgi708|{mDtd6^^K|P_ znd8+qp|X?Qo;q%*#HhV7#R79NfmAYs69&N(ZITqpdfRe)4Jr>o<$|&6>A5_Yw_AtG z9Iv(sm7V1F^pUSZaxz-YEg@ZdVU6jvUEtjzX7+N@v-i(xCDHR9KvhFAAUbFTNG1 zbR1K$G#2aj@<$C7hg=k)Eo8((;!q4MNfb=VSjaVW-u=FfjOd9QQW{eB6Db#6M^qf= z5|5( 2H!L-K>=cmIDIIsN>j*MXj~8#~fP`nqv|c2p;4IVdCIRCKc7v7Azt$VH;E$R1OI$ zJ6Ww_jyngWvXdga1T0xHR $z!QlPv@cQgqflAkL z6+v9lN1j-=C2K}N0cs2mk}*jl(H^ujOy!cJN~|T88&VDgDLbkEvB$muPVtaPqY|tC zBS=HuumV`T;Kc_fvu}1vgUSO?xw#mJ3x9roe)Emg|6Fz*QDMeddE|ThVJR}Q;=mLm zNgPhmg9!)LV-g`L?t^Z2N<+$FAm!tae;=|xzxZSNlTrWr@!gNV{BL=)f|OyNj{9+r zDe2ROG{1)}l1YMul4^tVx3bT5!|nvd#|$#oGw>v%k)@;L-ZQd$!XxKzt>ee<+#QH> zX>ITsh>2ubWD{uAqXq9Hdhb%UTpz-DKnuImeCeX+;L3+#IU7cGezm6jNruE@JqgG- z12Sb^afN`=8WEhRQ_lwHkkD_#dMM56K_p!CkkN0qa> vDd+|v zL?{Tlp6 )Dqk`An0iR;UtMTYTQ1FHA1;3sYafK9TuRA4$ zvrm)4#Nvwa0?>L*6AsT1f>gDL-+Q!$+r?nx5-GM|K``M|$f!XmL8Q>k5;HVSC_#`X zVU@H_94slEeGVz&&8<2e&_K9cV?l&$$>kKFov!F-g_Pzw6epv=AZ2#yNm58l9uMy! z;u-^j`W@;2)hi6E4I$X(C!FO90`i5?gC#F%X%{2Kz>$lT0BJN-oE)Kfar)-g%||kZ zzodV50u3V_XPPr3pN57LLI&q0gi@%3Dn2Yn@@bt$RtO|1KjwDe*Fz&~a(1$bU|baS z&^}xD9`QA0Vl+*L eKKyrRDIiq#X-Wu%6r ziJPh9y>aw!^iLZWf> `!skTgk{p4=P?T9G3Qwi*x$;_;XVr-K!x54%yi;;5U_ z_f+YhJ7+(Y&JDQ{f~*6zqI6X~g*oT3Dg7a(^A)Arj+8DWHl^>Y((gNGFO@Fne?>OE zZgPG?&Sx)|<(z-&NX|;xCg**X^L^*+rJRx6b|dQi-SqsFo>yG%JnBf##>l4Uebw`Q z=j^4P$)_fJ+q$Oar_T;vQ1e8_*Bu7XjMnODHES1A)E22D_-soC4c0}3Bt45Mx)?Kd ztLB(k3dzt=aJE)NNxFj+0x+ADFG1R%_`yZpzjV=aG<|k+eQ|d4{^LlmsaoaE3ma=z zi-)=pgWyQUJp>VbmVsNF_$DT|wLPUa)^tt~4LA>m<^`G%mQI!l8LR>2YlJ-+Kz=l$ zAG}- on-y5d z4#RN`7JChg`!3o~3qkj|Q{>ExRai*TbJ$y5@#lDt{+mCev4P^*=kP9pBK@tyKwksJ zUW4Mki}n%}sC&9*>sD9!B6B@Q7?H98;j4kL5?e_94kLvP2zw2L`!3o~AaL>)>asq$ zc12%ZyJv7+Nr0jEAU0NfEh~mEZ(*JlZ$|H+vp6JYPU9x^Ln#WZM|fosEl8&dv7!V> zx`b4i4kkXf`d4hBen=FbnM;5n*Sv<3;4RTATahj2mTQ&{G7kBvi=M*@QO+;>fyvv^ zn%qLkhH00n2L%TyjRPx?9#b0=5`8oB4RVFT^yn_DtFf`}ovC#|UO|Auz%11}I`AGm zXazJSnYhFRYWq@aB{iS`e}s#Chi{hHAWvU)7Opgn4f`IhH2cejS#6 o;oq9SXZP| za~4Np)AUcw#>Skj-pAQBay%yxil0ty&bCu;F?N*F-hQ&M-Z``F;lu2n$-*z2St1 z#1CWJx91arFB4+;Lw9k{Mu@$Sd+skGxG_D8J-f;WL^J!XY~#Q)Iq=t)D;)U0?!uLg z1A8B?++z;VgkM7pYTed~Ze~buTwR?xE4{4c)Qu6(WyG5mMttZlaM>8K_kqhjW`q^9 zH5ABXllyb!z7jmR_+xi*#U}T?k1Ou6+!^n2;d*La>_FBUEqX~K!E;IQZsj1m;Xk?y zBWg>defx(xY;bVH?<4`ndUj 42+DzHibf4RdEri2p$}~b#7~{GmnTGMJ4-FB zskh*UI-JwBb&&(21HBVbw}T}*g2&2%b;Hx{B8&|l`@5;4* 37hr4{*xwxr|Rd>uShas_hyEy1~Ht>alQt?JkDcFtEQnHM)Ja23(KVqRSP3 zL71MX3mjK3=vj85p7+$q(G59Pf)E#_yKrP9$NugKsrHu~?U>5nPl5jj=U5XJIFbec DzXCnw literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/endpoint/resolver_tree/api_feature/mappings.json b/x-pack/test/functional/es_archives/endpoint/resolver_tree/api_feature/mappings.json new file mode 100644 index 0000000000000..13a16ee4e646d --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/resolver_tree/api_feature/mappings.json @@ -0,0 +1,2906 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "events-endpoint-1", + "mappings": { + "_meta": { + "version": "1.5.0-dev" + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "compile_time": { + "type": "date" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mapped_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "mapped_size": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "question": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "endpoint": { + "properties": { + "artifact": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "type": "object" + }, + "group": { + "type": "object" + }, + "policy": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "type": "object" + }, + "user": { + "type": "object" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "entry_modified": { + "type": "double" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "macro": { + "properties": { + "code_page": { + "type": "long" + }, + "collection": { + "properties": { + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "errors": { + "properties": { + "count": { + "type": "long" + }, + "error_type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "file_extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "project_file": { + "properties": { + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "stream": { + "properties": { + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "raw_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "raw_code_size": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "temp_file_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "variant": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "message": { + "type": "text" + }, + "network": { + "properties": { + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "cpu_percent": { + "type": "double" + }, + "cwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "env_variables": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "handles": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "memory_percent": { + "type": "double" + }, + "memory_region": { + "properties": { + "allocation_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "allocation_protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram": { + "properties": { + "histogram_array": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_flavor": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_resolution": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "length": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "permission": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_tag": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "unbacked_on_disk": { + "type": "boolean" + } + }, + "type": "nested" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "num_threads": { + "type": "long" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "phys_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "services": { + "ignore_above": 1024, + "type": "keyword" + }, + "session_id": { + "type": "long" + }, + "short_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "call_stack": { + "properties": { + "instruction_pointer": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_section": { + "properties": { + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "rva": { + "ignore_above": 1024, + "type": "keyword" + }, + "symbol_info": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "elevation": { + "type": "boolean" + }, + "elevation_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tty_device": { + "properties": { + "major_number": { + "type": "integer" + }, + "minor_number": { + "type": "integer" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + }, + "virt_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "target": { + "properties": { + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "compile_time": { + "type": "date" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mapped_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "mapped_size": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "cpu_percent": { + "type": "double" + }, + "cwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "env_variables": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "handles": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "memory_percent": { + "type": "double" + }, + "memory_region": { + "properties": { + "allocation_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "allocation_protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram": { + "properties": { + "histogram_array": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_flavor": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_resolution": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "length": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "permission": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_tag": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "unbacked_on_disk": { + "type": "boolean" + } + }, + "type": "nested" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "num_threads": { + "type": "long" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "phys_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "services": { + "ignore_above": 1024, + "type": "keyword" + }, + "session_id": { + "type": "long" + }, + "short_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "call_stack": { + "properties": { + "instruction_pointer": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_section": { + "properties": { + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "rva": { + "ignore_above": 1024, + "type": "keyword" + }, + "symbol_info": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "elevation": { + "type": "boolean" + }, + "elevation_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tty_device": { + "properties": { + "major_number": { + "type": "integer" + }, + "minor_number": { + "type": "integer" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + }, + "virt_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "events-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" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "metrics-endpoint-default-1", + "mappings": { + "_meta": { + "version": "1.5.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": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "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": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "event": { + "properties": { + "created": { + "type": "date" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "variant": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "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" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional_endpoint/apps/endpoint/index.ts b/x-pack/test/functional_endpoint/apps/endpoint/index.ts index 3e70a1cc67670..05fed43d1272b 100644 --- a/x-pack/test/functional_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/functional_endpoint/apps/endpoint/index.ts @@ -15,5 +15,6 @@ export default function({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./host_list')); loadTestFile(require.resolve('./policy_list')); loadTestFile(require.resolve('./alerts')); + loadTestFile(require.resolve('./resolver')); }); } diff --git a/x-pack/test/functional_endpoint/apps/endpoint/resolver.ts b/x-pack/test/functional_endpoint/apps/endpoint/resolver.ts new file mode 100644 index 0000000000000..417073865df7b --- /dev/null +++ b/x-pack/test/functional_endpoint/apps/endpoint/resolver.ts @@ -0,0 +1,259 @@ +/* + * 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'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects(['common', 'timePicker', 'endpointAlerts']); + const testSubjects = getService('testSubjects'); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const browser = getService('browser'); + + describe('Endpoint Alert Resolver', function() { + this.tags(['ciGroup7']); + before(async () => { + const fromTime = 'Sep 22, 2019 @ 20:31:44.000'; + const toTime = 'Now'; + await esArchiver.load('endpoint/resolver_tree/api_feature'); + await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/alerts'); + await testSubjects.existOrFail('superDatePickerShowDatesButton', { timeout: 20000 }); + await pageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await testSubjects.existOrFail('alertListPage'); + await testSubjects.click('alertTypeCellLink'); + await testSubjects.existOrFail('alertDetailFlyout'); + await testSubjects.click('overviewResolverTab'); + await testSubjects.existOrFail('resolverEmbeddable', { timeout: 20000 }); + await browser.setWindowSize(2400, 1800); + }); + + it('resolver column Process Name exits', async () => { + await testSubjects.existOrFail('tableHeaderCell_name_0'); + }); + + it('resolver column Timestamp exits', async () => { + await testSubjects.existOrFail('tableHeaderCell_timestamp_1'); + }); + + it('resolver Table and Node data same length', async () => { + let count = 1; + const tableData = await pageObjects.endpointAlerts.getEndpointAlertResolverTableData( + 'resolverEmbeddable', + 'tr' + ); + await retry.try(async function() { + await testSubjects.click('zoom-out'); + const Nodes = await testSubjects.findAll('resolverNode'); + expect(tableData.length - 1).to.eql(Nodes.length); + count++; + }); + for (let i = 0; i < count; i++) { + await testSubjects.click('zoom-in'); + } + }); + + it('compare resolver Nodes and Table data', async () => { + const $: string[] = []; + const tableData = await pageObjects.endpointAlerts.getEndpointAlertResolverTableData( + 'resolverEmbeddable', + 'tr' + ); + await testSubjects.click('zoom-out'); + const Nodes = await testSubjects.findAll('euiButton__text'); + for (const value of Nodes) { + $.push(await value._webElement.getText()); + } + for (let i = 0; i < $.length; i++) { + expect(tableData[i + 1][0]).to.eql($[i]); + } + await testSubjects.click('zoom-in'); + }); + + it('resolver Nodes navigation Up', async () => { + const OriginalNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + await testSubjects.click('north-button'); + const NewNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + for (let i = 0; i < OriginalNodeDataStyle.length; i++) { + expect(parseFloat(OriginalNodeDataStyle[i].top)).to.lessThan( + parseFloat(NewNodeDataStyle[i].top) + ); + expect(parseFloat(OriginalNodeDataStyle[i].left)).to.equal( + parseFloat(NewNodeDataStyle[i].left) + ); + } + await testSubjects.click('center-button'); + }); + + it('resolver Nodes navigation Down', async () => { + const OriginalNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + await testSubjects.click('south-button'); + + const NewNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + for (let i = 0; i < NewNodeDataStyle.length; i++) { + expect(parseFloat(NewNodeDataStyle[i].top)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].top) + ); + expect(parseFloat(OriginalNodeDataStyle[i].left)).to.equal( + parseFloat(NewNodeDataStyle[i].left) + ); + } + await testSubjects.click('center-button'); + }); + + it('resolver Nodes navigation Right', async () => { + const OriginalNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + await testSubjects.click('west-button'); + const NewNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + for (let i = 0; i < NewNodeDataStyle.length; i++) { + expect(parseFloat(OriginalNodeDataStyle[i].left)).to.lessThan( + parseFloat(NewNodeDataStyle[i].left) + ); + expect(parseFloat(NewNodeDataStyle[i].top)).to.equal( + parseFloat(OriginalNodeDataStyle[i].top) + ); + } + await testSubjects.click('center-button'); + }); + + it('resolver Nodes navigation Left', async () => { + const OriginalNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + await testSubjects.click('east-button'); + + const NewNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + for (let i = 0; i < OriginalNodeDataStyle.length; i++) { + expect(parseFloat(NewNodeDataStyle[i].left)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].left) + ); + expect(parseFloat(NewNodeDataStyle[i].top)).to.equal( + parseFloat(OriginalNodeDataStyle[i].top) + ); + } + await testSubjects.click('center-button'); + }); + + it('resolver Nodes navigation Center', async () => { + const OriginalNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + await testSubjects.click('east-button'); + await testSubjects.click('south-button'); + + const NewNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + for (let i = 0; i < NewNodeDataStyle.length; i++) { + expect(parseFloat(NewNodeDataStyle[i].left)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].left) + ); + expect(parseFloat(NewNodeDataStyle[i].top)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].top) + ); + } + await (await testSubjects.find('center-button')).click(); + const CenterNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + + for (let i = 0; i < CenterNodeDataStyle.length; i++) { + expect(parseFloat(CenterNodeDataStyle[i].left)).to.equal( + parseFloat(OriginalNodeDataStyle[i].left) + ); + expect(parseFloat(CenterNodeDataStyle[i].top)).to.equal( + parseFloat(OriginalNodeDataStyle[i].top) + ); + } + }); + + it('resolver Nodes navigation zoom in', async () => { + const OriginalNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + await testSubjects.click('zoom-in'); + const NewNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + for (let i = 1; i < NewNodeDataStyle.length; i++) { + expect(parseFloat(OriginalNodeDataStyle[i].left)).to.lessThan( + parseFloat(NewNodeDataStyle[i].left) + ); + expect(parseFloat(OriginalNodeDataStyle[i].top)).to.lessThan( + parseFloat(NewNodeDataStyle[i].top) + ); + expect(parseFloat(OriginalNodeDataStyle[i].width)).to.lessThan( + parseFloat(NewNodeDataStyle[i].width) + ); + expect(parseFloat(OriginalNodeDataStyle[i].height)).to.lessThan( + parseFloat(NewNodeDataStyle[i].height) + ); + await testSubjects.click('zoom-out'); + } + }); + + it('resolver Nodes navigation zoom out', async () => { + const OriginalNodeDataStyle = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + await testSubjects.click('zoom-out'); + const NewNodeDataStyle1 = await pageObjects.endpointAlerts.parseStyles( + 'resolverNode', + 'style' + ); + for (let i = 1; i < OriginalNodeDataStyle.length; i++) { + expect(parseFloat(NewNodeDataStyle1[i].left)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].left) + ); + expect(parseFloat(NewNodeDataStyle1[i].top)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].top) + ); + expect(parseFloat(NewNodeDataStyle1[i].width)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].width) + ); + expect(parseFloat(NewNodeDataStyle1[i].height)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].height) + ); + } + await testSubjects.click('zoom-in'); + }); + + after(async () => { + await browser.setWindowSize(1600, 1000); + await testSubjects.click('euiFlyoutCloseButton'); + await pageObjects.common.sleep(2000); + await esArchiver.unload('endpoint/resolver_tree/api_feature'); + }); + }); +} diff --git a/x-pack/test/functional_endpoint/page_objects/endpoint_alerts_page.ts b/x-pack/test/functional_endpoint/page_objects/endpoint_alerts_page.ts index a5ad45536de89..ff675f151c087 100644 --- a/x-pack/test/functional_endpoint/page_objects/endpoint_alerts_page.ts +++ b/x-pack/test/functional_endpoint/page_objects/endpoint_alerts_page.ts @@ -5,16 +5,48 @@ */ import { FtrProviderContext } from '../ftr_provider_context'; +import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; export function EndpointAlertsPageProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + /** + * @function parseStyles + * Parses a string of inline styles into a javascript object with casing for react + * + * @param {string} styles + * @returns {Object} + */ + const parseStyle = (styles: any) => + styles + .split(';') + .filter((style: any) => style.split(':')[0] && style.split(':')[1]) + .map((style: any) => [ + style + .split(':')[0] + .trim() + .replace(/-./g, (c: any) => c.substr(1).toUpperCase()), + style + .split(':') + .slice(1) + .join(':') + .trim(), + ]) + .reduce( + (styleObj: any, style: any) => ({ + ...styleObj, + [style[0]]: style[1], + }), + {} + ); return { async enterSearchBarQuery(query: string) { return await testSubjects.setValue('alertsSearchBar', query, { clearWithKeyboard: true }); }, async submitSearchBarFilter() { - return await testSubjects.click('querySubmitButton'); + return testSubjects.click('querySubmitButton'); }, async setSearchBarDate(timestamp: string) { await testSubjects.click('superDatePickerShowDatesButton'); @@ -23,5 +55,70 @@ export function EndpointAlertsPageProvider({ getService }: FtrProviderContext) { await testSubjects.setValue('superDatePickerAbsoluteDateInput', timestamp); await this.submitSearchBarFilter(); }, + /** + * Finds a table and returns the data in a nested array with row 0 is the headers if they exist. + * It uses euiTableCellContent to avoid poluting the array data with the euiTableRowCell__mobileHeader data. + * @param dataTestSubj + * @param element + * @returns Promise + */ + async getEndpointAlertResolverTableData(dataTestSubj: string, element: string) { + await testSubjects.exists(dataTestSubj); + const hostTable: WebElementWrapper = await testSubjects.find(dataTestSubj); + const $ = await hostTable.parseDomContent(); + return $(element) + .toArray() + .map(row => + $(row) + .find('.euiTableCellContent') + .toArray() + .map(cell => + $(cell) + .text() + .replace(/ /g, '') + .trim() + ) + ); + }, + /** + * Finds a nodes and returns the data in a nested array of nodes. + * @param dataTestSubj + * @param element + * @returns Promise + */ + async getEndpointAlertResolverNodeData(dataTestSubj: string, element: string) { + await testSubjects.exists(dataTestSubj); + const Elements = await testSubjects.findAll(dataTestSubj); + const $ = []; + for (const value of Elements) { + $.push(await value.getAttribute(element)); + } + return $; + }, + /** + * Gets a array of not parsed styles and returns the Array of parsed styles. + * @returns Promise + * @param dataTestSubj + * @param element + */ + async parseStyles(dataTestSubj: string, element: string) { + const tableData = await this.getEndpointAlertResolverNodeData(dataTestSubj, element); + const $ = []; + for (let i = 1; i < tableData.length; i++) { + const eachStyle = parseStyle(tableData[i]); + $.push(eachStyle); + } + return $; + }, + + async waitForTableToHaveData(dataTestSubj: string) { + await retry.waitForWithTimeout('table to have data', 2000, async () => { + const tableData = await this.getEndpointAlertResolverTableData(dataTestSubj, 'tr'); + if (tableData[1][0] === 'No items found') { + return false; + } + return true; + }); + }, }; } From 932d6ad214a879ddf398438d321ec865776a592f Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 5 May 2020 14:56:31 -0600 Subject: [PATCH 40/72] [Maps] do not show auto-clustering capabilities for a geo_shape source (#65306) * [Maps] do not show auto-clustering capabilities for a geo_shape source * add check in UpdateSourceEditor --- .../plugins/maps/public/index_pattern_util.js | 20 +++++++++++-------- .../create_source_editor.js | 9 +++------ .../es_pew_pew_source/create_source_editor.js | 9 +++------ .../es_search_source/create_source_editor.js | 15 +++++--------- .../es_search_source/update_source_editor.js | 4 ++-- 5 files changed, 25 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/maps/public/index_pattern_util.js b/x-pack/plugins/maps/public/index_pattern_util.js index 6cb02c7605e28..bbea4a9e3ab2a 100644 --- a/x-pack/plugins/maps/public/index_pattern_util.js +++ b/x-pack/plugins/maps/public/index_pattern_util.js @@ -32,14 +32,18 @@ export function getTermsFields(fields) { export const AGGREGATABLE_GEO_FIELD_TYPES = [ES_GEO_FIELD_TYPE.GEO_POINT]; -export function getAggregatableGeoFields(fields) { - return fields.filter(field => { - return ( - field.aggregatable && - !indexPatterns.isNestedField(field) && - AGGREGATABLE_GEO_FIELD_TYPES.includes(field.type) - ); - }); +export function getFieldsWithGeoTileAgg(fields) { + return fields.filter(supportsGeoTileAgg); +} + +export function supportsGeoTileAgg(field) { + // TODO add geo_shape support with license check + return ( + field && + field.aggregatable && + !indexPatterns.isNestedField(field) && + field.type === ES_GEO_FIELD_TYPE.GEO_POINT + ); } // Returns filtered fields list containing only fields that exist in _source. diff --git a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js index 265606dc87e0f..77d2ffb8c577e 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js @@ -14,10 +14,7 @@ import { NoIndexPatternCallout } from '../../../components/no_index_pattern_call import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiSpacer } from '@elastic/eui'; -import { - AGGREGATABLE_GEO_FIELD_TYPES, - getAggregatableGeoFields, -} from '../../../index_pattern_util'; +import { AGGREGATABLE_GEO_FIELD_TYPES, getFieldsWithGeoTileAgg } from '../../../index_pattern_util'; import { RenderAsSelect } from './render_as_select'; export class CreateSourceEditor extends Component { @@ -90,7 +87,7 @@ export class CreateSourceEditor extends Component { }); //make default selection - const geoFields = getAggregatableGeoFields(indexPattern.fields); + const geoFields = getFieldsWithGeoTileAgg(indexPattern.fields); if (geoFields[0]) { this._onGeoFieldSelect(geoFields[0].name); } @@ -145,7 +142,7 @@ export class CreateSourceEditor extends Component { onChange={this._onGeoFieldSelect} fields={ this.state.indexPattern - ? getAggregatableGeoFields(this.state.indexPattern.fields) + ? getFieldsWithGeoTileAgg(this.state.indexPattern.fields) : undefined } /> diff --git a/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js index a4af1a3c19c83..78c16130891b8 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js @@ -14,10 +14,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFormRow, EuiCallOut } from '@elastic/eui'; -import { - AGGREGATABLE_GEO_FIELD_TYPES, - getAggregatableGeoFields, -} from '../../../index_pattern_util'; +import { AGGREGATABLE_GEO_FIELD_TYPES, getFieldsWithGeoTileAgg } from '../../../index_pattern_util'; export class CreateSourceEditor extends Component { static propTypes = { @@ -86,7 +83,7 @@ export class CreateSourceEditor extends Component { return; } - const geoFields = getAggregatableGeoFields(indexPattern.fields); + const geoFields = getFieldsWithGeoTileAgg(indexPattern.fields); this.setState({ isLoadingIndexPattern: false, indexPattern: indexPattern, @@ -128,7 +125,7 @@ export class CreateSourceEditor extends Component { } const fields = this.state.indexPattern - ? getAggregatableGeoFields(this.state.indexPattern.fields) + ? getFieldsWithGeoTileAgg(this.state.indexPattern.fields) : undefined; return ( diff --git a/x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js index aeb3835354f07..3a25bd90384e9 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js @@ -17,7 +17,7 @@ import { ES_GEO_FIELD_TYPE, SCALING_TYPES } from '../../../../common/constants'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { ScalingForm } from './scaling_form'; -import { getTermsFields } from '../../../index_pattern_util'; +import { getTermsFields, supportsGeoTileAgg } from '../../../index_pattern_util'; function getGeoFields(fields) { return fields.filter(field => { @@ -28,13 +28,8 @@ function getGeoFields(fields) { }); } -function isGeoFieldAggregatable(indexPattern, geoFieldName) { - if (!indexPattern) { - return false; - } - - const geoField = indexPattern.fields.getByName(geoFieldName); - return geoField && geoField.aggregatable; +function doesGeoFieldSupportGeoTileAgg(indexPattern, geoFieldName) { + return indexPattern ? supportsGeoTileAgg(indexPattern.fields.getByName(geoFieldName)) : false; } const RESET_INDEX_PATTERN_STATE = { @@ -133,7 +128,7 @@ export class CreateSourceEditor extends Component { // Respect previous scaling type selection unless newly selected geo field does not support clustering. const scalingType = this.state.scalingType === SCALING_TYPES.CLUSTERS && - !isGeoFieldAggregatable(this.state.indexPattern, geoFieldName) + !doesGeoFieldSupportGeoTileAgg(this.state.indexPattern, geoFieldName) ? SCALING_TYPES.LIMIT : this.state.scalingType; this.setState( @@ -218,7 +213,7 @@ export class CreateSourceEditor extends Component { indexPatternId={this.state.indexPatternId} onChange={this._onScalingPropChange} scalingType={this.state.scalingType} - supportsClustering={isGeoFieldAggregatable( + supportsClustering={doesGeoFieldSupportGeoTileAgg( this.state.indexPattern, this.state.geoFieldName )} diff --git a/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js index cb6255afd0a42..59b41c2a79532 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js @@ -12,7 +12,7 @@ import { TooltipSelector } from '../../../components/tooltip_selector'; import { getIndexPatternService } from '../../../kibana_services'; import { i18n } from '@kbn/i18n'; -import { getTermsFields, getSourceFields } from '../../../index_pattern_util'; +import { getTermsFields, getSourceFields, supportsGeoTileAgg } from '../../../index_pattern_util'; import { SORT_ORDER } from '../../../../common/constants'; import { ESDocField } from '../../fields/es_doc_field'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -90,7 +90,7 @@ export class UpdateSourceEditor extends Component { }); this.setState({ - supportsClustering: geoField.aggregatable, + supportsClustering: supportsGeoTileAgg(geoField), sourceFields: sourceFields, termFields: getTermsFields(indexPattern.fields), //todo change term fields to use fields sortFields: indexPattern.fields.filter( From 5bad855e4b759a3bb486349b1bbbaf7a56d4fffe Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Tue, 5 May 2020 16:58:36 -0400 Subject: [PATCH 41/72] Return to dashboard after editing embeddable (#62865) Changed the process for saving visualizations and lens visualizations when the user has been redirected there from another application. --- .../viewport/dashboard_viewport.test.tsx | 3 +- .../tests/dashboard_container.test.tsx | 5 +- .../dashboard/public/dashboard_constants.ts | 1 - src/plugins/embeddable/public/index.ts | 1 + .../lib/actions/edit_panel_action.test.tsx | 12 +- .../public/lib/actions/edit_panel_action.ts | 22 ++- .../lib/panel/embeddable_panel.test.tsx | 12 +- src/plugins/embeddable/public/types.ts | 2 + src/plugins/saved_objects/public/index.ts | 9 +- .../saved_objects/public/save_modal/index.ts | 3 +- .../save_modal/saved_object_save_modal.tsx | 13 +- .../saved_object_save_modal_origin.tsx | 117 ++++++++++++ .../visualize_embeddable_factory.tsx | 18 +- src/plugins/visualizations/public/mocks.ts | 3 +- src/plugins/visualizations/public/plugin.ts | 10 +- .../public/wizard/new_vis_modal.test.tsx | 6 +- .../public/wizard/new_vis_modal.tsx | 8 +- .../public/application/editor/editor.js | 107 +++++++---- .../application/editor/lib/url_helper.ts | 6 +- .../dashboard/create_and_add_embeddables.js | 6 +- .../dashboard/edit_embeddable_redirects.js | 79 ++++++++ test/functional/apps/dashboard/index.js | 1 + test/functional/apps/dashboard/view_edit.js | 5 +- .../functional/page_objects/visualize_page.ts | 38 +++- .../services/dashboard/visualizations.js | 5 +- test/functional/services/test_subjects.ts | 18 +- .../lens/public/app_plugin/app.test.tsx | 41 ++--- x-pack/plugins/lens/public/app_plugin/app.tsx | 172 +++++++++++------- .../lens/public/app_plugin/mounter.tsx | 57 ++++-- .../plugins/lens/public/helpers/url_helper.ts | 6 +- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../dashboard_mode/dashboard_empty_screen.js | 39 +++- .../test/functional/page_objects/lens_page.ts | 19 +- 34 files changed, 650 insertions(+), 198 deletions(-) create mode 100644 src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx create mode 100644 test/functional/apps/dashboard/edit_embeddable_redirects.js diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index 4f9aa75f52105..b3fc462fd1c50 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -34,6 +34,7 @@ import { import { KibanaContextProvider } from '../../../../../kibana_react/public'; // eslint-disable-next-line import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; +import { applicationServiceMock } from '../../../../../../core/public/mocks'; let dashboardContainer: DashboardContainer | undefined; @@ -50,7 +51,7 @@ function getProps( const start = doStart(); const options: DashboardContainerOptions = { - application: {} as any, + application: applicationServiceMock.createStartContract(), embeddable: { getTriggerCompatibleActions: (() => []) as any, getEmbeddableFactories: start.getEmbeddableFactories, diff --git a/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx index 40231de7597f1..6eb85faeea014 100644 --- a/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx @@ -38,6 +38,7 @@ import { embeddablePluginMock } from '../../../../embeddable/public/mocks'; import { inspectorPluginMock } from '../../../../inspector/public/mocks'; import { KibanaContextProvider } from '../../../../kibana_react/public'; import { uiActionsPluginMock } from '../../../../ui_actions/public/mocks'; +import { applicationServiceMock } from '../../../../../core/public/mocks'; test('DashboardContainer in edit mode shows edit mode actions', async () => { const inspector = inspectorPluginMock.createStartContract(); @@ -56,7 +57,7 @@ test('DashboardContainer in edit mode shows edit mode actions', async () => { const initialInput = getSampleDashboardInput({ viewMode: ViewMode.VIEW }); const options: DashboardContainerOptions = { - application: {} as any, + application: applicationServiceMock.createStartContract(), embeddable: start, notifications: {} as any, overlays: {} as any, @@ -84,7 +85,7 @@ test('DashboardContainer in edit mode shows edit mode actions', async () => { getAllEmbeddableFactories={(() => []) as any} getEmbeddableFactory={(() => null) as any} notifications={{} as any} - application={{} as any} + application={options.application} overlays={{} as any} inspector={inspector} SavedObjectFinder={() => null} diff --git a/src/plugins/dashboard/public/dashboard_constants.ts b/src/plugins/dashboard/public/dashboard_constants.ts index 0820ebd371004..490ddbed933d9 100644 --- a/src/plugins/dashboard/public/dashboard_constants.ts +++ b/src/plugins/dashboard/public/dashboard_constants.ts @@ -18,7 +18,6 @@ */ export const DashboardConstants = { - ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM: 'addToDashboard', LANDING_PAGE_PATH: '/dashboards', CREATE_NEW_DASHBOARD_URL: '/dashboard', ADD_EMBEDDABLE_ID: 'addEmbeddableId', diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index e61ad2a6eefed..84c6eea7c4ff1 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -22,6 +22,7 @@ import './index.scss'; import { PluginInitializerContext } from 'src/core/public'; import { EmbeddablePublicPlugin } from './plugin'; +export { EMBEDDABLE_ORIGINATING_APP_PARAM } from './types'; export { ACTION_ADD_PANEL, ACTION_APPLY_FILTER, diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx index fc5438b8c8dcb..196bd593eb8d5 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx @@ -22,10 +22,12 @@ import { Embeddable, EmbeddableInput } from '../embeddables'; import { ViewMode } from '../types'; import { ContactCardEmbeddable } from '../test_samples'; import { embeddablePluginMock } from '../../mocks'; +import { applicationServiceMock } from '../../../../../core/public/mocks'; const { doStart } = embeddablePluginMock.createInstance(); const start = doStart(); const getFactory = start.getEmbeddableFactory; +const applicationMock = applicationServiceMock.createStartContract(); class EditableEmbeddable extends Embeddable { public readonly type = 'EDITABLE_EMBEDDABLE'; @@ -41,7 +43,7 @@ class EditableEmbeddable extends Embeddable { } test('is compatible when edit url is available, in edit mode and editable', async () => { - const action = new EditPanelAction(getFactory, {} as any); + const action = new EditPanelAction(getFactory, applicationMock); expect( await action.isCompatible({ embeddable: new EditableEmbeddable({ id: '123', viewMode: ViewMode.EDIT }, true), @@ -50,7 +52,7 @@ test('is compatible when edit url is available, in edit mode and editable', asyn }); test('getHref returns the edit urls', async () => { - const action = new EditPanelAction(getFactory, {} as any); + const action = new EditPanelAction(getFactory, applicationMock); expect(action.getHref).toBeDefined(); if (action.getHref) { @@ -64,7 +66,7 @@ test('getHref returns the edit urls', async () => { }); test('is not compatible when edit url is not available', async () => { - const action = new EditPanelAction(getFactory, {} as any); + const action = new EditPanelAction(getFactory, applicationMock); const embeddable = new ContactCardEmbeddable( { id: '123', @@ -83,7 +85,7 @@ test('is not compatible when edit url is not available', async () => { }); test('is not visible when edit url is available but in view mode', async () => { - const action = new EditPanelAction(getFactory, {} as any); + const action = new EditPanelAction(getFactory, applicationMock); expect( await action.isCompatible({ embeddable: new EditableEmbeddable( @@ -98,7 +100,7 @@ test('is not visible when edit url is available but in view mode', async () => { }); test('is not compatible when edit url is available, in edit mode, but not editable', async () => { - const action = new EditPanelAction(getFactory, {} as any); + const action = new EditPanelAction(getFactory, applicationMock); expect( await action.isCompatible({ embeddable: new EditableEmbeddable( diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts index d57867900c24b..d1edddb2aa86b 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts @@ -20,10 +20,11 @@ import { i18n } from '@kbn/i18n'; import { ApplicationStart } from 'kibana/public'; import { Action } from 'src/plugins/ui_actions/public'; +import { take } from 'rxjs/operators'; import { ViewMode } from '../types'; import { EmbeddableFactoryNotFoundError } from '../errors'; -import { IEmbeddable } from '../embeddables'; import { EmbeddableStart } from '../../plugin'; +import { EMBEDDABLE_ORIGINATING_APP_PARAM, IEmbeddable } from '../..'; export const ACTION_EDIT_PANEL = 'editPanel'; @@ -35,11 +36,18 @@ export class EditPanelAction implements Action { public readonly type = ACTION_EDIT_PANEL; public readonly id = ACTION_EDIT_PANEL; public order = 50; + public currentAppId: string | undefined; constructor( private readonly getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'], private readonly application: ApplicationStart - ) {} + ) { + if (this.application?.currentAppId$) { + this.application.currentAppId$ + .pipe(take(1)) + .subscribe((appId: string | undefined) => (this.currentAppId = appId)); + } + } public getDisplayName({ embeddable }: ActionContext) { const factory = this.getEmbeddableFactory(embeddable.type); @@ -93,7 +101,15 @@ export class EditPanelAction implements Action { } public async getHref({ embeddable }: ActionContext): Promise { - const editUrl = embeddable ? embeddable.getOutput().editUrl : undefined; + let editUrl = embeddable ? embeddable.getOutput().editUrl : undefined; + if (editUrl && this.currentAppId) { + editUrl += `?${EMBEDDABLE_ORIGINATING_APP_PARAM}=${this.currentAppId}`; + + // TODO: Remove this after https://github.com/elastic/kibana/pull/63443 + if (this.currentAppId === 'kibana') { + editUrl += `:${window.location.hash.split(/[\/\?]/)[1]}`; + } + } return editUrl ? editUrl : ''; } } diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 9dd4c74c624d9..384297d8dee7d 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -44,6 +44,7 @@ import { import { inspectorPluginMock } from '../../../../inspector/public/mocks'; import { EuiBadge } from '@elastic/eui'; import { embeddablePluginMock } from '../../mocks'; +import { applicationServiceMock } from '../../../../../core/public/mocks'; const actionRegistry = new Map (); const triggerRegistry = new Map (); @@ -55,6 +56,7 @@ const trigger: Trigger = { id: CONTEXT_MENU_TRIGGER, }; const embeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); +const applicationMock = applicationServiceMock.createStartContract(); actionRegistry.set(editModeAction.id, editModeAction); triggerRegistry.set(trigger.id, trigger); @@ -159,7 +161,7 @@ test('HelloWorldContainer in view mode hides edit mode actions', async () => { getAllEmbeddableFactories={start.getEmbeddableFactories} getEmbeddableFactory={start.getEmbeddableFactory} notifications={{} as any} - application={{} as any} + application={applicationMock} overlays={{} as any} inspector={inspector} SavedObjectFinder={() => null} @@ -199,7 +201,7 @@ const renderInEditModeAndOpenContextMenu = async ( getEmbeddableFactory={start.getEmbeddableFactory} notifications={{} as any} overlays={{} as any} - application={{} as any} + application={applicationMock} inspector={inspector} SavedObjectFinder={() => null} /> @@ -306,7 +308,7 @@ test('HelloWorldContainer in edit mode shows edit mode actions', async () => { getEmbeddableFactory={start.getEmbeddableFactory} notifications={{} as any} overlays={{} as any} - application={{} as any} + application={applicationMock} inspector={inspector} SavedObjectFinder={() => null} /> @@ -369,7 +371,7 @@ test('Updates when hidePanelTitles is toggled', async () => { getEmbeddableFactory={start.getEmbeddableFactory} notifications={{} as any} overlays={{} as any} - application={{} as any} + application={applicationMock} inspector={inspector} SavedObjectFinder={() => null} /> @@ -422,7 +424,7 @@ test('Check when hide header option is false', async () => { getEmbeddableFactory={start.getEmbeddableFactory} notifications={{} as any} overlays={{} as any} - application={{} as any} + application={applicationMock} inspector={inspector} SavedObjectFinder={() => null} hideHeader={false} diff --git a/src/plugins/embeddable/public/types.ts b/src/plugins/embeddable/public/types.ts index 2d112b2359818..a57af862f2a34 100644 --- a/src/plugins/embeddable/public/types.ts +++ b/src/plugins/embeddable/public/types.ts @@ -26,6 +26,8 @@ import { EmbeddableFactoryDefinition, } from './lib/embeddables'; +export const EMBEDDABLE_ORIGINATING_APP_PARAM = 'embeddableOriginatingApp'; + export type EmbeddableFactoryRegistry = Map ; export type EmbeddableFactoryProvider = < diff --git a/src/plugins/saved_objects/public/index.ts b/src/plugins/saved_objects/public/index.ts index 9e0a7c40c043f..e38a0ef9830ea 100644 --- a/src/plugins/saved_objects/public/index.ts +++ b/src/plugins/saved_objects/public/index.ts @@ -19,7 +19,14 @@ import { SavedObjectsPublicPlugin } from './plugin'; -export { OnSaveProps, SavedObjectSaveModal, SaveResult, showSaveModal } from './save_modal'; +export { + OnSaveProps, + SavedObjectSaveModal, + SavedObjectSaveModalOrigin, + SaveModalState, + SaveResult, + showSaveModal, +} from './save_modal'; export { getSavedObjectFinder, SavedObjectFinderUi, SavedObjectMetaData } from './finder'; export { SavedObjectLoader, diff --git a/src/plugins/saved_objects/public/save_modal/index.ts b/src/plugins/saved_objects/public/save_modal/index.ts index f26aa732f30a1..7c32337bb314a 100644 --- a/src/plugins/saved_objects/public/save_modal/index.ts +++ b/src/plugins/saved_objects/public/save_modal/index.ts @@ -17,5 +17,6 @@ * under the License. */ -export { SavedObjectSaveModal, OnSaveProps } from './saved_object_save_modal'; +export { SavedObjectSaveModal, OnSaveProps, SaveModalState } from './saved_object_save_modal'; +export { SavedObjectSaveModalOrigin } from './saved_object_save_modal_origin'; export { showSaveModal, SaveResult } from './show_saved_object_save_modal'; diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx index 95eb56c0e874b..962f993633e6f 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx @@ -53,14 +53,15 @@ interface Props { onClose: () => void; title: string; showCopyOnSave: boolean; + initialCopyOnSave?: boolean; objectType: string; confirmButtonLabel?: React.ReactNode; - options?: React.ReactNode; + options?: React.ReactNode | ((state: SaveModalState) => React.ReactNode); description?: string; showDescription: boolean; } -interface State { +export interface SaveModalState { title: string; copyOnSave: boolean; isTitleDuplicateConfirmed: boolean; @@ -71,11 +72,11 @@ interface State { const generateId = htmlIdGenerator(); -export class SavedObjectSaveModal extends React.Component { +export class SavedObjectSaveModal extends React.Component { private warning = React.createRef (); public readonly state = { title: this.props.title, - copyOnSave: false, + copyOnSave: Boolean(this.props.initialCopyOnSave), isTitleDuplicateConfirmed: false, hasTitleDuplicate: false, isLoading: false, @@ -139,7 +140,9 @@ export class SavedObjectSaveModal extends React.Component { {this.renderViewDescription()} - {this.props.options} + {typeof this.props.options === 'function' + ? this.props.options(this.state) + : this.props.options} diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx new file mode 100644 index 0000000000000..34f4bc593fdc4 --- /dev/null +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx @@ -0,0 +1,117 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { Fragment, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFormRow, EuiSwitch } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { OnSaveProps, SaveModalState, SavedObjectSaveModal } from '.'; + +interface SaveModalDocumentInfo { + id?: string; + title: string; + description?: string; +} + +interface OriginSaveModalProps { + originatingApp?: string; + documentInfo: SaveModalDocumentInfo; + objectType: string; + onClose: () => void; + onSave: (props: OnSaveProps & { returnToOrigin: boolean }) => void; +} + +export function SavedObjectSaveModalOrigin(props: OriginSaveModalProps) { + const [returnToOriginMode, setReturnToOriginMode] = useState(Boolean(props.originatingApp)); + const { documentInfo } = props; + + const returnLabel = i18n.translate('savedObjects.saveModalOrigin.returnToOriginLabel', { + defaultMessage: 'Return', + }); + const addLabel = i18n.translate('savedObjects.saveModalOrigin.addToOriginLabel', { + defaultMessage: 'Add', + }); + + const getReturnToOriginSwitch = (state: SaveModalState) => { + if (!props.originatingApp) { + return; + } + let origin = props.originatingApp!; + + // TODO: Remove this after https://github.com/elastic/kibana/pull/63443 + if (origin.startsWith('kibana:')) { + origin = origin.split(':')[1]; + } + + if ( + !state.copyOnSave || + origin === 'dashboard' // dashboard supports adding a copied panel on save... + ) { + const originVerb = !documentInfo.id || state.copyOnSave ? addLabel : returnLabel; + return ( + + + ); + } else { + setReturnToOriginMode(false); + } + }; + + const onModalSave = (onSaveProps: OnSaveProps) => { + props.onSave({ ...onSaveProps, returnToOrigin: returnToOriginMode }); + }; + + const confirmButtonLabel = returnToOriginMode + ? i18n.translate('savedObjects.saveModalOrigin.saveAndReturnLabel', { + defaultMessage: 'Save and return', + }) + : null; + + return ( ++ +{ + setReturnToOriginMode(event.target.checked); + }} + label={ + + } + /> + + ); +} diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index 6ab1c98645988..c6d43a4ef2f80 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -19,12 +19,14 @@ import { i18n } from '@kbn/i18n'; import { SavedObjectMetaData } from 'src/plugins/saved_objects/public'; +import { first } from 'rxjs/operators'; import { SavedObjectAttributes } from '../../../../core/public'; import { EmbeddableFactoryDefinition, EmbeddableOutput, ErrorEmbeddable, IContainer, + EMBEDDABLE_ORIGINATING_APP_PARAM, } from '../../../embeddable/public'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable'; @@ -59,6 +61,7 @@ export class VisualizeEmbeddableFactory VisualizationAttributes > { public readonly type = VISUALIZE_EMBEDDABLE_TYPE; + public readonly savedObjectMetaData: SavedObjectMetaData = { name: i18n.translate('visualizations.savedObjectName', { defaultMessage: 'Visualization' }), includeFields: ['visState'], @@ -98,6 +101,18 @@ export class VisualizeEmbeddableFactory }); } + public async getCurrentAppId() { + let currentAppId = await this.deps + .start() + .core.application.currentAppId$.pipe(first()) + .toPromise(); + // TODO: Remove this after https://github.com/elastic/kibana/pull/63443 + if (currentAppId === 'kibana') { + currentAppId += `:${window.location.hash.split(/[\/\?]/)[1]}`; + } + return currentAppId; + } + public async createFromSavedObject( savedObjectId: string, input: Partial & { id: string }, @@ -118,8 +133,9 @@ export class VisualizeEmbeddableFactory public async create() { // TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up // to allow for in place creation of visualizations without having to navigate away to a new URL. + const originatingAppParam = await this.getCurrentAppId(); showNewVisModal({ - editorParams: ['addToDashboard'], + editorParams: [`${EMBEDDABLE_ORIGINATING_APP_PARAM}=${originatingAppParam}`], }); return undefined; } diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts index d6eeffdb01459..70c3bc2c1ed05 100644 --- a/src/plugins/visualizations/public/mocks.ts +++ b/src/plugins/visualizations/public/mocks.ts @@ -20,7 +20,7 @@ import { PluginInitializerContext } from '../../../core/public'; import { VisualizationsSetup, VisualizationsStart } from './'; import { VisualizationsPlugin } from './plugin'; -import { coreMock } from '../../../core/public/mocks'; +import { coreMock, applicationServiceMock } from '../../../core/public/mocks'; import { embeddablePluginMock } from '../../../plugins/embeddable/public/mocks'; import { expressionsPluginMock } from '../../../plugins/expressions/public/mocks'; import { dataPluginMock } from '../../../plugins/data/public/mocks'; @@ -65,6 +65,7 @@ const createInstance = async () => { expressions: expressionsPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), uiActions: uiActionsPluginMock.createStartContract(), + application: applicationServiceMock.createStartContract(), }); return { diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index b3e8c9b5b61b3..29d66ea963a66 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -17,7 +17,13 @@ * under the License. */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + ApplicationStart, +} from '../../../core/public'; import { TypesService, TypesSetup, TypesStart } from './vis_types'; import { setUISettings, @@ -95,6 +101,7 @@ export interface VisualizationsStartDeps { expressions: ExpressionsStart; inspector: InspectorStart; uiActions: UiActionsStart; + application: ApplicationStart; } /** @@ -131,7 +138,6 @@ export class VisualizationsPlugin expressions.registerRenderer(visualizationRenderer); expressions.registerFunction(rangeExpressionFunction); expressions.registerFunction(visDimensionExpressionFunction); - const embeddableFactory = new VisualizeEmbeddableFactory({ start }); embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx index 5637aeafc6f14..2fdbdedd5b590 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx @@ -144,7 +144,7 @@ describe('NewVisModal', () => { isOpen={true} onClose={onClose} visTypesRegistry={visTypes} - editorParams={['foo=true', 'bar=42', 'addToDashboard']} + editorParams={['foo=true', 'bar=42', 'embeddableOriginatingApp=notAnApp']} addBasePath={addBasePath} uiSettings={uiSettings} savedObjects={{} as SavedObjectsStart} @@ -152,7 +152,9 @@ describe('NewVisModal', () => { ); const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]'); visButton.simulate('click'); - expect(window.location.assign).toBeCalledWith('testbasepath/aliasUrl?addToDashboard'); + expect(window.location.assign).toBeCalledWith( + 'testbasepath/aliasUrl?embeddableOriginatingApp=notAnApp' + ); expect(onClose).toHaveBeenCalled(); }); diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx index 448077819bb8d..6fd65da7e88d2 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -28,6 +28,7 @@ import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; import { TypesStart, VisType, VisTypeAlias } from '../vis_types'; import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; +import { EMBEDDABLE_ORIGINATING_APP_PARAM } from '../../../embeddable/public'; interface TypeSelectionProps { isOpen: boolean; @@ -143,8 +144,11 @@ class NewVisModal extends React.Component + param.startsWith(EMBEDDABLE_ORIGINATING_APP_PARAM) + ); + params = originatingAppParam ? `${params}?${originatingAppParam}` : params; } this.props.onClose(); window.location.assign(params); diff --git a/src/plugins/visualize/public/application/editor/editor.js b/src/plugins/visualize/public/application/editor/editor.js index 9ec411a7744bf..bd699c762371c 100644 --- a/src/plugins/visualize/public/application/editor/editor.js +++ b/src/plugins/visualize/public/application/editor/editor.js @@ -25,11 +25,12 @@ import { i18n } from '@kbn/i18n'; import { EventEmitter } from 'events'; import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; import { makeStateful, useVisualizeAppState, addEmbeddableToDashboardUrl } from './lib'; import { VisualizeConstants } from '../visualize_constants'; import { getEditBreadcrumbs } from '../breadcrumbs'; +import { EMBEDDABLE_ORIGINATING_APP_PARAM } from '../../../../embeddable/public'; + import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; import { unhashUrl, removeQueryParam } from '../../../../kibana_utils/public'; import { MarkdownSimple, toMountPoint } from '../../../../kibana_react/public'; @@ -38,9 +39,8 @@ import { subscribeWithScope, migrateLegacyQuery, } from '../../../../kibana_legacy/public'; -import { SavedObjectSaveModal, showSaveModal } from '../../../../saved_objects/public'; +import { showSaveModal, SavedObjectSaveModalOrigin } from '../../../../saved_objects/public'; import { esFilters, connectToQueryState, syncQueryStateWithUrl } from '../../../../data/public'; -import { DashboardConstants } from '../../../../dashboard/public'; import { initVisEditorDirective } from './visualization_editor'; import { initVisualizationDirective } from './visualization'; @@ -110,6 +110,11 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'), }; + const originatingApp = $route.current.params[EMBEDDABLE_ORIGINATING_APP_PARAM]; + removeQueryParam(history, EMBEDDABLE_ORIGINATING_APP_PARAM); + + $scope.getOriginatingApp = () => originatingApp; + const visStateToEditorState = () => { const savedVisState = visualizations.convertFromSerializedVis(vis.serialize()); return { @@ -144,13 +149,58 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState $scope.embeddableHandler = embeddableHandler; $scope.topNavMenu = [ + ...($scope.getOriginatingApp() && savedVis.id + ? [ + { + id: 'saveAndReturn', + label: i18n.translate('visualize.topNavMenu.saveAndReturnVisualizationButtonLabel', { + defaultMessage: 'Save and return', + }), + emphasize: true, + iconType: 'check', + description: i18n.translate( + 'visualize.topNavMenu.saveAndReturnVisualizationButtonAriaLabel', + { + defaultMessage: 'Finish editing visualization and return to the last app', + } + ), + testId: 'visualizesaveAndReturnButton', + disableButton() { + return Boolean($scope.dirty); + }, + tooltip() { + if ($scope.dirty) { + return i18n.translate( + 'visualize.topNavMenu.saveAndReturnVisualizationDisabledButtonTooltip', + { + defaultMessage: 'Apply or Discard your changes before finishing', + } + ); + } + }, + run: async () => { + const saveOptions = { + confirmOverwrite: false, + returnToOrigin: true, + }; + return doSave(saveOptions); + }, + }, + ] + : []), ...(visualizeCapabilities.save ? [ { id: 'save', - label: i18n.translate('visualize.topNavMenu.saveVisualizationButtonLabel', { - defaultMessage: 'save', - }), + label: + savedVis.id && $scope.getOriginatingApp() + ? i18n.translate('visualize.topNavMenu.saveVisualizationAsButtonLabel', { + defaultMessage: 'save as', + }) + : i18n.translate('visualize.topNavMenu.saveVisualizationButtonLabel', { + defaultMessage: 'save', + }), + emphasize: !savedVis.id || !$scope.getOriginatingApp(), description: i18n.translate('visualize.topNavMenu.saveVisualizationButtonAriaLabel', { defaultMessage: 'Save Visualization', }), @@ -175,6 +225,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState isTitleDuplicateConfirmed, onTitleDuplicate, newDescription, + returnToOrigin, }) => { const currentTitle = savedVis.title; savedVis.title = newTitle; @@ -184,6 +235,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState confirmOverwrite: false, isTitleDuplicateConfirmed, onTitleDuplicate, + returnToOrigin, }; return doSave(saveOptions).then(response => { // If the save wasn't successful, put the original values back. @@ -194,23 +246,13 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState }); }; - const confirmButtonLabel = $scope.isAddToDashMode() ? ( - - ) : null; - const saveModal = ( - {}} - title={savedVis.title} - showCopyOnSave={savedVis.id ? true : false} - objectType="visualization" - confirmButtonLabel={confirmButtonLabel} - description={savedVis.description} - showDescription={true} + originatingApp={$scope.getOriginatingApp()} /> ); showSaveModal(saveModal, I18nContext); @@ -398,12 +440,6 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState $scope.refreshInterval = timefilter.getRefreshInterval(); handleLinkedSearch(initialState.linked); - const addToDashMode = - $route.current.params[DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM]; - removeQueryParam(history, DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM); - - $scope.isAddToDashMode = () => addToDashMode; - $scope.showFilterBar = () => { return vis.type.options.showFilterBar; }; @@ -604,6 +640,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState */ function doSave(saveOptions) { // vis.title was not bound and it's needed to reflect title into visState + const firstSave = !Boolean(savedVis.id); stateContainer.transitions.setVis({ title: savedVis.title, type: savedVis.type || stateContainer.getState().vis.type, @@ -631,15 +668,23 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState 'data-test-subj': 'saveVisualizationSuccess', }); - if ($scope.isAddToDashMode()) { + if ($scope.getOriginatingApp() && saveOptions.returnToOrigin) { const appPath = `${VisualizeConstants.EDIT_PATH}/${encodeURIComponent(savedVis.id)}`; + // Manually insert a new url so the back button will open the saved visualization. history.replace(appPath); setActiveUrl(appPath); - - const lastDashboardUrl = chrome.navLinks.get('kibana:dashboard').url; - const dashboardUrl = addEmbeddableToDashboardUrl(lastDashboardUrl, savedVis.id); - history.push(dashboardUrl); + const lastAppType = $scope.getOriginatingApp(); + let href = chrome.navLinks.get(lastAppType).url; + + // TODO: Remove this and use application.redirectTo after https://github.com/elastic/kibana/pull/63443 + if (lastAppType === 'kibana:dashboard') { + const savedVisId = firstSave || savedVis.copyOnSave ? savedVis.id : ''; + href = addEmbeddableToDashboardUrl(href, savedVisId); + history.push(href); + } else { + window.location.href = href; + } } else if (savedVis.id === $route.current.params.id) { chrome.docTitle.change(savedVis.lastSavedTitle); chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); diff --git a/src/plugins/visualize/public/application/editor/lib/url_helper.ts b/src/plugins/visualize/public/application/editor/lib/url_helper.ts index 84e1ef9687cd0..9f8a0075118ae 100644 --- a/src/plugins/visualize/public/application/editor/lib/url_helper.ts +++ b/src/plugins/visualize/public/application/editor/lib/url_helper.ts @@ -33,8 +33,10 @@ export function addEmbeddableToDashboardUrl(dashboardUrl: string, embeddableId: const { url, query } = parseUrl(dashboardUrl); const [, dashboardId] = url.split(DashboardConstants.CREATE_NEW_DASHBOARD_URL); - query[DashboardConstants.ADD_EMBEDDABLE_TYPE] = VISUALIZE_EMBEDDABLE_TYPE; - query[DashboardConstants.ADD_EMBEDDABLE_ID] = embeddableId; + if (embeddableId) { + query[DashboardConstants.ADD_EMBEDDABLE_TYPE] = VISUALIZE_EMBEDDABLE_TYPE; + query[DashboardConstants.ADD_EMBEDDABLE_ID] = embeddableId; + } return `${DashboardConstants.CREATE_NEW_DASHBOARD_URL}${dashboardId}?${stringify(query)}`; } diff --git a/test/functional/apps/dashboard/create_and_add_embeddables.js b/test/functional/apps/dashboard/create_and_add_embeddables.js index 410acdcb5680d..8180051f56e44 100644 --- a/test/functional/apps/dashboard/create_and_add_embeddables.js +++ b/test/functional/apps/dashboard/create_and_add_embeddables.js @@ -48,7 +48,8 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess( - 'visualization from top nav add new panel' + 'visualization from top nav add new panel', + { redirectToOrigin: true } ); await retry.try(async () => { const panelCount = await PageObjects.dashboard.getPanelCount(); @@ -64,7 +65,8 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess( - 'visualization from add new link' + 'visualization from add new link', + { redirectToOrigin: true } ); await retry.try(async () => { diff --git a/test/functional/apps/dashboard/edit_embeddable_redirects.js b/test/functional/apps/dashboard/edit_embeddable_redirects.js new file mode 100644 index 0000000000000..b45dcc2cedf9b --- /dev/null +++ b/test/functional/apps/dashboard/edit_embeddable_redirects.js @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import expect from '@kbn/expect'; + +export default function({ getService, getPageObjects }) { + const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'settings', 'common']); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const dashboardPanelActions = getService('dashboardPanelActions'); + + describe('edit embeddable redirects', () => { + before(async () => { + await esArchiver.load('dashboard/current/kibana'); + await kibanaServer.uiSettings.replace({ + defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', + }); + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.preserveCrossAppState(); + await PageObjects.dashboard.loadSavedDashboard('few panels'); + await PageObjects.dashboard.switchToEditMode(); + }); + + it('redirects via save and return button after edit', async () => { + await dashboardPanelActions.openContextMenu(); + await dashboardPanelActions.clickEdit(); + await PageObjects.visualize.saveVisualizationAndReturn(); + }); + + it('redirects via save as button after edit, renaming itself', async () => { + const newTitle = 'wowee, looks like I have a new title'; + const originalPanelCount = await PageObjects.dashboard.getPanelCount(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await dashboardPanelActions.openContextMenu(); + await dashboardPanelActions.clickEdit(); + await PageObjects.visualize.saveVisualizationExpectSuccess(newTitle, { + saveAsNew: false, + redirectToOrigin: true, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + const newPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(newPanelCount).to.eql(originalPanelCount); + const titles = await PageObjects.dashboard.getPanelTitles(); + expect(titles.indexOf(newTitle)).to.not.be(-1); + }); + + it('redirects via save as button after edit, adding a new panel', async () => { + const newTitle = 'wowee, my title just got cooler'; + const originalPanelCount = await PageObjects.dashboard.getPanelCount(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await dashboardPanelActions.openContextMenu(); + await dashboardPanelActions.clickEdit(); + await PageObjects.visualize.saveVisualizationExpectSuccess(newTitle, { + saveAsNew: true, + redirectToOrigin: true, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + const newPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(newPanelCount).to.eql(originalPanelCount + 1); + const titles = await PageObjects.dashboard.getPanelTitles(); + expect(titles.indexOf(newTitle)).to.not.be(-1); + }); + }); +} diff --git a/test/functional/apps/dashboard/index.js b/test/functional/apps/dashboard/index.js index bd8e6812147e1..3b81a4d974bec 100644 --- a/test/functional/apps/dashboard/index.js +++ b/test/functional/apps/dashboard/index.js @@ -51,6 +51,7 @@ export default function({ getService, loadTestFile }) { loadTestFile(require.resolve('./empty_dashboard')); loadTestFile(require.resolve('./embeddable_rendering')); loadTestFile(require.resolve('./create_and_add_embeddables')); + loadTestFile(require.resolve('./edit_embeddable_redirects')); loadTestFile(require.resolve('./time_zones')); loadTestFile(require.resolve('./dashboard_options')); loadTestFile(require.resolve('./data_shared_attributes')); diff --git a/test/functional/apps/dashboard/view_edit.js b/test/functional/apps/dashboard/view_edit.js index a0b972f3ab63c..c8eb10d43ea83 100644 --- a/test/functional/apps/dashboard/view_edit.js +++ b/test/functional/apps/dashboard/view_edit.js @@ -136,7 +136,10 @@ export default function({ getService, getPageObjects }) { await dashboardAddPanel.clickAddNewEmbeddableLink('visualization'); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); - await PageObjects.visualize.saveVisualizationExpectSuccess('new viz panel'); + await PageObjects.visualize.saveVisualizationExpectSuccess('new viz panel', { + saveAsNew: false, + redirectToOrigin: true, + }); await PageObjects.dashboard.clickCancelOutOfEditMode(); // for this sleep see https://github.com/elastic/kibana/issues/22299 diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 220c2d8f6b363..8fa15fc8268ed 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -300,12 +300,25 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide } } - public async saveVisualization(vizName: string, { saveAsNew = false } = {}) { + public async saveVisualization( + vizName: string, + { saveAsNew = false, redirectToOrigin = false } = {} + ) { await this.ensureSavePanelOpen(); await testSubjects.setValue('savedObjectTitle', vizName); - if (saveAsNew) { - log.debug('Check save as new visualization'); - await testSubjects.click('saveAsNewCheckbox'); + + const saveAsNewCheckboxExists = await testSubjects.exists('saveAsNewCheckbox'); + if (saveAsNewCheckboxExists) { + const state = saveAsNew ? 'check' : 'uncheck'; + log.debug('save as new checkbox exists. Setting its state to', state); + await testSubjects.setEuiSwitch('saveAsNewCheckbox', state); + } + + const redirectToOriginCheckboxExists = await testSubjects.exists('returnToOriginModeSwitch'); + if (redirectToOriginCheckboxExists) { + const state = redirectToOrigin ? 'check' : 'uncheck'; + log.debug('redirect to origin checkbox exists. Setting its state to', state); + await testSubjects.setEuiSwitch('returnToOriginModeSwitch', state); } log.debug('Click Save Visualization button'); @@ -320,8 +333,11 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide return message; } - public async saveVisualizationExpectSuccess(vizName: string, { saveAsNew = false } = {}) { - const saveMessage = await this.saveVisualization(vizName, { saveAsNew }); + public async saveVisualizationExpectSuccess( + vizName: string, + { saveAsNew = false, redirectToOrigin = false } = {} + ) { + const saveMessage = await this.saveVisualization(vizName, { saveAsNew, redirectToOrigin }); if (!saveMessage) { throw new Error( `Expected saveVisualization to respond with the saveMessage from the toast, got ${saveMessage}` @@ -331,14 +347,20 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide public async saveVisualizationExpectSuccessAndBreadcrumb( vizName: string, - { saveAsNew = false } = {} + { saveAsNew = false, redirectToOrigin = false } = {} ) { - await this.saveVisualizationExpectSuccess(vizName, { saveAsNew }); + await this.saveVisualizationExpectSuccess(vizName, { saveAsNew, redirectToOrigin }); await retry.waitFor( 'last breadcrumb to have new vis name', async () => (await globalNav.getLastBreadcrumb()) === vizName ); } + + public async saveVisualizationAndReturn() { + await header.waitUntilLoadingHasFinished(); + await testSubjects.existOrFail('visualizesaveAndReturnButton'); + await testSubjects.click('visualizesaveAndReturnButton'); + } } return new VisualizePage(); diff --git a/test/functional/services/dashboard/visualizations.js b/test/functional/services/dashboard/visualizations.js index f7a6fb7d2f694..676e4c384fe36 100644 --- a/test/functional/services/dashboard/visualizations.js +++ b/test/functional/services/dashboard/visualizations.js @@ -116,7 +116,10 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { await PageObjects.visualize.clickMarkdownWidget(); await PageObjects.visEditor.setMarkdownTxt(markdown); await PageObjects.visEditor.clickGo(); - await PageObjects.visualize.saveVisualizationExpectSuccess(name); + await PageObjects.visualize.saveVisualizationExpectSuccess(name, { + saveAsNew: false, + redirectToOrigin: true, + }); } })(); } diff --git a/test/functional/services/test_subjects.ts b/test/functional/services/test_subjects.ts index e5c2e61c48a0b..090dc995ddc11 100644 --- a/test/functional/services/test_subjects.ts +++ b/test/functional/services/test_subjects.ts @@ -307,6 +307,7 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) { await element.scrollIntoViewIfNecessary(); } + // isChecked always returns false when run on an euiSwitch, because they use the aria-checked attribute public async isChecked(selector: string) { const checkbox = await this.find(selector); return await checkbox.isSelected(); @@ -316,7 +317,22 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) { const isChecked = await this.isChecked(selector); const states = { check: true, uncheck: false }; if (isChecked !== states[state]) { - log.debug(`updating checkbox ${selector}`); + log.debug(`updating checkbox ${selector} from ${isChecked} to ${states[state]}`); + await this.click(selector); + } + } + + public async isEuiSwitchChecked(selector: string) { + const euiSwitch = await this.find(selector); + const isChecked = await euiSwitch.getAttribute('aria-checked'); + return isChecked === 'true'; + } + + public async setEuiSwitch(selector: string, state: 'check' | 'uncheck') { + const isChecked = await this.isEuiSwitchChecked(selector); + const states = { check: true, uncheck: false }; + if (isChecked !== states[state]) { + log.debug(`updating checkbox ${selector} from ${isChecked} to ${states[state]}`); await this.click(selector); } } diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 41d0e3a7aa9a0..888854a4e83b8 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -104,8 +104,8 @@ describe('Lens App', () => { storage: Storage; docId?: string; docStorage: SavedObjectStore; - redirectTo: (id?: string) => void; - addToDashboardMode?: boolean; + redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; + originatingApp: string | undefined; }> { return ({ navigation: navigationStartMock, @@ -140,7 +140,7 @@ describe('Lens App', () => { load: jest.fn(), save: jest.fn(), }, - redirectTo: jest.fn(id => {}), + redirectTo: jest.fn((id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => {}), } as unknown) as jest.Mocked<{ navigation: typeof navigationStartMock; editorFrame: EditorFrameInstance; @@ -149,8 +149,8 @@ describe('Lens App', () => { storage: Storage; docId?: string; docStorage: SavedObjectStore; - redirectTo: (id?: string) => void; - addToDashboardMode?: boolean; + redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; + originatingApp: string | undefined; }>; } @@ -336,6 +336,7 @@ describe('Lens App', () => { describe('save button', () => { interface SaveProps { newCopyOnSave: boolean; + returnToOrigin?: boolean; newTitle: string; } @@ -347,8 +348,8 @@ describe('Lens App', () => { storage: Storage; docId?: string; docStorage: SavedObjectStore; - redirectTo: (id?: string) => void; - addToDashboardMode?: boolean; + redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; + originatingApp: string | undefined; }>; beforeEach(() => { @@ -374,32 +375,25 @@ describe('Lens App', () => { async function testSave(inst: ReactWrapper, saveProps: SaveProps) { await getButton(inst).run(inst.getDOMNode()); - inst.update(); - - const handler = inst.findWhere(el => el.prop('onSave')).prop('onSave') as ( + const handler = inst.find('[data-test-subj="lnsApp_saveModalOrigin"]').prop('onSave') as ( p: unknown ) => void; handler(saveProps); } async function save({ - initialDocId, - addToDashboardMode, lastKnownDoc = { expression: 'kibana 3' }, + initialDocId, ...saveProps }: SaveProps & { lastKnownDoc?: object; initialDocId?: string; - addToDashboardMode?: boolean; }) { const args = { ...defaultArgs, docId: initialDocId, }; - if (addToDashboardMode) { - args.addToDashboardMode = addToDashboardMode; - } args.editorFrame = frame; (args.docStorage.load as jest.Mock).mockResolvedValue({ id: '1234', @@ -438,7 +432,7 @@ describe('Lens App', () => { expect(getButton(instance).disableButton).toEqual(false); await act(async () => { - testSave(instance, saveProps); + testSave(instance, { ...saveProps }); }); return { args, instance }; @@ -527,7 +521,7 @@ describe('Lens App', () => { expression: 'kibana 3', }); - expect(args.redirectTo).toHaveBeenCalledWith('aaa'); + expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, true); inst.setProps({ docId: 'aaa' }); @@ -547,7 +541,7 @@ describe('Lens App', () => { expression: 'kibana 3', }); - expect(args.redirectTo).toHaveBeenCalledWith('aaa'); + expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, true); inst.setProps({ docId: 'aaa' }); @@ -601,10 +595,10 @@ describe('Lens App', () => { expect(getButton(instance).disableButton).toEqual(false); }); - it('saves new doc and redirects to dashboard', async () => { + it('saves new doc and redirects to originating app', async () => { const { args } = await save({ initialDocId: undefined, - addToDashboardMode: true, + returnToOrigin: true, newCopyOnSave: false, newTitle: 'hello there', }); @@ -615,7 +609,7 @@ describe('Lens App', () => { title: 'hello there', }); - expect(args.redirectTo).toHaveBeenCalledWith('aaa'); + expect(args.redirectTo).toHaveBeenCalledWith('aaa', true, true); }); it('saves app filters and does not save pinned filters', async () => { @@ -666,7 +660,6 @@ describe('Lens App', () => { }) ); instance.update(); - await act(async () => getButton(instance).run(instance.getDOMNode())); instance.update(); @@ -684,7 +677,7 @@ describe('Lens App', () => { storage: Storage; docId?: string; docStorage: SavedObjectStore; - redirectTo: (id?: string) => void; + redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; }>; beforeEach(() => { diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 28135dd12a724..6b8248fa2030b 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -12,9 +12,11 @@ import { Query, DataPublicPluginStart } from 'src/plugins/data/public'; import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; import { AppMountContext, NotificationsStart } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; -import { FormattedMessage } from '@kbn/i18n/react'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; -import { SavedObjectSaveModal } from '../../../../../src/plugins/saved_objects/public'; +import { + SavedObjectSaveModalOrigin, + OnSaveProps, +} from '../../../../../src/plugins/saved_objects/public'; import { Document, SavedObjectStore } from '../persistence'; import { EditorFrameInstance } from '../types'; import { NativeRenderer } from '../native_renderer'; @@ -52,7 +54,7 @@ export function App({ docId, docStorage, redirectTo, - addToDashboardMode, + originatingApp, navigation, }: { editorFrame: EditorFrameInstance; @@ -62,8 +64,8 @@ export function App({ storage: IStorageWrapper; docId?: string; docStorage: SavedObjectStore; - redirectTo: (id?: string) => void; - addToDashboardMode?: boolean; + redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; + originatingApp?: string | undefined; }) { const language = storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage'); @@ -182,6 +184,63 @@ export function App({ lastKnownDoc.expression.length > 0 && core.application.capabilities.visualize.save; + const runSave = ( + saveProps: Omit & { + returnToOrigin: boolean; + } + ) => { + if (!lastKnownDoc) { + return; + } + const [pinnedFilters, appFilters] = _.partition( + lastKnownDoc.state?.filters, + esFilters.isFilterPinned + ); + const lastDocWithoutPinned = pinnedFilters?.length + ? { + ...lastKnownDoc, + state: { + ...lastKnownDoc.state, + filters: appFilters, + }, + } + : lastKnownDoc; + + const doc = { + ...lastDocWithoutPinned, + id: saveProps.newCopyOnSave ? undefined : lastKnownDoc.id, + title: saveProps.newTitle, + }; + + const newlyCreated: boolean = saveProps.newCopyOnSave || !lastKnownDoc?.id; + docStorage + .save(doc) + .then(({ id }) => { + // Prevents unnecessary network request and disables save button + const newDoc = { ...doc, id }; + setState(s => ({ + ...s, + isSaveModalVisible: false, + persistedDoc: newDoc, + lastKnownDoc: newDoc, + })); + if (docId !== id || saveProps.returnToOrigin) { + redirectTo(id, saveProps.returnToOrigin, newlyCreated); + } + }) + .catch(e => { + // eslint-disable-next-line no-console + console.dir(e); + trackUiEvent('save_failed'); + core.notifications.toasts.addDanger( + i18n.translate('xpack.lens.app.docSavingError', { + defaultMessage: 'Error saving document', + }) + ); + setState(s => ({ ...s, isSaveModalVisible: false })); + }); + }; + const onError = useCallback( (e: { message: string }) => core.notifications.toasts.addDanger({ @@ -192,13 +251,6 @@ export function App({ const { TopNavMenu } = navigation.ui; - const confirmButton = addToDashboardMode ? ( - - ) : null; - return ( { + if (isSaveable && lastKnownDoc) { + runSave({ + newTitle: lastKnownDoc.title, + newCopyOnSave: false, + isTitleDuplicateConfirmed: false, + returnToOrigin: true, + }); + } + }, + testId: 'lnsApp_saveAndReturnButton', + disableButton: !isSaveable, + }, + ] + : []), { - label: i18n.translate('xpack.lens.app.save', { - defaultMessage: 'Save', - }), + label: + lastKnownDoc?.id && !!originatingApp + ? i18n.translate('xpack.lens.app.saveAs', { + defaultMessage: 'Save as', + }) + : i18n.translate('xpack.lens.app.save', { + defaultMessage: 'Save', + }), + emphasize: !originatingApp || !lastKnownDoc?.id, run: () => { if (isSaveable && lastKnownDoc) { setState(s => ({ ...s, isSaveModalVisible: true })); @@ -336,63 +417,18 @@ export function App({ )}
never
| |
+
+Returns:
+
+`never`
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.deepfreeze.md b/docs/development/core/public/kibana-plugin-core-public.deepfreeze.md
new file mode 100644
index 0000000000000..7c879b659a852
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.deepfreeze.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [deepFreeze](./kibana-plugin-core-public.deepfreeze.md)
+
+## deepFreeze() function
+
+Apply Object.freeze to a value recursively and convert the return type to Readonly variant recursively
+
+Signature:
+
+```typescript
+export declare function deepFreezeT
| |
+
+Returns:
+
+`RecursiveReadonlyRecord<string, any>
| |
+
+Returns:
+
+`{
+ [key: string]: any;
+}`
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.isrelativeurl.md b/docs/development/core/public/kibana-plugin-core-public.isrelativeurl.md
new file mode 100644
index 0000000000000..3c2ffa6340a97
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.isrelativeurl.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [isRelativeUrl](./kibana-plugin-core-public.isrelativeurl.md)
+
+## isRelativeUrl() function
+
+Determine if a url is relative. Any url including a protocol, hostname, or port is not considered relative. This means that absolute \*paths\* are considered to be relative \*urls\*
+
+Signature:
+
+```typescript
+export declare function isRelativeUrl(candidatePath: string): boolean;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| candidatePath | string
| |
+
+Returns:
+
+`boolean`
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md
index adc87de2b9e7e..c24e4cf908b87 100644
--- a/docs/development/core/public/kibana-plugin-core-public.md
+++ b/docs/development/core/public/kibana-plugin-core-public.md
@@ -27,6 +27,16 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [AppNavLinkStatus](./kibana-plugin-core-public.appnavlinkstatus.md) | Status of the application's navLink. |
| [AppStatus](./kibana-plugin-core-public.appstatus.md) | Accessibility status of an application. |
+## Functions
+
+| Function | Description |
+| --- | --- |
+| [assertNever(x)](./kibana-plugin-core-public.assertnever.md) | Can be used in switch statements to ensure we perform exhaustive checks, see https://www.typescriptlang.org/docs/handbook/advanced-types.html\#exhaustiveness-checking |
+| [deepFreeze(object)](./kibana-plugin-core-public.deepfreeze.md) | Apply Object.freeze to a value recursively and convert the return type to Readonly variant recursively |
+| [getFlattenedObject(rootValue)](./kibana-plugin-core-public.getflattenedobject.md) | Flattens a deeply nested object to a map of dot-separated paths pointing to all primitive values \*\*and arrays\*\* from rootValue
.example: getFlattenedObject({ a: { b: 1, c: \[2,3\] } }) // => { 'a.b': 1, 'a.c': \[2,3\] } |
+| [isRelativeUrl(candidatePath)](./kibana-plugin-core-public.isrelativeurl.md) | Determine if a url is relative. Any url including a protocol, hostname, or port is not considered relative. This means that absolute \*paths\* are considered to be relative \*urls\* |
+| [modifyUrl(url, urlModifier)](./kibana-plugin-core-public.modifyurl.md) | Takes a URL and a function that takes the meaningful parts of the URL as a key-value object, modifies some or all of the parts, and returns the modified parts formatted again as a url.Url Parts sent: - protocol - slashes (does the url have the //) - auth - hostname (just the name of the host, no port or auth information) - port - pathname (the path after the hostname, no query or hash, starts with a slash if there was a path) - query (always an object, even when no query on original url) - hashWhy? - The default url library in node produces several conflicting properties on the "parsed" output. Modifying any of these might lead to the modifications being ignored (depending on which property was modified) - It's not always clear whether to use path/pathname, host/hostname, so this tries to add helpful constraints |
+
## Interfaces
| Interface | Description |
@@ -118,6 +128,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [ToastOptions](./kibana-plugin-core-public.toastoptions.md) | Options available for [IToasts](./kibana-plugin-core-public.itoasts.md) APIs. |
| [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) | UiSettings parameters defined by the plugins. |
| [UiSettingsState](./kibana-plugin-core-public.uisettingsstate.md) | |
+| [URLMeaningfulParts](./kibana-plugin-core-public.urlmeaningfulparts.md) | We define our own typings because the current version of @types/node declares properties to be optional "hostname?: string". Although, parse call returns "hostname: null \| string". |
| [UserProvidedValues](./kibana-plugin-core-public.userprovidedvalues.md) | Describes the values explicitly set by user. |
## Type Aliases
@@ -139,6 +150,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [ChromeHelpExtensionMenuLink](./kibana-plugin-core-public.chromehelpextensionmenulink.md) | |
| [ChromeNavLinkUpdateableFields](./kibana-plugin-core-public.chromenavlinkupdateablefields.md) | |
| [FatalErrorsStart](./kibana-plugin-core-public.fatalerrorsstart.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. |
+| [Freezable](./kibana-plugin-core-public.freezable.md) | |
| [HandlerContextType](./kibana-plugin-core-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-core-public.handlerfunction.md) to represent the type of the context. |
| [HandlerFunction](./kibana-plugin-core-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-core-public.icontextcontainer.md) |
| [HandlerParameters](./kibana-plugin-core-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-core-public.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-core-public.handlercontexttype.md). |
diff --git a/docs/development/core/public/kibana-plugin-core-public.modifyurl.md b/docs/development/core/public/kibana-plugin-core-public.modifyurl.md
new file mode 100644
index 0000000000000..b174f733a5c64
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.modifyurl.md
@@ -0,0 +1,31 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [modifyUrl](./kibana-plugin-core-public.modifyurl.md)
+
+## modifyUrl() function
+
+Takes a URL and a function that takes the meaningful parts of the URL as a key-value object, modifies some or all of the parts, and returns the modified parts formatted again as a url.
+
+Url Parts sent: - protocol - slashes (does the url have the //) - auth - hostname (just the name of the host, no port or auth information) - port - pathname (the path after the hostname, no query or hash, starts with a slash if there was a path) - query (always an object, even when no query on original url) - hash
+
+Why? - The default url library in node produces several conflicting properties on the "parsed" output. Modifying any of these might lead to the modifications being ignored (depending on which property was modified) - It's not always clear whether to use path/pathname, host/hostname, so this tries to add helpful constraints
+
+Signature:
+
+```typescript
+export declare function modifyUrl(url: string, urlModifier: (urlParts: URLMeaningfulParts) => Partialstring
| |
+| urlModifier | (urlParts: URLMeaningfulParts) => Partial<URLMeaningfulParts> | void
| |
+
+Returns:
+
+`string`
+
+The modified and reformatted url
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.auth.md b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.auth.md
new file mode 100644
index 0000000000000..238dd66885896
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.auth.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [URLMeaningfulParts](./kibana-plugin-core-public.urlmeaningfulparts.md) > [auth](./kibana-plugin-core-public.urlmeaningfulparts.auth.md)
+
+## URLMeaningfulParts.auth property
+
+Signature:
+
+```typescript
+auth?: string | null;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.hash.md b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.hash.md
new file mode 100644
index 0000000000000..161e7dc7ebfae
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.hash.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [URLMeaningfulParts](./kibana-plugin-core-public.urlmeaningfulparts.md) > [hash](./kibana-plugin-core-public.urlmeaningfulparts.hash.md)
+
+## URLMeaningfulParts.hash property
+
+Signature:
+
+```typescript
+hash?: string | null;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.hostname.md b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.hostname.md
new file mode 100644
index 0000000000000..f1884718337b5
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.hostname.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [URLMeaningfulParts](./kibana-plugin-core-public.urlmeaningfulparts.md) > [hostname](./kibana-plugin-core-public.urlmeaningfulparts.hostname.md)
+
+## URLMeaningfulParts.hostname property
+
+Signature:
+
+```typescript
+hostname?: string | null;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.md b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.md
new file mode 100644
index 0000000000000..2816d4c7df541
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [URLMeaningfulParts](./kibana-plugin-core-public.urlmeaningfulparts.md)
+
+## URLMeaningfulParts interface
+
+We define our own typings because the current version of @types/node declares properties to be optional "hostname?: string". Although, parse call returns "hostname: null \| string".
+
+Signature:
+
+```typescript
+export interface URLMeaningfulParts
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [auth](./kibana-plugin-core-public.urlmeaningfulparts.auth.md) | string | null
| |
+| [hash](./kibana-plugin-core-public.urlmeaningfulparts.hash.md) | string | null
| |
+| [hostname](./kibana-plugin-core-public.urlmeaningfulparts.hostname.md) | string | null
| |
+| [pathname](./kibana-plugin-core-public.urlmeaningfulparts.pathname.md) | string | null
| |
+| [port](./kibana-plugin-core-public.urlmeaningfulparts.port.md) | string | null
| |
+| [protocol](./kibana-plugin-core-public.urlmeaningfulparts.protocol.md) | string | null
| |
+| [query](./kibana-plugin-core-public.urlmeaningfulparts.query.md) | ParsedQuery
| |
+| [slashes](./kibana-plugin-core-public.urlmeaningfulparts.slashes.md) | boolean | null
| |
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.pathname.md b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.pathname.md
new file mode 100644
index 0000000000000..5ad21f004481c
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.pathname.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [URLMeaningfulParts](./kibana-plugin-core-public.urlmeaningfulparts.md) > [pathname](./kibana-plugin-core-public.urlmeaningfulparts.pathname.md)
+
+## URLMeaningfulParts.pathname property
+
+Signature:
+
+```typescript
+pathname?: string | null;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.port.md b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.port.md
new file mode 100644
index 0000000000000..2e70da2f17421
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.port.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [URLMeaningfulParts](./kibana-plugin-core-public.urlmeaningfulparts.md) > [port](./kibana-plugin-core-public.urlmeaningfulparts.port.md)
+
+## URLMeaningfulParts.port property
+
+Signature:
+
+```typescript
+port?: string | null;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.protocol.md b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.protocol.md
new file mode 100644
index 0000000000000..cedc7f0b878e3
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.protocol.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [URLMeaningfulParts](./kibana-plugin-core-public.urlmeaningfulparts.md) > [protocol](./kibana-plugin-core-public.urlmeaningfulparts.protocol.md)
+
+## URLMeaningfulParts.protocol property
+
+Signature:
+
+```typescript
+protocol?: string | null;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.query.md b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.query.md
new file mode 100644
index 0000000000000..a9541efe0882a
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.query.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [URLMeaningfulParts](./kibana-plugin-core-public.urlmeaningfulparts.md) > [query](./kibana-plugin-core-public.urlmeaningfulparts.query.md)
+
+## URLMeaningfulParts.query property
+
+Signature:
+
+```typescript
+query: ParsedQuery;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.slashes.md b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.slashes.md
new file mode 100644
index 0000000000000..cb28a25f9e162
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.urlmeaningfulparts.slashes.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [URLMeaningfulParts](./kibana-plugin-core-public.urlmeaningfulparts.md) > [slashes](./kibana-plugin-core-public.urlmeaningfulparts.slashes.md)
+
+## URLMeaningfulParts.slashes property
+
+Signature:
+
+```typescript
+slashes?: boolean | null;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.assertnever.md b/docs/development/core/server/kibana-plugin-core-server.assertnever.md
new file mode 100644
index 0000000000000..c13c88df9b9bf
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.assertnever.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [assertNever](./kibana-plugin-core-server.assertnever.md)
+
+## assertNever() function
+
+Can be used in switch statements to ensure we perform exhaustive checks, see https://www.typescriptlang.org/docs/handbook/advanced-types.html\#exhaustiveness-checking
+
+Signature:
+
+```typescript
+export declare function assertNever(x: never): never;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| x | never
| |
+
+Returns:
+
+`never`
+
diff --git a/docs/development/core/server/kibana-plugin-core-server.deepfreeze.md b/docs/development/core/server/kibana-plugin-core-server.deepfreeze.md
new file mode 100644
index 0000000000000..946050bff0585
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.deepfreeze.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [deepFreeze](./kibana-plugin-core-server.deepfreeze.md)
+
+## deepFreeze() function
+
+Apply Object.freeze to a value recursively and convert the return type to Readonly variant recursively
+
+Signature:
+
+```typescript
+export declare function deepFreezeT
| |
+
+Returns:
+
+`RecursiveReadonlyRecord<string, any>
| |
+
+Returns:
+
+`{
+ [key: string]: any;
+}`
+
diff --git a/docs/development/core/server/kibana-plugin-core-server.isrelativeurl.md b/docs/development/core/server/kibana-plugin-core-server.isrelativeurl.md
new file mode 100644
index 0000000000000..bff9eb05419be
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.isrelativeurl.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [isRelativeUrl](./kibana-plugin-core-server.isrelativeurl.md)
+
+## isRelativeUrl() function
+
+Determine if a url is relative. Any url including a protocol, hostname, or port is not considered relative. This means that absolute \*paths\* are considered to be relative \*urls\*
+
+Signature:
+
+```typescript
+export declare function isRelativeUrl(candidatePath: string): boolean;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| candidatePath | string
| |
+
+Returns:
+
+`boolean`
+
diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md
index a91a5bec988b7..14e01fda3d287 100644
--- a/docs/development/core/server/kibana-plugin-core-server.md
+++ b/docs/development/core/server/kibana-plugin-core-server.md
@@ -41,8 +41,13 @@ The plugin integrates with the core system via lifecycle events: `setup`
| Function | Description |
| --- | --- |
+| [assertNever(x)](./kibana-plugin-core-server.assertnever.md) | Can be used in switch statements to ensure we perform exhaustive checks, see https://www.typescriptlang.org/docs/handbook/advanced-types.html\#exhaustiveness-checking |
+| [deepFreeze(object)](./kibana-plugin-core-server.deepfreeze.md) | Apply Object.freeze to a value recursively and convert the return type to Readonly variant recursively |
| [exportSavedObjectsToStream({ types, objects, search, savedObjectsClient, exportSizeLimit, includeReferencesDeep, excludeExportDetails, namespace, })](./kibana-plugin-core-server.exportsavedobjectstostream.md) | Generates sorted saved object stream to be used for export. See the [options](./kibana-plugin-core-server.savedobjectsexportoptions.md) for more detailed information. |
+| [getFlattenedObject(rootValue)](./kibana-plugin-core-server.getflattenedobject.md) | Flattens a deeply nested object to a map of dot-separated paths pointing to all primitive values \*\*and arrays\*\* from rootValue
.example: getFlattenedObject({ a: { b: 1, c: \[2,3\] } }) // => { 'a.b': 1, 'a.c': \[2,3\] } |
| [importSavedObjectsFromStream({ readStream, objectLimit, overwrite, savedObjectsClient, supportedTypes, namespace, })](./kibana-plugin-core-server.importsavedobjectsfromstream.md) | Import saved objects from given stream. See the [options](./kibana-plugin-core-server.savedobjectsimportoptions.md) for more detailed information. |
+| [isRelativeUrl(candidatePath)](./kibana-plugin-core-server.isrelativeurl.md) | Determine if a url is relative. Any url including a protocol, hostname, or port is not considered relative. This means that absolute \*paths\* are considered to be relative \*urls\* |
+| [modifyUrl(url, urlModifier)](./kibana-plugin-core-server.modifyurl.md) | Takes a URL and a function that takes the meaningful parts of the URL as a key-value object, modifies some or all of the parts, and returns the modified parts formatted again as a url.Url Parts sent: - protocol - slashes (does the url have the //) - auth - hostname (just the name of the host, no port or auth information) - port - pathname (the path after the hostname, no query or hash, starts with a slash if there was a path) - query (always an object, even when no query on original url) - hashWhy? - The default url library in node produces several conflicting properties on the "parsed" output. Modifying any of these might lead to the modifications being ignored (depending on which property was modified) - It's not always clear whether to use path/pathname, host/hostname, so this tries to add helpful constraints |
| [resolveSavedObjectsImportErrors({ readStream, objectLimit, retries, savedObjectsClient, supportedTypes, namespace, })](./kibana-plugin-core-server.resolvesavedobjectsimporterrors.md) | Resolve and return saved object import errors. See the [options](./kibana-plugin-core-server.savedobjectsresolveimporterrorsoptions.md) for more detailed informations. |
## Interfaces
@@ -186,6 +191,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. |
| [UiSettingsServiceSetup](./kibana-plugin-core-server.uisettingsservicesetup.md) | |
| [UiSettingsServiceStart](./kibana-plugin-core-server.uisettingsservicestart.md) | |
+| [URLMeaningfulParts](./kibana-plugin-core-server.urlmeaningfulparts.md) | We define our own typings because the current version of @types/node declares properties to be optional "hostname?: string". Although, parse call returns "hostname: null \| string". |
| [UserProvidedValues](./kibana-plugin-core-server.userprovidedvalues.md) | Describes the values explicitly set by user. |
| [UuidServiceSetup](./kibana-plugin-core-server.uuidservicesetup.md) | APIs to access the application's instance uuid. |
@@ -212,6 +218,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [ConfigPath](./kibana-plugin-core-server.configpath.md) | |
| [DestructiveRouteMethod](./kibana-plugin-core-server.destructiveroutemethod.md) | Set of HTTP methods changing the state of the server. |
| [ElasticsearchClientConfig](./kibana-plugin-core-server.elasticsearchclientconfig.md) | |
+| [Freezable](./kibana-plugin-core-server.freezable.md) | |
| [GetAuthHeaders](./kibana-plugin-core-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. |
| [GetAuthState](./kibana-plugin-core-server.getauthstate.md) | Gets authentication state for a request. Returned by auth
interceptor. |
| [HandlerContextType](./kibana-plugin-core-server.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-core-server.handlerfunction.md) to represent the type of the context. |
diff --git a/docs/development/core/server/kibana-plugin-core-server.modifyurl.md b/docs/development/core/server/kibana-plugin-core-server.modifyurl.md
new file mode 100644
index 0000000000000..fc0bc354a3ca3
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.modifyurl.md
@@ -0,0 +1,31 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [modifyUrl](./kibana-plugin-core-server.modifyurl.md)
+
+## modifyUrl() function
+
+Takes a URL and a function that takes the meaningful parts of the URL as a key-value object, modifies some or all of the parts, and returns the modified parts formatted again as a url.
+
+Url Parts sent: - protocol - slashes (does the url have the //) - auth - hostname (just the name of the host, no port or auth information) - port - pathname (the path after the hostname, no query or hash, starts with a slash if there was a path) - query (always an object, even when no query on original url) - hash
+
+Why? - The default url library in node produces several conflicting properties on the "parsed" output. Modifying any of these might lead to the modifications being ignored (depending on which property was modified) - It's not always clear whether to use path/pathname, host/hostname, so this tries to add helpful constraints
+
+Signature:
+
+```typescript
+export declare function modifyUrl(url: string, urlModifier: (urlParts: URLMeaningfulParts) => Partialstring
| |
+| urlModifier | (urlParts: URLMeaningfulParts) => Partial<URLMeaningfulParts> | void
| |
+
+Returns:
+
+`string`
+
+The modified and reformatted url
+
diff --git a/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.auth.md b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.auth.md
new file mode 100644
index 0000000000000..0422738669a70
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.auth.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [URLMeaningfulParts](./kibana-plugin-core-server.urlmeaningfulparts.md) > [auth](./kibana-plugin-core-server.urlmeaningfulparts.auth.md)
+
+## URLMeaningfulParts.auth property
+
+Signature:
+
+```typescript
+auth?: string | null;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.hash.md b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.hash.md
new file mode 100644
index 0000000000000..13a3f4a9c95c8
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.hash.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [URLMeaningfulParts](./kibana-plugin-core-server.urlmeaningfulparts.md) > [hash](./kibana-plugin-core-server.urlmeaningfulparts.hash.md)
+
+## URLMeaningfulParts.hash property
+
+Signature:
+
+```typescript
+hash?: string | null;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.hostname.md b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.hostname.md
new file mode 100644
index 0000000000000..6631f6f6744c5
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.hostname.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [URLMeaningfulParts](./kibana-plugin-core-server.urlmeaningfulparts.md) > [hostname](./kibana-plugin-core-server.urlmeaningfulparts.hostname.md)
+
+## URLMeaningfulParts.hostname property
+
+Signature:
+
+```typescript
+hostname?: string | null;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.md b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.md
new file mode 100644
index 0000000000000..257f7b4b634ab
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [URLMeaningfulParts](./kibana-plugin-core-server.urlmeaningfulparts.md)
+
+## URLMeaningfulParts interface
+
+We define our own typings because the current version of @types/node declares properties to be optional "hostname?: string". Although, parse call returns "hostname: null \| string".
+
+Signature:
+
+```typescript
+export interface URLMeaningfulParts
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [auth](./kibana-plugin-core-server.urlmeaningfulparts.auth.md) | string | null
| |
+| [hash](./kibana-plugin-core-server.urlmeaningfulparts.hash.md) | string | null
| |
+| [hostname](./kibana-plugin-core-server.urlmeaningfulparts.hostname.md) | string | null
| |
+| [pathname](./kibana-plugin-core-server.urlmeaningfulparts.pathname.md) | string | null
| |
+| [port](./kibana-plugin-core-server.urlmeaningfulparts.port.md) | string | null
| |
+| [protocol](./kibana-plugin-core-server.urlmeaningfulparts.protocol.md) | string | null
| |
+| [query](./kibana-plugin-core-server.urlmeaningfulparts.query.md) | ParsedQuery
| |
+| [slashes](./kibana-plugin-core-server.urlmeaningfulparts.slashes.md) | boolean | null
| |
+
diff --git a/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.pathname.md b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.pathname.md
new file mode 100644
index 0000000000000..8fee8c8e146ca
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.pathname.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [URLMeaningfulParts](./kibana-plugin-core-server.urlmeaningfulparts.md) > [pathname](./kibana-plugin-core-server.urlmeaningfulparts.pathname.md)
+
+## URLMeaningfulParts.pathname property
+
+Signature:
+
+```typescript
+pathname?: string | null;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.port.md b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.port.md
new file mode 100644
index 0000000000000..dcf3517d92ba2
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.port.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [URLMeaningfulParts](./kibana-plugin-core-server.urlmeaningfulparts.md) > [port](./kibana-plugin-core-server.urlmeaningfulparts.port.md)
+
+## URLMeaningfulParts.port property
+
+Signature:
+
+```typescript
+port?: string | null;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.protocol.md b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.protocol.md
new file mode 100644
index 0000000000000..914dcd4e8a8a5
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.protocol.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [URLMeaningfulParts](./kibana-plugin-core-server.urlmeaningfulparts.md) > [protocol](./kibana-plugin-core-server.urlmeaningfulparts.protocol.md)
+
+## URLMeaningfulParts.protocol property
+
+Signature:
+
+```typescript
+protocol?: string | null;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.query.md b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.query.md
new file mode 100644
index 0000000000000..358adcfd3d180
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.query.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [URLMeaningfulParts](./kibana-plugin-core-server.urlmeaningfulparts.md) > [query](./kibana-plugin-core-server.urlmeaningfulparts.query.md)
+
+## URLMeaningfulParts.query property
+
+Signature:
+
+```typescript
+query: ParsedQuery;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.slashes.md b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.slashes.md
new file mode 100644
index 0000000000000..d5b598167f2f2
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.urlmeaningfulparts.slashes.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [URLMeaningfulParts](./kibana-plugin-core-server.urlmeaningfulparts.md) > [slashes](./kibana-plugin-core-server.urlmeaningfulparts.slashes.md)
+
+## URLMeaningfulParts.slashes property
+
+Signature:
+
+```typescript
+slashes?: boolean | null;
+```
diff --git a/src/core/public/index.ts b/src/core/public/index.ts
index b4f64125a03ef..c30996b83c946 100644
--- a/src/core/public/index.ts
+++ b/src/core/public/index.ts
@@ -77,7 +77,17 @@ import {
} from './context';
export { CoreContext, CoreSystem } from './core_system';
-export { RecursiveReadonly, DEFAULT_APP_CATEGORIES } from '../utils';
+export {
+ RecursiveReadonly,
+ DEFAULT_APP_CATEGORIES,
+ getFlattenedObject,
+ URLMeaningfulParts,
+ modifyUrl,
+ isRelativeUrl,
+ Freezable,
+ deepFreeze,
+ assertNever,
+} from '../utils';
export {
AppCategory,
UiSettingsParams,
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index af06b207889c2..c9fad5952bc7a 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -16,6 +16,7 @@ import { Location } from 'history';
import { LocationDescriptorObject } from 'history';
import { MaybePromise } from '@kbn/utility-types';
import { Observable } from 'rxjs';
+import { ParsedQuery } from 'query-string';
import { PublicUiSettingsParams as PublicUiSettingsParams_2 } from 'src/core/server/types';
import React from 'react';
import * as Rx from 'rxjs';
@@ -174,6 +175,9 @@ export type AppUpdatableFields = Pick
-
+
+ string
| If the visual label isn't appropriate for screen readers, can override it here |
| [euiIconType](./kibana-plugin-core-public.appcategory.euiicontype.md) | string
| Define an icon to be used for the category If the category is only 1 item, and no icon is defined, will default to the product icon Defaults to initials if no icon is defined |
+| [id](./kibana-plugin-core-public.appcategory.id.md) | string
| Unique identifier for the categories |
| [label](./kibana-plugin-core-public.appcategory.label.md) | string
| Label used for cateogry name. Also used as aria-label if one isn't set. |
| [order](./kibana-plugin-core-public.appcategory.order.md) | number
| The order that categories will be sorted in Prefer large steps between categories to allow for further editing (Default categories are in steps of 1000) |
diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.getnavtype_.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.getnavtype_.md
new file mode 100644
index 0000000000000..09864be43996d
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.getnavtype_.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeStart](./kibana-plugin-core-public.chromestart.md) > [getNavType$](./kibana-plugin-core-public.chromestart.getnavtype_.md)
+
+## ChromeStart.getNavType$() method
+
+Get the navigation type TODO \#64541 Can delete
+
+Signature:
+
+```typescript
+getNavType$(): ObservableaddApplicationClass()
. If className is unknown it is ignored. |
| [setAppTitle(appTitle)](./kibana-plugin-core-public.chromestart.setapptitle.md) | Sets the current app's title |
| [setBadge(badge)](./kibana-plugin-core-public.chromestart.setbadge.md) | Override the current badge |
diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md
index c24e4cf908b87..eafc81447ee03 100644
--- a/docs/development/core/public/kibana-plugin-core-public.md
+++ b/docs/development/core/public/kibana-plugin-core-public.md
@@ -158,6 +158,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [IContextProvider](./kibana-plugin-core-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. |
| [IToasts](./kibana-plugin-core-public.itoasts.md) | Methods for adding and removing global toast messages. See [ToastsApi](./kibana-plugin-core-public.toastsapi.md). |
| [MountPoint](./kibana-plugin-core-public.mountpoint.md) | A function that should mount DOM content inside the provided container element and return a handler to unmount it. |
+| [NavType](./kibana-plugin-core-public.navtype.md) | |
| [PluginInitializer](./kibana-plugin-core-public.plugininitializer.md) | The plugin
export at the root of a plugin's public
directory should conform to this interface. |
| [PluginOpaqueId](./kibana-plugin-core-public.pluginopaqueid.md) | |
| [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. |
diff --git a/docs/development/core/public/kibana-plugin-core-public.navtype.md b/docs/development/core/public/kibana-plugin-core-public.navtype.md
new file mode 100644
index 0000000000000..8f1d9a4351754
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.navtype.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [NavType](./kibana-plugin-core-public.navtype.md)
+
+## NavType type
+
+Signature:
+
+```typescript
+export declare type NavType = 'modern' | 'legacy';
+```
diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc
index 51910169e8673..cafd50d92376f 100644
--- a/docs/management/advanced-options.asciidoc
+++ b/docs/management/advanced-options.asciidoc
@@ -68,6 +68,9 @@ into the document when displaying it.
`metrics:max_buckets`:: The maximum numbers of buckets that a single
data source can return. This might arise when the user selects a
short interval (for example, 1s) for a long time period (1 year).
+`pageNavigation`:: The style of navigation menu for Kibana.
+Choices are Legacy, the legacy style where every plugin is represented in the nav,
+and Modern, a new format that bundles related plugins together in flyaway nested navigation.
`query:allowLeadingWildcards`:: Allows a wildcard (*) as the first character
in a query clause. Only applies when experimental query features are
enabled in the query bar. To disallow leading wildcards in Lucene queries,
diff --git a/package.json b/package.json
index 178ccbac7d420..8a92b46489308 100644
--- a/package.json
+++ b/package.json
@@ -125,7 +125,7 @@
"@elastic/charts": "19.2.0",
"@elastic/datemath": "5.0.3",
"@elastic/ems-client": "7.8.0",
- "@elastic/eui": "22.3.0",
+ "@elastic/eui": "22.3.1",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "8.1.1-kibana2",
"@elastic/numeral": "2.4.0",
diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json
index ae883a5032fe7..8259f251a9be3 100644
--- a/packages/kbn-ui-shared-deps/package.json
+++ b/packages/kbn-ui-shared-deps/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@elastic/charts": "19.2.0",
- "@elastic/eui": "22.3.0",
+ "@elastic/eui": "22.3.1",
"@kbn/i18n": "1.0.0",
"abortcontroller-polyfill": "^1.4.0",
"angular": "^1.7.9",
diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts
index 89007461b63e6..4a79dd8869c1c 100644
--- a/src/core/public/chrome/chrome_service.mock.ts
+++ b/src/core/public/chrome/chrome_service.mock.ts
@@ -23,7 +23,8 @@ import {
ChromeBreadcrumb,
ChromeService,
InternalChromeStart,
-} from './chrome_service';
+ NavType,
+} from './';
const createStartContractMock = () => {
const startContract: DeeplyMockedKeys
+ {i18n.translate('core.ui.EmptyRecentlyViewed', { + defaultMessage: 'No recently viewed items', + })} +
+- {this.props.vis.type.title} visualization, not yet accessible -
- +