From e7bb8b7b8a563daa3f30355a4feac1c4f0c24659 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Mon, 22 Mar 2021 11:59:18 -0400 Subject: [PATCH 01/93] [SECURITY SOLUTION] add `enableExperimental` plugin configuration setting (#94944) * Added new setting (enableExperimental) to plugin configuration * Remove plugin config setting `fleetServerEnabled` and use instead `enableExperimental` --- .../common/experimental_features.ts | 52 +++++++++++++++++++ .../security_solution/server/config.ts | 32 +++++++++++- .../routes/__mocks__/index.ts | 2 +- .../security_solution/server/plugin.ts | 3 +- 4 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/experimental_features.ts diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts new file mode 100644 index 00000000000000..c764c31a2d781e --- /dev/null +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type ExperimentalFeatures = typeof allowedExperimentalValues; + +/** + * A list of allowed values that can be used in `xpack.securitySolution.enableExperimental`. + * This object is then used to validate and parse the value entered. + */ +const allowedExperimentalValues = Object.freeze({ + fleetServerEnabled: false, +}); + +type ExperimentalConfigKeys = Array; +type Mutable = { -readonly [P in keyof T]: T[P] }; + +const SecuritySolutionInvalidExperimentalValue = class extends Error {}; +const allowedKeys = Object.keys(allowedExperimentalValues) as Readonly; + +/** + * Parses the string value used in `xpack.securitySolution.enableExperimental` kibana configuration, + * which should be a string of values delimited by a comma (`,`) + * + * @param configValue + * @throws SecuritySolutionInvalidExperimentalValue + */ +export const parseExperimentalConfigValue = (configValue: string[]): ExperimentalFeatures => { + const enabledFeatures: Mutable> = {}; + + for (const value of configValue) { + if (!isValidExperimentalValue(value)) { + throw new SecuritySolutionInvalidExperimentalValue(`[${value}] is not valid.`); + } + + enabledFeatures[value as keyof ExperimentalFeatures] = true; + } + + return { + ...allowedExperimentalValues, + ...enabledFeatures, + }; +}; + +export const isValidExperimentalValue = (value: string): boolean => { + return allowedKeys.includes(value as keyof ExperimentalFeatures); +}; + +export const getExperimentalAllowedValues = (): string[] => [...allowedKeys]; diff --git a/x-pack/plugins/security_solution/server/config.ts b/x-pack/plugins/security_solution/server/config.ts index b2d54df80e06ad..88d57a47b6c428 100644 --- a/x-pack/plugins/security_solution/server/config.ts +++ b/x-pack/plugins/security_solution/server/config.ts @@ -8,6 +8,12 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { PluginInitializerContext } from '../../../../src/core/server'; import { SIGNALS_INDEX_KEY, DEFAULT_SIGNALS_INDEX } from '../common/constants'; +import { + getExperimentalAllowedValues, + isValidExperimentalValue, +} from '../common/experimental_features'; + +const allowedExperimentalValues = getExperimentalAllowedValues(); export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), @@ -17,8 +23,30 @@ export const configSchema = schema.object({ maxTimelineImportPayloadBytes: schema.number({ defaultValue: 10485760 }), [SIGNALS_INDEX_KEY]: schema.string({ defaultValue: DEFAULT_SIGNALS_INDEX }), - /** Fleet server integration */ - fleetServerEnabled: schema.boolean({ defaultValue: false }), + /** + * For internal use. A list of string values (comma delimited) that will enable experimental + * type of functionality that is not yet released. Valid values for this settings need to + * be defined in: + * `x-pack/plugins/security_solution/common/experimental_features.ts` + * under the `allowedExperimentalValues` object + * + * @example + * xpack.securitySolution.enableExperimental: + * - fleetServerEnabled + * - trustedAppsByPolicyEnabled + */ + enableExperimental: schema.arrayOf(schema.string(), { + defaultValue: () => [], + validate(list) { + for (const key of list) { + if (!isValidExperimentalValue(key)) { + return `[${key}] is not allowed. Allowed values are: ${allowedExperimentalValues.join( + ', ' + )}`; + } + } + }, + }), /** * Host Endpoint Configuration diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts index 31e1d9c2699ce2..2e72ac137adcf4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts @@ -21,7 +21,7 @@ export const createMockConfig = (): ConfigType => ({ maxRuleImportPayloadBytes: 10485760, maxTimelineImportExportSize: 10000, maxTimelineImportPayloadBytes: 10485760, - fleetServerEnabled: true, + enableExperimental: [], endpointResultListDefaultFirstPageIndex: 0, endpointResultListDefaultPageSize: 10, alertResultListDefaultDateRange: { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 5ce10299515634..43096805544a1b 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -77,6 +77,7 @@ import { import { licenseService } from './lib/license/license'; import { PolicyWatcher } from './endpoint/lib/policy/license_watch'; import { securitySolutionTimelineEqlSearchStrategyProvider } from './search_strategy/timeline/eql'; +import { parseExperimentalConfigValue } from '../common/experimental_features'; export interface SetupPlugins { alerting: AlertingSetup; @@ -357,7 +358,7 @@ export class Plugin implements IPlugin Date: Mon, 22 Mar 2021 09:35:08 -0700 Subject: [PATCH 02/93] [DOCS] Adds docs for search sessions (#94661) * [DOCS] Adds docs for search sessions * [DOCS] Improves intro and title for search sessions doc * [DOCS] Fixes bulleted list * [DOCS] Makes doc title more descriptive * [DOCS] Incorporates review comments * [DOCS] Includes a better explanation of restore action * [DOCS] Changed titled and fixed setting names * [DOCS] Verified content about maps and restored session * [DOCS] Fixes setting names * [DOCS] Changes order of last two steps Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../discover/images/search-session-awhile.png | Bin 0 -> 75159 bytes docs/discover/images/search-session.png | Bin 0 -> 801799 bytes docs/discover/images/search-sessions-menu.png | Bin 0 -> 180466 bytes docs/discover/search-sessions.asciidoc | 72 ++++++++++++++++++ docs/discover/search.asciidoc | 37 ++++----- .../search-sessions-settings.asciidoc | 25 ++++++ docs/setup/settings.asciidoc | 3 +- docs/user/management.asciidoc | 6 ++ 8 files changed, 118 insertions(+), 25 deletions(-) create mode 100644 docs/discover/images/search-session-awhile.png create mode 100644 docs/discover/images/search-session.png create mode 100644 docs/discover/images/search-sessions-menu.png create mode 100644 docs/discover/search-sessions.asciidoc create mode 100644 docs/settings/search-sessions-settings.asciidoc diff --git a/docs/discover/images/search-session-awhile.png b/docs/discover/images/search-session-awhile.png new file mode 100644 index 0000000000000000000000000000000000000000..a2dba24571e124a8563378e8999796b0b878e9a1 GIT binary patch literal 75159 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%0D<%Z6&Y_uFBoTAmZ^*U3hy-^>eW)R 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

9B1xNm=*9(F#@{p%V>#xi{khD$ew&*JXn6%2u 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 `*xaMDxcw+U0=@-VhO}tkJl6j+ul}M!3sHOcK8{ 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%0Dfag!Ufm#t`v;@#caX(8 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�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%0DyCl%+k_-z*7eH}jG$HxEimcz< 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

%aaRB$2MtM_uxjFMnCS@VPG- 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%0D9dR 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

C%_`J$JU`2EJjZ6g^e5JSt@7*p`ROSU{u8qP z!Qse#SXz;6)ho+WNmw%;@iGZgWnG18l``Lg#n^+<6pxHx;xYmdwnHjxACzHE%NR$@ z&n?Q##3aCy$Y(3}C70Qi+xI?0dlNPz0FX}Pj4#fib?59mO)DU#^_u`IlMEAFnL`Sr zc3OfJw!DLbg4J6)7RCM#wEh8(SmlxhH4 zZZ02xsaq`)3wok43JW72P{Mi6mucf=iNhZk#>uA zX27S}41j@vm5>ohJ18T*Nzw14eVBI5&&9d!>3J|c zjQNI#H)65Qt0=6#NWfDEwlS)&3XtMTThPvtvbZar{LH?yeD-2ZwtmkAh zrlGes^qpSgq~30IX`^nvF(5EifY=oEDScd)jg?KAT%LrDTam-VBeXz6u*rkO z)l}OzA~xU1LJ(?QsGh@R7=d8~h7lM>U>Jd61cniK=@GCr90zvpkIW8@q zRYnJ8*0IHRZd{j3<#8$D-n@xklb69D<7u=P6B0oaDj82mNC$1~SnrFK(`O?Mt-mdR zU$%+AFEMe52WHbT%#>z1CwCFtcRKeNWaG|E+PNCHP9@OEGLD~5KZ-DeJx!Iv^cH3e z>rob6tU+!6-II^jFYv}611X0)XLjW%uY|R#f zJVxRan?EYXS@A!&NlW{(3t)4YLEj7wy#}mEm(Kx+(IAC5BR8*Hml$rU^I=Wh)?nyZ z@bMN0%XI!bv+}EaARY}sgBHiG^h4D;bgNz?K$KPH!SY(4Sw%D{qrCB;1v;Me%asHM zM427QkXGu2q2t6MHw9~U9Cv18%*6i8cui9x0YFw(c4Za*O_!8Uhl02`G21bQ(Ks7G zfw)=w|NOpnjVEJkK6pbOta4k$=>zmIXJN=_nz#+8=N{UhV}W;MT$$g@i}YY7E3Be1 znje{vgOfZ4JKM+~0K8D=^gM--7Vz@E#W@NhqY#MLVH7V*2qUK^+gzNvhBO6|5rC4h zNSL{lm@H0=%UFcpdCy5KYGyI1vmKF(G>Q4LhpHL+N&+II%{yS#|>L2u*?u)>V$tZ6C-y2tgRlC6j@ z2{?{v(bU+uVJ`2(EUUI*H!n=fO(xGaPSUXDI})VbN5W%f0>ZBjamX{M-l?)Gb-5^J z{crk@`n@t72g_-FZylF)Jl5yW@@&qlnq?(NBvc|zAE}aFp(|O^Ya8e;rqEMXH)CD& z@D-J;nW-vVL~wF04f=O|T8~H3*2VhAnMe zt9zvDvmPwkglz=iob-0svAt$jiYE`5SU}Rm3$S?9f<+1A4)pT0YzG*-OFiTo1rm09 z>!G|qk&-ZmN;W3R6dY5)v_W5uur2}Z_suTM&9vlICM!lFK8cMiFu~;|-ueUjas$ov z`(EC>QH!3NM{*e)($w6noUE zEXydLmp3to!ceBCre@>BgY8z(f7A?vCfD!H)m7Q8rNx7xn~O?5L0wS2Qe97@WYw=X zDmZU?VF;X&_@JH*H5}jclQk^gFapB}3?nd%z%T-T8zKOW;Y`DE@Mzvjm+HX)e9`MK z8-RV$Z=dNq#z7+qoL=MC#SQp{e+61{WVG^H33)Yc@O_N1bv)SPnc>khZT5Apq%rHN zy9A?H8>}!*r&hj^mo!VwOrIUcIhpqg9OyHKzh>bVuv0p)r%1all8y-oNqts@T_D>dm|OgKY{TM43wD#m^(U{>&MC(09)_P&cI9B z7%Heai@J&H^2GP%lV^K4|2u1c&-UvtU&{kXU?&B29VXBA;TGj3MUTjl6)Z8>iCq9l z6VD4arJr{gomX)=f4F`Zc34Z6u3nN6m?@i>M`U&nWo=_eZeE8K275(Areol(Nc3t9y{MI zx@`eu6qctZC4r`$ZcpQ<0C*@IkQsoPDvi@-C*8b*VFIbtJsi=)A*2F0C%R}kt=A@& zdY<&3`MMw2r=2Uafj99%iFjHs0C&wothLT7*sb-@4XaTB@f0bki{&xd!t5n|=a&)! zl=b?E=Z%}NYie>>FH5WKkQ^-SHtV~nmtCCfZKP0Y8(f%Qlx2WMENkfR0Kqiy<_z?A zYu>S62dIu+gG+fPF$|dwCSX=|F`xh#0R_dE9-N0BlW?1I5_|>7zHG79{j&95*7pkP zXj-ivn${PZHDFl3{vm-&JEpJG!{D`9=qs6$pQ?6%#E@(q?bG*iPSkvhmG|~` zm=_Ocj`cmv7xnWRi|o%qEkA7PTidigq%2((`U=SB-u*{`UyLUzXint1vF$3tj(w$arM zp!X^ET`3ncuw#n=WFc9axF(Tkg3obTr=IpO*UAURaGKwi;vohmxnr1(r2ozUzH41F zx-_(DcNkEzM7nzgHa$!{u1{J9HK+<;&Xv#ZNP@Lh-#Ns7v|~LgHzk#v;k5-W>e1+L z^y;TQ4kzdhTda6%$%p{PV{uuOIZ1ErON|N@!#pQ77MDnHRNACg7j{pWgwbQNAx#mK z(bNb)W0tzPO3JnNvjRI5;~YI?!qD(w#+Ag_h;2ux1hJ8z1Tc$Y5_M1YfL zrBn|!9KS{!hd&)gU>Jd61ioSf2FHdkdR+Jwi!xmOEsB8Nv%UU1$%{1{t<&&Ux>Q>S z|Fq|q-AOPOF@t9ZgAvWfGDCW53-U+ib5)Lfbi=QsQ2l#Fi`7|LF|Y>Y7k#cpdbA#8 z>?l){gRwr-Okf>fxIh^@usn1a;lvGB#}jG|Q{=N2U{0y9CmZODHA$vmMa1gMvCPO`)f%GL!B7_3lB^S3z*gYKdrojWj_B}-#_bO z9pkgEuj`$UrS)5@ywTo{JjZQ*_$3d6=SyGf-qe-04O?g(vzE>M9meZzlk%KF4=tB3 z$h&|JntrFn|DtkP-!UGR0I^Y%nGco&QXzgFh(-a>3NOHc0$S|S{3)~e+Vv$<4&A;5 z6_EUCKGV9aDgP$s{HO)(>d>N*7&6Ki70@Jsv+Ah^DN(9z0&&zVyY4!TB9M z?Q^Sq*81j%OuN$F3Sh^)*0{Lk!^NvI5zfeVHY0Jsod_&ahaYwtbmll3Zl!ENN)PVH z?!gAJm;@Ya%0BHq;z%7S4^JN&^zX8@MdyDqih8?O#J01mksXz+ISon(x z4}GjoCXAu;=*Fxl>St1hwdi3bhcrbniXjwC=o%@iUR^|NzYMN-{=32VPg_qvx5EkyA|0|cnUWJ&ugZdVVu<4i(Uu4UXGIBw zl--2Hv?V>$5i~n>(YB=zB$d&Gt7<*7;nnYERdaA97-(6QuHB` z3#Sm#+j7tG8Z zoRTtA4k4JuDt+8JThIQ^yx3qVYQJe6QuZi|>0MlUiy`Th=)1Y5bZ!7R zXFY$_-#_d6FMIpXT8?k}dT&x3wJf?=?d?q}+naixpNtXl>xa;~Owo=9OKx4`^)v1h zp7?HXy~nSubMg3-;j3W;o*n_K4O?sUtC-e1y#Z4K!;_WnRo1UT4M)XfbaY~#-$s{e z>jb^_XB}t_jx~6#TnB25Dcayyud}SrQc-oltl*FhHds^35Af3F&&A-a%pkf{EJ&8r zULT_te=;di(khFUvYfy;$&=!?y|>E1(l75{UgkGpz@&XS0#pj7-EwJa0bMK?gJu`z z3d9KJqa%t|mg<%<$YYpDW#;Z$+n4T}=kty2b;j?@_WdZnlwA+O{PB>SnvxkigH#PC zpF`@@F|Kh)uaq*fk=`a%H6&xWx<)WHP_-~kL2oO-MeKW^+HSNko6ZF1rXh7)YfCls z%5b`_1KQdZXauCyZR$RiL8*(rw*J(>QFl4_bO-VG+H* znB@14aiv|Aor6QU>|4N4!-4}%QJ?x?fPR9htT7wNco;N;5SRl#KRz?S3}Az`=sD2E zT2D=TTXgOkp$29=xq1QfnIa55kE9|I@xjvVV771o%cl(Rs{`{XG)og`A9~MBsrp;| zW<5TtuNn&l28fX|v`0o@gC&wk0Vq@XQ1YZ%$ASO~ff(toF&RsbN(L|?bGXlG1x$jS zd~JMLVqUL_sYZHm+fR#%wT|g4btQ9(9-pVd-@PnW-rzpI>g&Nar0q-(zC|N1Vn&k5 zA4={3n-a7rBRDUIG3Rl>{&K3bHS!u>t*oFt3R_1nj|qva{oN}Hjy_6M9jetKxeZFE z0pg|EALqyd8m;&ZmZQIsr$6d%US=%0a==a(nI{c*QIAENGtZXuJ4V)Kf~t;LQys9& zrjFYVw}ibxqrqx`SdLY@usX49)6i|SH{PBt#Lhbk^L7M4c7*Ss+X0xJRUV$Tm-EtJ zPKpl)i#s-G#+x+r(WoP9`O3gCtQb1ZH z0xr)jFtMY^>9Y+!*zlXPXqhRE)&X-b%ee^TS(&wrEDnBu? zT72&c`k2e+cdCW!P^WPG!Oa90+jM9;$6(*B`_c>2aQS)>plVue$DFgTx3yRDYpY+n zXlL42ebM(9{pL;iOr7d$=$CuA7jq%`V;zgt|LSqInfPqAR9pA4VVr(n<63IWx;Plq zpRumT<72%(>spUd`%aZGsWq5|*Bcx&Go!r~mkJ~cbeqa5(QUcOW>Mz#U@Xh!Q@S^| zxrb^n*{_pcW}%UKxkkIc!S^ct19@{ERX(M{{j7<vIS5MymmP%#hFwog%JXR;8%bZjCRsFqYG-;g}T%NYi!FAr| z*QTv9lh!7qf`lkKSH_%7`r6Q%?lHNk6Oc{^rUX_$#kr~G>(X&BF&FZ%9(vh)H(Z7h zcu^6s+HAjW_V0CjE%dWt)!5IZd|uSPzUp=?4M(l(LM{jiyRt&B9wa7JV*4uOx+>CPker4G3>?3|Q zgeLXYv7@o(_)Dd{++ACdQ^fn*n96jakWQxk@(#?;P$OKjH;TTglT`CEdY3@M{J$kp1|ua{>( zHBTnp(cG5z%g&+UDyu8@2mj_%edz!EiS6hOng-siu0P@L@uK>o9XUCzqVc#V#Ts@U zO&nXjm&wyxZIET^J-pM@w4`J z{(M;94}Pw%b@s|mCF;P|DilxT?)nN&;OO!3*-m>$W|V<^?Yg{s^OBro#xdYYOAI!t zI~J75_y}w^f`K{y7&-JzS{R=O|*#@oc5AL_kep{r3x42RGIp#Ul zGMa%525gOr+&$c2km_R~=$3dC2k>B*zYzVLL&6@vgh@R$DXhfK0m$gG!^TtOO{IKlfS^R*mO!&H8c;@}b(C&&8^^df5G-9V zG%+sW>_->l9u5sOHVSNL$N5P253FlCN1m+hs5#f8h0nE|x>zyk0QiQStE}gDZq*C@USB^tQrkS7G2|xP|-q zI)KSCCUa8`9B6~VWTV7t3acMP94yAse7}^)qxRhmF0XHhwq;; z0vcXDQQDRi7=Ls!pYiv@)m}&hG~?%^pWl+Z=)eBvum4?{LtEFX=hkzZ*_6Nf>z~Ro zCTqX>_kRnCl0GxL%%t!i{n5XYQn?Hfn|=w#wBCPx_q)F#SFbLa^655|#lL!gRet=7 zpCGft31>Z|BJukKJk!iG5034!{kU*u3x<-3v+Y0&_4-kJ=)0d#WO`f`?Rg?ZTbKH{Li_p z(5E8*#DyO%d6ob0$alW;ZJ8&|=NJ9m@$s?z@gM)cGC?NSkACzcvx?S>Kl`_TChxxc zfn2)OKc@;pe*DSDa{JaD`TqBRXiN?N-Cz8r9MD(WkhQU6u9?Ij^2YV+W?xRda1NI* zj{xVZ&Dd)DlV74$j+7TB40cCL{2}f)a%AyLioE}=-&TZQI`|80uB^z~{cWrnO0u*# zCzChcGn4THSJf}y=WvZ-1imB!+Beu(r^mhT(ZQ}{AFfGc@v=-WPI3?R`|UK}T!p zviL~DywlkNL@dZHI_lh5p5OXO4=lk3OE8!r#gM^Y2Sh?mQ&!N}s-2Wzl+|Pc4XGHe z!wr~al~N6zsGfvzs-BLe(2??R^zznDC!bWS8 zqS?WDbd(hCYfFm~Mbpp%a$i+AZO~d3jIwo@9Y(t@q{bM`5_gDQ(?EVtFNxM^O(#{* zq7Fcf;V(djCSC$c>&+G`7{KhTZtHpq+9-@xXMbJ5FbiZ=z`Ngp@*7_0wzH=#`Q_bP zvXVcLAcYJA5OtV=eu&QK@x$9jVjvY8k!cJjY}~}}t?$YD{+=Wg3DPVR21tK;>AyMO zD9)wz?Ec1@RAD{M&0d#q#4RVVJF{pz+K+dbM3|NFk)+JPaMsvqEH(D}!T=8DlNWo; z&%(;nvh)k2B|3X)UcKB069XD{4f%*I2AkE&smzRWvoN2)(5NCme)u<1r>!rIEud?d z#;k*sU_h(@+IcR3ucR+7E>eyiZBltjQJqOnnE|HN`aLs3D52g%y*!@-Y z(+d~JO$QE;JO^?ysQV7-N#A zBU{J&lH1Em8I6Pq^e7gWmr2);D)ePa)q{CQ&u4DLtYTXG)Ay=XiW;P;g-vCNGjLbu zZ=W*uIR1X^uzq>*v(ewA%-AG}&aL_EDPBJwW?1q2l5k(V@mKh%DW}F>*-|{4K9v2d zM5?y`3F#`hv2M!sVNZ7XvyHyT?E+Q#4$+R0PCS zI|Xfd!7d)f*D)LffBiG9F+Bsv|2AkiTJ7fR2j&%Sfl9r%w_c7N zdFQ4%2rDMqyA?#dvb?xxe*f96 z&*UHd_y40zOijpd|0n+dnVquy;lKDpsiUR7c=;;x`BnMlFLLrn|Ifdc|K`8`hw`hx z{^2VO-rv4m>Ahv4P?YV>Ev{emfljZvFWuW-lhw5??wRH|*K?mc@CG-nfB1)gDF5W2 z{O|HRzx~_tgCG2W`(ZoxR(W$yL+A)A;HdTK*2lNxfA~lLlPoMQ$ba?^{!=F8PvsB( z*&iUC)R5)n>r9AOxLB`5(*9r}yMH|BL@hrkU7! z-bMSHQ*ecZUUQyn0h~8_=NI4Q4mBK~E2!W69}Qk)bX=jg{4Ip@OEBP61YRAWeK^Sa zF>F!SSi>}7HuiuUqa#?1U0f-9xJrg&qh?@se4LflqX*(g8|%i>O$HSK`46{0fr5T4 z-x_}p(R@&M+z#qvfR2DJs=Vj0MJ3nGaFc|aEr87aahmP3ZCF3y|DV10j*s)a?tIT+ z1~Y@fGz@@2?*!N>ilVAxNp4c?IB{I!*ohN+opSe+;$(Mk>?E7ScYW8%UpM7$_TFr6 zoZXE#wo~k6*IltK%T}{YQl!|?dl_IbgXx%hzvmgeBnW{bMUj?d;gyJKZ#_>vzvp*; z2Mw}$#OL>vb^$t)T@)~J7N_$(%pb2$0Ywi$*@B!Nn}g+8CYvctM*yX{6=?5mX&LI> zC3S>^=)|nrIA0lMruU*wbe5YPU6_&4sYMu}IAW<4!d?<`eso0Y2~G3YHZutclWeCA z2d^UMrlw^SrecOkXOs>>wJ@ECV&w?iI|k4*JF|oinq3;3!T_Ew)bMZK`9NXW7{As%d%ryORd=OwisCy7I=_?tpZUTtIqUKIOe zMGg(^F|1M@fH&K8`>y52?X6{|V3fM0%IXThhMSmQfHhkn<7@+oZ>B-M%hEj5Wwh?h z{cO3M)>q9Ho2{f}Igx?+34oenLQWWj-_s~zGITdnR}GrUzIqPbKUvD4fmcG~a)oTS zcI@V*4ftpaP8dssPHTpA0QCY1WR4G?k+GS19BzQ_77`$^s#J%rsIqY5tBc}jXoXR* zi|e>y{1IBwQIWGFFl1u`u?T9R_M#BwWHdQ`FE)-!8=p;upr zJwM0s@zpijd<|^L2J9T9--CJ1KGtR(XMMI_t?w@_%y5VTk=3hNd%bdA^|`gSTlcNc zXgy_#g+)*b>LfbL>lwz9ED5w6vamb{K#`Kc{@bOk9hNd|kC4wM$4;G=L}WpV&FG6# z39WPLtGSMe?3Bjf$_+RT)iGF=&dx;jH0Ij4kFY!#54p)Zr3X6~$ zk{M%>vq1TE!e(8E;i^uAe%9aZL};|Pg~iLbt>w8+Ci8eAPQ*o>tVEXC7d>v~G@9UV zj(K1vlSYR*D6Oq6<~)}!_$q&sWKmRsqHHz;sK?xZIT+@kO;jID+eCCVE}#CJzrm*T zGY#$hSgrfIn7R^+NMoo`{^NiABY*~@R=ll6Gy&qh_uOm#SvzD|jQ^8A`7`E`btDdB z{>Z)Per_Uyv`mJFhvonJ<3BN=$nXBn?@1WRla-YyV}VEB`Qtw-@BP4g%&C@_m*r3Y z>_5wAzwkM^{q931?AN-+&g+Y_fG&O>z2k@+J#zb{N{}an{V)IOuNjjakS_)=ozNOz zb;0Mt#bJ3Amf{|gKHAYAw)#{BmmNeNX&AQdfA;J-`M>}4|DmpR%fJ2D$Iuh@OL9I# zxG!b;_8*m>{^_4K=U-S@q)h)``P}C|Bex&V;}TnqN*H++ zYkqUZ1+MX75r*qy-+oL!@N++B$PU-oedp&NLJMde)Xi%D#(x5@S({ssr@s7E=3R^8 zWgJyff>{+dbI$8Ggw^NF&d^E23NIdjunk>=5 zJfT9ArX63}ns4g96^(e)=%VY~EO>b7WO(1-r4Qtv4HA(kZHU`+5!NVq;*NhmH8$$QUYGgg^sONc52>P33kX=Q=~Ed14a45avyk zBm~E&PDwt!E_R$?-TY`7I?xs?#3?I))>J+s&7DKCZ}5Nt$HF}SV&Vd+eMqvz7H zRwiyy!P3a8oClPMtfG&H{))StV)76qJliYx?Aj}B8j?vJQ6Q|ugnyj6Gr<6#SxZQK zJwbhRN^8q5>FR2c6d~MmOxV=eu|-F zXtHwzf<5qH}lF5`W=r%TP+DWBP57z zGk~&jY&AMHC-aGjSqYw_4;bj^#Gc9;tVUBNIB;)!7S>`272>KCkM6K#a00T+BT_~axRFH~53&-z z{EB2)IEkSfJi0hTQj$TyFP+3}+OUsSV?DoaS`SraCJQ@K$G_3JNvWle(eU39nV0UC z4!I4WQ)LidY@M|xh}A2-a{lVG>$B)0i!P)TiCC!Gl~5dh)B2*UR+UCb!iGNyJ3F#A z#}G?GH`t{bHq!v$?X<%*CzQO5Oah8=G$9B5sMU8=`wV?*1&F>$%&fMbMg^K`>B6l4<-N^Yd`OH6lRyzCpWbFKTA~n_tAH7#v z+xz6(IOFs4u-}nN`Q=~!uKNB@lh8}(GCU(?%T zUB%k`^ryYu9rEPUPgAbwrqloF_G&L5>-k=m*Nr-$Hk_E8lifx`OE+BQ^?c=6gsQk%M>0(S7%ldq6{6b$(MF-?VJ5chmkvS&n94<{+zFulM+! z{a*bR*gSUV(`fCn0XPO}&+q=JUzC-^q&)h~e*sUSp6GP<`m2|o?$V|U!Rw6CI{&ls zI$ZkkI^S3tj+=Kkv%eh;$IT{rZN1-Ifz|1li^t@`@Fdxu2_p+PlU$@M3ot#_&!^r`Yj!77;CVRMFYG8$FvXTqaQ&Nwhe~{jJti~gA zfIi_6S&`8LoMsTkXQa~b^=VNPx^7;@z z){qh#;9@6$T6LvXSBBN$2{$zmR@f+YOvY3`;JJlm!y;>jg03R=uar#(3U#~+HE3NH z6UO=KY3pO9l7WZ`d@BH&BLG_jT}n3^ZL`^=Ja+MvWYcj2rrQ04U8C2)x&UC7h6k2m zcOpoPUU7zrzJ?d-bX4b!1)NBdf?^-1nZQnENn8T^PFEJ}ICnNcuvr|xU@$0m!yIzs zT$acl?5Ia<+JQzSAvLO1V#j_Bb$(fcy_baL(!@2DnRl}{YXCNbJ9etpPeRJMZU-zQ z-=4iNYm4F_)V{vaC-s2T4(ggZ%Q={jHr{J;Avb82f(J!x9A7WXbCZj*ik6~gi|y%0 z$1PVQ$@CK9-D`53`g=csi;u}SQr>)*k@49%S*EwP;Xq4WfKaELoS8T$C+E%*Mgh>F z_GAK*LZ7L&t}ISJD>WQ@Y;r`7^a9)$bCOT2ik*pIo_aik1VAHU)s^)mY)JHxIMzXE zx&y~u%WCs;-)x)S$oZ`Mp#T6II0JxB3E->32gPmF#$}jsYGeIhCBib4fxA8gaP5^? zY5`yZJrP}j2h=Vy0V{i5a&dSBV8JEffQFgs@S|X570A4i`@EiGHZvTp=d$`eic(Uv zuV>CbC%IJGgufK2FN={m+0)Y}1HHX~?S#{6N5y6xXFlA(pUuZ_o(m|j*+p3De0EGm zs2{D3F4|s>#TTxLt$oye4RlpL=AwxN>li$j~N)7E0HDIo!_t`i-|=R2Pn%< zv|p*Xst>8>Qiv@JgIW7gmIVk=uS3h0I?C^hu);?drb*hImjgvmbitp6Yf^@Yt zOPCOCWqfZok#+BS&(?drZQV@s;dQ&;%pnWecfYb<;MVE~VuB%)BhlEHW|9GS-s{4hQVOP@fES^m9$X${mKIvDZubgZWL; z7|P*hX*jCWd;G)+GhcYmyWcH+eSOjlGw#BL3v&F~GxDByyKTEefn?yhxGLAB1H5@0kQ5fp`oAq+~3K+|M$OZ zK&{PnRom3&&o9X?KcIsZDEd2|=Z$ZCv)p_Cz4Fgr`JePpEIx95Z5jGZlamvKF#jz! z`e~z+_}TT(RDaj9V=kDAugk*Hs(k8GpOUqB(lA2b{_xx6z6b9!jP$LKV%bSUFTeD~ zF9IaC$UOkNX482hjMC}JC3*ZCPf3LS?2~`^N$FwUXC0^WC%QJ~+;gT(%dCj%JwEfy zae2#|A2!TZ>zc2&^04mv<$Sg5Y~6z<@%idkzCyX!*8TgPkESuMv-OwY(S9{C30+6no?Kl}vaf4j8{;}-)Zb!{4d>>^A_ z)f62W8IfmCJR=XI1*zq&_obV4-Af1Wyt%>xmVH$FS?3ZdeA=fT|F*Ob4N2!9)(Qw>sjo0vdt;}UA*W~;p;ZjN2edrECIhqV0a1I9C%^+K5-nwX_R2umdwk=0V2MhHtC zfK53{*pV|tI4;^)fLys;TKt5Y*Wk$20pdpE5m^EZwgZ@H?(PNv9L@T>zD|fvk`=c# zpE&aXgzUF~v)D{xjz!pgB|wQD>cM$uHkMe@qH%5h+nWBdk#3 zC7a3D}b!4obL>V_me3cw$A4 z6ArK0SObJ=-?#e^x}pg}l~&~fQURrOOmgXpw4pDzmRyr2H+q<=i?`)@ujivR@v!#2 zk#ktbZhqBqs)UoO`3B~b(aR7id zqR}|Jupsl;)=vFyb2Z9fM+*xEtuixn5odEoyoVY9d7NbUMwb-8$Vt1l0SLKF4CWFMdKHTyhxqv zYHF4m*xSQ|A4Xx!^*0YlUk78z^ck6T2VmK*Nu06E7bM#^51}mP2<>ogmW4D}Kg$5S zea+3%2;0gnMFlE9~IL&PIT86~w?sTt{u7*VDk6mE{=H7|PZn zytCpX$rSU7^nyHdf()$a3M=!*K{ocC?Il_OgIgihH%r4&`{;TWB;NJTcL5x}%hdlA zZOcYKzjx1&{Of=H3pgruau1bxrvzwiZVXL0E5KmHDxXW>%;zFHQ8byF5`Pj`>pckkVDki~vw zDSrF0@5rD2#eZV#^~jIB?&!A9qoA;cOFxB<+`swF-(X%+J)i2VGYrW8{KtPHN+co= zz3Fb|_#tx+%f6bux|T8cJKy;ZEXX-YXA1^!Y-k9{(W6IL=-O$Q+Zz2kzqIVEyK6a3 zO--Sv__Vy|J@1kRza}cvzOp*5=eGudJ+F>ID=m_^6lb?UN?>(-``YRYodh$8i$bgS0M+;!Hq9SujzmanMStxkwF zkXYaA=UY7(==qg#$Nyr7@m6C;{|hl=-~gD6P9wU%K&4B=f(Q|ItD&F~L`vHM_=4^R z@xowqKygw{CNC_+!E%U@v=u-g1pUjWrJae+9o_p0neiCiF+T|soP@IaVdc~^82-!Y zyhL>}25YBCqj`4hnA9++sDKC=s?Xw>%%Tz1Dmj3%aYBTXFeBWc`$+(u0zAIO z$ePSH!~x*enJkf9fwmYx&r7eNEl?+0FZX7ZrMhCuB=Nzl@rMF35?PT1Y_L2mvU4*j zNs>HgjgV?beAa|g`f;djb{L(&6iu$6RQ%BE35U#p3RTb;%oAdn&yo-ZKxoZ})+1rs zDF8DU4!IYdz}@I2DS*3-^X(;cEl6li2yH#hM7UU7K;y8E1OQ~PCJfdIaJII#1}l_= zG@N&AW)5&HDYk4Gkhw*COpJ6TTsNz}n)l|LoA<{$x4tS4W!eOtr~BN>2P|aokb2W$&hsQxaycy3@bz* z*rr*S&EQ8lZnWj94T|&y@O`m?h9%cgf6eh52HnhXwCt*O&_1sSU3GqanIO+vY3k_% z%twEdljcH6lDLesu#uJ^EY+79a%9-)`{!O+cKo4?i4tZwG{wj6Wio;UB4Wm7$) zE-K{sPy6?2`n#6a4&)BnO?#WTFfJF-@eQ+ZlOyrl1+vyAk%thMO#-mW%YaI$_&k$R zI#cdF#?#a=R`_5lhGA|y3C+&qSGl-`9eXJ_%jhI&<`>JbLefrH>)eBj`r8O=v?d*p zskvEsmc^PHLIfLBSN8{GlbdJVAKaO&I;Q2hxlEsT2i0Nvd0*e?7wU+pexqtF>UH%# z)aj*l0wV6^yJ{qf_Dcs29YS~RTP#q+)Mmc`i)`Uw6;x4uLXDzO*$O>g&!y+p1u8~1 zb9>e^-sru)9LHJZaq{FT`R9NBXZg+F{0MzEjCsbqGV?Tz7%2e6Dj5I76W^2XJ^hUQ z&hLEGXjrZ>m%;Z#&zE^1EEOkG0}ns^kojjl%l-F1Aiw+jpO7zq>GN{WJ-2Txs8`2z z%U;TE{K{9pj;uzHVK*9v8z)epLV-{_^YNek!29L9Pdq7~`}`N==Lvgvs&oijwJgeV z|LY%=H@)!z-tMy`ZBDydU?yk51Tfk zC$-9P8K2;@pZz;&3%ALe-~46+Q0IugVC9?;d*-XOr!rucvLM}P6#vZo-XxEI?@9TG zFZ`2y0Ify~9M<`d4mNN2k=M!nZ+Sqn)Po3M^0&VEZTai}`f2&?kNrETBhq1KyFLpj z^O#wjtTEsE&Qsr&eTVNxqrHa)MkI`54H+!_2uDF~|HU7dFwuHAW+X8U%Rl^&PfPFU zm>j%~gilH|+2}v@s``HOmPT(?Gso}Uzh7Edz}HRpQP77s=Ci)PaPguf@YPhFVrTo| zwt)5vRz0!$2u*o8A5o|`2`IcF%Vp42=w(6O6taXg_ngBm}?(}IE4EK=HzQug4 z3C5J+XkACOna>l&kSC=7=+UF*KCIuY^K5qkyp^?YZczDyj64UjsC92?~xKZ zJ<4|TxtpMf=Mf?$kqH&e3tPm4Q|w1;CyrA#yD&-Uq&n>I7SIG!Fea0a!gO?!@Wn4J zfPw9VI1lxa)WhYbA=sp^rJK+-O>)2lhmKQ|ncOw7SDu@jkq8r|Bw@obCapoh#(nO0mH%oiG)1fc!qcS60zrJ2^H^8!pOp8g{4+{Wic01r}zot8RK8T|fwU zmC48?8M`eo$zFy1e0p>gW@8EFEd2@zP<$}UY&cnQvMlc&8Xy6Ro5>aRoAQ`lfr-W- z8i1)0YzZ>aCV>y&at&a{at1xkxn*PlB2q^mzFB&;v}swIL(y2Z77LZpK!uUJMq4BkXd;cC9et@X z^=bi1!-)I$wC{%H6_iQrm06#a1mI!_Hn}D(Dg*lVXHzWhR2_TG+J12fJ>6VFG)P^u zjW)L;A;H7zq}bZ3_h0>{hK%oGbHn_dk&Dq4BO~Bua#90xtpE^k^1`V2_xoAgd3jdN z)y9^+t@}rt-RPv&;VgHxcF8h2z8YR0Zs4f_?@J_kYK7^!h=yfd{DhoNG@CK2K$}$^ z*R!)1d1Q2839Vk@ne!xZXhFAi|Ih)YDxg7JBO|eCSiCt|;I~Rnd%ieo3LzF_D#&x3 zlV@>P=mAyKx9)) zPA)A{PZLaHnaHxBl4DW8MSqr8maVIWXIFM8i&`-3HOjyVfE%a&96z6wXhTeP?KvpT zL_O%VS6h+F7wCf78XwApvPYR5x3C(2XU~9)r_dCpE$0h0GEAuF;qHEO%q!V`ulRe@ zdbzPOvyq47CEv0xHk;fAoI!xxYe~Y;=_@?xPcCu_GFd0vN>%$P*5>O3z@I zeD!NzrayNeGcsoW%}k@4&cf6G`r_xY~QOj0yTkkC68LXnzqbmB8G4}*YH4;jJ6 zVsQp$F$@73@(Ys+v>(+(T916~tMb;jzSU%=R`#(8bHzVUu<gS(t2OaE@_5v8RL)<9n-~7EY zag3|Tz;UxSy`Em<(sehc_gyWmte(YXn8{BplQ3XY9>=&n+-h`tG*?LvtJ~{1y)m-u zMWb=FuH1yMlH3LG<@C%H^}HmFj(Qn9(hrc;j^me?x#d{^%7_Gb7I%Y!fn5M(Fzj$3 zosK3s+*t$5WX2?!2on_OfbHm`1D`FT#|P_AAsbH+T@pfDHFUE`=Y2<82jR5QyFGo5a7~_Gni-M zlh0|8CW$2ofvdx5(Aub*RflepNz7@idVjTZZmzTIbg+q4!XF9eEi=(7P$v}3&QNdH z(IQ<&&!&OUUuA{u3-^f)M_??mB+sxas9=?9Q?_=tN~dPhRNw}%bv3q1#%nL&p97e? z>iEpBk(D)h5&PSv6)?L@y-df)S?wVVeFgBB316c!X|ZuT?8n1HyJY}oqtTYc&DLFeOelONecZdJ^=80N~bT5EfW# zYnz-|1keGfZi78`Zf!wQXpri4;LYhDoFgau!^ES_s&1+#I=O&eo0D+i$;I;$UtEGg z*KC|9U0^8Dmfe6x0|WhL(3=2k2rL8Yw)M(FG-g)Sihw-IwO%apOaRH6CB3x?7B*n! ziE|@TqTTzjWw{n+GvT)7SVnH!e~@{f2bnD`YSA2w_u}WgdON(<2uoLz0>Vplt%l39rvZ zSa647b)RQ(Cv8V3&_S3YY-2wX2RZDoPQx0&qt@Fa9X;&hlq@UfWhM$qG&0+n&>n(y z+t*KW6qY-6ys++J^F8Z#4fF$ELUk_8FVNoO(%V3WVHPR!Xvc=gc)5<2<5u)+Z{V3!0VZ(6cq+)adzYd3SW}mA+QOt=l^Rz<>$Sm`wrb z)_Yh~p^mg6TTo9H@5x29m2d_Uh`6hi#XkB+C-lQ2;A=B=@WkAtoCL^JmX)neS$i<3 zVNUC zkAs9r(l-F@I_d;<*99a^x*~~{0K`f5mQ?mP(h;llk!ux_!XdSgs;o(u2NpejYCgLt z7hs2alu5sP$kbW2A*v{ZZ%_@hGUThXoz@MY#L5&r<{(RIE~g^#AKh2MwjKu zC!R8iVCoyaVq<73k==ZqvaN2kX2vjO@@coK<*MVIj_DRl@`{VRiasb>fPZ%&T5nsk0EZ{H9%jzl%iMehAT&^M? z@u3fWkdSJ>(N;Wvepvbd>2++=4APoNqP?Tj%mL!)RR6Cpeu>QCRqaN-7pwi$Fvn4i zZe_73oAe#;c)N^GOvqpS#ebI0t~TlG?J-u?Fgj(><(LQBVASb7t>I-hmk zS5eWtVs2HV#lH0AFPr(rLl3>JYIo)-3QQ}Ts0oRWdY@Y!y#IA3Y1et?da8dGXS`1l zc``FIZpx@s)f$p;M+b^PE@TegDdXc)@@N0)&!nsSk6^iWFfIW!Q}!w3KuppRuBNZi z6v;W!B+~iK&UT#^ps!M<$m-&(JpRo0V0-NsZ?9&a2bsq&^AH8Fo5XS#k3A`qvt!a5 z+CxH57RB+&D$Lg!h@eny?dqJS+V5?>vi_#>2@gK_pgi{2WAe4HeGQiL!&Sx^J6C9* z7l(%p*r?3L+mWx>*)>`ns`QZSJHAq~7p`H^{fY{cU*! z9oUB+ddT?9bws&p-+%u6dDylmSircCMX-I=>8|-|(?l9uX2nYGDM9)vBSWSOi#u7W zuW7~?x5MQ@nQnhmzpjimov2;f^tVlwePJwRAB9krSPU}F~)yBUDV1S}Ju(Py+tJ5Dy_ z2LrMcT_NcJ&`6P8*~$E^9;RJ03^vaOAmAaClQeG959?32%TwDcZEt>jIsfoHrt$O2 z(__W3}SRb2BhWbJFPx8*t5zgR6-P8kkJZq>?hw z7?R%B0oalX&SFY*3=^8kn(9IjoU->N!}ojpb`fHTR?@-@%&;Wi1*C(prafH;VS=je zOUZ;SI+^s_vgu$yUGqj#q#r15C&H!Xno+zxfK zKmv>vi8GlBP>J>fVz(0V|0p17e03ZxOPH~M1ZAA+I*{_i`|lCg!Y~P8hFOs;Nk>bk z90a6j4YkQcWJ)p%(|E-tSWPaI{X0S;jVunbf@?M$@NlpVk2#9n>~dyu0#+&24UgO% zZXh%8I=1&nU)OHwA$gHY?-5%$U~#wsf9n7R*I+>=DKAg0hSM@ItYr*HSz;ou&tv}Z zfXc{IM`627_bUN-j?BRZ;#@sQ74)%h+>4S z!ple_gqp|{zC;#Oo^|)oUTFlBOTcD6d-}XA5d!K3V9SuqDo2Lq{0)sRLH|WT<}^I;Q|H zY7p*uyL0GyUTD*CU)zgvX7jM{&Z38_;C8sV1&ITaFFD9S5Aaf7PiSygldN(76KLX1 zp+Os@y&rDwKnFKUwZt)J!df8{@ZN#l(!l*i$s(WBhRxPS2!A~qLMRBCaz?&h z+m8No)2AHTJt)rt)~>H>s61dMA+ooT0n=d*67s&tgn&BAF}l$6O4#(iv+G3zHWmRb zLTHNlk&2iH_@5d*j?BbS!ZS(o(p)3Q=CLsbPbOm$X3W~tzn|n%ZE|e%1b}Z2hYlT8 zoVysHS-8PZxu|Yq+JL%V7AHz{m<7f#Z4I@2F4LcsIbo7}*acu24s`1&H!B-tT7?k;nr?ho<4>4*m)Mv=4H=7pHvvLH4J?%k&C+eF6aVMO<=1}oH{`&6fC_-8Mj{{n;UE49`#PkntDlV5PnpCZ|J%>M-#CMR z|M!0nXgDRG{NyLGvz@HN=L~D^qaXdZMqcEy|G~V0GFk`VRVPxvSKYS{Q$D}OSy@%aP|PZ>ty$3FI3 z=DRh>A!Qi)c^1)h^CM~X3%~I5@&|wLaZ{!r|35x%%BjTS=IN8)W3flc3uTESY4bE8 z{Rj3QkpBLDbL{52V!qh<^Lz`y@Gq{XWc2u`Bv&(X-!Hur#$q+eUWHHx8`4zGy?xTN zZ?`=1|K!zgv|VXgXg~760}mL`@sUR!k-@=1 zv%|@gCk^B5o_p@u0UNJo#Mj*Sj5XBF8vIOG4YR1c4ysC+c|K`nJJa|`n)={_Re<}E zNB#wddpEvaUY>pSS;M5g>#n;lv!k~=!8YHKiji-xoW#}WvI+SVbnMrEMfxSp+fl+t z-{|JdnQZ3Xb*^xI9!NFbOE1UsDy`TEj+s;a@ctz0O?KF6yta!}^iaW*L)1|9$*3~KH3QY?AJWPuJ& z=Z}hU!~hUI+@B^BDB}z$!KI#1Kb5-1_czY7Ubmjf%V0PNL{*>);kf91!H5gh_wyc6 zTRx|h1qt{NrPFPyCscBGJE6Kx89^v{c63~pSPl2pY0?Z0q~VZslgmjXPN<%d*|A&&R;xAj>hKer_^<`gnGShV%K*q%6S> z;kOHOUWuSDSS+SwR_hfY>z)0B5`rdn4${+<5Z=tKo`HEPpBOz_oIMCE%&VNIoz}vJD_6^ zY}_o@Igj4qauXrc09nhkvpD!6>2GQ`)OCGgg@vxha8#QpyAg?BSXvA8s}(@gM(T}S zvH+NEFnXhC)s<6esQ|v1qzxRSn>C3sA2$8bb1JB+0>R31(z|xEx*G7-NP-5Rsh~Z@ ze^aZIJQv~BOtvztPDR&fSJbHlfL#c1EC67*ZgWZmMq^VYPw23n_nOU|K9TTemI4S< zc1wtm1gr^Tp+^s6%1vf>KY>3G8uzion%7?V8`}J2IvxFLb9+dA>=LE z0@ZeaR5T$FB@@>G18GZcCIjuER)Dh=si#iG>^^iO z=OmLlD-kq^dg=g}%VaNwNr~}H{kiHx-{K#%4=8aDvt*xEz0W;;x5)q+t!_L0)M~9f zclNAIqZz!%TSw?Qkm~}i2z|#22~CQy?3PnN-L4hJ5DoK;NMCo431L4wIxI_#=m#=U zJ3Vz4c61$pWg8*YF`_W8cP7Ncd# zExxRFx~^j28FbvpQ!lgY2}z(U=V|Sf09k}*(P^BWiJk|6EA0v$4elt*1ME5q}nEWDtGN?(vGpnXdntT1;eF}^E1_vG1=Fiu7K z8en;|_&B#XCIzxwJ89QJ+ITIY@#`#Zs7|1lV|1+1P3^w<)qL3bb8QP~eMH8Frhn5m zH_{hJ@4DS+FIwfI0EYH3Z~M`QkXtDMkbRait3^}pJ@R8e_G9MSL0A+=2@Ul#w&>+I~5+it&$o32W>Z0)nnj<2Raj9u0<_>r)=>+T~Y>Ih%@PJh#o zHEU}mO6blz?}pukMmJ@riF1CGEW&k+mtXqwzW^qlAIy!-CGPu~5GpEPnN*7>Y+Sf6)Zug?NHA6X!r`gY3jL6}9> zrtL}VNt*FSS!`SFt8>F$1G`|SLj0hD=>FOs@4oA92@^G9ZCzbm@~gl0>++e;0Y>9% z_4E12?FVI&2$&}xe~P)@x>*3f?{)Xd``-0l18nKBR(rP2v-7%*1(=KEk^r{M!4ACX zK4}i`!|$cBS{r_bk2e06cl?Ar`lYYQ)8Ba%U&SFu?s=U&@b({P5_cIu*=+C4@AXvH zX9XP}eDFbe>ZzyXyWf4>5EfLX=$^apC6N?bkUQIs1&jq4N2r_X@3##ONOQZ-^xZf! zrY>oJp_^5&%!e(1s-WW=0k|KhpLmi;LHt7Id&;8SyLa!Fy|4XoI}OKM#c;G0)I(q+ zX|S2l=W*gAWPfdJ3^IUTZgkha?N|05R>!X^mFLb5%jD!38gMzeN=9q2M)A`A_H zY)s}C&_l`rfZ*H}2>Wv2Fxm-842GLXz|ar9zRLb#G%@;gnha%XaaqzZ4Qp}c>q8!x zZ(V?sFI_{=x^J~E1P%k(B?z6JWR*Iz0xOI#=|)&2U5#ipG4WC6zE1lM6VeokPBH;1 zq3>t{4=wD`F=ZAd04-yL;_-|)X$i6@F9EddAJ_-0rdK?KoE8B%P9A>-!Tx61zh@Aq z4v>)(rn-YP$G`w(I0uyiOcW#nl&!8K4z0SQsx zc$dNM6ARNaP4-|XjEGhMpCa0Z6C)P^fnD-?gvtY4b90^9YMvY+8cyoT)tGr(_rKO> z1#Aq^z>YZpkR`xS4Ie6@e-a}6xUH*OYU=*iL=fLxO*9#QG1zZ*w}eALYCVZbP*HiIRg9y z$ZXvYn>eiD*f4)JluUtLbtK#j99stmJzs%IW&yzOsdK{;=l=J>5^f6ticns&gm;g_ z0KD_SLFsJoz=5wiJbI&gEK329FdI|Uc?HMp$}ppydt88huuaqPGNS2j>8f|rM-ZN_iI32T z^fz=!udhYsc;DOGMRJR7^kr*gY;i^=#ztib#_yhfvJTURv~Fsq&h6}}eTCkGwhcpcBxFkN*eCPFHNbKDcWmyYKZr50)t*-CS&A|UV89_S8PW;UX@ljt@8AKPe-^i> zH%Y>+PMn-Xu$<+g{3`XiL5d`j^3`(=G)ISE;6&Np!)s3) zCQ_PJ8fSqJZ=BpiWJc``9l4{C1bqEH(SrCpEcP?0f%~eVopxYo|8j+ z_5-xH@GamhfYnpSk1?lk%i(>8WPW*Co?V!iD6CMeZ9a4t&Ap?CtJ%hzan278-9}lg z$~g5ThHkD46l|3&_Y{ikYweNIh390MF!dZDK2@9Js}G^)@ljU~b#xl$UvxPk@yI#3 z`^f91O|QdlmYuHWv9&E|Xxnym^LmcIk$qIkAxo&_4B_P~$Rk8Z7Uc6akYgwV%h zNm#c|WDi0t4mQHnMw4}AK~7zmkSO8QHE3H#OG(&jEcj8Un#p*s%X|g2SSyUnHtMd! zhX!yWEgfWh|FJi{0hTfn9;eRBnW^XG5MXid?taE*Ct0b#AuF&ZJuFPP10B*7sD~Z7 zEcFdexo6+)Xa={l;JIP^8+q8*z%klBRPSwmeuXeh<_w$XhEy=tV@%(cXH1DQ59;U$ zGe+NBnaR-OcS;7kRDufqt*!Ms-b|6_Jh2+Gq1| z)^9&7uiHJ_cHeD&UhON?&OTU)mJO}nt;H=phe^g`jB3qLti3kBD&PJ}6k6@BK1{7L z`q}ieZZ$}>lwH-uGxNpHpPOOb-N&t_Y5CHU* zIlgY!J5R96CP$LiC!c&uW@hK)$lV9!;O^VZd3OHnSirJ?E>hT-ml?u=E_Q7!W@%ls z>gV;;JJk+-oP~+S#T6F7?gVTcurBeUUeBI8Bfs^(|2FfEBx9CHb5jsT^=|qjGOOp2 z=YY&mWjL~MLK}*p)qnQf1=>cfyyNX}k$?AFzrppk#|kqfqFY4O${XsP(zP)?}?WfCKJDWqOjf^QTkvnrm}9P7P~VVWuR z_{Fo5p%9&(T1l;DWoZczqNhm6Z5Kd{lVAvepLkbc%!? zgbDXyKNn1^n?3R7hF@?iC~!7HW@$jI>6wN%rqOEZu~{=P3J`vlnDFLTGZ&Za^Ve zd}L|;L2Ozt4YQGpB>`>5(bk)YF5C9FEAcg^RBa`PP)JZrI`z}*Q=GMk>m^I5EES;trk|n{s1BjWB zQ&Q_{k`VRo4#2xc)d{5&uWrgJyS!=r_h8>D_;`8psC|RZJJil<_gyf8o4xxaO&RIL zu?3(=SEzL}8ZhP^4kCkwP87Fm<9aPkbkm0BUo0M#v%{w()6q^}QUfb1B29?PHxO#7 z32Jn+`X{TcTi@yDG<{MA&C|I_00e-9n%azHsS671wDI4b?miePS)ADsNkX}A4rI{4 z3jln#l9(hakDrZ7oc^ec#-Jv6YSqlXgipp8Oqw~D-a~800a;r(0l=Uk@VfL;k!Qv) zR@*&Qvx0#%QIrElZ3i~a0yJjQ1U1uci!dBD3-07pOyWLtSpg&z&~0GV*HMU&wYrsT z+4X4Uut>6o_8WlXGWvt{045=HO%>GL{M`EN)@QYg`A5MhsHkm&6Yw7!OlkcNl=PDQ z^y}-{^!!?PwLhO)os;q5DU#%*2^sgoaM${rlCgzZ!%}bpHYEUbL0M_96&afyC7jrSL8SaiwKsMqLQ%bd>v zU`X02>`R8!Scx%bMaly`5?{*5(!w&@qa;jn!lH)3I=@_yER5E0cNbc)9?HB~CTC!v zPfkfI4BR?R@bxlon3Kg@nD@k!WR1V834u#VSix&T&-J6Xr)rfXao}y z0$7`8k)c2xD!_Uzz*^oz-|vTs??j5DVV_@;c|;~)%P&O20QP5K`GNJ?f)cDDQ+)x4 zf{xa(_bCG{+x6@+I;H@+Gn2?EARR(G^ux}pwzld%R-1bnO}6c&P2J>|>Bx+nVR6s_ zGsBHeX)cX+Z8VQID(t4+s?mc)3caH008#r$?BbOM76THdouW01Mo@)J=l#edNT^v- zETk%vv7BcdV$6~R3>*e@8;dYCv@6Y|nl7g}7n*Z0DLYw^bdvEaNdI6@poyEQl?FF5 z280DBkTxjsnnKcJqYK;iS$4j>4hv|z(UoFdR8gkmMn7^LN4~gSOrKYvw^7Ze3Zw$0 zG&Z))ZDA`=W>@Mpo7PL+PhYKlHXpZfsvnw{tBcg>lK^Y-Ln1Wa;TuuLrcD5-<=OH-2!GNxl!l!Y*txWb-{YQ;!0W! z)h2Vr*PB0Bbwbal^}@aZAnQI>`RaRf{*7|AuC3qiY}c>=1y>=WNbqsZ#vBA=UOq_6 z2$rKE5BPcYK_XCj7MS1G9RJ1G+uBzN2ycAj8!lgG=iQD4wpc)AwKU4q#$36gI!+s# z)hAe=U+7i$RYv_o554({-K=w1_3?@yukv9>!|^JHxfx#%lQO)O?4Y+pUH2i%{}=z| zzro6iplj4>oN5OQj9OQX^mq45OGk)_0We(BDQC_e5MCN>+LYei%3IRRDNIN0hGdu)I*wE<}bpxnhudJ!Nj3cK;?laKQ( zIhmd#6EGm-@-hs_GWv@_>_7vlJ~`(Ca8ppn2dL3N!h$mF#^f4_64Z%??c_tNunA3@ zfrc)^S|ex?t{9y<>shtiSH_Zh=p#!J!tYYj(>WmR&3=Hlbs5Le9-mcqVLiGjsQv+t zXd}JVo31?MQtN2Xt&jydF5T!`MiwTpr!pSnvP5WK7iC&QNT82U#@)Sju<8{PzyOLZo7E2@YqVaGm_05oBD8=!A&W?5=+qhHbp)0=9}R7h`|Iaq9uMV1osD_PC>g1ZQ#;0HD5Bz>;*kBuI2Kv#?Drw6^Y&+uB>? zS=whA@XHNDrY1>22-1Ztj@j)Y)Swkz~z-y*sq*2K!{mS z+b*OQ!hpTcZ`;+UH;-Zd*f-ET>$PgPjq0@(g?F| ziA5Y4{vKI@qcV3QBX;VW2fOpM)b+fS2)(XdCM#)YM53_JK!0&CXurI$zEeGMu4qC~ zHT=O=1JFx9I5tKooc3otzYVZ84EwWvcMoBzfITd3J$HT?mN&@@sISi*n?i$g8735Z znuG?IR$3%dn3sBt*U_~HK#l}T8sZ%pl_&sH31*>3lAA?Nkc&+JtZlo#(faM>e%1ayOWCT9q6?Px&=6Uq0b#SSgioFwMgMhPIz#Pb zxhEl$sl_}hz~9o;A{v!suB~f=u)zWWi(s^NLh-$Tj0M8|>HA1Nrb*0PXuAekZ_W{} zT|qWRaeO^j9~!q4y2^rX<6?xl3tE~b5|mY#3%Knq;zV|31vvxl3!G^BWLY?BAXMH^!IE_oc^d{@yx=wU70i^=f_p z!~Sa7W%F}e?>F1X`fjT|w)+{a*^;tk|Sz$t^^Y_iRzI?RW-P?Dm33S)F+b-LyIqe%S4> zumFBsgr#O+0hEYgs$mtRf)kS^{L6(lQIL>w!~Ens!$2dnutfNlYAdN;S^}^yo6f;1 z1fU>nP7{Z;G*)DBB_OL%Nz*v_eji{8y_psIaJ>VF)$!>==`pOk;8b#Mz=9$GK>((V z8;8S5*m24gmBcz(bYm-k8NO-)g-h1Zj<-4yF=#sHO2b^p69!n1gX+hTBf{1Q^7~0* zlFF6=$p{m)WeCxBq7?%W7{W;g$WRU*N@E7-YQ~9NEk*(5n#oGtCu`ogJUezmGR~CT zv*&KXSRv!Rd1I|zp@Utvq`6a@* z$ZA{h8QX2Phd$c%+EMr2ZqxFx8m)D%8YuV^vy+mYjgt7K2|_DQS`r#DNnUi3e{wP+ z_2Ct19e}9@NKivqaM0fbP}(44(`mqzB`k_|2p~oTr_4z`3f8xxzqKw!!k27WJD^ZK z&RLmId@``sv=Mrnz@})A;b0nJatcfD%>uHj48kfWPcx5u2w6=hVO*`C9|=?8;`jye z0YqkMnP) z$pJipy`-f%Co8k)xh=)X=Ixj6um`5jp!lGGYX;>kq4Y}trSp*(0PQMzH84MEpQU&j z$C<3ZIJ(Xdq2ti8^V5Xpb6y7y@oH*?y3e_IzAQRA9#2_@(71F_NHqX5I(aDStF9!` zcCUEF&gQe0zyAKbSAORN+Gcdqg$47vWk3B^%gL%oW~a@6XfLXXi=w97-ajO90FNZB zAayK#LB9kDNp@`PqoGz-9kfni0mEfFAQgw#kZK?8(eEP^n#|+~Ey_x6NpFI1sxobr zK8yNjZkzM!2HZl%*^&(Q^rOW%M`m?Gk!jzKrhs&l_F=tIHKk}vFnX2Eh|nTHVHV(s zx(FB%B=JUw9dZH05)!LM+p-go%n#VDeVS&=RgJIO`gRF$9|b_i5UXx=WStIaWlfQ| zChd~uR(G z?rq9t+w!fpd&6!dEI*AsN`&iTJb>>ob50nXEp;&Ink55hm>|?9XjiSwAoq?=Z&7u9 z0aIa`r&Dwa)Oj^JNq`lxp^ft%jV?DpS%EKAUD6B4xUjk)%d~?O*+hXT0eSQfQUV@y zb?N`SB$;s+lGJBqjB26qGm98?j2{l#g$uwJ?lPI^(FtaOp|D;rKwgKx(Jbib zGwY^y)c4B3)qcd-@+x*T2i)kNt>>iPsrDa`qTG%?&2W7)*;wui#fgf8cNBr3f`5wz1s>PX>0Td)j^wZec~hQX-+xnSTO?n4hX zi$ldKtC>O1 zug6&S)%~pg^lHak>wBwyTj#Mp>opX-%fMnU8*N_}-Bf~tHsN9sq=o)}CbkCn$UKI& zXX**%urjwSivX0)dWRfjF|r0-Q~&S?ndfH#$jJsCO^S_(4j=QLS{6bA!Fu}7fK1QA zc7yd;0g$xQX6sl8K)xA0YGhWXpQdfVf{d|X26*eH%@xTs&j=#D^c`h~M^e*KSsx_a z8v7dKU+ckle&5~#I$64jn^m7SpHkU3?|a|-Hh*=+`>n@p{d~pFub~ewWQP~}?OG_+`ccDZ-dq$KfuZnN&j1KJSDnkm0?CHvrO=NVHOcX;rQP zdgEN`&}Y%*TSuF&jANC@2`>_YS0OP%u9(LuXCkkT?~R(0&WrAg^8pItgeeQFKw_H| zdP)rlzn9ROhyZM6c%BM+it4bdL#xiL**2b2KUavk*XOP)){)S_X0#a9@rIO0SR3*C zWC8U91`PD{;H)9^A0*KPY!wARG@MPhf`$P)G(-)?4Q!?7rst&}pl$)C)jHa7&17+H z($I1)ueXo~lwAkpDq)8*wgz7^-Un-ba|9lMv8)9Jxx$Ez=jq z#1Hk&325ZTgU!PrZEtBK6K_H;j*Lj6ehFt6&=xJi_U0zp*W;BZPR&V*kjf&gCYPfg z?Z+e`YlPtUu(m+M(_}*#KqbEd1Q<>0jNY38R5prHF{Cor>uYEu1eByEr%po>?Ak=QWDgdEQt!jI{$BEYiMNyp^^<` z^NkXIog31B*(E_z_o0!%x)8_*W|B*I5FstR9Ji`xbeX{&{;NITlwe?OVi zzf3sl0t_?&EHbFaIWDISl5k;nCIC)`m%d(}LFKTw)G#n=+@}(o~D>X#@1Ku&4km2?I~SoUGR@;Q>H+Gz$wb zJxe@;sh8ZZ+ET-cv^}a#ja|%>Z2#wZ<*{YAt-o)r2i7HYxGN%HT!F=zr>)o80jbDn zUIgUgNr==1{6=5WMw_Wa8li~JWf9P@7S>`7eZCzK*R(5uMr+fgFuJ;~%B!^nz?ueF zpb-E<79A>S#vedu@JQN->PKqZkVr*(5bs8^o9i{v_O!Tq#-0BWkd8>t6O z$D$H|74iq!5yIBT#w3FN@tT6Jm31_5`vAP^O?Zj&az_f~taLxB*+s-c*x{8FeUJj|3}Vzj z78>X$AnMAVW~U^xUda*dU`qn?7E;oN_Ntc!$=EDB+RB=VfN<0Y(Kn9BG-2Pn4-V3w z-<(8wFWQ)|_5-vGHKdx2waA@q#{xSR*s;Kl1$Hd3V}TtD+;R)3q!lxeE3Tun<((|m zSD56*eXyL1aJW?_CMKE0M*;1~?n7df1~kX)Xdf!9rIY45K*m)7Jv)N!ki|$`lE-B! z!i;Rh(IiO$jTy){H2Mz*(W;M1Lf2f9<= z(dV(lD$AtjaD-vHk!UDjucO{oDg7>!*;$?Ks;#sLRKpR|Nk^LtW{X44KKYd7D0kJI zvjK#ylNbO37Ku&>-JNenhp@>cInZ;MXQS**UE=f2N*bf*)6K`Z_qBIXZiL}2MJQL` z>Pl83%S+-VEWV|yn-E9Z7)c4rn&5$IScc(8K6u&N+sm_uV1&-o#gPe>Q1rp(E|XBl z3+NLAh%T&`q>f9iuFi^^@YS*#U@u~bpTbh<#rm5*6|9v>1KjAU?!`Esj(WzXO5>Drl zCjg|4tG-$S@Gt`y=#fAx+22(L0@l*zwIW?WQ5)z}Yw{X9ENG9rL+njxvx54RogoR> z=3wl(VLNq7cT6cUD1QZ3H08oG&F5;-ldA%21+cj`^)wr=$||)}5lP&OmRU#^78dEl$0Pvb8#0*b^ZU^5B(1lk zA}~eQqJY_C(uQ?i7x0j%Dn(Y!c3_C1p6PhR2ma~!%GwQYUDDk^ zGMOR5ebFIBXpjV0Ug?Al-r7l^FXhjj^PjdF7EgE%-893is}}YnbuaJH1Us-9(JjmY z%=V)fS|W)~4lqphM04eN*y#a|&NF6qnMDv|XU?VI-ixt|x&1BnwC>5=*h~1M{fBPW z{pe?v8*#I+*VWo1XVD8Co|=^=k{FR+NJ=acv{By8u!xpz2Zr!f2}c54Q)kjpyl>#Ss*cD;vIKyDMsyoHaIh943o<#m$XzLm zp;iXFJ75kGbf@0YRW=ouSH_itjN48qHi@~2oLhh*hZm@hU>T-}Lm6VQ?d&E?7Z0fa zD=?xSd0PL*ak>TUMHAZ?fB#d(<=ALC*UKud>o@7iS&#(=6?m%Zh$6+$evTz%-H*(RFN z2P^~9z+`ms-7akk$113;;gDKlyxTCpmPI3gM}QDj z7dl-|n199k8pHal*a%Axy9n)$Nf!=kE!WlkP5Dt4<~`@ST#|fv3EQRfazYQF{Xl!K zj4e%*$YP4J2cQSz>FXXOTe4>7)eF&Xu!(L8p4tfoS3rNT4~AwJAdF@<=XZ*MDp(-% z`hc7`Lq_$vX+Q#i2lU74sOt(`mhg0)UI&@5$s7&9lS<|!Pj+B8w$Y22%b6?P*dKki z>)jOGx!V3)a3D^}dD-9EtmC!_P>1pni$DTZn#%h`@$S|T{FYP<1IZoT51;Kbk0QxwO{TXLJt-lwpzl}wJy79 z-(|A-j#Ne@mWTred8C(qvPhk7$c1DXo!SztswJ|Or87yHpS=LX6j-86j(Wr1Q)c9**>oy`! zQIL_*3A12=Sch4tsY9=|Ry9}ACrl=1(V`^*7WPfA5%vmr1LO)5ZB>RxK#`Q>ri^*U z4%L8FJ=5EccFWu>A=c+klb9z4fLW5JwhlRZ#{l<%Bm<%E4&*cf4!z>#`lJu4_S+i% z`(o|$JcGT+FAR)^fiEq7nfKL`RAnMWx>-^$8_8AhML8~w-FKm#z(qQB{; z3DAV{((%c)>Hts!q(!H+4pw?c2Z^Z&wZ6D;PVy<6Oq>Ojlm%Gk?b6cSX*7qc=eY8m z=ET?W=Lcy|)hYF|>fF}bC-D-}EMzTC0{+*+f(y4b%N;#~yr)*wF>ZONAAPj( zr7@D7kzl5yr%(C^`b^Y+4z_aTd>JMwNr~2M5@$icMV$(=K+^;pUir9gvRcQfJJ}Lh zt-}E7upj4;MW`oHOevlxiCSDoBs79a1Xo<8A96A7u{cz{Wl)^a)+O8x0RjZKpbfzt zg1dWgcM{y)Jp>5u5Zr?WcZXm>8+W(Ht8&s7wTIv#e5zye{}L7>b4}rvSFw=8QHjYUqoeKFu=6!paXor-42?3@=R>k_3$q8k3midZ!M zyTuI~mcubwJpQ<_{JxdImdcG0=W>S?k4qOr&);Yl)An6*1Nh0B+nK)diBDVJV{#!i zMX}fDBL+BsFBr{Y5LRu%gmwqz+<)z&YRUxs$K{?OC?A<|B|j`rHH=K1dCQhdFa#N9 z6!&+#f+yd%DMk|A=a2i-p08?+tm6lZcSGWERDO7kZL6;v>)zZ8-Ui+;cZFHZXMultt{D5ukkwl<;X$k;$D3Ve{!h_(M4bUDWgg@>QFqAt7@-_*Cs=uL_8P%LARgB z!N^?Q5HMvD7)lM-dcmZStJ&^OW-pbiL_qlU5SE}&NRb=tN=}2f@?J1=rOSI z_zy918JXzPHOcH}Rn-1Rg^vAgirL0`UAH8rsfBf~+OvKMTsD=FAu{8yl#H1*&tYi% zCt!0p@t-;Oq3)ty7QmaGo%JJc{ zadk1X2&zpD|HK;leyn5T>u+`}=9!t54d~_0X-*Et6Lj{zc@0Sg5vgIHOCkO$tL~E* zTG8+oU|_d*e&r}SB-;5?MF(#U4y!E+>(#>|wx7)~$Kq^=(VE0O0!YiWqM@nj-6VEq z6P1+i;y41~#g&bnveBVkUq1e|GVkg?@DK+Z*}J{JHYqe|I=#Vpk`wLsJOcueU4j(*$R4|=N8nZ0-K_Qw$#u>9X7efE19 z#fy`D)poZZ6XO`75P~pyrC&xt4}2ciZDb zi!%!jq`Bh^7pkCqLfkh;?mtQGd<&o>5(Ec! z3_jjWl&X*h44GMlZ|HPINeN&Acddj%u1Ios$L6>H7M@d-ZgxS>~aL0&D*}e)B9f;&<{* zEXrW)gH;#+=|MEH@#vq3yL6VR{prE5BE>2 zs@shtbXdO+BrESYV~eZGzWe!sWMt;lZz(?041acF!;;|Q$3YUYw!{8n zf)0p^xcKSi!Cz>`w@aso(jSd@mqq*;yy>gOevP9{c^C7J44Ui3HoONgSqzU>Xgzuv zd*bxpeXF)unXB^T{eGg-)Qq9o zC9735)vx}FPed5S=+WW~B^kj|^czSQJUvB)9W)xqUKL~!S9m2I zvjfGwIL2nx9@37YI-JtG_m*bg%9YBj|HFp?1A=(elF|MDoNm_}& z0)Iv2SIAabOPvq4qp3T%%8@!deqW|s$Lc9JwIO|WP5I3?ht!Fa6vWIU#VSL5Z$?sf z#Y7V*BT=jOO-XZk>EH;eY4B&-#$@R2ny4QbZddV>Gg6aS5d7m@sqX-{Exd3h?CRvl z((1`skldX?4pXXwUz+!rVOWwJwQ!{%+*A!7_?*cRmL94v+rXl)HU8^;!T`2}`X-8sdnxj$i*As=TUr(}t| zNliyN@aV&wF9y?=-&ue6w#<&r(g2gIgsZUo-E(+{8O6F+M&!pgS*kU|uHwoh3@`dZ ze9w|aUZ$=iqS8}p2Jj-B^s^10rEnT4LkAPTM8n5K3{djHZU>LCUXPDU7^}bU<(AZ| zBp-E2H&Bu$e;JpsR~J%0e-y-by?)+=PB+4Lk~(hrNxJ9LOhFuHV1IAvE|6Y%;j zy#%wX!$lZkCU&e#j-UL$$CNi$r#Xg*z084sTx*b582Xt=0yWpb(S zKg)w;hyjBq$X-ZB&QD3Qt^#Y@)RGSakV3Dz$|If6Y6-srt5wDBhKu~ZS_WUdknW1g zU`G~Bbxb#(#0~zn9K3^Xo=M2*I zmpe1i)SWJ3xq`4HbiQ-e7Uak-(~X(VD6$coe&vo@`!B?5brvQ?K$So>to~}ixsP6VM+d-zS(k!^m(>OP-*p3pD3Ln{bUjSe+g~^@ zy-OsYCs_T3+@hOiHo9j}#?#i*@M0hUQkhO(I`UQva4N^Zg3z-cnQ&~i>$R@im*duH z-pxPyJDx>e$V%c;H+V*AK0s2uOe;G1fGgG ztv>fxXmvu84!Z8A(=Jc1XZV=!qqEl@2@6g&`O00dbG}G<9p0rM0h>kfWf1Nbo0mX^ z-rjBZzAvReUUaVoVXF|xJ$A;U53G<;@JW{hfz$ZC)vm4Jo4Fjxq4>%Ft^@wF3KWVm zDNaXR-ZX*5jLJFQJ#*s~%45>ZFm;G3?mkpAkt^oA;%1 zyn`YIB?KX=fA8ZQNS}vp-;-HWlkN*mqZh(H2T^#eG4=)|$CdbnMXW?2Z8>)kLy-xa z`LvC!y?HMWj{eF4#H>3Q_9xqAKo=IH|__CNqLFy$XO$YFu}Lm;!Ng^$}5<^G7_^6@668(L9zXAEjEN}{JL_`{jUr0!w?=4b*4XY zO-Q_%^k2`g-3p;rvV9^fxrT30w3%@A7QWfLFLWIGK_VL+)1$u_sBj%If~|~)kN;I2 zW58lWWl}XKXdv^*a!UOcb99ukh9fOhW|jEDwFwVz%a1NWTVy>?^W4R5xhz)rUeK~_ zzFzJmpbV5%&gV@pGNrp7vSUAB^s!@a^zz~0;BgSNllPG%wC!thOWZG;4{#2Q^=agJRWjIfb9^?+)AA+vx&7`b2iI4@xKC ziQS1^VS%+YtVjkAxnq;Us;V+)JAZbGi8D^I3RfRES6PmfwCgvRH_Jt7Ua1mlYHG&j zv}tudKKhbk$Q%pC`nVe_gIqk5w2Y#EwL^c#YFiwY8`C@)4SLBhhi2u+b~(I!tZbfm z_K!Jc&k~N{dF~_|Aqz2fZKV~4-laGI!quOj;d!gE{k{*m4C?86r`xWeok&J{`Vkf5 z3FUF(cB2z@pIcBT>a33 zU}ak0#I?1xNuRMs#kMK`%Q0n} z+CVLf10gp&M+)qII;vuXGfTa28Q2lG=8NHj*OOL|gdtD4esbKG!+{quwNA4Cemv89 zc5kqf`0ROSgXbIJW$nGejnP~?sq8M}3c+EQz}4@!u;zx`-aIsX+8X_e z7%;NYJO?S;nVh7%A@bM&m4W@UpHwdkA+CmRw^wNk#p;VcC#}5$h$h%A818?7fsC-9 zwE{}y{$V4WfqUPSW;1pcfvb+-r@rk7_kof$kObv2WvS?KKDmjYg5oR~tF^@SIU*3= z^66RR=?M}Umh|QGz{H1w*pLuPrbt&^%&{ji5$&cxMKEW?`+qHd51*ARCR#${@JI9x z0-kRcJf7~I1*ME#P`}g3y6&HNKhEU7m}#?kE;iaOoXkcfF?C$B>8jg<0`3z?1-%wC zGKi8);;&#AeHfJi-jt$xm(i%9=?CR7(nZgnSd!<#u7`cx?wMrIP~%Kyn-hGUqU>NcIA#+moS`kk%RbcjL+81X_d2TDzvLw5OlGk z^5}GSq3LBgZ6yQaRH%Z?N9ZJiKOw7bNShJqaijwF;q=h^2M+FOu(%F7vCiZJ!qnq= zpovU3pG{?D^95VL;KU4wYp!vAechoOHs{EcDWz+#h}P6|p%v}9Z~U^I#@(~sL1xLU zmj%|~2~;*OjeARV64CRPN=X6Mr;xX`yDX-)DYfY9A76E@)L!+mGXHX@8V}Ri2#g=6*B}GavoK}#Q-`>_S zkLlM{-22}?Ad!H)Y z#KoE}%~IJrS)B7i`fKh19$KXwrw;GiRck@i=3X9!lm0UG+i;PtIA)fPA$YTj7wsyK zMb>~z@Pz8jbTQX(S5AcyhtP4$y6O4zS+hq%D`a_2Yd>%FVinES&fpBDXzlYDcKUCN zlJ^G|Xb{K{EH8r&YNkZOSQ?HZu>0fKUv67r@8!1Wak^@42{vkQO=IDuWe9!C{F~rN z5qa(W=(-z3ykKu%89m3a09w#nvAa8aPIes`kAgGp&qS%IKV5=0`rpiHdsK9eP8hdY zl}s1`o<@+~G1dgWHV1}UR`+Ex{zFaCC8_ee=#^>mJQvn)_lSX~gj6rMMSnz8=86l1 z9hQu_)2<2zgk9>6NlPQ=Ogy75BnvLKblhXg8dl+KU}D6cJ7SVsUeldp~o#PJjN|1P)1jB zKx(JbzvA((%PWt8GAuWI&+nq!73N2toX(!SkI#<23RgGvT?{-1!X}9_iIMx5EG?<_^T{OjlD7L>tAfQ5j^mAjL_0}5jx?Ne;?CQr9*whBV!<>o+%FVSBX z&qbaWZ3x>#gJP%hniw{MgT+?b2@-c>?R*LtMD z!iO<$CBl&1!0QA0{XH@-oDAS=?9!v*sAtOqVDNNHYe)s(X(jCLLZ&J_>e-cIFiSt;vBh*U| zkqw>)1m1(l?|W`Z4N(ob`~%VnUCoi_HZPs+ZDF(jn|A-R20r7qtS_17`ak8P#mK6$ zqfyjOqff4t~ zDh1LNlk`}w^moeta`h$Q2uIjP=Cw8e3v&k}@Trh=@wG7q?t<=Qu{LC2PD+1$@u%C; zFaMVZh*H78Oew)mrtU#U_x&{Vc7El25!Og-cb-E!@7&oZC;a~U@ZCpjE6!e|{j#JI z08`b{do@~=F1y@~FEaie40ZI^YY+bQpLl1ABExDpehveDglKei}(Bw$n^II}^j7ZoPd%W!9x`)QZQgcW!gp^@dozT)XVCTI3TgAGHoy%?Hk3|@tCamT z2-NQsS~!dvm{K=~xr8T_lI%7=6T&WJ&L`s>m+HY?07F_rjBNCuAkYx}%``vK-wM0p zPM^xYuKO!iEj>-U@lm1^X!Xmt${;JaQGNl(r7*P2$>gz`9ZT>hgyJ;tcF0KjyG35O z(n|`z>Tx0yUg zxE;ZXB|^;p;`+vRNHNF;v`|7aS$>62tg$Y}EnLO%`gCF`QvdoO@*1wGrM3Lxk3_hj zKE@1>j@#*Vh=LuTs0C_%4#!xTFf``9P(H--@j*IGiW6@9Bzp8)Gv;<~LEvOJT^-=d z1@OGlg5P9{Kaxg*CfM<@uz6*aW_fTU2?yVKp06W4?`Bb2K$NpB>#dua?Ms^(NWNIv zxvj-t8+QQg&#VMR9#=$3q4-`;uYlz z3{$=+>6mC{;}*jpd?T~t}$LnpTwP(rk;qiq=+SCz=i6urB#G3eAPjW33MhqrT-cZA?-`+Q2}qVg!lG zEJi=v?_m7mQEq)Oi*Ge1W?cSafR5>Ta=^}mA)lcsX8to34Q=#vd?8?Szv1c05I(V@t%WV5kn)}DshuG|zgflFsycYbU z{{El(uL=Nm?l2kyWk35;R{hGx86YJAQ6giW%v9Kpf#ft&7e9Gs_JJPNnzVFL9~teR zA_gO$2XlRTIkukiY(Tu4zzqenDI7BWY%CV9)LSGSLzIf)SS_+g6?H8oa^k2gHW!Pr z1y7Z`suE4qnqH-yA3l^!lP9zQMT3_wHE(?`5H&u({DWNQXWFm2>gpIjYY@wtlIF)h zSwWNZb#wxDM01b`p7S?V&Tu>^eIVw%<8JOKpVjPU_JoTb%0`mY1trt|4G!HUns$&v$TgH{S^tQI`dME*S2M4mO3ELIO-2id zYmmhky*+US>#Lt)Kf0ZhJ@ztI>kwYPJV8WOmfbUfC8_NZ$~F+VXCvPm&7n-o>Ku5< z?txT=F%)QhrJEdrDI?N4L$2nWOPj-n@aT_b+p<3DdnBZR%@}s#td9+qE6qKq`ipiS zrP{98%y0GA>N2QaA-iTe!fHxafb1x${Mj?pq+iCOoo&Uf>-@|7S+nN+)6Lo0{!K3C zsA^`dQG`cTbpaj6p9BQpxy&_Y(k%zQf&FYSeKoZ`(NNN$h0xL)ZlL6vB!7AB?+;wi z7Q&!}EwYsk?+{|>TlW^=;Cq>|JmKdw9`pF;54-P4ZQ}(}`TXRL5Cf2gIxStocXK9G>OqQqSmk+(_>gpo- z{)9i2lFlF%dyvUhs@=Q0pp-C?tr?qzB77eq%Y$|NY51UWh5$nuk{=@#$ln>C$mEXn zZ2I#A=Zi&lim*9tFd6XNrm0cDpqzVqd%fbbU#)EvFahjB@&GlRy(q189CFb{(378y zTtaWRF>;(Y)5{Mwm{H5Bwny}3>~FAXx9EqztPy1peamu$@7XxwPP#oWTPwi|t+s&Q z!XG_rZfc4nx=m~cP+AUEw*-FN$z(Fx7KB%}<>T|_?$=3@G;>!hzVWnnMb><(d(*Q2 zQ0!oAy1mo{_(3+~814z_Z_7f*A!Qx$5!O*h8O7JnrH0@9s=V!rvqb`j_w;|Z#ya;* za>zF2@VUYfS@cBh+%6M~G*qOqE-l!7!^6{DN>X-^B!U*XH#C3!Z8DvqRkiu7Z`!;# z_Hd;T{Omtgrf@ROv*C2UJj?&%`1~haH0V5tcG2l6)`~0=8TbAF53gDJ_;hqZ|GQ@sj&Jj@G#Luziro?R((?{t7Etma6h8T zi#{t_O3LJJHL?pozdc!s;dhUi8kH@R?faYLAKZ1&)BX}{0;^X@Sn$CZ&Vl@TVOUZ^?$9 z`rj2iR;KCM&!{Wc1*>iLw1;WN^;T#mJo#-4M1ytQ03 zIcNq8KmJY6Efv3VJ)UPiS;T`ER9YMy;t7@+JbBAb?GysucUqkCmrQiDnVYM*G&p-R zo9-$i<$(BIaM%CU&nXh;8Kx=%_Wdsh2()xm-|UGO6^g!uhDJMrl%3NS7IcKlCux@e zM2MJpul5M<7A9LfV(!Lrgf29xp>d)OEe)l&u4d{kL$TSjt#F4eN{oXf{gHU%w>RTL z^Q+U_8h_doGLBD)znzTkGj9sNlFYPcusnh3u3@9bn=;%u@8A>?y|Wa*@6b*x3&{Rf z{xtO%e)rNNEz9RB_QAwg=={6AY;V9emErNxF=-y%N*rs~{in^-WRCocIc))NV{Z6( zZpu?nT+Ka^PpX7{S>ov_CNJOe8?LXDg{Izqp^?GI!OF_&l}_Sv6_EGcnx_FMN6;nA z+w3P=4Z)h<(M*{L^{J5jnhX-{B3+w}vd|+Z4ewQ+ZET7b&*DVS8O#ihdrEcnG{rsg*k3E6n zF_vd(7S@~T$TxQ}s=a*L{d^*_a?n$HD(y>-YO)BZH1<7}vPrRceK~)%0SmUTl^>*G zcG04+#dgm8Ixj=aU;OVGkW4h3x(Pc9j3u`}pzDVsKO5Ry6#iE(abEMK#SvxV+TN(puoN{zJu z>iqp*T|%?iVtp}(V9h!!R(d-4e>x*@wkb)RM1;q+Hx=BqUWZaJ(WV85<-r(Y(PUQY z-c&+H_H#w{@rDcw#3NttQXK^p8E)`Z{bXl1g{D&B7ru;MtoPFib!vDxeR6AYmOKvY zE}J9cRRO+-#oA?_h3XW^8dDiJwkmj1*%*^{q~vBw=!ua(2AE*D8v>tI%)han;c2vc zoC`~4!%k!zpMwxQLJC^*V82)BQ0L?=Q75KtPxdU0Yfy?Lt80EGf{V)cr2$4u+I^`M ziL`nbw|D3*O)$TG0d8h}han(3251?nreT(OT{(wIbihSKO;1+y(~WzfJBB@J$uq%P z5F<+!fVcHsOS*02Y)KK-0+H6d0Qm^Uw-iSD5}{HXBY38z28Ti+;78~bEePoGp`BY{ z+7Xw`)*5Jqprfwy=1DFatuzI)6ZV>aXLU9hyGzmj-LU&c+0F?N=~Q+{{lP~h<=^TXZF8Z9|41(!3HDKP>QjI3%_IM7-Y$8? zlLM>SNj5Esyv}ql<7DROaBh~W4Fau5E>h?xbEo+ zuYeXg`34_dgiTR26CI%rJ;di&J)^H>bC5mL@UstsY*vg!XVDq)u73t>CoSbm+;ntHNzoXhn7nu4I=qUPzEieL&}Z<&4h}V0^Pu3>9J5_S$0*G3th_=`D5GY4Nf~d5!Bjmgl+uGzji9l-(1M2OE>qdwA6o z!>Wq99WPk0#04XPxx{&gYxO%L0nu~BoDnAk%$ zx=R1z}+$I_rI#PT0iOvEEa~?#XEb4|B(}>Smi>%-dRpx>^dG3NBr+z zYA~Fux~QJu;l$F#>E$5X(hNHHOmCvpM%eV5>c|W~!-@`}6|+NQL#e*K?FvNbKy3nG; zx}r}cYGmryF^My+R(aT1@=0zRMy=_XrxBHroNfcQwZ*%2MAk=cZwm^R;`0q>N(aRW z;xoGNt3?;d>cQ<24kFHVKd(3fJ}I15@)!*eq82nQ(mw4bfwSDek$5qHKQ|MH60?+L zCF*tg(5!#+S;{ls&@fkr$)B8yjMbfcvtP~Q*a?Pdo1K31CEn^Z1~gV0*6%lE(V9{! z?L9=q=%4h(iPS0;DFwjlbM>_K({7Yj$sPP^o8t$pq@-%$@z60mY`+YLY1giRM~H9aQ)+En@vDJZf>r+gXn7Vevb3* z*I+c$zDpUAhy1xS<8M>~^27C-4>^AjuyyH33L`T8 z9vz*@PBk6?32O4(c*gQiv8OCuG?rMaAz|YsXtV(;yNG{Isey0(1!Jf>*%|)F{I_Hl zT(jlXyZ*Z4+k{rway&1D{v6*y>3Ci$&Tx?wWJ4U!X*ji-#iV4PRBprFE?xoD2)JwC zn4Ab$x!n!)B(r}OO#D*w564@zq-jj(Mk&-_c*~kxJi~XkWy{lg;DZkGwA7S~IXpWc&uUApS2?89d z2S$#Mgm1%Xx2vL)=9ej>w~|1=9>&fy*zdZnxL5f=j7;b~ ztI}ZV+j-|J*^YgG(~}$H(Y8JJ#2_vq({G!gUMI@)%)N`kFFkyOMF zYjE4@cQzV*Z*?*e?H7V9)0*2`1zGO>wpR7SSyq=1jRXHbNE{2hn)S@nQrD9(gJX)_veLM7b-AZ7D?ER4enXmwS~fb5*1dv97Ch^%aFl=q zdh0%eqS0NT`&>C9J}rn}E@XR~WVDAx9T8jZ=t=+?J7j!9b^Vf`d!e$%ku6f>2g(%} z)$DvqRjqp|i?P&|Vl3w8I!9gFrhoI5#-rbvjVu_k<0T}7v|>*?DIdFQu}*$)Rr`&} z<&*N)fWisw=Mj7AE!?4oaw2>6n2G+1Zpc6A=evWy_`L8)(Q_U(d z*5^TuDx0Aa|B(IWaxf+{tB;(IExtYj4rV)(#O!hVL{0LUq<(Zxs?^()|9ml^nBM(1 zlNTc%>1c;G?rTxtm`(ik(szI6Y9$5}H)Pa9#M!pOQ+?TN*Mx85;Bd#^zesdMH~#k7 zczYu!DwNQp0+7nuX?g6Ou(d{51&G<`QAOd}-(*xgGYN^qgZN(&qjK8Xe9q1r2iv4+ zW0HH}MELqzG*Dj)h39pKr|#wQ!Wn2WhDC%%#RgP8R2Uf){@_{;wqv{zuMV8u0J{C-=@{ z;6(fwPXWpA`21LAHPE;7JDaL`jYs0v{*PpqQ5&d8klaaG6`kW1}00F z{K;i+^4$=Q7^a|ZMSuuro|0p?ggnDf>_doV4d*O9aY%%m>yp!B;Xg4j;_j6N(9t##!yhQJD?ki$AvEB7{%&&-5OgLz^E=jVlp9{sH)wg`R0$@`!Lbqvl`mBw z*=_SV{p6Q%e!_FsL+qyq`%%xuFvNqRz9>sC`geZC@y5I_am@U}*r!VKQ%a^-SIVL` zGb?M*#DzZ-`ZdiC>n2rZz!b?TsW5#6H{N`eA)1I`0|#UR{v)M(ES33JiYJUX`A}o* zxhr&LYwgRR@30QE%n&|15`%%-Sp*V~y}q|xV(m8XThy#rJS8k1p`rOdcCD1Yl?*bR zHP^gsp3gAmm@?7ymo+&a}g;5t` zv(Jzk$5(bs5w565A0DwVDI~o7mr?U*$o_B2y%oUwcCZ#t^==_+xbsAucl`(D!Oqft z&-IfK*2mN38(XFNg2j)Xezt$~pdW8DSr;8N&e|9M(KCFH?XZL`>9_?yl>Z#hkh{{T zILj-Ka?qD$gGV6m(k^?WG0QyV7Cbu4{{yFU`W_-tZW7aZ$UJ9ysqR0mj*AMC8n|>G zBEqSR&*=O%Scy+R+-zBcQ)Y;|Ts7Ht&R@46BA!_H%U?{t7L)p(`amGiV6>k%rHIUQ zPk-42N+#t*G`8tPkIK}zn9RSXjTX@5Vg$;?YiMdx6DTe%A)pr}&V2B{XT0wisZA;J zYcTnbX?6w3_k&&%1~$hipy}zaZLJ&Fv{zfb9arCTfXRvDkBc?+SiyiVf8L^Wk#feL zcf@X^$3!)%bA6H4V@<-1c*xvTr`V!$A12GYx+YK*T;m;Z<5R%m4b$BGPE4qCqYk!u z%RgazGjd4pyK;mv0)-Z8agopxpT;b)(!hUfiSVjF-B$uO<&@kR@{6q)05VZ|;wNUq zEuHwfQ}GyH`l_m^HMuLNq( zA7nL}|I9PrUpbEz@T2x0o^?sRrficS(ld@&S=rS9MYF!(f7fqzXd3)2J-hbq#b#0o zjzOEkUhO+H!i|w-tfb3)LM{|0M}eVl*eENwPo34>k_mmfMBV7<5Qj(>Hq=Ge3Vh|# zOi`+n@}olftX9levTg)x0th(Z;iarHx^|S^wu71mi(-vbnb&_P6CK$}Dc~d;Jg|WF z7$@Cp#cq;|imALCH}Mov@?7!qDm29QE~KwZzkSiz+NBF?&_uI|QWmVmqMx(@H1J}w z5AA&)e&mwoba$G>;w;r7gND+glo}Ptc;4)mz1jtx49m`3zFH8ao6a zTVQ>ZSu{$P$oQ_d45WMeQ7`WSfxIlbk%oiT7resrbJ92MS2zcZneHwg8af6JT)fG2 zh)frN6Y(Pmgp#!0IZLCy0obM77fmXS63dbmyNVN^=6l|5jZM zm{jjD&@u{vajpl|KJc;$_I%WfXge5O*$*(j6+a@3Nm6{}p8s^CrF8%xl5>Hiu{8t8 z2EB2Rw68m>jlES7pceK9+lgJ6F?gT1f~OP3@AIemjP%e|mRbs?#=GYrgD}W=0<7`wSzPK9F=*93Gv1Pl?7xd6&2F-SzJw#8Ad{7 zxfK{V(M+uP?8g`lgG841li4gnfnzh%{dZL3}f80=@j)=3 zdrk>gnoM*AS?L8fGC@ZR_8ck4;2l-rndl58V@A+TFZMnUa4dt-{F6B6{PVyp2P^B^ zI3ZVuf}aSTKgCX6oezEab@nU;s>vfgtbMk;{Mboe6ZnyTaBzhILPCQwGP0#d?JA0X znw)IJ0v%kQV^nFZZVuRA{{wgB^TJnA_=>NttUTkjCZDfNdV^0V5*jn+lXZmqnZ@a; z->>*CCkL5V^Pdc*C=E`eRnqjMF}_JQw^w<7=e}aPhLyOGVQe^OT*?j=vwWa&cx&vp zM<#AO8|z%|@@ru`JGB$*qwyH`(UW3rBWLWt<{cago62gzk(b-M{{KOSX(mr?suF9BarF4iblW~cJ1 zafQ3@jedzv2hMhal4*YYcbz60{3r(G`}=e<1D>ptoRPXM#MDbNk@)>CDgK(F=X$c` zUE(|8Z4GvUufgR~DQpUBC3!<|3ej5$+n zv;nE**OXv?_(GAQ3VO0xmI*X}Ms@J&hPGFxO*4+&Ir}y5wTN3hxR!W>(^y~z5EN*m zYbt~AMRTEjr*XMAzgER&RAUE)vaWzEM?9-6IR=YdEm6bezNQJCq-v+kf3I!$K4~NV8YyOMzRZ8F2ro6TURmlGfn?@>|i?utq zfv;zdr_vZ6uS-AhCwYSz8jFb8cYa<(1j%sT$2%nxlG*=_i-i;X)N-}c9BA?iaeU>O z`=wi>uwUHP24kDiLNrO|A|3TElm0tpU%wuuCAjN-k0G@YJ!>1d0lM*P;%$3fWB%)a zMCAAEc6}2yf74#R;I}7vj9&@$gSbx3L->rzUc7Cdtg_wDs_|H1narKtxjxr>W63f$ ztF2Z3!c)AiYng}X%Ev4F)#pR(jUP5%(nqe`Z)8BUH_IE{X+yIa-%8g1iW31a!3sj|&O>4vHDkcE4~}W`-EeDtk*pBbr1SJC>>}D{{BNH+D@= z7VRR&MqOuR_VS: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�n)b9YxVxf*KzcqmjeT5 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 Ze>y2!`Nk>l1%4gyaXR5>. Saving +a search session is only available when +<> is off. + + +[float] +==== Requirements + + +* To save a session, you must have permissions for *Discover* and *Dashboard*, +and the <>. + +* To view and restore a saved session, you must have access to *Stack Management*. + +[float] +==== Example: Save a search session + +You’re trying to understand a trend you see on a dashboard. You +need to look at several years of data, currently in +{ref}/data-tiers.html#cold-tier[cold storage], +but you don’t have time to wait. You want {kib} to +continue working in the background, so tomorrow you can +open your browser and pick up where you left off. + +. Load your dashboard. ++ +Your search session begins automatically. The icon after the dashboard title +displays the current state of the search session. A clock indicates the search session is in progress. +A checkmark indicates that the search session is complete. + +. To instruct {kib} to continue a search in the background, click the clock icon, +and then click *Save session*. Once you save a search session, you can start a new search, +navigate to a different application, or close the browser. ++ +[role="screenshot"] +image::images/search-session-awhile.png[Search Session indicator displaying the current state of the search, which you can click to stop or save a running Search Session ] + +. To view your saved searches, open the main menu, and then click +*Stack Management > Search Sessions*. You can also open this view from the search sessions popup for a saved or completed session. ++ +[role="screenshot"] +image::images/search-sessions-menu.png[Search Sessions management view with actions for inspecting, extending, and deleting a session. ] + +. Use the edit menu in *Search Sessions* to: +* *Inspect* the queries and filters that makeup the session. +* *Extend* the expiration of a completed session. +* *Delete* a session. + +. To restore a search session, click its name in the *Search Sessions* view. ++ +You're returned to the place from where you started the search session. The data is the same, but +behaves differently: ++ +* Relative dates are converted to absolute dates. +* Panning and zooming is disabled for maps. +* Changing a filter, query, or drilldown starts a new search session, which can be slow. diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index e8faccd50661a8..9971a6f574f9c6 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -1,13 +1,15 @@ [[search]] -== Search data -Many Kibana apps embed a query bar for real-time search, including -*Discover* and *Dashboard*. +== Search your data + +You can search your data in any app that has a query bar, or by clicking on +elements in a visualization. A search matches indices in the current +<> and in the current <>. + [float] -=== Search your data +=== Search with KQL -To search the indices that match the current <>, -enter your search criteria in the query bar. By default, you'll use +By default, you search using {kib}'s <> (KQL), which features autocomplete and a simple, easy-to-use syntax. If you prefer to use {kib}'s legacy query @@ -21,32 +23,17 @@ JSON-based {ref}/query-dsl.html[Elasticsearch Query DSL]. [float] [[autorefresh]] === Refresh search results -As more documents are added to the indices you're searching, the search results -shown in *Discover*, and used to display visualizations, get stale. Using the -time filter, you can +As more documents are added to the indices you're searching, the search results get stale. +Using the time filter, you can configure a refresh interval to periodically resubmit your searches to retrieve the latest results. [role="screenshot"] -image::images/autorefresh-interval.png[Image showing what refresh interval option looks like. The configurable time interval is located in the dropdown] +image::images/autorefresh-interval.png[Refresh interval option in time filter. The configurable time interval is located in the dropdown.] You can also manually refresh the search results by clicking the *Refresh* button. -[float] -=== Searching large amounts of data - -Sometimes you want to search through large amounts of data no matter how long -the search takes. While this might not happen often, there are times -that long-running queries are required. Consider a threat hunting scenario -where you need to search through years of data. - -If you run a query, and the run time gets close to the -timeout, you're presented the option to ignore the timeout. This enables you to -run queries with large amounts of data to completion. - -By default, a query times out after 30 seconds. -The timeout is in place to avoid unintentional load on the cluster. include::kuery.asciidoc[] @@ -211,3 +198,5 @@ To completely delete a query: image::discover/images/saved-query-management-component-delete-query-button.png["Example of the saved query management popover when a query is hovered over and we are about to delete a query",width="80%"] You can import, export, and delete saved queries from <>. + +include::search-sessions.asciidoc[] diff --git a/docs/settings/search-sessions-settings.asciidoc b/docs/settings/search-sessions-settings.asciidoc new file mode 100644 index 00000000000000..c9a9e709ac7f89 --- /dev/null +++ b/docs/settings/search-sessions-settings.asciidoc @@ -0,0 +1,25 @@ + +[[search-session-settings-kb]] +=== Search sessions settings in {kib} +++++ +Search sessions settings +++++ + +Configure the search session settings in your `kibana.yml` configuration file. + + +[cols="2*<"] +|=== +a| `xpack.data_enhanced.` +`search.sessions:enabled` + | Set to `true` (default) to enable search sessions. + +a| `xpack.data.enhanced.` +`search.sessions:trackingInterval` + | The frequency for updating the state of a search session. The default is 10s. + +a| `xpack.data.enhanced.` +`search.sessions:defaultExpiration` + | How long search session results are stored before they are deleted. + Extending a search session resets the expiration by the same value. The default is 7d. +|=== diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index a28a52834db39a..62e0f0847cbac6 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -227,7 +227,7 @@ The default application to load. *Default: `"home"`* for more details. {kib} uses an index in {es} to store saved searches, visualizations, and dashboards. {kib} creates a new index if the index doesn’t already exist. If you configure a custom index, the name must be lowercase, and conform to the -{es} {ref}/indices-create-index.html[index name limitations]. +{es} {ref}/indices-create-index.html[index name limitations]. *Default: `".kibana"`* | `kibana.autocompleteTimeout:` {ess-icon} @@ -696,6 +696,7 @@ include::{kib-repo-dir}/settings/ml-settings.asciidoc[] include::{kib-repo-dir}/settings/monitoring-settings.asciidoc[] include::{kib-repo-dir}/settings/reporting-settings.asciidoc[] include::secure-settings.asciidoc[] +include::{kib-repo-dir}/settings/search-sessions-settings.asciidoc[] include::{kib-repo-dir}/settings/security-settings.asciidoc[] include::{kib-repo-dir}/settings/spaces-settings.asciidoc[] include::{kib-repo-dir}/settings/task-manager-settings.asciidoc[] diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index 5644cdbfc45ec1..7c73a80362eb6f 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -141,6 +141,12 @@ These include dashboards, visualizations, maps, index patterns, Canvas workpads, | <> |Create, manage, and assign tags to your saved objects. +| <> +| Manage your saved search sessions, groups of queries that run in the background. +Search sessions are useful when your queries take longer than usual to process, +for example, when you have a large volume of data or when the performance of your storage location is slow. + + | <> | Create spaces to organize your dashboards and other saved objects into categories. A space is isolated from all other spaces, From 7270f6d31837a72c82838483eaf4f701e9ffbe4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Mon, 22 Mar 2021 17:55:20 +0100 Subject: [PATCH 03/93] [SECURITY_SOLUTION] Adds hash validation on UI for trusted app (#94958) * Adds hash validation on UI. Display as many error messages as they are instead of displaying just the first one on entry fields. Updates related unit test * Fixes failing test and added new test case Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../create_trusted_app_form.test.tsx | 23 +++++++++++++++-- .../components/create_trusted_app_form.tsx | 18 ++++++++++--- .../view/trusted_apps_page.test.tsx | 25 ++++++++++++++++++- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx index 7d056ae6999e7d..24797bb483bdb9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx @@ -267,6 +267,11 @@ describe('When showing the Trusted App Create Form', () => { expect(renderResult.getByText('Name is required')); }); + it('should validate invalid Hash value', () => { + setTextFieldValue(getConditionValue(getCondition(renderResult)), 'someHASH'); + expect(renderResult.getByText('[1] Invalid hash value')); + }); + it('should validate that a condition value has a non empty space value', () => { setTextFieldValue(getConditionValue(getCondition(renderResult)), ' '); expect(renderResult.getByText('[1] Field entry must have a value')); @@ -281,13 +286,27 @@ describe('When showing the Trusted App Create Form', () => { setTextFieldValue(getConditionValue(getCondition(renderResult)), 'someHASH'); expect(renderResult.getByText('[2] Field entry must have a value')); }); + + it('should validate multiple errors in form', () => { + const andButton = getConditionBuilderAndButton(renderResult); + reactTestingLibrary.act(() => { + fireEvent.click(andButton, { button: 1 }); + }); + + setTextFieldValue(getConditionValue(getCondition(renderResult)), 'someHASH'); + expect(renderResult.getByText('[1] Invalid hash value')); + expect(renderResult.getByText('[2] Field entry must have a value')); + }); }); describe('and all required data passes validation', () => { it('should call change callback with isValid set to true and contain the new item', () => { const renderResult = render(); setTextFieldValue(getNameField(renderResult), 'Some Process'); - setTextFieldValue(getConditionValue(getCondition(renderResult)), 'someHASH'); + setTextFieldValue( + getConditionValue(getCondition(renderResult)), + 'e50fb1a0e5fff590ece385082edc6c41' + ); setTextFieldValue(getDescriptionField(renderResult), 'some description'); expect(getAllValidationErrors(renderResult)).toHaveLength(0); @@ -300,7 +319,7 @@ describe('When showing the Trusted App Create Form', () => { field: ConditionEntryField.HASH, operator: 'included', type: 'match', - value: 'someHASH', + value: 'e50fb1a0e5fff590ece385082edc6c41', }, ], name: 'Some Process', diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx index f4344796fc5626..f99c3567e79127 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx @@ -17,10 +17,13 @@ import { import { i18n } from '@kbn/i18n'; import { EuiFormProps } from '@elastic/eui/src/components/form/form'; import { + ConditionEntryField, MacosLinuxConditionEntry, NewTrustedApp, OperatingSystem, } from '../../../../../../common/endpoint/types'; +import { isValidHash } from '../../../../../../common/endpoint/validation/trusted_apps'; + import { isMacosLinuxTrustedAppCondition, isWindowsTrustedAppCondition, @@ -113,7 +116,7 @@ const validateFormValues = (values: NewTrustedApp): ValidationResult => { }) ); } else { - values.entries.some((entry, index) => { + values.entries.forEach((entry, index) => { if (!entry.field || !entry.value.trim()) { isValid = false; addResultToValidation( @@ -128,9 +131,18 @@ const validateFormValues = (values: NewTrustedApp): ValidationResult => { } ) ); - return true; + } else if (entry.field === ConditionEntryField.HASH && !isValidHash(entry.value)) { + isValid = false; + addResultToValidation( + validation, + 'entries', + 'errors', + i18n.translate('xpack.securitySolution.trustedapps.create.conditionFieldInvalidHashMsg', { + defaultMessage: '[{row}] Invalid hash value', + values: { row: index + 1 }, + }) + ); } - return false; }); } diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx index 69c7616b502c88..d891731f6d768f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx @@ -201,7 +201,7 @@ describe('When on the Trusted Apps Page', () => { fireEvent.change( getByTestId('addTrustedAppFlyout-createForm-conditionsBuilder-group1-entry0-value'), - { target: { value: 'SOME$HASH#HERE' } } + { target: { value: '44ed10b389dbcd1cf16cec79d16d7378' } } ); fireEvent.change(getByTestId('addTrustedAppFlyout-createForm-descriptionField'), { @@ -363,6 +363,29 @@ describe('When on the Trusted Apps Page', () => { }); }); }); + + describe('and when the form data is not valid', () => { + it('should not enable the Flyout Add button with an invalid hash', async () => { + const renderResult = await renderAndClickAddButton(); + const { getByTestId } = renderResult; + + reactTestingLibrary.act(() => { + fireEvent.change(getByTestId('addTrustedAppFlyout-createForm-nameTextField'), { + target: { value: 'trusted app A' }, + }); + + fireEvent.change( + getByTestId('addTrustedAppFlyout-createForm-conditionsBuilder-group1-entry0-value'), + { target: { value: 'invalid hash' } } + ); + }); + + const flyoutAddButton = getByTestId( + 'addTrustedAppFlyout-createButton' + ) as HTMLButtonElement; + expect(flyoutAddButton.disabled).toBe(true); + }); + }); }); describe('and there are no trusted apps', () => { From f1708ddcd3bb6029607abf2ead97b711182cb30a Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 22 Mar 2021 10:31:52 -0700 Subject: [PATCH 04/93] [uiSettings/theme] restrict theme options based on KBN_OPTIMIZER_THEMES (#94834) Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/core/server/ui_settings/settings/index.ts | 10 +++- .../server/ui_settings/settings/theme.test.ts | 54 +++++++++++++++++++ src/core/server/ui_settings/settings/theme.ts | 49 ++++++++++++++--- .../server/ui_settings/ui_settings_service.ts | 8 ++- 4 files changed, 112 insertions(+), 9 deletions(-) diff --git a/src/core/server/ui_settings/settings/index.ts b/src/core/server/ui_settings/settings/index.ts index 494ab2e01e7c7f..944ada3a63e4fc 100644 --- a/src/core/server/ui_settings/settings/index.ts +++ b/src/core/server/ui_settings/settings/index.ts @@ -15,14 +15,20 @@ import { getNotificationsSettings } from './notifications'; import { getThemeSettings } from './theme'; import { getStateSettings } from './state'; -export const getCoreSettings = (): Record => { +interface GetCoreSettingsOptions { + isDist?: boolean; +} + +export const getCoreSettings = ( + options?: GetCoreSettingsOptions +): Record => { return { ...getAccessibilitySettings(), ...getDateFormatSettings(), ...getMiscUiSettings(), ...getNavigationSettings(), ...getNotificationsSettings(), - ...getThemeSettings(), + ...getThemeSettings(options), ...getStateSettings(), }; }; diff --git a/src/core/server/ui_settings/settings/theme.test.ts b/src/core/server/ui_settings/settings/theme.test.ts index f0ca4f1eff4cde..58cbffb255b53e 100644 --- a/src/core/server/ui_settings/settings/theme.test.ts +++ b/src/core/server/ui_settings/settings/theme.test.ts @@ -44,3 +44,57 @@ describe('theme settings', () => { }); }); }); + +describe('process.env.KBN_OPTIMIZER_THEMES handling', () => { + it('provides valid options based on tags', () => { + process.env.KBN_OPTIMIZER_THEMES = 'v7light,v8dark'; + let settings = getThemeSettings({ isDist: false }); + expect(settings['theme:version'].options).toEqual(['v7', 'v8']); + + process.env.KBN_OPTIMIZER_THEMES = 'v8dark,v7light'; + settings = getThemeSettings({ isDist: false }); + expect(settings['theme:version'].options).toEqual(['v7', 'v8']); + + process.env.KBN_OPTIMIZER_THEMES = 'v8dark,v7light,v7dark,v8light'; + settings = getThemeSettings({ isDist: false }); + expect(settings['theme:version'].options).toEqual(['v7', 'v8']); + + process.env.KBN_OPTIMIZER_THEMES = '*'; + settings = getThemeSettings({ isDist: false }); + expect(settings['theme:version'].options).toEqual(['v7', 'v8']); + + process.env.KBN_OPTIMIZER_THEMES = 'v7light'; + settings = getThemeSettings({ isDist: false }); + expect(settings['theme:version'].options).toEqual(['v7']); + + process.env.KBN_OPTIMIZER_THEMES = 'v8light'; + settings = getThemeSettings({ isDist: false }); + expect(settings['theme:version'].options).toEqual(['v8']); + }); + + it('defaults to properties of first tag', () => { + process.env.KBN_OPTIMIZER_THEMES = 'v8dark,v7light'; + let settings = getThemeSettings({ isDist: false }); + expect(settings['theme:darkMode'].value).toBe(true); + expect(settings['theme:version'].value).toBe('v8'); + + process.env.KBN_OPTIMIZER_THEMES = 'v7light,v8dark'; + settings = getThemeSettings({ isDist: false }); + expect(settings['theme:darkMode'].value).toBe(false); + expect(settings['theme:version'].value).toBe('v7'); + }); + + it('ignores the value when isDist is undefined', () => { + process.env.KBN_OPTIMIZER_THEMES = 'v7light'; + const settings = getThemeSettings({ isDist: undefined }); + expect(settings['theme:darkMode'].value).toBe(false); + expect(settings['theme:version'].options).toEqual(['v7', 'v8']); + }); + + it('ignores the value when isDist is true', () => { + process.env.KBN_OPTIMIZER_THEMES = 'v7light'; + const settings = getThemeSettings({ isDist: true }); + expect(settings['theme:darkMode'].value).toBe(false); + expect(settings['theme:version'].options).toEqual(['v7', 'v8']); + }); +}); diff --git a/src/core/server/ui_settings/settings/theme.ts b/src/core/server/ui_settings/settings/theme.ts index 35b8f0217c1140..cc2919f7555c27 100644 --- a/src/core/server/ui_settings/settings/theme.ts +++ b/src/core/server/ui_settings/settings/theme.ts @@ -6,17 +6,54 @@ * Side Public License, v 1. */ -import { schema } from '@kbn/config-schema'; +import { schema, Type } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { UiSettingsParams } from '../../../types'; -export const getThemeSettings = (): Record => { +function parseThemeTags() { + if (!process.env.KBN_OPTIMIZER_THEMES) { + return ['v8light', 'v8dark']; + } + + if (process.env.KBN_OPTIMIZER_THEMES === '*') { + return ['v8light', 'v8dark', 'v7light', 'v7dark']; + } + + return process.env.KBN_OPTIMIZER_THEMES.split(',').map((t) => t.trim()); +} + +function getThemeInfo(options: GetThemeSettingsOptions) { + if (options?.isDist ?? true) { + return { + defaultDarkMode: false, + defaultVersion: 'v8', + availableVersions: ['v7', 'v8'], + }; + } + + const themeTags = parseThemeTags(); + return { + defaultDarkMode: themeTags[0].endsWith('dark'), + defaultVersion: themeTags[0].slice(0, 2), + availableVersions: ['v7', 'v8'].filter((v) => themeTags.some((t) => t.startsWith(v))), + }; +} + +interface GetThemeSettingsOptions { + isDist?: boolean; +} + +export const getThemeSettings = ( + options: GetThemeSettingsOptions = {} +): Record => { + const { availableVersions, defaultDarkMode, defaultVersion } = getThemeInfo(options); + return { 'theme:darkMode': { name: i18n.translate('core.ui_settings.params.darkModeTitle', { defaultMessage: 'Dark mode', }), - value: false, + value: defaultDarkMode, description: i18n.translate('core.ui_settings.params.darkModeText', { defaultMessage: `Enable a dark mode for the Kibana UI. A page refresh is required for the setting to be applied.`, }), @@ -27,14 +64,14 @@ export const getThemeSettings = (): Record => { name: i18n.translate('core.ui_settings.params.themeVersionTitle', { defaultMessage: 'Theme version', }), - value: 'v8', + value: defaultVersion, type: 'select', - options: ['v7', 'v8'], + options: availableVersions, description: i18n.translate('core.ui_settings.params.themeVersionText', { defaultMessage: `Switch between the theme used for the current and next version of Kibana. A page refresh is required for the setting to be applied.`, }), requiresPageReload: true, - schema: schema.oneOf([schema.literal('v7'), schema.literal('v8')]), + schema: schema.oneOf(availableVersions.map((v) => schema.literal(v)) as [Type]), }, }; }; diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index efa024e096599d..93878d264541cd 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -37,11 +37,13 @@ export class UiSettingsService implements CoreService { private readonly log: Logger; private readonly config$: Observable; + private readonly isDist: boolean; private readonly uiSettingsDefaults = new Map(); private overrides: Record = {}; constructor(private readonly coreContext: CoreContext) { this.log = coreContext.logger.get('ui-settings-service'); + this.isDist = coreContext.env.packageInfo.dist; this.config$ = coreContext.configService.atPath(uiConfigDefinition.path); } @@ -50,7 +52,11 @@ export class UiSettingsService savedObjects.registerType(uiSettingsType); registerRoutes(http.createRouter('')); - this.register(getCoreSettings()); + this.register( + getCoreSettings({ + isDist: this.isDist, + }) + ); const config = await this.config$.pipe(first()).toPromise(); this.overrides = config.overrides; From 041566d85aaf24f61bc4304cbd978f0aa00ea9d7 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 22 Mar 2021 18:35:57 +0100 Subject: [PATCH 05/93] [dashboard/bwc_shared_urls] check viz are loaded (#94941) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- test/functional/apps/dashboard/bwc_shared_urls.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/functional/apps/dashboard/bwc_shared_urls.ts b/test/functional/apps/dashboard/bwc_shared_urls.ts index e9d892fcd3bcfe..d40cf03327fd3c 100644 --- a/test/functional/apps/dashboard/bwc_shared_urls.ts +++ b/test/functional/apps/dashboard/bwc_shared_urls.ts @@ -81,6 +81,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await pieChart.expectPieSliceCount(0); await dashboardExpect.panelCount(2); + await PageObjects.dashboard.waitForRenderComplete(); }); }); @@ -96,6 +97,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await pieChart.expectPieSliceCount(5); await dashboardExpect.panelCount(2); + await PageObjects.dashboard.waitForRenderComplete(); await dashboardExpect.selectedLegendColorCount('#F9D9F9', 5); }); @@ -115,6 +117,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await pieChart.expectPieSliceCount(5); await dashboardExpect.panelCount(2); + await PageObjects.dashboard.waitForRenderComplete(); await dashboardExpect.selectedLegendColorCount('#F9D9F9', 5); }); From 416eea750c5c6e09292f04eb4219a13f6f25ede8 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 22 Mar 2021 12:31:22 -0600 Subject: [PATCH 06/93] [Security Solution] [Cases] Add deprecation in cases plugin for rename (#94808) --- x-pack/plugins/cases/server/index.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/cases/server/index.ts b/x-pack/plugins/cases/server/index.ts index 9fdc62c0f4ab05..628a39ba77489b 100644 --- a/x-pack/plugins/cases/server/index.ts +++ b/x-pack/plugins/cases/server/index.ts @@ -5,11 +5,16 @@ * 2.0. */ -import { PluginInitializerContext } from 'kibana/server'; -import { ConfigSchema } from './config'; +import { PluginConfigDescriptor, PluginInitializerContext } from 'kibana/server'; +import { ConfigType, ConfigSchema } from './config'; import { CasePlugin } from './plugin'; export { CaseRequestContext } from './types'; -export const config = { schema: ConfigSchema }; +export const config: PluginConfigDescriptor = { + schema: ConfigSchema, + deprecations: ({ renameFromRoot }) => [ + renameFromRoot('xpack.case.enabled', 'xpack.cases.enabled'), + ], +}; export const plugin = (initializerContext: PluginInitializerContext) => new CasePlugin(initializerContext); From 7ebffc3728a04fa806a29e39f5fd0eb87f514368 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Mon, 22 Mar 2021 15:26:37 -0400 Subject: [PATCH 07/93] [FLEET][SECURITY_SOLUTION] Fix .fleet-artifacs index property names and add endpoint package policy migrations (#94977) * migration of Endpoint Integration policies to adjust artifact relative urs * Fix ``.fleet-artifacts` property name to be snake_cased --- .../fleet/server/saved_objects/index.ts | 4 +- .../migrations/security_solution/index.ts | 1 + .../security_solution/to_v7_13_0.test.ts | 145 ++++++++++++++++++ .../security_solution/to_v7_13_0.ts | 39 +++++ .../saved_objects/migrations/to_v7_13_0.ts | 17 ++ .../services/artifacts/artifacts.test.ts | 3 +- .../server/services/artifacts/artifacts.ts | 12 +- .../server/services/artifacts/client.test.ts | 6 +- .../fleet/server/services/artifacts/client.ts | 7 +- .../server/services/artifacts/mappings.ts | 52 ++++++- .../fleet/server/services/artifacts/mocks.ts | 7 +- .../fleet/server/services/artifacts/types.ts | 20 ++- .../elasticsearch/fleet_artifacts.json | 14 +- .../services/artifacts/artifact_client.ts | 2 +- .../artifacts/fleet_artifacts/data.json | 28 ++-- .../artifacts/fleet_artifacts/mappings.json | 16 +- 16 files changed, 318 insertions(+), 55 deletions(-) create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.test.ts create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.ts diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index b3edf9e449e3d5..e5f0537a8c27aa 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -38,8 +38,7 @@ import { migrateAgentToV7120, migratePackagePolicyToV7120, } from './migrations/to_v7_12_0'; - -import { migrateSettingsToV7130 } from './migrations/to_v7_13_0'; +import { migratePackagePolicyToV7130, migrateSettingsToV7130 } from './migrations/to_v7_13_0'; /* * Saved object types and mappings @@ -289,6 +288,7 @@ const getSavedObjectTypes = ( '7.10.0': migratePackagePolicyToV7100, '7.11.0': migratePackagePolicyToV7110, '7.12.0': migratePackagePolicyToV7120, + '7.13.0': migratePackagePolicyToV7130, }, }, [PACKAGES_SAVED_OBJECT_TYPE]: { diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/index.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/index.ts index bbdd3f14fe22fe..ddce95a96879a9 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/index.ts @@ -7,3 +7,4 @@ export { migratePackagePolicyToV7110 } from './to_v7_11_0'; export { migratePackagePolicyToV7120 } from './to_v7_12_0'; +export { migrateEndpointPackagePolicyToV7130 } from './to_v7_13_0'; diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.test.ts new file mode 100644 index 00000000000000..75e2922bd51499 --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.test.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectUnsanitizedDoc } from 'kibana/server'; +import { cloneDeepWith, cloneDeep } from 'lodash'; + +import type { PackagePolicy } from '../../../../common'; + +import { migrationMocks } from '../../../../../../../src/core/server/mocks'; + +import { migrateEndpointPackagePolicyToV7130 } from './to_v7_13_0'; + +describe('7.13.0 Endpoint Package Policy migration', () => { + const createOldPackagePolicySO = (): SavedObjectUnsanitizedDoc => { + return { + id: 'mock-saved-object-id', + attributes: { + name: 'Some Policy Name', + package: { + name: 'endpoint', + title: '', + version: '', + }, + id: 'mock-saved-object-id', + policy_id: '', + enabled: true, + namespace: '', + output_id: '', + revision: 0, + updated_at: '', + updated_by: '', + created_at: '', + created_by: '', + inputs: [ + { + type: 'endpoint', + enabled: true, + streams: [], + config: { + artifact_manifest: { + value: { + manifest_version: '1.0.0', + schema_version: 'v1', + artifacts: { + 'endpoint-exceptionlist-macos-v1': { + encryption_algorithm: 'none', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + relative_url: + '/api/endpoint/artifacts/download/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + compression_algorithm: 'zlib', + }, + 'endpoint-exceptionlist-windows-v1': { + encryption_algorithm: 'none', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + relative_url: + '/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + compression_algorithm: 'zlib', + }, + 'endpoint-trustlist-macos-v1': { + encryption_algorithm: 'none', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + compression_algorithm: 'zlib', + }, + 'endpoint-trustlist-windows-v1': { + encryption_algorithm: 'none', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + compression_algorithm: 'zlib', + }, + 'endpoint-trustlist-linux-v1': { + encryption_algorithm: 'none', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + compression_algorithm: 'zlib', + }, + }, + }, + }, + }, + }, + ], + }, + type: ' nested', + }; + }; + const createNewPackagePolicySO = (): SavedObjectUnsanitizedDoc => { + return cloneDeepWith(createOldPackagePolicySO(), (value, key) => { + if (key === 'relative_url') { + return value.replace('/api/endpoint/artifacts/download/', '/api/fleet/artifacts/'); + } + }); + }; + + const migrationContext = migrationMocks.createContext(); + + it('should adjust the relative url for all artifact manifests', () => { + expect( + migrateEndpointPackagePolicyToV7130(createOldPackagePolicySO(), migrationContext) + ).toEqual(createNewPackagePolicySO()); + }); + + it('should NOT touch non-endpoint package policies', () => { + const packagePolicySo = createOldPackagePolicySO(); + packagePolicySo.attributes.package!.name = 'not endpoint'; + + const unchangedPackagePolicySo = cloneDeep(packagePolicySo); + + expect(migrateEndpointPackagePolicyToV7130(packagePolicySo, migrationContext)).toEqual( + unchangedPackagePolicySo + ); + }); +}); diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.ts new file mode 100644 index 00000000000000..655ce37b4faaf2 --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectMigrationFn } from 'kibana/server'; + +import type { PackagePolicy } from '../../../../common'; +import { relativeDownloadUrlFromArtifact } from '../../../services/artifacts/mappings'; +import type { ArtifactElasticsearchProperties } from '../../../services'; + +type ArtifactManifestList = Record< + string, + Pick +>; + +export const migrateEndpointPackagePolicyToV7130: SavedObjectMigrationFn< + PackagePolicy, + PackagePolicy +> = (packagePolicyDoc) => { + if (packagePolicyDoc.attributes.package?.name === 'endpoint') { + // Adjust all artifact URLs so that they point at fleet-server + const artifactList: ArtifactManifestList = + packagePolicyDoc.attributes?.inputs[0]?.config?.artifact_manifest.value.artifacts; + + if (artifactList) { + for (const [identifier, artifactManifest] of Object.entries(artifactList)) { + artifactManifest.relative_url = relativeDownloadUrlFromArtifact({ + identifier, + decodedSha256: artifactManifest.decoded_sha256, + }); + } + } + } + + return packagePolicyDoc; +}; diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_13_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_13_0.ts index 5c660d4309ac7b..1cc2394a8e5fe0 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_13_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_13_0.ts @@ -9,6 +9,10 @@ import type { SavedObjectMigrationFn } from 'kibana/server'; import type { Settings } from '../../types'; +import type { PackagePolicy } from '../../../common'; + +import { migrateEndpointPackagePolicyToV7130 } from './security_solution'; + export const migrateSettingsToV7130: SavedObjectMigrationFn< Settings & { package_auto_upgrade: string; @@ -23,3 +27,16 @@ export const migrateSettingsToV7130: SavedObjectMigrationFn< return settingsDoc; }; + +export const migratePackagePolicyToV7130: SavedObjectMigrationFn = ( + packagePolicyDoc, + migrationContext +) => { + // Endpoint specific migrations + // FIXME:PT remove `-OFF` from below once ready to be released + if (packagePolicyDoc.attributes.package?.name === 'endpoint-OFF') { + return migrateEndpointPackagePolicyToV7130(packagePolicyDoc, migrationContext); + } + + return packagePolicyDoc; +}; diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts index b483a4e534e6c8..07232e66b4467e 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts @@ -30,6 +30,7 @@ import { } from './artifacts'; import type { NewArtifact } from './types'; +import { newArtifactToElasticsearchProperties } from './mappings'; describe('When using the artifacts services', () => { let esClientMock: ReturnType; @@ -86,7 +87,7 @@ describe('When using the artifacts services', () => { index: FLEET_SERVER_ARTIFACTS_INDEX, id: expect.any(String), body: { - ...newArtifact, + ...newArtifactToElasticsearchProperties(newArtifact), created: expect.any(String), }, refresh: 'wait_for', diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts index 2a5f39c4e8a26e..9a12f6a3c0bdfc 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts @@ -29,7 +29,7 @@ import type { ListArtifactsProps, NewArtifact, } from './types'; -import { esSearchHitToArtifact } from './mappings'; +import { esSearchHitToArtifact, newArtifactToElasticsearchProperties } from './mappings'; const deflateAsync = promisify(deflate); @@ -58,10 +58,7 @@ export const createArtifact = async ( artifact: NewArtifact ): Promise => { const id = uuid.v4(); - const newArtifactData: ArtifactElasticsearchProperties = { - ...artifact, - created: new Date().toISOString(), - }; + const newArtifactData = newArtifactToElasticsearchProperties(artifact); try { await esClient.create({ @@ -71,10 +68,7 @@ export const createArtifact = async ( refresh: 'wait_for', }); - return { - ...newArtifactData, - id, - }; + return esSearchHitToArtifact({ _id: id, _source: newArtifactData }); } catch (e) { throw new ArtifactsElasticsearchError(e); } diff --git a/x-pack/plugins/fleet/server/services/artifacts/client.test.ts b/x-pack/plugins/fleet/server/services/artifacts/client.test.ts index da1387b24cab09..470f1d4ce0a844 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/client.test.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/client.test.ts @@ -25,7 +25,7 @@ describe('When using the Fleet Artifacts Client', () => { const singleHit = generateArtifactEsGetSingleHitMock(); if (withInvalidArtifact) { - singleHit._source.packageName = 'not endpoint'; + singleHit._source.package_name = 'not endpoint'; } esClientMock.get.mockImplementation(() => { @@ -129,7 +129,7 @@ describe('When using the Fleet Artifacts Client', () => { expect(esClientMock.search).toHaveBeenCalledWith( expect.objectContaining({ - q: '(packageName: "endpoint") AND identifier: one', + q: '(package_name: "endpoint") AND identifier: one', }) ); }); @@ -143,7 +143,7 @@ describe('When using the Fleet Artifacts Client', () => { }); expect(esClientMock.search).toHaveBeenCalledWith( expect.objectContaining({ - q: '(packageName: "endpoint")', + q: '(package_name: "endpoint")', }) ); }); diff --git a/x-pack/plugins/fleet/server/services/artifacts/client.ts b/x-pack/plugins/fleet/server/services/artifacts/client.ts index 87b752c5a2069b..42355aee8e4fbd 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/client.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/client.ts @@ -85,12 +85,17 @@ export class FleetArtifactsClient implements ArtifactsClientInterface { } } + /** + * Get a list of artifacts. + * NOTE that when using the `kuery` filtering param, that all filters property names should + * match the internal attribute names of the index + */ async listArtifacts({ kuery, ...options }: ListArtifactsProps = {}): Promise< ListResult > { // All filtering for artifacts should be bound to the `packageName`, so we insert // that into the KQL value and use `AND` to add the defined `kuery` (if any) to it. - const filter = `(packageName: "${this.packageName}")${kuery ? ` AND ${kuery}` : ''}`; + const filter = `(package_name: "${this.packageName}")${kuery ? ` AND ${kuery}` : ''}`; return listArtifacts(this.esClient, { ...options, diff --git a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts index 863eff5aac74cc..43aa111f2efcf6 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts @@ -7,17 +7,57 @@ import type { ESSearchHit } from '../../../../../../typings/elasticsearch'; -import type { Artifact, ArtifactElasticsearchProperties } from './types'; +import type { Artifact, ArtifactElasticsearchProperties, NewArtifact } from './types'; import { ARTIFACT_DOWNLOAD_RELATIVE_PATH } from './constants'; export const esSearchHitToArtifact = < T extends Pick, '_id' | '_source'> ->( - searchHit: T -): Artifact => { +>({ + _id: id, + _source: { + compression_algorithm: compressionAlgorithm, + decoded_sha256: decodedSha256, + decoded_size: decodedSize, + encoded_sha256: encodedSha256, + encoded_size: encodedSize, + encryption_algorithm: encryptionAlgorithm, + package_name: packageName, + ...attributesNotNeedingRename + }, +}: T): Artifact => { + return { + ...attributesNotNeedingRename, + id, + compressionAlgorithm, + decodedSha256, + decodedSize, + encodedSha256, + encodedSize, + encryptionAlgorithm, + packageName, + }; +}; + +export const newArtifactToElasticsearchProperties = ({ + encryptionAlgorithm, + packageName, + encodedSize, + encodedSha256, + decodedSize, + decodedSha256, + compressionAlgorithm, + ...attributesNotNeedingRename +}: NewArtifact): ArtifactElasticsearchProperties => { return { - ...searchHit._source, - id: searchHit._id, + ...attributesNotNeedingRename, + encryption_algorithm: encryptionAlgorithm, + package_name: packageName, + encoded_size: encodedSize, + encoded_sha256: encodedSha256, + decoded_size: decodedSize, + decoded_sha256: decodedSha256, + compression_algorithm: compressionAlgorithm, + created: new Date().toISOString(), }; }; diff --git a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts index b1e01208a24ca0..5569e4ac77d20f 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts @@ -13,6 +13,7 @@ import { elasticsearchServiceMock } from '../../../../../../src/core/server/mock import type { ESSearchHit, ESSearchResponse } from '../../../../../../typings/elasticsearch'; import type { Artifact, ArtifactElasticsearchProperties, ArtifactsClientInterface } from './types'; +import { newArtifactToElasticsearchProperties } from './mappings'; export const createArtifactsClientMock = (): jest.Mocked => { return { @@ -77,7 +78,11 @@ export const generateEsRequestErrorApiResponseMock = ( }; export const generateArtifactEsGetSingleHitMock = (): ESSearchHit => { - const { id, ..._source } = generateArtifactMock(); + const { id, created, ...newArtifact } = generateArtifactMock(); + const _source = { + ...newArtifactToElasticsearchProperties(newArtifact), + created, + }; return { _index: '.fleet-artifacts_1', diff --git a/x-pack/plugins/fleet/server/services/artifacts/types.ts b/x-pack/plugins/fleet/server/services/artifacts/types.ts index bbad7974bde793..b6765e5208d8f9 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/types.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/types.ts @@ -38,7 +38,18 @@ export interface Artifact extends NewArtifact { /** * The set of Properties in Artifact that are actually stored in the Artifact document defined by the schema */ -export type ArtifactElasticsearchProperties = Omit; +export type ArtifactElasticsearchProperties = Pick< + Artifact, + 'identifier' | 'body' | 'created' | 'type' | 'relative_url' +> & { + compression_algorithm: Artifact['compressionAlgorithm']; + encryption_algorithm: Artifact['encryptionAlgorithm']; + encoded_sha256: Artifact['encodedSha256']; + encoded_size: Artifact['encodedSize']; + decoded_sha256: Artifact['decodedSha256']; + decoded_size: Artifact['decodedSize']; + package_name: Artifact['packageName']; +}; export type ArtifactEncodedMetadata = Pick< Artifact, @@ -66,9 +77,14 @@ export type ListArtifactsProps = Pick; + createArtifact(options: ArtifactsClientCreateOptions): Promise; + deleteArtifact(id: string): Promise; - listArtifacts(options?: ListWithKuery): Promise>; + + listArtifacts(options?: ListArtifactsProps): Promise>; + encodeContent(content: ArtifactsClientCreateOptions['content']): Promise; + generateHash(content: string): string; } diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_artifacts.json b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_artifacts.json index f6e1bd39ed8737..1f9643fd599d5f 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_artifacts.json +++ b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_artifacts.json @@ -6,32 +6,32 @@ "identifier": { "type": "keyword" }, - "compressionAlgorithm": { + "compression_algorithm": { "type": "keyword", "index": false }, - "encryptionAlgorithm": { + "encryption_algorithm": { "type": "keyword", "index": false }, - "encodedSha256": { + "encoded_sha256": { "type": "keyword" }, - "encodedSize": { + "encoded_size": { "type": "long", "index": false }, - "decodedSha256": { + "decoded_sha256": { "type": "keyword" }, - "decodedSize": { + "decoded_size": { "type": "long", "index": false }, "created": { "type": "date" }, - "packageName": { + "package_name": { "type": "keyword" }, "type": { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts index a798de29e2ea60..d9a2e86159d6cf 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts @@ -83,7 +83,7 @@ export class EndpointArtifactClient implements EndpointArtifactClientInterface { async getArtifact(id: string) { const { decodedSha256, identifier } = this.parseArtifactId(id); const artifacts = await this.fleetArtifacts.listArtifacts({ - kuery: `decodedSha256: "${decodedSha256}" AND identifier: "${identifier}"`, + kuery: `decoded_sha256: "${decodedSha256}" AND identifier: "${identifier}"`, perPage: 1, }); diff --git a/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/data.json b/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/data.json index c7d6ba9918eb24..730a7478c13a1d 100644 --- a/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/data.json +++ b/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/data.json @@ -6,14 +6,14 @@ "source": { "body": "eJyrVkrNKynKTC1WsoqOrQUAJxkFKQ==", "created": "2021-03-10T21:51:33.155Z", - "compressionAlgorithm": "zlib", - "encryptionAlgorithm": "none", + "compression_algorithm": "zlib", + "encryption_algorithm": "none", "identifier": "endpoint-exceptionlist-macos-v1", - "encodedSha256": "f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda", - "encodedSize": 14, - "decodedSha256": "d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", - "decodedSize": 22, - "packageName": "endpoint", + "encoded_sha256": "f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda", + "encoded_size": 14, + "decoded_sha256": "d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", + "decoded_size": 22, + "package_name": "endpoint", "relative_url": "/api/fleet/artifacts/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", "type": "exceptionlist" } @@ -28,14 +28,14 @@ "source": { "body": "eJzFkL0KwjAUhV+lZA55gG4OXcXJRYqE9LZeiElJbotSsvsIbr6ij2AaakVwUqTr+fkOnIGBIYfgWb4bGJ1bYDnzeGw1MP7m1Qi6iqZUhKbZOKvAe1GjBuGxMeBi3rbgJFkXY2iU7iqoojpR4RSreyV9Enupu1EttPSEimdrsRUs8OHj6C8L99v1ksBPGLnOU4p8QYtlYKHkM21+QFLn4FU3kEZCOU4vcOzKWDqAyybGP54tetSLPluGB+Nu8h4=", "created": "2021-03-10T21:51:33.155Z", - "compressionAlgorithm": "zlib", - "encryptionAlgorithm": "none", + "compression_algorithm": "zlib", + "encryption_algorithm": "none", "identifier": "endpoint-exceptionlist-windows-v1", - "encodedSha256": "73015ee5131dabd1b48aa4776d3e766d836f8dd8c9fa8999c9b931f60027f07f", - "encodedSize": 191, - "decodedSha256": "8d2bcc37e82fad5d06e2c9e4bd96793ea8905ace1d528a57d0d0579ecc8c647e", - "decodedSize": 704, - "packageName": "endpoint", + "encoded_sha256": "73015ee5131dabd1b48aa4776d3e766d836f8dd8c9fa8999c9b931f60027f07f", + "encoded_size": 191, + "decoded_sha256": "8d2bcc37e82fad5d06e2c9e4bd96793ea8905ace1d528a57d0d0579ecc8c647e", + "decoded_size": 704, + "package_name": "endpoint", "relative_url": "/api/fleet/artifacts/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", "type": "exceptionlist" } diff --git a/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/mappings.json b/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/mappings.json index b35b0acc6bdd20..132b605890dc0c 100644 --- a/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/mappings.json @@ -15,35 +15,35 @@ "body": { "type": "binary" }, - "compressionAlgorithm": { + "compression_algorithm": { "index": false, "type": "keyword" }, "created": { "type": "date" }, - "decodedSha256": { + "decoded_sha256": { "type": "keyword" }, - "decodedSize": { + "decoded_size": { "index": false, "type": "long" }, - "encodedSha256": { + "encoded_sha256": { "type": "keyword" }, - "encodedSize": { + "encoded_size": { "index": false, "type": "long" }, - "encryptionAlgorithm": { + "encryption_algorithm": { "index": false, "type": "keyword" }, "identifier": { "type": "keyword" }, - "packageName": { + "package_name": { "type": "keyword" }, "relative_url": { @@ -61,4 +61,4 @@ } } } -} \ No newline at end of file +} From b4ff1f65dc6f5fc5bca1319e487002a553976456 Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 22 Mar 2021 12:36:08 -0700 Subject: [PATCH 08/93] [cliDevMode/timings] report time until optimizer and server are ready (#94860) Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/developer/telemetry.asciidoc | 3 +- src/dev/cli_dev_mode/cli_dev_mode.test.ts | 2 + src/dev/cli_dev_mode/cli_dev_mode.ts | 70 ++++++++++++++++++++++- src/dev/cli_dev_mode/dev_server.ts | 9 +++ src/dev/cli_dev_mode/log.ts | 20 +++++++ src/dev/cli_dev_mode/optimizer.ts | 8 ++- 6 files changed, 108 insertions(+), 4 deletions(-) diff --git a/docs/developer/telemetry.asciidoc b/docs/developer/telemetry.asciidoc index 45d2a140cf8b96..fe2bf5f9573791 100644 --- a/docs/developer/telemetry.asciidoc +++ b/docs/developer/telemetry.asciidoc @@ -5,8 +5,9 @@ To help us provide a good developer experience, we track some straightforward me The operations we current report timing data for: -* Total execution time of `yarn kbn bootstrap` +* Total execution time of `yarn kbn bootstrap`. * Total execution time of `@kbn/optimizer` runs as well as the following metadata about the runs: The number of bundles created, the number of bundles which were cached, usage of `--watch`, `--dist`, `--workers` and `--no-cache` flags, and the count of themes being built. +* The time from when you run `yarn start` until both the Kibana server and `@kbn/optimizer` are ready for use. Along with the execution time of each execution, we ship the following information about your machine to the service: diff --git a/src/dev/cli_dev_mode/cli_dev_mode.test.ts b/src/dev/cli_dev_mode/cli_dev_mode.test.ts index 13b29a3221dbf2..54c49ce21505ff 100644 --- a/src/dev/cli_dev_mode/cli_dev_mode.test.ts +++ b/src/dev/cli_dev_mode/cli_dev_mode.test.ts @@ -189,6 +189,7 @@ describe('#start()/#stop()', () => { optimizerReady$ = new Rx.Subject(); return { isReady$: jest.fn(() => optimizerReady$), + getPhase$: jest.fn(() => Rx.NEVER), run$: optimizerRun$, }; }); @@ -203,6 +204,7 @@ describe('#start()/#stop()', () => { devServerReady$ = new Rx.Subject(); return { isReady$: jest.fn(() => devServerReady$), + getPhase$: jest.fn(() => Rx.NEVER), run$: devServerRun$, }; }); diff --git a/src/dev/cli_dev_mode/cli_dev_mode.ts b/src/dev/cli_dev_mode/cli_dev_mode.ts index ce3c068d489765..1eed8b14aed4ab 100644 --- a/src/dev/cli_dev_mode/cli_dev_mode.ts +++ b/src/dev/cli_dev_mode/cli_dev_mode.ts @@ -8,9 +8,9 @@ import Path from 'path'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT, CiStatsReporter } from '@kbn/dev-utils'; import * as Rx from 'rxjs'; -import { mapTo, filter, take, tap, distinctUntilChanged, switchMap } from 'rxjs/operators'; +import { map, mapTo, filter, take, tap, distinctUntilChanged, switchMap } from 'rxjs/operators'; import { CliArgs } from '../../core/server/config'; import { LegacyConfig } from '../../core/server/legacy'; @@ -91,6 +91,7 @@ export class CliDevMode { private readonly watcher: Watcher; private readonly devServer: DevServer; private readonly optimizer: Optimizer; + private startTime?: number; private subscription?: Rx.Subscription; @@ -164,6 +165,31 @@ export class CliDevMode { } this.subscription = new Rx.Subscription(); + this.startTime = Date.now(); + + this.subscription.add( + this.getStarted$() + .pipe( + switchMap(async (success) => { + const reporter = CiStatsReporter.fromEnv(this.log.toolingLog); + await reporter.timings({ + timings: [ + { + group: 'yarn start', + id: 'started', + ms: Date.now() - this.startTime!, + meta: { success }, + }, + ], + }); + }) + ) + .subscribe({ + error: (error) => { + this.log.bad(`[ci-stats/timings] unable to record startup time:`, error.stack); + }, + }) + ); if (basePathProxy) { const serverReady$ = new Rx.BehaviorSubject(false); @@ -219,6 +245,46 @@ export class CliDevMode { this.subscription.add(this.devServer.run$.subscribe(this.observer('dev server'))); } + /** + * returns an observable that emits once the dev server and optimizer are started, emits + * true if they both started successfully, otherwise false + */ + private getStarted$() { + return Rx.combineLatest([ + // convert the dev server and optimizer phase to: + // - true if they are started successfully + // - false if they failed to start + // - undefined if they are still coming up + this.devServer.getPhase$().pipe( + map((phase) => { + if (phase === 'listening') { + return true; + } + if (phase === 'fatal exit') { + return false; + } + }) + ), + this.optimizer.getPhase$().pipe( + map((phase) => { + if (phase === 'issue') { + return false; + } + if (phase === 'success') { + return true; + } + }) + ), + ]).pipe( + // ignore states where either start state is undefined + filter((states) => states.every((s) => typeof s === 'boolean')), + // merge the states to true only if all states are true, otherwise false + map((states) => states.every((s) => s === true)), + // we only "started" once + take(1) + ); + } + public stop() { if (!this.subscription) { throw new Error('CliDevMode has not been started'); diff --git a/src/dev/cli_dev_mode/dev_server.ts b/src/dev/cli_dev_mode/dev_server.ts index 3326d6b9f04425..a4e32a40665e3c 100644 --- a/src/dev/cli_dev_mode/dev_server.ts +++ b/src/dev/cli_dev_mode/dev_server.ts @@ -45,6 +45,7 @@ export class DevServer { private readonly sigint$: Rx.Observable; private readonly sigterm$: Rx.Observable; private readonly ready$ = new Rx.BehaviorSubject(false); + private readonly phase$ = new Rx.ReplaySubject<'starting' | 'fatal exit' | 'listening'>(1); private readonly script: string; private readonly argv: string[]; @@ -68,6 +69,10 @@ export class DevServer { return this.ready$.asObservable(); } + getPhase$() { + return this.phase$.asObservable(); + } + /** * Run the Kibana server * @@ -113,6 +118,8 @@ export class DevServer { const runServer = () => usingServerProcess(this.script, this.argv, (proc) => { + this.phase$.next('starting'); + // observable which emits devServer states containing lines // logged to stdout/stderr, completes when stdio streams complete const log$ = Rx.merge(observeLines(proc.stdout!), observeLines(proc.stderr!)).pipe( @@ -131,6 +138,7 @@ export class DevServer { this.ready$.next(false); if (code != null && code !== 0) { + this.phase$.next('fatal exit'); if (this.watcher.enabled) { this.log.bad(`server crashed`, 'with status code', code); } else { @@ -160,6 +168,7 @@ export class DevServer { const msg = received[0]; if (msg === 'SERVER_LISTENING') { + this.phase$.next('listening'); this.ready$.next(true); } diff --git a/src/dev/cli_dev_mode/log.ts b/src/dev/cli_dev_mode/log.ts index 495b3a4fa5e11c..86956abec202ac 100644 --- a/src/dev/cli_dev_mode/log.ts +++ b/src/dev/cli_dev_mode/log.ts @@ -9,8 +9,10 @@ /* eslint-disable max-classes-per-file */ import Chalk from 'chalk'; +import { ToolingLog } from '@kbn/dev-utils'; export interface Log { + toolingLog: ToolingLog; good(label: string, ...args: any[]): void; warn(label: string, ...args: any[]): void; bad(label: string, ...args: any[]): void; @@ -18,6 +20,15 @@ export interface Log { } export class CliLog implements Log { + public toolingLog = new ToolingLog({ + level: this.silent ? 'silent' : this.quiet ? 'error' : 'info', + writeTo: { + write: (msg) => { + this.write(msg); + }, + }, + }); + constructor(private readonly quiet: boolean, private readonly silent: boolean) {} good(label: string, ...args: any[]) { @@ -54,6 +65,15 @@ export class CliLog implements Log { } export class TestLog implements Log { + public toolingLog = new ToolingLog({ + level: 'verbose', + writeTo: { + write: (msg) => { + this.messages.push({ type: 'toolingLog', args: [msg] }); + }, + }, + }); + public readonly messages: Array<{ type: string; args: any[] }> = []; bad(label: string, ...args: any[]) { diff --git a/src/dev/cli_dev_mode/optimizer.ts b/src/dev/cli_dev_mode/optimizer.ts index 9c7bd63921e67d..771da21e6151b8 100644 --- a/src/dev/cli_dev_mode/optimizer.ts +++ b/src/dev/cli_dev_mode/optimizer.ts @@ -18,7 +18,7 @@ import { } from '@kbn/dev-utils'; import * as Rx from 'rxjs'; import { ignoreElements } from 'rxjs/operators'; -import { runOptimizer, OptimizerConfig, logOptimizerState } from '@kbn/optimizer'; +import { runOptimizer, OptimizerConfig, logOptimizerState, OptimizerUpdate } from '@kbn/optimizer'; export interface Options { enabled: boolean; @@ -37,6 +37,7 @@ export interface Options { export class Optimizer { public readonly run$: Rx.Observable; private readonly ready$ = new Rx.ReplaySubject(1); + private readonly phase$ = new Rx.ReplaySubject(1); constructor(options: Options) { if (!options.enabled) { @@ -105,12 +106,17 @@ export class Optimizer { this.run$ = runOptimizer(config).pipe( logOptimizerState(log, config), tap(({ state }) => { + this.phase$.next(state.phase); this.ready$.next(state.phase === 'success' || state.phase === 'issue'); }), ignoreElements() ); } + getPhase$() { + return this.phase$.asObservable(); + } + isReady$() { return this.ready$.asObservable(); } From 19aeb99dc30aa315a8b6e25ed0c822e3e6b303eb Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Mon, 22 Mar 2021 15:58:32 -0400 Subject: [PATCH 09/93] [App Search] Remaining Result Settings logic and routes (#94947) --- .../components/result_settings/constants.ts | 2 + .../result_settings_logic.test.ts | 493 +++++++++++++++++- .../result_settings/result_settings_logic.ts | 155 +++++- .../components/result_settings/types.ts | 2 +- .../components/result_settings/utils.test.ts | 67 +++ .../components/result_settings/utils.ts | 16 + .../server/routes/app_search/index.ts | 2 + .../routes/app_search/result_settings.test.ts | 91 ++++ .../routes/app_search/result_settings.ts | 48 ++ 9 files changed, 862 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts index d5e7035348b452..717cdc952fd679 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts @@ -9,6 +9,8 @@ import { i18n } from '@kbn/i18n'; import { FieldResultSetting } from './types'; +export const DEFAULT_SNIPPET_SIZE = 100; + export const RESULT_SETTINGS_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.resultSettings.title', { defaultMessage: 'Result Settings' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts index 91479403746459..1b7eb6cc00792a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts @@ -5,7 +5,13 @@ * 2.0. */ -import { LogicMounter } from '../../../__mocks__'; +import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../__mocks__'; + +import { mockEngineValues } from '../../__mocks__'; + +import { omit } from 'lodash'; + +import { nextTick } from '@kbn/test/jest'; import { Schema, SchemaConflicts, SchemaTypes } from '../../../shared/types'; @@ -29,13 +35,27 @@ describe('ResultSettingsLogic', () => { schemaConflicts: {}, }; + const SELECTORS = { + reducedServerResultFields: {}, + resultFieldsAtDefaultSettings: true, + resultFieldsEmpty: true, + stagedUpdates: false, + }; + + // Values without selectors + const resultSettingLogicValues = () => omit(ResultSettingsLogic.values, Object.keys(SELECTORS)); + beforeEach(() => { jest.clearAllMocks(); + mockEngineValues.engineName = 'test-engine'; }); it('has expected default values', () => { mount(); - expect(ResultSettingsLogic.values).toEqual(DEFAULT_VALUES); + expect(ResultSettingsLogic.values).toEqual({ + ...DEFAULT_VALUES, + ...SELECTORS, + }); }); describe('actions', () => { @@ -71,7 +91,7 @@ describe('ResultSettingsLogic', () => { schemaConflicts ); - expect(ResultSettingsLogic.values).toEqual({ + expect(resultSettingLogicValues()).toEqual({ ...DEFAULT_VALUES, dataLoading: false, saving: false, @@ -173,7 +193,7 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.openConfirmSaveModal(); - expect(ResultSettingsLogic.values).toEqual({ + expect(resultSettingLogicValues()).toEqual({ ...DEFAULT_VALUES, openModal: OpenModal.ConfirmSaveModal, }); @@ -186,7 +206,7 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.openConfirmResetModal(); - expect(ResultSettingsLogic.values).toEqual({ + expect(resultSettingLogicValues()).toEqual({ ...DEFAULT_VALUES, openModal: OpenModal.ConfirmResetModal, }); @@ -200,7 +220,7 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.closeModals(); - expect(ResultSettingsLogic.values).toEqual({ + expect(resultSettingLogicValues()).toEqual({ ...DEFAULT_VALUES, openModal: OpenModal.None, }); @@ -230,7 +250,7 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.clearAllFields(); - expect(ResultSettingsLogic.values).toEqual({ + expect(resultSettingLogicValues()).toEqual({ ...DEFAULT_VALUES, nonTextResultFields: { foo: {}, @@ -275,7 +295,7 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.resetAllFields(); - expect(ResultSettingsLogic.values).toEqual({ + expect(resultSettingLogicValues()).toEqual({ ...DEFAULT_VALUES, nonTextResultFields: { bar: { raw: true, snippet: false, snippetFallback: false }, @@ -303,7 +323,7 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.resetAllFields(); - expect(ResultSettingsLogic.values).toEqual({ + expect(resultSettingLogicValues()).toEqual({ ...DEFAULT_VALUES, openModal: OpenModal.None, }); @@ -339,7 +359,7 @@ describe('ResultSettingsLogic', () => { snippetFallback: false, }); - expect(ResultSettingsLogic.values).toEqual({ + expect(resultSettingLogicValues()).toEqual({ ...DEFAULT_VALUES, // the settings for foo are updated below for any *ResultFields state in which they appear nonTextResultFields: { @@ -372,7 +392,7 @@ describe('ResultSettingsLogic', () => { }); // 'baz' does not exist in state, so nothing is updated - expect(ResultSettingsLogic.values).toEqual({ + expect(resultSettingLogicValues()).toEqual({ ...DEFAULT_VALUES, ...initialValues, }); @@ -387,7 +407,7 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.saving(); - expect(ResultSettingsLogic.values).toEqual({ + expect(resultSettingLogicValues()).toEqual({ ...DEFAULT_VALUES, saving: true, openModal: OpenModal.None, @@ -395,4 +415,453 @@ describe('ResultSettingsLogic', () => { }); }); }); + + describe('selectors', () => { + describe('resultFieldsAtDefaultSettings', () => { + it('should return true if all fields are at their default settings', () => { + mount({ + resultFields: { + foo: { raw: true, snippet: false, snippetFallback: false }, + bar: { raw: true, snippet: false, snippetFallback: false }, + }, + }); + + expect(ResultSettingsLogic.values.resultFieldsAtDefaultSettings).toEqual(true); + }); + + it('should return false otherwise', () => { + mount({ + resultFields: { + foo: { raw: true, snippet: false, snippetFallback: false }, + bar: { raw: true, snippet: true, snippetFallback: false }, + }, + }); + + expect(ResultSettingsLogic.values.resultFieldsAtDefaultSettings).toEqual(false); + }); + }); + + describe('resultFieldsEmpty', () => { + it('should return true if all fields are empty', () => { + mount({ + resultFields: { + foo: {}, + bar: {}, + }, + }); + + expect(ResultSettingsLogic.values.resultFieldsEmpty).toEqual(true); + }); + + it('should return false otherwise', () => { + mount({ + resultFields: { + foo: {}, + bar: { raw: true, snippet: true, snippetFallback: false }, + }, + }); + + expect(ResultSettingsLogic.values.resultFieldsEmpty).toEqual(false); + }); + }); + + describe('stagedUpdates', () => { + it('should return true if changes have been made since the last save', () => { + mount({ + lastSavedResultFields: { + foo: {}, + bar: { raw: true, snippet: true, snippetFallback: false }, + }, + resultFields: { + foo: { raw: false, snippet: true, snippetFallback: true }, + bar: { raw: true, snippet: true, snippetFallback: false }, + }, + }); + + // resultFields is different than lastSavedResultsFields, which happens if changes + // have been made since the last save, which is represented by lastSavedResultFields + expect(ResultSettingsLogic.values.stagedUpdates).toEqual(true); + }); + + it('should return false otherwise', () => { + mount({ + lastSavedResultFields: { + foo: { raw: false, snippet: true, snippetFallback: true }, + bar: { raw: true, snippet: true, snippetFallback: false }, + }, + resultFields: { + foo: { raw: false, snippet: true, snippetFallback: true }, + bar: { raw: true, snippet: true, snippetFallback: false }, + }, + }); + + expect(ResultSettingsLogic.values.stagedUpdates).toEqual(false); + }); + }); + + describe('reducedServerResultFields', () => { + it('filters out fields that do not have any settings', () => { + mount({ + serverResultFields: { + foo: { raw: { size: 5 } }, + bar: {}, + }, + }); + + expect(ResultSettingsLogic.values.reducedServerResultFields).toEqual({ + // bar was filtered out because it has neither raw nor snippet data set + foo: { raw: { size: 5 } }, + }); + }); + }); + }); + + describe('listeners', () => { + const { http } = mockHttpValues; + const { flashAPIErrors } = mockFlashMessageHelpers; + + const serverFieldResultSettings = { + foo: { + raw: {}, + }, + bar: { + raw: {}, + }, + }; + const schema = { + foo: 'text', + bar: 'number', + }; + const schemaConflicts = { + baz: { + text: ['test'], + number: ['test2'], + }, + }; + + describe('clearRawSizeForField', () => { + it('should remove the raw size set on a field', () => { + mount({ + resultFields: { + foo: { raw: true, rawSize: 5, snippet: false }, + bar: { raw: true, rawSize: 5, snippet: false }, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.clearRawSizeForField('foo'); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', { + raw: true, + snippet: false, + }); + }); + }); + + describe('clearSnippetSizeForField', () => { + it('should remove the snippet size set on a field', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5 }, + bar: { raw: true, rawSize: 5, snippet: false }, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.clearSnippetSizeForField('foo'); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', { + raw: false, + snippet: true, + }); + }); + }); + + describe('toggleRawForField', () => { + it('should toggle the raw value on for a field', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5 }, + bar: { raw: false, snippet: false }, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.toggleRawForField('bar'); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { + raw: true, + snippet: false, + }); + }); + + it('should maintain rawSize if it was set prior', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5 }, + bar: { raw: false, rawSize: 10, snippet: false }, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.toggleRawForField('bar'); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { + raw: true, + rawSize: 10, + snippet: false, + }); + }); + + it('should remove rawSize value when toggling off', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5 }, + bar: { raw: true, rawSize: 5, snippet: false }, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.toggleRawForField('bar'); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { + raw: false, + snippet: false, + }); + }); + + it('should still work if the object is empty', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5 }, + bar: {}, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.toggleRawForField('bar'); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { + raw: true, + }); + }); + }); + + describe('toggleSnippetForField', () => { + it('should toggle the raw value on for a field, always setting the snippet size to 100', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5 }, + bar: { raw: false, snippet: false }, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.toggleSnippetForField('bar'); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { + raw: false, + snippet: true, + snippetSize: 100, + }); + }); + + it('should remove rawSize value when toggling off', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5 }, + bar: { raw: false, snippet: true, snippetSize: 5 }, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.toggleSnippetForField('bar'); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { + raw: false, + snippet: false, + }); + }); + + it('should still work if the object is empty', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5 }, + bar: {}, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.toggleSnippetForField('bar'); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { + snippet: true, + snippetSize: 100, + }); + }); + }); + + describe('toggleSnippetFallbackForField', () => { + it('should toggle the snippetFallback value for a field', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true }, + bar: { raw: false, snippet: false }, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.toggleSnippetFallbackForField('foo'); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', { + raw: false, + snippet: true, + snippetSize: 5, + snippetFallback: false, + }); + }); + }); + + describe('updateRawSizeForField', () => { + it('should update the rawSize value for a field', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true }, + bar: { raw: true, rawSize: 5, snippet: false }, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.updateRawSizeForField('bar', 7); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { + raw: true, + rawSize: 7, + snippet: false, + }); + }); + }); + + describe('updateSnippetSizeForField', () => { + it('should update the snippetSize value for a field', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true }, + bar: { raw: true, rawSize: 5, snippet: false }, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.updateSnippetSizeForField('foo', 7); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', { + raw: false, + snippet: true, + snippetSize: 7, + snippetFallback: true, + }); + }); + }); + + describe('initializeResultSettingsData', () => { + it('should remove the snippet size set on a field', () => { + mount({ + resultFields: { + foo: { raw: false, snippet: true, snippetSize: 5 }, + bar: { raw: true, rawSize: 5, snippet: false }, + }, + }); + jest.spyOn(ResultSettingsLogic.actions, 'updateField'); + + ResultSettingsLogic.actions.clearSnippetSizeForField('foo'); + + expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', { + raw: false, + snippet: true, + }); + }); + }); + + describe('initializeResultFields', () => { + it('should make an API call and set state based on the response', async () => { + mount(); + http.get.mockReturnValueOnce( + Promise.resolve({ + searchSettings: { + result_fields: serverFieldResultSettings, + }, + schema, + schemaConflicts, + }) + ); + jest.spyOn(ResultSettingsLogic.actions, 'initializeResultFields'); + + ResultSettingsLogic.actions.initializeResultSettingsData(); + await nextTick(); + + expect(http.get).toHaveBeenCalledWith( + '/api/app_search/engines/test-engine/result_settings/details' + ); + expect(ResultSettingsLogic.actions.initializeResultFields).toHaveBeenCalledWith( + serverFieldResultSettings, + schema, + schemaConflicts + ); + }); + + it('handles errors', async () => { + mount(); + http.get.mockReturnValueOnce(Promise.reject('error')); + + ResultSettingsLogic.actions.initializeResultSettingsData(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }); + + describe('saveResultSettings', () => { + it('should make an API call to update result settings and update state accordingly', async () => { + mount({ + schema, + }); + http.put.mockReturnValueOnce( + Promise.resolve({ + result_fields: serverFieldResultSettings, + }) + ); + jest.spyOn(ResultSettingsLogic.actions, 'saving'); + jest.spyOn(ResultSettingsLogic.actions, 'initializeResultFields'); + + ResultSettingsLogic.actions.saveResultSettings(serverFieldResultSettings); + + expect(ResultSettingsLogic.actions.saving).toHaveBeenCalled(); + + await nextTick(); + + expect(http.put).toHaveBeenCalledWith( + '/api/app_search/engines/test-engine/result_settings', + { + body: JSON.stringify({ + result_fields: serverFieldResultSettings, + }), + } + ); + expect(ResultSettingsLogic.actions.initializeResultFields).toHaveBeenCalledWith( + serverFieldResultSettings, + schema + ); + }); + + it('handles errors', async () => { + mount(); + http.put.mockReturnValueOnce(Promise.reject('error')); + + ResultSettingsLogic.actions.saveResultSettings(serverFieldResultSettings); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts index b2ffd3de19f047..9969a950528abd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts @@ -6,9 +6,16 @@ */ import { kea, MakeLogicType } from 'kea'; +import { omit, isEqual } from 'lodash'; +import { i18n } from '@kbn/i18n'; + +import { flashAPIErrors, setSuccessMessage } from '../../../shared/flash_messages'; +import { HttpLogic } from '../../../shared/http'; import { Schema, SchemaConflicts } from '../../../shared/types'; +import { EngineLogic } from '../engine'; +import { DEFAULT_SNIPPET_SIZE } from './constants'; import { FieldResultSetting, FieldResultSettingObject, @@ -17,6 +24,8 @@ import { } from './types'; import { + areFieldsAtDefaultSettings, + areFieldsEmpty, clearAllFields, clearAllServerFields, convertServerResultFieldsToResultFields, @@ -46,9 +55,21 @@ interface ResultSettingsActions { resetAllFields(): void; updateField( fieldName: string, - settings: FieldResultSetting + settings: FieldResultSetting | {} ): { fieldName: string; settings: FieldResultSetting }; saving(): void; + // Listeners + clearRawSizeForField(fieldName: string): { fieldName: string }; + clearSnippetSizeForField(fieldName: string): { fieldName: string }; + toggleRawForField(fieldName: string): { fieldName: string }; + toggleSnippetForField(fieldName: string): { fieldName: string }; + toggleSnippetFallbackForField(fieldName: string): { fieldName: string }; + updateRawSizeForField(fieldName: string, size: number): { fieldName: string; size: number }; + updateSnippetSizeForField(fieldName: string, size: number): { fieldName: string; size: number }; + initializeResultSettingsData(): void; + saveResultSettings( + resultFields: ServerFieldResultSettingObject + ): { resultFields: ServerFieldResultSettingObject }; } interface ResultSettingsValues { @@ -62,6 +83,11 @@ interface ResultSettingsValues { lastSavedResultFields: FieldResultSettingObject; schema: Schema; schemaConflicts: SchemaConflicts; + // Selectors + resultFieldsAtDefaultSettings: boolean; + resultFieldsEmpty: boolean; + stagedUpdates: true; + reducedServerResultFields: ServerFieldResultSettingObject; } export const ResultSettingsLogic = kea>({ @@ -90,6 +116,15 @@ export const ResultSettingsLogic = kea true, updateField: (fieldName, settings) => ({ fieldName, settings }), saving: () => true, + clearRawSizeForField: (fieldName) => ({ fieldName }), + clearSnippetSizeForField: (fieldName) => ({ fieldName }), + toggleRawForField: (fieldName) => ({ fieldName }), + toggleSnippetForField: (fieldName) => ({ fieldName }), + toggleSnippetFallbackForField: (fieldName) => ({ fieldName }), + updateRawSizeForField: (fieldName, size) => ({ fieldName, size }), + updateSnippetSizeForField: (fieldName, size) => ({ fieldName, size }), + initializeResultSettingsData: () => true, + saveResultSettings: (resultFields) => ({ resultFields }), }), reducers: () => ({ dataLoading: [ @@ -187,4 +222,122 @@ export const ResultSettingsLogic = kea ({ + resultFieldsAtDefaultSettings: [ + () => [selectors.resultFields], + (resultFields) => areFieldsAtDefaultSettings(resultFields), + ], + resultFieldsEmpty: [ + () => [selectors.resultFields], + (resultFields) => areFieldsEmpty(resultFields), + ], + stagedUpdates: [ + () => [selectors.lastSavedResultFields, selectors.resultFields], + (lastSavedResultFields, resultFields) => !isEqual(lastSavedResultFields, resultFields), + ], + reducedServerResultFields: [ + () => [selectors.serverResultFields], + (serverResultFields: ServerFieldResultSettingObject) => + Object.entries(serverResultFields).reduce( + (acc: ServerFieldResultSettingObject, [fieldName, resultSetting]) => { + if (resultSetting.raw || resultSetting.snippet) { + acc[fieldName] = resultSetting; + } + return acc; + }, + {} + ), + ], + }), + listeners: ({ actions, values }) => ({ + clearRawSizeForField: ({ fieldName }) => { + actions.updateField(fieldName, omit(values.resultFields[fieldName], ['rawSize'])); + }, + clearSnippetSizeForField: ({ fieldName }) => { + actions.updateField(fieldName, omit(values.resultFields[fieldName], ['snippetSize'])); + }, + toggleRawForField: ({ fieldName }) => { + // We cast this because it could be an empty object, which we can still treat as a FieldResultSetting safely + const field = values.resultFields[fieldName] as FieldResultSetting; + const raw = !field.raw; + actions.updateField(fieldName, { + ...omit(field, ['rawSize']), + raw, + ...(raw ? { rawSize: field.rawSize } : {}), + }); + }, + toggleSnippetForField: ({ fieldName }) => { + // We cast this because it could be an empty object, which we can still treat as a FieldResultSetting safely + const field = values.resultFields[fieldName] as FieldResultSetting; + const snippet = !field.snippet; + + actions.updateField(fieldName, { + ...omit(field, ['snippetSize']), + snippet, + ...(snippet ? { snippetSize: DEFAULT_SNIPPET_SIZE } : {}), + }); + }, + toggleSnippetFallbackForField: ({ fieldName }) => { + // We cast this because it could be an empty object, which we can still treat as a FieldResultSetting safely + const field = values.resultFields[fieldName] as FieldResultSetting; + actions.updateField(fieldName, { + ...field, + snippetFallback: !field.snippetFallback, + }); + }, + updateRawSizeForField: ({ fieldName, size }) => { + actions.updateField(fieldName, { ...values.resultFields[fieldName], rawSize: size }); + }, + updateSnippetSizeForField: ({ fieldName, size }) => { + actions.updateField(fieldName, { ...values.resultFields[fieldName], snippetSize: size }); + }, + initializeResultSettingsData: async () => { + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + + const url = `/api/app_search/engines/${engineName}/result_settings/details`; + + try { + const { + schema, + schemaConflicts, + searchSettings: { result_fields: serverFieldResultSettings }, + } = await http.get(url); + + actions.initializeResultFields(serverFieldResultSettings, schema, schemaConflicts); + } catch (e) { + flashAPIErrors(e); + } + }, + saveResultSettings: async ({ resultFields }) => { + actions.saving(); + + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + const url = `/api/app_search/engines/${engineName}/result_settings`; + + actions.saving(); + + let response; + try { + response = await http.put(url, { + body: JSON.stringify({ + result_fields: resultFields, + }), + }); + } catch (e) { + flashAPIErrors(e); + } + + actions.initializeResultFields(response.result_fields, values.schema); + setSuccessMessage( + i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.saveSuccessMessage', + { + defaultMessage: 'Result settings have been saved successfully.', + } + ) + ); + }, + }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts index da763dfe7cdc4a..96bf277314a7b2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts @@ -34,4 +34,4 @@ export interface FieldResultSetting { snippetFallback: boolean; } -export type FieldResultSettingObject = Record; +export type FieldResultSettingObject = Record; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts index 2482ecab5892c4..0ed0353790a77a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts @@ -8,6 +8,8 @@ import { SchemaTypes } from '../../../shared/types'; import { + areFieldsAtDefaultSettings, + areFieldsEmpty, convertServerResultFieldsToResultFields, convertToServerFieldResultSetting, clearAllServerFields, @@ -172,3 +174,68 @@ describe('splitResultFields', () => { }); }); }); + +describe('areFieldsEmpty', () => { + it('should return true if all fields are empty objects', () => { + expect( + areFieldsEmpty({ + foo: {}, + bar: {}, + }) + ).toBe(true); + }); + it('should return false otherwise', () => { + expect( + areFieldsEmpty({ + foo: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + bar: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + }) + ).toBe(false); + }); +}); + +describe('areFieldsAtDefaultSettings', () => { + it('will return true if all settings for all fields are at their defaults', () => { + expect( + areFieldsAtDefaultSettings({ + foo: { + raw: true, + snippet: false, + snippetFallback: false, + }, + bar: { + raw: true, + snippet: false, + snippetFallback: false, + }, + }) + ).toEqual(true); + }); + + it('will return false otherwise', () => { + expect( + areFieldsAtDefaultSettings({ + foo: { + raw: true, + snippet: false, + snippetFallback: false, + }, + bar: { + raw: false, + snippet: true, + snippetFallback: true, + }, + }) + ).toEqual(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts index 0311132542d993..a44a18bef2810e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { isEqual, isEmpty } from 'lodash'; + import { Schema } from '../../../shared/types'; import { DEFAULT_FIELD_SETTINGS, DISABLED_FIELD_SETTINGS } from './constants'; @@ -115,3 +117,17 @@ export const splitResultFields = (resultFields: FieldResultSettingObject, schema return { textResultFields, nonTextResultFields }; }; + +export const areFieldsEmpty = (fields: FieldResultSettingObject) => { + const anyNonEmptyField = Object.values(fields).find((resultSettings) => { + return !isEmpty(resultSettings); + }); + return !anyNonEmptyField; +}; + +export const areFieldsAtDefaultSettings = (fields: FieldResultSettingObject) => { + const anyNonDefaultSettingsValue = Object.values(fields).find((resultSettings) => { + return !isEqual(resultSettings, DEFAULT_FIELD_SETTINGS); + }); + return !anyNonDefaultSettingsValue; +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts index 74f13a05aa7e64..1bd88c111f79f0 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts @@ -12,6 +12,7 @@ import { registerCredentialsRoutes } from './credentials'; import { registerCurationsRoutes } from './curations'; import { registerDocumentsRoutes, registerDocumentRoutes } from './documents'; import { registerEnginesRoutes } from './engines'; +import { registerResultSettingsRoutes } from './result_settings'; import { registerRoleMappingsRoutes } from './role_mappings'; import { registerSearchSettingsRoutes } from './search_settings'; import { registerSettingsRoutes } from './settings'; @@ -26,4 +27,5 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerCurationsRoutes(dependencies); registerSearchSettingsRoutes(dependencies); registerRoleMappingsRoutes(dependencies); + registerResultSettingsRoutes(dependencies); }; diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts new file mode 100644 index 00000000000000..8d1a7e3ead37b5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mockDependencies, mockRequestHandler, MockRouter } from '../../__mocks__'; + +import { registerResultSettingsRoutes } from './result_settings'; + +const resultFields = { + id: { + raw: {}, + }, + hp: { + raw: {}, + }, + name: { + raw: {}, + }, +}; + +describe('result settings routes', () => { + describe('GET /api/app_search/engines/{name}/result_settings/details', () => { + const mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/engines/{engineName}/result_settings/details', + }); + + beforeEach(() => { + registerResultSettingsRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request to enterprise search', () => { + mockRouter.callRoute({ + params: { engineName: 'some-engine' }, + }); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/result_settings/details', + }); + }); + }); + + describe('PUT /api/app_search/engines/{name}/result_settings', () => { + const mockRouter = new MockRouter({ + method: 'put', + path: '/api/app_search/engines/{engineName}/result_settings', + }); + + beforeEach(() => { + registerResultSettingsRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request to enterprise search', () => { + mockRouter.callRoute({ + params: { engineName: 'some-engine' }, + body: { + result_settings: resultFields, + }, + }); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/result_settings', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + result_fields: resultFields, + }, + }; + mockRouter.shouldValidate(request); + }); + + it('missing required fields', () => { + const request = { body: {} }; + mockRouter.shouldThrow(request); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts new file mode 100644 index 00000000000000..38cb4aa922738d --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../plugin'; + +const resultFields = schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })); + +export function registerResultSettingsRoutes({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/app_search/engines/{engineName}/result_settings/details', + validate: { + params: schema.object({ + engineName: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:engineName/result_settings/details', + }) + ); + + router.put( + { + path: '/api/app_search/engines/{engineName}/result_settings', + validate: { + params: schema.object({ + engineName: schema.string(), + }), + body: schema.object({ + result_fields: resultFields, + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:engineName/result_settings', + }) + ); +} From e90e5bae8c644708f795364d35023abf91f8709f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 22 Mar 2021 23:19:58 +0100 Subject: [PATCH 10/93] [APM] Fix readme for running tsc (#95074) --- x-pack/plugins/apm/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md index 9ddbd1757ad94f..b35024844a892a 100644 --- a/x-pack/plugins/apm/readme.md +++ b/x-pack/plugins/apm/readme.md @@ -118,7 +118,7 @@ _Note: Run the following commands from `kibana/`._ ### Typescript ``` -yarn tsc --noEmit --project x-pack/plugins/apm/tsconfig.json --skipLibCheck +yarn tsc --noEmit --emitDeclarationOnly false --project x-pack/plugins/apm/tsconfig.json --skipLibCheck ``` ### Prettier From 7d303eb42d17dc64fa26970fa2bfa4616cf11374 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 23 Mar 2021 08:24:58 +0200 Subject: [PATCH 11/93] [TSVB] Integrates the color service (#93749) * [TSVB] Integrates the color service * Fix i18n failure * Sync colors :) * Fix unit tests * Apply the multiple colors also for gauge * Fix * More unit tests * Cleanup * Be backwards compatible * Fetch palettesService on vis renderer * Fix eslint * Fix jest test * Fix color mapping for empty labels Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../vis_type_timeseries/common/types.ts | 7 +- .../vis_type_timeseries/common/vis_schema.ts | 4 + .../components/palette_picker.test.tsx | 64 ++++++++++ .../application/components/palette_picker.tsx | 95 +++++++++++++++ .../components/timeseries_visualization.tsx | 7 ++ .../application/components/vis_types/index.ts | 3 + .../components/vis_types/timeseries/config.js | 93 +++++++-------- .../components/vis_types/timeseries/vis.js | 14 ++- .../application/components/vis_with_splits.js | 34 +++++- .../lib/compute_gradient_final_color.test.ts | 21 ++++ .../lib/compute_gradient_final_color.ts | 16 +++ .../lib/get_split_by_terms_color.test.ts | 93 +++++++++++++++ .../lib/get_split_by_terms_color.ts | 77 ++++++++++++ .../public/application/lib/rainbow_colors.ts | 37 ++++++ .../visualizations/views/timeseries/index.js | 35 +++++- .../vis_type_timeseries/public/metrics_fn.ts | 5 +- .../public/metrics_type.ts | 5 +- .../public/timeseries_vis_renderer.tsx | 5 + .../lib/vis_data/helpers/get_split_colors.js | 53 --------- .../server/lib/vis_data/helpers/get_splits.js | 5 +- .../lib/vis_data/helpers/get_splits.test.js | 112 ------------------ .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 23 files changed, 556 insertions(+), 231 deletions(-) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/palette_picker.test.tsx create mode 100644 src/plugins/vis_type_timeseries/public/application/components/palette_picker.tsx create mode 100644 src/plugins/vis_type_timeseries/public/application/lib/compute_gradient_final_color.test.ts create mode 100644 src/plugins/vis_type_timeseries/public/application/lib/compute_gradient_final_color.ts create mode 100644 src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.test.ts create mode 100644 src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts create mode 100644 src/plugins/vis_type_timeseries/public/application/lib/rainbow_colors.ts delete mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_split_colors.js diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts index 155474e64b36e9..7d93232f310c97 100644 --- a/src/plugins/vis_type_timeseries/common/types.ts +++ b/src/plugins/vis_type_timeseries/common/types.ts @@ -25,7 +25,7 @@ export type PanelSchema = TypeOf; export type VisPayload = TypeOf; export type FieldObject = TypeOf; -interface PanelData { +export interface PanelData { id: string; label: string; data: Array<[number, number]>; @@ -57,3 +57,8 @@ export interface SanitizedFieldType { type: string; label?: string; } + +export enum PALETTES { + GRADIENT = 'gradient', + RAINBOW = 'rainbow', +} diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts index f5c48c2c8b2eb3..9c7e8ab04fc1d4 100644 --- a/src/plugins/vis_type_timeseries/common/vis_schema.ts +++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts @@ -172,6 +172,10 @@ export const seriesItems = schema.object({ series_interval: stringOptionalNullable, series_drop_last_bucket: numberIntegerOptional, split_color_mode: stringOptionalNullable, + palette: schema.object({ + type: stringRequired, + name: stringRequired, + }), split_filters: schema.maybe(schema.arrayOf(splitFiltersItems)), split_mode: stringRequired, stacked: stringRequired, diff --git a/src/plugins/vis_type_timeseries/public/application/components/palette_picker.test.tsx b/src/plugins/vis_type_timeseries/public/application/components/palette_picker.test.tsx new file mode 100644 index 00000000000000..7ee98317882cd3 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/palette_picker.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { mountWithIntl } from '@kbn/test/jest'; +import { ReactWrapper } from 'enzyme'; +import { PalettePicker, PalettePickerProps } from './palette_picker'; +import { chartPluginMock } from '../../../../charts/public/mocks'; +import { EuiColorPalettePicker } from '@elastic/eui'; +import { PALETTES } from '../../../common/types'; + +describe('PalettePicker', function () { + let props: PalettePickerProps; + let component: ReactWrapper; + + beforeAll(() => { + props = { + palettes: chartPluginMock.createPaletteRegistry(), + activePalette: { + type: 'palette', + name: 'kibana_palette', + }, + setPalette: jest.fn(), + color: '#68BC00', + }; + }); + + it('renders the EuiPalettePicker', () => { + component = mountWithIntl(); + expect(component.find(EuiColorPalettePicker).length).toBe(1); + }); + + it('renders the default palette if not activePalette is given', function () { + const { activePalette, ...newProps } = props; + component = mountWithIntl(); + const palettePicker = component.find(EuiColorPalettePicker); + expect(palettePicker.props().valueOfSelected).toBe('default'); + }); + + it('renders the activePalette palette if given', function () { + component = mountWithIntl(); + const palettePicker = component.find(EuiColorPalettePicker); + expect(palettePicker.props().valueOfSelected).toBe('kibana_palette'); + }); + + it('renders two additional palettes, rainbow and gradient', function () { + component = mountWithIntl(); + const palettePicker = component.find(EuiColorPalettePicker); + expect(palettePicker.props().palettes).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: PALETTES.RAINBOW, + }), + expect.objectContaining({ + value: PALETTES.GRADIENT, + }), + ]) + ); + }); +}); diff --git a/src/plugins/vis_type_timeseries/public/application/components/palette_picker.tsx b/src/plugins/vis_type_timeseries/public/application/components/palette_picker.tsx new file mode 100644 index 00000000000000..e094cb9add9787 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/palette_picker.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; +import { EuiColorPalettePicker } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { rainbowColors } from '../lib/rainbow_colors'; +import { computeGradientFinalColor } from '../lib/compute_gradient_final_color'; +import { PALETTES } from '../../../common/types'; + +export interface PalettePickerProps { + activePalette?: PaletteOutput; + palettes: PaletteRegistry; + setPalette: (value: PaletteOutput) => void; + color: string; +} + +export function PalettePicker({ activePalette, palettes, setPalette, color }: PalettePickerProps) { + const finalGradientColor = computeGradientFinalColor(color); + + return ( + !internal) + .map(({ id, title, getColors }) => { + return { + value: id, + title, + type: 'fixed' as const, + palette: getColors(10), + }; + }), + { + value: PALETTES.GRADIENT, + title: i18n.translate('visTypeTimeseries.timeSeries.gradientLabel', { + defaultMessage: 'Gradient', + }), + type: 'fixed', + palette: palettes + .get('custom') + .getColors(10, { colors: [color, finalGradientColor], gradient: true }), + }, + { + value: PALETTES.RAINBOW, + title: i18n.translate('visTypeTimeseries.timeSeries.rainbowLabel', { + defaultMessage: 'Rainbow', + }), + type: 'fixed', + palette: palettes + .get('custom') + .getColors(10, { colors: rainbowColors.slice(0, 10), gradient: false }), + }, + ]} + onChange={(newPalette) => { + if (newPalette === PALETTES.RAINBOW) { + setPalette({ + type: 'palette', + name: PALETTES.RAINBOW, + params: { + colors: rainbowColors, + gradient: false, + }, + }); + } else if (newPalette === PALETTES.GRADIENT) { + setPalette({ + type: 'palette', + name: PALETTES.GRADIENT, + params: { + colors: [color, finalGradientColor], + gradient: true, + }, + }); + } else { + setPalette({ + type: 'palette', + name: newPalette, + }); + } + }} + valueOfSelected={activePalette?.name || 'default'} + selectionDisplay={'palette'} + /> + ); +} diff --git a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx index 9c8944a2e6e622..ac15a788d6dabb 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx @@ -11,6 +11,7 @@ import React, { useCallback, useEffect } from 'react'; import { IUiSettingsClient } from 'src/core/public'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { PersistedState } from 'src/plugins/visualizations/public'; +import { PaletteRegistry } from 'src/plugins/charts/public'; // @ts-expect-error import { ErrorComponent } from './error'; @@ -25,6 +26,8 @@ interface TimeseriesVisualizationProps { model: TimeseriesVisParams; visData: TimeseriesVisData; uiState: PersistedState; + syncColors: boolean; + palettesService: PaletteRegistry; } function TimeseriesVisualization({ @@ -34,6 +37,8 @@ function TimeseriesVisualization({ handlers, uiState, getConfig, + syncColors, + palettesService, }: TimeseriesVisualizationProps) { const onBrush = useCallback( (gte: string, lte: string) => { @@ -91,6 +96,8 @@ function TimeseriesVisualization({ uiState={uiState} onBrush={onBrush} onUiState={handleUiState} + syncColors={syncColors} + palettesService={palettesService} /> ); } diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/index.ts b/src/plugins/vis_type_timeseries/public/application/components/vis_types/index.ts index 150a3a716a8790..0e169c50e4db66 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/index.ts +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/index.ts @@ -10,6 +10,7 @@ import React, { lazy } from 'react'; import { IUiSettingsClient } from 'src/core/public'; import { PersistedState } from 'src/plugins/visualizations/public'; +import { PaletteRegistry } from 'src/plugins/charts/public'; import { TimeseriesVisParams } from '../../../types'; import { TimeseriesVisData } from '../../../../common/types'; @@ -54,4 +55,6 @@ export interface TimeseriesVisProps { uiState: PersistedState; visData: TimeseriesVisData; getConfig: IUiSettingsClient['get']; + syncColors: boolean; + palettesService: PaletteRegistry; } diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js index b0dd5ea4572afb..3df12dafd5a66b 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js @@ -7,7 +7,7 @@ */ import PropTypes from 'prop-types'; -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { DataFormatPicker } from '../../data_format_picker'; import { createSelectHandler } from '../../lib/create_select_handler'; import { YesNo } from '../../yes_no'; @@ -28,7 +28,8 @@ import { } from '@elastic/eui'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; import { SeriesConfigQueryBarWithIgnoreGlobalFilter } from '../../series_config_query_bar_with_ignore_global_filter'; - +import { PalettePicker } from '../../palette_picker'; +import { getChartsSetup } from '../../../../services'; import { isPercentDisabled } from '../../lib/stacked'; import { STACKED_OPTIONS } from '../../../visualizations/constants/chart'; @@ -41,7 +42,6 @@ export const TimeseriesConfig = injectI18n(function (props) { point_size: '', value_template: '{{value}}', offset_time: '', - split_color_mode: 'kibana', axis_min: '', axis_max: '', stacked: STACKED_OPTIONS.NONE, @@ -124,33 +124,23 @@ export const TimeseriesConfig = injectI18n(function (props) { const selectedChartTypeOption = chartTypeOptions.find((option) => { return model.chart_type === option.value; }); + const { palettes } = getChartsSetup(); + const [palettesRegistry, setPalettesRegistry] = useState(null); - const splitColorOptions = [ - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeSeries.defaultPaletteLabel', - defaultMessage: 'Default palette', - }), - value: 'kibana', - }, - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeSeries.rainbowLabel', - defaultMessage: 'Rainbow', - }), - value: 'rainbow', - }, - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeSeries.gradientLabel', - defaultMessage: 'Gradient', - }), - value: 'gradient', - }, - ]; - const selectedSplitColorOption = splitColorOptions.find((option) => { - return model.split_color_mode === option.value; - }); + useEffect(() => { + const fetchPalettes = async () => { + const palettesService = await palettes.getPalettes(); + setPalettesRegistry(palettesService); + }; + fetchPalettes(); + }, [palettes]); + + const handlePaletteChange = (val) => { + props.onChange({ + split_color_mode: null, + palette: val, + }); + }; let type; @@ -342,6 +332,14 @@ export const TimeseriesConfig = injectI18n(function (props) { ? props.model.series_index_pattern : props.indexPatternForQuery; + const initialPalette = { + ...model.palette, + name: + model.split_color_mode === 'kibana' + ? 'kibana_palette' + : model.split_color_mode || model.palette.name, + }; + return (

@@ -420,25 +418,26 @@ export const TimeseriesConfig = injectI18n(function (props) { - - + + } + > + - } - > - - - + + + )} diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js index fb99b890d23276..5a2fc05817f715 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js @@ -132,7 +132,7 @@ class TimeseriesVisualization extends Component { }; render() { - const { model, visData, onBrush } = this.props; + const { model, visData, onBrush, syncColors, palettesService } = this.props; const series = get(visData, `${model.id}.series`, []); const interval = getInterval(visData, model); const yAxisIdGenerator = htmlIdGenerator('yaxis'); @@ -163,6 +163,13 @@ class TimeseriesVisualization extends Component { seriesGroup, this.props.getConfig ); + const palette = { + ...seriesGroup.palette, + name: + seriesGroup.split_color_mode === 'kibana' + ? 'kibana_palette' + : seriesGroup.split_color_mode || seriesGroup.palette?.name, + }; const yScaleType = hasSeparateAxis ? TimeseriesVisualization.getAxisScaleType(seriesGroup) : mainAxisScaleType; @@ -182,6 +189,9 @@ class TimeseriesVisualization extends Component { seriesDataRow.groupId = groupId; seriesDataRow.yScaleType = yScaleType; seriesDataRow.hideInLegend = Boolean(seriesGroup.hide_in_legend); + seriesDataRow.palette = palette; + seriesDataRow.baseColor = seriesGroup.color; + seriesDataRow.isSplitByTerms = seriesGroup.split_mode === 'terms'; }); if (isCustomDomain) { @@ -223,6 +233,8 @@ class TimeseriesVisualization extends Component { xAxisLabel={getAxisLabelString(interval)} xAxisFormatter={this.xAxisFormatter(interval)} annotations={this.prepareAnnotations()} + syncColors={syncColors} + palettesService={palettesService} />
); diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js b/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js index 59f4724c1394cb..7dc6a26185e2bf 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js @@ -6,15 +6,40 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { getDisplayName } from './lib/get_display_name'; import { labelDateFormatter } from './lib/label_date_formatter'; import { findIndex, first } from 'lodash'; import { emptyLabel } from '../../../common/empty_label'; +import { getSplitByTermsColor } from '../lib/get_split_by_terms_color'; export function visWithSplits(WrappedComponent) { function SplitVisComponent(props) { - const { model, visData } = props; + const { model, visData, syncColors, palettesService } = props; + + const getSeriesColor = useCallback( + (seriesName, seriesId, baseColor) => { + const palette = { + ...model.series[0].palette, + name: + model.series[0].split_color_mode === 'kibana' + ? 'kibana_palette' + : model.series[0].split_color_mode || model.series[0].palette.name, + }; + const props = { + seriesById: visData[model.id].series, + seriesName, + seriesId, + baseColor, + seriesPalette: palette, + palettesRegistry: palettesService, + syncColors, + }; + return getSplitByTermsColor(props) || null; + }, + [model, palettesService, syncColors, visData] + ); + if (!model || !visData || !visData[model.id]) return ; if (visData[model.id].series.every((s) => s.id.split(':').length === 1)) { return ; @@ -36,11 +61,14 @@ export function visWithSplits(WrappedComponent) { } const labelHasKeyPlaceholder = /{{\s*key\s*}}/.test(seriesModel.label); + const color = series.color || seriesModel.color; + const finalColor = + model.series[0].split_mode === 'terms' ? getSeriesColor(label, series.id, color) : color; acc[splitId].series.push({ ...series, id: seriesId, - color: series.color || seriesModel.color, + color: finalColor, label: seriesModel.label && !labelHasKeyPlaceholder ? seriesModel.label : label, }); return acc; diff --git a/src/plugins/vis_type_timeseries/public/application/lib/compute_gradient_final_color.test.ts b/src/plugins/vis_type_timeseries/public/application/lib/compute_gradient_final_color.test.ts new file mode 100644 index 00000000000000..326b14c112c352 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/lib/compute_gradient_final_color.test.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { computeGradientFinalColor } from './compute_gradient_final_color'; + +describe('computeGradientFinalColor Function', () => { + it('Should compute the gradient final color correctly for rgb color', () => { + const color = computeGradientFinalColor('rgba(211,96,134,1)'); + expect(color).toEqual('rgb(145, 40, 75)'); + }); + + it('Should compute the gradient final color correctly for hex color', () => { + const color = computeGradientFinalColor('#6092C0'); + expect(color).toEqual('rgb(43, 77, 108)'); + }); +}); diff --git a/src/plugins/vis_type_timeseries/public/application/lib/compute_gradient_final_color.ts b/src/plugins/vis_type_timeseries/public/application/lib/compute_gradient_final_color.ts new file mode 100644 index 00000000000000..3dbd6796c0574c --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/lib/compute_gradient_final_color.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import Color from 'color'; + +export const computeGradientFinalColor = (color: string): string => { + let inputColor = new Color(color); + const hsl = inputColor.hsl().object(); + hsl.l -= inputColor.luminosity() * 100; + inputColor = Color.hsl(hsl); + return inputColor.rgb().toString(); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.test.ts b/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.test.ts new file mode 100644 index 00000000000000..1d815316425663 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.test.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { chartPluginMock } from '../../../../charts/public/mocks'; +import { getSplitByTermsColor, SplitByTermsColorProps } from './get_split_by_terms_color'; + +const chartsRegistry = chartPluginMock.createPaletteRegistry(); +const props = ({ + seriesById: [ + { + id: '61ca57f1-469d-11e7-af02-69e470af7417', + label: 'Count', + color: 'rgb(104, 188, 0)', + data: [ + [1615273200000, 45], + [1615284000000, 78], + ], + seriesId: '61ca57f1-469d-11e7-af02-69e470af7417', + stack: 'none', + lines: { + show: true, + fill: 0.5, + lineWidth: 1, + steps: false, + }, + points: { + show: true, + radius: 1, + lineWidth: 1, + }, + bars: { + show: false, + fill: 0.5, + lineWidth: 1, + }, + groupId: 'yaxis_2b3507e0-8630-11eb-b627-ff396f1f7246_main_group', + yScaleType: 'linear', + }, + ], + seriesName: 'Count', + seriesId: '61ca57f1-469d-11e7-af02-69e470af7417', + baseColor: '#68BC00', + seriesPalette: { + name: 'rainbow', + params: { + colors: ['#0F1419', '#666666'], + gradient: false, + }, + type: 'palette', + }, + palettesRegistry: chartsRegistry, + syncColors: false, +} as unknown) as SplitByTermsColorProps; + +describe('getSplitByTermsColor Function', () => { + it('Should return null if no palette given', () => { + const newProps = ({ ...props, seriesPalette: null } as unknown) as SplitByTermsColorProps; + const color = getSplitByTermsColor(newProps); + expect(color).toEqual(null); + }); + + it('Should return color for empty seriesName', () => { + const newProps = { ...props, seriesName: '' }; + const color = getSplitByTermsColor(newProps); + expect(color).toEqual('blue'); + }); + + it('Should return color for the given palette', () => { + const color = getSplitByTermsColor(props); + expect(color).toEqual('blue'); + }); + + it('Should call the `get` palette method with the correct arguments', () => { + const spy = jest.spyOn(chartsRegistry, 'get'); + const gradientPalette = { + name: 'gradient', + params: { + colors: ['#68BC00', '#666666'], + gradient: true, + }, + }; + const newProps = ({ + ...props, + seriesPalette: gradientPalette, + } as unknown) as SplitByTermsColorProps; + getSplitByTermsColor(newProps); + expect(spy).toHaveBeenCalledWith('custom'); + }); +}); diff --git a/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts b/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts new file mode 100644 index 00000000000000..e8f81bd8c60453 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; +import { PALETTES, PanelData } from '../../../common/types'; +import { computeGradientFinalColor } from './compute_gradient_final_color'; +import { rainbowColors } from './rainbow_colors'; +import { emptyLabel } from '../../../common/empty_label'; + +interface PaletteParams { + colors: string[]; + gradient: boolean; +} + +export interface SplitByTermsColorProps { + seriesById: PanelData[]; + seriesName: string; + seriesId: string; + baseColor: string; + seriesPalette: PaletteOutput; + palettesRegistry: PaletteRegistry; + syncColors: boolean; +} + +export const getSplitByTermsColor = ({ + seriesById, + seriesName, + seriesId, + baseColor, + seriesPalette, + palettesRegistry, + syncColors, +}: SplitByTermsColorProps) => { + if (!seriesPalette) { + return null; + } + const paletteName = + seriesPalette.name === PALETTES.RAINBOW || seriesPalette.name === PALETTES.GRADIENT + ? 'custom' + : seriesPalette.name; + + const paletteParams = + seriesPalette.name === PALETTES.GRADIENT + ? { + ...seriesPalette.params, + colors: [baseColor, computeGradientFinalColor(baseColor)], + gradient: true, + } + : seriesPalette.name === PALETTES.RAINBOW + ? { + ...seriesPalette.params, + colors: rainbowColors, + } + : seriesPalette.params; + + const outputColor = palettesRegistry?.get(paletteName).getColor( + [ + { + name: seriesName || emptyLabel, + rankAtDepth: seriesById.findIndex(({ id }) => id === seriesId), + totalSeriesAtDepth: seriesById.length, + }, + ], + { + maxDepth: 1, + totalSeries: seriesById.length, + behindText: false, + syncColors, + }, + paletteParams + ); + return outputColor; +}; diff --git a/src/plugins/vis_type_timeseries/public/application/lib/rainbow_colors.ts b/src/plugins/vis_type_timeseries/public/application/lib/rainbow_colors.ts new file mode 100644 index 00000000000000..5ad8668326cd44 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/lib/rainbow_colors.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Using a random color generator presented awful colors and unpredictable color schemes. + * So we needed to come up with a color scheme of our own that creates consistent, pleasing color patterns. + * The order allows us to guarantee that 1st, 2nd, 3rd, etc values always get the same color. + */ +export const rainbowColors: string[] = [ + '#68BC00', + '#009CE0', + '#B0BC00', + '#16A5A5', + '#D33115', + '#E27300', + '#FCC400', + '#7B64FF', + '#FA28FF', + '#333333', + '#808080', + '#194D33', + '#0062B1', + '#808900', + '#0C797D', + '#9F0500', + '#C45100', + '#FB9E00', + '#653294', + '#AB149E', + '#0F1419', + '#666666', +]; diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js index d306704463c2ed..537344a6da39a8 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useCallback } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { labelDateFormatter } from '../../../components/lib/label_date_formatter'; @@ -31,6 +31,7 @@ import { BarSeriesDecorator } from './decorators/bar_decorator'; import { getStackAccessors } from './utils/stack_format'; import { getBaseTheme, getChartClasses } from './utils/theme'; import { emptyLabel } from '../../../../../common/empty_label'; +import { getSplitByTermsColor } from '../../../lib/get_split_by_terms_color'; const generateAnnotationData = (values, formatter) => values.map(({ key, docs }) => ({ @@ -59,8 +60,11 @@ export const TimeSeries = ({ onBrush, xAxisFormatter, annotations, + syncColors, + palettesService, }) => { const chartRef = useRef(); + // const [palettesRegistry, setPalettesRegistry] = useState(null); useEffect(() => { const updateCursor = (cursor) => { @@ -87,10 +91,9 @@ export const TimeSeries = ({ // If the color isn't configured by the user, use the color mapping service // to assign a color from the Kibana palette. Colors will be shared across the // session, including dashboards. - const { legacyColors: colors, theme: themeService } = getChartsSetup(); - const baseTheme = getBaseTheme(themeService.useChartsBaseTheme(), backgroundColor); + const { theme: themeService } = getChartsSetup(); - colors.mappedColors.mapKeys(series.filter(({ color }) => !color).map(({ label }) => label)); + const baseTheme = getBaseTheme(themeService.useChartsBaseTheme(), backgroundColor); const onBrushEndListener = ({ x }) => { if (!x) { @@ -100,6 +103,23 @@ export const TimeSeries = ({ onBrush(min, max); }; + const getSeriesColor = useCallback( + (seriesName, seriesGroupId, seriesId) => { + const seriesById = series.filter((s) => s.seriesId === seriesGroupId); + const props = { + seriesById, + seriesName, + seriesId, + baseColor: seriesById[0].baseColor, + seriesPalette: seriesById[0].palette, + palettesRegistry: palettesService, + syncColors, + }; + return getSplitByTermsColor(props) || null; + }, + [palettesService, series, syncColors] + ); + return ( ({ help: '', }, }, - async fn(input, args, { getSearchSessionId }) { + async fn(input, args, { getSearchSessionId, isSyncColorsEnabled }) { const visParams: TimeseriesVisParams = JSON.parse(args.params); const uiState = JSON.parse(args.uiState); + const syncColors = isSyncColorsEnabled?.() ?? false; const response = await metricsRequestHandler({ input, @@ -70,6 +72,7 @@ export const createMetricsFn = (): TimeseriesExpressionFunctionDefinition => ({ value: { visParams, visData: response, + syncColors, }, }; }, diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts index a19d664a949279..9e996fcc74833c 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -31,7 +31,10 @@ export const metricsVisDefinition = { id: '61ca57f1-469d-11e7-af02-69e470af7417', color: '#68BC00', split_mode: 'everything', - split_color_mode: 'kibana', + palette: { + type: 'palette', + name: 'default', + }, metrics: [ { id: '61ca57f2-469d-11e7-af02-69e470af7417', diff --git a/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx b/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx index 06c5d20f08a7cb..c314594aa54209 100644 --- a/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx +++ b/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx @@ -16,6 +16,7 @@ import { ExpressionRenderDefinition } from '../../expressions/common/expression_ import { TimeseriesRenderValue } from './metrics_fn'; import { TimeseriesVisData } from '../common/types'; import { TimeseriesVisParams } from './types'; +import { getChartsSetup } from './services'; const TimeseriesVisualization = lazy( () => import('./application/components/timeseries_visualization') @@ -39,8 +40,10 @@ export const getTimeseriesVisRenderer: (deps: { handlers.onDestroy(() => { unmountComponentAtNode(domNode); }); + const { palettes } = getChartsSetup(); const showNoResult = !checkIfDataExists(config.visData, config.visParams); + const palettesService = await palettes.getPalettes(); render( , domNode diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_split_colors.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_split_colors.js deleted file mode 100644 index 7a4d61487f7183..00000000000000 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_split_colors.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Color from 'color'; - -export function getSplitColors(inputColor, size = 10, style = 'kibana') { - const color = new Color(inputColor); - const colors = []; - let workingColor = Color.hsl(color.hsl().object()); - - if (style === 'rainbow') { - return [ - '#68BC00', - '#009CE0', - '#B0BC00', - '#16A5A5', - '#D33115', - '#E27300', - '#FCC400', - '#7B64FF', - '#FA28FF', - '#333333', - '#808080', - '#194D33', - '#0062B1', - '#808900', - '#0C797D', - '#9F0500', - '#C45100', - '#FB9E00', - '#653294', - '#AB149E', - '#0F1419', - '#666666', - ]; - } else if (style === 'gradient') { - colors.push(color.string()); - const rotateBy = color.luminosity() / (size - 1); - for (let i = 0; i < size - 1; i++) { - const hsl = workingColor.hsl().object(); - hsl.l -= rotateBy * 100; - workingColor = Color.hsl(hsl); - colors.push(workingColor.rgb().toString()); - } - } - - return colors; -} diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js index 952696644c7d9a..f22226e03a5aab 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js @@ -10,7 +10,6 @@ import Color from 'color'; import { calculateLabel } from '../../../../common/calculate_label'; import _ from 'lodash'; import { getLastMetric } from './get_last_metric'; -import { getSplitColors } from './get_split_colors'; import { formatKey } from './format_key'; const getTimeSeries = (resp, series) => @@ -30,14 +29,12 @@ export async function getSplits(resp, panel, series, meta, extractFields) { if (buckets) { if (Array.isArray(buckets)) { - const size = buckets.length; - const colors = getSplitColors(series.color, size, series.split_color_mode); return buckets.map((bucket) => { bucket.id = `${series.id}:${bucket.key}`; bucket.splitByLabel = splitByLabel; bucket.label = formatKey(bucket.key, series); bucket.labelFormatted = bucket.key_as_string ? formatKey(bucket.key_as_string, series) : ''; - bucket.color = panel.type === 'top_n' ? color.string() : colors.shift(); + bucket.color = color.string(); bucket.meta = meta; return bucket; }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js index d605bfc9d8de72..e2ae404d98970e 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js @@ -223,118 +223,6 @@ describe('getSplits(resp, panel, series)', () => { ]); }); - describe('terms group bys', () => { - const resp = { - aggregations: { - SERIES: { - buckets: [ - { - key: 'example-01', - timeseries: { buckets: [] }, - SIBAGG: { value: 1 }, - }, - { - key: 'example-02', - timeseries: { buckets: [] }, - SIBAGG: { value: 2 }, - }, - ], - meta: { bucketSize: 10 }, - }, - }, - }; - - test('should return a splits with no color', async () => { - const series = { - id: 'SERIES', - color: '#F00', - split_mode: 'terms', - terms_field: 'beat.hostname', - terms_size: 10, - metrics: [ - { id: 'AVG', type: 'avg', field: 'cpu' }, - { id: 'SIBAGG', type: 'avg_bucket', field: 'AVG' }, - ], - }; - const panel = { type: 'timeseries' }; - - expect(await getSplits(resp, panel, series)).toEqual([ - { - id: 'SERIES:example-01', - key: 'example-01', - label: 'example-01', - labelFormatted: '', - meta: { bucketSize: 10 }, - color: undefined, - splitByLabel: 'Overall Average of Average of cpu', - timeseries: { buckets: [] }, - SIBAGG: { value: 1 }, - }, - { - id: 'SERIES:example-02', - key: 'example-02', - label: 'example-02', - labelFormatted: '', - meta: { bucketSize: 10 }, - color: undefined, - splitByLabel: 'Overall Average of Average of cpu', - timeseries: { buckets: [] }, - SIBAGG: { value: 2 }, - }, - ]); - }); - - test('should return gradient color', async () => { - const series = { - id: 'SERIES', - color: '#F00', - split_mode: 'terms', - split_color_mode: 'gradient', - terms_field: 'beat.hostname', - terms_size: 10, - metrics: [ - { id: 'AVG', type: 'avg', field: 'cpu' }, - { id: 'SIBAGG', type: 'avg_bucket', field: 'AVG' }, - ], - }; - const panel = { type: 'timeseries' }; - - expect(await getSplits(resp, panel, series)).toEqual([ - expect.objectContaining({ - color: 'rgb(255, 0, 0)', - }), - expect.objectContaining({ - color: 'rgb(147, 0, 0)', - }), - ]); - }); - - test('should return rainbow color', async () => { - const series = { - id: 'SERIES', - color: '#F00', - split_mode: 'terms', - split_color_mode: 'rainbow', - terms_field: 'beat.hostname', - terms_size: 10, - metrics: [ - { id: 'AVG', type: 'avg', field: 'cpu' }, - { id: 'SIBAGG', type: 'avg_bucket', field: 'AVG' }, - ], - }; - const panel = { type: 'timeseries' }; - - expect(await getSplits(resp, panel, series)).toEqual([ - expect.objectContaining({ - color: '#68BC00', - }), - expect.objectContaining({ - color: '#009CE0', - }), - ]); - }); - }); - test('should return a splits for filters group bys', async () => { const resp = { aggregations: { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 53245fde2796fd..ad3cf86081e156 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4438,7 +4438,6 @@ "visTypeTimeseries.timeSeries.chartLine.stepsLabel": "ステップ", "visTypeTimeseries.timeSeries.cloneSeriesTooltip": "数列のクローンを作成", "visTypeTimeseries.timeseries.dataTab.dataButtonLabel": "データ", - "visTypeTimeseries.timeSeries.defaultPaletteLabel": "既定のパレット", "visTypeTimeseries.timeSeries.deleteSeriesTooltip": "数列を削除", "visTypeTimeseries.timeSeries.gradientLabel": "グラデーション", "visTypeTimeseries.timeSeries.hideInLegendLabel": "凡例で非表示", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d26a0111fe3822..babbccfab4ddec 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4464,7 +4464,6 @@ "visTypeTimeseries.timeSeries.chartLine.stepsLabel": "步长", "visTypeTimeseries.timeSeries.cloneSeriesTooltip": "克隆序列", "visTypeTimeseries.timeseries.dataTab.dataButtonLabel": "数据", - "visTypeTimeseries.timeSeries.defaultPaletteLabel": "默认调色板", "visTypeTimeseries.timeSeries.deleteSeriesTooltip": "删除序列", "visTypeTimeseries.timeSeries.gradientLabel": "渐变", "visTypeTimeseries.timeSeries.hideInLegendLabel": "在图例中隐藏", From 2b4bd107816a370930eeedb1615ac88a79f935b5 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 23 Mar 2021 09:25:00 +0100 Subject: [PATCH 12/93] [Observability] Shared Field Suggestion value component (#94841) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../field_value_selection.stories.tsx | 101 ++++++++++++++ .../field_value_selection.test.tsx | 53 +++++++ .../field_value_selection.tsx | 131 ++++++++++++++++++ .../shared/field_value_suggestions/index.tsx | 56 ++++++++ .../public/components/shared/index.tsx | 13 +- .../public/hooks/use_values_list.ts | 36 +++++ x-pack/plugins/observability/public/index.ts | 6 +- 7 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx create mode 100644 x-pack/plugins/observability/public/hooks/use_values_list.ts diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx new file mode 100644 index 00000000000000..8e95d8e711ad0a --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ComponentType, useEffect, useState } from 'react'; +import { IntlProvider } from 'react-intl'; +import { Observable } from 'rxjs'; +import { CoreStart } from 'src/core/public'; +import { text } from '@storybook/addon-knobs'; +import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; +import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public'; +import { FieldValueSelection, FieldValueSelectionProps } from '../field_value_selection'; + +const KibanaReactContext = createKibanaReactContext(({ + uiSettings: { get: () => {}, get$: () => new Observable() }, +} as unknown) as Partial); + +export default { + title: 'app/Shared/FieldValueSuggestions', + component: FieldValueSelection, + decorators: [ + (Story: ComponentType) => ( + + + + {}} + value={''} + loading={false} + setQuery={() => {}} + /> + + + + ), + ], +}; + +export function ValuesLoaded() { + return ( + {}} + value={''} + loading={false} + setQuery={() => {}} + /> + ); +} + +export function LoadingState() { + return ( + {}} + value={''} + loading={true} + setQuery={() => {}} + /> + ); +} + +export function EmptyState() { + return ( + {}} + value={''} + loading={false} + setQuery={() => {}} + /> + ); +} + +export function SearchState(args: FieldValueSelectionProps) { + const name = text('Query', ''); + + const [, setQuery] = useState(''); + useEffect(() => { + setQuery(name); + }, [name]); + + return ( + {}} + value={''} + loading={false} + setQuery={setQuery} + /> + ); +} diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.test.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.test.tsx new file mode 100644 index 00000000000000..46f2ce6efa97c0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.test.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount, render } from 'enzyme'; +import { FieldValueSelection } from './field_value_selection'; +import { EuiButton, EuiSelectableList } from '@elastic/eui'; + +describe('FieldValueSelection', () => { + it('renders a label for button', async () => { + const wrapper = render( + {}} + value={''} + loading={false} + setQuery={() => {}} + /> + ); + + const btn = wrapper.find('[data-test-subj=fieldValueSelectionBtn]'); + + expect(btn.text()).toBe('Service name'); + }); + it('renders a list on click', async () => { + const wrapper = mount( + {}} + value={''} + loading={false} + setQuery={() => {}} + /> + ); + + const btn = wrapper.find(EuiButton); + btn.simulate('click'); + + const list = wrapper.find(EuiSelectableList); + + expect((list.props() as any).visibleOptions).toEqual([ + { label: 'elastic co frontend' }, + { label: 'apm server' }, + { label: 'opbean python' }, + ]); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx new file mode 100644 index 00000000000000..b2c682dc58937f --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FormEvent, Fragment, useEffect, useState, Dispatch, SetStateAction } from 'react'; +import { + EuiButton, + EuiPopover, + EuiPopoverFooter, + EuiPopoverTitle, + EuiSelectable, + EuiSelectableOption, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface FieldValueSelectionProps { + value?: string; + label: string; + loading: boolean; + onChange: (val?: string) => void; + values?: string[]; + setQuery: Dispatch>; +} + +const formatOptions = (values?: string[], value?: string): EuiSelectableOption[] => { + return (values ?? []).map((val) => ({ + label: val, + ...(value === val ? { checked: 'on' } : {}), + })); +}; + +export function FieldValueSelection({ + label, + value, + loading, + values, + setQuery, + onChange: onSelectionChange, +}: FieldValueSelectionProps) { + const [options, setOptions] = useState(formatOptions(values, value)); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + useEffect(() => { + setOptions(formatOptions(values, value)); + }, [values, value]); + + const onButtonClick = () => { + setIsPopoverOpen(!isPopoverOpen); + }; + + const closePopover = () => { + setIsPopoverOpen(false); + }; + + const onChange = (optionsN: EuiSelectableOption[]) => { + setOptions(optionsN); + }; + + const onValueChange = (evt: FormEvent) => { + setQuery((evt.target as HTMLInputElement).value); + }; + + const button = ( + + {label} + + ); + + return ( + + + + {(list, search) => ( +
+ {search} + {list} + + opt?.checked === 'on')) + } + onClick={() => { + const selected = options.find((opt) => opt?.checked === 'on'); + onSelectionChange(selected?.label); + setIsPopoverOpen(false); + }} + > + {i18n.translate('xpack.observability.fieldValueSelection.apply', { + defaultMessage: 'Apply', + })} + + +
+ )} +
+
+
+ ); +} diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx new file mode 100644 index 00000000000000..20c37cf753a015 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; + +import { useDebounce } from 'react-use'; +import { useValuesList } from '../../../hooks/use_values_list'; +import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; +import { FieldValueSelection } from './field_value_selection'; + +export interface FieldValueSuggestionsProps { + value?: string; + label: string; + indexPattern: IIndexPattern; + sourceField: string; + onChange: (val?: string) => void; +} + +export function FieldValueSuggestions({ + sourceField, + label, + indexPattern, + value, + onChange: onSelectionChange, +}: FieldValueSuggestionsProps) { + const [query, setQuery] = useState(''); + const [debouncedValue, setDebouncedValue] = useState(''); + + const { values, loading } = useValuesList({ indexPattern, query, sourceField }); + + useDebounce( + () => { + setQuery(debouncedValue); + }, + 400, + [debouncedValue] + ); + + return ( + + ); +} + +// eslint-disable-next-line import/no-default-export +export default FieldValueSuggestions; diff --git a/x-pack/plugins/observability/public/components/shared/index.tsx b/x-pack/plugins/observability/public/components/shared/index.tsx index bdeb4a1ea99904..976139fdc12171 100644 --- a/x-pack/plugins/observability/public/components/shared/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/index.tsx @@ -6,7 +6,8 @@ */ import React, { lazy, Suspense } from 'react'; -import { CoreVitalProps, HeaderMenuPortalProps } from './types'; +import type { CoreVitalProps, HeaderMenuPortalProps } from './types'; +import type { FieldValueSuggestionsProps } from './field_value_suggestions'; const CoreVitalsLazy = lazy(() => import('./core_web_vitals/index')); @@ -27,3 +28,13 @@ export function HeaderMenuPortal(props: HeaderMenuPortalProps) { ); } + +const FieldValueSuggestionsLazy = lazy(() => import('./field_value_suggestions/index')); + +export function FieldValueSuggestions(props: FieldValueSuggestionsProps) { + return ( + + + + ); +} diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts new file mode 100644 index 00000000000000..25a12ab4a9ebd1 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IIndexPattern } from '../../../../../src/plugins/data/common'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { useFetcher } from './use_fetcher'; +import { ESFilter } from '../../../../../typings/elasticsearch'; +import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; + +interface Props { + sourceField: string; + query?: string; + indexPattern: IIndexPattern; + filters?: ESFilter[]; +} + +export const useValuesList = ({ sourceField, indexPattern, query, filters }: Props) => { + const { + services: { data }, + } = useKibana<{ data: DataPublicPluginStart }>(); + + const { data: values, status } = useFetcher(() => { + return data.autocomplete.getValueSuggestions({ + indexPattern, + query: query || '', + field: indexPattern.fields.find(({ name }) => name === sourceField)!, + boolFilter: filters ?? [], + }); + }, [sourceField, query, data.autocomplete, indexPattern, filters]); + + return { values, loading: status === 'loading' || status === 'pending' }; +}; diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 1db5f62823e9bc..dfe454ccc7b871 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -18,7 +18,11 @@ export const plugin: PluginInitializer Date: Tue, 23 Mar 2021 04:40:21 -0400 Subject: [PATCH 13/93] Changed filter popover switches to checkboxes (#95002) --- .../public/ui/apply_filters/apply_filter_popover_content.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/data/public/ui/apply_filters/apply_filter_popover_content.tsx b/src/plugins/data/public/ui/apply_filters/apply_filter_popover_content.tsx index 6a669a0f19b532..23de8327ce1f19 100644 --- a/src/plugins/data/public/ui/apply_filters/apply_filter_popover_content.tsx +++ b/src/plugins/data/public/ui/apply_filters/apply_filter_popover_content.tsx @@ -15,7 +15,7 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiSwitch, + EuiCheckbox, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; @@ -64,7 +64,8 @@ export default class ApplyFiltersPopoverContent extends Component {mappedFilters.map((filter, i) => ( - this.toggleFilterSelected(i)} From bfe6c5186c87731e63b2a6a7ec58f67663e0a371 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Tue, 23 Mar 2021 11:20:21 +0100 Subject: [PATCH 14/93] [APM] Fix TypeScript optimization script (#95137) --- .../apm/scripts/optimize-tsconfig/optimize.js | 68 ++++++++----------- .../apm/scripts/optimize-tsconfig/paths.js | 6 +- .../scripts/optimize-tsconfig/tsconfig.json | 8 +-- x-pack/plugins/apm/scripts/precommit.js | 9 +-- 4 files changed, 35 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/apm/scripts/optimize-tsconfig/optimize.js b/x-pack/plugins/apm/scripts/optimize-tsconfig/optimize.js index ae941c1d2de0c9..fed938119c4a64 100644 --- a/x-pack/plugins/apm/scripts/optimize-tsconfig/optimize.js +++ b/x-pack/plugins/apm/scripts/optimize-tsconfig/optimize.js @@ -18,51 +18,41 @@ const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); const unlink = promisify(fs.unlink); -const { - xpackRoot, - kibanaRoot, - tsconfigTpl, - filesToIgnore, -} = require('./paths'); +const { kibanaRoot, tsconfigTpl, filesToIgnore } = require('./paths'); const { unoptimizeTsConfig } = require('./unoptimize'); -function prepareParentTsConfigs() { - return Promise.all( - [ - path.resolve(xpackRoot, 'tsconfig.json'), - path.resolve(kibanaRoot, 'tsconfig.base.json'), - path.resolve(kibanaRoot, 'tsconfig.json'), - ].map(async (filename) => { - const config = json5.parse(await readFile(filename, 'utf-8')); - - await writeFile( - filename, - JSON.stringify( - { - ...omit(config, 'references'), - compilerOptions: { - ...config.compilerOptions, - incremental: false, - }, - include: [], - }, - null, - 2 - ), - { encoding: 'utf-8' } - ); - }) +async function prepareBaseTsConfig() { + const baseConfigFilename = path.resolve(kibanaRoot, 'tsconfig.base.json'); + const config = json5.parse(await readFile(baseConfigFilename, 'utf-8')); + + await writeFile( + baseConfigFilename, + JSON.stringify( + { + ...omit(config, 'references'), + compilerOptions: { + ...config.compilerOptions, + incremental: false, + }, + include: [], + }, + null, + 2 + ), + { encoding: 'utf-8' } ); } -async function addApmFilesToXpackTsConfig() { +async function addApmFilesToRootTsConfig() { const template = json5.parse(await readFile(tsconfigTpl, 'utf-8')); - const xpackTsConfig = path.join(xpackRoot, 'tsconfig.json'); - const config = json5.parse(await readFile(xpackTsConfig, 'utf-8')); + const rootTsConfigFilename = path.join(kibanaRoot, 'tsconfig.json'); + const rootTsConfig = json5.parse( + await readFile(rootTsConfigFilename, 'utf-8') + ); await writeFile( - xpackTsConfig, - JSON.stringify({ ...config, ...template }, null, 2), + rootTsConfigFilename, + JSON.stringify({ ...rootTsConfig, ...template, references: [] }, null, 2), { encoding: 'utf-8' } ); } @@ -80,9 +70,9 @@ async function deleteApmTsConfig() { async function optimizeTsConfig() { await unoptimizeTsConfig(); - await prepareParentTsConfigs(); + await prepareBaseTsConfig(); - await addApmFilesToXpackTsConfig(); + await addApmFilesToRootTsConfig(); await deleteApmTsConfig(); diff --git a/x-pack/plugins/apm/scripts/optimize-tsconfig/paths.js b/x-pack/plugins/apm/scripts/optimize-tsconfig/paths.js index d697c073fa17a0..dbc207c9e6d260 100644 --- a/x-pack/plugins/apm/scripts/optimize-tsconfig/paths.js +++ b/x-pack/plugins/apm/scripts/optimize-tsconfig/paths.js @@ -7,20 +7,16 @@ const path = require('path'); -const xpackRoot = path.resolve(__dirname, '../../../..'); -const kibanaRoot = path.resolve(xpackRoot, '..'); - +const kibanaRoot = path.resolve(__dirname, '../../../../..'); const tsconfigTpl = path.resolve(__dirname, './tsconfig.json'); const filesToIgnore = [ - path.resolve(xpackRoot, 'tsconfig.json'), path.resolve(kibanaRoot, 'tsconfig.json'), path.resolve(kibanaRoot, 'tsconfig.base.json'), path.resolve(kibanaRoot, 'x-pack/plugins/apm', 'tsconfig.json'), ]; module.exports = { - xpackRoot, kibanaRoot, tsconfigTpl, filesToIgnore, diff --git a/x-pack/plugins/apm/scripts/optimize-tsconfig/tsconfig.json b/x-pack/plugins/apm/scripts/optimize-tsconfig/tsconfig.json index 4a791ed18121ee..319eb533132314 100644 --- a/x-pack/plugins/apm/scripts/optimize-tsconfig/tsconfig.json +++ b/x-pack/plugins/apm/scripts/optimize-tsconfig/tsconfig.json @@ -1,13 +1,13 @@ { "include": [ - "./plugins/apm/**/*", - "./plugins/observability/**/*", + "./x-pack/plugins/apm/**/*", + "./x-pack/plugins/observability/**/*", "./typings/**/*" ], "exclude": [ "**/__fixtures__/**/*", - "./plugins/apm/e2e", - "./plugins/apm/ftr_e2e" + "./x-pack/plugins/apm/e2e", + "./x-pack/plugins/apm/ftr_e2e" ], "compilerOptions": { "noErrorTruncation": true diff --git a/x-pack/plugins/apm/scripts/precommit.js b/x-pack/plugins/apm/scripts/precommit.js index 42fe0734d9160d..c7102ce913f01c 100644 --- a/x-pack/plugins/apm/scripts/precommit.js +++ b/x-pack/plugins/apm/scripts/precommit.js @@ -20,16 +20,9 @@ const execaOpts = { cwd: root, stderr: 'pipe' }; const useOptimizedTsConfig = !!argv.optimizeTs; const tsconfig = useOptimizedTsConfig - ? resolve(root, 'x-pack/tsconfig.json') + ? resolve(root, 'tsconfig.json') : resolve(root, 'x-pack/plugins/apm/tsconfig.json'); -console.log( - resolve( - __dirname, - useOptimizedTsConfig ? './optimize-tsonfig.js' : './unoptimize-tsconfig.js' - ) -); - const tasks = new Listr( [ { From 71466b2dd0c76f523ce0688da4c754cd4f7a61c6 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 23 Mar 2021 06:27:41 -0600 Subject: [PATCH 15/93] [Maps] fix tooltips with timestamp appear as epoch (#95106) * [Maps] fix tooltips with timestamp appear as epoch * request dates as strict_date_optional_time for tooltips --- .../es_search_source/es_search_source.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index b3ecdbf51f3c3a..3b6a7202691b6f 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -71,7 +71,8 @@ export interface ScriptField { function getDocValueAndSourceFields( indexPattern: IndexPattern, - fieldNames: string[] + fieldNames: string[], + dateFormat: string ): { docValueFields: Array; sourceOnlyFields: string[]; @@ -94,7 +95,7 @@ function getDocValueAndSourceFields( field.type === 'date' ? { field: fieldName, - format: 'epoch_millis', + format: dateFormat, } : fieldName; docValueFields.push(docValueField); @@ -277,7 +278,8 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye const { docValueFields, sourceOnlyFields, scriptFields } = getDocValueAndSourceFields( indexPattern, - searchFilters.fieldNames + searchFilters.fieldNames, + 'epoch_millis' ); const topHits: { size: number; @@ -373,7 +375,8 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye const { docValueFields, sourceOnlyFields } = getDocValueAndSourceFields( indexPattern, - searchFilters.fieldNames + searchFilters.fieldNames, + 'epoch_millis' ); const initialSearchContext = { docvalue_fields: docValueFields }; // Request fields in docvalue_fields insted of _source @@ -498,7 +501,8 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye const { docValueFields } = getDocValueAndSourceFields( indexPattern, - this._getTooltipPropertyNames() + this._getTooltipPropertyNames(), + 'strict_date_optional_time' ); const initialSearchContext = { docvalue_fields: docValueFields }; // Request fields in docvalue_fields insted of _source @@ -704,7 +708,8 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye const { docValueFields, sourceOnlyFields } = getDocValueAndSourceFields( indexPattern, - searchFilters.fieldNames + searchFilters.fieldNames, + 'epoch_millis' ); const initialSearchContext = { docvalue_fields: docValueFields }; // Request fields in docvalue_fields insted of _source From 1e6a024c4d09d5a66dd2f242757e1827ac6ae6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Tue, 23 Mar 2021 13:31:02 +0100 Subject: [PATCH 16/93] [Monitoring] Bulk Uploader uses new ES client (#94908) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/kibana_monitoring/bulk_uploader.ts | 53 +++++++++---------- .../lib/send_bulk_payload.ts | 7 +-- .../plugins/monitoring/server/plugin.test.ts | 5 +- x-pack/plugins/monitoring/server/plugin.ts | 3 +- x-pack/plugins/monitoring/server/types.ts | 3 +- 5 files changed, 33 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.ts index f9e5cfdebdf261..ea5b36710abe04 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.ts @@ -5,29 +5,28 @@ * 2.0. */ -import { Observable, Subscription } from 'rxjs'; +import type { Observable, Subscription } from 'rxjs'; import { take } from 'rxjs/operators'; import moment from 'moment'; -import { - ElasticsearchServiceSetup, - ILegacyCustomClusterClient, +import type { + ElasticsearchClient, Logger, OpsMetrics, ServiceStatus, ServiceStatusLevel, - ServiceStatusLevels, -} from '../../../../../src/core/server'; +} from 'src/core/server'; +import { ServiceStatusLevels } from '../../../../../src/core/server'; import { KIBANA_STATS_TYPE_MONITORING, KIBANA_SETTINGS_TYPE } from '../../common/constants'; -import { sendBulkPayload, monitoringBulk } from './lib'; +import { sendBulkPayload } from './lib'; import { getKibanaSettings } from './collectors'; -import { MonitoringConfig } from '../config'; +import type { MonitoringConfig } from '../config'; +import type { IBulkUploader } from '../types'; export interface BulkUploaderOptions { log: Logger; config: MonitoringConfig; interval: number; - elasticsearch: ElasticsearchServiceSetup; statusGetter$: Observable; opsMetrics$: Observable; kibanaStats: KibanaStats; @@ -61,11 +60,11 @@ export interface KibanaStats { * @param {Object} server HapiJS server instance * @param {Object} xpackInfo server.plugins.xpack_main.info object */ -export class BulkUploader { +export class BulkUploader implements IBulkUploader { private readonly _log: Logger; - private readonly _cluster: ILegacyCustomClusterClient; private readonly kibanaStats: KibanaStats; - private readonly kibanaStatusGetter$: Subscription; + private readonly kibanaStatusGetter$: Observable; + private kibanaStatusSubscription?: Subscription; private readonly opsMetrics$: Observable; private kibanaStatus: ServiceStatusLevel | null; private _timer: NodeJS.Timer | null; @@ -75,7 +74,6 @@ export class BulkUploader { log, config, interval, - elasticsearch, statusGetter$, opsMetrics$, kibanaStats, @@ -91,16 +89,10 @@ export class BulkUploader { this._interval = interval; this._log = log; - this._cluster = elasticsearch.legacy.createClient('admin', { - plugins: [monitoringBulk], - }); - this.kibanaStats = kibanaStats; this.kibanaStatus = null; - this.kibanaStatusGetter$ = statusGetter$.subscribe((nextStatus) => { - this.kibanaStatus = nextStatus.level; - }); + this.kibanaStatusGetter$ = statusGetter$; } /* @@ -108,17 +100,21 @@ export class BulkUploader { * @param {usageCollection} usageCollection object to use for initial the fetch/upload and fetch/uploading on interval * @return undefined */ - public start() { + public start(esClient: ElasticsearchClient) { this._log.info('Starting monitoring stats collection'); + this.kibanaStatusSubscription = this.kibanaStatusGetter$.subscribe((nextStatus) => { + this.kibanaStatus = nextStatus.level; + }); + if (this._timer) { clearInterval(this._timer); } else { - this._fetchAndUpload(); // initial fetch + this._fetchAndUpload(esClient); // initial fetch } this._timer = setInterval(() => { - this._fetchAndUpload(); + this._fetchAndUpload(esClient); }, this._interval); } @@ -131,8 +127,7 @@ export class BulkUploader { if (this._timer) clearInterval(this._timer); this._timer = null; - this.kibanaStatusGetter$.unsubscribe(); - this._cluster.close(); + this.kibanaStatusSubscription?.unsubscribe(); const prefix = logPrefix ? logPrefix + ':' : ''; this._log.info(prefix + 'Monitoring stats collection is stopped'); @@ -168,7 +163,7 @@ export class BulkUploader { }; } - private async _fetchAndUpload() { + private async _fetchAndUpload(esClient: ElasticsearchClient) { const data = await Promise.all([ { type: KIBANA_STATS_TYPE_MONITORING, result: await this.getOpsMetrics() }, { type: KIBANA_SETTINGS_TYPE, result: await getKibanaSettings(this._log, this.config) }, @@ -178,7 +173,7 @@ export class BulkUploader { if (payload && payload.length > 0) { try { this._log.debug(`Uploading bulk stats payload to the local cluster`); - await this._onPayload(payload); + await this._onPayload(esClient, payload); this._log.debug(`Uploaded bulk stats payload to the local cluster`); } catch (err) { this._log.warn(err.stack); @@ -189,8 +184,8 @@ export class BulkUploader { } } - private async _onPayload(payload: object[]) { - return await sendBulkPayload(this._cluster, this._interval, payload); + private async _onPayload(esClient: ElasticsearchClient, payload: object[]) { + return await sendBulkPayload(esClient, this._interval, payload); } private getConvertedKibanaStatus() { diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/lib/send_bulk_payload.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/lib/send_bulk_payload.ts index 3b26049505c4eb..175f9b69ef5231 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/lib/send_bulk_payload.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/lib/send_bulk_payload.ts @@ -5,21 +5,22 @@ * 2.0. */ -import { ILegacyClusterClient } from 'src/core/server'; +import type { ElasticsearchClient } from 'src/core/server'; import { MONITORING_SYSTEM_API_VERSION, KIBANA_SYSTEM_ID } from '../../../common/constants'; /* * Send the Kibana usage data to the ES Monitoring Bulk endpoint */ export async function sendBulkPayload( - cluster: ILegacyClusterClient, + esClient: ElasticsearchClient, interval: number, payload: object[] ) { - return cluster.callAsInternalUser('monitoring.bulk', { + const { body } = await esClient.monitoring.bulk({ system_id: KIBANA_SYSTEM_ID, system_api_version: MONITORING_SYSTEM_API_VERSION, interval: interval + 'ms', body: payload, }); + return body; } diff --git a/x-pack/plugins/monitoring/server/plugin.test.ts b/x-pack/plugins/monitoring/server/plugin.test.ts index 5ce62e9587e8bd..1bdef23dd0c369 100644 --- a/x-pack/plugins/monitoring/server/plugin.test.ts +++ b/x-pack/plugins/monitoring/server/plugin.test.ts @@ -32,7 +32,6 @@ jest.mock('./config', () => ({ describe('Monitoring plugin', () => { const coreSetup = coreMock.createSetup(); coreSetup.http.getServerInfo.mockReturnValue({ port: 5601 } as any); - coreSetup.status.overall$.subscribe = jest.fn(); const setupPlugins = { usageCollection: { @@ -60,13 +59,13 @@ describe('Monitoring plugin', () => { afterEach(() => { (setupPlugins.alerting.registerType as jest.Mock).mockReset(); - (coreSetup.status.overall$.subscribe as jest.Mock).mockReset(); }); it('always create the bulk uploader', async () => { const plugin = new MonitoringPlugin(initializerContext as any); await plugin.setup(coreSetup, setupPlugins as any); - expect(coreSetup.status.overall$.subscribe).toHaveBeenCalled(); + // eslint-disable-next-line dot-notation + expect(plugin['bulkUploader']).not.toBeUndefined(); }); it('should register all alerts', async () => { diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index e7606ece4457a2..87134c765fbf96 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -138,7 +138,6 @@ export class MonitoringPlugin // Always create the bulk uploader const kibanaMonitoringLog = this.getLogger(KIBANA_MONITORING_LOGGING_TAG); const bulkUploader = (this.bulkUploader = initBulkUploader({ - elasticsearch: core.elasticsearch, config, log: kibanaMonitoringLog, opsMetrics$: core.metrics.getOpsMetrics$(), @@ -210,7 +209,7 @@ export class MonitoringPlugin const monitoringBulkEnabled = mainMonitoring && mainMonitoring.isAvailable && mainMonitoring.isEnabled; if (monitoringBulkEnabled) { - this.bulkUploader?.start(); + this.bulkUploader?.start(core.elasticsearch.client.asInternalUser); } else { this.bulkUploader?.handleNotEnabled(); } diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts index c0bfe32079cd24..de3d044ccabcba 100644 --- a/x-pack/plugins/monitoring/server/types.ts +++ b/x-pack/plugins/monitoring/server/types.ts @@ -12,6 +12,7 @@ import type { Logger, ILegacyCustomClusterClient, RequestHandlerContext, + ElasticsearchClient, } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { LicenseFeature, ILicense } from '../../licensing/server'; @@ -92,7 +93,7 @@ export interface LegacyShimDependencies { export interface IBulkUploader { getKibanaStats: () => any; stop: () => void; - start: () => void; + start: (esClient: ElasticsearchClient) => void; handleNotEnabled: () => void; } From 5e31f916144a8a37e734e88e8bfa68d51e1c8219 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Tue, 23 Mar 2021 07:57:56 -0500 Subject: [PATCH 17/93] Alerts and cases page components for observability plugin (#93365) Create components for the alerts and cases pages. This only contains basic empty components and stories for them. --- .../server/collectors/management/schema.ts | 4 + .../server/collectors/management/types.ts | 1 + src/plugins/telemetry/schema/oss_plugins.json | 6 + .../observability/common/ui_settings_keys.ts | 8 + .../public/application/index.tsx | 33 +- .../components/app/section/alerts/index.tsx | 7 +- .../components/shared/experimental_badge.tsx | 24 + .../public/pages/alerts/alerts.stories.tsx | 72 +++ .../public/pages/alerts/alerts_flyout.tsx | 132 +++++ .../public/pages/alerts/alerts_search_bar.tsx | 24 + .../public/pages/alerts/alerts_table.tsx | 123 +++++ .../public/pages/alerts/example_data.ts | 509 ++++++++++++++++++ .../public/pages/alerts/index.tsx | 99 ++++ .../public/pages/cases/cases.stories.tsx | 24 + .../public/pages/cases/index.tsx | 49 ++ x-pack/plugins/observability/public/plugin.ts | 52 +- .../observability/public/routes/index.tsx | 43 ++ x-pack/plugins/observability/server/plugin.ts | 3 + .../observability/server/ui_settings.ts | 32 ++ 19 files changed, 1219 insertions(+), 26 deletions(-) create mode 100644 x-pack/plugins/observability/common/ui_settings_keys.ts create mode 100644 x-pack/plugins/observability/public/components/shared/experimental_badge.tsx create mode 100644 x-pack/plugins/observability/public/pages/alerts/alerts.stories.tsx create mode 100644 x-pack/plugins/observability/public/pages/alerts/alerts_flyout.tsx create mode 100644 x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx create mode 100644 x-pack/plugins/observability/public/pages/alerts/alerts_table.tsx create mode 100644 x-pack/plugins/observability/public/pages/alerts/example_data.ts create mode 100644 x-pack/plugins/observability/public/pages/alerts/index.tsx create mode 100644 x-pack/plugins/observability/public/pages/cases/cases.stories.tsx create mode 100644 x-pack/plugins/observability/public/pages/cases/index.tsx create mode 100644 x-pack/plugins/observability/server/ui_settings.ts diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index b8100c048d512d..5959eb6aca4d41 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -408,4 +408,8 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:enableAlertingExperience': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, }; diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 15d78e3e79b0ea..fd63bb5bcaf435 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -30,6 +30,7 @@ export interface UsageStats { 'securitySolution:rulesTableRefresh': string; 'apm:enableSignificantTerms': boolean; 'apm:enableServiceOverview': boolean; + 'observability:enableAlertingExperience': boolean; 'visualize:enableLabs': boolean; 'visualization:heatmap:maxBuckets': number; 'visualization:colorMapping': string; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index fc9f5e2693fa49..451b3ffe91535e 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -8026,6 +8026,12 @@ "_meta": { "description": "Non-default value of setting." } + }, + "observability:enableAlertingExperience": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } } } }, diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts new file mode 100644 index 00000000000000..05abac80b67ce3 --- /dev/null +++ b/x-pack/plugins/observability/common/ui_settings_keys.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const enableAlertingExperience = 'observability:enableAlertingExperience'; diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 0185f9b4ef5bc0..9628a5cc61ba93 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useEffect } from 'react'; +import React, { MouseEvent, useEffect } from 'react'; import ReactDOM from 'react-dom'; import { Route, Router, Switch } from 'react-router-dom'; import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; @@ -21,12 +21,7 @@ import { useRouteParams } from '../hooks/use_route_params'; import { ObservabilityPluginSetupDeps } from '../plugin'; import { HasDataContextProvider } from '../context/has_data_context'; import { Breadcrumbs, routes } from '../routes'; - -const observabilityLabelBreadcrumb = { - text: i18n.translate('xpack.observability.observability.breadcrumb.', { - defaultMessage: 'Observability', - }), -}; +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; function getTitleFromBreadCrumbs(breadcrumbs: Breadcrumbs) { return breadcrumbs.map(({ text }) => text).reverse(); @@ -42,12 +37,24 @@ function App() { const Wrapper = () => { const { core } = usePluginContext(); - // eslint-disable-next-line react-hooks/exhaustive-deps - const breadcrumb = [observabilityLabelBreadcrumb, ...route.breadcrumb]; useEffect(() => { - core.chrome.setBreadcrumbs(breadcrumb); - core.chrome.docTitle.change(getTitleFromBreadCrumbs(breadcrumb)); - }, [core, breadcrumb]); + const href = core.http.basePath.prepend('/app/observability'); + const breadcrumbs = [ + { + href, + text: i18n.translate('xpack.observability.observability.breadcrumb.', { + defaultMessage: 'Observability', + }), + onClick: (event: MouseEvent) => { + event.preventDefault(); + core.application.navigateToUrl(href); + }, + }, + ...route.breadcrumb, + ]; + core.chrome.setBreadcrumbs(breadcrumbs); + core.chrome.docTitle.change(getTitleFromBreadCrumbs(breadcrumbs)); + }, [core]); const params = useRouteParams(path); return route.handler(params); @@ -76,7 +83,7 @@ export const renderApp = ( }); ReactDOM.render( - + diff --git a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx index 96dc1221630e8d..adc6a0208dc423 100644 --- a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx @@ -21,6 +21,7 @@ import React, { useState } from 'react'; import { EuiSelect } from '@elastic/eui'; import { uniqBy } from 'lodash'; import { Alert } from '../../../../../../alerting/common'; +import { enableAlertingExperience } from '../../../../../common/ui_settings_keys'; import { usePluginContext } from '../../../../hooks/use_plugin_context'; import { SectionContainer } from '..'; @@ -40,6 +41,10 @@ export function AlertsSection({ alerts }: Props) { const { core } = usePluginContext(); const [filter, setFilter] = useState(ALL_TYPES); + const href = core.uiSettings.get(enableAlertingExperience) + ? '/app/observability/alerts' + : '/app/management/insightsAndAlerting/triggersActions/alerts'; + const filterOptions = uniqBy(alerts, (alert) => alert.consumer).map(({ consumer }) => ({ value: consumer, text: consumer, @@ -51,7 +56,7 @@ export function AlertsSection({ alerts }: Props) { defaultMessage: 'Alerts', })} appLink={{ - href: '/app/management/insightsAndAlerting/triggersActions/alerts', + href, label: i18n.translate('xpack.observability.overview.alert.appLink', { defaultMessage: 'Manage alerts', }), diff --git a/x-pack/plugins/observability/public/components/shared/experimental_badge.tsx b/x-pack/plugins/observability/public/components/shared/experimental_badge.tsx new file mode 100644 index 00000000000000..a99187271806a5 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/experimental_badge.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiBetaBadge } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export function ExperimentalBadge() { + return ( + + ); +} diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts.stories.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts.stories.tsx new file mode 100644 index 00000000000000..4adff299c30b75 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/alerts.stories.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ComponentType } from 'react'; +import { IntlProvider } from 'react-intl'; +import { AlertsPage } from '.'; +import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { PluginContext, PluginContextValue } from '../../context/plugin_context'; +import { AlertsFlyout } from './alerts_flyout'; +import { AlertItem } from './alerts_table'; +import { eventLogPocData, wireframeData } from './example_data'; + +export default { + title: 'app/Alerts', + component: AlertsPage, + decorators: [ + (Story: ComponentType) => { + return ( + + {} }, + uiSettings: { + get: (setting: string) => { + if (setting === 'dateFormat') { + return ''; + } else { + return []; + } + }, + }, + }} + > + '' } }, + }, + } as unknown) as PluginContextValue + } + > + + + + + ); + }, + ], +}; + +export function Example() { + return ; +} + +export function EventLog() { + return ; +} + +export function EmptyState() { + return ; +} + +export function Flyout() { + return {}} />; +} diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_flyout.tsx new file mode 100644 index 00000000000000..0b63049ec1f72e --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_flyout.tsx @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiBadge, + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutProps, + EuiInMemoryTable, + EuiSpacer, + EuiTabbedContent, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { AlertItem } from './alerts_table'; + +type AlertsFlyoutProps = AlertItem & EuiFlyoutProps; + +export function AlertsFlyout(props: AlertsFlyoutProps) { + const { + actualValue, + affectedEntity, + expectedValue, + onClose, + reason, + severity, + severityLog, + status, + duration, + type, + } = props; + const timestamp = props['@timestamp']; + + const overviewListItems = [ + { + title: 'Status', + description: status || '-', + }, + { + title: 'Severity', + description: severity || '-', // TODO: badge and "(changed 2 min ago)" + }, + { + title: 'Affected entity', + description: affectedEntity || '-', // TODO: link to entity + }, + { + title: 'Triggered', + description: timestamp, // TODO: format date + }, + { + title: 'Duration', + description: duration || '-', // TODO: format duration + }, + { + title: 'Expected value', + description: expectedValue || '-', + }, + { + title: 'Actual value', + description: actualValue || '-', + }, + { + title: 'Type', + description: type || '-', + }, + ]; + + const tabs = [ + { + id: 'overview', + name: i18n.translate('xpack.observability.alerts.flyoutOverviewTabTitle', { + defaultMessage: 'Overview', + }), + content: ( + <> + + + + +

Severity log

+
+ ( + <> + {item.severity} {item.message} + + ), + }, + ]} + items={severityLog ?? []} + /> + + ), + }, + { + id: 'metadata', + name: i18n.translate('xpack.observability.alerts.flyoutMetadataTabTitle', { + defaultMessage: 'Metadata', + }), + disabled: true, + content: <>, + }, + ]; + + return ( + + + +

{reason}

+
+ +
+
+ ); +} diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx new file mode 100644 index 00000000000000..1afab90f2999e4 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { SearchBar, TimeHistory } from '../../../../../../src/plugins/data/public'; +import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; + +export function AlertsSearchBar() { + return ( + + ); +} diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_table.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_table.tsx new file mode 100644 index 00000000000000..057e3e74b84d8d --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_table.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiBasicTableProps, + DefaultItemAction, + EuiTableSelectionType, + EuiLink, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { AlertsFlyout } from './alerts_flyout'; + +/** + * The type of an item in the alert list. + * + * The fields here are the minimum to make this work at this time, but + * eventually this type should be derived from the schema of what is returned in + * the API response. + */ +export interface AlertItem { + '@timestamp': number; + reason: string; + severity: string; + // These are just made up so we can make example links + service?: { name?: string }; + pod?: string; + log?: boolean; + // Other fields used in the flyout + actualValue?: string; + affectedEntity?: string; + expectedValue?: string; + severityLog?: Array<{ '@timestamp': number; severity: string; message: string }>; + status?: string; + duration?: string; + type?: string; +} + +type AlertsTableProps = Omit< + EuiBasicTableProps, + 'columns' | 'isSelectable' | 'pagination' | 'selection' +>; + +export function AlertsTable(props: AlertsTableProps) { + const [flyoutAlert, setFlyoutAlert] = useState(undefined); + const handleFlyoutClose = () => setFlyoutAlert(undefined); + const { prepend } = usePluginContext().core.http.basePath; + + // This is a contrived implementation of the reason field that shows how + // you could link to certain types of resources based on what's contained + // in their alert data. + function reasonRenderer(text: string, item: AlertItem) { + const serviceName = item.service?.name; + const pod = item.pod; + const log = item.log; + + if (serviceName) { + return {text}; + } else if (pod) { + return {text}; + } else if (log) { + return {text}; + } else { + return <>{text}; + } + } + + const actions: Array> = [ + { + name: 'Alert details', + description: 'Alert details', + onClick: (item) => { + setFlyoutAlert(item); + }, + isPrimary: true, + }, + ]; + + const columns: Array> = [ + { + field: '@timestamp', + name: 'Triggered', + dataType: 'date', + }, + { + field: 'duration', + name: 'Duration', + }, + { + field: 'severity', + name: 'Severity', + }, + { + field: 'reason', + name: 'Reason', + dataType: 'string', + render: reasonRenderer, + }, + { + actions, + name: 'Actions', + }, + ]; + + return ( + <> + {flyoutAlert && } + + {...props} + isSelectable={true} + selection={{} as EuiTableSelectionType} + columns={columns} + pagination={{ pageIndex: 0, pageSize: 0, totalItemCount: 0 }} + /> + + ); +} diff --git a/x-pack/plugins/observability/public/pages/alerts/example_data.ts b/x-pack/plugins/observability/public/pages/alerts/example_data.ts new file mode 100644 index 00000000000000..584408a23d9bdb --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/example_data.ts @@ -0,0 +1,509 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Example data from Whimsical wireframes: https://whimsical.com/observability-alerting-user-journeys-8TFDcHRPMQDJgtLpJ7XuBj + */ +export const wireframeData = [ + { + '@timestamp': 1615392661000, + duration: '10 min 2 s', + severity: '-', + reason: 'Error count is greater than 100 (current value is 135) on shippingService', + service: { name: 'opbeans-go' }, + affectedEntity: 'opbeans-go service', + status: 'Active', + expectedValue: '< 100', + actualValue: '135', + severityLog: [ + { '@timestamp': 1615392661000, severity: 'critical', message: 'Load is 3.5' }, + { '@timestamp': 1615392600000, severity: 'warning', message: 'Load is 2.5' }, + { '@timestamp': 1615392552000, severity: 'critical', message: 'Load is 3.5' }, + ], + type: 'APM Error count', + }, + { + '@timestamp': 1615392600000, + duration: '11 min 1 s', + severity: '-', + reason: 'Latency is greater than 1500ms (current value is 1700ms) on frontend', + service: { name: 'opbeans-go' }, + severityLog: [], + }, + { + '@timestamp': 1615392552000, + duration: '10 min 2 s', + severity: 'critical', + reason: 'Latency anomaly score is 84 on checkoutService', + service: { name: 'opbeans-go' }, + severityLog: [], + }, + { + '@timestamp': 1615392391000, + duration: '10 min 2 s', + severity: '-', + reason: + 'CPU is greater than a threshold of 75% (current value is 83%) on gke-eden-3-prod-pool-2-395ef018-06xg', + pod: 'gke-dev-oblt-dev-oblt-pool-30f1ba48-skw', + severityLog: [], + }, + { + '@timestamp': 1615392363000, + duration: '10 min 2 s', + severity: '-', + reason: + "Log count with 'Log.level.error' and 'service.name; frontend' is greater than 75 (current value 122)", + log: true, + severityLog: [], + }, + { + '@timestamp': 1615392361000, + duration: '10 min 2 s', + severity: 'critical', + reason: 'Load is greater than 2 (current value is 3.5) on gke-eden-3-prod-pool-2-395ef018-06xg', + pod: 'gke-dev-oblt-dev-oblt-pool-30f1ba48-skw', + severityLog: [], + }, +]; + +/** + * Example data from this proof of concept: https://github.com/dgieselaar/kibana/tree/alerting-event-log-poc + */ +export const eventLogPocData = [ + { + '@timestamp': 1615395754597, + first_seen: 1615362488702, + severity: 'warning', + severity_value: 1241.4546, + reason: + 'Transaction duration for opbeans-java/request in production was above the threshold of 1.0 ms (1.2 ms)', + rule_id: 'cb1fc3e0-7fef-11eb-827d-d94e80a23d8d', + rule_name: 'Latency threshold | opbeans-java', + rule_type_id: 'apm.transaction_duration', + rule_type_name: 'Latency threshold', + alert_instance_title: ['opbeans-java/request:production'], + alert_instance_name: 'apm.transaction_duration_production', + unique: 1, + group_by_field: 'alert_instance.uuid', + group_by_value: '1b354805-4bf3-4626-b6be-5801d7d1e256', + influencers: [ + 'service.name:opbeans-java', + 'service.environment:production', + 'transaction.type:request', + ], + fields: { + 'processor.event': 'transaction', + 'service.name': 'opbeans-java', + 'service.environment': 'production', + 'transaction.type': 'request', + }, + timeseries: [ + { + x: 1615359600000, + y: 48805, + threshold: 1000, + }, + { + x: 1615370400000, + y: 3992.5, + threshold: 1000, + }, + { + x: 1615381200000, + y: 4296.7998046875, + threshold: 1000, + }, + { + x: 1615392000000, + y: 1633.8182373046875, + threshold: 1000, + }, + ], + recovered: false, + }, + { + '@timestamp': 1615326143423, + first_seen: 1615323802378, + severity: 'warning', + severity_value: 27, + reason: 'Error count for opbeans-node in production was above the threshold of 2 (27)', + rule_id: '335b38d0-80be-11eb-9fd1-d3725789930d', + rule_name: 'Error count threshold', + rule_type_id: 'apm.error_rate', + rule_type_name: 'Error count threshold', + alert_instance_title: ['opbeans-node:production'], + alert_instance_name: 'opbeans-node_production', + unique: 1, + group_by_field: 'alert_instance.uuid', + group_by_value: '19165a4f-296a-4045-9448-40c793d97d02', + influencers: ['service.name:opbeans-node', 'service.environment:production'], + fields: { + 'processor.event': 'error', + 'service.name': 'opbeans-node', + 'service.environment': 'production', + }, + timeseries: [ + { + x: 1615323780000, + y: 32, + threshold: 2, + }, + { + x: 1615324080000, + y: 34, + threshold: 2, + }, + { + x: 1615324380000, + y: 32, + threshold: 2, + }, + { + x: 1615324680000, + y: 34, + threshold: 2, + }, + { + x: 1615324980000, + y: 35, + threshold: 2, + }, + { + x: 1615325280000, + y: 31, + threshold: 2, + }, + { + x: 1615325580000, + y: 36, + threshold: 2, + }, + { + x: 1615325880000, + y: 35, + threshold: 2, + }, + ], + recovered: true, + }, + { + '@timestamp': 1615326143423, + first_seen: 1615325783256, + severity: 'warning', + severity_value: 27, + reason: 'Error count for opbeans-java in production was above the threshold of 2 (27)', + rule_id: '335b38d0-80be-11eb-9fd1-d3725789930d', + rule_name: 'Error count threshold', + rule_type_id: 'apm.error_rate', + rule_type_name: 'Error count threshold', + alert_instance_title: ['opbeans-java:production'], + alert_instance_name: 'opbeans-java_production', + unique: 1, + group_by_field: 'alert_instance.uuid', + group_by_value: '73075d90-e27a-4e20-9ba0-3512a16c2829', + influencers: ['service.name:opbeans-java', 'service.environment:production'], + fields: { + 'processor.event': 'error', + 'service.name': 'opbeans-java', + 'service.environment': 'production', + }, + timeseries: [ + { + x: 1615325760000, + y: 36, + threshold: 2, + }, + { + x: 1615325820000, + y: 26, + threshold: 2, + }, + { + x: 1615325880000, + y: 28, + threshold: 2, + }, + { + x: 1615325940000, + y: 35, + threshold: 2, + }, + { + x: 1615326000000, + y: 32, + threshold: 2, + }, + { + x: 1615326060000, + y: 23, + threshold: 2, + }, + { + x: 1615326120000, + y: 27, + threshold: 2, + }, + ], + recovered: true, + }, + { + '@timestamp': 1615326143423, + first_seen: 1615323802378, + severity: 'warning', + severity_value: 4759.9116, + reason: + 'Transaction duration for opbeans-java/request in production was above the threshold of 1.0 ms (4.8 ms)', + rule_id: 'cb1fc3e0-7fef-11eb-827d-d94e80a23d8d', + rule_name: 'Latency threshold | opbeans-java', + rule_type_id: 'apm.transaction_duration', + rule_type_name: 'Latency threshold', + alert_instance_title: ['opbeans-java/request:production'], + alert_instance_name: 'apm.transaction_duration_production', + unique: 1, + group_by_field: 'alert_instance.uuid', + group_by_value: 'ffa0437d-6656-4553-a1cd-c170fc6e2f81', + influencers: [ + 'service.name:opbeans-java', + 'service.environment:production', + 'transaction.type:request', + ], + fields: { + 'processor.event': 'transaction', + 'service.name': 'opbeans-java', + 'service.environment': 'production', + 'transaction.type': 'request', + }, + timeseries: [ + { + x: 1615323780000, + y: 13145.51171875, + threshold: 1000, + }, + { + x: 1615324080000, + y: 15995.15625, + threshold: 1000, + }, + { + x: 1615324380000, + y: 18974.59375, + threshold: 1000, + }, + { + x: 1615324680000, + y: 11604.87890625, + threshold: 1000, + }, + { + x: 1615324980000, + y: 17945.9609375, + threshold: 1000, + }, + { + x: 1615325280000, + y: 9933.22265625, + threshold: 1000, + }, + { + x: 1615325580000, + y: 10011.58984375, + threshold: 1000, + }, + { + x: 1615325880000, + y: 10953.1845703125, + threshold: 1000, + }, + ], + recovered: true, + }, + { + '@timestamp': 1615325663207, + first_seen: 1615324762861, + severity: 'warning', + severity_value: 27, + reason: 'Error count for opbeans-java in production was above the threshold of 2 (27)', + rule_id: '335b38d0-80be-11eb-9fd1-d3725789930d', + rule_name: 'Error count threshold', + rule_type_id: 'apm.error_rate', + rule_type_name: 'Error count threshold', + alert_instance_title: ['opbeans-java:production'], + alert_instance_name: 'opbeans-java_production', + unique: 1, + group_by_field: 'alert_instance.uuid', + group_by_value: 'bf5f9574-57c8-44ed-9a3c-512b446695cf', + influencers: ['service.name:opbeans-java', 'service.environment:production'], + fields: { + 'processor.event': 'error', + 'service.name': 'opbeans-java', + 'service.environment': 'production', + }, + timeseries: [ + { + x: 1615324740000, + y: 34, + threshold: 2, + }, + { + x: 1615325040000, + y: 35, + threshold: 2, + }, + { + x: 1615325340000, + y: 31, + threshold: 2, + }, + { + x: 1615325640000, + y: 27, + threshold: 2, + }, + ], + recovered: true, + }, + { + '@timestamp': 1615324642764, + first_seen: 1615324402620, + severity: 'warning', + severity_value: 32, + reason: 'Error count for opbeans-java in production was above the threshold of 2 (32)', + rule_id: '335b38d0-80be-11eb-9fd1-d3725789930d', + rule_name: 'Error count threshold', + rule_type_id: 'apm.error_rate', + rule_type_name: 'Error count threshold', + alert_instance_title: ['opbeans-java:production'], + alert_instance_name: 'opbeans-java_production', + unique: 1, + group_by_field: 'alert_instance.uuid', + group_by_value: '87768bef-67a3-4ddd-b95d-7ab8830b30ef', + influencers: ['service.name:opbeans-java', 'service.environment:production'], + fields: { + 'processor.event': 'error', + 'service.name': 'opbeans-java', + 'service.environment': 'production', + }, + timeseries: [ + { + x: 1615324402000, + y: 30, + threshold: 2, + }, + { + x: 1615324432000, + y: null, + threshold: null, + }, + { + x: 1615324462000, + y: 28, + threshold: 2, + }, + { + x: 1615324492000, + y: null, + threshold: null, + }, + { + x: 1615324522000, + y: 30, + threshold: 2, + }, + { + x: 1615324552000, + y: null, + threshold: null, + }, + { + x: 1615324582000, + y: 18, + threshold: 2, + }, + { + x: 1615324612000, + y: null, + threshold: null, + }, + { + x: 1615324642000, + y: 32, + threshold: 2, + }, + ], + recovered: true, + }, + { + '@timestamp': 1615324282583, + first_seen: 1615323802378, + severity: 'warning', + severity_value: 30, + reason: 'Error count for opbeans-java in production was above the threshold of 2 (30)', + rule_id: '335b38d0-80be-11eb-9fd1-d3725789930d', + rule_name: 'Error count threshold', + rule_type_id: 'apm.error_rate', + rule_type_name: 'Error count threshold', + alert_instance_title: ['opbeans-java:production'], + alert_instance_name: 'opbeans-java_production', + unique: 1, + group_by_field: 'alert_instance.uuid', + group_by_value: '31d087bd-51ae-419d-81c0-d0671eb97392', + influencers: ['service.name:opbeans-java', 'service.environment:production'], + fields: { + 'processor.event': 'error', + 'service.name': 'opbeans-java', + 'service.environment': 'production', + }, + timeseries: [ + { + x: 1615323780000, + y: 31, + threshold: 2, + }, + { + x: 1615323840000, + y: 30, + threshold: 2, + }, + { + x: 1615323900000, + y: 24, + threshold: 2, + }, + { + x: 1615323960000, + y: 32, + threshold: 2, + }, + { + x: 1615324020000, + y: 32, + threshold: 2, + }, + { + x: 1615324080000, + y: 30, + threshold: 2, + }, + { + x: 1615324140000, + y: 25, + threshold: 2, + }, + { + x: 1615324200000, + y: 34, + threshold: 2, + }, + { + x: 1615324260000, + y: 30, + threshold: 2, + }, + ], + recovered: true, + }, +]; diff --git a/x-pack/plugins/observability/public/pages/alerts/index.tsx b/x-pack/plugins/observability/public/pages/alerts/index.tsx new file mode 100644 index 00000000000000..b4cc600e59d563 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/index.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButton, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPage, + EuiPageHeader, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { ExperimentalBadge } from '../../components/shared/experimental_badge'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { RouteParams } from '../../routes'; +import { AlertsSearchBar } from './alerts_search_bar'; +import { AlertItem, AlertsTable } from './alerts_table'; +import { wireframeData } from './example_data'; + +interface AlertsPageProps { + items?: AlertItem[]; + routeParams: RouteParams<'/alerts'>; +} + +export function AlertsPage({ items }: AlertsPageProps) { + // For now, if we're not passed any items load the example wireframe data. + if (!items) { + items = wireframeData; + } + + const { core } = usePluginContext(); + const { prepend } = core.http.basePath; + + // In a future milestone we'll have a page dedicated to rule management in + // observability. For now link to the settings page. + const manageDetectionRulesHref = prepend( + '/app/management/insightsAndAlerting/triggersActions/alerts' + ); + + return ( + + + {i18n.translate('xpack.observability.alertsTitle', { defaultMessage: 'Alerts' })}{' '} + + + } + rightSideItems={[ + + {i18n.translate('xpack.observability.alerts.manageDetectionRulesButtonLabel', { + defaultMessage: 'Manage detection rules', + })} + , + ]} + > + + + +

+ {i18n.translate('xpack.observability.alertsDisclaimerText', { + defaultMessage: + 'This page shows an experimental alerting view. The data shown here will probably not be an accurate representation of alerts. A non-experimental list of alerts is available in the Alerts and Actions settings in Stack Management.', + })} +

+

+ + {i18n.translate('xpack.observability.alertsDisclaimerLinkText', { + defaultMessage: 'Alerts and Actions', + })} + +

+
+
+ + + + + + +
+
+
+ ); +} diff --git a/x-pack/plugins/observability/public/pages/cases/cases.stories.tsx b/x-pack/plugins/observability/public/pages/cases/cases.stories.tsx new file mode 100644 index 00000000000000..49df932766b335 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/cases/cases.stories.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ComponentType } from 'react'; +import { CasesPage } from '.'; +import { RouteParams } from '../../routes'; + +export default { + title: 'app/Cases', + component: CasesPage, + decorators: [ + (Story: ComponentType) => { + return ; + }, + ], +}; + +export function EmptyState() { + return } />; +} diff --git a/x-pack/plugins/observability/public/pages/cases/index.tsx b/x-pack/plugins/observability/public/pages/cases/index.tsx new file mode 100644 index 00000000000000..4af5de274dd65d --- /dev/null +++ b/x-pack/plugins/observability/public/pages/cases/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiPage, EuiPageHeader } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { ExperimentalBadge } from '../../components/shared/experimental_badge'; +import { RouteParams } from '../../routes'; + +interface CasesProps { + routeParams: RouteParams<'/cases'>; +} + +export function CasesPage(props: CasesProps) { + return ( + + + {i18n.translate('xpack.observability.casesTitle', { defaultMessage: 'Cases' })}{' '} + + + } + > + + + +

+ {i18n.translate('xpack.observability.casesDisclaimerText', { + defaultMessage: 'This is the future home of cases.', + })} +

+
+
+
+
+
+ ); +} diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index a30d2a7411c23b..81c174932914ba 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -38,24 +38,52 @@ export class Plugin implements PluginClass) => { + // Load application bundle + const { renderApp } = await import('./application'); + // Get start services + const [coreStart] = await core.getStartServices(); + + return renderApp(coreStart, plugins, params); + }; + const updater$ = this.appUpdater$; + core.application.register({ id: 'observability-overview', title: 'Overview', - order: 8000, - euiIconType: 'logoObservability', appRoute: '/app/observability', - updater$: this.appUpdater$, - category: DEFAULT_APP_CATEGORIES.observability, + order: 8000, + category, + euiIconType, + mount, + updater$, + }); - mount: async (params: AppMountParameters) => { - // Load application bundle - const { renderApp } = await import('./application'); - // Get start services - const [coreStart] = await core.getStartServices(); + if (core.uiSettings.get('observability:enableAlertingExperience')) { + core.application.register({ + id: 'observability-alerts', + title: 'Alerts', + appRoute: '/app/observability/alerts', + order: 8025, + category, + euiIconType, + mount, + updater$, + }); - return renderApp(coreStart, plugins, params); - }, - }); + core.application.register({ + id: 'observability-cases', + title: 'Cases', + appRoute: '/app/observability/cases', + order: 8050, + category, + euiIconType, + mount, + updater$, + }); + } if (plugins.home) { plugins.home.featureCatalogue.registerSolution({ diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index afaa87d486dce7..20817901dab82d 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -12,6 +12,8 @@ import { HomePage } from '../pages/home'; import { LandingPage } from '../pages/landing'; import { OverviewPage } from '../pages/overview'; import { jsonRt } from './json_rt'; +import { AlertsPage } from '../pages/alerts'; +import { CasesPage } from '../pages/cases'; export type RouteParams = DecodeParams; @@ -25,6 +27,7 @@ export interface Params { query?: t.HasProps; path?: t.HasProps; } + export const routes = { '/': { handler: () => { @@ -72,4 +75,44 @@ export const routes = { }, ], }, + '/cases': { + handler: (routeParams: any) => { + return ; + }, + params: { + query: t.partial({ + rangeFrom: t.string, + rangeTo: t.string, + refreshPaused: jsonRt.pipe(t.boolean), + refreshInterval: jsonRt.pipe(t.number), + }), + }, + breadcrumb: [ + { + text: i18n.translate('xpack.observability.cases.breadcrumb', { + defaultMessage: 'Cases', + }), + }, + ], + }, + '/alerts': { + handler: (routeParams: any) => { + return ; + }, + params: { + query: t.partial({ + rangeFrom: t.string, + rangeTo: t.string, + refreshPaused: jsonRt.pipe(t.boolean), + refreshInterval: jsonRt.pipe(t.number), + }), + }, + breadcrumb: [ + { + text: i18n.translate('xpack.observability.alerts.breadcrumb', { + defaultMessage: 'Alerts', + }), + }, + ], + }, }; diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index a5843d1c4ade14..cb9878f578885f 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -13,6 +13,7 @@ import { ScopedAnnotationsClientFactory, AnnotationsAPI, } from './lib/annotations/bootstrap_annotations'; +import { uiSettings } from './ui_settings'; type LazyScopedAnnotationsClientFactory = ( ...args: Parameters @@ -32,6 +33,8 @@ export class ObservabilityPlugin implements Plugin { let annotationsApiPromise: Promise | undefined; + core.uiSettings.register(uiSettings); + if (config.annotations.enabled) { annotationsApiPromise = bootstrapAnnotations({ core, diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts new file mode 100644 index 00000000000000..3123ce96114d79 --- /dev/null +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { UiSettingsParams } from '../../../../src/core/types'; +import { enableAlertingExperience } from '../common/ui_settings_keys'; + +/** + * uiSettings definitions for Observability. + */ +export const uiSettings: Record> = { + [enableAlertingExperience]: { + category: ['observability'], + name: i18n.translate('xpack.observability.enableAlertingExperienceExperimentName', { + defaultMessage: 'Observability alerting experience', + }), + value: false, + description: i18n.translate( + 'xpack.observability.enableAlertingExperienceExperimentDescription', + { + defaultMessage: + 'Enable the experimental alerting experience for Observability. Adds the Alerts and Cases pages.', + } + ), + schema: schema.boolean(), + }, +}; From 8961f8523e1fe421566b6de3dd1fb48aee932cf6 Mon Sep 17 00:00:00 2001 From: Daniil Date: Tue, 23 Mar 2021 16:31:50 +0300 Subject: [PATCH 18/93] [TSVB] Type public code. Step 2 - panel configs (#94403) * Remove request facade and update search strategies * Use typescript * Type files * Update structure * Update tests * Type annotations * Fix type for infra * Type editor_controller * Type vis_editor * Type vis_picker * Fix types * Type panel_config * Fix vis data type * Enhance types * Remove generics * Use constant * Update docs * Use empty object as default data * Convert yes_no component to typescript * Type color rules * Type panel configs * Type helpers * Type color rules * Type collection actions * Get rid of create_text_handler * Fix collection actions types * Revert get_request_params changes, do some code refactoring, type create_number_handler and get rid of detect_ie Co-authored-by: Daniil Suleiman Co-authored-by: Diana Derevyankina Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../vis_type_timeseries/common/vis_schema.ts | 15 +- .../application/components/aggs/aggs.tsx | 3 +- .../components/aggs/field_select.tsx | 2 +- .../components/aggs/percentile_hdr.tsx | 2 +- .../aggs/percentile_rank/percentile_rank.tsx | 5 +- .../application/components/color_picker.tsx | 10 +- ...lor_rules.test.js => color_rules.test.tsx} | 49 ++-- .../{color_rules.js => color_rules.tsx} | 201 ++++++++------- .../components/lib/collection_actions.js | 40 --- ...ons.test.js => collection_actions.test.ts} | 37 +-- .../components/lib/collection_actions.ts | 45 ++++ .../components/lib/create_number_handler.js | 19 -- ....test.js => create_number_handler.test.ts} | 16 +- .../components/lib/create_number_handler.ts | 18 ++ .../components/lib/create_text_handler.js | 20 -- ...er.test.js => create_text_handler.test.ts} | 16 +- .../components/lib/create_text_handler.ts | 18 ++ .../application/components/lib/detect_ie.js | 33 --- ...guage.js => get_default_query_language.ts} | 0 .../{gauge.test.js => gauge.test.tsx} | 15 +- .../panel_config/{gauge.js => gauge.tsx} | 115 ++++----- .../components/panel_config/index.ts | 23 +- .../{markdown.js => markdown.tsx} | 113 +++++---- .../panel_config/{metric.js => metric.tsx} | 56 ++-- .../{ => panel_config}/panel_config.tsx | 35 +-- .../panel_config/{table.js => table.tsx} | 90 ++++--- .../{timeseries.js => timeseries.tsx} | 240 +++++++++--------- .../panel_config/{top_n.js => top_n.tsx} | 80 +++--- .../components/panel_config/types.ts | 28 ++ .../{yes_no.test.js => yes_no.test.tsx} | 26 +- .../components/{yes_no.js => yes_no.tsx} | 47 ++-- 31 files changed, 710 insertions(+), 707 deletions(-) rename src/plugins/vis_type_timeseries/public/application/components/{color_rules.test.js => color_rules.test.tsx} (67%) rename src/plugins/vis_type_timeseries/public/application/components/{color_rules.js => color_rules.tsx} (54%) delete mode 100644 src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.js rename src/plugins/vis_type_timeseries/public/application/components/lib/{collection_actions.test.js => collection_actions.test.ts} (62%) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.ts delete mode 100644 src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.js rename src/plugins/vis_type_timeseries/public/application/components/lib/{create_number_handler.test.js => create_number_handler.test.ts} (70%) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.ts delete mode 100644 src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.js rename src/plugins/vis_type_timeseries/public/application/components/lib/{create_text_handler.test.js => create_text_handler.test.ts} (68%) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.ts delete mode 100644 src/plugins/vis_type_timeseries/public/application/components/lib/detect_ie.js rename src/plugins/vis_type_timeseries/public/application/components/lib/{get_default_query_language.js => get_default_query_language.ts} (100%) rename src/plugins/vis_type_timeseries/public/application/components/panel_config/{gauge.test.js => gauge.test.tsx} (75%) rename src/plugins/vis_type_timeseries/public/application/components/panel_config/{gauge.js => gauge.tsx} (80%) rename src/plugins/vis_type_timeseries/public/application/components/panel_config/{markdown.js => markdown.tsx} (79%) rename src/plugins/vis_type_timeseries/public/application/components/panel_config/{metric.js => metric.tsx} (82%) rename src/plugins/vis_type_timeseries/public/application/components/{ => panel_config}/panel_config.tsx (75%) rename src/plugins/vis_type_timeseries/public/application/components/panel_config/{table.js => table.tsx} (80%) rename src/plugins/vis_type_timeseries/public/application/components/panel_config/{timeseries.js => timeseries.tsx} (73%) rename src/plugins/vis_type_timeseries/public/application/components/panel_config/{top_n.js => top_n.tsx} (79%) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/panel_config/types.ts rename src/plugins/vis_type_timeseries/public/application/components/{yes_no.test.js => yes_no.test.tsx} (53%) rename src/plugins/vis_type_timeseries/public/application/components/{yes_no.js => yes_no.tsx} (69%) diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts index 9c7e8ab04fc1d4..a6bf70948bc1b4 100644 --- a/src/plugins/vis_type_timeseries/common/vis_schema.ts +++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts @@ -10,6 +10,7 @@ import { schema } from '@kbn/config-schema'; import { TypeOptions } from '@kbn/config-schema/target/types/types'; const stringOptionalNullable = schema.maybe(schema.nullable(schema.string())); +const stringOptional = schema.maybe(schema.string()); const stringRequired = schema.string(); @@ -205,23 +206,15 @@ export const panel = schema.object({ background_color_rules: schema.maybe(schema.arrayOf(backgroundColorRulesItems)), default_index_pattern: stringOptionalNullable, default_timefield: stringOptionalNullable, - drilldown_url: stringOptionalNullable, + drilldown_url: stringOptional, drop_last_bucket: numberIntegerOptional, - filter: schema.nullable( - schema.oneOf([ - stringOptionalNullable, - schema.object({ - language: stringOptionalNullable, - query: stringOptionalNullable, - }), - ]) - ), + filter: schema.maybe(queryObject), gauge_color_rules: schema.maybe(schema.arrayOf(gaugeColorRulesItems)), gauge_width: schema.nullable(schema.oneOf([stringOptionalNullable, numberOptional])), gauge_inner_color: stringOptionalNullable, gauge_inner_width: stringOrNumberOptionalNullable, gauge_style: stringOptionalNullable, - gauge_max: stringOrNumberOptionalNullable, + gauge_max: numberOptionalOrEmptyString, id: stringRequired, ignore_global_filters: numberOptional, ignore_global_filter: numberOptional, diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.tsx index 889ec1b76ef4b9..876c9c3726d7e3 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.tsx @@ -13,7 +13,6 @@ import { EuiDraggable, EuiDroppable } from '@elastic/eui'; import { Agg } from './agg'; // @ts-ignore import { seriesChangeHandler } from '../lib/series_change_handler'; -// @ts-ignore import { handleAdd, handleDelete } from '../lib/collection_actions'; import { newMetricAggFn } from '../lib/new_metric_agg_fn'; import { PanelSchema, SeriesItemsSchema } from '../../../../common/types'; @@ -23,10 +22,12 @@ import { IFieldType } from '../../../../../data/common/index_patterns/fields'; const DROPPABLE_ID = 'aggs_dnd'; export interface AggsProps { + name: keyof SeriesItemsSchema; panel: PanelSchema; model: SeriesItemsSchema; fields: IFieldType[]; uiRestrictions: TimeseriesUIRestrictions; + onChange(): void; } export class Aggs extends PureComponent { diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx index 7d9d81a8a966ed..4fc7b89e237651 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx @@ -21,7 +21,7 @@ interface FieldSelectProps { type: string; fields: Record; indexPattern: string; - value: string; + value?: string | null; onChange: (options: Array>) => void; disabled?: boolean; restrict?: string[]; diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_hdr.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_hdr.tsx index ad9cbc807a0713..5255378c1d0801 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_hdr.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_hdr.tsx @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; export interface PercentileHdrProps { value: number | undefined; - onChange: () => void; + onChange: (e: React.ChangeEvent) => void; } export const PercentileHdr = ({ value, onChange }: PercentileHdrProps) => ( diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank.tsx index c6563ea34a7444..b2f3383a761935 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank.tsx @@ -17,19 +17,16 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { AggSelect } from '../agg_select'; -// @ts-ignore import { FieldSelect } from '../field_select'; // @ts-ignore import { createChangeHandler } from '../../lib/create_change_handler'; -// @ts-ignore import { createSelectHandler } from '../../lib/create_select_handler'; -// @ts-ignore import { createNumberHandler } from '../../lib/create_number_handler'; import { AggRow } from '../agg_row'; import { PercentileRankValues } from './percentile_rank_values'; -import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../data/public'; import { MetricsItemsSchema, PanelSchema, SanitizedFieldType } from '../../../../../common/types'; import { DragHandleProps } from '../../../../types'; import { PercentileHdr } from '../percentile_hdr'; diff --git a/src/plugins/vis_type_timeseries/public/application/components/color_picker.tsx b/src/plugins/vis_type_timeseries/public/application/components/color_picker.tsx index 3d15ee35940402..280e4eda338998 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/color_picker.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/color_picker.tsx @@ -26,7 +26,7 @@ interface ColorProps { export interface ColorPickerProps { name: string; - value: string | null; + value?: string | null; disableTrash?: boolean; onChange: (props: ColorProps) => void; } @@ -39,16 +39,12 @@ export function ColorPicker({ name, value, disableTrash = false, onChange }: Col const handleColorChange: EuiColorPickerProps['onChange'] = (text: string, { rgba, hex }) => { setColor(text); - const part: ColorProps = {}; - part[name] = hex ? `rgba(${rgba.join(',')})` : ''; - onChange(part); + onChange({ [name]: hex ? `rgba(${rgba.join(',')})` : '' }); }; const handleClear = () => { setColor(''); - const part: ColorProps = {}; - part[name] = null; - onChange(part); + onChange({ [name]: null }); }; const label = value diff --git a/src/plugins/vis_type_timeseries/public/application/components/color_rules.test.js b/src/plugins/vis_type_timeseries/public/application/components/color_rules.test.tsx similarity index 67% rename from src/plugins/vis_type_timeseries/public/application/components/color_rules.test.js rename to src/plugins/vis_type_timeseries/public/application/components/color_rules.test.tsx index 05d8e0802b459e..9ea8898636cec4 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/color_rules.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/color_rules.test.tsx @@ -7,59 +7,58 @@ */ import React from 'react'; -import { collectionActions } from './lib/collection_actions'; -import { ColorRules } from './color_rules'; import { keys } from '@elastic/eui'; import { findTestSubject } from '@elastic/eui/lib/test'; import { mountWithIntl } from '@kbn/test/jest'; +import { collectionActions } from './lib/collection_actions'; +import { ColorRules, ColorRulesProps } from './color_rules'; + describe('src/legacy/core_plugins/metrics/public/components/color_rules.test.js', () => { - let defaultProps; - beforeAll(() => { - defaultProps = { - name: 'gauge_color_rules', - model: { - gauge_color_rules: [ - { - gauge: null, - value: 0, - id: 'unique value', - }, - ], - }, - onChange: jest.fn(), - }; - }); + const defaultProps = ({ + name: 'gauge_color_rules', + model: { + gauge_color_rules: [ + { + gauge: null, + value: 0, + id: 'unique value', + }, + ], + }, + onChange: jest.fn(), + } as unknown) as ColorRulesProps; + describe('ColorRules', () => { it('should render empty
node', () => { - const emptyProps = { + const emptyProps = ({ name: 'gauge_color_rules', model: {}, onChange: jest.fn(), - }; - const wrapper = mountWithIntl(); + } as unknown) as ColorRulesProps; + const wrapper = mountWithIntl(); const isNode = wrapper.find('div').children().exists(); expect(isNode).toBeFalsy(); }); it('should render non-empty
node', () => { - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl(); const isNode = wrapper.find('div.tvbColorPicker').exists(); expect(isNode).toBeTruthy(); }); it('should handle change of operator and value correctly', () => { collectionActions.handleChange = jest.fn(); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl(); const operatorInput = findTestSubject(wrapper, 'colorRuleOperator'); operatorInput.simulate('keyDown', { key: keys.ARROW_DOWN }); operatorInput.simulate('keyDown', { key: keys.ARROW_DOWN }); operatorInput.simulate('keyDown', { key: keys.ENTER }); - expect(collectionActions.handleChange.mock.calls[0][1].operator).toEqual('gt'); + expect((collectionActions.handleChange as jest.Mock).mock.calls[0][1].operator).toEqual('gt'); const numberInput = findTestSubject(wrapper, 'colorRuleValue'); numberInput.simulate('change', { target: { value: '123' } }); - expect(collectionActions.handleChange.mock.calls[1][1].value).toEqual(123); + expect((collectionActions.handleChange as jest.Mock).mock.calls[1][1].value).toEqual(123); }); }); }); diff --git a/src/plugins/vis_type_timeseries/public/application/components/color_rules.js b/src/plugins/vis_type_timeseries/public/application/components/color_rules.tsx similarity index 54% rename from src/plugins/vis_type_timeseries/public/application/components/color_rules.js rename to src/plugins/vis_type_timeseries/public/application/components/color_rules.tsx index 373d1f306bbd76..7aea5f934ee90b 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/color_rules.js +++ b/src/plugins/vis_type_timeseries/public/application/components/color_rules.tsx @@ -6,12 +6,7 @@ * Side Public License, v 1. */ -import PropTypes from 'prop-types'; import React, { Component, Fragment } from 'react'; -import _ from 'lodash'; -import { AddDeleteButtons } from './add_delete_buttons'; -import { collectionActions } from './lib/collection_actions'; -import { ColorPicker } from './color_picker'; import { htmlIdGenerator, EuiComboBox, @@ -19,76 +14,117 @@ import { EuiFormLabel, EuiFlexGroup, EuiFlexItem, + EuiComboBoxOptionOption, } from '@elastic/eui'; -import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -class ColorRulesUI extends Component { - constructor(props) { +import { AddDeleteButtons } from './add_delete_buttons'; +import { collectionActions } from './lib/collection_actions'; +import { ColorPicker, ColorPickerProps } from './color_picker'; +import { TimeseriesVisParams } from '../../types'; + +export interface ColorRulesProps { + name: keyof TimeseriesVisParams; + model: TimeseriesVisParams; + onChange: (partialModel: Partial) => void; + primaryName?: string; + primaryVarName?: string; + secondaryName?: string; + secondaryVarName?: string; + hideSecondary?: boolean; +} + +interface ColorRule { + value?: number; + id: string; + background_color?: string; + color?: string; + operator?: string; + text?: string; +} + +const defaultSecondaryName = i18n.translate( + 'visTypeTimeseries.colorRules.defaultSecondaryNameLabel', + { + defaultMessage: 'text', + } +); +const defaultPrimaryName = i18n.translate('visTypeTimeseries.colorRules.defaultPrimaryNameLabel', { + defaultMessage: 'background', +}); + +const operatorOptions = [ + { + label: i18n.translate('visTypeTimeseries.colorRules.greaterThanLabel', { + defaultMessage: '> greater than', + }), + value: 'gt', + }, + { + label: i18n.translate('visTypeTimeseries.colorRules.greaterThanOrEqualLabel', { + defaultMessage: '>= greater than or equal', + }), + value: 'gte', + }, + { + label: i18n.translate('visTypeTimeseries.colorRules.lessThanLabel', { + defaultMessage: '< less than', + }), + value: 'lt', + }, + { + label: i18n.translate('visTypeTimeseries.colorRules.lessThanOrEqualLabel', { + defaultMessage: '<= less than or equal', + }), + value: 'lte', + }, +]; + +export class ColorRules extends Component { + constructor(props: ColorRulesProps) { super(props); this.renderRow = this.renderRow.bind(this); } - handleChange(item, name, cast = String) { - return (e) => { - const handleChange = collectionActions.handleChange.bind(null, this.props); - const part = {}; - part[name] = cast(_.get(e, '[0].value', _.get(e, 'target.value'))); - if (part[name] === 'undefined') part[name] = undefined; - if (cast === Number && isNaN(part[name])) part[name] = undefined; - handleChange(_.assign({}, item, part)); + handleValueChange(item: ColorRule) { + return (e: React.ChangeEvent) => { + let value: number | undefined = Number(e.target.value); + if (isNaN(value)) value = undefined; + collectionActions.handleChange(this.props, { + ...item, + value, + }); }; } - renderRow(row, i, items) { + handleOperatorChange = (item: ColorRule) => { + return (options: Array>) => { + collectionActions.handleChange(this.props, { + ...item, + operator: options[0].value, + }); + }; + }; + + renderRow(row: ColorRule, i: number, items: ColorRule[]) { const defaults = { value: 0 }; const model = { ...defaults, ...row }; const handleAdd = () => collectionActions.handleAdd(this.props); - const handleDelete = collectionActions.handleDelete.bind(null, this.props, model); - const { intl } = this.props; - const operatorOptions = [ - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.colorRules.greaterThanLabel', - defaultMessage: '> greater than', - }), - value: 'gt', - }, - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.colorRules.greaterThanOrEqualLabel', - defaultMessage: '>= greater than or equal', - }), - value: 'gte', - }, - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.colorRules.lessThanLabel', - defaultMessage: '< less than', - }), - value: 'lt', - }, - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.colorRules.lessThanOrEqualLabel', - defaultMessage: '<= less than or equal', - }), - value: 'lte', - }, - ]; - const handleColorChange = (part) => { - const handleChange = collectionActions.handleChange.bind(null, this.props); - handleChange(_.assign({}, model, part)); + const handleDelete = () => collectionActions.handleDelete(this.props, model); + const handleColorChange: ColorPickerProps['onChange'] = (part) => { + collectionActions.handleChange(this.props, { ...model, ...part }); }; const htmlId = htmlIdGenerator(model.id); - const selectedOperatorOption = operatorOptions.find((option) => { - return model.operator === option.value; - }); + const selectedOperatorOption = operatorOptions.find( + (option) => model.operator === option.value + ); const labelStyle = { marginBottom: 0 }; let secondary; if (!this.props.hideSecondary) { + const secondaryVarName = this.props.secondaryVarName ?? 'color'; secondary = ( @@ -96,7 +132,7 @@ class ColorRulesUI extends Component { @@ -105,8 +141,8 @@ class ColorRulesUI extends Component { @@ -126,7 +162,7 @@ class ColorRulesUI extends Component { @@ -135,8 +171,12 @@ class ColorRulesUI extends Component { @@ -157,7 +197,7 @@ class ColorRulesUI extends Component { id={htmlId('ifMetricIs')} options={operatorOptions} selectedOptions={selectedOperatorOption ? [selectedOperatorOption] : []} - onChange={this.handleChange(model, 'operator')} + onChange={this.handleOperatorChange(model)} singleSelection={{ asPlainText: true }} data-test-subj="colorRuleOperator" fullWidth @@ -166,12 +206,11 @@ class ColorRulesUI extends Component { @@ -191,34 +230,6 @@ class ColorRulesUI extends Component { render() { const { model, name } = this.props; - if (!model[name]) return
; - const rows = model[name].map(this.renderRow); - return
{rows}
; + return !model[name] ?
:
{(model[name] as ColorRule[]).map(this.renderRow)}
; } } - -ColorRulesUI.defaultProps = { - name: 'color_rules', - primaryName: i18n.translate('visTypeTimeseries.colorRules.defaultPrimaryNameLabel', { - defaultMessage: 'background', - }), - primaryVarName: 'background_color', - secondaryName: i18n.translate('visTypeTimeseries.colorRules.defaultSecondaryNameLabel', { - defaultMessage: 'text', - }), - secondaryVarName: 'color', - hideSecondary: false, -}; - -ColorRulesUI.propTypes = { - name: PropTypes.string, - model: PropTypes.object, - onChange: PropTypes.func, - primaryName: PropTypes.string, - primaryVarName: PropTypes.string, - secondaryName: PropTypes.string, - secondaryVarName: PropTypes.string, - hideSecondary: PropTypes.bool, -}; - -export const ColorRules = injectI18n(ColorRulesUI); diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.js b/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.js deleted file mode 100644 index 7ab1ceae10d894..00000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import uuid from 'uuid'; - -const newFn = () => ({ id: uuid.v1() }); - -export function handleChange(props, doc) { - const { model, name } = props; - const collection = model[name] || []; - const part = {}; - part[name] = collection.map((row) => { - if (row.id === doc.id) return doc; - return row; - }); - props.onChange?.({ ...model, ...part }); -} - -export function handleDelete(props, doc) { - const { model, name } = props; - const collection = model[name] || []; - const part = {}; - part[name] = collection.filter((row) => row.id !== doc.id); - props.onChange?.({ ...model, ...part }); -} - -export function handleAdd(props, fn = newFn) { - const { model, name } = props; - const collection = model[name] || []; - const part = {}; - part[name] = collection.concat([fn()]); - props.onChange?.({ ...model, ...part }); -} - -export const collectionActions = { handleAdd, handleDelete, handleChange }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.test.ts similarity index 62% rename from src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.test.ts index 92283fcfdfa267..e8b2f03b71d1b2 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.test.ts @@ -6,50 +6,55 @@ * Side Public License, v 1. */ -import { handleChange, handleAdd, handleDelete } from './collection_actions'; +import { + handleChange, + handleAdd, + handleDelete, + CollectionActionsProps, +} from './collection_actions'; describe('collection actions', () => { test('handleChange() calls props.onChange() with updated collection', () => { const fn = jest.fn(); - const props = { - model: { test: [{ id: 1, title: 'foo' }] }, + const props = ({ + model: { test: [{ id: '1', title: 'foo' }] }, name: 'test', onChange: fn, - }; - handleChange.call(null, props, { id: 1, title: 'bar' }); + } as unknown) as CollectionActionsProps; + handleChange.call(null, props, { id: '1', type: 'bar' }); expect(fn.mock.calls.length).toEqual(1); expect(fn.mock.calls[0][0]).toEqual({ - test: [{ id: 1, title: 'bar' }], + test: [{ id: '1', type: 'bar' }], }); }); test('handleAdd() calls props.onChange() with update collection', () => { - const newItemFn = jest.fn(() => ({ id: 2, title: 'example' })); + const newItemFn = jest.fn(() => ({ id: '2', text: 'example' })); const fn = jest.fn(); - const props = { - model: { test: [{ id: 1, title: 'foo' }] }, + const props = ({ + model: { test: [{ id: '1', text: 'foo' }] }, name: 'test', onChange: fn, - }; + } as unknown) as CollectionActionsProps; handleAdd.call(null, props, newItemFn); expect(fn.mock.calls.length).toEqual(1); expect(newItemFn.mock.calls.length).toEqual(1); expect(fn.mock.calls[0][0]).toEqual({ test: [ - { id: 1, title: 'foo' }, - { id: 2, title: 'example' }, + { id: '1', text: 'foo' }, + { id: '2', text: 'example' }, ], }); }); test('handleDelete() calls props.onChange() with update collection', () => { const fn = jest.fn(); - const props = { - model: { test: [{ id: 1, title: 'foo' }] }, + const props = ({ + model: { test: [{ id: '1', title: 'foo' }] }, name: 'test', onChange: fn, - }; - handleDelete.call(null, props, { id: 1 }); + } as unknown) as CollectionActionsProps; + handleDelete.call(null, props, { id: '1' }); expect(fn.mock.calls.length).toEqual(1); expect(fn.mock.calls[0][0]).toEqual({ test: [], diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.ts new file mode 100644 index 00000000000000..8307969faa97e7 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import uuid from 'uuid'; + +interface DocType { + id: string; + type?: string; +} + +const newFn = (): DocType => ({ id: uuid.v1() }); + +export interface CollectionActionsProps { + model: T; + name: keyof T; + onChange: (partialModel: Partial) => void; +} + +export function handleChange(props: CollectionActionsProps, doc: P) { + const { model, name } = props; + const collection = ((model[name] as unknown) as DocType[]) || []; + const part = { [name]: collection.map((row) => (row.id === doc.id ? doc : row)) }; + props.onChange({ ...model, ...part }); +} + +export function handleDelete(props: CollectionActionsProps, doc: P) { + const { model, name } = props; + const collection = ((model[name] as unknown) as DocType[]) || []; + const part = { [name]: collection.filter((row) => row.id !== doc.id) }; + props.onChange?.({ ...model, ...part }); +} + +export function handleAdd(props: CollectionActionsProps, fn = newFn) { + const { model, name } = props; + const collection = ((model[name] as unknown) as DocType[]) || []; + const part = { [name]: collection.concat([fn()]) }; + props.onChange?.({ ...model, ...part }); +} + +export const collectionActions = { handleAdd, handleDelete, handleChange }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.js deleted file mode 100644 index e0088e91c4527f..00000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import { detectIE } from './detect_ie'; - -export const createNumberHandler = (handleChange) => { - return (name, defaultValue) => (e) => { - if (!detectIE() || e.keyCode === 13) e.preventDefault(); - - const value = Number(_.get(e, 'target.value', defaultValue)); - return handleChange?.({ [name]: value }); - }; -}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.test.ts similarity index 70% rename from src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.test.ts index 078812a1e5b0f2..10463be82901a7 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.test.ts @@ -9,23 +9,25 @@ import { createNumberHandler } from './create_number_handler'; describe('createNumberHandler()', () => { - let handleChange; - let changeHandler; - let event; + let handleChange: jest.Mock; + let changeHandler: ReturnType; + let event: React.ChangeEvent; beforeEach(() => { handleChange = jest.fn(); changeHandler = createNumberHandler(handleChange); - event = { preventDefault: jest.fn(), target: { value: '1' } }; - const fn = changeHandler('test'); + event = ({ + preventDefault: jest.fn(), + target: { value: '1' }, + } as unknown) as React.ChangeEvent; + const fn = changeHandler('unit'); fn(event); }); test('calls handleChange() function with partial', () => { - expect(event.preventDefault.mock.calls.length).toEqual(1); expect(handleChange.mock.calls.length).toEqual(1); expect(handleChange.mock.calls[0][0]).toEqual({ - test: 1, + unit: 1, }); }); }); diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.ts new file mode 100644 index 00000000000000..a86247c3080c47 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { MetricsItemsSchema } from '../../../../common/types'; +import { TimeseriesVisParams } from '../../../types'; + +export const createNumberHandler = ( + handleChange: (partialModel: Partial) => void +) => { + return (name: keyof MetricsItemsSchema, defaultValue?: string) => ( + e: React.ChangeEvent + ) => handleChange?.({ [name]: Number(e.target.value ?? defaultValue) }); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.js deleted file mode 100644 index f2e6e50c49b0d4..00000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import { detectIE } from './detect_ie'; - -export const createTextHandler = (handleChange) => { - return (name, defaultValue) => (e) => { - // IE preventDefault breaks input, but we still need top prevent enter from being pressed - if (!detectIE() || e.keyCode === 13) e.preventDefault(); - - const value = _.get(e, 'target.value', defaultValue); - return handleChange?.({ [name]: value }); - }; -}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.test.ts similarity index 68% rename from src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.test.ts index 0510e7978a655c..367e09c5968e9b 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.test.ts @@ -9,23 +9,25 @@ import { createTextHandler } from './create_text_handler'; describe('createTextHandler()', () => { - let handleChange; - let changeHandler; - let event; + let handleChange: jest.Mock; + let changeHandler: ReturnType; + let event: React.ChangeEvent; beforeEach(() => { handleChange = jest.fn(); changeHandler = createTextHandler(handleChange); - event = { preventDefault: jest.fn(), target: { value: 'foo' } }; - const fn = changeHandler('test'); + event = ({ + preventDefault: jest.fn(), + target: { value: 'foo' }, + } as unknown) as React.ChangeEvent; + const fn = changeHandler('axis_scale'); fn(event); }); test('calls handleChange() function with partial', () => { - expect(event.preventDefault.mock.calls.length).toEqual(1); expect(handleChange.mock.calls.length).toEqual(1); expect(handleChange.mock.calls[0][0]).toEqual({ - test: 'foo', + axis_scale: 'foo', }); }); }); diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.ts new file mode 100644 index 00000000000000..b5b6255d614d21 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { TimeseriesVisParams } from '../../../types'; + +// TODO: replace with explicit callback in each component +export const createTextHandler = ( + handleChange: (partialModel: Partial) => void +) => { + return (name: keyof TimeseriesVisParams, defaultValue?: string) => ( + e: React.ChangeEvent + ) => handleChange({ [name]: e.target.value ?? defaultValue }); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/detect_ie.js b/src/plugins/vis_type_timeseries/public/application/components/lib/detect_ie.js deleted file mode 100644 index b30ead75ba0ca1..00000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/detect_ie.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function detectIE() { - const ua = window.navigator.userAgent; - - const msie = ua.indexOf('MSIE '); - if (msie > 0) { - // IE 10 or older => return version number - return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); - } - - const trident = ua.indexOf('Trident/'); - if (trident > 0) { - // IE 11 => return version number - const rv = ua.indexOf('rv:'); - return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); - } - - const edge = ua.indexOf('Edge/'); - if (edge > 0) { - // Edge (IE 12+) => return version number - return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); - } - - // other browser - return false; -} diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.ts similarity index 100% rename from src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.ts diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.test.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.test.tsx similarity index 75% rename from src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.test.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.test.tsx index 714fdfb4be3af6..8b4f5d9134162a 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.test.tsx @@ -7,34 +7,35 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallow } from 'enzyme'; jest.mock('../lib/get_default_query_language', () => ({ getDefaultQueryLanguage: () => 'kuery', })); import { GaugePanelConfig } from './gauge'; +import { PanelConfigProps } from './types'; describe('GaugePanelConfig', () => { it('call switch tab onChange={handleChange}', () => { - const props = { + const props = ({ fields: {}, model: {}, onChange: jest.fn(), - }; - const wrapper = shallowWithIntl(); + } as unknown) as PanelConfigProps; + const wrapper = shallow(); wrapper.find('EuiTab').first().simulate('onClick'); expect(props.onChange).toBeCalled(); }); it('call onChange={handleChange}', () => { - const props = { + const props = ({ fields: {}, model: {}, onChange: jest.fn(), - }; - const wrapper = shallowWithIntl(); + } as unknown) as PanelConfigProps; + const wrapper = shallow(); wrapper.simulate('onClick'); expect(props.onChange).toBeCalled(); diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx similarity index 80% rename from src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx index ef986a2f2111bb..e302bbb9adb0b7 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.js +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx @@ -6,16 +6,8 @@ * Side Public License, v 1. */ -import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { SeriesEditor } from '../series_editor'; -import { IndexPattern } from '../index_pattern'; -import { createSelectHandler } from '../lib/create_select_handler'; -import { createTextHandler } from '../lib/create_text_handler'; -import { ColorRules } from '../color_rules'; -import { ColorPicker } from '../color_picker'; import uuid from 'uuid'; -import { YesNo } from '../yes_no'; import { htmlIdGenerator, EuiComboBox, @@ -31,26 +23,40 @@ import { EuiTitle, EuiHorizontalRule, } from '@elastic/eui'; -import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import type { Writable } from '@kbn/utility-types'; + +// @ts-ignore +import { SeriesEditor } from '../series_editor'; +// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts +import { IndexPattern } from '../index_pattern'; +import { createSelectHandler } from '../lib/create_select_handler'; +import { ColorRules } from '../color_rules'; +import { ColorPicker } from '../color_picker'; +// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging import { QueryBarWrapper } from '../query_bar_wrapper'; import { getDefaultQueryLanguage } from '../lib/get_default_query_language'; +import { YesNo } from '../yes_no'; import { limitOfSeries } from '../../../../common/ui_restrictions'; import { PANEL_TYPES } from '../../../../common/panel_types'; +import { TimeseriesVisParams } from '../../../types'; +import { PanelConfigProps, PANEL_CONFIG_TABS } from './types'; -class GaugePanelConfigUi extends Component { - constructor(props) { +export class GaugePanelConfig extends Component< + PanelConfigProps, + { selectedTab: PANEL_CONFIG_TABS } +> { + constructor(props: PanelConfigProps) { super(props); - this.state = { selectedTab: 'data' }; + this.state = { selectedTab: PANEL_CONFIG_TABS.DATA }; } UNSAFE_componentWillMount() { const { model } = this.props; - const parts = {}; - if ( - !model.gauge_color_rules || - (model.gauge_color_rules && model.gauge_color_rules.length === 0) - ) { + const parts: Writable> = {}; + if (!model.gauge_color_rules || !model.gauge_color_rules.length) { parts.gauge_color_rules = [{ id: uuid.v1() }]; } if (model.gauge_width == null) parts.gauge_width = 10; @@ -59,14 +65,17 @@ class GaugePanelConfigUi extends Component { this.props.onChange(parts); } - switchTab(selectedTab) { + switchTab(selectedTab: PANEL_CONFIG_TABS) { this.setState({ selectedTab }); } + handleTextChange = (name: keyof TimeseriesVisParams) => ( + e: React.ChangeEvent + ) => this.props.onChange({ [name]: e.target.value }); + render() { const { selectedTab } = this.state; - const { intl } = this.props; - const defaults = { + const defaults: Partial = { gauge_max: '', filter: { query: '', language: getDefaultQueryLanguage() }, gauge_style: 'circle', @@ -75,41 +84,34 @@ class GaugePanelConfigUi extends Component { }; const model = { ...defaults, ...this.props.model }; const handleSelectChange = createSelectHandler(this.props.onChange); - const handleTextChange = createTextHandler(this.props.onChange); const styleOptions = [ { - label: intl.formatMessage({ - id: 'visTypeTimeseries.gauge.styleOptions.circleLabel', + label: i18n.translate('visTypeTimeseries.gauge.styleOptions.circleLabel', { defaultMessage: 'Circle', }), value: 'circle', }, { - label: intl.formatMessage({ - id: 'visTypeTimeseries.gauge.styleOptions.halfCircleLabel', + label: i18n.translate('visTypeTimeseries.gauge.styleOptions.halfCircleLabel', { defaultMessage: 'Half Circle', }), value: 'half', }, ]; const htmlId = htmlIdGenerator(); - const selectedGaugeStyleOption = styleOptions.find((option) => { - return model.gauge_style === option.value; - }); - let view; - if (selectedTab === 'data') { - view = ( + const selectedGaugeStyleOption = styleOptions.find( + (option) => model.gauge_style === option.value + ); + const view = + selectedTab === PANEL_CONFIG_TABS.DATA ? ( - ); - } else { - view = ( + ) : (
@@ -144,10 +146,12 @@ class GaugePanelConfigUi extends Component { > this.props.onChange({ filter })} + onChange={(filter: PanelConfigProps['model']['filter']) => + this.props.onChange({ filter }) + } indexPatterns={[model.index_pattern || model.default_index_pattern]} /> @@ -193,15 +197,8 @@ class GaugePanelConfigUi extends Component { /> } > - {/* - EUITODO: The following input couldn't be converted to EUI because of type mis-match. - It accepts a null value, but is passed a empty string. - */} - @@ -236,7 +233,7 @@ class GaugePanelConfigUi extends Component { } > @@ -252,7 +249,7 @@ class GaugePanelConfigUi extends Component { } > @@ -317,17 +314,23 @@ class GaugePanelConfigUi extends Component {
); - } + return ( <> - this.switchTab('data')}> + this.switchTab(PANEL_CONFIG_TABS.DATA)} + > - this.switchTab('options')}> + this.switchTab(PANEL_CONFIG_TABS.OPTIONS)} + > { + constructor(props: PanelConfigProps) { super(props); - this.state = { selectedTab: 'markdown' }; + this.state = { selectedTab: PANEL_CONFIG_TABS.MARKDOWN }; this.handleCSSChange = this.handleCSSChange.bind(this); } - switchTab(selectedTab) { + switchTab(selectedTab: PANEL_CONFIG_TABS) { this.setState({ selectedTab }); } - handleCSSChange(value) { + handleCSSChange(value: string) { const { model } = this.props; - const lessSrc = `#markdown-${model.id} { - ${value} -}`; - lessC.render(lessSrc, { compress: true, javascriptEnabled: false }, (e, output) => { - const parts = { markdown_less: value }; - if (output) { - parts.markdown_css = output.css; + const lessSrc = `#markdown-${model.id} {${value}}`; + lessC.render( + lessSrc, + { compress: true, javascriptEnabled: false }, + (e: unknown, output: any) => { + const parts: Writable> = { + markdown_less: value, + }; + if (output) { + parts.markdown_css = output.css; + } + this.props.onChange(parts); } - this.props.onChange(parts); - }); + ); } render() { @@ -67,28 +84,23 @@ class MarkdownPanelConfigUi extends Component { const model = { ...defaults, ...this.props.model }; const { selectedTab } = this.state; const handleSelectChange = createSelectHandler(this.props.onChange); - const { intl } = this.props; - const htmlId = htmlIdGenerator(); const alignOptions = [ { - label: intl.formatMessage({ - id: 'visTypeTimeseries.markdown.alignOptions.topLabel', + label: i18n.translate('visTypeTimeseries.markdown.alignOptions.topLabel', { defaultMessage: 'Top', }), value: 'top', }, { - label: intl.formatMessage({ - id: 'visTypeTimeseries.markdown.alignOptions.middleLabel', + label: i18n.translate('visTypeTimeseries.markdown.alignOptions.middleLabel', { defaultMessage: 'Middle', }), value: 'middle', }, { - label: intl.formatMessage({ - id: 'visTypeTimeseries.markdown.alignOptions.bottomLabel', + label: i18n.translate('visTypeTimeseries.markdown.alignOptions.bottomLabel', { defaultMessage: 'Bottom', }), value: 'bottom', @@ -98,19 +110,18 @@ class MarkdownPanelConfigUi extends Component { return model.markdown_vertical_align === option.value; }); let view; - if (selectedTab === 'markdown') { + if (selectedTab === PANEL_CONFIG_TABS.MARKDOWN) { view = ( {(visData) => } ); - } else if (selectedTab === 'data') { + } else if (selectedTab === PANEL_CONFIG_TABS.DATA) { view = ( ); @@ -150,12 +161,12 @@ class MarkdownPanelConfigUi extends Component { > this.props.onChange({ filter })} + onChange={(filter: PanelConfigProps['model']['filter']) => + this.props.onChange({ filter }) + } indexPatterns={[model.index_pattern || model.default_index_pattern]} /> @@ -275,7 +286,7 @@ class MarkdownPanelConfigUi extends Component { width="100%" name={`ace-css-${model.id}`} setOptions={{ fontSize: '14px' }} - value={model.markdown_less} + value={model.markdown_less ?? ''} onChange={this.handleCSSChange} /> @@ -286,16 +297,16 @@ class MarkdownPanelConfigUi extends Component { <> this.switchTab('markdown')} + isSelected={selectedTab === PANEL_CONFIG_TABS.MARKDOWN} + onClick={() => this.switchTab(PANEL_CONFIG_TABS.MARKDOWN)} data-test-subj="markdown-subtab" > Markdown this.switchTab('data')} + isSelected={selectedTab === PANEL_CONFIG_TABS.DATA} + onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)} > this.switchTab('options')} + isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS} + onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)} data-test-subj="options-subtab" > { + constructor(props: PanelConfigProps) { super(props); - this.state = { selectedTab: 'data' }; + this.state = { selectedTab: PANEL_CONFIG_TABS.DATA }; } UNSAFE_componentWillMount() { @@ -51,7 +58,7 @@ export class MetricPanelConfig extends Component { } } - switchTab(selectedTab) { + switchTab(selectedTab: PANEL_CONFIG_TABS) { this.setState({ selectedTab }); } @@ -60,20 +67,16 @@ export class MetricPanelConfig extends Component { const defaults = { filter: { query: '', language: getDefaultQueryLanguage() } }; const model = { ...defaults, ...this.props.model }; const htmlId = htmlIdGenerator(); - let view; - if (selectedTab === 'data') { - view = ( + const view = + selectedTab === PANEL_CONFIG_TABS.DATA ? ( - ); - } else { - view = ( + ) : (
@@ -111,7 +114,9 @@ export class MetricPanelConfig extends Component { language: model.filter.language || getDefaultQueryLanguage(), query: model.filter.query || '', }} - onChange={(filter) => this.props.onChange({ filter })} + onChange={(filter: PanelConfigProps['model']['filter']) => + this.props.onChange({ filter }) + } indexPatterns={[model.index_pattern || model.default_index_pattern]} /> @@ -154,19 +159,22 @@ export class MetricPanelConfig extends Component {
); - } + return ( <> - this.switchTab('data')}> + this.switchTab(PANEL_CONFIG_TABS.DATA)} + > this.switchTab('options')} + isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS} + onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)} data-test-subj="metricEditorPanelOptionsBtn" > ; - getConfig: IUiSettingsClient['get']; - onChange: (partialModel: Partial) => void; -} - const checkModelValidity = (validationResults: FormValidationResults) => Object.values(validationResults).every((isValid) => isValid); diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx similarity index 80% rename from src/plugins/vis_type_timeseries/public/application/components/panel_config/table.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx index 7a8b919c25db89..20e07be4e3fa48 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.js +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx @@ -7,14 +7,8 @@ */ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { FieldSelect } from '../aggs/field_select'; -import { SeriesEditor } from '../series_editor'; -import { IndexPattern } from '../index_pattern'; -import { createTextHandler } from '../lib/create_text_handler'; import { get } from 'lodash'; import uuid from 'uuid'; -import { YesNo } from '../yes_no'; import { htmlIdGenerator, EuiTabs, @@ -30,36 +24,49 @@ import { EuiHorizontalRule, EuiCode, EuiText, + EuiComboBoxOptionOption, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; + +import { FieldSelect } from '../aggs/field_select'; +// @ts-expect-error not typed yet +import { SeriesEditor } from '../series_editor'; +// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts +import { IndexPattern } from '../index_pattern'; +import { YesNo } from '../yes_no'; +// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging import { QueryBarWrapper } from '../query_bar_wrapper'; import { getDefaultQueryLanguage } from '../lib/get_default_query_language'; import { VisDataContext } from '../../contexts/vis_data_context'; import { BUCKET_TYPES } from '../../../../common/metric_types'; -export class TablePanelConfig extends Component { +import { PanelConfigProps, PANEL_CONFIG_TABS } from './types'; +import { TimeseriesVisParams } from '../../../types'; + +export class TablePanelConfig extends Component< + PanelConfigProps, + { selectedTab: PANEL_CONFIG_TABS } +> { static contextType = VisDataContext; - constructor(props) { + constructor(props: PanelConfigProps) { super(props); - this.state = { selectedTab: 'data' }; + this.state = { selectedTab: PANEL_CONFIG_TABS.DATA }; } UNSAFE_componentWillMount() { const { model } = this.props; - const parts = {}; - if (!model.bar_color_rules || (model.bar_color_rules && model.bar_color_rules.length === 0)) { - parts.bar_color_rules = [{ id: uuid.v1() }]; + if (!model.bar_color_rules || !model.bar_color_rules.length) { + this.props.onChange({ bar_color_rules: [{ id: uuid.v1() }] }); } - this.props.onChange(parts); } - switchTab(selectedTab) { + switchTab(selectedTab: PANEL_CONFIG_TABS) { this.setState({ selectedTab }); } - handlePivotChange = (selectedOption) => { + handlePivotChange = (selectedOption: Array>) => { const { fields, model } = this.props; const pivotId = get(selectedOption, '[0].value', null); - const field = fields[model.index_pattern].find((field) => field.name === pivotId); + const field = fields[model.index_pattern].find((f) => f.name === pivotId); const pivotType = get(field, 'type', model.pivot_type); this.props.onChange({ @@ -68,6 +75,10 @@ export class TablePanelConfig extends Component { }); }; + handleTextChange = (name: keyof TimeseriesVisParams) => ( + e: React.ChangeEvent + ) => this.props.onChange({ [name]: e.target.value }); + render() { const { selectedTab } = this.state; const defaults = { @@ -78,11 +89,9 @@ export class TablePanelConfig extends Component { pivot_type: '', }; const model = { ...defaults, ...this.props.model }; - const handleTextChange = createTextHandler(this.props.onChange); const htmlId = htmlIdGenerator(); - let view; - if (selectedTab === 'data') { - view = ( + const view = + selectedTab === PANEL_CONFIG_TABS.DATA ? (
@@ -114,7 +123,6 @@ export class TablePanelConfig extends Component { onChange={this.handlePivotChange} uiRestrictions={this.context.uiRestrictions} type={BUCKET_TYPES.TERMS} - fullWidth /> @@ -131,8 +139,8 @@ export class TablePanelConfig extends Component { > @@ -154,8 +162,8 @@ export class TablePanelConfig extends Component { @@ -166,13 +174,10 @@ export class TablePanelConfig extends Component {
- ); - } else { - view = ( + ) : (
@@ -203,8 +208,8 @@ export class TablePanelConfig extends Component { } > @@ -237,7 +242,9 @@ export class TablePanelConfig extends Component { : getDefaultQueryLanguage(), query: model.filter.query || '', }} - onChange={(filter) => this.props.onChange({ filter })} + onChange={(filter: PanelConfigProps['model']['filter']) => + this.props.onChange({ filter }) + } indexPatterns={[model.index_pattern || model.default_index_pattern]} /> @@ -251,7 +258,6 @@ export class TablePanelConfig extends Component {
); - } + return ( <> - this.switchTab('data')}> + this.switchTab(PANEL_CONFIG_TABS.DATA)} + > - this.switchTab('options')}> + this.switchTab(PANEL_CONFIG_TABS.OPTIONS)} + > { + constructor(props: PanelConfigProps) { super(props); - this.state = { selectedTab: 'data' }; + this.state = { selectedTab: PANEL_CONFIG_TABS.DATA }; } - switchTab(selectedTab) { + switchTab(selectedTab: PANEL_CONFIG_TABS) { this.setState({ selectedTab }); } + handleTextChange = (name: keyof TimeseriesVisParams) => ( + e: React.ChangeEvent + ) => this.props.onChange({ [name]: e.target.value }); + render() { const defaults = { filter: { query: '', language: getDefaultQueryLanguage() }, @@ -56,106 +132,31 @@ class TimeseriesPanelConfigUi extends Component { const model = { ...defaults, ...this.props.model }; const { selectedTab } = this.state; const handleSelectChange = createSelectHandler(this.props.onChange); - const handleTextChange = createTextHandler(this.props.onChange); const htmlId = htmlIdGenerator(); - const { intl } = this.props; - const positionOptions = [ - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeseries.positionOptions.rightLabel', - defaultMessage: 'Right', - }), - value: 'right', - }, - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeseries.positionOptions.leftLabel', - defaultMessage: 'Left', - }), - value: 'left', - }, - ]; - const tooltipModeOptions = [ - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeseries.tooltipOptions.showAll', - defaultMessage: 'Show all values', - }), - value: 'show_all', - }, - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeseries.tooltipOptions.showFocused', - defaultMessage: 'Show focused values', - }), - value: 'show_focused', - }, - ]; - const selectedPositionOption = positionOptions.find((option) => { - return model.axis_position === option.value; - }); - const scaleOptions = [ - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeseries.scaleOptions.normalLabel', - defaultMessage: 'Normal', - }), - value: 'normal', - }, - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeseries.scaleOptions.logLabel', - defaultMessage: 'Log', - }), - value: 'log', - }, - ]; - const selectedAxisScaleOption = scaleOptions.find((option) => { - return model.axis_scale === option.value; - }); - const legendPositionOptions = [ - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeseries.legendPositionOptions.rightLabel', - defaultMessage: 'Right', - }), - value: 'right', - }, - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeseries.legendPositionOptions.leftLabel', - defaultMessage: 'Left', - }), - value: 'left', - }, - { - label: intl.formatMessage({ - id: 'visTypeTimeseries.timeseries.legendPositionOptions.bottomLabel', - defaultMessage: 'Bottom', - }), - value: 'bottom', - }, - ]; - const selectedLegendPosOption = legendPositionOptions.find((option) => { - return model.legend_position === option.value; - }); - - const selectedTooltipMode = tooltipModeOptions.find((option) => { - return model.tooltip_mode === option.value; - }); + const selectedPositionOption = positionOptions.find( + (option) => model.axis_position === option.value + ); + const selectedAxisScaleOption = scaleOptions.find( + (option) => model.axis_scale === option.value + ); + const selectedLegendPosOption = legendPositionOptions.find( + (option) => model.legend_position === option.value + ); + const selectedTooltipMode = tooltipModeOptions.find( + (option) => model.tooltip_mode === option.value + ); let view; - if (selectedTab === 'data') { + if (selectedTab === PANEL_CONFIG_TABS.DATA) { view = ( ); - } else if (selectedTab === 'annotations') { + } else if (selectedTab === PANEL_CONFIG_TABS.ANNOTATIONS) { view = ( this.props.onChange({ filter })} + onChange={(filter: PanelConfigProps['model']['filter']) => + this.props.onChange({ filter }) + } indexPatterns={[model.index_pattern || model.default_index_pattern]} /> @@ -250,7 +253,10 @@ class TimeseriesPanelConfigUi extends Component { /> } > - + @@ -263,7 +269,10 @@ class TimeseriesPanelConfigUi extends Component { /> } > - + @@ -394,15 +403,18 @@ class TimeseriesPanelConfigUi extends Component { return ( <> - this.switchTab('data')}> + this.switchTab(PANEL_CONFIG_TABS.DATA)} + > this.switchTab('options')} + isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS} + onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)} data-test-subj="timeSeriesEditorPanelOptionsBtn" > this.switchTab('annotations')} + isSelected={selectedTab === PANEL_CONFIG_TABS.ANNOTATIONS} + onClick={() => this.switchTab(PANEL_CONFIG_TABS.ANNOTATIONS)} > { + constructor(props: PanelConfigProps) { super(props); - this.state = { selectedTab: 'data' }; + this.state = { selectedTab: PANEL_CONFIG_TABS.DATA }; } UNSAFE_componentWillMount() { const { model } = this.props; - const parts = {}; - if (!model.bar_color_rules || (model.bar_color_rules && model.bar_color_rules.length === 0)) { - parts.bar_color_rules = [{ id: uuid.v1() }]; + if (!model.bar_color_rules || !model.bar_color_rules.length) { + this.props.onChange({ bar_color_rules: [{ id: uuid.v1() }] }); } - this.props.onChange(parts); } - switchTab(selectedTab) { + switchTab(selectedTab: PANEL_CONFIG_TABS) { this.setState({ selectedTab }); } + handleTextChange = (name: keyof TimeseriesVisParams) => ( + e: React.ChangeEvent + ) => this.props.onChange({ [name]: e.target.value }); + render() { const { selectedTab } = this.state; const defaults = { @@ -61,20 +70,15 @@ export class TopNPanelConfig extends Component { }; const model = { ...defaults, ...this.props.model }; const htmlId = htmlIdGenerator(); - const handleTextChange = createTextHandler(this.props.onChange); - let view; - if (selectedTab === 'data') { - view = ( + const view = + selectedTab === PANEL_CONFIG_TABS.DATA ? ( - ); - } else { - view = ( + ) : (
@@ -105,8 +109,8 @@ export class TopNPanelConfig extends Component { } > @@ -134,12 +138,12 @@ export class TopNPanelConfig extends Component { > this.props.onChange({ filter })} + onChange={(filter: PanelConfigProps['model']['filter']) => + this.props.onChange({ filter }) + } indexPatterns={[model.index_pattern || model.default_index_pattern]} /> @@ -214,17 +218,23 @@ export class TopNPanelConfig extends Component {
); - } + return ( <> - this.switchTab('data')}> + this.switchTab(PANEL_CONFIG_TABS.DATA)} + > - this.switchTab('options')}> + this.switchTab(PANEL_CONFIG_TABS.OPTIONS)} + > ; + getConfig: IUiSettingsClient['get']; + onChange: (partialModel: Partial) => void; +} + +export enum PANEL_CONFIG_TABS { + DATA = 'data', + OPTIONS = 'options', + ANNOTATIONS = 'annotations', + MARKDOWN = 'markdown', +} diff --git a/src/plugins/vis_type_timeseries/public/application/components/yes_no.test.js b/src/plugins/vis_type_timeseries/public/application/components/yes_no.test.tsx similarity index 53% rename from src/plugins/vis_type_timeseries/public/application/components/yes_no.test.js rename to src/plugins/vis_type_timeseries/public/application/components/yes_no.test.tsx index 6b5ac3febae951..1a87ba342f90c4 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/yes_no.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/yes_no.test.tsx @@ -7,29 +7,29 @@ */ import React from 'react'; -import { expect } from 'chai'; -import { shallowWithIntl } from '@kbn/test/jest'; -import sinon from 'sinon'; +import { shallow } from 'enzyme'; import { YesNo } from './yes_no'; describe('YesNo', () => { it('call onChange={handleChange} on yes', () => { - const handleChange = sinon.spy(); - const wrapper = shallowWithIntl(); + const handleChange = jest.fn(); + const wrapper = shallow( + + ); wrapper.find('EuiRadio').first().simulate('change'); - expect(handleChange.calledOnce).to.equal(true); - expect(handleChange.firstCall.args[0]).to.eql({ - test: 1, + expect(handleChange).toHaveBeenCalledTimes(1); + expect(handleChange).toHaveBeenCalledWith({ + ignore_global_filters: 1, }); }); it('call onChange={handleChange} on no', () => { - const handleChange = sinon.spy(); - const wrapper = shallowWithIntl(); + const handleChange = jest.fn(); + const wrapper = shallow(); wrapper.find('EuiRadio').last().simulate('change'); - expect(handleChange.calledOnce).to.equal(true); - expect(handleChange.firstCall.args[0]).to.eql({ - test: 0, + expect(handleChange).toHaveBeenCalledTimes(1); + expect(handleChange).toHaveBeenCalledWith({ + show_legend: 0, }); }); }); diff --git a/src/plugins/vis_type_timeseries/public/application/components/yes_no.js b/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx similarity index 69% rename from src/plugins/vis_type_timeseries/public/application/components/yes_no.js rename to src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx index b9cf33faf378c2..708892bbc681d3 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/yes_no.js +++ b/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx @@ -6,23 +6,35 @@ * Side Public License, v 1. */ -import PropTypes from 'prop-types'; -import React from 'react'; -import _ from 'lodash'; +import React, { useCallback } from 'react'; import { EuiRadio, htmlIdGenerator } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { TimeseriesVisParams } from '../../types'; -export function YesNo(props) { - const { name, value, disabled, 'data-test-subj': dataTestSubj } = props; - const handleChange = (value) => { - const { name } = props; - return () => { - const parts = { [name]: value }; - props.onChange(parts); - }; - }; +interface YesNoProps { + name: ParamName; + value: TimeseriesVisParams[ParamName]; + disabled?: boolean; + 'data-test-subj'?: string; + onChange: (partialModel: Partial) => void; +} + +export function YesNo({ + name, + value, + disabled, + 'data-test-subj': dataTestSubj, + onChange, +}: YesNoProps) { + const handleChange = useCallback( + (val: number) => { + return () => onChange({ [name]: val }); + }, + [onChange, name] + ); const htmlId = htmlIdGenerator(); - const inputName = name + _.uniqueId(); + const inputName = htmlId(name); + return (
); } - -YesNo.propTypes = { - name: PropTypes.string, - value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), -}; - -YesNo.defaultProps = { - disabled: false, -}; From 4dec429fc95e1abc5761f3130b3e494b62515352 Mon Sep 17 00:00:00 2001 From: Byron Hulcher Date: Tue, 23 Mar 2021 10:09:11 -0400 Subject: [PATCH 19/93] Use string values for EngineTypes instead of int (#94823) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../applications/app_search/components/engine/types.ts | 6 +++--- .../components/engines/engines_logic.test.ts | 10 ++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts index 0cfef4320825c0..75828fa9bfc4c7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts @@ -9,9 +9,9 @@ import { Schema, SchemaConflicts, IIndexingStatus } from '../../../shared/types' import { ApiToken } from '../credentials/types'; export enum EngineTypes { - default, - indexed, - meta, + default = 'default', + indexed = 'indexed', + meta = 'meta', } export interface Engine { name: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts index e4776f7a75df41..a67e5bb9ae7dc4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts @@ -204,7 +204,10 @@ describe('EnginesLogic', () => { it('should call loadEngines if engine.type === default', () => { jest.spyOn(EnginesLogic.actions, 'loadEngines'); - EnginesLogic.actions.onDeleteEngineSuccess({ ...MOCK_ENGINE, type: EngineTypes.default }); + EnginesLogic.actions.onDeleteEngineSuccess({ + ...MOCK_ENGINE, + type: 'default' as EngineTypes.default, + }); expect(EnginesLogic.actions.loadEngines).toHaveBeenCalled(); }); @@ -212,7 +215,10 @@ describe('EnginesLogic', () => { it('should call loadMetaEngines if engine.type === meta', () => { jest.spyOn(EnginesLogic.actions, 'loadMetaEngines'); - EnginesLogic.actions.onDeleteEngineSuccess({ ...MOCK_ENGINE, type: EngineTypes.meta }); + EnginesLogic.actions.onDeleteEngineSuccess({ + ...MOCK_ENGINE, + type: 'meta' as EngineTypes.meta, + }); expect(EnginesLogic.actions.loadMetaEngines).toHaveBeenCalled(); }); From 12d54648527485bd5796e8f2d052e08257b62b6f Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 23 Mar 2021 15:31:27 +0100 Subject: [PATCH 20/93] [QueryBar][KQL] Fix spaces are converted to non-breaking spaces in Safari. Support escaping unicode characters in KQL. Make non-breaking space part of a KQL grammar (#94688) --- .../es_query/kuery/ast/_generated_/kuery.js | 451 ++++++++++++------ .../common/es_query/kuery/ast/ast.test.ts | 21 + .../data/common/es_query/kuery/ast/kuery.peg | 15 +- .../query_string_input.test.tsx | 22 + .../query_string_input/query_string_input.tsx | 43 +- 5 files changed, 396 insertions(+), 156 deletions(-) diff --git a/src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js b/src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js index 4d1cede837f137..7ee744ad5f4c82 100644 --- a/src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js +++ b/src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js @@ -246,24 +246,32 @@ module.exports = (function() { peg$c65 = function(keyword) { return keyword; }, peg$c66 = /^[\\():<>"*{}]/, peg$c67 = { type: "class", value: "[\\\\():<>\"*{}]", description: "[\\\\():<>\"*{}]" }, - peg$c68 = "<=", - peg$c69 = { type: "literal", value: "<=", description: "\"<=\"" }, - peg$c70 = function() { return 'lte'; }, - peg$c71 = ">=", - peg$c72 = { type: "literal", value: ">=", description: "\">=\"" }, - peg$c73 = function() { return 'gte'; }, - peg$c74 = "<", - peg$c75 = { type: "literal", value: "<", description: "\"<\"" }, - peg$c76 = function() { return 'lt'; }, - peg$c77 = ">", - peg$c78 = { type: "literal", value: ">", description: "\">\"" }, - peg$c79 = function() { return 'gt'; }, - peg$c80 = { type: "other", description: "whitespace" }, - peg$c81 = /^[ \t\r\n]/, - peg$c82 = { type: "class", value: "[\\ \\t\\r\\n]", description: "[\\ \\t\\r\\n]" }, - peg$c83 = "@kuery-cursor@", - peg$c84 = { type: "literal", value: "@kuery-cursor@", description: "\"@kuery-cursor@\"" }, - peg$c85 = function() { return cursorSymbol; }, + peg$c68 = function(sequence) { return sequence; }, + peg$c69 = "u", + peg$c70 = { type: "literal", value: "u", description: "\"u\"" }, + peg$c71 = function(digits) { + return String.fromCharCode(parseInt(digits, 16)); + }, + peg$c72 = /^[0-9a-f]/i, + peg$c73 = { type: "class", value: "[0-9a-f]i", description: "[0-9a-f]i" }, + peg$c74 = "<=", + peg$c75 = { type: "literal", value: "<=", description: "\"<=\"" }, + peg$c76 = function() { return 'lte'; }, + peg$c77 = ">=", + peg$c78 = { type: "literal", value: ">=", description: "\">=\"" }, + peg$c79 = function() { return 'gte'; }, + peg$c80 = "<", + peg$c81 = { type: "literal", value: "<", description: "\"<\"" }, + peg$c82 = function() { return 'lt'; }, + peg$c83 = ">", + peg$c84 = { type: "literal", value: ">", description: "\">\"" }, + peg$c85 = function() { return 'gt'; }, + peg$c86 = { type: "other", description: "whitespace" }, + peg$c87 = /^[ \t\r\n\xA0]/, + peg$c88 = { type: "class", value: "[\\ \\t\\r\\n\\u00A0]", description: "[\\ \\t\\r\\n\\u00A0]" }, + peg$c89 = "@kuery-cursor@", + peg$c90 = { type: "literal", value: "@kuery-cursor@", description: "\"@kuery-cursor@\"" }, + peg$c91 = function() { return cursorSymbol; }, peg$currPos = 0, peg$savedPos = 0, @@ -455,7 +463,7 @@ module.exports = (function() { function peg$parsestart() { var s0, s1, s2, s3; - var key = peg$currPos * 34 + 0, + var key = peg$currPos * 37 + 0, cached = peg$resultsCache[key]; if (cached) { @@ -503,7 +511,7 @@ module.exports = (function() { function peg$parseOrQuery() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 34 + 1, + var key = peg$currPos * 37 + 1, cached = peg$resultsCache[key]; if (cached) { @@ -579,7 +587,7 @@ module.exports = (function() { function peg$parseAndQuery() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 34 + 2, + var key = peg$currPos * 37 + 2, cached = peg$resultsCache[key]; if (cached) { @@ -655,7 +663,7 @@ module.exports = (function() { function peg$parseNotQuery() { var s0, s1, s2; - var key = peg$currPos * 34 + 3, + var key = peg$currPos * 37 + 3, cached = peg$resultsCache[key]; if (cached) { @@ -692,7 +700,7 @@ module.exports = (function() { function peg$parseSubQuery() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 34 + 4, + var key = peg$currPos * 37 + 4, cached = peg$resultsCache[key]; if (cached) { @@ -764,7 +772,7 @@ module.exports = (function() { function peg$parseNestedQuery() { var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; - var key = peg$currPos * 34 + 5, + var key = peg$currPos * 37 + 5, cached = peg$resultsCache[key]; if (cached) { @@ -876,7 +884,7 @@ module.exports = (function() { function peg$parseExpression() { var s0; - var key = peg$currPos * 34 + 6, + var key = peg$currPos * 37 + 6, cached = peg$resultsCache[key]; if (cached) { @@ -901,7 +909,7 @@ module.exports = (function() { function peg$parseField() { var s0, s1; - var key = peg$currPos * 34 + 7, + var key = peg$currPos * 37 + 7, cached = peg$resultsCache[key]; if (cached) { @@ -926,7 +934,7 @@ module.exports = (function() { function peg$parseFieldRangeExpression() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 34 + 8, + var key = peg$currPos * 37 + 8, cached = peg$resultsCache[key]; if (cached) { @@ -988,7 +996,7 @@ module.exports = (function() { function peg$parseFieldValueExpression() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 34 + 9, + var key = peg$currPos * 37 + 9, cached = peg$resultsCache[key]; if (cached) { @@ -1056,7 +1064,7 @@ module.exports = (function() { function peg$parseValueExpression() { var s0, s1; - var key = peg$currPos * 34 + 10, + var key = peg$currPos * 37 + 10, cached = peg$resultsCache[key]; if (cached) { @@ -1081,7 +1089,7 @@ module.exports = (function() { function peg$parseListOfValues() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 34 + 11, + var key = peg$currPos * 37 + 11, cached = peg$resultsCache[key]; if (cached) { @@ -1153,7 +1161,7 @@ module.exports = (function() { function peg$parseOrListOfValues() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 34 + 12, + var key = peg$currPos * 37 + 12, cached = peg$resultsCache[key]; if (cached) { @@ -1229,7 +1237,7 @@ module.exports = (function() { function peg$parseAndListOfValues() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 34 + 13, + var key = peg$currPos * 37 + 13, cached = peg$resultsCache[key]; if (cached) { @@ -1305,7 +1313,7 @@ module.exports = (function() { function peg$parseNotListOfValues() { var s0, s1, s2; - var key = peg$currPos * 34 + 14, + var key = peg$currPos * 37 + 14, cached = peg$resultsCache[key]; if (cached) { @@ -1342,7 +1350,7 @@ module.exports = (function() { function peg$parseValue() { var s0, s1; - var key = peg$currPos * 34 + 15, + var key = peg$currPos * 37 + 15, cached = peg$resultsCache[key]; if (cached) { @@ -1382,7 +1390,7 @@ module.exports = (function() { function peg$parseOr() { var s0, s1, s2, s3, s4; - var key = peg$currPos * 34 + 16, + var key = peg$currPos * 37 + 16, cached = peg$resultsCache[key]; if (cached) { @@ -1451,7 +1459,7 @@ module.exports = (function() { function peg$parseAnd() { var s0, s1, s2, s3, s4; - var key = peg$currPos * 34 + 17, + var key = peg$currPos * 37 + 17, cached = peg$resultsCache[key]; if (cached) { @@ -1520,7 +1528,7 @@ module.exports = (function() { function peg$parseNot() { var s0, s1, s2, s3; - var key = peg$currPos * 34 + 18, + var key = peg$currPos * 37 + 18, cached = peg$resultsCache[key]; if (cached) { @@ -1574,7 +1582,7 @@ module.exports = (function() { function peg$parseLiteral() { var s0, s1; - var key = peg$currPos * 34 + 19, + var key = peg$currPos * 37 + 19, cached = peg$resultsCache[key]; if (cached) { @@ -1602,7 +1610,7 @@ module.exports = (function() { function peg$parseQuotedString() { var s0, s1, s2, s3, s4, s5, s6; - var key = peg$currPos * 34 + 20, + var key = peg$currPos * 37 + 20, cached = peg$resultsCache[key]; if (cached) { @@ -1729,7 +1737,7 @@ module.exports = (function() { function peg$parseQuotedCharacter() { var s0, s1, s2; - var key = peg$currPos * 34 + 21, + var key = peg$currPos * 37 + 21, cached = peg$resultsCache[key]; if (cached) { @@ -1740,53 +1748,23 @@ module.exports = (function() { s0 = peg$parseEscapedWhitespace(); if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c44; - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c45); } - } - if (s1 !== peg$FAILED) { - if (peg$c46.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } - } - if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c48(s2); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + s0 = peg$parseEscapedUnicodeSequence(); if (s0 === peg$FAILED) { s0 = peg$currPos; - s1 = peg$currPos; - peg$silentFails++; - s2 = peg$parseCursor(); - peg$silentFails--; - if (s2 === peg$FAILED) { - s1 = void 0; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c44; + peg$currPos++; } else { - peg$currPos = s1; s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c45); } } if (s1 !== peg$FAILED) { - if (peg$c49.test(input.charAt(peg$currPos))) { + if (peg$c46.test(input.charAt(peg$currPos))) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c50); } + if (peg$silentFails === 0) { peg$fail(peg$c47); } } if (s2 !== peg$FAILED) { peg$savedPos = s0; @@ -1800,6 +1778,39 @@ module.exports = (function() { peg$currPos = s0; s0 = peg$FAILED; } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$currPos; + peg$silentFails++; + s2 = peg$parseCursor(); + peg$silentFails--; + if (s2 === peg$FAILED) { + s1 = void 0; + } else { + peg$currPos = s1; + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + if (peg$c49.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c50); } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c48(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } } } @@ -1811,7 +1822,7 @@ module.exports = (function() { function peg$parseUnquotedLiteral() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 34 + 22, + var key = peg$currPos * 37 + 22, cached = peg$resultsCache[key]; if (cached) { @@ -1891,7 +1902,7 @@ module.exports = (function() { function peg$parseUnquotedCharacter() { var s0, s1, s2, s3, s4; - var key = peg$currPos * 34 + 23, + var key = peg$currPos * 37 + 23, cached = peg$resultsCache[key]; if (cached) { @@ -1904,55 +1915,61 @@ module.exports = (function() { if (s0 === peg$FAILED) { s0 = peg$parseEscapedSpecialCharacter(); if (s0 === peg$FAILED) { - s0 = peg$parseEscapedKeyword(); + s0 = peg$parseEscapedUnicodeSequence(); if (s0 === peg$FAILED) { - s0 = peg$parseWildcard(); + s0 = peg$parseEscapedKeyword(); if (s0 === peg$FAILED) { - s0 = peg$currPos; - s1 = peg$currPos; - peg$silentFails++; - s2 = peg$parseSpecialCharacter(); - peg$silentFails--; - if (s2 === peg$FAILED) { - s1 = void 0; - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - s2 = peg$currPos; + s0 = peg$parseWildcard(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$currPos; peg$silentFails++; - s3 = peg$parseKeyword(); + s2 = peg$parseSpecialCharacter(); peg$silentFails--; - if (s3 === peg$FAILED) { - s2 = void 0; + if (s2 === peg$FAILED) { + s1 = void 0; } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s1; + s1 = peg$FAILED; } - if (s2 !== peg$FAILED) { - s3 = peg$currPos; + if (s1 !== peg$FAILED) { + s2 = peg$currPos; peg$silentFails++; - s4 = peg$parseCursor(); + s3 = peg$parseKeyword(); peg$silentFails--; - if (s4 === peg$FAILED) { - s3 = void 0; + if (s3 === peg$FAILED) { + s2 = void 0; } else { - peg$currPos = s3; - s3 = peg$FAILED; + peg$currPos = s2; + s2 = peg$FAILED; } - if (s3 !== peg$FAILED) { - if (input.length > peg$currPos) { - s4 = input.charAt(peg$currPos); - peg$currPos++; + if (s2 !== peg$FAILED) { + s3 = peg$currPos; + peg$silentFails++; + s4 = peg$parseCursor(); + peg$silentFails--; + if (s4 === peg$FAILED) { + s3 = void 0; } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c52); } + peg$currPos = s3; + s3 = peg$FAILED; } - if (s4 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c48(s4); - s0 = s1; + if (s3 !== peg$FAILED) { + if (input.length > peg$currPos) { + s4 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c52); } + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c48(s4); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1965,9 +1982,6 @@ module.exports = (function() { peg$currPos = s0; s0 = peg$FAILED; } - } else { - peg$currPos = s0; - s0 = peg$FAILED; } } } @@ -1982,7 +1996,7 @@ module.exports = (function() { function peg$parseWildcard() { var s0, s1; - var key = peg$currPos * 34 + 24, + var key = peg$currPos * 37 + 24, cached = peg$resultsCache[key]; if (cached) { @@ -2013,7 +2027,7 @@ module.exports = (function() { function peg$parseOptionalSpace() { var s0, s1, s2, s3, s4, s5; - var key = peg$currPos * 34 + 25, + var key = peg$currPos * 37 + 25, cached = peg$resultsCache[key]; if (cached) { @@ -2083,7 +2097,7 @@ module.exports = (function() { function peg$parseEscapedWhitespace() { var s0, s1; - var key = peg$currPos * 34 + 26, + var key = peg$currPos * 37 + 26, cached = peg$resultsCache[key]; if (cached) { @@ -2144,7 +2158,7 @@ module.exports = (function() { function peg$parseEscapedSpecialCharacter() { var s0, s1, s2; - var key = peg$currPos * 34 + 27, + var key = peg$currPos * 37 + 27, cached = peg$resultsCache[key]; if (cached) { @@ -2184,7 +2198,7 @@ module.exports = (function() { function peg$parseEscapedKeyword() { var s0, s1, s2; - var key = peg$currPos * 34 + 28, + var key = peg$currPos * 37 + 28, cached = peg$resultsCache[key]; if (cached) { @@ -2248,7 +2262,7 @@ module.exports = (function() { function peg$parseKeyword() { var s0; - var key = peg$currPos * 34 + 29, + var key = peg$currPos * 37 + 29, cached = peg$resultsCache[key]; if (cached) { @@ -2273,7 +2287,7 @@ module.exports = (function() { function peg$parseSpecialCharacter() { var s0; - var key = peg$currPos * 34 + 30, + var key = peg$currPos * 37 + 30, cached = peg$resultsCache[key]; if (cached) { @@ -2295,10 +2309,147 @@ module.exports = (function() { return s0; } + function peg$parseEscapedUnicodeSequence() { + var s0, s1, s2; + + var key = peg$currPos * 37 + 31, + cached = peg$resultsCache[key]; + + if (cached) { + peg$currPos = cached.nextPos; + + return cached.result; + } + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c44; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c45); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseUnicodeSequence(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c68(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; + + return s0; + } + + function peg$parseUnicodeSequence() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + var key = peg$currPos * 37 + 32, + cached = peg$resultsCache[key]; + + if (cached) { + peg$currPos = cached.nextPos; + + return cached.result; + } + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 117) { + s1 = peg$c69; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c70); } + } + if (s1 !== peg$FAILED) { + s2 = peg$currPos; + s3 = peg$currPos; + s4 = peg$parseHexDigit(); + if (s4 !== peg$FAILED) { + s5 = peg$parseHexDigit(); + if (s5 !== peg$FAILED) { + s6 = peg$parseHexDigit(); + if (s6 !== peg$FAILED) { + s7 = peg$parseHexDigit(); + if (s7 !== peg$FAILED) { + s4 = [s4, s5, s6, s7]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s2 = input.substring(s2, peg$currPos); + } else { + s2 = s3; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c71(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; + + return s0; + } + + function peg$parseHexDigit() { + var s0; + + var key = peg$currPos * 37 + 33, + cached = peg$resultsCache[key]; + + if (cached) { + peg$currPos = cached.nextPos; + + return cached.result; + } + + if (peg$c72.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c73); } + } + + peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; + + return s0; + } + function peg$parseRangeOperator() { var s0, s1; - var key = peg$currPos * 34 + 31, + var key = peg$currPos * 37 + 34, cached = peg$resultsCache[key]; if (cached) { @@ -2308,58 +2459,58 @@ module.exports = (function() { } s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c68) { - s1 = peg$c68; + if (input.substr(peg$currPos, 2) === peg$c74) { + s1 = peg$c74; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c69); } + if (peg$silentFails === 0) { peg$fail(peg$c75); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c70(); + s1 = peg$c76(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c71) { - s1 = peg$c71; + if (input.substr(peg$currPos, 2) === peg$c77) { + s1 = peg$c77; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c72); } + if (peg$silentFails === 0) { peg$fail(peg$c78); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c73(); + s1 = peg$c79(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 60) { - s1 = peg$c74; + s1 = peg$c80; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c75); } + if (peg$silentFails === 0) { peg$fail(peg$c81); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c76(); + s1 = peg$c82(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 62) { - s1 = peg$c77; + s1 = peg$c83; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c78); } + if (peg$silentFails === 0) { peg$fail(peg$c84); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c79(); + s1 = peg$c85(); } s0 = s1; } @@ -2374,7 +2525,7 @@ module.exports = (function() { function peg$parseSpace() { var s0, s1; - var key = peg$currPos * 34 + 32, + var key = peg$currPos * 37 + 35, cached = peg$resultsCache[key]; if (cached) { @@ -2384,17 +2535,17 @@ module.exports = (function() { } peg$silentFails++; - if (peg$c81.test(input.charAt(peg$currPos))) { + if (peg$c87.test(input.charAt(peg$currPos))) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c82); } + if (peg$silentFails === 0) { peg$fail(peg$c88); } } peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c80); } + if (peg$silentFails === 0) { peg$fail(peg$c86); } } peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; @@ -2405,7 +2556,7 @@ module.exports = (function() { function peg$parseCursor() { var s0, s1, s2; - var key = peg$currPos * 34 + 33, + var key = peg$currPos * 37 + 36, cached = peg$resultsCache[key]; if (cached) { @@ -2423,16 +2574,16 @@ module.exports = (function() { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 14) === peg$c83) { - s2 = peg$c83; + if (input.substr(peg$currPos, 14) === peg$c89) { + s2 = peg$c89; peg$currPos += 14; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c84); } + if (peg$silentFails === 0) { peg$fail(peg$c90); } } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c85(); + s1 = peg$c91(); s0 = s1; } else { peg$currPos = s0; diff --git a/src/plugins/data/common/es_query/kuery/ast/ast.test.ts b/src/plugins/data/common/es_query/kuery/ast/ast.test.ts index 7d6e4c14f15020..f8d7dc02d38fce 100644 --- a/src/plugins/data/common/es_query/kuery/ast/ast.test.ts +++ b/src/plugins/data/common/es_query/kuery/ast/ast.test.ts @@ -55,6 +55,15 @@ describe('kuery AST API', () => { expect(actual).toEqual(expected); }); + test('nbsp should be recognised as whitespace', () => { + const expected = nodeTypes.function.buildNode('and', [ + nodeTypes.function.buildNode('is', null, 'foo'), + nodeTypes.function.buildNode('is', null, 'bar'), + ]); + const actual = fromKueryExpression('foo and\u00A0bar'); + expect(actual).toEqual(expected); + }); + test('should support "or" as a binary operator', () => { const expected = nodeTypes.function.buildNode('or', [ nodeTypes.function.buildNode('is', null, 'foo'), @@ -315,6 +324,12 @@ describe('kuery AST API', () => { expect(actual).toEqual(expected); }); + test('should allow escaping of unicode sequences with a backslash', () => { + const expected = nodeTypes.literal.buildNode('\\u00A0'); + const actual = fromLiteralExpression('\\\\u00A0'); + expect(actual).toEqual(expected); + }); + test('should support double quoted strings that do not need escapes except for quotes', () => { const expected = nodeTypes.literal.buildNode('\\():<>"*'); const actual = fromLiteralExpression('"\\():<>\\"*"'); @@ -327,6 +342,12 @@ describe('kuery AST API', () => { expect(actual).toEqual(expected); }); + test('should support escaped unicode sequences inside quoted strings', () => { + const expected = nodeTypes.literal.buildNode('\\u00A0'); + const actual = fromLiteralExpression('"\\\\u00A0"'); + expect(actual).toEqual(expected); + }); + test('should detect wildcards and build wildcard AST nodes', () => { const expected = nodeTypes.wildcard.buildNode('foo*bar'); const actual = fromLiteralExpression('foo*bar'); diff --git a/src/plugins/data/common/es_query/kuery/ast/kuery.peg b/src/plugins/data/common/es_query/kuery/ast/kuery.peg index 2e195ec7a618fd..dbea96eaac5b28 100644 --- a/src/plugins/data/common/es_query/kuery/ast/kuery.peg +++ b/src/plugins/data/common/es_query/kuery/ast/kuery.peg @@ -226,6 +226,7 @@ QuotedString QuotedCharacter = EscapedWhitespace + / EscapedUnicodeSequence / '\\' char:[\\"] { return char; } / !Cursor char:[^"] { return char; } @@ -253,6 +254,7 @@ UnquotedLiteral UnquotedCharacter = EscapedWhitespace / EscapedSpecialCharacter + / EscapedUnicodeSequence / EscapedKeyword / Wildcard / !SpecialCharacter !Keyword !Cursor char:. { return char; } @@ -291,6 +293,17 @@ Keyword SpecialCharacter = [\\():<>"*{}] +EscapedUnicodeSequence + = '\\' sequence:UnicodeSequence { return sequence; } + +UnicodeSequence + = "u" digits:$(HexDigit HexDigit HexDigit HexDigit) { + return String.fromCharCode(parseInt(digits, 16)); + } + +HexDigit + = [0-9a-f]i + RangeOperator = '<=' { return 'lte'; } / '>=' { return 'gte'; } @@ -298,7 +311,7 @@ RangeOperator / '>' { return 'gt'; } Space "whitespace" - = [\ \t\r\n] + = [\ \t\r\n\u00A0] Cursor = &{ return parseCursor; } '@kuery-cursor@' { return cursorSymbol; } diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx index 45d7c0a2be6655..c9530ad3f51951 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx @@ -366,4 +366,26 @@ describe('QueryStringInput', () => { ); expect(mockFetchIndexPatterns.mock.calls[0][1]).toStrictEqual(patternStrings); }); + + it('Should convert non-breaking spaces into regular spaces', () => { + const mockCallback = jest.fn(); + + const component = mount( + wrapQueryStringInputInContext({ + query: kqlQuery, + onChange: mockCallback, + indexPatterns: [stubIndexPatternWithFields], + disableAutoFocus: true, + }) + ); + + const instance = component.find('QueryStringInputUI').instance() as QueryStringInputUI; + const input = instance.inputRef; + const inputWrapper = component.find(EuiTextArea).find('textarea'); + input!.value = 'foo\u00A0bar'; + inputWrapper.simulate('change'); + + expect(mockCallback).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith({ query: 'foo bar', language: 'kuery' }); + }); }); diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 5e34c401c76154..71ff09e81c567b 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -264,7 +264,8 @@ export default class QueryStringInputUI extends Component { }; private onInputChange = (event: React.ChangeEvent) => { - this.onQueryStringChange(event.target.value); + const value = this.formatTextAreaValue(event.target.value); + this.onQueryStringChange(value); if (event.target.value === '') { this.handleRemoveHeight(); } else { @@ -274,7 +275,8 @@ export default class QueryStringInputUI extends Component { private onClickInput = (event: React.MouseEvent) => { if (event.target instanceof HTMLTextAreaElement) { - this.onQueryStringChange(event.target.value); + const value = this.formatTextAreaValue(event.target.value); + this.onQueryStringChange(value); } }; @@ -282,7 +284,8 @@ export default class QueryStringInputUI extends Component { if ([KEY_CODES.LEFT, KEY_CODES.RIGHT, KEY_CODES.HOME, KEY_CODES.END].includes(event.keyCode)) { this.setState({ isSuggestionsVisible: true }); if (event.target instanceof HTMLTextAreaElement) { - this.onQueryStringChange(event.target.value); + const value = this.formatTextAreaValue(event.target.value); + this.onQueryStringChange(value); } } }; @@ -698,7 +701,7 @@ export default class QueryStringInputUI extends Component { defaultMessage: 'Search', }) } - value={this.getQueryString()} + value={this.forwardNewValueIfNeeded(this.getQueryString())} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} onChange={this.onInputChange} @@ -734,7 +737,7 @@ export default class QueryStringInputUI extends Component { data-test-subj={this.props.dataTestSubj || 'queryInput'} isInvalid={this.props.isInvalid} > - {this.getQueryString()} + {this.forwardNewValueIfNeeded(this.getQueryString())} {this.props.iconType ? (
@@ -788,4 +791,34 @@ export default class QueryStringInputUI extends Component {
); } + + /** + * Used to apply any string formatting to textarea value before converting it to {@link Query} and emitting it to the parent. + * This is a bit lower level then {@link fromUser} and needed to address any cross-browser inconsistencies where + * {@link forwardNewValueIfNeeded} should be kept in mind + */ + private formatTextAreaValue(newValue: string): string { + // Safari has a bug that it sometimes uses a non-breaking space instead of a regular space + // this breaks the search query: https://github.com/elastic/kibana/issues/87176 + return newValue.replace(/\u00A0/g, ' '); + } + + /** + * When passing a "value" prop into a textarea, + * check first if value has changed because of {@link formatTextAreaValue}, + * if this is just a formatting change, then skip this update by re-using current textarea value. + * This is needed to avoid re-rendering to preserve focus and selection + * @private + */ + private forwardNewValueIfNeeded(newQueryString: string) { + const oldQueryString = this.inputRef?.value ?? ''; + + const formattedNewQueryString = this.formatTextAreaValue(newQueryString); + // if old & new values are equal with formatting applied, then return an old query without formatting applied + if (formattedNewQueryString === this.formatTextAreaValue(oldQueryString)) { + return oldQueryString; + } else { + return formattedNewQueryString; + } + } } From 61db3f3cac7271373a966fb52c0e427aab7636aa Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 23 Mar 2021 10:45:59 -0400 Subject: [PATCH 21/93] Only check required fields for the default space (#95147) --- .../apis/spaces/get_active_space.ts | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/x-pack/test/api_integration/apis/spaces/get_active_space.ts b/x-pack/test/api_integration/apis/spaces/get_active_space.ts index 0d0a8bbeeceb78..e1edfdf5a70920 100644 --- a/x-pack/test/api_integration/apis/spaces/get_active_space.ts +++ b/x-pack/test/api_integration/apis/spaces/get_active_space.ts @@ -5,6 +5,7 @@ * 2.0. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -26,27 +27,32 @@ export default function ({ getService }: FtrProviderContext) { }); it('returns the default space', async () => { - await supertest.get('/internal/spaces/_active_space').set('kbn-xsrf', 'xxx').expect(200, { - id: 'default', - name: 'Default', - description: 'This is your default space!', - color: '#00bfb3', - disabledFeatures: [], - _reserved: true, - }); + await supertest + .get('/internal/spaces/_active_space') + .set('kbn-xsrf', 'xxx') + .expect(200) + .then((response) => { + const { id, name, _reserved } = response.body; + expect({ id, name, _reserved }).to.eql({ + id: 'default', + name: 'Default', + _reserved: true, + }); + }); }); it('returns the default space when explicitly referenced', async () => { await supertest .get('/s/default/internal/spaces/_active_space') .set('kbn-xsrf', 'xxx') - .expect(200, { - id: 'default', - name: 'Default', - description: 'This is your default space!', - color: '#00bfb3', - disabledFeatures: [], - _reserved: true, + .expect(200) + .then((response) => { + const { id, name, _reserved } = response.body; + expect({ id, name, _reserved }).to.eql({ + id: 'default', + name: 'Default', + _reserved: true, + }); }); }); From 523cc3984a7574f1e9b066d2ed692e5415bfdb2b Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Tue, 23 Mar 2021 09:49:33 -0500 Subject: [PATCH 22/93] [DOCS] Fixes Timelion link (#95168) --- docs/user/dashboard/timelion.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/dashboard/timelion.asciidoc b/docs/user/dashboard/timelion.asciidoc index 941f78168ecf71..676c46368a6ee4 100644 --- a/docs/user/dashboard/timelion.asciidoc +++ b/docs/user/dashboard/timelion.asciidoc @@ -13,7 +13,7 @@ dashboard features. In 8.0 and later, the *Timelion app* is removed from {kib}. To prepare for the removal of *Timelion app*, you must migrate *Timelion app* worksheets to a dashboard. For information on how to migrate *Timelion app* worksheets, refer to the -link:https://www.elastic.co/guide/en/kibana/current/release-notes-7.10.0.html#deprecation-v7.10.0[7.10.0 Release Notes]. +link:https://www.elastic.co/guide/en/kibana/7.10/release-notes-7.10.0.html#deprecation-v7.10.0[7.10.0 Release Notes]. ==== [float] From f3b71bce2a57dfbe6d48b02d2447c96d6f6c62e9 Mon Sep 17 00:00:00 2001 From: James Rucker Date: Tue, 23 Mar 2021 08:04:22 -0700 Subject: [PATCH 23/93] Change the type of content source errorReason to match ent-search (#95098) A recent refactor of content source jobs models unintentionally changed this API. This change restores the functionality of the re-authentication "Fix" link. --- .../workplace_search/__mocks__/content_sources.mock.ts | 4 ++-- .../components/shared/source_row/source_row.test.tsx | 2 +- .../components/shared/source_row/source_row.tsx | 2 +- .../public/applications/workplace_search/types.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts index ce92f62d3a0177..30f0dc73eeb02e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts @@ -21,7 +21,7 @@ export const contentSources = [ name: 'source', documentCount: '123', isFederatedSource: false, - errorReason: 0, + errorReason: null, allowsReauth: true, boost: 1, }, @@ -35,7 +35,7 @@ export const contentSources = [ name: 'Jira', documentCount: '34234', isFederatedSource: false, - errorReason: 0, + errorReason: null, allowsReauth: true, boost: 0.5, }, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx index b3ce0a01d5dd4b..f5c9858714cfd3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx @@ -39,7 +39,7 @@ describe('SourceRow', () => { const source = { ...contentSources[0], status: 'error', - errorReason: 1, + errorReason: 'credentials_invalid', }; const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx index 38d7945ca17532..5d15196fba5a6e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx @@ -34,7 +34,7 @@ import { import { ContentSourceDetails } from '../../../types'; import { SourceIcon } from '../source_icon'; -const CREDENTIALS_INVALID_ERROR_REASON = 1; +const CREDENTIALS_INVALID_ERROR_REASON = 'credentials_invalid'; export interface ISourceRow { showDetails?: boolean; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts index 79fe6dc8c92cbd..a653401aade276 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts @@ -106,7 +106,7 @@ export interface ContentSourceDetails extends ContentSource { isFederatedSource: boolean; searchable: boolean; supportedByLicense: boolean; - errorReason: number; + errorReason: string | null; allowsReauth: boolean; boost: number; } From c0c5fba4b0beb87f0c809883468632463ace2b5e Mon Sep 17 00:00:00 2001 From: John Schulz Date: Tue, 23 Mar 2021 11:22:03 -0400 Subject: [PATCH 24/93] [Fleet] Support force flag in bulk upgrade agents API (#94952) ## Summary https://github.com/elastic/kibana/pull/94952/commits/0cbbb41da2865e51339c6e4b1baa4e031b13ee54 is just a rearrangement of the tests. https://github.com/elastic/kibana/pull/94952/commits/5cad301e8748d874403ed98be6029b7d43400b44 has the real changes: * Bug fix: `force: true` should bypass any restrictions re: managed policies * Refactoring towards new response shape coming as part of https://github.com/elastic/kibana/issues/90437 * Added test to confirm new behavior ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../server/routes/agent/upgrade_handler.ts | 23 +- .../fleet/server/services/agents/upgrade.ts | 39 +- .../apis/agents/upgrade.ts | 908 ++++++++++-------- 3 files changed, 509 insertions(+), 461 deletions(-) diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts index 279018ef4212cd..b8af265883091b 100644 --- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts @@ -92,21 +92,14 @@ export const postBulkAgentsUpgradeHandler: RequestHandler< } try { - if (Array.isArray(agents)) { - await AgentService.sendUpgradeAgentsActions(soClient, esClient, { - agentIds: agents, - sourceUri, - version, - force, - }); - } else { - await AgentService.sendUpgradeAgentsActions(soClient, esClient, { - kuery: agents, - sourceUri, - version, - force, - }); - } + const agentOptions = Array.isArray(agents) ? { agentIds: agents } : { kuery: agents }; + const upgradeOptions = { + ...agentOptions, + sourceUri, + version, + force, + }; + await AgentService.sendUpgradeAgentsActions(soClient, esClient, upgradeOptions); const body: PostBulkAgentUpgradeResponse = {}; return response.ok({ body }); diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index 12623be0ed0443..6c3b404a5b6f3f 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -83,32 +83,33 @@ export async function sendUpgradeAgentsActions( force?: boolean; } ) { - const kibanaVersion = appContextService.getKibanaVersion(); - // Filter out agents currently unenrolling, agents unenrolled, and agents not upgradeable - const agents = await getAgents(esClient, options); + // Full set of agents + const agentsGiven = await getAgents(esClient, options); - // upgradeable if they pass the version check + // Filter out agents currently unenrolling, unenrolled, or not upgradeable b/c of version check + const kibanaVersion = appContextService.getKibanaVersion(); const upgradeableAgents = options.force - ? agents - : agents.filter((agent) => isAgentUpgradeable(agent, kibanaVersion)); + ? agentsGiven + : agentsGiven.filter((agent) => isAgentUpgradeable(agent, kibanaVersion)); - // get any policy ids from upgradable agents - const policyIdsToGet = new Set( - upgradeableAgents.filter((agent) => agent.policy_id).map((agent) => agent.policy_id!) - ); + if (!options.force) { + // get any policy ids from upgradable agents + const policyIdsToGet = new Set( + upgradeableAgents.filter((agent) => agent.policy_id).map((agent) => agent.policy_id!) + ); - // get the agent policies for those ids - const agentPolicies = await agentPolicyService.getByIDs(soClient, Array.from(policyIdsToGet), { - fields: ['is_managed'], - }); + // get the agent policies for those ids + const agentPolicies = await agentPolicyService.getByIDs(soClient, Array.from(policyIdsToGet), { + fields: ['is_managed'], + }); - // throw if any of those agent policies are managed - for (const policy of agentPolicies) { - if (policy.is_managed) { - throw new IngestManagerError(`Cannot upgrade agent in managed policy ${policy.id}`); + // throw if any of those agent policies are managed + for (const policy of agentPolicies) { + if (policy.is_managed) { + throw new IngestManagerError(`Cannot upgrade agent in managed policy ${policy.id}`); + } } } - // Create upgrade action for each agent const now = new Date().toISOString(); const data = { diff --git a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts index 9f2280ed9613ef..9a747fb11a6a62 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts @@ -23,7 +23,7 @@ export default function (providerContext: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); - describe('fleet upgrade agent', () => { + describe('fleet upgrade', () => { skipIfNoDockerRegistry(providerContext); before(async () => { await esArchiver.loadIfNeeded('fleet/agents'); @@ -36,478 +36,511 @@ export default function (providerContext: FtrProviderContext) { await esArchiver.unload('fleet/agents'); }); - it('should respond 200 to upgrade agent and update the agent SO', async () => { - const kibanaVersion = await kibanaServer.version.get(); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + describe('one agent', () => { + it('should respond 200 to upgrade agent and update the agent SO', async () => { + const kibanaVersion = await kibanaServer.version.get(); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, }, - }, - }); - await supertest - .post(`/api/fleet/agents/agent1/upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - version: kibanaVersion, - source_uri: 'http://path/to/download', - }) - .expect(200); + }); + await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersion, + source_uri: 'http://path/to/download', + }) + .expect(200); - const res = await supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'); - expect(typeof res.body.item.upgrade_started_at).to.be('string'); - }); - it('should respond 400 if upgrading agent with version the same as snapshot version', async () => { - const kibanaVersion = await kibanaServer.version.get(); - const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: kibanaVersion } } }, + const res = await supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'); + expect(typeof res.body.item.upgrade_started_at).to.be('string'); + }); + it('should respond 400 if upgrading agent with version the same as snapshot version', async () => { + const kibanaVersion = await kibanaServer.version.get(); + const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: kibanaVersion } } }, + }, }, - }, + }); + await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersionSnapshot, + }) + .expect(400); }); - await supertest - .post(`/api/fleet/agents/agent1/upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - version: kibanaVersionSnapshot, - }) - .expect(400); - }); - it('should respond 200 if upgrading agent with version the same as snapshot version and force flag is passed', async () => { - const kibanaVersion = await kibanaServer.version.get(); - const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: kibanaVersion } } }, + it('should respond 200 if upgrading agent with version the same as snapshot version and force flag is passed', async () => { + const kibanaVersion = await kibanaServer.version.get(); + const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: kibanaVersion } } }, + }, }, - }, + }); + await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersionSnapshot, + force: true, + }) + .expect(200); }); - await supertest - .post(`/api/fleet/agents/agent1/upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - version: kibanaVersionSnapshot, - force: true, - }) - .expect(200); - }); - it('should respond 200 if upgrading agent with version less than kibana snapshot version', async () => { - const kibanaVersion = await kibanaServer.version.get(); - const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion); + it('should respond 200 if upgrading agent with version less than kibana snapshot version', async () => { + const kibanaVersion = await kibanaServer.version.get(); + const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, }, - }, + }); + await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersionSnapshot, + }) + .expect(200); }); - await supertest - .post(`/api/fleet/agents/agent1/upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - version: kibanaVersionSnapshot, - }) - .expect(200); - }); - it('should respond 200 to upgrade agent and update the agent SO without source_uri', async () => { - const kibanaVersion = await kibanaServer.version.get(); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + it('should respond 200 to upgrade agent and update the agent SO without source_uri', async () => { + const kibanaVersion = await kibanaServer.version.get(); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, }, - }, + }); + await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersion, + }) + .expect(200); + const res = await supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'); + expect(typeof res.body.item.upgrade_started_at).to.be('string'); }); - await supertest - .post(`/api/fleet/agents/agent1/upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - version: kibanaVersion, - }) - .expect(200); - const res = await supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'); - expect(typeof res.body.item.upgrade_started_at).to.be('string'); - }); - it('should respond 400 if trying to upgrade to a version that does not match installed kibana version', async () => { - const kibanaVersion = await kibanaServer.version.get(); - const higherVersion = semver.inc(kibanaVersion, 'patch'); - await supertest - .post(`/api/fleet/agents/agent1/upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - version: higherVersion, - source_uri: 'http://path/to/download', - }) - .expect(400); - }); - it('should respond 400 if trying to upgrade an agent that is unenrolling', async () => { - const kibanaVersion = await kibanaServer.version.get(); - await supertest.post(`/api/fleet/agents/agent1/unenroll`).set('kbn-xsrf', 'xxx').send({ - force: true, + it('should respond 400 if trying to upgrade to a version that does not match installed kibana version', async () => { + const kibanaVersion = await kibanaServer.version.get(); + const higherVersion = semver.inc(kibanaVersion, 'patch'); + await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: higherVersion, + source_uri: 'http://path/to/download', + }) + .expect(400); }); - await supertest - .post(`/api/fleet/agents/agent1/upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - version: kibanaVersion, - }) - .expect(400); - }); - it('should respond 400 if trying to upgrade an agent that is unenrolled', async () => { - const kibanaVersion = await kibanaServer.version.get(); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - unenrolled_at: new Date().toISOString(), + it('should respond 400 if trying to upgrade an agent that is unenrolling', async () => { + const kibanaVersion = await kibanaServer.version.get(); + await supertest.post(`/api/fleet/agents/agent1/unenroll`).set('kbn-xsrf', 'xxx').send({ + force: true, + }); + await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersion, + }) + .expect(400); + }); + it('should respond 400 if trying to upgrade an agent that is unenrolled', async () => { + const kibanaVersion = await kibanaServer.version.get(); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + unenrolled_at: new Date().toISOString(), + }, }, - }, + }); + await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersion, + }) + .expect(400); }); - await supertest - .post(`/api/fleet/agents/agent1/upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - version: kibanaVersion, - }) - .expect(400); - }); - it('should respond 400 if trying to upgrade an agent that is not upgradeable', async () => { - const kibanaVersion = await kibanaServer.version.get(); - const res = await supertest - .post(`/api/fleet/agents/agent1/upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - version: kibanaVersion, - }) - .expect(400); - expect(res.body.message).to.equal('agent agent1 is not upgradeable'); - }); + it('should respond 400 if trying to upgrade an agent that is not upgradeable', async () => { + const kibanaVersion = await kibanaServer.version.get(); + const res = await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersion, + }) + .expect(400); + expect(res.body.message).to.equal('agent agent1 is not upgradeable'); + }); - it('should respond 200 to bulk upgrade upgradeable agents and update the agent SOs', async () => { - const kibanaVersion = await kibanaServer.version.get(); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + it('enrolled in a managed policy should respond 400 to upgrade and not update the agent SOs', async () => { + // update enrolled policy to managed + await supertest.put(`/api/fleet/agent_policies/policy1`).set('kbn-xsrf', 'xxxx').send({ + name: 'Test policy', + namespace: 'default', + is_managed: true, + }); + + const kibanaVersion = await kibanaServer.version.get(); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, }, - }, + }); + // attempt to upgrade agent in managed policy + const { body } = await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ version: kibanaVersion }) + .expect(400); + expect(body.message).to.contain('Cannot upgrade agent agent1 in managed policy policy1'); + + const agent1data = await supertest.get(`/api/fleet/agents/agent1`); + expect(typeof agent1data.body.item.upgrade_started_at).to.be('undefined'); }); - await es.update({ - id: 'agent2', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { - elastic: { - agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') }, + }); + + describe('multiple agents', () => { + it('should respond 200 to bulk upgrade upgradeable agents and update the agent SOs', async () => { + const kibanaVersion = await kibanaServer.version.get(); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, + }, + }); + await es.update({ + id: 'agent2', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { + elastic: { + agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') }, + }, }, }, }, - }, - }); - await supertest - .post(`/api/fleet/agents/bulk_upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - version: kibanaVersion, - agents: ['agent1', 'agent2'], - }) - .expect(200); + }); + await supertest + .post(`/api/fleet/agents/bulk_upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersion, + agents: ['agent1', 'agent2'], + }) + .expect(200); - const [agent1data, agent2data] = await Promise.all([ - supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), - supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), - ]); - expect(typeof agent1data.body.item.upgrade_started_at).to.be('string'); - expect(typeof agent2data.body.item.upgrade_started_at).to.be('undefined'); - }); + const [agent1data, agent2data] = await Promise.all([ + supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), + supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), + ]); + expect(typeof agent1data.body.item.upgrade_started_at).to.be('string'); + expect(typeof agent2data.body.item.upgrade_started_at).to.be('undefined'); + }); - it('should allow to upgrade multiple upgradeable agents by kuery', async () => { - const kibanaVersion = await kibanaServer.version.get(); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + it('should allow to upgrade multiple upgradeable agents by kuery', async () => { + const kibanaVersion = await kibanaServer.version.get(); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, }, - }, - }); - await es.update({ - id: 'agent2', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { - elastic: { - agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') }, + }); + await es.update({ + id: 'agent2', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { + elastic: { + agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') }, + }, }, }, }, - }, + }); + await supertest + .post(`/api/fleet/agents/bulk_upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: 'active:true', + version: kibanaVersion, + }) + .expect(200); + const [agent1data, agent2data] = await Promise.all([ + supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), + supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), + ]); + expect(typeof agent1data.body.item.upgrade_started_at).to.be('string'); + expect(typeof agent2data.body.item.upgrade_started_at).to.be('undefined'); }); - await supertest - .post(`/api/fleet/agents/bulk_upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - agents: 'active:true', - version: kibanaVersion, - }) - .expect(200); - const [agent1data, agent2data] = await Promise.all([ - supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), - supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), - ]); - expect(typeof agent1data.body.item.upgrade_started_at).to.be('string'); - expect(typeof agent2data.body.item.upgrade_started_at).to.be('undefined'); - }); - it('should not upgrade an unenrolling agent during bulk_upgrade', async () => { - const kibanaVersion = await kibanaServer.version.get(); - await supertest.post(`/api/fleet/agents/agent1/unenroll`).set('kbn-xsrf', 'xxx').send({ - force: true, - }); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + it('should not upgrade an unenrolling agent during bulk_upgrade', async () => { + const kibanaVersion = await kibanaServer.version.get(); + await supertest.post(`/api/fleet/agents/agent1/unenroll`).set('kbn-xsrf', 'xxx').send({ + force: true, + }); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, }, - }, - }); - await es.update({ - id: 'agent2', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }); + await es.update({ + id: 'agent2', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, }, - }, + }); + await supertest + .post(`/api/fleet/agents/bulk_upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: ['agent1', 'agent2'], + version: kibanaVersion, + }); + const [agent1data, agent2data] = await Promise.all([ + supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), + supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), + ]); + expect(typeof agent1data.body.item.upgrade_started_at).to.be('undefined'); + expect(typeof agent2data.body.item.upgrade_started_at).to.be('string'); }); - await supertest - .post(`/api/fleet/agents/bulk_upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - agents: ['agent1', 'agent2'], - version: kibanaVersion, - }); - const [agent1data, agent2data] = await Promise.all([ - supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), - supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), - ]); - expect(typeof agent1data.body.item.upgrade_started_at).to.be('undefined'); - expect(typeof agent2data.body.item.upgrade_started_at).to.be('string'); - }); - it('should not upgrade an unenrolled agent during bulk_upgrade', async () => { - const kibanaVersion = await kibanaServer.version.get(); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - unenrolled_at: new Date().toISOString(), - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + it('should not upgrade an unenrolled agent during bulk_upgrade', async () => { + const kibanaVersion = await kibanaServer.version.get(); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + unenrolled_at: new Date().toISOString(), + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, }, - }, - }); - await es.update({ - id: 'agent2', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { - elastic: { agent: { upgradeable: true, version: '0.0.0' } }, + }); + await es.update({ + id: 'agent2', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { + elastic: { agent: { upgradeable: true, version: '0.0.0' } }, + }, }, }, - }, + }); + await supertest + .post(`/api/fleet/agents/bulk_upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: ['agent1', 'agent2'], + version: kibanaVersion, + }); + const [agent1data, agent2data] = await Promise.all([ + supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), + supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), + ]); + expect(typeof agent1data.body.item.upgrade_started_at).to.be('undefined'); + expect(typeof agent2data.body.item.upgrade_started_at).to.be('string'); }); - await supertest - .post(`/api/fleet/agents/bulk_upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - agents: ['agent1', 'agent2'], - version: kibanaVersion, - }); - const [agent1data, agent2data] = await Promise.all([ - supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), - supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), - ]); - expect(typeof agent1data.body.item.upgrade_started_at).to.be('undefined'); - expect(typeof agent2data.body.item.upgrade_started_at).to.be('string'); - }); - it('should not upgrade an non upgradeable agent during bulk_upgrade', async () => { - const kibanaVersion = await kibanaServer.version.get(); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + it('should not upgrade an non upgradeable agent during bulk_upgrade', async () => { + const kibanaVersion = await kibanaServer.version.get(); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, }, - }, - }); - await es.update({ - id: 'agent2', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { - elastic: { - agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') }, + }); + await es.update({ + id: 'agent2', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { + elastic: { + agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') }, + }, }, }, }, - }, - }); - await es.update({ - id: 'agent3', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: false, version: '0.0.0' } } }, + }); + await es.update({ + id: 'agent3', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: false, version: '0.0.0' } } }, + }, }, - }, + }); + await supertest + .post(`/api/fleet/agents/bulk_upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: ['agent1', 'agent2', 'agent3'], + version: kibanaVersion, + }); + const [agent1data, agent2data, agent3data] = await Promise.all([ + supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), + supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), + supertest.get(`/api/fleet/agents/agent3`).set('kbn-xsrf', 'xxx'), + ]); + expect(typeof agent1data.body.item.upgrade_started_at).to.be('string'); + expect(typeof agent2data.body.item.upgrade_started_at).to.be('undefined'); + expect(typeof agent3data.body.item.upgrade_started_at).to.be('undefined'); }); - await supertest - .post(`/api/fleet/agents/bulk_upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - agents: ['agent1', 'agent2', 'agent3'], - version: kibanaVersion, - }); - const [agent1data, agent2data, agent3data] = await Promise.all([ - supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), - supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), - supertest.get(`/api/fleet/agents/agent3`).set('kbn-xsrf', 'xxx'), - ]); - expect(typeof agent1data.body.item.upgrade_started_at).to.be('string'); - expect(typeof agent2data.body.item.upgrade_started_at).to.be('undefined'); - expect(typeof agent3data.body.item.upgrade_started_at).to.be('undefined'); - }); - it('should upgrade a non upgradeable agent during bulk_upgrade with force flag', async () => { - const kibanaVersion = await kibanaServer.version.get(); - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + it('should upgrade a non upgradeable agent during bulk_upgrade with force flag', async () => { + const kibanaVersion = await kibanaServer.version.get(); + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, }, - }, - }); - await es.update({ - id: 'agent2', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { - elastic: { - agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') }, + }); + await es.update({ + id: 'agent2', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { + elastic: { + agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') }, + }, }, }, }, - }, - }); - await es.update({ - id: 'agent3', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: false, version: '0.0.0' } } }, - }, - }, - }); - await supertest - .post(`/api/fleet/agents/bulk_upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - agents: ['agent1', 'agent2', 'agent3'], - version: kibanaVersion, - force: true, }); - const [agent1data, agent2data, agent3data] = await Promise.all([ - supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), - supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), - supertest.get(`/api/fleet/agents/agent3`).set('kbn-xsrf', 'xxx'), - ]); - expect(typeof agent1data.body.item.upgrade_started_at).to.be('string'); - expect(typeof agent2data.body.item.upgrade_started_at).to.be('string'); - expect(typeof agent3data.body.item.upgrade_started_at).to.be('string'); - }); - it('should respond 400 if trying to bulk upgrade to a version that does not match installed kibana version', async () => { - await es.update({ - id: 'agent1', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + await es.update({ + id: 'agent3', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: false, version: '0.0.0' } } }, + }, }, - }, + }); + await supertest + .post(`/api/fleet/agents/bulk_upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: ['agent1', 'agent2', 'agent3'], + version: kibanaVersion, + force: true, + }); + const [agent1data, agent2data, agent3data] = await Promise.all([ + supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'), + supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), + supertest.get(`/api/fleet/agents/agent3`).set('kbn-xsrf', 'xxx'), + ]); + expect(typeof agent1data.body.item.upgrade_started_at).to.be('string'); + expect(typeof agent2data.body.item.upgrade_started_at).to.be('string'); + expect(typeof agent3data.body.item.upgrade_started_at).to.be('string'); }); - await es.update({ - id: 'agent2', - refresh: 'wait_for', - index: AGENTS_INDEX, - body: { - doc: { - local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + it('should respond 400 if trying to bulk upgrade to a version that does not match installed kibana version', async () => { + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, + }, + }); + await es.update({ + id: 'agent2', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, }, - }, + }); + await supertest + .post(`/api/fleet/agents/bulk_upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: ['agent1', 'agent2'], + version: '1.0.0', + force: true, + }) + .expect(400); }); - await supertest - .post(`/api/fleet/agents/bulk_upgrade`) - .set('kbn-xsrf', 'xxx') - .send({ - agents: ['agent1', 'agent2'], - version: '1.0.0', - force: true, - }) - .expect(400); - }); - describe('fleet upgrade agent(s) in a managed policy', function () { - it('should respond 400 to bulk upgrade and not update the agent SOs', async () => { + it('enrolled in a managed policy bulk upgrade should respond with 400 and not update the agent SOs', async () => { // update enrolled policy to managed await supertest.put(`/api/fleet/agent_policies/policy1`).set('kbn-xsrf', 'xxxx').send({ name: 'Test policy', @@ -560,7 +593,7 @@ export default function (providerContext: FtrProviderContext) { expect(typeof agent2data.body.item.upgrade_started_at).to.be('undefined'); }); - it('should respond 400 to upgrade and not update the agent SOs', async () => { + it('enrolled in a managed policy bulk upgrade with force flag should respond with 200 and update the agent SOs', async () => { // update enrolled policy to managed await supertest.put(`/api/fleet/agent_policies/policy1`).set('kbn-xsrf', 'xxxx').send({ name: 'Test policy', @@ -579,16 +612,37 @@ export default function (providerContext: FtrProviderContext) { }, }, }); + await es.update({ + id: 'agent2', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { + elastic: { + agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') }, + }, + }, + }, + }, + }); // attempt to upgrade agent in managed policy const { body } = await supertest - .post(`/api/fleet/agents/agent1/upgrade`) + .post(`/api/fleet/agents/bulk_upgrade`) .set('kbn-xsrf', 'xxx') - .send({ version: kibanaVersion }) - .expect(400); - expect(body.message).to.contain('Cannot upgrade agent agent1 in managed policy policy1'); + .send({ + version: kibanaVersion, + agents: ['agent1', 'agent2'], + force: true, + }); + expect(body).to.eql({}); - const agent1data = await supertest.get(`/api/fleet/agents/agent1`); - expect(typeof agent1data.body.item.upgrade_started_at).to.be('undefined'); + const [agent1data, agent2data] = await Promise.all([ + supertest.get(`/api/fleet/agents/agent1`), + supertest.get(`/api/fleet/agents/agent2`), + ]); + expect(typeof agent1data.body.item.upgrade_started_at).to.be('string'); + expect(typeof agent2data.body.item.upgrade_started_at).to.be('string'); }); }); }); From 76b55207f7ea9144d79c9a13fa7b4f6dfb603cd7 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 23 Mar 2021 11:24:25 -0400 Subject: [PATCH 25/93] [Fleet] Fix agent status count (#95099) --- .../fleet/server/services/agents/crud.ts | 5 +- .../fleet/server/services/agents/helpers.ts | 2 + .../apis/agents/status.ts | 86 +++++++++++++++++++ .../test/fleet_api_integration/apis/index.js | 1 + 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 x-pack/test/fleet_api_integration/apis/agents/status.ts diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 52a6b98bd0c418..22e9f559c56b8a 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -9,6 +9,7 @@ import Boom from '@hapi/boom'; import type { SearchResponse, MGetResponse, GetResponse } from 'elasticsearch'; import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; +import type { ESSearchResponse } from '../../../../../../typings/elasticsearch'; import type { AgentSOAttributes, Agent, ListWithKuery } from '../../types'; import { appContextService, agentPolicyService } from '../../services'; import type { FleetServerAgent } from '../../../common'; @@ -118,7 +119,7 @@ export async function getAgentsByKuery( const kueryNode = _joinFilters(filters); const body = kueryNode ? { query: esKuery.toElasticsearchQuery(kueryNode) } : {}; - const res = await esClient.search>({ + const res = await esClient.search>({ index: AGENTS_INDEX, from: (page - 1) * perPage, size: perPage, @@ -138,7 +139,7 @@ export async function getAgentsByKuery( return { agents, - total: agents.length, + total: res.body.hits.total.value, page, perPage, }; diff --git a/x-pack/plugins/fleet/server/services/agents/helpers.ts b/x-pack/plugins/fleet/server/services/agents/helpers.ts index bcc065badcd50a..89f37a01a6b002 100644 --- a/x-pack/plugins/fleet/server/services/agents/helpers.ts +++ b/x-pack/plugins/fleet/server/services/agents/helpers.ts @@ -7,10 +7,12 @@ import type { GetResponse, SearchResponse } from 'elasticsearch'; +import type { ESSearchHit } from '../../../../../../typings/elasticsearch'; import type { Agent, AgentSOAttributes, FleetServerAgent } from '../../types'; type FleetServerAgentESResponse = | GetResponse + | ESSearchHit | SearchResponse['hits']['hits'][0]; export function searchHitToAgent(hit: FleetServerAgentESResponse): Agent { diff --git a/x-pack/test/fleet_api_integration/apis/agents/status.ts b/x-pack/test/fleet_api_integration/apis/agents/status.ts new file mode 100644 index 00000000000000..3245b9a459fb18 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/agents/status.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { AGENTS_INDEX } from '../../../../plugins/fleet/common'; + +export default function ({ getService }: FtrProviderContext) { + const es = getService('es'); + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('fleet_agents_status', () => { + before(async () => { + await esArchiver.loadIfNeeded('fleet/agents'); + // 2 agents online + await es.update({ + id: 'agent1', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + last_checkin: new Date().toISOString(), + }, + }, + }); + await es.update({ + id: 'agent2', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + last_checkin: new Date().toISOString(), + }, + }, + }); + // 1 agents offline + await es.update({ + id: 'agent3', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + last_checkin: new Date(Date.now() - 1000 * 60 * 60 * 60 * 10).toISOString(), + }, + }, + }); + // 1 agent upgrading + await es.update({ + id: 'agent4', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + last_checkin: new Date().toISOString(), + upgrade_started_at: new Date().toISOString(), + }, + }, + }); + }); + after(async () => { + await esArchiver.unload('fleet/agents'); + }); + + it('should return the status of agents', async () => { + const { body: apiResponse } = await supertest.get(`/api/fleet/agent-status`).expect(200); + + expect(apiResponse).to.eql({ + results: { + events: 0, + total: 4, + online: 2, + error: 0, + offline: 1, + updating: 1, + other: 1, + }, + }); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/index.js b/x-pack/test/fleet_api_integration/apis/index.js index 948985c52bd1b9..3d7ee3686b575c 100644 --- a/x-pack/test/fleet_api_integration/apis/index.js +++ b/x-pack/test/fleet_api_integration/apis/index.js @@ -25,6 +25,7 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./agents/actions')); loadTestFile(require.resolve('./agents/upgrade')); loadTestFile(require.resolve('./agents/reassign')); + loadTestFile(require.resolve('./agents/status')); // Enrollment API keys loadTestFile(require.resolve('./enrollment_api_keys/crud')); From 6a858412898d29ce23efc558b83d35c14ad7fc30 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 23 Mar 2021 16:53:35 +0100 Subject: [PATCH 26/93] [Discover][EuiDataGrid] Add document navigation to flyout (#94439) - allows the user to navigate to the previous and next document of the list in the document flyout Co-authored-by: Ryan Keairns --- .../discover_grid/discover_grid.scss | 11 ++ .../discover_grid/discover_grid.tsx | 2 + .../discover_grid_flyout.test.tsx | 143 +++++++++++++----- .../discover_grid/discover_grid_flyout.tsx | 78 ++++++++-- 4 files changed, 184 insertions(+), 50 deletions(-) diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss b/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss index 4754c1700f28de..5bb6c01da5ad63 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss @@ -40,6 +40,10 @@ white-space: nowrap; } +.dscTable__flyoutDocumentNavigation { + justify-content: flex-end; +} + // We only truncate if the cell is not a control column. .euiDataGridHeader { .euiDataGridHeaderCell__content { @@ -78,3 +82,10 @@ .dscDiscoverGrid__descriptionListDescription { word-break: normal !important; } + +@include euiBreakpoint('xs', 's', 'm') { + // EUI issue to hide 'of' text https://github.com/elastic/eui/issues/4654 + .dscTable__flyoutDocumentNavigation .euiPagination__compressedText { + display: none; + } +} diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx index 380b4dc5e8e9a0..20d7d80b498a8a 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -324,12 +324,14 @@ export const DiscoverGrid = ({ setExpandedDoc(undefined)} + setExpandedDoc={setExpandedDoc} services={services} /> )} diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx index b63aca85b1ec9b..54620dff1f63f0 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx @@ -21,51 +21,41 @@ import { indexPatternWithTimefieldMock } from '../../../__mocks__/index_pattern_ describe('Discover flyout', function () { setDocViewsRegistry(new DocViewsRegistry()); - it('should be rendered correctly using an index pattern without timefield', async () => { + const getProps = () => { const onClose = jest.fn(); - const component = mountWithIntl( - path, - } as unknown) as DiscoverServices - } - /> - ); + const services = ({ + filterManager: createFilterManagerMock(), + addBasePath: (path: string) => `/base${path}`, + } as unknown) as DiscoverServices; + + return { + columns: ['date'], + indexPattern: indexPatternMock, + hit: esHits[0], + hits: esHits, + onAddColumn: jest.fn(), + onClose, + onFilter: jest.fn(), + onRemoveColumn: jest.fn(), + services, + setExpandedDoc: jest.fn(), + }; + }; + + it('should be rendered correctly using an index pattern without timefield', async () => { + const props = getProps(); + const component = mountWithIntl(); const url = findTestSubject(component, 'docTableRowAction').prop('href'); - expect(url).toMatchInlineSnapshot(`"#/doc/the-index-pattern-id/i?id=1"`); + expect(url).toMatchInlineSnapshot(`"/base#/doc/the-index-pattern-id/i?id=1"`); findTestSubject(component, 'euiFlyoutCloseButton').simulate('click'); - expect(onClose).toHaveBeenCalled(); + expect(props.onClose).toHaveBeenCalled(); }); it('should be rendered correctly using an index pattern with timefield', async () => { - const onClose = jest.fn(); - const component = mountWithIntl( - `/base${path}`, - } as unknown) as DiscoverServices - } - /> - ); + const props = getProps(); + props.indexPattern = indexPatternWithTimefieldMock; + const component = mountWithIntl(); const actions = findTestSubject(component, 'docTableRowAction'); expect(actions.length).toBe(2); @@ -76,6 +66,81 @@ describe('Discover flyout', function () { `"/base/app/discover#/context/index-pattern-with-timefield-id/1?_g=(filters:!())&_a=(columns:!(date),filters:!())"` ); findTestSubject(component, 'euiFlyoutCloseButton').simulate('click'); - expect(onClose).toHaveBeenCalled(); + expect(props.onClose).toHaveBeenCalled(); + }); + + it('displays document navigation when there is more than 1 doc available', async () => { + const props = getProps(); + const component = mountWithIntl(); + const docNav = findTestSubject(component, 'dscDocNavigation'); + expect(docNav.length).toBeTruthy(); + }); + + it('displays no document navigation when there are 0 docs available', async () => { + const props = getProps(); + props.hits = []; + const component = mountWithIntl(); + const docNav = findTestSubject(component, 'dscDocNavigation'); + expect(docNav.length).toBeFalsy(); + }); + + it('displays no document navigation when the expanded doc is not part of the given docs', async () => { + // scenario: you've expanded a doc, and in the next request differed docs where fetched + const props = getProps(); + props.hits = [ + { + _index: 'new', + _id: '1', + _score: 1, + _type: '_doc', + _source: { date: '2020-20-01T12:12:12.123', message: 'test1', bytes: 20 }, + }, + { + _index: 'new', + _id: '2', + _score: 1, + _type: '_doc', + _source: { date: '2020-20-01T12:12:12.124', name: 'test2', extension: 'jpg' }, + }, + ]; + const component = mountWithIntl(); + const docNav = findTestSubject(component, 'dscDocNavigation'); + expect(docNav.length).toBeFalsy(); + }); + + it('allows you to navigate to the next doc, if expanded doc is the first', async () => { + // scenario: you've expanded a doc, and in the next request different docs where fetched + const props = getProps(); + const component = mountWithIntl(); + findTestSubject(component, 'pagination-button-next').simulate('click'); + // we selected 1, so we'd expect 2 + expect(props.setExpandedDoc.mock.calls[0][0]._id).toBe('2'); + }); + + it('doesnt allow you to navigate to the previous doc, if expanded doc is the first', async () => { + // scenario: you've expanded a doc, and in the next request differed docs where fetched + const props = getProps(); + const component = mountWithIntl(); + findTestSubject(component, 'pagination-button-previous').simulate('click'); + expect(props.setExpandedDoc).toHaveBeenCalledTimes(0); + }); + + it('doesnt allow you to navigate to the next doc, if expanded doc is the last', async () => { + // scenario: you've expanded a doc, and in the next request differed docs where fetched + const props = getProps(); + props.hit = props.hits[props.hits.length - 1]; + const component = mountWithIntl(); + findTestSubject(component, 'pagination-button-next').simulate('click'); + expect(props.setExpandedDoc).toHaveBeenCalledTimes(0); + }); + + it('allows you to navigate to the previous doc, if expanded doc is the last', async () => { + // scenario: you've expanded a doc, and in the next request differed docs where fetched + const props = getProps(); + props.hit = props.hits[props.hits.length - 1]; + const component = mountWithIntl(); + findTestSubject(component, 'pagination-button-previous').simulate('click'); + expect(props.setExpandedDoc).toHaveBeenCalledTimes(1); + expect(props.setExpandedDoc.mock.calls[0][0]._id).toBe('4'); }); }); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx index 5994892ca2d40a..87b9c6243abd84 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useMemo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, @@ -19,6 +19,8 @@ import { EuiText, EuiSpacer, EuiPortal, + EuiPagination, + EuiHideFor, } from '@elastic/eui'; import { DocViewer } from '../doc_viewer/doc_viewer'; import { IndexPattern } from '../../../kibana_services'; @@ -29,19 +31,34 @@ import { getContextUrl } from '../../helpers/get_context_url'; interface Props { columns: string[]; hit: ElasticSearchHit; + hits?: ElasticSearchHit[]; indexPattern: IndexPattern; onAddColumn: (column: string) => void; onClose: () => void; onFilter: DocViewFilterFn; onRemoveColumn: (column: string) => void; services: DiscoverServices; + setExpandedDoc: (doc: ElasticSearchHit) => void; } +type ElasticSearchHitWithRouting = ElasticSearchHit & { _routing?: string }; + +function getDocFingerprintId(doc: ElasticSearchHitWithRouting) { + const routing = doc._routing || ''; + return [doc._index, doc._id, routing].join('||'); +} + +function getIndexByDocId(hits: ElasticSearchHit[], id: string) { + return hits.findIndex((h) => { + return getDocFingerprintId(h) === id; + }); +} /** * Flyout displaying an expanded Elasticsearch document */ export function DiscoverGridFlyout({ hit, + hits, indexPattern, columns, onFilter, @@ -49,7 +66,27 @@ export function DiscoverGridFlyout({ onRemoveColumn, onAddColumn, services, + setExpandedDoc, }: Props) { + const pageCount = useMemo(() => (hits ? hits.length : 0), [hits]); + const activePage = useMemo(() => { + const id = getDocFingerprintId(hit); + if (!hits || pageCount <= 1) { + return -1; + } + + return getIndexByDocId(hits, id); + }, [hits, hit, pageCount]); + + const setPage = useCallback( + (pageIdx: number) => { + if (hits && hits[pageIdx]) { + setExpandedDoc(hits[pageIdx]); + } + }, + [hits, setExpandedDoc] + ); + return ( @@ -67,20 +104,23 @@ export function DiscoverGridFlyout({ - - - - - {i18n.translate('discover.grid.tableRow.viewText', { - defaultMessage: 'View:', - })} - - - + + + + + + {i18n.translate('discover.grid.tableRow.viewText', { + defaultMessage: 'View:', + })} + + + + )} + {activePage !== -1 && ( + + + + )} From aa60c383180edefa02d74815d50fedb0337b1229 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Tue, 23 Mar 2021 10:59:31 -0500 Subject: [PATCH 27/93] Fixes links to docs (#94984) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/core/public/doc_links/doc_links_service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index e187b7ea581bf7..6279d62d2c40e6 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -187,9 +187,9 @@ export class DocLinksService { }, visualize: { guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`, - timelionDeprecation: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/create-panels-with-timelion.html`, + timelionDeprecation: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/timelion.html`, lens: `${ELASTIC_WEBSITE_URL}what-is/kibana-lens`, - lensPanels: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html#create-panels-with-lens`, + lensPanels: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/lens.html`, maps: `${ELASTIC_WEBSITE_URL}maps`, }, observability: { From a158f9d771c63e145e9f7cb5fa7d445ae4cbf51a Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 23 Mar 2021 12:00:21 -0400 Subject: [PATCH 28/93] Remove duplicative RBAC tests (#95162) --- x-pack/test/functional/apps/security/index.js | 1 - .../functional/apps/security/rbac_phase1.js | 128 ------------------ 2 files changed, 129 deletions(-) delete mode 100644 x-pack/test/functional/apps/security/rbac_phase1.js diff --git a/x-pack/test/functional/apps/security/index.js b/x-pack/test/functional/apps/security/index.js index d0592f43dfa60c..188f49e3002560 100644 --- a/x-pack/test/functional/apps/security/index.js +++ b/x-pack/test/functional/apps/security/index.js @@ -15,7 +15,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./users')); loadTestFile(require.resolve('./secure_roles_perm')); loadTestFile(require.resolve('./field_level_security')); - loadTestFile(require.resolve('./rbac_phase1')); loadTestFile(require.resolve('./user_email')); loadTestFile(require.resolve('./role_mappings')); }); diff --git a/x-pack/test/functional/apps/security/rbac_phase1.js b/x-pack/test/functional/apps/security/rbac_phase1.js deleted file mode 100644 index 851a923ec77580..00000000000000 --- a/x-pack/test/functional/apps/security/rbac_phase1.js +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { keyBy } from 'lodash'; -export default function ({ getService, getPageObjects }) { - const PageObjects = getPageObjects([ - 'security', - 'settings', - 'common', - 'visualize', - 'timePicker', - 'visChart', - ]); - const log = getService('log'); - const esArchiver = getService('esArchiver'); - const browser = getService('browser'); - const kibanaServer = getService('kibanaServer'); - - describe('rbac ', function () { - before(async () => { - await browser.setWindowSize(1600, 1000); - log.debug('users'); - await esArchiver.loadIfNeeded('logstash_functional'); - log.debug('load kibana index with default index pattern'); - await esArchiver.load('security/discover'); - await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); - await PageObjects.settings.navigateTo(); - await PageObjects.security.clickElasticsearchRoles(); - await PageObjects.security.addRole('rbac_all', { - kibana: { - global: ['all'], - }, - elasticsearch: { - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - }, - }); - - await PageObjects.security.clickElasticsearchRoles(); - await PageObjects.security.addRole('rbac_read', { - kibana: { - global: ['read'], - }, - elasticsearch: { - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - }, - }); - log.debug('After Add user new: , userObj.userName'); - await PageObjects.security.createUser({ - username: 'kibanauser', - password: 'changeme', - confirm_password: 'changeme', - full_name: 'kibanafirst kibanalast', - email: 'kibanauser@myEmail.com', - save: true, - roles: ['rbac_all'], - }); - log.debug('After Add user: , userObj.userName'); - const users = keyBy(await PageObjects.security.getElasticsearchUsers(), 'username'); - log.debug('actualUsers = %j', users); - log.debug('roles: ', users.kibanauser.roles); - expect(users.kibanauser.roles).to.eql(['rbac_all']); - expect(users.kibanauser.fullname).to.eql('kibanafirst kibanalast'); - expect(users.kibanauser.reserved).to.be(false); - log.debug('After Add user new: , userObj.userName'); - await PageObjects.security.createUser({ - username: 'kibanareadonly', - password: 'changeme', - confirm_password: 'changeme', - full_name: 'kibanareadonlyFirst kibanareadonlyLast', - email: 'kibanareadonly@myEmail.com', - save: true, - roles: ['rbac_read'], - }); - log.debug('After Add user: , userObj.userName'); - const users1 = keyBy(await PageObjects.security.getElasticsearchUsers(), 'username'); - const user = users1.kibanareadonly; - log.debug('actualUsers = %j', users1); - log.debug('roles: ', user.roles); - expect(user.roles).to.eql(['rbac_read']); - expect(user.fullname).to.eql('kibanareadonlyFirst kibanareadonlyLast'); - expect(user.reserved).to.be(false); - await PageObjects.security.forceLogout(); - }); - - // this is to acertain that all role assigned to the user can perform actions like creating a Visualization - it('rbac all role can save a visualization', async function () { - const vizName1 = 'Visualization VerticalBarChart'; - - log.debug('log in as kibanauser with rbac_all role'); - await PageObjects.security.login('kibanauser', 'changeme'); - log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewAggBasedVisualization(); - log.debug('clickVerticalBarChart'); - await PageObjects.visualize.clickVerticalBarChart(); - await PageObjects.visualize.clickNewSearch(); - log.debug( - 'Set absolute time range from "' + - PageObjects.timePicker.defaultStartTime + - '" to "' + - PageObjects.timePicker.defaultEndTime + - '"' - ); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visChart.waitForVisualization(); - await PageObjects.visualize.saveVisualizationExpectSuccess(vizName1); - await PageObjects.security.forceLogout(); - }); - - after(async function () { - await PageObjects.security.forceLogout(); - }); - }); -} From b2433cdcd99abf15aef04887e7991fa5b686bb6a Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 23 Mar 2021 17:33:06 +0100 Subject: [PATCH 29/93] [Lens] Telemetry: Add missing operations (#95040) --- x-pack/plugins/lens/server/usage/schema.ts | 95 ++++++++++- .../schema/xpack_plugins.json | 154 ++++++++++++++++-- 2 files changed, 222 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/lens/server/usage/schema.ts b/x-pack/plugins/lens/server/usage/schema.ts index ba892f5ca994cc..158e62ee8cfd80 100644 --- a/x-pack/plugins/lens/server/usage/schema.ts +++ b/x-pack/plugins/lens/server/usage/schema.ts @@ -34,15 +34,92 @@ const eventsSchema: MakeSchemaFrom = { xy_change_layer_display: { type: 'long' }, xy_layer_removed: { type: 'long' }, xy_layer_added: { type: 'long' }, - indexpattern_dimension_operation_terms: { type: 'long' }, - indexpattern_dimension_operation_date_histogram: { type: 'long' }, - indexpattern_dimension_operation_avg: { type: 'long' }, - indexpattern_dimension_operation_min: { type: 'long' }, - indexpattern_dimension_operation_max: { type: 'long' }, - indexpattern_dimension_operation_sum: { type: 'long' }, - indexpattern_dimension_operation_count: { type: 'long' }, - indexpattern_dimension_operation_cardinality: { type: 'long' }, - indexpattern_dimension_operation_filters: { type: 'long' }, + indexpattern_dimension_operation_terms: { + type: 'long', + _meta: { + description: 'Number of times the top values function was selected', + }, + }, + indexpattern_dimension_operation_date_histogram: { + type: 'long', + _meta: { + description: 'Number of times the date histogram function was selected', + }, + }, + indexpattern_dimension_operation_avg: { + type: 'long', + _meta: { + description: 'Number of times the average function was selected', + }, + }, + indexpattern_dimension_operation_min: { + type: 'long', + _meta: { + description: 'Number of times the min function was selected', + }, + }, + indexpattern_dimension_operation_max: { + type: 'long', + _meta: { + description: 'Number of times the max function was selected', + }, + }, + indexpattern_dimension_operation_sum: { + type: 'long', + _meta: { + description: 'Number of times the sum function was selected', + }, + }, + indexpattern_dimension_operation_count: { + type: 'long', + _meta: { + description: 'Number of times the count function was selected', + }, + }, + indexpattern_dimension_operation_cardinality: { + type: 'long', + _meta: { + description: 'Number of times the cardinality function was selected', + }, + }, + indexpattern_dimension_operation_filters: { + type: 'long', + _meta: { + description: 'Number of times the filters function was selected', + }, + }, + indexpattern_dimension_operation_range: { + type: 'long', + _meta: { description: 'Number of times the range function was selected' }, + }, + indexpattern_dimension_operation_median: { + type: 'long', + _meta: { description: 'Number of times the median function was selected' }, + }, + indexpattern_dimension_operation_percentile: { + type: 'long', + _meta: { description: 'Number of times the percentile function was selected' }, + }, + indexpattern_dimension_operation_last_value: { + type: 'long', + _meta: { description: 'Number of times the last value function was selected' }, + }, + indexpattern_dimension_operation_cumulative_sum: { + type: 'long', + _meta: { description: 'Number of times the cumulative sum function was selected' }, + }, + indexpattern_dimension_operation_counter_rate: { + type: 'long', + _meta: { description: 'Number of times the counter rate function was selected' }, + }, + indexpattern_dimension_operation_derivative: { + type: 'long', + _meta: { description: 'Number of times the derivative function was selected' }, + }, + indexpattern_dimension_operation_moving_average: { + type: 'long', + _meta: { description: 'Number of times the moving average function was selected' }, + }, }; const suggestionEventsSchema: MakeSchemaFrom = { diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index bb9356014e7a34..81a7030fe0edd5 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -1949,31 +1949,90 @@ "type": "long" }, "indexpattern_dimension_operation_terms": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the top values function was selected" + } }, "indexpattern_dimension_operation_date_histogram": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the date histogram function was selected" + } }, "indexpattern_dimension_operation_avg": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the average function was selected" + } }, "indexpattern_dimension_operation_min": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the min function was selected" + } }, "indexpattern_dimension_operation_max": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the max function was selected" + } }, "indexpattern_dimension_operation_sum": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the sum function was selected" + } }, "indexpattern_dimension_operation_count": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the count function was selected" + } }, "indexpattern_dimension_operation_cardinality": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the cardinality function was selected" + } }, "indexpattern_dimension_operation_filters": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the filters function was selected" + } + }, + "indexpattern_dimension_operation_range": { + "type": "long", + "_meta": { "description": "Number of times the range function was selected" } + }, + "indexpattern_dimension_operation_median": { + "type": "long", + "_meta": { "description": "Number of times the median function was selected" } + }, + "indexpattern_dimension_operation_percentile": { + "type": "long", + "_meta": { "description": "Number of times the percentile function was selected" } + }, + "indexpattern_dimension_operation_last_value": { + "type": "long", + "_meta": { "description": "Number of times the last value function was selected" } + }, + "indexpattern_dimension_operation_cumulative_sum": { + "type": "long", + "_meta": { "description": "Number of times the cumulative sum function was selected" } + }, + "indexpattern_dimension_operation_counter_rate": { + "type": "long", + "_meta": { "description": "Number of times the counter rate function was selected" } + }, + "indexpattern_dimension_operation_derivative": { + "type": "long", + "_meta": { "description": "Number of times the derivative function was selected" } + }, + "indexpattern_dimension_operation_moving_average": { + "type": "long", + "_meta": { "description": "Number of times the moving average function was selected" } } } }, @@ -2055,31 +2114,90 @@ "type": "long" }, "indexpattern_dimension_operation_terms": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the top values function was selected" + } }, "indexpattern_dimension_operation_date_histogram": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the date histogram function was selected" + } }, "indexpattern_dimension_operation_avg": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the average function was selected" + } }, "indexpattern_dimension_operation_min": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the min function was selected" + } }, "indexpattern_dimension_operation_max": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the max function was selected" + } }, "indexpattern_dimension_operation_sum": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the sum function was selected" + } }, "indexpattern_dimension_operation_count": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the count function was selected" + } }, "indexpattern_dimension_operation_cardinality": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the cardinality function was selected" + } }, "indexpattern_dimension_operation_filters": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times the filters function was selected" + } + }, + "indexpattern_dimension_operation_range": { + "type": "long", + "_meta": { "description": "Number of times the range function was selected" } + }, + "indexpattern_dimension_operation_median": { + "type": "long", + "_meta": { "description": "Number of times the median function was selected" } + }, + "indexpattern_dimension_operation_percentile": { + "type": "long", + "_meta": { "description": "Number of times the percentile function was selected" } + }, + "indexpattern_dimension_operation_last_value": { + "type": "long", + "_meta": { "description": "Number of times the last value function was selected" } + }, + "indexpattern_dimension_operation_cumulative_sum": { + "type": "long", + "_meta": { "description": "Number of times the cumulative sum function was selected" } + }, + "indexpattern_dimension_operation_counter_rate": { + "type": "long", + "_meta": { "description": "Number of times the counter rate function was selected" } + }, + "indexpattern_dimension_operation_derivative": { + "type": "long", + "_meta": { "description": "Number of times the derivative function was selected" } + }, + "indexpattern_dimension_operation_moving_average": { + "type": "long", + "_meta": { "description": "Number of times the moving average function was selected" } } } }, From 134959d2244e20af422f72b002ffd0cc68ae90e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 23 Mar 2021 12:42:36 -0400 Subject: [PATCH 30/93] [APM] The Observability Getting Started page infinitely redirects, breaking browser's back button (#95083) * fixing browser back button * fixing test --- x-pack/plugins/observability/public/pages/home/index.test.tsx | 2 +- x-pack/plugins/observability/public/pages/home/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/home/index.test.tsx b/x-pack/plugins/observability/public/pages/home/index.test.tsx index 755757ea9dc879..a2c784cb4b2de9 100644 --- a/x-pack/plugins/observability/public/pages/home/index.test.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.test.tsx @@ -14,7 +14,7 @@ import { HomePage } from './'; const mockHistoryPush = jest.fn(); jest.mock('react-router-dom', () => ({ useHistory: () => ({ - push: mockHistoryPush, + replace: mockHistoryPush, }), })); diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index e5d22fac918ad3..9e7fe99e3f7802 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -16,9 +16,9 @@ export function HomePage() { useEffect(() => { if (hasAnyData === true) { - history.push({ pathname: '/overview' }); + history.replace({ pathname: '/overview' }); } else if (hasAnyData === false && isAllRequestsComplete === true) { - history.push({ pathname: '/landing' }); + history.replace({ pathname: '/landing' }); } }, [hasAnyData, isAllRequestsComplete, history]); From adeda3991d2542155fe40f9c127b7544d92349e5 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 23 Mar 2021 18:08:43 +0100 Subject: [PATCH 31/93] Compress color maps (#94297) --- .../public/static/color_maps/color_maps.ts | 3012 +---------------- .../vislib/visualizations/gauge_chart.test.js | 8 +- 2 files changed, 17 insertions(+), 3003 deletions(-) diff --git a/src/plugins/charts/public/static/color_maps/color_maps.ts b/src/plugins/charts/public/static/color_maps/color_maps.ts index fed0ae684e8417..22b200871ff70b 100644 --- a/src/plugins/charts/public/static/color_maps/color_maps.ts +++ b/src/plugins/charts/public/static/color_maps/color_maps.ts @@ -40,518 +40,16 @@ export const vislibColorMaps: ColorMap = { defaultMessage: 'Blues', }), value: [ - [0.0, [0.969, 0.984, 1.0]], - [0.002, [0.969, 0.984, 1.0]], - [0.004, [0.966, 0.982, 0.999]], - [0.006, [0.966, 0.982, 0.999]], - [0.008, [0.962, 0.98, 0.998]], - [0.01, [0.962, 0.98, 0.998]], - [0.012, [0.959, 0.978, 0.997]], - [0.014, [0.959, 0.978, 0.997]], - [0.016, [0.956, 0.976, 0.996]], - [0.018, [0.956, 0.976, 0.996]], - [0.02, [0.953, 0.974, 0.995]], - [0.022, [0.953, 0.974, 0.995]], - [0.023, [0.95, 0.973, 0.994]], - [0.025, [0.95, 0.973, 0.994]], - [0.027, [0.947, 0.971, 0.993]], - [0.029, [0.947, 0.971, 0.993]], - [0.031, [0.944, 0.969, 0.992]], - [0.033, [0.944, 0.969, 0.992]], - [0.035, [0.941, 0.967, 0.991]], - [0.037, [0.941, 0.967, 0.991]], - [0.039, [0.938, 0.965, 0.99]], - [0.041, [0.938, 0.965, 0.99]], - [0.043, [0.935, 0.963, 0.989]], - [0.045, [0.935, 0.963, 0.989]], - [0.047, [0.932, 0.961, 0.988]], - [0.049, [0.932, 0.961, 0.988]], - [0.051, [0.929, 0.959, 0.987]], - [0.053, [0.929, 0.959, 0.987]], - [0.055, [0.926, 0.957, 0.986]], - [0.057, [0.926, 0.957, 0.986]], - [0.059, [0.922, 0.955, 0.985]], - [0.061, [0.922, 0.955, 0.985]], - [0.063, [0.919, 0.953, 0.984]], - [0.065, [0.919, 0.953, 0.984]], - [0.067, [0.916, 0.951, 0.983]], - [0.068, [0.916, 0.951, 0.983]], - [0.07, [0.913, 0.949, 0.982]], - [0.072, [0.913, 0.949, 0.982]], - [0.074, [0.91, 0.947, 0.981]], - [0.076, [0.91, 0.947, 0.981]], - [0.078, [0.907, 0.945, 0.98]], - [0.08, [0.907, 0.945, 0.98]], - [0.082, [0.904, 0.943, 0.979]], - [0.084, [0.904, 0.943, 0.979]], - [0.086, [0.901, 0.941, 0.978]], - [0.088, [0.901, 0.941, 0.978]], - [0.09, [0.898, 0.939, 0.977]], - [0.092, [0.898, 0.939, 0.977]], - [0.094, [0.895, 0.937, 0.976]], - [0.096, [0.895, 0.937, 0.976]], - [0.098, [0.892, 0.935, 0.975]], - [0.1, [0.892, 0.935, 0.975]], - [0.102, [0.889, 0.933, 0.974]], - [0.104, [0.889, 0.933, 0.974]], - [0.106, [0.886, 0.931, 0.973]], - [0.108, [0.886, 0.931, 0.973]], - [0.11, [0.883, 0.929, 0.972]], - [0.112, [0.883, 0.929, 0.972]], + [0, [0.969, 0.984, 1]], [0.114, [0.879, 0.927, 0.971]], - [0.115, [0.879, 0.927, 0.971]], - [0.117, [0.876, 0.925, 0.97]], - [0.119, [0.876, 0.925, 0.97]], - [0.121, [0.873, 0.923, 0.969]], - [0.123, [0.873, 0.923, 0.969]], - [0.125, [0.87, 0.921, 0.969]], - [0.127, [0.87, 0.921, 0.969]], - [0.129, [0.867, 0.919, 0.968]], - [0.131, [0.867, 0.919, 0.968]], - [0.133, [0.864, 0.917, 0.967]], - [0.135, [0.864, 0.917, 0.967]], - [0.137, [0.861, 0.915, 0.966]], - [0.139, [0.861, 0.915, 0.966]], - [0.141, [0.858, 0.913, 0.965]], - [0.143, [0.858, 0.913, 0.965]], - [0.145, [0.855, 0.911, 0.964]], - [0.147, [0.855, 0.911, 0.964]], - [0.149, [0.853, 0.91, 0.963]], - [0.151, [0.853, 0.91, 0.963]], - [0.153, [0.85, 0.908, 0.962]], - [0.155, [0.85, 0.908, 0.962]], - [0.157, [0.847, 0.906, 0.961]], - [0.159, [0.847, 0.906, 0.961]], - [0.16, [0.844, 0.904, 0.96]], - [0.162, [0.844, 0.904, 0.96]], - [0.164, [0.841, 0.902, 0.959]], - [0.166, [0.841, 0.902, 0.959]], - [0.168, [0.838, 0.9, 0.958]], - [0.17, [0.838, 0.9, 0.958]], - [0.172, [0.835, 0.898, 0.957]], - [0.174, [0.835, 0.898, 0.957]], - [0.176, [0.832, 0.896, 0.956]], - [0.178, [0.832, 0.896, 0.956]], - [0.18, [0.829, 0.894, 0.955]], - [0.182, [0.829, 0.894, 0.955]], - [0.184, [0.826, 0.892, 0.954]], - [0.186, [0.826, 0.892, 0.954]], - [0.188, [0.823, 0.89, 0.953]], - [0.19, [0.823, 0.89, 0.953]], - [0.192, [0.82, 0.888, 0.952]], - [0.194, [0.82, 0.888, 0.952]], - [0.196, [0.817, 0.886, 0.951]], - [0.198, [0.817, 0.886, 0.951]], - [0.2, [0.814, 0.884, 0.95]], - [0.202, [0.814, 0.884, 0.95]], - [0.204, [0.811, 0.882, 0.949]], - [0.205, [0.811, 0.882, 0.949]], - [0.207, [0.808, 0.88, 0.948]], - [0.209, [0.808, 0.88, 0.948]], - [0.211, [0.805, 0.878, 0.947]], - [0.213, [0.805, 0.878, 0.947]], - [0.215, [0.802, 0.876, 0.946]], - [0.217, [0.802, 0.876, 0.946]], - [0.219, [0.799, 0.874, 0.945]], - [0.221, [0.799, 0.874, 0.945]], - [0.223, [0.796, 0.872, 0.944]], - [0.225, [0.796, 0.872, 0.944]], [0.227, [0.793, 0.87, 0.943]], - [0.229, [0.793, 0.87, 0.943]], - [0.231, [0.79, 0.868, 0.942]], - [0.233, [0.79, 0.868, 0.942]], - [0.235, [0.788, 0.866, 0.941]], - [0.237, [0.788, 0.866, 0.941]], - [0.239, [0.785, 0.864, 0.94]], - [0.241, [0.785, 0.864, 0.94]], - [0.243, [0.782, 0.862, 0.939]], - [0.245, [0.782, 0.862, 0.939]], - [0.247, [0.779, 0.86, 0.938]], - [0.249, [0.779, 0.86, 0.938]], - [0.25, [0.775, 0.858, 0.937]], - [0.252, [0.775, 0.858, 0.937]], - [0.254, [0.77, 0.856, 0.935]], - [0.256, [0.77, 0.856, 0.935]], - [0.258, [0.765, 0.854, 0.933]], - [0.26, [0.765, 0.854, 0.933]], - [0.262, [0.76, 0.852, 0.932]], - [0.264, [0.76, 0.852, 0.932]], - [0.266, [0.756, 0.85, 0.93]], - [0.268, [0.756, 0.85, 0.93]], - [0.27, [0.751, 0.848, 0.928]], - [0.272, [0.751, 0.848, 0.928]], - [0.274, [0.746, 0.846, 0.926]], - [0.276, [0.746, 0.846, 0.926]], - [0.278, [0.741, 0.844, 0.925]], - [0.28, [0.741, 0.844, 0.925]], - [0.282, [0.736, 0.842, 0.923]], - [0.284, [0.736, 0.842, 0.923]], - [0.286, [0.731, 0.839, 0.921]], - [0.288, [0.731, 0.839, 0.921]], - [0.29, [0.726, 0.837, 0.92]], - [0.292, [0.726, 0.837, 0.92]], - [0.294, [0.721, 0.835, 0.918]], - [0.295, [0.721, 0.835, 0.918]], - [0.297, [0.716, 0.833, 0.916]], - [0.299, [0.716, 0.833, 0.916]], - [0.301, [0.711, 0.831, 0.914]], - [0.303, [0.711, 0.831, 0.914]], - [0.305, [0.706, 0.829, 0.913]], - [0.307, [0.706, 0.829, 0.913]], - [0.309, [0.701, 0.827, 0.911]], - [0.311, [0.701, 0.827, 0.911]], - [0.313, [0.697, 0.825, 0.909]], - [0.315, [0.697, 0.825, 0.909]], - [0.317, [0.692, 0.823, 0.908]], - [0.319, [0.692, 0.823, 0.908]], - [0.321, [0.687, 0.821, 0.906]], - [0.323, [0.687, 0.821, 0.906]], - [0.325, [0.682, 0.819, 0.904]], - [0.327, [0.682, 0.819, 0.904]], - [0.329, [0.677, 0.816, 0.902]], - [0.331, [0.677, 0.816, 0.902]], - [0.333, [0.672, 0.814, 0.901]], - [0.335, [0.672, 0.814, 0.901]], - [0.337, [0.667, 0.812, 0.899]], - [0.339, [0.667, 0.812, 0.899]], [0.341, [0.662, 0.81, 0.897]], - [0.342, [0.662, 0.81, 0.897]], - [0.344, [0.657, 0.808, 0.895]], - [0.346, [0.657, 0.808, 0.895]], - [0.348, [0.652, 0.806, 0.894]], - [0.35, [0.652, 0.806, 0.894]], - [0.352, [0.647, 0.804, 0.892]], - [0.354, [0.647, 0.804, 0.892]], - [0.356, [0.642, 0.802, 0.89]], - [0.358, [0.642, 0.802, 0.89]], - [0.36, [0.637, 0.8, 0.889]], - [0.362, [0.637, 0.8, 0.889]], - [0.364, [0.633, 0.798, 0.887]], - [0.366, [0.633, 0.798, 0.887]], - [0.368, [0.628, 0.796, 0.885]], - [0.37, [0.628, 0.796, 0.885]], - [0.372, [0.623, 0.793, 0.883]], - [0.374, [0.623, 0.793, 0.883]], - [0.376, [0.617, 0.791, 0.882]], - [0.378, [0.617, 0.791, 0.882]], - [0.38, [0.611, 0.787, 0.88]], - [0.382, [0.611, 0.787, 0.88]], - [0.384, [0.605, 0.784, 0.879]], - [0.386, [0.605, 0.784, 0.879]], - [0.387, [0.598, 0.781, 0.878]], - [0.389, [0.598, 0.781, 0.878]], - [0.391, [0.592, 0.777, 0.876]], - [0.393, [0.592, 0.777, 0.876]], - [0.395, [0.586, 0.774, 0.875]], - [0.397, [0.586, 0.774, 0.875]], - [0.399, [0.58, 0.77, 0.874]], - [0.401, [0.58, 0.77, 0.874]], - [0.403, [0.573, 0.767, 0.872]], - [0.405, [0.573, 0.767, 0.872]], - [0.407, [0.567, 0.763, 0.871]], - [0.409, [0.567, 0.763, 0.871]], - [0.411, [0.561, 0.76, 0.87]], - [0.413, [0.561, 0.76, 0.87]], - [0.415, [0.555, 0.756, 0.868]], - [0.417, [0.555, 0.756, 0.868]], - [0.419, [0.548, 0.753, 0.867]], - [0.421, [0.548, 0.753, 0.867]], - [0.423, [0.542, 0.75, 0.866]], - [0.425, [0.542, 0.75, 0.866]], - [0.427, [0.536, 0.746, 0.864]], - [0.429, [0.536, 0.746, 0.864]], - [0.431, [0.529, 0.743, 0.863]], - [0.432, [0.529, 0.743, 0.863]], - [0.434, [0.523, 0.739, 0.862]], - [0.436, [0.523, 0.739, 0.862]], - [0.438, [0.517, 0.736, 0.86]], - [0.44, [0.517, 0.736, 0.86]], - [0.442, [0.511, 0.732, 0.859]], - [0.444, [0.511, 0.732, 0.859]], - [0.446, [0.504, 0.729, 0.857]], - [0.448, [0.504, 0.729, 0.857]], - [0.45, [0.498, 0.725, 0.856]], - [0.452, [0.498, 0.725, 0.856]], [0.454, [0.492, 0.722, 0.855]], - [0.456, [0.492, 0.722, 0.855]], - [0.458, [0.485, 0.719, 0.853]], - [0.46, [0.485, 0.719, 0.853]], - [0.462, [0.479, 0.715, 0.852]], - [0.464, [0.479, 0.715, 0.852]], - [0.466, [0.473, 0.712, 0.851]], - [0.468, [0.473, 0.712, 0.851]], - [0.47, [0.467, 0.708, 0.849]], - [0.472, [0.467, 0.708, 0.849]], - [0.474, [0.46, 0.705, 0.848]], - [0.476, [0.46, 0.705, 0.848]], - [0.477, [0.454, 0.701, 0.847]], - [0.479, [0.454, 0.701, 0.847]], - [0.481, [0.448, 0.698, 0.845]], - [0.483, [0.448, 0.698, 0.845]], - [0.485, [0.442, 0.694, 0.844]], - [0.487, [0.442, 0.694, 0.844]], - [0.489, [0.435, 0.691, 0.843]], - [0.491, [0.435, 0.691, 0.843]], - [0.493, [0.429, 0.688, 0.841]], - [0.495, [0.429, 0.688, 0.841]], - [0.497, [0.423, 0.684, 0.84]], - [0.499, [0.423, 0.684, 0.84]], - [0.501, [0.417, 0.681, 0.838]], - [0.503, [0.417, 0.681, 0.838]], - [0.505, [0.412, 0.677, 0.836]], - [0.507, [0.412, 0.677, 0.836]], - [0.509, [0.407, 0.674, 0.834]], - [0.511, [0.407, 0.674, 0.834]], - [0.513, [0.402, 0.67, 0.832]], - [0.515, [0.402, 0.67, 0.832]], - [0.517, [0.397, 0.667, 0.83]], - [0.519, [0.397, 0.667, 0.83]], - [0.521, [0.392, 0.663, 0.828]], - [0.523, [0.392, 0.663, 0.828]], - [0.524, [0.387, 0.66, 0.826]], - [0.526, [0.387, 0.66, 0.826]], - [0.528, [0.382, 0.657, 0.824]], - [0.53, [0.382, 0.657, 0.824]], - [0.532, [0.377, 0.653, 0.822]], - [0.534, [0.377, 0.653, 0.822]], - [0.536, [0.372, 0.65, 0.821]], - [0.538, [0.372, 0.65, 0.821]], - [0.54, [0.367, 0.646, 0.819]], - [0.542, [0.367, 0.646, 0.819]], - [0.544, [0.362, 0.643, 0.817]], - [0.546, [0.362, 0.643, 0.817]], - [0.548, [0.357, 0.639, 0.815]], - [0.55, [0.357, 0.639, 0.815]], - [0.552, [0.352, 0.636, 0.813]], - [0.554, [0.352, 0.636, 0.813]], - [0.556, [0.346, 0.632, 0.811]], - [0.558, [0.346, 0.632, 0.811]], - [0.56, [0.341, 0.629, 0.809]], - [0.562, [0.341, 0.629, 0.809]], - [0.564, [0.336, 0.626, 0.807]], - [0.566, [0.336, 0.626, 0.807]], [0.568, [0.331, 0.622, 0.805]], - [0.569, [0.331, 0.622, 0.805]], - [0.571, [0.326, 0.619, 0.803]], - [0.573, [0.326, 0.619, 0.803]], - [0.575, [0.321, 0.615, 0.801]], - [0.577, [0.321, 0.615, 0.801]], - [0.579, [0.316, 0.612, 0.799]], - [0.581, [0.316, 0.612, 0.799]], - [0.583, [0.311, 0.608, 0.797]], - [0.585, [0.311, 0.608, 0.797]], - [0.587, [0.306, 0.605, 0.795]], - [0.589, [0.306, 0.605, 0.795]], - [0.591, [0.301, 0.601, 0.793]], - [0.593, [0.301, 0.601, 0.793]], - [0.595, [0.296, 0.598, 0.791]], - [0.597, [0.296, 0.598, 0.791]], - [0.599, [0.291, 0.595, 0.789]], - [0.601, [0.291, 0.595, 0.789]], - [0.603, [0.286, 0.591, 0.787]], - [0.605, [0.286, 0.591, 0.787]], - [0.607, [0.281, 0.588, 0.785]], - [0.609, [0.281, 0.588, 0.785]], - [0.611, [0.276, 0.584, 0.783]], - [0.613, [0.276, 0.584, 0.783]], - [0.614, [0.271, 0.581, 0.781]], - [0.616, [0.271, 0.581, 0.781]], - [0.618, [0.266, 0.577, 0.779]], - [0.62, [0.266, 0.577, 0.779]], - [0.622, [0.261, 0.574, 0.777]], - [0.624, [0.261, 0.574, 0.777]], - [0.626, [0.256, 0.57, 0.775]], - [0.628, [0.256, 0.57, 0.775]], - [0.63, [0.252, 0.566, 0.773]], - [0.632, [0.252, 0.566, 0.773]], - [0.634, [0.248, 0.562, 0.771]], - [0.636, [0.248, 0.562, 0.771]], - [0.638, [0.244, 0.558, 0.769]], - [0.64, [0.244, 0.558, 0.769]], - [0.642, [0.24, 0.554, 0.767]], - [0.644, [0.24, 0.554, 0.767]], - [0.646, [0.236, 0.55, 0.765]], - [0.648, [0.236, 0.55, 0.765]], - [0.65, [0.232, 0.546, 0.763]], - [0.652, [0.232, 0.546, 0.763]], - [0.654, [0.228, 0.542, 0.761]], - [0.656, [0.228, 0.542, 0.761]], - [0.658, [0.224, 0.538, 0.758]], - [0.659, [0.224, 0.538, 0.758]], - [0.661, [0.22, 0.533, 0.756]], - [0.663, [0.22, 0.533, 0.756]], - [0.665, [0.216, 0.529, 0.754]], - [0.667, [0.216, 0.529, 0.754]], - [0.669, [0.212, 0.525, 0.752]], - [0.671, [0.212, 0.525, 0.752]], - [0.673, [0.208, 0.521, 0.75]], - [0.675, [0.208, 0.521, 0.75]], - [0.677, [0.204, 0.517, 0.748]], - [0.679, [0.204, 0.517, 0.748]], [0.681, [0.199, 0.513, 0.746]], - [0.683, [0.199, 0.513, 0.746]], - [0.685, [0.195, 0.509, 0.744]], - [0.687, [0.195, 0.509, 0.744]], - [0.689, [0.191, 0.505, 0.742]], - [0.691, [0.191, 0.505, 0.742]], - [0.693, [0.187, 0.501, 0.74]], - [0.695, [0.187, 0.501, 0.74]], - [0.697, [0.183, 0.497, 0.738]], - [0.699, [0.183, 0.497, 0.738]], - [0.701, [0.179, 0.493, 0.735]], - [0.703, [0.179, 0.493, 0.735]], - [0.705, [0.175, 0.489, 0.733]], - [0.706, [0.175, 0.489, 0.733]], - [0.708, [0.171, 0.485, 0.731]], - [0.71, [0.171, 0.485, 0.731]], - [0.712, [0.167, 0.481, 0.729]], - [0.714, [0.167, 0.481, 0.729]], - [0.716, [0.163, 0.477, 0.727]], - [0.718, [0.163, 0.477, 0.727]], - [0.72, [0.159, 0.473, 0.725]], - [0.722, [0.159, 0.473, 0.725]], - [0.724, [0.155, 0.469, 0.723]], - [0.726, [0.155, 0.469, 0.723]], - [0.728, [0.151, 0.464, 0.721]], - [0.73, [0.151, 0.464, 0.721]], - [0.732, [0.147, 0.46, 0.719]], - [0.734, [0.147, 0.46, 0.719]], - [0.736, [0.143, 0.456, 0.717]], - [0.738, [0.143, 0.456, 0.717]], - [0.74, [0.139, 0.452, 0.715]], - [0.742, [0.139, 0.452, 0.715]], - [0.744, [0.134, 0.448, 0.712]], - [0.746, [0.134, 0.448, 0.712]], - [0.748, [0.13, 0.444, 0.71]], - [0.75, [0.13, 0.444, 0.71]], - [0.751, [0.127, 0.44, 0.707]], - [0.753, [0.127, 0.44, 0.707]], - [0.755, [0.124, 0.436, 0.704]], - [0.757, [0.124, 0.436, 0.704]], - [0.759, [0.121, 0.432, 0.701]], - [0.761, [0.121, 0.432, 0.701]], - [0.763, [0.118, 0.428, 0.698]], - [0.765, [0.118, 0.428, 0.698]], - [0.767, [0.115, 0.424, 0.695]], - [0.769, [0.115, 0.424, 0.695]], - [0.771, [0.112, 0.42, 0.692]], - [0.773, [0.112, 0.42, 0.692]], - [0.775, [0.109, 0.417, 0.689]], - [0.777, [0.109, 0.417, 0.689]], - [0.779, [0.106, 0.413, 0.686]], - [0.781, [0.106, 0.413, 0.686]], - [0.783, [0.102, 0.409, 0.683]], - [0.785, [0.102, 0.409, 0.683]], - [0.787, [0.099, 0.405, 0.68]], - [0.789, [0.099, 0.405, 0.68]], - [0.791, [0.096, 0.401, 0.677]], - [0.793, [0.096, 0.401, 0.677]], [0.795, [0.093, 0.397, 0.674]], - [0.796, [0.093, 0.397, 0.674]], - [0.798, [0.09, 0.393, 0.671]], - [0.8, [0.09, 0.393, 0.671]], - [0.802, [0.087, 0.389, 0.668]], - [0.804, [0.087, 0.389, 0.668]], - [0.806, [0.084, 0.385, 0.664]], - [0.808, [0.084, 0.385, 0.664]], - [0.81, [0.081, 0.381, 0.661]], - [0.812, [0.081, 0.381, 0.661]], - [0.814, [0.078, 0.377, 0.658]], - [0.816, [0.078, 0.377, 0.658]], - [0.818, [0.075, 0.373, 0.655]], - [0.82, [0.075, 0.373, 0.655]], - [0.822, [0.072, 0.369, 0.652]], - [0.824, [0.072, 0.369, 0.652]], - [0.826, [0.069, 0.365, 0.649]], - [0.828, [0.069, 0.365, 0.649]], - [0.83, [0.066, 0.361, 0.646]], - [0.832, [0.066, 0.361, 0.646]], - [0.834, [0.063, 0.358, 0.643]], - [0.836, [0.063, 0.358, 0.643]], - [0.838, [0.059, 0.354, 0.64]], - [0.84, [0.059, 0.354, 0.64]], - [0.841, [0.056, 0.35, 0.637]], - [0.843, [0.056, 0.35, 0.637]], - [0.845, [0.053, 0.346, 0.634]], - [0.847, [0.053, 0.346, 0.634]], - [0.849, [0.05, 0.342, 0.631]], - [0.851, [0.05, 0.342, 0.631]], - [0.853, [0.047, 0.338, 0.628]], - [0.855, [0.047, 0.338, 0.628]], - [0.857, [0.044, 0.334, 0.624]], - [0.859, [0.044, 0.334, 0.624]], - [0.861, [0.041, 0.33, 0.621]], - [0.863, [0.041, 0.33, 0.621]], - [0.865, [0.038, 0.326, 0.618]], - [0.867, [0.038, 0.326, 0.618]], - [0.869, [0.035, 0.322, 0.615]], - [0.871, [0.035, 0.322, 0.615]], - [0.873, [0.032, 0.318, 0.612]], - [0.875, [0.032, 0.318, 0.612]], - [0.877, [0.031, 0.314, 0.606]], - [0.879, [0.031, 0.314, 0.606]], - [0.881, [0.031, 0.31, 0.6]], - [0.883, [0.031, 0.31, 0.6]], - [0.885, [0.031, 0.306, 0.594]], - [0.886, [0.031, 0.306, 0.594]], - [0.888, [0.031, 0.302, 0.588]], - [0.89, [0.031, 0.302, 0.588]], - [0.892, [0.031, 0.298, 0.582]], - [0.894, [0.031, 0.298, 0.582]], - [0.896, [0.031, 0.294, 0.576]], - [0.898, [0.031, 0.294, 0.576]], - [0.9, [0.031, 0.29, 0.57]], - [0.902, [0.031, 0.29, 0.57]], - [0.904, [0.031, 0.286, 0.564]], - [0.906, [0.031, 0.286, 0.564]], [0.908, [0.031, 0.282, 0.558]], - [0.91, [0.031, 0.282, 0.558]], - [0.912, [0.031, 0.278, 0.552]], - [0.914, [0.031, 0.278, 0.552]], - [0.916, [0.031, 0.273, 0.546]], - [0.918, [0.031, 0.273, 0.546]], - [0.92, [0.031, 0.269, 0.54]], - [0.922, [0.031, 0.269, 0.54]], - [0.924, [0.031, 0.265, 0.534]], - [0.926, [0.031, 0.265, 0.534]], - [0.928, [0.031, 0.261, 0.528]], - [0.93, [0.031, 0.261, 0.528]], - [0.932, [0.031, 0.257, 0.522]], - [0.933, [0.031, 0.257, 0.522]], - [0.935, [0.031, 0.253, 0.516]], - [0.937, [0.031, 0.253, 0.516]], - [0.939, [0.031, 0.249, 0.51]], - [0.941, [0.031, 0.249, 0.51]], - [0.943, [0.031, 0.245, 0.504]], - [0.945, [0.031, 0.245, 0.504]], - [0.947, [0.031, 0.241, 0.498]], - [0.949, [0.031, 0.241, 0.498]], - [0.951, [0.031, 0.237, 0.492]], - [0.953, [0.031, 0.237, 0.492]], - [0.955, [0.031, 0.233, 0.486]], - [0.957, [0.031, 0.233, 0.486]], - [0.959, [0.031, 0.229, 0.48]], - [0.961, [0.031, 0.229, 0.48]], - [0.963, [0.031, 0.225, 0.474]], - [0.965, [0.031, 0.225, 0.474]], - [0.967, [0.031, 0.221, 0.468]], - [0.969, [0.031, 0.221, 0.468]], - [0.971, [0.031, 0.217, 0.462]], - [0.973, [0.031, 0.217, 0.462]], - [0.975, [0.031, 0.213, 0.456]], - [0.977, [0.031, 0.213, 0.456]], - [0.978, [0.031, 0.209, 0.45]], - [0.98, [0.031, 0.209, 0.45]], - [0.982, [0.031, 0.204, 0.444]], - [0.984, [0.031, 0.204, 0.444]], - [0.986, [0.031, 0.2, 0.438]], - [0.988, [0.031, 0.2, 0.438]], - [0.99, [0.031, 0.196, 0.432]], - [0.992, [0.031, 0.196, 0.432]], - [0.994, [0.031, 0.192, 0.426]], - [0.996, [0.031, 0.192, 0.426]], - [0.998, [0.031, 0.188, 0.42]], - [1.0, [0.031, 0.188, 0.42]], + [1, [0.031, 0.188, 0.42]], ], }, [ColorSchemas.Greens]: { @@ -560,518 +58,16 @@ export const vislibColorMaps: ColorMap = { defaultMessage: 'Greens', }), value: [ - [0.0, [0.969, 0.988, 0.961]], - [0.002, [0.969, 0.988, 0.961]], - [0.004, [0.966, 0.987, 0.958]], - [0.006, [0.966, 0.987, 0.958]], - [0.008, [0.964, 0.987, 0.956]], - [0.01, [0.964, 0.987, 0.956]], - [0.012, [0.962, 0.986, 0.953]], - [0.014, [0.962, 0.986, 0.953]], - [0.016, [0.96, 0.985, 0.95]], - [0.018, [0.96, 0.985, 0.95]], - [0.02, [0.958, 0.984, 0.948]], - [0.022, [0.958, 0.984, 0.948]], - [0.023, [0.955, 0.983, 0.945]], - [0.025, [0.955, 0.983, 0.945]], - [0.027, [0.953, 0.982, 0.943]], - [0.029, [0.953, 0.982, 0.943]], - [0.031, [0.951, 0.981, 0.94]], - [0.033, [0.951, 0.981, 0.94]], - [0.035, [0.949, 0.98, 0.938]], - [0.037, [0.949, 0.98, 0.938]], - [0.039, [0.946, 0.98, 0.935]], - [0.041, [0.946, 0.98, 0.935]], - [0.043, [0.944, 0.979, 0.932]], - [0.045, [0.944, 0.979, 0.932]], - [0.047, [0.942, 0.978, 0.93]], - [0.049, [0.942, 0.978, 0.93]], - [0.051, [0.94, 0.977, 0.927]], - [0.053, [0.94, 0.977, 0.927]], - [0.055, [0.938, 0.976, 0.925]], - [0.057, [0.938, 0.976, 0.925]], - [0.059, [0.935, 0.975, 0.922]], - [0.061, [0.935, 0.975, 0.922]], - [0.063, [0.933, 0.974, 0.919]], - [0.065, [0.933, 0.974, 0.919]], - [0.067, [0.931, 0.974, 0.917]], - [0.068, [0.931, 0.974, 0.917]], - [0.07, [0.929, 0.973, 0.914]], - [0.072, [0.929, 0.973, 0.914]], - [0.074, [0.927, 0.972, 0.912]], - [0.076, [0.927, 0.972, 0.912]], - [0.078, [0.924, 0.971, 0.909]], - [0.08, [0.924, 0.971, 0.909]], - [0.082, [0.922, 0.97, 0.907]], - [0.084, [0.922, 0.97, 0.907]], - [0.086, [0.92, 0.969, 0.904]], - [0.088, [0.92, 0.969, 0.904]], - [0.09, [0.918, 0.968, 0.901]], - [0.092, [0.918, 0.968, 0.901]], - [0.094, [0.915, 0.968, 0.899]], - [0.096, [0.915, 0.968, 0.899]], - [0.098, [0.913, 0.967, 0.896]], - [0.1, [0.913, 0.967, 0.896]], - [0.102, [0.911, 0.966, 0.894]], - [0.104, [0.911, 0.966, 0.894]], - [0.106, [0.909, 0.965, 0.891]], - [0.108, [0.909, 0.965, 0.891]], - [0.11, [0.907, 0.964, 0.888]], - [0.112, [0.907, 0.964, 0.888]], + [0, [0.969, 0.988, 0.961]], [0.114, [0.904, 0.963, 0.886]], - [0.115, [0.904, 0.963, 0.886]], - [0.117, [0.902, 0.962, 0.883]], - [0.119, [0.902, 0.962, 0.883]], - [0.121, [0.9, 0.962, 0.881]], - [0.123, [0.9, 0.962, 0.881]], - [0.125, [0.898, 0.961, 0.878]], - [0.127, [0.898, 0.961, 0.878]], - [0.129, [0.894, 0.959, 0.874]], - [0.131, [0.894, 0.959, 0.874]], - [0.133, [0.89, 0.958, 0.87]], - [0.135, [0.89, 0.958, 0.87]], - [0.137, [0.887, 0.956, 0.866]], - [0.139, [0.887, 0.956, 0.866]], - [0.141, [0.883, 0.955, 0.862]], - [0.143, [0.883, 0.955, 0.862]], - [0.145, [0.879, 0.953, 0.858]], - [0.147, [0.879, 0.953, 0.858]], - [0.149, [0.875, 0.952, 0.854]], - [0.151, [0.875, 0.952, 0.854]], - [0.153, [0.872, 0.95, 0.85]], - [0.155, [0.872, 0.95, 0.85]], - [0.157, [0.868, 0.949, 0.846]], - [0.159, [0.868, 0.949, 0.846]], - [0.16, [0.864, 0.947, 0.843]], - [0.162, [0.864, 0.947, 0.843]], - [0.164, [0.861, 0.946, 0.839]], - [0.166, [0.861, 0.946, 0.839]], - [0.168, [0.857, 0.944, 0.835]], - [0.17, [0.857, 0.944, 0.835]], - [0.172, [0.853, 0.943, 0.831]], - [0.174, [0.853, 0.943, 0.831]], - [0.176, [0.85, 0.941, 0.827]], - [0.178, [0.85, 0.941, 0.827]], - [0.18, [0.846, 0.94, 0.823]], - [0.182, [0.846, 0.94, 0.823]], - [0.184, [0.842, 0.938, 0.819]], - [0.186, [0.842, 0.938, 0.819]], - [0.188, [0.839, 0.937, 0.815]], - [0.19, [0.839, 0.937, 0.815]], - [0.192, [0.835, 0.936, 0.811]], - [0.194, [0.835, 0.936, 0.811]], - [0.196, [0.831, 0.934, 0.807]], - [0.198, [0.831, 0.934, 0.807]], - [0.2, [0.827, 0.933, 0.803]], - [0.202, [0.827, 0.933, 0.803]], - [0.204, [0.824, 0.931, 0.799]], - [0.205, [0.824, 0.931, 0.799]], - [0.207, [0.82, 0.93, 0.795]], - [0.209, [0.82, 0.93, 0.795]], - [0.211, [0.816, 0.928, 0.791]], - [0.213, [0.816, 0.928, 0.791]], - [0.215, [0.813, 0.927, 0.787]], - [0.217, [0.813, 0.927, 0.787]], - [0.219, [0.809, 0.925, 0.783]], - [0.221, [0.809, 0.925, 0.783]], - [0.223, [0.805, 0.924, 0.78]], - [0.225, [0.805, 0.924, 0.78]], [0.227, [0.802, 0.922, 0.776]], - [0.229, [0.802, 0.922, 0.776]], - [0.231, [0.798, 0.921, 0.772]], - [0.233, [0.798, 0.921, 0.772]], - [0.235, [0.794, 0.919, 0.768]], - [0.237, [0.794, 0.919, 0.768]], - [0.239, [0.791, 0.918, 0.764]], - [0.241, [0.791, 0.918, 0.764]], - [0.243, [0.787, 0.916, 0.76]], - [0.245, [0.787, 0.916, 0.76]], - [0.247, [0.783, 0.915, 0.756]], - [0.249, [0.783, 0.915, 0.756]], - [0.25, [0.779, 0.913, 0.752]], - [0.252, [0.779, 0.913, 0.752]], - [0.254, [0.775, 0.911, 0.747]], - [0.256, [0.775, 0.911, 0.747]], - [0.258, [0.77, 0.909, 0.743]], - [0.26, [0.77, 0.909, 0.743]], - [0.262, [0.765, 0.907, 0.738]], - [0.264, [0.765, 0.907, 0.738]], - [0.266, [0.761, 0.905, 0.734]], - [0.268, [0.761, 0.905, 0.734]], - [0.27, [0.756, 0.903, 0.729]], - [0.272, [0.756, 0.903, 0.729]], - [0.274, [0.751, 0.901, 0.724]], - [0.276, [0.751, 0.901, 0.724]], - [0.278, [0.746, 0.899, 0.72]], - [0.28, [0.746, 0.899, 0.72]], - [0.282, [0.742, 0.897, 0.715]], - [0.284, [0.742, 0.897, 0.715]], - [0.286, [0.737, 0.896, 0.711]], - [0.288, [0.737, 0.896, 0.711]], - [0.29, [0.732, 0.894, 0.706]], - [0.292, [0.732, 0.894, 0.706]], - [0.294, [0.728, 0.892, 0.702]], - [0.295, [0.728, 0.892, 0.702]], - [0.297, [0.723, 0.89, 0.697]], - [0.299, [0.723, 0.89, 0.697]], - [0.301, [0.718, 0.888, 0.693]], - [0.303, [0.718, 0.888, 0.693]], - [0.305, [0.714, 0.886, 0.688]], - [0.307, [0.714, 0.886, 0.688]], - [0.309, [0.709, 0.884, 0.684]], - [0.311, [0.709, 0.884, 0.684]], - [0.313, [0.704, 0.882, 0.679]], - [0.315, [0.704, 0.882, 0.679]], - [0.317, [0.7, 0.88, 0.674]], - [0.319, [0.7, 0.88, 0.674]], - [0.321, [0.695, 0.878, 0.67]], - [0.323, [0.695, 0.878, 0.67]], - [0.325, [0.69, 0.876, 0.665]], - [0.327, [0.69, 0.876, 0.665]], - [0.329, [0.686, 0.874, 0.661]], - [0.331, [0.686, 0.874, 0.661]], - [0.333, [0.681, 0.872, 0.656]], - [0.335, [0.681, 0.872, 0.656]], - [0.337, [0.676, 0.87, 0.652]], - [0.339, [0.676, 0.87, 0.652]], [0.341, [0.672, 0.868, 0.647]], - [0.342, [0.672, 0.868, 0.647]], - [0.344, [0.667, 0.866, 0.643]], - [0.346, [0.667, 0.866, 0.643]], - [0.348, [0.662, 0.864, 0.638]], - [0.35, [0.662, 0.864, 0.638]], - [0.352, [0.658, 0.862, 0.633]], - [0.354, [0.658, 0.862, 0.633]], - [0.356, [0.653, 0.86, 0.629]], - [0.358, [0.653, 0.86, 0.629]], - [0.36, [0.648, 0.858, 0.624]], - [0.362, [0.648, 0.858, 0.624]], - [0.364, [0.644, 0.856, 0.62]], - [0.366, [0.644, 0.856, 0.62]], - [0.368, [0.639, 0.854, 0.615]], - [0.37, [0.639, 0.854, 0.615]], - [0.372, [0.634, 0.852, 0.611]], - [0.374, [0.634, 0.852, 0.611]], - [0.376, [0.629, 0.85, 0.606]], - [0.378, [0.629, 0.85, 0.606]], - [0.38, [0.624, 0.847, 0.602]], - [0.382, [0.624, 0.847, 0.602]], - [0.384, [0.618, 0.845, 0.597]], - [0.386, [0.618, 0.845, 0.597]], - [0.387, [0.613, 0.842, 0.592]], - [0.389, [0.613, 0.842, 0.592]], - [0.391, [0.607, 0.84, 0.588]], - [0.393, [0.607, 0.84, 0.588]], - [0.395, [0.602, 0.837, 0.583]], - [0.397, [0.602, 0.837, 0.583]], - [0.399, [0.596, 0.835, 0.579]], - [0.401, [0.596, 0.835, 0.579]], - [0.403, [0.591, 0.832, 0.574]], - [0.405, [0.591, 0.832, 0.574]], - [0.407, [0.585, 0.829, 0.57]], - [0.409, [0.585, 0.829, 0.57]], - [0.411, [0.579, 0.827, 0.565]], - [0.413, [0.579, 0.827, 0.565]], - [0.415, [0.574, 0.824, 0.561]], - [0.417, [0.574, 0.824, 0.561]], - [0.419, [0.568, 0.822, 0.556]], - [0.421, [0.568, 0.822, 0.556]], - [0.423, [0.563, 0.819, 0.552]], - [0.425, [0.563, 0.819, 0.552]], - [0.427, [0.557, 0.816, 0.547]], - [0.429, [0.557, 0.816, 0.547]], - [0.431, [0.552, 0.814, 0.542]], - [0.432, [0.552, 0.814, 0.542]], - [0.434, [0.546, 0.811, 0.538]], - [0.436, [0.546, 0.811, 0.538]], - [0.438, [0.541, 0.809, 0.533]], - [0.44, [0.541, 0.809, 0.533]], - [0.442, [0.535, 0.806, 0.529]], - [0.444, [0.535, 0.806, 0.529]], - [0.446, [0.53, 0.804, 0.524]], - [0.448, [0.53, 0.804, 0.524]], - [0.45, [0.524, 0.801, 0.52]], - [0.452, [0.524, 0.801, 0.52]], [0.454, [0.519, 0.798, 0.515]], - [0.456, [0.519, 0.798, 0.515]], - [0.458, [0.513, 0.796, 0.511]], - [0.46, [0.513, 0.796, 0.511]], - [0.462, [0.507, 0.793, 0.506]], - [0.464, [0.507, 0.793, 0.506]], - [0.466, [0.502, 0.791, 0.501]], - [0.468, [0.502, 0.791, 0.501]], - [0.47, [0.496, 0.788, 0.497]], - [0.472, [0.496, 0.788, 0.497]], - [0.474, [0.491, 0.785, 0.492]], - [0.476, [0.491, 0.785, 0.492]], - [0.477, [0.485, 0.783, 0.488]], - [0.479, [0.485, 0.783, 0.488]], - [0.481, [0.48, 0.78, 0.483]], - [0.483, [0.48, 0.78, 0.483]], - [0.485, [0.474, 0.778, 0.479]], - [0.487, [0.474, 0.778, 0.479]], - [0.489, [0.469, 0.775, 0.474]], - [0.491, [0.469, 0.775, 0.474]], - [0.493, [0.463, 0.773, 0.47]], - [0.495, [0.463, 0.773, 0.47]], - [0.497, [0.458, 0.77, 0.465]], - [0.499, [0.458, 0.77, 0.465]], - [0.501, [0.452, 0.767, 0.461]], - [0.503, [0.452, 0.767, 0.461]], - [0.505, [0.445, 0.764, 0.458]], - [0.507, [0.445, 0.764, 0.458]], - [0.509, [0.439, 0.761, 0.455]], - [0.511, [0.439, 0.761, 0.455]], - [0.513, [0.433, 0.758, 0.452]], - [0.515, [0.433, 0.758, 0.452]], - [0.517, [0.427, 0.755, 0.449]], - [0.519, [0.427, 0.755, 0.449]], - [0.521, [0.42, 0.752, 0.446]], - [0.523, [0.42, 0.752, 0.446]], - [0.524, [0.414, 0.749, 0.443]], - [0.526, [0.414, 0.749, 0.443]], - [0.528, [0.408, 0.746, 0.44]], - [0.53, [0.408, 0.746, 0.44]], - [0.532, [0.402, 0.742, 0.437]], - [0.534, [0.402, 0.742, 0.437]], - [0.536, [0.395, 0.739, 0.434]], - [0.538, [0.395, 0.739, 0.434]], - [0.54, [0.389, 0.736, 0.43]], - [0.542, [0.389, 0.736, 0.43]], - [0.544, [0.383, 0.733, 0.427]], - [0.546, [0.383, 0.733, 0.427]], - [0.548, [0.376, 0.73, 0.424]], - [0.55, [0.376, 0.73, 0.424]], - [0.552, [0.37, 0.727, 0.421]], - [0.554, [0.37, 0.727, 0.421]], - [0.556, [0.364, 0.724, 0.418]], - [0.558, [0.364, 0.724, 0.418]], - [0.56, [0.358, 0.721, 0.415]], - [0.562, [0.358, 0.721, 0.415]], - [0.564, [0.351, 0.718, 0.412]], - [0.566, [0.351, 0.718, 0.412]], [0.568, [0.345, 0.715, 0.409]], - [0.569, [0.345, 0.715, 0.409]], - [0.571, [0.339, 0.712, 0.406]], - [0.573, [0.339, 0.712, 0.406]], - [0.575, [0.333, 0.709, 0.403]], - [0.577, [0.333, 0.709, 0.403]], - [0.579, [0.326, 0.706, 0.4]], - [0.581, [0.326, 0.706, 0.4]], - [0.583, [0.32, 0.702, 0.397]], - [0.585, [0.32, 0.702, 0.397]], - [0.587, [0.314, 0.699, 0.394]], - [0.589, [0.314, 0.699, 0.394]], - [0.591, [0.307, 0.696, 0.39]], - [0.593, [0.307, 0.696, 0.39]], - [0.595, [0.301, 0.693, 0.387]], - [0.597, [0.301, 0.693, 0.387]], - [0.599, [0.295, 0.69, 0.384]], - [0.601, [0.295, 0.69, 0.384]], - [0.603, [0.289, 0.687, 0.381]], - [0.605, [0.289, 0.687, 0.381]], - [0.607, [0.282, 0.684, 0.378]], - [0.609, [0.282, 0.684, 0.378]], - [0.611, [0.276, 0.681, 0.375]], - [0.613, [0.276, 0.681, 0.375]], - [0.614, [0.27, 0.678, 0.372]], - [0.616, [0.27, 0.678, 0.372]], - [0.618, [0.264, 0.675, 0.369]], - [0.62, [0.264, 0.675, 0.369]], - [0.622, [0.257, 0.672, 0.366]], - [0.624, [0.257, 0.672, 0.366]], - [0.626, [0.253, 0.668, 0.363]], - [0.628, [0.253, 0.668, 0.363]], - [0.63, [0.249, 0.664, 0.36]], - [0.632, [0.249, 0.664, 0.36]], - [0.634, [0.245, 0.66, 0.357]], - [0.636, [0.245, 0.66, 0.357]], - [0.638, [0.242, 0.656, 0.354]], - [0.64, [0.242, 0.656, 0.354]], - [0.642, [0.238, 0.652, 0.351]], - [0.644, [0.238, 0.652, 0.351]], - [0.646, [0.234, 0.648, 0.348]], - [0.648, [0.234, 0.648, 0.348]], - [0.65, [0.23, 0.645, 0.345]], - [0.652, [0.23, 0.645, 0.345]], - [0.654, [0.227, 0.641, 0.342]], - [0.656, [0.227, 0.641, 0.342]], - [0.658, [0.223, 0.637, 0.339]], - [0.659, [0.223, 0.637, 0.339]], - [0.661, [0.219, 0.633, 0.336]], - [0.663, [0.219, 0.633, 0.336]], - [0.665, [0.216, 0.629, 0.333]], - [0.667, [0.216, 0.629, 0.333]], - [0.669, [0.212, 0.625, 0.33]], - [0.671, [0.212, 0.625, 0.33]], - [0.673, [0.208, 0.621, 0.327]], - [0.675, [0.208, 0.621, 0.327]], - [0.677, [0.205, 0.617, 0.324]], - [0.679, [0.205, 0.617, 0.324]], [0.681, [0.201, 0.613, 0.322]], - [0.683, [0.201, 0.613, 0.322]], - [0.685, [0.197, 0.609, 0.319]], - [0.687, [0.197, 0.609, 0.319]], - [0.689, [0.194, 0.605, 0.316]], - [0.691, [0.194, 0.605, 0.316]], - [0.693, [0.19, 0.601, 0.313]], - [0.695, [0.19, 0.601, 0.313]], - [0.697, [0.186, 0.597, 0.31]], - [0.699, [0.186, 0.597, 0.31]], - [0.701, [0.182, 0.593, 0.307]], - [0.703, [0.182, 0.593, 0.307]], - [0.705, [0.179, 0.589, 0.304]], - [0.706, [0.179, 0.589, 0.304]], - [0.708, [0.175, 0.585, 0.301]], - [0.71, [0.175, 0.585, 0.301]], - [0.712, [0.171, 0.582, 0.298]], - [0.714, [0.171, 0.582, 0.298]], - [0.716, [0.168, 0.578, 0.295]], - [0.718, [0.168, 0.578, 0.295]], - [0.72, [0.164, 0.574, 0.292]], - [0.722, [0.164, 0.574, 0.292]], - [0.724, [0.16, 0.57, 0.289]], - [0.726, [0.16, 0.57, 0.289]], - [0.728, [0.157, 0.566, 0.286]], - [0.73, [0.157, 0.566, 0.286]], - [0.732, [0.153, 0.562, 0.283]], - [0.734, [0.153, 0.562, 0.283]], - [0.736, [0.149, 0.558, 0.28]], - [0.738, [0.149, 0.558, 0.28]], - [0.74, [0.146, 0.554, 0.277]], - [0.742, [0.146, 0.554, 0.277]], - [0.744, [0.142, 0.55, 0.274]], - [0.746, [0.142, 0.55, 0.274]], - [0.748, [0.138, 0.546, 0.271]], - [0.75, [0.138, 0.546, 0.271]], - [0.751, [0.134, 0.542, 0.268]], - [0.753, [0.134, 0.542, 0.268]], - [0.755, [0.13, 0.539, 0.265]], - [0.757, [0.13, 0.539, 0.265]], - [0.759, [0.125, 0.535, 0.262]], - [0.761, [0.125, 0.535, 0.262]], - [0.763, [0.121, 0.531, 0.259]], - [0.765, [0.121, 0.531, 0.259]], - [0.767, [0.117, 0.528, 0.256]], - [0.769, [0.117, 0.528, 0.256]], - [0.771, [0.112, 0.524, 0.253]], - [0.773, [0.112, 0.524, 0.253]], - [0.775, [0.108, 0.52, 0.25]], - [0.777, [0.108, 0.52, 0.25]], - [0.779, [0.104, 0.516, 0.247]], - [0.781, [0.104, 0.516, 0.247]], - [0.783, [0.1, 0.513, 0.244]], - [0.785, [0.1, 0.513, 0.244]], - [0.787, [0.095, 0.509, 0.241]], - [0.789, [0.095, 0.509, 0.241]], - [0.791, [0.091, 0.505, 0.238]], - [0.793, [0.091, 0.505, 0.238]], [0.795, [0.087, 0.502, 0.234]], - [0.796, [0.087, 0.502, 0.234]], - [0.798, [0.082, 0.498, 0.231]], - [0.8, [0.082, 0.498, 0.231]], - [0.802, [0.078, 0.494, 0.228]], - [0.804, [0.078, 0.494, 0.228]], - [0.806, [0.074, 0.491, 0.225]], - [0.808, [0.074, 0.491, 0.225]], - [0.81, [0.069, 0.487, 0.222]], - [0.812, [0.069, 0.487, 0.222]], - [0.814, [0.065, 0.483, 0.219]], - [0.816, [0.065, 0.483, 0.219]], - [0.818, [0.061, 0.48, 0.216]], - [0.82, [0.061, 0.48, 0.216]], - [0.822, [0.057, 0.476, 0.213]], - [0.824, [0.057, 0.476, 0.213]], - [0.826, [0.052, 0.472, 0.21]], - [0.828, [0.052, 0.472, 0.21]], - [0.83, [0.048, 0.469, 0.207]], - [0.832, [0.048, 0.469, 0.207]], - [0.834, [0.044, 0.465, 0.204]], - [0.836, [0.044, 0.465, 0.204]], - [0.838, [0.039, 0.461, 0.201]], - [0.84, [0.039, 0.461, 0.201]], - [0.841, [0.035, 0.457, 0.198]], - [0.843, [0.035, 0.457, 0.198]], - [0.845, [0.031, 0.454, 0.194]], - [0.847, [0.031, 0.454, 0.194]], - [0.849, [0.026, 0.45, 0.191]], - [0.851, [0.026, 0.45, 0.191]], - [0.853, [0.022, 0.446, 0.188]], - [0.855, [0.022, 0.446, 0.188]], - [0.857, [0.018, 0.443, 0.185]], - [0.859, [0.018, 0.443, 0.185]], - [0.861, [0.013, 0.439, 0.182]], - [0.863, [0.013, 0.439, 0.182]], - [0.865, [0.009, 0.435, 0.179]], - [0.867, [0.009, 0.435, 0.179]], - [0.869, [0.005, 0.432, 0.176]], - [0.871, [0.005, 0.432, 0.176]], - [0.873, [0.001, 0.428, 0.173]], - [0.875, [0.001, 0.428, 0.173]], - [0.877, [0.0, 0.423, 0.171]], - [0.879, [0.0, 0.423, 0.171]], - [0.881, [0.0, 0.418, 0.169]], - [0.883, [0.0, 0.418, 0.169]], - [0.885, [0.0, 0.413, 0.167]], - [0.886, [0.0, 0.413, 0.167]], - [0.888, [0.0, 0.408, 0.164]], - [0.89, [0.0, 0.408, 0.164]], - [0.892, [0.0, 0.403, 0.162]], - [0.894, [0.0, 0.403, 0.162]], - [0.896, [0.0, 0.398, 0.16]], - [0.898, [0.0, 0.398, 0.16]], - [0.9, [0.0, 0.393, 0.158]], - [0.902, [0.0, 0.393, 0.158]], - [0.904, [0.0, 0.388, 0.156]], - [0.906, [0.0, 0.388, 0.156]], - [0.908, [0.0, 0.383, 0.154]], - [0.91, [0.0, 0.383, 0.154]], - [0.912, [0.0, 0.378, 0.152]], - [0.914, [0.0, 0.378, 0.152]], - [0.916, [0.0, 0.373, 0.15]], - [0.918, [0.0, 0.373, 0.15]], - [0.92, [0.0, 0.368, 0.148]], - [0.922, [0.0, 0.368, 0.148]], - [0.924, [0.0, 0.363, 0.146]], - [0.926, [0.0, 0.363, 0.146]], - [0.928, [0.0, 0.357, 0.144]], - [0.93, [0.0, 0.357, 0.144]], - [0.932, [0.0, 0.352, 0.141]], - [0.933, [0.0, 0.352, 0.141]], - [0.935, [0.0, 0.347, 0.139]], - [0.937, [0.0, 0.347, 0.139]], - [0.939, [0.0, 0.342, 0.137]], - [0.941, [0.0, 0.342, 0.137]], - [0.943, [0.0, 0.337, 0.135]], - [0.945, [0.0, 0.337, 0.135]], - [0.947, [0.0, 0.332, 0.133]], - [0.949, [0.0, 0.332, 0.133]], - [0.951, [0.0, 0.327, 0.131]], - [0.953, [0.0, 0.327, 0.131]], - [0.955, [0.0, 0.322, 0.129]], - [0.957, [0.0, 0.322, 0.129]], - [0.959, [0.0, 0.317, 0.127]], - [0.961, [0.0, 0.317, 0.127]], - [0.963, [0.0, 0.312, 0.125]], - [0.965, [0.0, 0.312, 0.125]], - [0.967, [0.0, 0.307, 0.123]], - [0.969, [0.0, 0.307, 0.123]], - [0.971, [0.0, 0.302, 0.121]], - [0.973, [0.0, 0.302, 0.121]], - [0.975, [0.0, 0.297, 0.118]], - [0.977, [0.0, 0.297, 0.118]], - [0.978, [0.0, 0.292, 0.116]], - [0.98, [0.0, 0.292, 0.116]], - [0.982, [0.0, 0.287, 0.114]], - [0.984, [0.0, 0.287, 0.114]], - [0.986, [0.0, 0.282, 0.112]], - [0.988, [0.0, 0.282, 0.112]], - [0.99, [0.0, 0.277, 0.11]], - [0.992, [0.0, 0.277, 0.11]], - [0.994, [0.0, 0.272, 0.108]], - [0.996, [0.0, 0.272, 0.108]], - [0.998, [0.0, 0.267, 0.106]], - [1.0, [0.0, 0.267, 0.106]], + [0.908, [0, 0.383, 0.154]], + [1, [0, 0.267, 0.106]], ], }, [ColorSchemas.Greys]: { @@ -1080,518 +76,16 @@ export const vislibColorMaps: ColorMap = { defaultMessage: 'Greys', }), value: [ - [0.0, [1.0, 1.0, 1.0]], - [0.002, [1.0, 1.0, 1.0]], - [0.004, [0.998, 0.998, 0.998]], - [0.006, [0.998, 0.998, 0.998]], - [0.008, [0.996, 0.996, 0.996]], - [0.01, [0.996, 0.996, 0.996]], - [0.012, [0.994, 0.994, 0.994]], - [0.014, [0.994, 0.994, 0.994]], - [0.016, [0.993, 0.993, 0.993]], - [0.018, [0.993, 0.993, 0.993]], - [0.02, [0.991, 0.991, 0.991]], - [0.022, [0.991, 0.991, 0.991]], - [0.023, [0.989, 0.989, 0.989]], - [0.025, [0.989, 0.989, 0.989]], - [0.027, [0.987, 0.987, 0.987]], - [0.029, [0.987, 0.987, 0.987]], - [0.031, [0.985, 0.985, 0.985]], - [0.033, [0.985, 0.985, 0.985]], - [0.035, [0.983, 0.983, 0.983]], - [0.037, [0.983, 0.983, 0.983]], - [0.039, [0.982, 0.982, 0.982]], - [0.041, [0.982, 0.982, 0.982]], - [0.043, [0.98, 0.98, 0.98]], - [0.045, [0.98, 0.98, 0.98]], - [0.047, [0.978, 0.978, 0.978]], - [0.049, [0.978, 0.978, 0.978]], - [0.051, [0.976, 0.976, 0.976]], - [0.053, [0.976, 0.976, 0.976]], - [0.055, [0.974, 0.974, 0.974]], - [0.057, [0.974, 0.974, 0.974]], - [0.059, [0.972, 0.972, 0.972]], - [0.061, [0.972, 0.972, 0.972]], - [0.063, [0.97, 0.97, 0.97]], - [0.065, [0.97, 0.97, 0.97]], - [0.067, [0.969, 0.969, 0.969]], - [0.068, [0.969, 0.969, 0.969]], - [0.07, [0.967, 0.967, 0.967]], - [0.072, [0.967, 0.967, 0.967]], - [0.074, [0.965, 0.965, 0.965]], - [0.076, [0.965, 0.965, 0.965]], - [0.078, [0.963, 0.963, 0.963]], - [0.08, [0.963, 0.963, 0.963]], - [0.082, [0.961, 0.961, 0.961]], - [0.084, [0.961, 0.961, 0.961]], - [0.086, [0.959, 0.959, 0.959]], - [0.088, [0.959, 0.959, 0.959]], - [0.09, [0.958, 0.958, 0.958]], - [0.092, [0.958, 0.958, 0.958]], - [0.094, [0.956, 0.956, 0.956]], - [0.096, [0.956, 0.956, 0.956]], - [0.098, [0.954, 0.954, 0.954]], - [0.1, [0.954, 0.954, 0.954]], - [0.102, [0.952, 0.952, 0.952]], - [0.104, [0.952, 0.952, 0.952]], - [0.106, [0.95, 0.95, 0.95]], - [0.108, [0.95, 0.95, 0.95]], - [0.11, [0.948, 0.948, 0.948]], - [0.112, [0.948, 0.948, 0.948]], + [0, [1, 1, 1]], [0.114, [0.946, 0.946, 0.946]], - [0.115, [0.946, 0.946, 0.946]], - [0.117, [0.945, 0.945, 0.945]], - [0.119, [0.945, 0.945, 0.945]], - [0.121, [0.943, 0.943, 0.943]], - [0.123, [0.943, 0.943, 0.943]], - [0.125, [0.941, 0.941, 0.941]], - [0.127, [0.941, 0.941, 0.941]], - [0.129, [0.938, 0.938, 0.938]], - [0.131, [0.938, 0.938, 0.938]], - [0.133, [0.935, 0.935, 0.935]], - [0.135, [0.935, 0.935, 0.935]], - [0.137, [0.932, 0.932, 0.932]], - [0.139, [0.932, 0.932, 0.932]], - [0.141, [0.93, 0.93, 0.93]], - [0.143, [0.93, 0.93, 0.93]], - [0.145, [0.927, 0.927, 0.927]], - [0.147, [0.927, 0.927, 0.927]], - [0.149, [0.924, 0.924, 0.924]], - [0.151, [0.924, 0.924, 0.924]], - [0.153, [0.921, 0.921, 0.921]], - [0.155, [0.921, 0.921, 0.921]], - [0.157, [0.918, 0.918, 0.918]], - [0.159, [0.918, 0.918, 0.918]], - [0.16, [0.915, 0.915, 0.915]], - [0.162, [0.915, 0.915, 0.915]], - [0.164, [0.913, 0.913, 0.913]], - [0.166, [0.913, 0.913, 0.913]], - [0.168, [0.91, 0.91, 0.91]], - [0.17, [0.91, 0.91, 0.91]], - [0.172, [0.907, 0.907, 0.907]], - [0.174, [0.907, 0.907, 0.907]], - [0.176, [0.904, 0.904, 0.904]], - [0.178, [0.904, 0.904, 0.904]], - [0.18, [0.901, 0.901, 0.901]], - [0.182, [0.901, 0.901, 0.901]], - [0.184, [0.898, 0.898, 0.898]], - [0.186, [0.898, 0.898, 0.898]], - [0.188, [0.896, 0.896, 0.896]], - [0.19, [0.896, 0.896, 0.896]], - [0.192, [0.893, 0.893, 0.893]], - [0.194, [0.893, 0.893, 0.893]], - [0.196, [0.89, 0.89, 0.89]], - [0.198, [0.89, 0.89, 0.89]], - [0.2, [0.887, 0.887, 0.887]], - [0.202, [0.887, 0.887, 0.887]], - [0.204, [0.884, 0.884, 0.884]], - [0.205, [0.884, 0.884, 0.884]], - [0.207, [0.881, 0.881, 0.881]], - [0.209, [0.881, 0.881, 0.881]], - [0.211, [0.879, 0.879, 0.879]], - [0.213, [0.879, 0.879, 0.879]], - [0.215, [0.876, 0.876, 0.876]], - [0.217, [0.876, 0.876, 0.876]], - [0.219, [0.873, 0.873, 0.873]], - [0.221, [0.873, 0.873, 0.873]], - [0.223, [0.87, 0.87, 0.87]], - [0.225, [0.87, 0.87, 0.87]], [0.227, [0.867, 0.867, 0.867]], - [0.229, [0.867, 0.867, 0.867]], - [0.231, [0.864, 0.864, 0.864]], - [0.233, [0.864, 0.864, 0.864]], - [0.235, [0.862, 0.862, 0.862]], - [0.237, [0.862, 0.862, 0.862]], - [0.239, [0.859, 0.859, 0.859]], - [0.241, [0.859, 0.859, 0.859]], - [0.243, [0.856, 0.856, 0.856]], - [0.245, [0.856, 0.856, 0.856]], - [0.247, [0.853, 0.853, 0.853]], - [0.249, [0.853, 0.853, 0.853]], - [0.25, [0.85, 0.85, 0.85]], - [0.252, [0.85, 0.85, 0.85]], - [0.254, [0.847, 0.847, 0.847]], - [0.256, [0.847, 0.847, 0.847]], - [0.258, [0.843, 0.843, 0.843]], - [0.26, [0.843, 0.843, 0.843]], - [0.262, [0.84, 0.84, 0.84]], - [0.264, [0.84, 0.84, 0.84]], - [0.266, [0.836, 0.836, 0.836]], - [0.268, [0.836, 0.836, 0.836]], - [0.27, [0.833, 0.833, 0.833]], - [0.272, [0.833, 0.833, 0.833]], - [0.274, [0.829, 0.829, 0.829]], - [0.276, [0.829, 0.829, 0.829]], - [0.278, [0.826, 0.826, 0.826]], - [0.28, [0.826, 0.826, 0.826]], - [0.282, [0.823, 0.823, 0.823]], - [0.284, [0.823, 0.823, 0.823]], - [0.286, [0.819, 0.819, 0.819]], - [0.288, [0.819, 0.819, 0.819]], - [0.29, [0.816, 0.816, 0.816]], - [0.292, [0.816, 0.816, 0.816]], - [0.294, [0.812, 0.812, 0.812]], - [0.295, [0.812, 0.812, 0.812]], - [0.297, [0.809, 0.809, 0.809]], - [0.299, [0.809, 0.809, 0.809]], - [0.301, [0.805, 0.805, 0.805]], - [0.303, [0.805, 0.805, 0.805]], - [0.305, [0.802, 0.802, 0.802]], - [0.307, [0.802, 0.802, 0.802]], - [0.309, [0.798, 0.798, 0.798]], - [0.311, [0.798, 0.798, 0.798]], - [0.313, [0.795, 0.795, 0.795]], - [0.315, [0.795, 0.795, 0.795]], - [0.317, [0.792, 0.792, 0.792]], - [0.319, [0.792, 0.792, 0.792]], - [0.321, [0.788, 0.788, 0.788]], - [0.323, [0.788, 0.788, 0.788]], - [0.325, [0.785, 0.785, 0.785]], - [0.327, [0.785, 0.785, 0.785]], - [0.329, [0.781, 0.781, 0.781]], - [0.331, [0.781, 0.781, 0.781]], - [0.333, [0.778, 0.778, 0.778]], - [0.335, [0.778, 0.778, 0.778]], - [0.337, [0.774, 0.774, 0.774]], - [0.339, [0.774, 0.774, 0.774]], [0.341, [0.771, 0.771, 0.771]], - [0.342, [0.771, 0.771, 0.771]], - [0.344, [0.767, 0.767, 0.767]], - [0.346, [0.767, 0.767, 0.767]], - [0.348, [0.764, 0.764, 0.764]], - [0.35, [0.764, 0.764, 0.764]], - [0.352, [0.761, 0.761, 0.761]], - [0.354, [0.761, 0.761, 0.761]], - [0.356, [0.757, 0.757, 0.757]], - [0.358, [0.757, 0.757, 0.757]], - [0.36, [0.754, 0.754, 0.754]], - [0.362, [0.754, 0.754, 0.754]], - [0.364, [0.75, 0.75, 0.75]], - [0.366, [0.75, 0.75, 0.75]], - [0.368, [0.747, 0.747, 0.747]], - [0.37, [0.747, 0.747, 0.747]], - [0.372, [0.743, 0.743, 0.743]], - [0.374, [0.743, 0.743, 0.743]], - [0.376, [0.739, 0.739, 0.739]], - [0.378, [0.739, 0.739, 0.739]], - [0.38, [0.735, 0.735, 0.735]], - [0.382, [0.735, 0.735, 0.735]], - [0.384, [0.73, 0.73, 0.73]], - [0.386, [0.73, 0.73, 0.73]], - [0.387, [0.725, 0.725, 0.725]], - [0.389, [0.725, 0.725, 0.725]], - [0.391, [0.72, 0.72, 0.72]], - [0.393, [0.72, 0.72, 0.72]], - [0.395, [0.715, 0.715, 0.715]], - [0.397, [0.715, 0.715, 0.715]], - [0.399, [0.711, 0.711, 0.711]], - [0.401, [0.711, 0.711, 0.711]], - [0.403, [0.706, 0.706, 0.706]], - [0.405, [0.706, 0.706, 0.706]], - [0.407, [0.701, 0.701, 0.701]], - [0.409, [0.701, 0.701, 0.701]], - [0.411, [0.696, 0.696, 0.696]], - [0.413, [0.696, 0.696, 0.696]], - [0.415, [0.691, 0.691, 0.691]], - [0.417, [0.691, 0.691, 0.691]], - [0.419, [0.687, 0.687, 0.687]], - [0.421, [0.687, 0.687, 0.687]], - [0.423, [0.682, 0.682, 0.682]], - [0.425, [0.682, 0.682, 0.682]], - [0.427, [0.677, 0.677, 0.677]], - [0.429, [0.677, 0.677, 0.677]], - [0.431, [0.672, 0.672, 0.672]], - [0.432, [0.672, 0.672, 0.672]], - [0.434, [0.667, 0.667, 0.667]], - [0.436, [0.667, 0.667, 0.667]], - [0.438, [0.663, 0.663, 0.663]], - [0.44, [0.663, 0.663, 0.663]], - [0.442, [0.658, 0.658, 0.658]], - [0.444, [0.658, 0.658, 0.658]], - [0.446, [0.653, 0.653, 0.653]], - [0.448, [0.653, 0.653, 0.653]], - [0.45, [0.648, 0.648, 0.648]], - [0.452, [0.648, 0.648, 0.648]], [0.454, [0.643, 0.643, 0.643]], - [0.456, [0.643, 0.643, 0.643]], - [0.458, [0.639, 0.639, 0.639]], - [0.46, [0.639, 0.639, 0.639]], - [0.462, [0.634, 0.634, 0.634]], - [0.464, [0.634, 0.634, 0.634]], - [0.466, [0.629, 0.629, 0.629]], - [0.468, [0.629, 0.629, 0.629]], - [0.47, [0.624, 0.624, 0.624]], - [0.472, [0.624, 0.624, 0.624]], - [0.474, [0.619, 0.619, 0.619]], - [0.476, [0.619, 0.619, 0.619]], - [0.477, [0.615, 0.615, 0.615]], - [0.479, [0.615, 0.615, 0.615]], - [0.481, [0.61, 0.61, 0.61]], - [0.483, [0.61, 0.61, 0.61]], - [0.485, [0.605, 0.605, 0.605]], - [0.487, [0.605, 0.605, 0.605]], - [0.489, [0.6, 0.6, 0.6]], - [0.491, [0.6, 0.6, 0.6]], - [0.493, [0.595, 0.595, 0.595]], - [0.495, [0.595, 0.595, 0.595]], - [0.497, [0.591, 0.591, 0.591]], - [0.499, [0.591, 0.591, 0.591]], - [0.501, [0.586, 0.586, 0.586]], - [0.503, [0.586, 0.586, 0.586]], - [0.505, [0.582, 0.582, 0.582]], - [0.507, [0.582, 0.582, 0.582]], - [0.509, [0.577, 0.577, 0.577]], - [0.511, [0.577, 0.577, 0.577]], - [0.513, [0.573, 0.573, 0.573]], - [0.515, [0.573, 0.573, 0.573]], - [0.517, [0.569, 0.569, 0.569]], - [0.519, [0.569, 0.569, 0.569]], - [0.521, [0.565, 0.565, 0.565]], - [0.523, [0.565, 0.565, 0.565]], - [0.524, [0.56, 0.56, 0.56]], - [0.526, [0.56, 0.56, 0.56]], - [0.528, [0.556, 0.556, 0.556]], - [0.53, [0.556, 0.556, 0.556]], - [0.532, [0.552, 0.552, 0.552]], - [0.534, [0.552, 0.552, 0.552]], - [0.536, [0.547, 0.547, 0.547]], - [0.538, [0.547, 0.547, 0.547]], - [0.54, [0.543, 0.543, 0.543]], - [0.542, [0.543, 0.543, 0.543]], - [0.544, [0.539, 0.539, 0.539]], - [0.546, [0.539, 0.539, 0.539]], - [0.548, [0.534, 0.534, 0.534]], - [0.55, [0.534, 0.534, 0.534]], - [0.552, [0.53, 0.53, 0.53]], - [0.554, [0.53, 0.53, 0.53]], - [0.556, [0.526, 0.526, 0.526]], - [0.558, [0.526, 0.526, 0.526]], - [0.56, [0.521, 0.521, 0.521]], - [0.562, [0.521, 0.521, 0.521]], - [0.564, [0.517, 0.517, 0.517]], - [0.566, [0.517, 0.517, 0.517]], [0.568, [0.513, 0.513, 0.513]], - [0.569, [0.513, 0.513, 0.513]], - [0.571, [0.509, 0.509, 0.509]], - [0.573, [0.509, 0.509, 0.509]], - [0.575, [0.504, 0.504, 0.504]], - [0.577, [0.504, 0.504, 0.504]], - [0.579, [0.5, 0.5, 0.5]], - [0.581, [0.5, 0.5, 0.5]], - [0.583, [0.496, 0.496, 0.496]], - [0.585, [0.496, 0.496, 0.496]], - [0.587, [0.491, 0.491, 0.491]], - [0.589, [0.491, 0.491, 0.491]], - [0.591, [0.487, 0.487, 0.487]], - [0.593, [0.487, 0.487, 0.487]], - [0.595, [0.483, 0.483, 0.483]], - [0.597, [0.483, 0.483, 0.483]], - [0.599, [0.478, 0.478, 0.478]], - [0.601, [0.478, 0.478, 0.478]], - [0.603, [0.474, 0.474, 0.474]], - [0.605, [0.474, 0.474, 0.474]], - [0.607, [0.47, 0.47, 0.47]], - [0.609, [0.47, 0.47, 0.47]], - [0.611, [0.466, 0.466, 0.466]], - [0.613, [0.466, 0.466, 0.466]], - [0.614, [0.461, 0.461, 0.461]], - [0.616, [0.461, 0.461, 0.461]], - [0.618, [0.457, 0.457, 0.457]], - [0.62, [0.457, 0.457, 0.457]], - [0.622, [0.453, 0.453, 0.453]], - [0.624, [0.453, 0.453, 0.453]], - [0.626, [0.448, 0.448, 0.448]], - [0.628, [0.448, 0.448, 0.448]], - [0.63, [0.444, 0.444, 0.444]], - [0.632, [0.444, 0.444, 0.444]], - [0.634, [0.44, 0.44, 0.44]], - [0.636, [0.44, 0.44, 0.44]], - [0.638, [0.436, 0.436, 0.436]], - [0.64, [0.436, 0.436, 0.436]], - [0.642, [0.432, 0.432, 0.432]], - [0.644, [0.432, 0.432, 0.432]], - [0.646, [0.428, 0.428, 0.428]], - [0.648, [0.428, 0.428, 0.428]], - [0.65, [0.424, 0.424, 0.424]], - [0.652, [0.424, 0.424, 0.424]], - [0.654, [0.42, 0.42, 0.42]], - [0.656, [0.42, 0.42, 0.42]], - [0.658, [0.416, 0.416, 0.416]], - [0.659, [0.416, 0.416, 0.416]], - [0.661, [0.412, 0.412, 0.412]], - [0.663, [0.412, 0.412, 0.412]], - [0.665, [0.408, 0.408, 0.408]], - [0.667, [0.408, 0.408, 0.408]], - [0.669, [0.404, 0.404, 0.404]], - [0.671, [0.404, 0.404, 0.404]], - [0.673, [0.4, 0.4, 0.4]], - [0.675, [0.4, 0.4, 0.4]], - [0.677, [0.396, 0.396, 0.396]], - [0.679, [0.396, 0.396, 0.396]], [0.681, [0.392, 0.392, 0.392]], - [0.683, [0.392, 0.392, 0.392]], - [0.685, [0.388, 0.388, 0.388]], - [0.687, [0.388, 0.388, 0.388]], - [0.689, [0.383, 0.383, 0.383]], - [0.691, [0.383, 0.383, 0.383]], - [0.693, [0.379, 0.379, 0.379]], - [0.695, [0.379, 0.379, 0.379]], - [0.697, [0.375, 0.375, 0.375]], - [0.699, [0.375, 0.375, 0.375]], - [0.701, [0.371, 0.371, 0.371]], - [0.703, [0.371, 0.371, 0.371]], - [0.705, [0.367, 0.367, 0.367]], - [0.706, [0.367, 0.367, 0.367]], - [0.708, [0.363, 0.363, 0.363]], - [0.71, [0.363, 0.363, 0.363]], - [0.712, [0.359, 0.359, 0.359]], - [0.714, [0.359, 0.359, 0.359]], - [0.716, [0.355, 0.355, 0.355]], - [0.718, [0.355, 0.355, 0.355]], - [0.72, [0.351, 0.351, 0.351]], - [0.722, [0.351, 0.351, 0.351]], - [0.724, [0.347, 0.347, 0.347]], - [0.726, [0.347, 0.347, 0.347]], - [0.728, [0.343, 0.343, 0.343]], - [0.73, [0.343, 0.343, 0.343]], - [0.732, [0.339, 0.339, 0.339]], - [0.734, [0.339, 0.339, 0.339]], - [0.736, [0.335, 0.335, 0.335]], - [0.738, [0.335, 0.335, 0.335]], - [0.74, [0.331, 0.331, 0.331]], - [0.742, [0.331, 0.331, 0.331]], - [0.744, [0.327, 0.327, 0.327]], - [0.746, [0.327, 0.327, 0.327]], - [0.748, [0.323, 0.323, 0.323]], - [0.75, [0.323, 0.323, 0.323]], - [0.751, [0.317, 0.317, 0.317]], - [0.753, [0.317, 0.317, 0.317]], - [0.755, [0.312, 0.312, 0.312]], - [0.757, [0.312, 0.312, 0.312]], - [0.759, [0.306, 0.306, 0.306]], - [0.761, [0.306, 0.306, 0.306]], - [0.763, [0.301, 0.301, 0.301]], - [0.765, [0.301, 0.301, 0.301]], - [0.767, [0.295, 0.295, 0.295]], - [0.769, [0.295, 0.295, 0.295]], - [0.771, [0.29, 0.29, 0.29]], - [0.773, [0.29, 0.29, 0.29]], - [0.775, [0.284, 0.284, 0.284]], - [0.777, [0.284, 0.284, 0.284]], - [0.779, [0.279, 0.279, 0.279]], - [0.781, [0.279, 0.279, 0.279]], - [0.783, [0.273, 0.273, 0.273]], - [0.785, [0.273, 0.273, 0.273]], - [0.787, [0.268, 0.268, 0.268]], - [0.789, [0.268, 0.268, 0.268]], - [0.791, [0.262, 0.262, 0.262]], - [0.793, [0.262, 0.262, 0.262]], [0.795, [0.257, 0.257, 0.257]], - [0.796, [0.257, 0.257, 0.257]], - [0.798, [0.251, 0.251, 0.251]], - [0.8, [0.251, 0.251, 0.251]], - [0.802, [0.245, 0.245, 0.245]], - [0.804, [0.245, 0.245, 0.245]], - [0.806, [0.24, 0.24, 0.24]], - [0.808, [0.24, 0.24, 0.24]], - [0.81, [0.234, 0.234, 0.234]], - [0.812, [0.234, 0.234, 0.234]], - [0.814, [0.229, 0.229, 0.229]], - [0.816, [0.229, 0.229, 0.229]], - [0.818, [0.223, 0.223, 0.223]], - [0.82, [0.223, 0.223, 0.223]], - [0.822, [0.218, 0.218, 0.218]], - [0.824, [0.218, 0.218, 0.218]], - [0.826, [0.212, 0.212, 0.212]], - [0.828, [0.212, 0.212, 0.212]], - [0.83, [0.207, 0.207, 0.207]], - [0.832, [0.207, 0.207, 0.207]], - [0.834, [0.201, 0.201, 0.201]], - [0.836, [0.201, 0.201, 0.201]], - [0.838, [0.196, 0.196, 0.196]], - [0.84, [0.196, 0.196, 0.196]], - [0.841, [0.19, 0.19, 0.19]], - [0.843, [0.19, 0.19, 0.19]], - [0.845, [0.185, 0.185, 0.185]], - [0.847, [0.185, 0.185, 0.185]], - [0.849, [0.179, 0.179, 0.179]], - [0.851, [0.179, 0.179, 0.179]], - [0.853, [0.173, 0.173, 0.173]], - [0.855, [0.173, 0.173, 0.173]], - [0.857, [0.168, 0.168, 0.168]], - [0.859, [0.168, 0.168, 0.168]], - [0.861, [0.162, 0.162, 0.162]], - [0.863, [0.162, 0.162, 0.162]], - [0.865, [0.157, 0.157, 0.157]], - [0.867, [0.157, 0.157, 0.157]], - [0.869, [0.151, 0.151, 0.151]], - [0.871, [0.151, 0.151, 0.151]], - [0.873, [0.146, 0.146, 0.146]], - [0.875, [0.146, 0.146, 0.146]], - [0.877, [0.141, 0.141, 0.141]], - [0.879, [0.141, 0.141, 0.141]], - [0.881, [0.137, 0.137, 0.137]], - [0.883, [0.137, 0.137, 0.137]], - [0.885, [0.132, 0.132, 0.132]], - [0.886, [0.132, 0.132, 0.132]], - [0.888, [0.127, 0.127, 0.127]], - [0.89, [0.127, 0.127, 0.127]], - [0.892, [0.123, 0.123, 0.123]], - [0.894, [0.123, 0.123, 0.123]], - [0.896, [0.118, 0.118, 0.118]], - [0.898, [0.118, 0.118, 0.118]], - [0.9, [0.114, 0.114, 0.114]], - [0.902, [0.114, 0.114, 0.114]], - [0.904, [0.109, 0.109, 0.109]], - [0.906, [0.109, 0.109, 0.109]], [0.908, [0.105, 0.105, 0.105]], - [0.91, [0.105, 0.105, 0.105]], - [0.912, [0.1, 0.1, 0.1]], - [0.914, [0.1, 0.1, 0.1]], - [0.916, [0.096, 0.096, 0.096]], - [0.918, [0.096, 0.096, 0.096]], - [0.92, [0.091, 0.091, 0.091]], - [0.922, [0.091, 0.091, 0.091]], - [0.924, [0.086, 0.086, 0.086]], - [0.926, [0.086, 0.086, 0.086]], - [0.928, [0.082, 0.082, 0.082]], - [0.93, [0.082, 0.082, 0.082]], - [0.932, [0.077, 0.077, 0.077]], - [0.933, [0.077, 0.077, 0.077]], - [0.935, [0.073, 0.073, 0.073]], - [0.937, [0.073, 0.073, 0.073]], - [0.939, [0.068, 0.068, 0.068]], - [0.941, [0.068, 0.068, 0.068]], - [0.943, [0.064, 0.064, 0.064]], - [0.945, [0.064, 0.064, 0.064]], - [0.947, [0.059, 0.059, 0.059]], - [0.949, [0.059, 0.059, 0.059]], - [0.951, [0.055, 0.055, 0.055]], - [0.953, [0.055, 0.055, 0.055]], - [0.955, [0.05, 0.05, 0.05]], - [0.957, [0.05, 0.05, 0.05]], - [0.959, [0.046, 0.046, 0.046]], - [0.961, [0.046, 0.046, 0.046]], - [0.963, [0.041, 0.041, 0.041]], - [0.965, [0.041, 0.041, 0.041]], - [0.967, [0.036, 0.036, 0.036]], - [0.969, [0.036, 0.036, 0.036]], - [0.971, [0.032, 0.032, 0.032]], - [0.973, [0.032, 0.032, 0.032]], - [0.975, [0.027, 0.027, 0.027]], - [0.977, [0.027, 0.027, 0.027]], - [0.978, [0.023, 0.023, 0.023]], - [0.98, [0.023, 0.023, 0.023]], - [0.982, [0.018, 0.018, 0.018]], - [0.984, [0.018, 0.018, 0.018]], - [0.986, [0.014, 0.014, 0.014]], - [0.988, [0.014, 0.014, 0.014]], - [0.99, [0.009, 0.009, 0.009]], - [0.992, [0.009, 0.009, 0.009]], - [0.994, [0.005, 0.005, 0.005]], - [0.996, [0.005, 0.005, 0.005]], - [0.998, [0.0, 0.0, 0.0]], - [1.0, [0.0, 0.0, 0.0]], + [1, [0, 0, 0]], ], }, [ColorSchemas.Reds]: { @@ -1600,518 +94,16 @@ export const vislibColorMaps: ColorMap = { defaultMessage: 'Reds', }), value: [ - [0.0, [1.0, 0.961, 0.941]], - [0.002, [1.0, 0.961, 0.941]], - [0.004, [1.0, 0.958, 0.937]], - [0.006, [1.0, 0.958, 0.937]], - [0.008, [1.0, 0.956, 0.934]], - [0.01, [1.0, 0.956, 0.934]], - [0.012, [1.0, 0.953, 0.93]], - [0.014, [1.0, 0.953, 0.93]], - [0.016, [1.0, 0.95, 0.926]], - [0.018, [1.0, 0.95, 0.926]], - [0.02, [0.999, 0.948, 0.923]], - [0.022, [0.999, 0.948, 0.923]], - [0.023, [0.999, 0.945, 0.919]], - [0.025, [0.999, 0.945, 0.919]], - [0.027, [0.999, 0.943, 0.915]], - [0.029, [0.999, 0.943, 0.915]], - [0.031, [0.999, 0.94, 0.912]], - [0.033, [0.999, 0.94, 0.912]], - [0.035, [0.999, 0.938, 0.908]], - [0.037, [0.999, 0.938, 0.908]], - [0.039, [0.999, 0.935, 0.904]], - [0.041, [0.999, 0.935, 0.904]], - [0.043, [0.999, 0.932, 0.901]], - [0.045, [0.999, 0.932, 0.901]], - [0.047, [0.999, 0.93, 0.897]], - [0.049, [0.999, 0.93, 0.897]], - [0.051, [0.998, 0.927, 0.893]], - [0.053, [0.998, 0.927, 0.893]], - [0.055, [0.998, 0.925, 0.89]], - [0.057, [0.998, 0.925, 0.89]], - [0.059, [0.998, 0.922, 0.886]], - [0.061, [0.998, 0.922, 0.886]], - [0.063, [0.998, 0.919, 0.882]], - [0.065, [0.998, 0.919, 0.882]], - [0.067, [0.998, 0.917, 0.878]], - [0.068, [0.998, 0.917, 0.878]], - [0.07, [0.998, 0.914, 0.875]], - [0.072, [0.998, 0.914, 0.875]], - [0.074, [0.998, 0.912, 0.871]], - [0.076, [0.998, 0.912, 0.871]], - [0.078, [0.998, 0.909, 0.867]], - [0.08, [0.998, 0.909, 0.867]], - [0.082, [0.997, 0.907, 0.864]], - [0.084, [0.997, 0.907, 0.864]], - [0.086, [0.997, 0.904, 0.86]], - [0.088, [0.997, 0.904, 0.86]], - [0.09, [0.997, 0.901, 0.856]], - [0.092, [0.997, 0.901, 0.856]], - [0.094, [0.997, 0.899, 0.853]], - [0.096, [0.997, 0.899, 0.853]], - [0.098, [0.997, 0.896, 0.849]], - [0.1, [0.997, 0.896, 0.849]], - [0.102, [0.997, 0.894, 0.845]], - [0.104, [0.997, 0.894, 0.845]], - [0.106, [0.997, 0.891, 0.842]], - [0.108, [0.997, 0.891, 0.842]], - [0.11, [0.997, 0.888, 0.838]], - [0.112, [0.997, 0.888, 0.838]], + [0, [1, 0.961, 0.941]], [0.114, [0.996, 0.886, 0.834]], - [0.115, [0.996, 0.886, 0.834]], - [0.117, [0.996, 0.883, 0.83]], - [0.119, [0.996, 0.883, 0.83]], - [0.121, [0.996, 0.881, 0.827]], - [0.123, [0.996, 0.881, 0.827]], - [0.125, [0.996, 0.878, 0.823]], - [0.127, [0.996, 0.878, 0.823]], - [0.129, [0.996, 0.873, 0.817]], - [0.131, [0.996, 0.873, 0.817]], - [0.133, [0.996, 0.869, 0.811]], - [0.135, [0.996, 0.869, 0.811]], - [0.137, [0.995, 0.864, 0.805]], - [0.139, [0.995, 0.864, 0.805]], - [0.141, [0.995, 0.86, 0.799]], - [0.143, [0.995, 0.86, 0.799]], - [0.145, [0.995, 0.855, 0.793]], - [0.147, [0.995, 0.855, 0.793]], - [0.149, [0.995, 0.851, 0.787]], - [0.151, [0.995, 0.851, 0.787]], - [0.153, [0.994, 0.846, 0.781]], - [0.155, [0.994, 0.846, 0.781]], - [0.157, [0.994, 0.841, 0.775]], - [0.159, [0.994, 0.841, 0.775]], - [0.16, [0.994, 0.837, 0.769]], - [0.162, [0.994, 0.837, 0.769]], - [0.164, [0.994, 0.832, 0.762]], - [0.166, [0.994, 0.832, 0.762]], - [0.168, [0.993, 0.828, 0.756]], - [0.17, [0.993, 0.828, 0.756]], - [0.172, [0.993, 0.823, 0.75]], - [0.174, [0.993, 0.823, 0.75]], - [0.176, [0.993, 0.819, 0.744]], - [0.178, [0.993, 0.819, 0.744]], - [0.18, [0.993, 0.814, 0.738]], - [0.182, [0.993, 0.814, 0.738]], - [0.184, [0.992, 0.81, 0.732]], - [0.186, [0.992, 0.81, 0.732]], - [0.188, [0.992, 0.805, 0.726]], - [0.19, [0.992, 0.805, 0.726]], - [0.192, [0.992, 0.8, 0.72]], - [0.194, [0.992, 0.8, 0.72]], - [0.196, [0.992, 0.796, 0.714]], - [0.198, [0.992, 0.796, 0.714]], - [0.2, [0.991, 0.791, 0.708]], - [0.202, [0.991, 0.791, 0.708]], - [0.204, [0.991, 0.787, 0.702]], - [0.205, [0.991, 0.787, 0.702]], - [0.207, [0.991, 0.782, 0.696]], - [0.209, [0.991, 0.782, 0.696]], - [0.211, [0.991, 0.778, 0.69]], - [0.213, [0.991, 0.778, 0.69]], - [0.215, [0.99, 0.773, 0.684]], - [0.217, [0.99, 0.773, 0.684]], - [0.219, [0.99, 0.769, 0.678]], - [0.221, [0.99, 0.769, 0.678]], - [0.223, [0.99, 0.764, 0.672]], - [0.225, [0.99, 0.764, 0.672]], [0.227, [0.99, 0.76, 0.666]], - [0.229, [0.99, 0.76, 0.666]], - [0.231, [0.989, 0.755, 0.66]], - [0.233, [0.989, 0.755, 0.66]], - [0.235, [0.989, 0.75, 0.654]], - [0.237, [0.989, 0.75, 0.654]], - [0.239, [0.989, 0.746, 0.648]], - [0.241, [0.989, 0.746, 0.648]], - [0.243, [0.989, 0.741, 0.642]], - [0.245, [0.989, 0.741, 0.642]], - [0.247, [0.988, 0.737, 0.636]], - [0.249, [0.988, 0.737, 0.636]], - [0.25, [0.988, 0.732, 0.63]], - [0.252, [0.988, 0.732, 0.63]], - [0.254, [0.988, 0.727, 0.624]], - [0.256, [0.988, 0.727, 0.624]], - [0.258, [0.988, 0.722, 0.618]], - [0.26, [0.988, 0.722, 0.618]], - [0.262, [0.988, 0.717, 0.613]], - [0.264, [0.988, 0.717, 0.613]], - [0.266, [0.988, 0.712, 0.607]], - [0.268, [0.988, 0.712, 0.607]], - [0.27, [0.988, 0.707, 0.601]], - [0.272, [0.988, 0.707, 0.601]], - [0.274, [0.988, 0.702, 0.595]], - [0.276, [0.988, 0.702, 0.595]], - [0.278, [0.988, 0.697, 0.589]], - [0.28, [0.988, 0.697, 0.589]], - [0.282, [0.988, 0.692, 0.584]], - [0.284, [0.988, 0.692, 0.584]], - [0.286, [0.988, 0.687, 0.578]], - [0.288, [0.988, 0.687, 0.578]], - [0.29, [0.988, 0.682, 0.572]], - [0.292, [0.988, 0.682, 0.572]], - [0.294, [0.988, 0.677, 0.566]], - [0.295, [0.988, 0.677, 0.566]], - [0.297, [0.988, 0.672, 0.561]], - [0.299, [0.988, 0.672, 0.561]], - [0.301, [0.988, 0.666, 0.555]], - [0.303, [0.988, 0.666, 0.555]], - [0.305, [0.988, 0.661, 0.549]], - [0.307, [0.988, 0.661, 0.549]], - [0.309, [0.988, 0.656, 0.543]], - [0.311, [0.988, 0.656, 0.543]], - [0.313, [0.988, 0.651, 0.537]], - [0.315, [0.988, 0.651, 0.537]], - [0.317, [0.988, 0.646, 0.532]], - [0.319, [0.988, 0.646, 0.532]], - [0.321, [0.988, 0.641, 0.526]], - [0.323, [0.988, 0.641, 0.526]], - [0.325, [0.988, 0.636, 0.52]], - [0.327, [0.988, 0.636, 0.52]], - [0.329, [0.988, 0.631, 0.514]], - [0.331, [0.988, 0.631, 0.514]], - [0.333, [0.988, 0.626, 0.508]], - [0.335, [0.988, 0.626, 0.508]], - [0.337, [0.988, 0.621, 0.503]], - [0.339, [0.988, 0.621, 0.503]], [0.341, [0.988, 0.616, 0.497]], - [0.342, [0.988, 0.616, 0.497]], - [0.344, [0.988, 0.611, 0.491]], - [0.346, [0.988, 0.611, 0.491]], - [0.348, [0.988, 0.606, 0.485]], - [0.35, [0.988, 0.606, 0.485]], - [0.352, [0.988, 0.601, 0.48]], - [0.354, [0.988, 0.601, 0.48]], - [0.356, [0.988, 0.596, 0.474]], - [0.358, [0.988, 0.596, 0.474]], - [0.36, [0.988, 0.591, 0.468]], - [0.362, [0.988, 0.591, 0.468]], - [0.364, [0.988, 0.586, 0.462]], - [0.366, [0.988, 0.586, 0.462]], - [0.368, [0.988, 0.581, 0.456]], - [0.37, [0.988, 0.581, 0.456]], - [0.372, [0.988, 0.576, 0.451]], - [0.374, [0.988, 0.576, 0.451]], - [0.376, [0.988, 0.571, 0.445]], - [0.378, [0.988, 0.571, 0.445]], - [0.38, [0.988, 0.566, 0.44]], - [0.382, [0.988, 0.566, 0.44]], - [0.384, [0.988, 0.561, 0.435]], - [0.386, [0.988, 0.561, 0.435]], - [0.387, [0.988, 0.556, 0.43]], - [0.389, [0.988, 0.556, 0.43]], - [0.391, [0.988, 0.551, 0.426]], - [0.393, [0.988, 0.551, 0.426]], - [0.395, [0.988, 0.546, 0.421]], - [0.397, [0.988, 0.546, 0.421]], - [0.399, [0.987, 0.541, 0.416]], - [0.401, [0.987, 0.541, 0.416]], - [0.403, [0.987, 0.536, 0.411]], - [0.405, [0.987, 0.536, 0.411]], - [0.407, [0.987, 0.531, 0.406]], - [0.409, [0.987, 0.531, 0.406]], - [0.411, [0.987, 0.526, 0.401]], - [0.413, [0.987, 0.526, 0.401]], - [0.415, [0.987, 0.521, 0.396]], - [0.417, [0.987, 0.521, 0.396]], - [0.419, [0.987, 0.517, 0.391]], - [0.421, [0.987, 0.517, 0.391]], - [0.423, [0.987, 0.512, 0.386]], - [0.425, [0.987, 0.512, 0.386]], - [0.427, [0.987, 0.507, 0.381]], - [0.429, [0.987, 0.507, 0.381]], - [0.431, [0.986, 0.502, 0.376]], - [0.432, [0.986, 0.502, 0.376]], - [0.434, [0.986, 0.497, 0.371]], - [0.436, [0.986, 0.497, 0.371]], - [0.438, [0.986, 0.492, 0.366]], - [0.44, [0.986, 0.492, 0.366]], - [0.442, [0.986, 0.487, 0.362]], - [0.444, [0.986, 0.487, 0.362]], - [0.446, [0.986, 0.482, 0.357]], - [0.448, [0.986, 0.482, 0.357]], - [0.45, [0.986, 0.477, 0.352]], - [0.452, [0.986, 0.477, 0.352]], [0.454, [0.986, 0.472, 0.347]], - [0.456, [0.986, 0.472, 0.347]], - [0.458, [0.986, 0.467, 0.342]], - [0.46, [0.986, 0.467, 0.342]], - [0.462, [0.985, 0.462, 0.337]], - [0.464, [0.985, 0.462, 0.337]], - [0.466, [0.985, 0.458, 0.332]], - [0.468, [0.985, 0.458, 0.332]], - [0.47, [0.985, 0.453, 0.327]], - [0.472, [0.985, 0.453, 0.327]], - [0.474, [0.985, 0.448, 0.322]], - [0.476, [0.985, 0.448, 0.322]], - [0.477, [0.985, 0.443, 0.317]], - [0.479, [0.985, 0.443, 0.317]], - [0.481, [0.985, 0.438, 0.312]], - [0.483, [0.985, 0.438, 0.312]], - [0.485, [0.985, 0.433, 0.307]], - [0.487, [0.985, 0.433, 0.307]], - [0.489, [0.985, 0.428, 0.302]], - [0.491, [0.985, 0.428, 0.302]], - [0.493, [0.984, 0.423, 0.298]], - [0.495, [0.984, 0.423, 0.298]], - [0.497, [0.984, 0.418, 0.293]], - [0.499, [0.984, 0.418, 0.293]], - [0.501, [0.984, 0.413, 0.288]], - [0.503, [0.984, 0.413, 0.288]], - [0.505, [0.982, 0.407, 0.285]], - [0.507, [0.982, 0.407, 0.285]], - [0.509, [0.981, 0.401, 0.281]], - [0.511, [0.981, 0.401, 0.281]], - [0.513, [0.979, 0.395, 0.277]], - [0.515, [0.979, 0.395, 0.277]], - [0.517, [0.978, 0.39, 0.274]], - [0.519, [0.978, 0.39, 0.274]], - [0.521, [0.976, 0.384, 0.27]], - [0.523, [0.976, 0.384, 0.27]], - [0.524, [0.975, 0.378, 0.266]], - [0.526, [0.975, 0.378, 0.266]], - [0.528, [0.973, 0.372, 0.263]], - [0.53, [0.973, 0.372, 0.263]], - [0.532, [0.972, 0.367, 0.259]], - [0.534, [0.972, 0.367, 0.259]], - [0.536, [0.97, 0.361, 0.255]], - [0.538, [0.97, 0.361, 0.255]], - [0.54, [0.969, 0.355, 0.251]], - [0.542, [0.969, 0.355, 0.251]], - [0.544, [0.967, 0.349, 0.248]], - [0.546, [0.967, 0.349, 0.248]], - [0.548, [0.966, 0.343, 0.244]], - [0.55, [0.966, 0.343, 0.244]], - [0.552, [0.964, 0.338, 0.24]], - [0.554, [0.964, 0.338, 0.24]], - [0.556, [0.963, 0.332, 0.237]], - [0.558, [0.963, 0.332, 0.237]], - [0.56, [0.961, 0.326, 0.233]], - [0.562, [0.961, 0.326, 0.233]], - [0.564, [0.96, 0.32, 0.229]], - [0.566, [0.96, 0.32, 0.229]], [0.568, [0.958, 0.314, 0.226]], - [0.569, [0.958, 0.314, 0.226]], - [0.571, [0.957, 0.309, 0.222]], - [0.573, [0.957, 0.309, 0.222]], - [0.575, [0.956, 0.303, 0.218]], - [0.577, [0.956, 0.303, 0.218]], - [0.579, [0.954, 0.297, 0.215]], - [0.581, [0.954, 0.297, 0.215]], - [0.583, [0.953, 0.291, 0.211]], - [0.585, [0.953, 0.291, 0.211]], - [0.587, [0.951, 0.286, 0.207]], - [0.589, [0.951, 0.286, 0.207]], - [0.591, [0.95, 0.28, 0.203]], - [0.593, [0.95, 0.28, 0.203]], - [0.595, [0.948, 0.274, 0.2]], - [0.597, [0.948, 0.274, 0.2]], - [0.599, [0.947, 0.268, 0.196]], - [0.601, [0.947, 0.268, 0.196]], - [0.603, [0.945, 0.262, 0.192]], - [0.605, [0.945, 0.262, 0.192]], - [0.607, [0.944, 0.257, 0.189]], - [0.609, [0.944, 0.257, 0.189]], - [0.611, [0.942, 0.251, 0.185]], - [0.613, [0.942, 0.251, 0.185]], - [0.614, [0.941, 0.245, 0.181]], - [0.616, [0.941, 0.245, 0.181]], - [0.618, [0.939, 0.239, 0.178]], - [0.62, [0.939, 0.239, 0.178]], - [0.622, [0.938, 0.234, 0.174]], - [0.624, [0.938, 0.234, 0.174]], - [0.626, [0.934, 0.229, 0.171]], - [0.628, [0.934, 0.229, 0.171]], - [0.63, [0.93, 0.224, 0.17]], - [0.632, [0.93, 0.224, 0.17]], - [0.634, [0.926, 0.22, 0.168]], - [0.636, [0.926, 0.22, 0.168]], - [0.638, [0.921, 0.216, 0.166]], - [0.64, [0.921, 0.216, 0.166]], - [0.642, [0.917, 0.211, 0.164]], - [0.644, [0.917, 0.211, 0.164]], - [0.646, [0.912, 0.207, 0.162]], - [0.648, [0.912, 0.207, 0.162]], - [0.65, [0.908, 0.203, 0.16]], - [0.652, [0.908, 0.203, 0.16]], - [0.654, [0.903, 0.199, 0.158]], - [0.656, [0.903, 0.199, 0.158]], - [0.658, [0.899, 0.194, 0.157]], - [0.659, [0.899, 0.194, 0.157]], - [0.661, [0.895, 0.19, 0.155]], - [0.663, [0.895, 0.19, 0.155]], - [0.665, [0.89, 0.186, 0.153]], - [0.667, [0.89, 0.186, 0.153]], - [0.669, [0.886, 0.181, 0.151]], - [0.671, [0.886, 0.181, 0.151]], - [0.673, [0.881, 0.177, 0.149]], - [0.675, [0.881, 0.177, 0.149]], - [0.677, [0.877, 0.173, 0.147]], - [0.679, [0.877, 0.173, 0.147]], [0.681, [0.872, 0.168, 0.146]], - [0.683, [0.872, 0.168, 0.146]], - [0.685, [0.868, 0.164, 0.144]], - [0.687, [0.868, 0.164, 0.144]], - [0.689, [0.864, 0.16, 0.142]], - [0.691, [0.864, 0.16, 0.142]], - [0.693, [0.859, 0.155, 0.14]], - [0.695, [0.859, 0.155, 0.14]], - [0.697, [0.855, 0.151, 0.138]], - [0.699, [0.855, 0.151, 0.138]], - [0.701, [0.85, 0.147, 0.136]], - [0.703, [0.85, 0.147, 0.136]], - [0.705, [0.846, 0.143, 0.134]], - [0.706, [0.846, 0.143, 0.134]], - [0.708, [0.841, 0.138, 0.133]], - [0.71, [0.841, 0.138, 0.133]], - [0.712, [0.837, 0.134, 0.131]], - [0.714, [0.837, 0.134, 0.131]], - [0.716, [0.833, 0.13, 0.129]], - [0.718, [0.833, 0.13, 0.129]], - [0.72, [0.828, 0.125, 0.127]], - [0.722, [0.828, 0.125, 0.127]], - [0.724, [0.824, 0.121, 0.125]], - [0.726, [0.824, 0.121, 0.125]], - [0.728, [0.819, 0.117, 0.123]], - [0.73, [0.819, 0.117, 0.123]], - [0.732, [0.815, 0.112, 0.122]], - [0.734, [0.815, 0.112, 0.122]], - [0.736, [0.81, 0.108, 0.12]], - [0.738, [0.81, 0.108, 0.12]], - [0.74, [0.806, 0.104, 0.118]], - [0.742, [0.806, 0.104, 0.118]], - [0.744, [0.802, 0.1, 0.116]], - [0.746, [0.802, 0.1, 0.116]], - [0.748, [0.797, 0.095, 0.114]], - [0.75, [0.797, 0.095, 0.114]], - [0.751, [0.793, 0.093, 0.113]], - [0.753, [0.793, 0.093, 0.113]], - [0.755, [0.788, 0.092, 0.112]], - [0.757, [0.788, 0.092, 0.112]], - [0.759, [0.783, 0.091, 0.111]], - [0.761, [0.783, 0.091, 0.111]], - [0.763, [0.779, 0.09, 0.11]], - [0.765, [0.779, 0.09, 0.11]], - [0.767, [0.774, 0.089, 0.109]], - [0.769, [0.774, 0.089, 0.109]], - [0.771, [0.769, 0.088, 0.108]], - [0.773, [0.769, 0.088, 0.108]], - [0.775, [0.765, 0.087, 0.107]], - [0.777, [0.765, 0.087, 0.107]], - [0.779, [0.76, 0.086, 0.106]], - [0.781, [0.76, 0.086, 0.106]], - [0.783, [0.755, 0.084, 0.105]], - [0.785, [0.755, 0.084, 0.105]], - [0.787, [0.75, 0.083, 0.104]], - [0.789, [0.75, 0.083, 0.104]], - [0.791, [0.746, 0.082, 0.103]], - [0.793, [0.746, 0.082, 0.103]], [0.795, [0.741, 0.081, 0.102]], - [0.796, [0.741, 0.081, 0.102]], - [0.798, [0.736, 0.08, 0.101]], - [0.8, [0.736, 0.08, 0.101]], - [0.802, [0.732, 0.079, 0.1]], - [0.804, [0.732, 0.079, 0.1]], - [0.806, [0.727, 0.078, 0.099]], - [0.808, [0.727, 0.078, 0.099]], - [0.81, [0.722, 0.077, 0.098]], - [0.812, [0.722, 0.077, 0.098]], - [0.814, [0.718, 0.076, 0.097]], - [0.816, [0.718, 0.076, 0.097]], - [0.818, [0.713, 0.074, 0.096]], - [0.82, [0.713, 0.074, 0.096]], - [0.822, [0.708, 0.073, 0.095]], - [0.824, [0.708, 0.073, 0.095]], - [0.826, [0.704, 0.072, 0.094]], - [0.828, [0.704, 0.072, 0.094]], - [0.83, [0.699, 0.071, 0.093]], - [0.832, [0.699, 0.071, 0.093]], - [0.834, [0.694, 0.07, 0.092]], - [0.836, [0.694, 0.07, 0.092]], - [0.838, [0.69, 0.069, 0.091]], - [0.84, [0.69, 0.069, 0.091]], - [0.841, [0.685, 0.068, 0.09]], - [0.843, [0.685, 0.068, 0.09]], - [0.845, [0.68, 0.067, 0.089]], - [0.847, [0.68, 0.067, 0.089]], - [0.849, [0.676, 0.066, 0.088]], - [0.851, [0.676, 0.066, 0.088]], - [0.853, [0.671, 0.064, 0.087]], - [0.855, [0.671, 0.064, 0.087]], - [0.857, [0.666, 0.063, 0.086]], - [0.859, [0.666, 0.063, 0.086]], - [0.861, [0.662, 0.062, 0.085]], - [0.863, [0.662, 0.062, 0.085]], - [0.865, [0.657, 0.061, 0.084]], - [0.867, [0.657, 0.061, 0.084]], - [0.869, [0.652, 0.06, 0.083]], - [0.871, [0.652, 0.06, 0.083]], - [0.873, [0.648, 0.059, 0.082]], - [0.875, [0.648, 0.059, 0.082]], - [0.877, [0.64, 0.057, 0.081]], - [0.879, [0.64, 0.057, 0.081]], - [0.881, [0.633, 0.055, 0.081]], - [0.883, [0.633, 0.055, 0.081]], - [0.885, [0.625, 0.054, 0.08]], - [0.886, [0.625, 0.054, 0.08]], - [0.888, [0.618, 0.052, 0.079]], - [0.89, [0.618, 0.052, 0.079]], - [0.892, [0.61, 0.05, 0.078]], - [0.894, [0.61, 0.05, 0.078]], - [0.896, [0.602, 0.048, 0.077]], - [0.898, [0.602, 0.048, 0.077]], - [0.9, [0.595, 0.046, 0.076]], - [0.902, [0.595, 0.046, 0.076]], - [0.904, [0.587, 0.044, 0.075]], - [0.906, [0.587, 0.044, 0.075]], [0.908, [0.579, 0.042, 0.074]], - [0.91, [0.579, 0.042, 0.074]], - [0.912, [0.572, 0.041, 0.073]], - [0.914, [0.572, 0.041, 0.073]], - [0.916, [0.564, 0.039, 0.072]], - [0.918, [0.564, 0.039, 0.072]], - [0.92, [0.556, 0.037, 0.071]], - [0.922, [0.556, 0.037, 0.071]], - [0.924, [0.549, 0.035, 0.07]], - [0.926, [0.549, 0.035, 0.07]], - [0.928, [0.541, 0.033, 0.069]], - [0.93, [0.541, 0.033, 0.069]], - [0.932, [0.534, 0.031, 0.068]], - [0.933, [0.534, 0.031, 0.068]], - [0.935, [0.526, 0.03, 0.067]], - [0.937, [0.526, 0.03, 0.067]], - [0.939, [0.518, 0.028, 0.066]], - [0.941, [0.518, 0.028, 0.066]], - [0.943, [0.511, 0.026, 0.065]], - [0.945, [0.511, 0.026, 0.065]], - [0.947, [0.503, 0.024, 0.064]], - [0.949, [0.503, 0.024, 0.064]], - [0.951, [0.495, 0.022, 0.063]], - [0.953, [0.495, 0.022, 0.063]], - [0.955, [0.488, 0.02, 0.062]], - [0.957, [0.488, 0.02, 0.062]], - [0.959, [0.48, 0.018, 0.061]], - [0.961, [0.48, 0.018, 0.061]], - [0.963, [0.473, 0.017, 0.06]], - [0.965, [0.473, 0.017, 0.06]], - [0.967, [0.465, 0.015, 0.059]], - [0.969, [0.465, 0.015, 0.059]], - [0.971, [0.457, 0.013, 0.058]], - [0.973, [0.457, 0.013, 0.058]], - [0.975, [0.45, 0.011, 0.057]], - [0.977, [0.45, 0.011, 0.057]], - [0.978, [0.442, 0.009, 0.056]], - [0.98, [0.442, 0.009, 0.056]], - [0.982, [0.434, 0.007, 0.055]], - [0.984, [0.434, 0.007, 0.055]], - [0.986, [0.427, 0.006, 0.054]], - [0.988, [0.427, 0.006, 0.054]], - [0.99, [0.419, 0.004, 0.053]], - [0.992, [0.419, 0.004, 0.053]], - [0.994, [0.412, 0.002, 0.052]], - [0.996, [0.412, 0.002, 0.052]], - [0.998, [0.404, 0.0, 0.051]], - [1.0, [0.404, 0.0, 0.051]], + [1, [0.404, 0, 0.051]], ], }, [ColorSchemas.YellowToRed]: { @@ -2120,518 +112,16 @@ export const vislibColorMaps: ColorMap = { defaultMessage: 'Yellow to Red', }), value: [ - [0.0, [1.0, 1.0, 0.8]], - [0.002, [1.0, 1.0, 0.8]], - [0.004, [1.0, 0.998, 0.795]], - [0.006, [1.0, 0.998, 0.795]], - [0.008, [1.0, 0.996, 0.789]], - [0.01, [1.0, 0.996, 0.789]], - [0.012, [1.0, 0.993, 0.784]], - [0.014, [1.0, 0.993, 0.784]], - [0.016, [1.0, 0.991, 0.778]], - [0.018, [1.0, 0.991, 0.778]], - [0.02, [1.0, 0.989, 0.773]], - [0.022, [1.0, 0.989, 0.773]], - [0.023, [1.0, 0.987, 0.768]], - [0.025, [1.0, 0.987, 0.768]], - [0.027, [1.0, 0.984, 0.762]], - [0.029, [1.0, 0.984, 0.762]], - [0.031, [1.0, 0.982, 0.757]], - [0.033, [1.0, 0.982, 0.757]], - [0.035, [1.0, 0.98, 0.751]], - [0.037, [1.0, 0.98, 0.751]], - [0.039, [1.0, 0.978, 0.746]], - [0.041, [1.0, 0.978, 0.746]], - [0.043, [1.0, 0.976, 0.74]], - [0.045, [1.0, 0.976, 0.74]], - [0.047, [1.0, 0.973, 0.735]], - [0.049, [1.0, 0.973, 0.735]], - [0.051, [1.0, 0.971, 0.73]], - [0.053, [1.0, 0.971, 0.73]], - [0.055, [1.0, 0.969, 0.724]], - [0.057, [1.0, 0.969, 0.724]], - [0.059, [1.0, 0.967, 0.719]], - [0.061, [1.0, 0.967, 0.719]], - [0.063, [1.0, 0.965, 0.713]], - [0.065, [1.0, 0.965, 0.713]], - [0.067, [1.0, 0.962, 0.708]], - [0.068, [1.0, 0.962, 0.708]], - [0.07, [1.0, 0.96, 0.703]], - [0.072, [1.0, 0.96, 0.703]], - [0.074, [1.0, 0.958, 0.697]], - [0.076, [1.0, 0.958, 0.697]], - [0.078, [1.0, 0.956, 0.692]], - [0.08, [1.0, 0.956, 0.692]], - [0.082, [1.0, 0.953, 0.686]], - [0.084, [1.0, 0.953, 0.686]], - [0.086, [1.0, 0.951, 0.681]], - [0.088, [1.0, 0.951, 0.681]], - [0.09, [1.0, 0.949, 0.675]], - [0.092, [1.0, 0.949, 0.675]], - [0.094, [1.0, 0.947, 0.67]], - [0.096, [1.0, 0.947, 0.67]], - [0.098, [1.0, 0.945, 0.665]], - [0.1, [1.0, 0.945, 0.665]], - [0.102, [1.0, 0.942, 0.659]], - [0.104, [1.0, 0.942, 0.659]], - [0.106, [1.0, 0.94, 0.654]], - [0.108, [1.0, 0.94, 0.654]], - [0.11, [1.0, 0.938, 0.648]], - [0.112, [1.0, 0.938, 0.648]], - [0.114, [1.0, 0.936, 0.643]], - [0.115, [1.0, 0.936, 0.643]], - [0.117, [1.0, 0.934, 0.638]], - [0.119, [1.0, 0.934, 0.638]], - [0.121, [1.0, 0.931, 0.632]], - [0.123, [1.0, 0.931, 0.632]], - [0.125, [1.0, 0.929, 0.627]], - [0.127, [1.0, 0.929, 0.627]], - [0.129, [1.0, 0.927, 0.622]], - [0.131, [1.0, 0.927, 0.622]], - [0.133, [1.0, 0.924, 0.616]], - [0.135, [1.0, 0.924, 0.616]], - [0.137, [1.0, 0.922, 0.611]], - [0.139, [1.0, 0.922, 0.611]], - [0.141, [0.999, 0.919, 0.606]], - [0.143, [0.999, 0.919, 0.606]], - [0.145, [0.999, 0.917, 0.601]], - [0.147, [0.999, 0.917, 0.601]], - [0.149, [0.999, 0.914, 0.596]], - [0.151, [0.999, 0.914, 0.596]], - [0.153, [0.999, 0.912, 0.591]], - [0.155, [0.999, 0.912, 0.591]], - [0.157, [0.999, 0.909, 0.585]], - [0.159, [0.999, 0.909, 0.585]], - [0.16, [0.999, 0.907, 0.58]], - [0.162, [0.999, 0.907, 0.58]], - [0.164, [0.999, 0.904, 0.575]], - [0.166, [0.999, 0.904, 0.575]], - [0.168, [0.999, 0.902, 0.57]], - [0.17, [0.999, 0.902, 0.57]], - [0.172, [0.999, 0.9, 0.565]], - [0.174, [0.999, 0.9, 0.565]], - [0.176, [0.998, 0.897, 0.56]], - [0.178, [0.998, 0.897, 0.56]], - [0.18, [0.998, 0.895, 0.554]], - [0.182, [0.998, 0.895, 0.554]], - [0.184, [0.998, 0.892, 0.549]], - [0.186, [0.998, 0.892, 0.549]], - [0.188, [0.998, 0.89, 0.544]], - [0.19, [0.998, 0.89, 0.544]], - [0.192, [0.998, 0.887, 0.539]], - [0.194, [0.998, 0.887, 0.539]], - [0.196, [0.998, 0.885, 0.534]], - [0.198, [0.998, 0.885, 0.534]], - [0.2, [0.998, 0.882, 0.529]], - [0.202, [0.998, 0.882, 0.529]], - [0.204, [0.998, 0.88, 0.523]], - [0.205, [0.998, 0.88, 0.523]], - [0.207, [0.997, 0.877, 0.518]], - [0.209, [0.997, 0.877, 0.518]], - [0.211, [0.997, 0.875, 0.513]], - [0.213, [0.997, 0.875, 0.513]], - [0.215, [0.997, 0.873, 0.508]], - [0.217, [0.997, 0.873, 0.508]], - [0.219, [0.997, 0.87, 0.503]], - [0.221, [0.997, 0.87, 0.503]], - [0.223, [0.997, 0.868, 0.498]], - [0.225, [0.997, 0.868, 0.498]], + [0, [1, 1, 0.8]], + [0.114, [1, 0.936, 0.643]], [0.227, [0.997, 0.865, 0.492]], - [0.229, [0.997, 0.865, 0.492]], - [0.231, [0.997, 0.863, 0.487]], - [0.233, [0.997, 0.863, 0.487]], - [0.235, [0.997, 0.86, 0.482]], - [0.237, [0.997, 0.86, 0.482]], - [0.239, [0.996, 0.858, 0.477]], - [0.241, [0.996, 0.858, 0.477]], - [0.243, [0.996, 0.855, 0.472]], - [0.245, [0.996, 0.855, 0.472]], - [0.247, [0.996, 0.853, 0.467]], - [0.249, [0.996, 0.853, 0.467]], - [0.25, [0.996, 0.85, 0.461]], - [0.252, [0.996, 0.85, 0.461]], - [0.254, [0.996, 0.845, 0.456]], - [0.256, [0.996, 0.845, 0.456]], - [0.258, [0.996, 0.84, 0.451]], - [0.26, [0.996, 0.84, 0.451]], - [0.262, [0.996, 0.835, 0.446]], - [0.264, [0.996, 0.835, 0.446]], - [0.266, [0.996, 0.831, 0.441]], - [0.268, [0.996, 0.831, 0.441]], - [0.27, [0.996, 0.826, 0.436]], - [0.272, [0.996, 0.826, 0.436]], - [0.274, [0.996, 0.821, 0.43]], - [0.276, [0.996, 0.821, 0.43]], - [0.278, [0.996, 0.816, 0.425]], - [0.28, [0.996, 0.816, 0.425]], - [0.282, [0.996, 0.811, 0.42]], - [0.284, [0.996, 0.811, 0.42]], - [0.286, [0.996, 0.807, 0.415]], - [0.288, [0.996, 0.807, 0.415]], - [0.29, [0.996, 0.802, 0.41]], - [0.292, [0.996, 0.802, 0.41]], - [0.294, [0.996, 0.797, 0.405]], - [0.295, [0.996, 0.797, 0.405]], - [0.297, [0.996, 0.792, 0.399]], - [0.299, [0.996, 0.792, 0.399]], - [0.301, [0.996, 0.787, 0.394]], - [0.303, [0.996, 0.787, 0.394]], - [0.305, [0.996, 0.783, 0.389]], - [0.307, [0.996, 0.783, 0.389]], - [0.309, [0.996, 0.778, 0.384]], - [0.311, [0.996, 0.778, 0.384]], - [0.313, [0.996, 0.773, 0.379]], - [0.315, [0.996, 0.773, 0.379]], - [0.317, [0.996, 0.768, 0.374]], - [0.319, [0.996, 0.768, 0.374]], - [0.321, [0.996, 0.763, 0.368]], - [0.323, [0.996, 0.763, 0.368]], - [0.325, [0.996, 0.759, 0.363]], - [0.327, [0.996, 0.759, 0.363]], - [0.329, [0.996, 0.754, 0.358]], - [0.331, [0.996, 0.754, 0.358]], - [0.333, [0.996, 0.749, 0.353]], - [0.335, [0.996, 0.749, 0.353]], - [0.337, [0.996, 0.744, 0.348]], - [0.339, [0.996, 0.744, 0.348]], [0.341, [0.996, 0.739, 0.343]], - [0.342, [0.996, 0.739, 0.343]], - [0.344, [0.996, 0.735, 0.337]], - [0.346, [0.996, 0.735, 0.337]], - [0.348, [0.996, 0.73, 0.332]], - [0.35, [0.996, 0.73, 0.332]], - [0.352, [0.996, 0.725, 0.327]], - [0.354, [0.996, 0.725, 0.327]], - [0.356, [0.996, 0.72, 0.322]], - [0.358, [0.996, 0.72, 0.322]], - [0.36, [0.996, 0.715, 0.317]], - [0.362, [0.996, 0.715, 0.317]], - [0.364, [0.996, 0.711, 0.312]], - [0.366, [0.996, 0.711, 0.312]], - [0.368, [0.996, 0.706, 0.306]], - [0.37, [0.996, 0.706, 0.306]], - [0.372, [0.996, 0.701, 0.301]], - [0.374, [0.996, 0.701, 0.301]], - [0.376, [0.996, 0.696, 0.297]], - [0.378, [0.996, 0.696, 0.297]], - [0.38, [0.996, 0.692, 0.295]], - [0.382, [0.996, 0.692, 0.295]], - [0.384, [0.996, 0.687, 0.293]], - [0.386, [0.996, 0.687, 0.293]], - [0.387, [0.996, 0.683, 0.291]], - [0.389, [0.996, 0.683, 0.291]], - [0.391, [0.996, 0.678, 0.289]], - [0.393, [0.996, 0.678, 0.289]], - [0.395, [0.995, 0.674, 0.287]], - [0.397, [0.995, 0.674, 0.287]], - [0.399, [0.995, 0.669, 0.285]], - [0.401, [0.995, 0.669, 0.285]], - [0.403, [0.995, 0.664, 0.284]], - [0.405, [0.995, 0.664, 0.284]], - [0.407, [0.995, 0.66, 0.282]], - [0.409, [0.995, 0.66, 0.282]], - [0.411, [0.995, 0.655, 0.28]], - [0.413, [0.995, 0.655, 0.28]], - [0.415, [0.995, 0.651, 0.278]], - [0.417, [0.995, 0.651, 0.278]], - [0.419, [0.995, 0.646, 0.276]], - [0.421, [0.995, 0.646, 0.276]], - [0.423, [0.995, 0.642, 0.274]], - [0.425, [0.995, 0.642, 0.274]], - [0.427, [0.994, 0.637, 0.272]], - [0.429, [0.994, 0.637, 0.272]], - [0.431, [0.994, 0.633, 0.27]], - [0.432, [0.994, 0.633, 0.27]], - [0.434, [0.994, 0.628, 0.268]], - [0.436, [0.994, 0.628, 0.268]], - [0.438, [0.994, 0.623, 0.266]], - [0.44, [0.994, 0.623, 0.266]], - [0.442, [0.994, 0.619, 0.264]], - [0.444, [0.994, 0.619, 0.264]], - [0.446, [0.994, 0.614, 0.262]], - [0.448, [0.994, 0.614, 0.262]], - [0.45, [0.994, 0.61, 0.26]], - [0.452, [0.994, 0.61, 0.26]], [0.454, [0.994, 0.605, 0.258]], - [0.456, [0.994, 0.605, 0.258]], - [0.458, [0.993, 0.601, 0.256]], - [0.46, [0.993, 0.601, 0.256]], - [0.462, [0.993, 0.596, 0.254]], - [0.464, [0.993, 0.596, 0.254]], - [0.466, [0.993, 0.592, 0.252]], - [0.468, [0.993, 0.592, 0.252]], - [0.47, [0.993, 0.587, 0.25]], - [0.472, [0.993, 0.587, 0.25]], - [0.474, [0.993, 0.583, 0.248]], - [0.476, [0.993, 0.583, 0.248]], - [0.477, [0.993, 0.578, 0.246]], - [0.479, [0.993, 0.578, 0.246]], - [0.481, [0.993, 0.573, 0.244]], - [0.483, [0.993, 0.573, 0.244]], - [0.485, [0.993, 0.569, 0.242]], - [0.487, [0.993, 0.569, 0.242]], - [0.489, [0.992, 0.564, 0.24]], - [0.491, [0.992, 0.564, 0.24]], - [0.493, [0.992, 0.56, 0.238]], - [0.495, [0.992, 0.56, 0.238]], - [0.497, [0.992, 0.555, 0.236]], - [0.499, [0.992, 0.555, 0.236]], - [0.501, [0.992, 0.549, 0.234]], - [0.503, [0.992, 0.549, 0.234]], - [0.505, [0.992, 0.541, 0.232]], - [0.507, [0.992, 0.541, 0.232]], - [0.509, [0.992, 0.534, 0.23]], - [0.511, [0.992, 0.534, 0.23]], - [0.513, [0.992, 0.526, 0.228]], - [0.515, [0.992, 0.526, 0.228]], - [0.517, [0.992, 0.518, 0.225]], - [0.519, [0.992, 0.518, 0.225]], - [0.521, [0.991, 0.51, 0.223]], - [0.523, [0.991, 0.51, 0.223]], - [0.524, [0.991, 0.503, 0.221]], - [0.526, [0.991, 0.503, 0.221]], - [0.528, [0.991, 0.495, 0.219]], - [0.53, [0.991, 0.495, 0.219]], - [0.532, [0.991, 0.487, 0.216]], - [0.534, [0.991, 0.487, 0.216]], - [0.536, [0.991, 0.479, 0.214]], - [0.538, [0.991, 0.479, 0.214]], - [0.54, [0.991, 0.472, 0.212]], - [0.542, [0.991, 0.472, 0.212]], - [0.544, [0.991, 0.464, 0.21]], - [0.546, [0.991, 0.464, 0.21]], - [0.548, [0.991, 0.456, 0.208]], - [0.55, [0.991, 0.456, 0.208]], - [0.552, [0.99, 0.448, 0.205]], - [0.554, [0.99, 0.448, 0.205]], - [0.556, [0.99, 0.441, 0.203]], - [0.558, [0.99, 0.441, 0.203]], - [0.56, [0.99, 0.433, 0.201]], - [0.562, [0.99, 0.433, 0.201]], - [0.564, [0.99, 0.425, 0.199]], - [0.566, [0.99, 0.425, 0.199]], [0.568, [0.99, 0.417, 0.197]], - [0.569, [0.99, 0.417, 0.197]], - [0.571, [0.99, 0.41, 0.194]], - [0.573, [0.99, 0.41, 0.194]], - [0.575, [0.99, 0.402, 0.192]], - [0.577, [0.99, 0.402, 0.192]], - [0.579, [0.99, 0.394, 0.19]], - [0.581, [0.99, 0.394, 0.19]], - [0.583, [0.99, 0.386, 0.188]], - [0.585, [0.99, 0.386, 0.188]], - [0.587, [0.989, 0.379, 0.185]], - [0.589, [0.989, 0.379, 0.185]], - [0.591, [0.989, 0.371, 0.183]], - [0.593, [0.989, 0.371, 0.183]], - [0.595, [0.989, 0.363, 0.181]], - [0.597, [0.989, 0.363, 0.181]], - [0.599, [0.989, 0.355, 0.179]], - [0.601, [0.989, 0.355, 0.179]], - [0.603, [0.989, 0.348, 0.177]], - [0.605, [0.989, 0.348, 0.177]], - [0.607, [0.989, 0.34, 0.174]], - [0.609, [0.989, 0.34, 0.174]], - [0.611, [0.989, 0.332, 0.172]], - [0.613, [0.989, 0.332, 0.172]], - [0.614, [0.989, 0.324, 0.17]], - [0.616, [0.989, 0.324, 0.17]], - [0.618, [0.988, 0.317, 0.168]], - [0.62, [0.988, 0.317, 0.168]], - [0.622, [0.988, 0.309, 0.166]], - [0.624, [0.988, 0.309, 0.166]], - [0.626, [0.986, 0.302, 0.164]], - [0.628, [0.986, 0.302, 0.164]], - [0.63, [0.983, 0.295, 0.162]], - [0.632, [0.983, 0.295, 0.162]], - [0.634, [0.98, 0.289, 0.16]], - [0.636, [0.98, 0.289, 0.16]], - [0.638, [0.977, 0.283, 0.158]], - [0.64, [0.977, 0.283, 0.158]], - [0.642, [0.974, 0.276, 0.157]], - [0.644, [0.974, 0.276, 0.157]], - [0.646, [0.971, 0.27, 0.155]], - [0.648, [0.971, 0.27, 0.155]], - [0.65, [0.968, 0.263, 0.153]], - [0.652, [0.968, 0.263, 0.153]], - [0.654, [0.965, 0.257, 0.152]], - [0.656, [0.965, 0.257, 0.152]], - [0.658, [0.962, 0.251, 0.15]], - [0.659, [0.962, 0.251, 0.15]], - [0.661, [0.959, 0.244, 0.148]], - [0.663, [0.959, 0.244, 0.148]], - [0.665, [0.956, 0.238, 0.146]], - [0.667, [0.956, 0.238, 0.146]], - [0.669, [0.952, 0.232, 0.145]], - [0.671, [0.952, 0.232, 0.145]], - [0.673, [0.949, 0.225, 0.143]], - [0.675, [0.949, 0.225, 0.143]], - [0.677, [0.946, 0.219, 0.141]], - [0.679, [0.946, 0.219, 0.141]], [0.681, [0.943, 0.212, 0.14]], - [0.683, [0.943, 0.212, 0.14]], - [0.685, [0.94, 0.206, 0.138]], - [0.687, [0.94, 0.206, 0.138]], - [0.689, [0.937, 0.2, 0.136]], - [0.691, [0.937, 0.2, 0.136]], - [0.693, [0.934, 0.193, 0.134]], - [0.695, [0.934, 0.193, 0.134]], - [0.697, [0.931, 0.187, 0.133]], - [0.699, [0.931, 0.187, 0.133]], - [0.701, [0.928, 0.18, 0.131]], - [0.703, [0.928, 0.18, 0.131]], - [0.705, [0.925, 0.174, 0.129]], - [0.706, [0.925, 0.174, 0.129]], - [0.708, [0.922, 0.168, 0.127]], - [0.71, [0.922, 0.168, 0.127]], - [0.712, [0.919, 0.161, 0.126]], - [0.714, [0.919, 0.161, 0.126]], - [0.716, [0.916, 0.155, 0.124]], - [0.718, [0.916, 0.155, 0.124]], - [0.72, [0.912, 0.148, 0.122]], - [0.722, [0.912, 0.148, 0.122]], - [0.724, [0.909, 0.142, 0.121]], - [0.726, [0.909, 0.142, 0.121]], - [0.728, [0.906, 0.136, 0.119]], - [0.73, [0.906, 0.136, 0.119]], - [0.732, [0.903, 0.129, 0.117]], - [0.734, [0.903, 0.129, 0.117]], - [0.736, [0.9, 0.123, 0.115]], - [0.738, [0.9, 0.123, 0.115]], - [0.74, [0.897, 0.116, 0.114]], - [0.742, [0.897, 0.116, 0.114]], - [0.744, [0.894, 0.11, 0.112]], - [0.746, [0.894, 0.11, 0.112]], - [0.748, [0.891, 0.104, 0.11]], - [0.75, [0.891, 0.104, 0.11]], - [0.751, [0.887, 0.1, 0.111]], - [0.753, [0.887, 0.1, 0.111]], - [0.755, [0.882, 0.096, 0.112]], - [0.757, [0.882, 0.096, 0.112]], - [0.759, [0.877, 0.093, 0.113]], - [0.761, [0.877, 0.093, 0.113]], - [0.763, [0.873, 0.09, 0.114]], - [0.765, [0.873, 0.09, 0.114]], - [0.767, [0.868, 0.087, 0.116]], - [0.769, [0.868, 0.087, 0.116]], - [0.771, [0.863, 0.084, 0.117]], - [0.773, [0.863, 0.084, 0.117]], - [0.775, [0.859, 0.08, 0.118]], - [0.777, [0.859, 0.08, 0.118]], - [0.779, [0.854, 0.077, 0.119]], - [0.781, [0.854, 0.077, 0.119]], - [0.783, [0.849, 0.074, 0.121]], - [0.785, [0.849, 0.074, 0.121]], - [0.787, [0.845, 0.071, 0.122]], - [0.789, [0.845, 0.071, 0.122]], - [0.791, [0.84, 0.068, 0.123]], - [0.793, [0.84, 0.068, 0.123]], [0.795, [0.835, 0.064, 0.124]], - [0.796, [0.835, 0.064, 0.124]], - [0.798, [0.831, 0.061, 0.125]], - [0.8, [0.831, 0.061, 0.125]], - [0.802, [0.826, 0.058, 0.127]], - [0.804, [0.826, 0.058, 0.127]], - [0.806, [0.821, 0.055, 0.128]], - [0.808, [0.821, 0.055, 0.128]], - [0.81, [0.817, 0.052, 0.129]], - [0.812, [0.817, 0.052, 0.129]], - [0.814, [0.812, 0.048, 0.13]], - [0.816, [0.812, 0.048, 0.13]], - [0.818, [0.807, 0.045, 0.132]], - [0.82, [0.807, 0.045, 0.132]], - [0.822, [0.803, 0.042, 0.133]], - [0.824, [0.803, 0.042, 0.133]], - [0.826, [0.798, 0.039, 0.134]], - [0.828, [0.798, 0.039, 0.134]], - [0.83, [0.793, 0.036, 0.135]], - [0.832, [0.793, 0.036, 0.135]], - [0.834, [0.789, 0.032, 0.137]], - [0.836, [0.789, 0.032, 0.137]], - [0.838, [0.784, 0.029, 0.138]], - [0.84, [0.784, 0.029, 0.138]], - [0.841, [0.779, 0.026, 0.139]], - [0.843, [0.779, 0.026, 0.139]], - [0.845, [0.774, 0.023, 0.14]], - [0.847, [0.774, 0.023, 0.14]], - [0.849, [0.77, 0.02, 0.141]], - [0.851, [0.77, 0.02, 0.141]], - [0.853, [0.765, 0.016, 0.143]], - [0.855, [0.765, 0.016, 0.143]], - [0.857, [0.76, 0.013, 0.144]], - [0.859, [0.76, 0.013, 0.144]], - [0.861, [0.756, 0.01, 0.145]], - [0.863, [0.756, 0.01, 0.145]], - [0.865, [0.751, 0.007, 0.146]], - [0.867, [0.751, 0.007, 0.146]], - [0.869, [0.746, 0.004, 0.148]], - [0.871, [0.746, 0.004, 0.148]], - [0.873, [0.742, 0.0, 0.149]], - [0.875, [0.742, 0.0, 0.149]], - [0.877, [0.735, 0.0, 0.149]], - [0.879, [0.735, 0.0, 0.149]], - [0.881, [0.727, 0.0, 0.149]], - [0.883, [0.727, 0.0, 0.149]], - [0.885, [0.72, 0.0, 0.149]], - [0.886, [0.72, 0.0, 0.149]], - [0.888, [0.712, 0.0, 0.149]], - [0.89, [0.712, 0.0, 0.149]], - [0.892, [0.705, 0.0, 0.149]], - [0.894, [0.705, 0.0, 0.149]], - [0.896, [0.697, 0.0, 0.149]], - [0.898, [0.697, 0.0, 0.149]], - [0.9, [0.69, 0.0, 0.149]], - [0.902, [0.69, 0.0, 0.149]], - [0.904, [0.682, 0.0, 0.149]], - [0.906, [0.682, 0.0, 0.149]], - [0.908, [0.675, 0.0, 0.149]], - [0.91, [0.675, 0.0, 0.149]], - [0.912, [0.667, 0.0, 0.149]], - [0.914, [0.667, 0.0, 0.149]], - [0.916, [0.66, 0.0, 0.149]], - [0.918, [0.66, 0.0, 0.149]], - [0.92, [0.652, 0.0, 0.149]], - [0.922, [0.652, 0.0, 0.149]], - [0.924, [0.645, 0.0, 0.149]], - [0.926, [0.645, 0.0, 0.149]], - [0.928, [0.637, 0.0, 0.149]], - [0.93, [0.637, 0.0, 0.149]], - [0.932, [0.63, 0.0, 0.149]], - [0.933, [0.63, 0.0, 0.149]], - [0.935, [0.622, 0.0, 0.149]], - [0.937, [0.622, 0.0, 0.149]], - [0.939, [0.615, 0.0, 0.149]], - [0.941, [0.615, 0.0, 0.149]], - [0.943, [0.607, 0.0, 0.149]], - [0.945, [0.607, 0.0, 0.149]], - [0.947, [0.6, 0.0, 0.149]], - [0.949, [0.6, 0.0, 0.149]], - [0.951, [0.592, 0.0, 0.149]], - [0.953, [0.592, 0.0, 0.149]], - [0.955, [0.585, 0.0, 0.149]], - [0.957, [0.585, 0.0, 0.149]], - [0.959, [0.577, 0.0, 0.149]], - [0.961, [0.577, 0.0, 0.149]], - [0.963, [0.57, 0.0, 0.149]], - [0.965, [0.57, 0.0, 0.149]], - [0.967, [0.562, 0.0, 0.149]], - [0.969, [0.562, 0.0, 0.149]], - [0.971, [0.554, 0.0, 0.149]], - [0.973, [0.554, 0.0, 0.149]], - [0.975, [0.547, 0.0, 0.149]], - [0.977, [0.547, 0.0, 0.149]], - [0.978, [0.539, 0.0, 0.149]], - [0.98, [0.539, 0.0, 0.149]], - [0.982, [0.532, 0.0, 0.149]], - [0.984, [0.532, 0.0, 0.149]], - [0.986, [0.524, 0.0, 0.149]], - [0.988, [0.524, 0.0, 0.149]], - [0.99, [0.517, 0.0, 0.149]], - [0.992, [0.517, 0.0, 0.149]], - [0.994, [0.509, 0.0, 0.149]], - [0.996, [0.509, 0.0, 0.149]], - [0.998, [0.502, 0.0, 0.149]], - [1.0, [0.502, 0.0, 0.149]], + [0.908, [0.675, 0, 0.149]], + [1, [0.502, 0, 0.149]], ], }, @@ -2642,515 +132,39 @@ export const vislibColorMaps: ColorMap = { }), value: [ [0, [0, 0.408, 0.216]], - [0.002, [0, 0.408, 0.216]], - [0.004, [0.004, 0.415, 0.22]], - [0.006, [0.004, 0.415, 0.22]], - [0.008, [0.008, 0.423, 0.223]], - [0.01, [0.008, 0.423, 0.223]], - [0.012, [0.012, 0.43, 0.227]], - [0.014, [0.012, 0.43, 0.227]], - [0.016, [0.016, 0.437, 0.231]], - [0.018, [0.016, 0.437, 0.231]], - [0.02, [0.02, 0.445, 0.235]], - [0.022, [0.02, 0.445, 0.235]], - [0.023, [0.024, 0.452, 0.239]], - [0.025, [0.024, 0.452, 0.239]], - [0.027, [0.028, 0.46, 0.243]], [0.029, [0.028, 0.46, 0.243]], - [0.031, [0.032, 0.467, 0.246]], - [0.033, [0.032, 0.467, 0.246]], - [0.035, [0.036, 0.474, 0.25]], - [0.037, [0.036, 0.474, 0.25]], - [0.039, [0.04, 0.482, 0.254]], - [0.041, [0.04, 0.482, 0.254]], - [0.043, [0.044, 0.489, 0.258]], - [0.045, [0.044, 0.489, 0.258]], - [0.047, [0.048, 0.496, 0.262]], - [0.049, [0.048, 0.496, 0.262]], - [0.051, [0.052, 0.504, 0.266]], - [0.053, [0.052, 0.504, 0.266]], - [0.055, [0.056, 0.511, 0.27]], - [0.057, [0.056, 0.511, 0.27]], [0.059, [0.06, 0.519, 0.273]], - [0.061, [0.06, 0.519, 0.273]], - [0.063, [0.064, 0.526, 0.277]], - [0.065, [0.064, 0.526, 0.277]], - [0.067, [0.068, 0.533, 0.281]], - [0.068, [0.068, 0.533, 0.281]], - [0.07, [0.072, 0.541, 0.285]], - [0.072, [0.072, 0.541, 0.285]], - [0.074, [0.076, 0.548, 0.289]], - [0.076, [0.076, 0.548, 0.289]], - [0.078, [0.08, 0.555, 0.293]], - [0.08, [0.08, 0.555, 0.293]], - [0.082, [0.084, 0.563, 0.296]], - [0.084, [0.084, 0.563, 0.296]], - [0.086, [0.088, 0.57, 0.3]], [0.088, [0.088, 0.57, 0.3]], - [0.09, [0.092, 0.578, 0.304]], - [0.092, [0.092, 0.578, 0.304]], - [0.094, [0.096, 0.585, 0.308]], - [0.096, [0.096, 0.585, 0.308]], - [0.098, [0.1, 0.592, 0.312]], - [0.1, [0.1, 0.592, 0.312]], - [0.102, [0.108, 0.599, 0.315]], - [0.104, [0.108, 0.599, 0.315]], - [0.106, [0.119, 0.605, 0.318]], - [0.108, [0.119, 0.605, 0.318]], - [0.11, [0.131, 0.61, 0.321]], - [0.112, [0.131, 0.61, 0.321]], - [0.114, [0.143, 0.616, 0.324]], - [0.115, [0.143, 0.616, 0.324]], [0.117, [0.155, 0.622, 0.327]], - [0.119, [0.155, 0.622, 0.327]], - [0.121, [0.166, 0.627, 0.33]], - [0.123, [0.166, 0.627, 0.33]], - [0.125, [0.178, 0.633, 0.333]], - [0.127, [0.178, 0.633, 0.333]], - [0.129, [0.19, 0.639, 0.336]], - [0.131, [0.19, 0.639, 0.336]], - [0.133, [0.201, 0.644, 0.339]], - [0.135, [0.201, 0.644, 0.339]], - [0.137, [0.213, 0.65, 0.341]], - [0.139, [0.213, 0.65, 0.341]], - [0.141, [0.225, 0.656, 0.344]], - [0.143, [0.225, 0.656, 0.344]], - [0.145, [0.236, 0.662, 0.347]], [0.147, [0.236, 0.662, 0.347]], - [0.149, [0.248, 0.667, 0.35]], - [0.151, [0.248, 0.667, 0.35]], - [0.153, [0.26, 0.673, 0.353]], - [0.155, [0.26, 0.673, 0.353]], - [0.157, [0.271, 0.679, 0.356]], - [0.159, [0.271, 0.679, 0.356]], - [0.16, [0.283, 0.684, 0.359]], - [0.162, [0.283, 0.684, 0.359]], - [0.164, [0.295, 0.69, 0.362]], - [0.166, [0.295, 0.69, 0.362]], - [0.168, [0.306, 0.696, 0.365]], - [0.17, [0.306, 0.696, 0.365]], - [0.172, [0.318, 0.701, 0.368]], - [0.174, [0.318, 0.701, 0.368]], [0.176, [0.33, 0.707, 0.371]], - [0.178, [0.33, 0.707, 0.371]], - [0.18, [0.342, 0.713, 0.374]], - [0.182, [0.342, 0.713, 0.374]], - [0.184, [0.353, 0.718, 0.377]], - [0.186, [0.353, 0.718, 0.377]], - [0.188, [0.365, 0.724, 0.379]], - [0.19, [0.365, 0.724, 0.379]], - [0.192, [0.377, 0.73, 0.382]], - [0.194, [0.377, 0.73, 0.382]], - [0.196, [0.388, 0.735, 0.385]], - [0.198, [0.388, 0.735, 0.385]], - [0.2, [0.4, 0.741, 0.388]], - [0.202, [0.4, 0.741, 0.388]], - [0.204, [0.41, 0.745, 0.389]], [0.205, [0.41, 0.745, 0.389]], - [0.207, [0.42, 0.75, 0.39]], - [0.209, [0.42, 0.75, 0.39]], - [0.211, [0.43, 0.754, 0.391]], - [0.213, [0.43, 0.754, 0.391]], - [0.215, [0.439, 0.758, 0.393]], - [0.217, [0.439, 0.758, 0.393]], - [0.219, [0.449, 0.763, 0.394]], - [0.221, [0.449, 0.763, 0.394]], - [0.223, [0.459, 0.767, 0.395]], - [0.225, [0.459, 0.767, 0.395]], - [0.227, [0.469, 0.771, 0.396]], - [0.229, [0.469, 0.771, 0.396]], - [0.231, [0.479, 0.776, 0.397]], - [0.233, [0.479, 0.776, 0.397]], [0.235, [0.489, 0.78, 0.398]], - [0.237, [0.489, 0.78, 0.398]], - [0.239, [0.498, 0.784, 0.399]], - [0.241, [0.498, 0.784, 0.399]], - [0.243, [0.508, 0.789, 0.4]], - [0.245, [0.508, 0.789, 0.4]], - [0.247, [0.518, 0.793, 0.401]], - [0.249, [0.518, 0.793, 0.401]], - [0.25, [0.528, 0.797, 0.402]], - [0.252, [0.528, 0.797, 0.402]], - [0.254, [0.538, 0.801, 0.403]], - [0.256, [0.538, 0.801, 0.403]], - [0.258, [0.548, 0.806, 0.404]], - [0.26, [0.548, 0.806, 0.404]], - [0.262, [0.557, 0.81, 0.405]], [0.264, [0.557, 0.81, 0.405]], - [0.266, [0.567, 0.814, 0.407]], - [0.268, [0.567, 0.814, 0.407]], - [0.27, [0.577, 0.819, 0.408]], - [0.272, [0.577, 0.819, 0.408]], - [0.274, [0.587, 0.823, 0.409]], - [0.276, [0.587, 0.823, 0.409]], - [0.278, [0.597, 0.827, 0.41]], - [0.28, [0.597, 0.827, 0.41]], - [0.282, [0.607, 0.832, 0.411]], - [0.284, [0.607, 0.832, 0.411]], - [0.286, [0.617, 0.836, 0.412]], - [0.288, [0.617, 0.836, 0.412]], - [0.29, [0.626, 0.84, 0.413]], - [0.292, [0.626, 0.84, 0.413]], [0.294, [0.636, 0.845, 0.414]], - [0.295, [0.636, 0.845, 0.414]], - [0.297, [0.646, 0.849, 0.415]], - [0.299, [0.646, 0.849, 0.415]], - [0.301, [0.655, 0.853, 0.418]], - [0.303, [0.655, 0.853, 0.418]], - [0.305, [0.663, 0.856, 0.423]], - [0.307, [0.663, 0.856, 0.423]], - [0.309, [0.671, 0.859, 0.428]], - [0.311, [0.671, 0.859, 0.428]], - [0.313, [0.678, 0.863, 0.433]], - [0.315, [0.678, 0.863, 0.433]], - [0.317, [0.686, 0.866, 0.439]], - [0.319, [0.686, 0.866, 0.439]], - [0.321, [0.694, 0.87, 0.444]], [0.323, [0.694, 0.87, 0.444]], - [0.325, [0.702, 0.873, 0.449]], - [0.327, [0.702, 0.873, 0.449]], - [0.329, [0.71, 0.876, 0.454]], - [0.331, [0.71, 0.876, 0.454]], - [0.333, [0.718, 0.88, 0.459]], - [0.335, [0.718, 0.88, 0.459]], - [0.337, [0.725, 0.883, 0.464]], - [0.339, [0.725, 0.883, 0.464]], - [0.341, [0.733, 0.887, 0.469]], - [0.342, [0.733, 0.887, 0.469]], - [0.344, [0.741, 0.89, 0.474]], - [0.346, [0.741, 0.89, 0.474]], - [0.348, [0.749, 0.893, 0.479]], - [0.35, [0.749, 0.893, 0.479]], [0.352, [0.757, 0.897, 0.484]], - [0.354, [0.757, 0.897, 0.484]], - [0.356, [0.765, 0.9, 0.489]], - [0.358, [0.765, 0.9, 0.489]], - [0.36, [0.773, 0.903, 0.494]], - [0.362, [0.773, 0.903, 0.494]], - [0.364, [0.78, 0.907, 0.499]], - [0.366, [0.78, 0.907, 0.499]], - [0.368, [0.788, 0.91, 0.504]], - [0.37, [0.788, 0.91, 0.504]], - [0.372, [0.796, 0.914, 0.51]], - [0.374, [0.796, 0.914, 0.51]], - [0.376, [0.804, 0.917, 0.515]], - [0.378, [0.804, 0.917, 0.515]], - [0.38, [0.812, 0.92, 0.52]], [0.382, [0.812, 0.92, 0.52]], - [0.384, [0.82, 0.924, 0.525]], - [0.386, [0.82, 0.924, 0.525]], - [0.387, [0.827, 0.927, 0.53]], - [0.389, [0.827, 0.927, 0.53]], - [0.391, [0.835, 0.93, 0.535]], - [0.393, [0.835, 0.93, 0.535]], - [0.395, [0.843, 0.934, 0.54]], - [0.397, [0.843, 0.934, 0.54]], - [0.399, [0.851, 0.937, 0.545]], - [0.401, [0.851, 0.937, 0.545]], - [0.403, [0.857, 0.94, 0.553]], - [0.405, [0.857, 0.94, 0.553]], - [0.407, [0.863, 0.942, 0.561]], - [0.409, [0.863, 0.942, 0.561]], [0.411, [0.869, 0.945, 0.569]], - [0.413, [0.869, 0.945, 0.569]], - [0.415, [0.874, 0.947, 0.577]], - [0.417, [0.874, 0.947, 0.577]], - [0.419, [0.88, 0.95, 0.585]], - [0.421, [0.88, 0.95, 0.585]], - [0.423, [0.886, 0.952, 0.593]], - [0.425, [0.886, 0.952, 0.593]], - [0.427, [0.892, 0.954, 0.601]], - [0.429, [0.892, 0.954, 0.601]], - [0.431, [0.898, 0.957, 0.609]], - [0.432, [0.898, 0.957, 0.609]], - [0.434, [0.904, 0.959, 0.617]], - [0.436, [0.904, 0.959, 0.617]], - [0.438, [0.909, 0.962, 0.625]], [0.44, [0.909, 0.962, 0.625]], - [0.442, [0.915, 0.964, 0.633]], - [0.444, [0.915, 0.964, 0.633]], - [0.446, [0.921, 0.967, 0.641]], - [0.448, [0.921, 0.967, 0.641]], - [0.45, [0.927, 0.969, 0.649]], - [0.452, [0.927, 0.969, 0.649]], - [0.454, [0.933, 0.972, 0.657]], - [0.456, [0.933, 0.972, 0.657]], - [0.458, [0.939, 0.974, 0.665]], - [0.46, [0.939, 0.974, 0.665]], - [0.462, [0.944, 0.977, 0.673]], - [0.464, [0.944, 0.977, 0.673]], - [0.466, [0.95, 0.979, 0.681]], - [0.468, [0.95, 0.979, 0.681]], [0.47, [0.956, 0.982, 0.689]], - [0.472, [0.956, 0.982, 0.689]], - [0.474, [0.962, 0.984, 0.697]], - [0.476, [0.962, 0.984, 0.697]], - [0.477, [0.968, 0.986, 0.705]], - [0.479, [0.968, 0.986, 0.705]], - [0.481, [0.974, 0.989, 0.713]], - [0.483, [0.974, 0.989, 0.713]], - [0.485, [0.98, 0.991, 0.721]], - [0.487, [0.98, 0.991, 0.721]], - [0.489, [0.985, 0.994, 0.729]], - [0.491, [0.985, 0.994, 0.729]], - [0.493, [0.991, 0.996, 0.737]], - [0.495, [0.991, 0.996, 0.737]], - [0.497, [0.997, 0.999, 0.745]], [0.499, [0.997, 0.999, 0.745]], - [0.501, [1, 0.998, 0.745]], - [0.503, [1, 0.998, 0.745]], - [0.505, [1, 0.993, 0.737]], - [0.507, [1, 0.993, 0.737]], - [0.509, [1, 0.988, 0.729]], - [0.511, [1, 0.988, 0.729]], - [0.513, [0.999, 0.983, 0.721]], - [0.515, [0.999, 0.983, 0.721]], - [0.517, [0.999, 0.979, 0.713]], - [0.519, [0.999, 0.979, 0.713]], - [0.521, [0.999, 0.974, 0.705]], - [0.523, [0.999, 0.974, 0.705]], - [0.524, [0.999, 0.969, 0.697]], - [0.526, [0.999, 0.969, 0.697]], [0.528, [0.999, 0.964, 0.689]], - [0.53, [0.999, 0.964, 0.689]], - [0.532, [0.999, 0.959, 0.681]], - [0.534, [0.999, 0.959, 0.681]], - [0.536, [0.999, 0.955, 0.673]], - [0.538, [0.999, 0.955, 0.673]], - [0.54, [0.998, 0.95, 0.665]], - [0.542, [0.998, 0.95, 0.665]], - [0.544, [0.998, 0.945, 0.657]], - [0.546, [0.998, 0.945, 0.657]], - [0.548, [0.998, 0.94, 0.649]], - [0.55, [0.998, 0.94, 0.649]], - [0.552, [0.998, 0.936, 0.641]], - [0.554, [0.998, 0.936, 0.641]], - [0.556, [0.998, 0.931, 0.633]], [0.558, [0.998, 0.931, 0.633]], - [0.56, [0.998, 0.926, 0.625]], - [0.562, [0.998, 0.926, 0.625]], - [0.564, [0.997, 0.921, 0.617]], - [0.566, [0.997, 0.921, 0.617]], - [0.568, [0.997, 0.917, 0.609]], - [0.569, [0.997, 0.917, 0.609]], - [0.571, [0.997, 0.912, 0.601]], - [0.573, [0.997, 0.912, 0.601]], - [0.575, [0.997, 0.907, 0.593]], - [0.577, [0.997, 0.907, 0.593]], - [0.579, [0.997, 0.902, 0.585]], - [0.581, [0.997, 0.902, 0.585]], - [0.583, [0.997, 0.898, 0.577]], - [0.585, [0.997, 0.898, 0.577]], [0.587, [0.997, 0.893, 0.569]], - [0.589, [0.997, 0.893, 0.569]], - [0.591, [0.996, 0.888, 0.561]], - [0.593, [0.996, 0.888, 0.561]], - [0.595, [0.996, 0.883, 0.553]], - [0.597, [0.996, 0.883, 0.553]], - [0.599, [0.996, 0.878, 0.545]], - [0.601, [0.996, 0.878, 0.545]], - [0.603, [0.996, 0.871, 0.539]], - [0.605, [0.996, 0.871, 0.539]], - [0.607, [0.996, 0.863, 0.532]], - [0.609, [0.996, 0.863, 0.532]], - [0.611, [0.996, 0.855, 0.526]], - [0.613, [0.996, 0.855, 0.526]], - [0.614, [0.995, 0.848, 0.519]], [0.616, [0.995, 0.848, 0.519]], - [0.618, [0.995, 0.84, 0.513]], - [0.62, [0.995, 0.84, 0.513]], - [0.622, [0.995, 0.832, 0.506]], - [0.624, [0.995, 0.832, 0.506]], - [0.626, [0.995, 0.825, 0.5]], - [0.628, [0.995, 0.825, 0.5]], - [0.63, [0.995, 0.817, 0.493]], - [0.632, [0.995, 0.817, 0.493]], - [0.634, [0.995, 0.809, 0.487]], - [0.636, [0.995, 0.809, 0.487]], - [0.638, [0.995, 0.802, 0.481]], - [0.64, [0.995, 0.802, 0.481]], - [0.642, [0.994, 0.794, 0.474]], - [0.644, [0.994, 0.794, 0.474]], [0.646, [0.994, 0.786, 0.468]], - [0.648, [0.994, 0.786, 0.468]], - [0.65, [0.994, 0.778, 0.461]], - [0.652, [0.994, 0.778, 0.461]], - [0.654, [0.994, 0.771, 0.455]], - [0.656, [0.994, 0.771, 0.455]], - [0.658, [0.994, 0.763, 0.448]], - [0.659, [0.994, 0.763, 0.448]], - [0.661, [0.994, 0.755, 0.442]], - [0.663, [0.994, 0.755, 0.442]], - [0.665, [0.993, 0.748, 0.435]], - [0.667, [0.993, 0.748, 0.435]], - [0.669, [0.993, 0.74, 0.429]], - [0.671, [0.993, 0.74, 0.429]], - [0.673, [0.993, 0.732, 0.422]], [0.675, [0.993, 0.732, 0.422]], - [0.677, [0.993, 0.725, 0.416]], - [0.679, [0.993, 0.725, 0.416]], - [0.681, [0.993, 0.717, 0.409]], - [0.683, [0.993, 0.717, 0.409]], - [0.685, [0.993, 0.709, 0.403]], - [0.687, [0.993, 0.709, 0.403]], - [0.689, [0.993, 0.702, 0.397]], - [0.691, [0.993, 0.702, 0.397]], - [0.693, [0.992, 0.694, 0.39]], - [0.695, [0.992, 0.694, 0.39]], - [0.697, [0.992, 0.686, 0.384]], - [0.699, [0.992, 0.686, 0.384]], - [0.701, [0.991, 0.677, 0.378]], - [0.703, [0.991, 0.677, 0.378]], [0.705, [0.99, 0.667, 0.373]], - [0.706, [0.99, 0.667, 0.373]], - [0.708, [0.989, 0.657, 0.369]], - [0.71, [0.989, 0.657, 0.369]], - [0.712, [0.987, 0.647, 0.364]], - [0.714, [0.987, 0.647, 0.364]], - [0.716, [0.986, 0.637, 0.36]], - [0.718, [0.986, 0.637, 0.36]], - [0.72, [0.985, 0.627, 0.355]], - [0.722, [0.985, 0.627, 0.355]], - [0.724, [0.983, 0.617, 0.35]], - [0.726, [0.983, 0.617, 0.35]], - [0.728, [0.982, 0.607, 0.346]], - [0.73, [0.982, 0.607, 0.346]], - [0.732, [0.98, 0.597, 0.341]], [0.734, [0.98, 0.597, 0.341]], - [0.736, [0.979, 0.587, 0.337]], - [0.738, [0.979, 0.587, 0.337]], - [0.74, [0.978, 0.577, 0.332]], - [0.742, [0.978, 0.577, 0.332]], - [0.744, [0.976, 0.567, 0.327]], - [0.746, [0.976, 0.567, 0.327]], - [0.748, [0.975, 0.557, 0.323]], - [0.75, [0.975, 0.557, 0.323]], - [0.751, [0.973, 0.547, 0.318]], - [0.753, [0.973, 0.547, 0.318]], - [0.755, [0.972, 0.537, 0.313]], - [0.757, [0.972, 0.537, 0.313]], - [0.759, [0.971, 0.527, 0.309]], - [0.761, [0.971, 0.527, 0.309]], [0.763, [0.969, 0.517, 0.304]], - [0.765, [0.969, 0.517, 0.304]], - [0.767, [0.968, 0.507, 0.3]], - [0.769, [0.968, 0.507, 0.3]], - [0.771, [0.967, 0.497, 0.295]], - [0.773, [0.967, 0.497, 0.295]], - [0.775, [0.965, 0.487, 0.29]], - [0.777, [0.965, 0.487, 0.29]], - [0.779, [0.964, 0.477, 0.286]], - [0.781, [0.964, 0.477, 0.286]], - [0.783, [0.962, 0.467, 0.281]], - [0.785, [0.962, 0.467, 0.281]], - [0.787, [0.961, 0.457, 0.277]], - [0.789, [0.961, 0.457, 0.277]], - [0.791, [0.96, 0.447, 0.272]], [0.793, [0.96, 0.447, 0.272]], - [0.795, [0.958, 0.437, 0.267]], - [0.796, [0.958, 0.437, 0.267]], - [0.798, [0.957, 0.427, 0.263]], - [0.8, [0.957, 0.427, 0.263]], - [0.802, [0.952, 0.418, 0.258]], - [0.804, [0.952, 0.418, 0.258]], - [0.806, [0.948, 0.409, 0.254]], - [0.808, [0.948, 0.409, 0.254]], - [0.81, [0.943, 0.399, 0.25]], - [0.812, [0.943, 0.399, 0.25]], - [0.814, [0.939, 0.39, 0.246]], - [0.816, [0.939, 0.39, 0.246]], - [0.818, [0.935, 0.381, 0.241]], - [0.82, [0.935, 0.381, 0.241]], [0.822, [0.93, 0.371, 0.237]], - [0.824, [0.93, 0.371, 0.237]], - [0.826, [0.926, 0.362, 0.233]], - [0.828, [0.926, 0.362, 0.233]], - [0.83, [0.921, 0.352, 0.228]], - [0.832, [0.921, 0.352, 0.228]], - [0.834, [0.917, 0.343, 0.224]], - [0.836, [0.917, 0.343, 0.224]], - [0.838, [0.912, 0.334, 0.22]], - [0.84, [0.912, 0.334, 0.22]], - [0.841, [0.908, 0.324, 0.215]], - [0.843, [0.908, 0.324, 0.215]], - [0.845, [0.903, 0.315, 0.211]], - [0.847, [0.903, 0.315, 0.211]], - [0.849, [0.899, 0.305, 0.207]], [0.851, [0.899, 0.305, 0.207]], - [0.853, [0.894, 0.296, 0.202]], - [0.855, [0.894, 0.296, 0.202]], - [0.857, [0.89, 0.287, 0.198]], - [0.859, [0.89, 0.287, 0.198]], - [0.861, [0.886, 0.277, 0.194]], - [0.863, [0.886, 0.277, 0.194]], - [0.865, [0.881, 0.268, 0.19]], - [0.867, [0.881, 0.268, 0.19]], - [0.869, [0.877, 0.259, 0.185]], - [0.871, [0.877, 0.259, 0.185]], - [0.873, [0.872, 0.249, 0.181]], - [0.875, [0.872, 0.249, 0.181]], - [0.877, [0.868, 0.24, 0.177]], - [0.879, [0.868, 0.24, 0.177]], [0.881, [0.863, 0.23, 0.172]], - [0.883, [0.863, 0.23, 0.172]], - [0.885, [0.859, 0.221, 0.168]], - [0.886, [0.859, 0.221, 0.168]], - [0.888, [0.854, 0.212, 0.164]], - [0.89, [0.854, 0.212, 0.164]], - [0.892, [0.85, 0.202, 0.159]], - [0.894, [0.85, 0.202, 0.159]], - [0.896, [0.845, 0.193, 0.155]], - [0.898, [0.845, 0.193, 0.155]], - [0.9, [0.839, 0.185, 0.153]], - [0.902, [0.839, 0.185, 0.153]], - [0.904, [0.832, 0.177, 0.153]], - [0.906, [0.832, 0.177, 0.153]], - [0.908, [0.824, 0.17, 0.153]], [0.91, [0.824, 0.17, 0.153]], - [0.912, [0.816, 0.162, 0.152]], - [0.914, [0.816, 0.162, 0.152]], - [0.916, [0.809, 0.155, 0.152]], - [0.918, [0.809, 0.155, 0.152]], - [0.92, [0.801, 0.148, 0.152]], - [0.922, [0.801, 0.148, 0.152]], - [0.924, [0.793, 0.14, 0.152]], - [0.926, [0.793, 0.14, 0.152]], - [0.928, [0.785, 0.133, 0.152]], - [0.93, [0.785, 0.133, 0.152]], - [0.932, [0.778, 0.125, 0.152]], - [0.933, [0.778, 0.125, 0.152]], - [0.935, [0.77, 0.118, 0.151]], - [0.937, [0.77, 0.118, 0.151]], [0.939, [0.762, 0.111, 0.151]], - [0.941, [0.762, 0.111, 0.151]], - [0.943, [0.755, 0.103, 0.151]], - [0.945, [0.755, 0.103, 0.151]], - [0.947, [0.747, 0.096, 0.151]], - [0.949, [0.747, 0.096, 0.151]], - [0.951, [0.739, 0.089, 0.151]], - [0.953, [0.739, 0.089, 0.151]], - [0.955, [0.732, 0.081, 0.151]], - [0.957, [0.732, 0.081, 0.151]], - [0.959, [0.724, 0.074, 0.151]], - [0.961, [0.724, 0.074, 0.151]], - [0.963, [0.716, 0.066, 0.15]], - [0.965, [0.716, 0.066, 0.15]], - [0.967, [0.709, 0.059, 0.15]], [0.969, [0.709, 0.059, 0.15]], - [0.971, [0.701, 0.052, 0.15]], - [0.973, [0.701, 0.052, 0.15]], - [0.975, [0.693, 0.044, 0.15]], - [0.977, [0.693, 0.044, 0.15]], - [0.978, [0.686, 0.037, 0.15]], - [0.98, [0.686, 0.037, 0.15]], - [0.982, [0.678, 0.03, 0.15]], - [0.984, [0.678, 0.03, 0.15]], - [0.986, [0.67, 0.022, 0.149]], - [0.988, [0.67, 0.022, 0.149]], - [0.99, [0.662, 0.015, 0.149]], - [0.992, [0.662, 0.015, 0.149]], - [0.994, [0.655, 0.007, 0.149]], - [0.996, [0.655, 0.007, 0.149]], [0.998, [0.647, 0, 0.149]], [1, [0.647, 0, 0.149]], ], diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.test.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.test.js index 035d6200648482..76830c66f3aa56 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.test.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.test.js @@ -134,8 +134,8 @@ describe('Vislib Gauge Chart Test Suite', function () { }); expect(fills).toEqual([ 'rgb(165,0,38)', - 'rgb(255,255,190)', - 'rgb(255,255,190)', + 'rgb(254,254,189)', + 'rgb(254,254,189)', 'rgb(0,104,55)', 'rgb(0,104,55)', ]); @@ -154,8 +154,8 @@ describe('Vislib Gauge Chart Test Suite', function () { }); expect(fills).toEqual([ 'rgb(8,48,107)', - 'rgb(107,174,214)', - 'rgb(107,174,214)', + 'rgb(109,174,213)', + 'rgb(109,174,213)', 'rgb(247,251,255)', 'rgb(247,251,255)', ]); From edb9453a83d3fa28b5492adfa5e8fe13116f7b27 Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Tue, 23 Mar 2021 18:29:26 +0100 Subject: [PATCH 32/93] adding kibana filter expression functions (#94069) --- .../common/es_query/filters/build_filters.ts | 6 +- .../search/expressions/exists_filter.test.ts | 33 +++++++ .../search/expressions/exists_filter.ts | 65 ++++++++++++++ .../common/search/expressions/field.test.ts | 42 +++++++++ .../data/common/search/expressions/field.ts | 67 ++++++++++++++ .../search/expressions/filters_to_ast.test.ts | 47 ++++++++++ .../search/expressions/filters_to_ast.ts | 23 +++++ .../data/common/search/expressions/index.ts | 7 ++ .../search/expressions/kibana_context.ts | 11 +-- .../search/expressions/kibana_context_type.ts | 3 + .../search/expressions/kibana_filter.test.ts | 30 +++++++ .../search/expressions/kibana_filter.ts | 61 +++++++++++++ .../search/expressions/phrase_filter.test.ts | 58 ++++++++++++ .../search/expressions/phrase_filter.ts | 89 +++++++++++++++++++ .../common/search/expressions/range.test.ts | 40 +++++++++ .../data/common/search/expressions/range.ts | 76 ++++++++++++++++ .../search/expressions/range_filter.test.ts | 41 +++++++++ .../common/search/expressions/range_filter.ts | 74 +++++++++++++++ .../data/public/search/search_service.ts | 12 +++ .../data/server/search/search_service.ts | 12 +++ .../common/expression_types/index.ts | 1 + .../unbox_expression_value.test.ts | 17 ++++ .../unbox_expression_value.ts | 16 ++++ src/plugins/expressions/common/util/index.ts | 1 + .../expressions/common/util/test_utils.ts | 20 +++++ .../public/embeddable/to_ast.ts | 9 +- 26 files changed, 851 insertions(+), 10 deletions(-) create mode 100644 src/plugins/data/common/search/expressions/exists_filter.test.ts create mode 100644 src/plugins/data/common/search/expressions/exists_filter.ts create mode 100644 src/plugins/data/common/search/expressions/field.test.ts create mode 100644 src/plugins/data/common/search/expressions/field.ts create mode 100644 src/plugins/data/common/search/expressions/filters_to_ast.test.ts create mode 100644 src/plugins/data/common/search/expressions/filters_to_ast.ts create mode 100644 src/plugins/data/common/search/expressions/kibana_filter.test.ts create mode 100644 src/plugins/data/common/search/expressions/kibana_filter.ts create mode 100644 src/plugins/data/common/search/expressions/phrase_filter.test.ts create mode 100644 src/plugins/data/common/search/expressions/phrase_filter.ts create mode 100644 src/plugins/data/common/search/expressions/range.test.ts create mode 100644 src/plugins/data/common/search/expressions/range.ts create mode 100644 src/plugins/data/common/search/expressions/range_filter.test.ts create mode 100644 src/plugins/data/common/search/expressions/range_filter.ts create mode 100644 src/plugins/expressions/common/expression_types/unbox_expression_value.test.ts create mode 100644 src/plugins/expressions/common/expression_types/unbox_expression_value.ts create mode 100644 src/plugins/expressions/common/util/test_utils.ts diff --git a/src/plugins/data/common/es_query/filters/build_filters.ts b/src/plugins/data/common/es_query/filters/build_filters.ts index 42a4d66359346e..ba1bd0a6154939 100644 --- a/src/plugins/data/common/es_query/filters/build_filters.ts +++ b/src/plugins/data/common/es_query/filters/build_filters.ts @@ -26,13 +26,15 @@ export function buildFilter( disabled: boolean, params: any, alias: string | null, - store: FilterStateStore + store?: FilterStateStore ): Filter { const filter = buildBaseFilter(indexPattern, field, type, params); filter.meta.alias = alias; filter.meta.negate = negate; filter.meta.disabled = disabled; - filter.$state = { store }; + if (store) { + filter.$state = { store }; + } return filter; } diff --git a/src/plugins/data/common/search/expressions/exists_filter.test.ts b/src/plugins/data/common/search/expressions/exists_filter.test.ts new file mode 100644 index 00000000000000..e3b53b22813983 --- /dev/null +++ b/src/plugins/data/common/search/expressions/exists_filter.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createMockContext } from '../../../../expressions/common'; +import { functionWrapper } from './utils'; +import { existsFilterFunction } from './exists_filter'; + +describe('interpreter/functions#existsFilter', () => { + const fn = functionWrapper(existsFilterFunction); + + it('returns an object with the correct structure', () => { + const actual = fn(null, { field: { spec: { name: 'test' } } }, createMockContext()); + expect(actual).toMatchInlineSnapshot(` + Object { + "exists": Object { + "field": "test", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": undefined, + "negate": false, + }, + "type": "kibana_filter", + } + `); + }); +}); diff --git a/src/plugins/data/common/search/expressions/exists_filter.ts b/src/plugins/data/common/search/expressions/exists_filter.ts new file mode 100644 index 00000000000000..0979328860b4c9 --- /dev/null +++ b/src/plugins/data/common/search/expressions/exists_filter.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { KibanaField, KibanaFilter } from './kibana_context_type'; +import { buildFilter, FILTERS } from '../../es_query/filters'; +import { IndexPattern } from '../../index_patterns/index_patterns'; + +interface Arguments { + field: KibanaField; + negate?: boolean; +} + +export type ExpressionFunctionExistsFilter = ExpressionFunctionDefinition< + 'existsFilter', + null, + Arguments, + KibanaFilter +>; + +export const existsFilterFunction: ExpressionFunctionExistsFilter = { + name: 'existsFilter', + type: 'kibana_filter', + inputTypes: ['null'], + help: i18n.translate('data.search.functions.existsFilter.help', { + defaultMessage: 'Create kibana exists filter', + }), + args: { + field: { + types: ['kibana_field'], + required: true, + help: i18n.translate('data.search.functions.existsFilter.field.help', { + defaultMessage: 'Specify the field you want to filter on. Use `field` function.', + }), + }, + negate: { + types: ['boolean'], + default: false, + help: i18n.translate('data.search.functions.existsFilter.negate.help', { + defaultMessage: 'Should the filter be negated.', + }), + }, + }, + + fn(input, args) { + return { + type: 'kibana_filter', + ...buildFilter( + ({} as any) as IndexPattern, + args.field.spec, + FILTERS.EXISTS, + args.negate || false, + false, + {}, + null + ), + }; + }, +}; diff --git a/src/plugins/data/common/search/expressions/field.test.ts b/src/plugins/data/common/search/expressions/field.test.ts new file mode 100644 index 00000000000000..2ad139e2bd0a7e --- /dev/null +++ b/src/plugins/data/common/search/expressions/field.test.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExecutionContext } from 'src/plugins/expressions/common'; +import { functionWrapper } from './utils'; +import { fieldFunction } from './field'; + +describe('interpreter/functions#field', () => { + const fn = functionWrapper(fieldFunction); + let context: ExecutionContext; + + beforeEach(() => { + context = { + getSearchContext: () => ({}), + getSearchSessionId: () => undefined, + types: {}, + variables: {}, + abortSignal: {} as any, + inspectorAdapters: {} as any, + }; + }); + + it('returns an object with the correct structure', () => { + const actual = fn(null, { name: 'test', type: 'number' }, context); + expect(actual).toMatchInlineSnapshot(` + Object { + "spec": Object { + "name": "test", + "script": undefined, + "scripted": false, + "type": "number", + }, + "type": "kibana_field", + } + `); + }); +}); diff --git a/src/plugins/data/common/search/expressions/field.ts b/src/plugins/data/common/search/expressions/field.ts new file mode 100644 index 00000000000000..8c13069f50ad5b --- /dev/null +++ b/src/plugins/data/common/search/expressions/field.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { KibanaField } from './kibana_context_type'; + +interface Arguments { + name: string; + type: string; + script?: string; +} + +export type ExpressionFunctionField = ExpressionFunctionDefinition< + 'field', + null, + Arguments, + KibanaField +>; + +export const fieldFunction: ExpressionFunctionField = { + name: 'field', + type: 'kibana_field', + inputTypes: ['null'], + help: i18n.translate('data.search.functions.field.help', { + defaultMessage: 'Create a Kibana field.', + }), + args: { + name: { + types: ['string'], + required: true, + help: i18n.translate('data.search.functions.field.name.help', { + defaultMessage: 'Name of the field', + }), + }, + type: { + types: ['string'], + required: true, + help: i18n.translate('data.search.functions.field.type.help', { + defaultMessage: 'Type of the field', + }), + }, + script: { + types: ['string'], + help: i18n.translate('data.search.functions.field.script.help', { + defaultMessage: 'A field script, in case the field is scripted.', + }), + }, + }, + + fn(input, args) { + return { + type: 'kibana_field', + spec: { + name: args.name, + type: args.type, + scripted: args.script ? true : false, + script: args.script, + }, + } as KibanaField; + }, +}; diff --git a/src/plugins/data/common/search/expressions/filters_to_ast.test.ts b/src/plugins/data/common/search/expressions/filters_to_ast.test.ts new file mode 100644 index 00000000000000..108b48f9ea77ea --- /dev/null +++ b/src/plugins/data/common/search/expressions/filters_to_ast.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { filtersToAst } from './filters_to_ast'; + +describe('interpreter/functions#filtersToAst', () => { + const normalFilter = { + meta: { negate: false, alias: '', disabled: false }, + query: { test: 'something' }, + }; + const negatedFilter = { + meta: { negate: true, alias: '', disabled: false }, + query: { test: 'something' }, + }; + + it('converts a list of filters to an expression AST node', () => { + const actual = filtersToAst([normalFilter, negatedFilter]); + expect(actual).toHaveLength(2); + expect(actual[0].functions[0]).toHaveProperty('name', 'kibanaFilter'); + expect(actual[0].functions[0].arguments).toMatchInlineSnapshot(` + Object { + "negate": Array [ + false, + ], + "query": Array [ + "{\\"query\\":{\\"test\\":\\"something\\"}}", + ], + } + `); + expect(actual[1].functions[0]).toHaveProperty('name', 'kibanaFilter'); + expect(actual[1].functions[0].arguments).toMatchInlineSnapshot(` + Object { + "negate": Array [ + true, + ], + "query": Array [ + "{\\"query\\":{\\"test\\":\\"something\\"}}", + ], + } + `); + }); +}); diff --git a/src/plugins/data/common/search/expressions/filters_to_ast.ts b/src/plugins/data/common/search/expressions/filters_to_ast.ts new file mode 100644 index 00000000000000..a4dd959caecf6f --- /dev/null +++ b/src/plugins/data/common/search/expressions/filters_to_ast.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { buildExpression, buildExpressionFunction } from '../../../../expressions/common'; +import { Filter } from '../../es_query/filters'; +import { ExpressionFunctionKibanaFilter } from './kibana_filter'; + +export const filtersToAst = (filters: Filter[] | Filter) => { + return (Array.isArray(filters) ? filters : [filters]).map((filter) => { + const { meta, $state, ...restOfFilter } = filter; + return buildExpression([ + buildExpressionFunction('kibanaFilter', { + query: JSON.stringify(restOfFilter), + negate: filter.meta.negate, + }), + ]); + }); +}; diff --git a/src/plugins/data/common/search/expressions/index.ts b/src/plugins/data/common/search/expressions/index.ts index b38dce247261c5..b80cbad778a118 100644 --- a/src/plugins/data/common/search/expressions/index.ts +++ b/src/plugins/data/common/search/expressions/index.ts @@ -15,4 +15,11 @@ export * from './timerange_to_ast'; export * from './kibana_context_type'; export * from './esaggs'; export * from './utils'; +export * from './range'; +export * from './field'; +export * from './phrase_filter'; +export * from './exists_filter'; +export * from './range_filter'; +export * from './kibana_filter'; +export * from './filters_to_ast'; export * from './timerange'; diff --git a/src/plugins/data/common/search/expressions/kibana_context.ts b/src/plugins/data/common/search/expressions/kibana_context.ts index 5c2e2f418e69c9..98d7a2c45b4fc1 100644 --- a/src/plugins/data/common/search/expressions/kibana_context.ts +++ b/src/plugins/data/common/search/expressions/kibana_context.ts @@ -10,14 +10,15 @@ import { uniqBy } from 'lodash'; import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition, ExecutionContext } from 'src/plugins/expressions/common'; import { Adapters } from 'src/plugins/inspector/common'; +import { unboxExpressionValue } from '../../../../expressions/common'; import { Query, uniqFilters } from '../../query'; -import { ExecutionContextSearch, KibanaContext } from './kibana_context_type'; +import { ExecutionContextSearch, KibanaContext, KibanaFilter } from './kibana_context_type'; import { KibanaQueryOutput } from './kibana_context_type'; import { KibanaTimerangeOutput } from './timerange'; interface Arguments { q?: KibanaQueryOutput | null; - filters?: string | null; + filters?: KibanaFilter[] | null; timeRange?: KibanaTimerangeOutput | null; savedSearchId?: string | null; } @@ -56,8 +57,8 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = { }), }, filters: { - types: ['string', 'null'], - default: '"[]"', + types: ['kibana_filter', 'null'], + multi: true, help: i18n.translate('data.search.functions.kibana_context.filters.help', { defaultMessage: 'Specify Kibana generic filters', }), @@ -81,7 +82,7 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = { async fn(input, args, { getSavedObject }) { const timeRange = args.timeRange || input?.timeRange; let queries = mergeQueries(input?.query, args?.q || []); - let filters = [...(input?.filters || []), ...getParsedValue(args?.filters, [])]; + let filters = [...(input?.filters || []), ...(args?.filters?.map(unboxExpressionValue) || [])]; if (args.savedSearchId) { if (typeof getSavedObject !== 'function') { diff --git a/src/plugins/data/common/search/expressions/kibana_context_type.ts b/src/plugins/data/common/search/expressions/kibana_context_type.ts index 090f09f7004ca6..0a7c365bb29142 100644 --- a/src/plugins/data/common/search/expressions/kibana_context_type.ts +++ b/src/plugins/data/common/search/expressions/kibana_context_type.ts @@ -9,6 +9,7 @@ import { ExpressionValueBoxed } from 'src/plugins/expressions/common'; import { Filter } from '../../es_query'; import { Query, TimeRange } from '../../query'; +import { IndexPatternField } from '../../index_patterns/fields'; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type ExecutionContextSearch = { @@ -23,6 +24,8 @@ export type ExpressionValueSearchContext = ExpressionValueBoxed< >; export type KibanaQueryOutput = ExpressionValueBoxed<'kibana_query', Query>; +export type KibanaFilter = ExpressionValueBoxed<'kibana_filter', Filter>; +export type KibanaField = ExpressionValueBoxed<'kibana_field', IndexPatternField>; // TODO: These two are exported for legacy reasons - remove them eventually. export type KIBANA_CONTEXT_NAME = 'kibana_context'; diff --git a/src/plugins/data/common/search/expressions/kibana_filter.test.ts b/src/plugins/data/common/search/expressions/kibana_filter.test.ts new file mode 100644 index 00000000000000..ac8ae55492cc06 --- /dev/null +++ b/src/plugins/data/common/search/expressions/kibana_filter.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createMockContext } from '../../../../expressions/common'; +import { functionWrapper } from './utils'; +import { kibanaFilterFunction } from './kibana_filter'; + +describe('interpreter/functions#kibanaFilter', () => { + const fn = functionWrapper(kibanaFilterFunction); + + it('returns an object with the correct structure', () => { + const actual = fn(null, { query: '{ "name": "test" }' }, createMockContext()); + expect(actual).toMatchInlineSnapshot(` + Object { + "meta": Object { + "alias": "", + "disabled": false, + "negate": false, + }, + "name": "test", + "type": "kibana_filter", + } + `); + }); +}); diff --git a/src/plugins/data/common/search/expressions/kibana_filter.ts b/src/plugins/data/common/search/expressions/kibana_filter.ts new file mode 100644 index 00000000000000..6d6f70fa8d1d6b --- /dev/null +++ b/src/plugins/data/common/search/expressions/kibana_filter.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { KibanaFilter } from './kibana_context_type'; + +interface Arguments { + query: string; + negate?: boolean; +} + +export type ExpressionFunctionKibanaFilter = ExpressionFunctionDefinition< + 'kibanaFilter', + null, + Arguments, + KibanaFilter +>; + +export const kibanaFilterFunction: ExpressionFunctionKibanaFilter = { + name: 'kibanaFilter', + type: 'kibana_filter', + inputTypes: ['null'], + help: i18n.translate('data.search.functions.kibanaFilter.help', { + defaultMessage: 'Create kibana filter', + }), + args: { + query: { + types: ['string'], + aliases: ['q', '_'], + required: true, + help: i18n.translate('data.search.functions.kibanaFilter.field.help', { + defaultMessage: 'Specify free form esdsl query', + }), + }, + negate: { + types: ['boolean'], + default: false, + help: i18n.translate('data.search.functions.kibanaFilter.negate.help', { + defaultMessage: 'Should the filter be negated', + }), + }, + }, + + fn(input, args) { + return { + type: 'kibana_filter', + meta: { + negate: args.negate || false, + alias: '', + disabled: false, + }, + ...JSON.parse(args.query), + }; + }, +}; diff --git a/src/plugins/data/common/search/expressions/phrase_filter.test.ts b/src/plugins/data/common/search/expressions/phrase_filter.test.ts new file mode 100644 index 00000000000000..39bd907513a0df --- /dev/null +++ b/src/plugins/data/common/search/expressions/phrase_filter.test.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createMockContext } from '../../../../expressions/common'; +import { functionWrapper } from './utils'; +import { phraseFilterFunction } from './phrase_filter'; + +describe('interpreter/functions#phraseFilter', () => { + const fn = functionWrapper(phraseFilterFunction); + + it('returns an object with the correct structure', () => { + const actual = fn( + null, + { field: { spec: { name: 'test' } }, phrase: ['test', 'something'] }, + createMockContext() + ); + expect(actual).toMatchInlineSnapshot(` + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": undefined, + "key": "test", + "negate": false, + "params": Array [ + "test", + "something", + ], + "type": "phrases", + "value": "test, something", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "test": "test", + }, + }, + Object { + "match_phrase": Object { + "test": "something", + }, + }, + ], + }, + }, + "type": "kibana_filter", + } + `); + }); +}); diff --git a/src/plugins/data/common/search/expressions/phrase_filter.ts b/src/plugins/data/common/search/expressions/phrase_filter.ts new file mode 100644 index 00000000000000..0b19e8a1e416d8 --- /dev/null +++ b/src/plugins/data/common/search/expressions/phrase_filter.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { KibanaField, KibanaFilter } from './kibana_context_type'; +import { buildFilter, FILTERS } from '../../es_query/filters'; +import { IndexPattern } from '../../index_patterns/index_patterns'; + +interface Arguments { + field: KibanaField; + phrase: string[]; + negate?: boolean; +} + +export type ExpressionFunctionPhraseFilter = ExpressionFunctionDefinition< + 'rangeFilter', + null, + Arguments, + KibanaFilter +>; + +export const phraseFilterFunction: ExpressionFunctionPhraseFilter = { + name: 'rangeFilter', + type: 'kibana_filter', + inputTypes: ['null'], + help: i18n.translate('data.search.functions.phraseFilter.help', { + defaultMessage: 'Create kibana phrase or phrases filter', + }), + args: { + field: { + types: ['kibana_field'], + required: true, + help: i18n.translate('data.search.functions.phraseFilter.field.help', { + defaultMessage: 'Specify the field you want to filter on. Use `field` function.', + }), + }, + phrase: { + types: ['string'], + multi: true, + required: true, + help: i18n.translate('data.search.functions.phraseFilter.phrase.help', { + defaultMessage: 'Specify the phrases', + }), + }, + negate: { + types: ['boolean'], + default: false, + help: i18n.translate('data.search.functions.phraseFilter.negate.help', { + defaultMessage: 'Should the filter be negated', + }), + }, + }, + + fn(input, args) { + if (args.phrase.length === 1) { + return { + type: 'kibana_filter', + ...buildFilter( + ({} as any) as IndexPattern, + args.field.spec, + FILTERS.PHRASE, + args.negate || false, + false, + args.phrase[0], + null + ), + }; + } + + return { + type: 'kibana_filter', + ...buildFilter( + ({} as any) as IndexPattern, + args.field.spec, + FILTERS.PHRASES, + args.negate || false, + false, + args.phrase, + null + ), + }; + }, +}; diff --git a/src/plugins/data/common/search/expressions/range.test.ts b/src/plugins/data/common/search/expressions/range.test.ts new file mode 100644 index 00000000000000..fbb4781c33a04d --- /dev/null +++ b/src/plugins/data/common/search/expressions/range.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExecutionContext } from 'src/plugins/expressions/common'; +import { functionWrapper } from './utils'; +import { rangeFunction } from './range'; + +describe('interpreter/functions#range', () => { + const fn = functionWrapper(rangeFunction); + let context: ExecutionContext; + + beforeEach(() => { + context = { + getSearchContext: () => ({}), + getSearchSessionId: () => undefined, + types: {}, + variables: {}, + abortSignal: {} as any, + inspectorAdapters: {} as any, + }; + }); + + it('returns an object with the correct structure', () => { + const actual = fn(null, { lt: 20, gt: 10 }, context); + expect(actual).toMatchInlineSnapshot(` + Object { + "gt": 10, + "gte": undefined, + "lt": 20, + "lte": undefined, + "type": "kibana_range", + } + `); + }); +}); diff --git a/src/plugins/data/common/search/expressions/range.ts b/src/plugins/data/common/search/expressions/range.ts new file mode 100644 index 00000000000000..c7649a6e0669c1 --- /dev/null +++ b/src/plugins/data/common/search/expressions/range.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition, ExpressionValueBoxed } from 'src/plugins/expressions/common'; + +interface Arguments { + gt?: number | string; + lt?: number | string; + gte?: number | string; + lte?: number | string; +} + +export type KibanaRange = ExpressionValueBoxed<'kibana_range', Arguments>; + +export type ExpressionFunctionRange = ExpressionFunctionDefinition< + 'range', + null, + Arguments, + KibanaRange +>; + +export const rangeFunction: ExpressionFunctionRange = { + name: 'range', + type: 'kibana_range', + inputTypes: ['null'], + help: i18n.translate('data.search.functions.range.help', { + defaultMessage: 'Create kibana range filter', + }), + args: { + gt: { + types: ['string', 'number'], + help: i18n.translate('data.search.functions.range.gt.help', { + defaultMessage: 'Greater than', + }), + }, + lt: { + types: ['string', 'number'], + help: i18n.translate('data.search.functions.range.lt.help', { + defaultMessage: 'Less than', + }), + }, + gte: { + types: ['string', 'number'], + help: i18n.translate('data.search.functions.range.gte.help', { + defaultMessage: 'Greater or equal than', + }), + }, + lte: { + types: ['string', 'number'], + help: i18n.translate('data.search.functions.range.lte.help', { + defaultMessage: 'Less or equal than', + }), + }, + }, + + fn(input, args) { + if (args.lt === undefined && args.lte === undefined) { + throw new Error('lt or lte must be provided'); + } + + if (args.gt === undefined && args.gte === undefined) { + throw new Error('gt or gte must be provided'); + } + + return { + type: 'kibana_range', + ...args, + }; + }, +}; diff --git a/src/plugins/data/common/search/expressions/range_filter.test.ts b/src/plugins/data/common/search/expressions/range_filter.test.ts new file mode 100644 index 00000000000000..92670f8a044ba6 --- /dev/null +++ b/src/plugins/data/common/search/expressions/range_filter.test.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createMockContext } from '../../../../expressions/common'; +import { functionWrapper } from './utils'; +import { rangeFilterFunction } from './range_filter'; + +describe('interpreter/functions#rangeFilter', () => { + const fn = functionWrapper(rangeFilterFunction); + + it('returns an object with the correct structure', () => { + const actual = fn( + null, + { field: { spec: { name: 'test' } }, range: { gt: 10, lt: 20 } }, + createMockContext() + ); + expect(actual).toMatchInlineSnapshot(` + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": undefined, + "negate": false, + "params": Object {}, + }, + "range": Object { + "test": Object { + "gte": 10, + "lt": 20, + }, + }, + "type": "kibana_filter", + } + `); + }); +}); diff --git a/src/plugins/data/common/search/expressions/range_filter.ts b/src/plugins/data/common/search/expressions/range_filter.ts new file mode 100644 index 00000000000000..ed71f5362fe858 --- /dev/null +++ b/src/plugins/data/common/search/expressions/range_filter.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { KibanaField, KibanaFilter } from './kibana_context_type'; +import { buildFilter, FILTERS } from '../../es_query/filters'; +import { IndexPattern } from '../../index_patterns/index_patterns'; +import { KibanaRange } from './range'; + +interface Arguments { + field: KibanaField; + range: KibanaRange; + negate?: boolean; +} + +export type ExpressionFunctionRangeFilter = ExpressionFunctionDefinition< + 'rangeFilter', + null, + Arguments, + KibanaFilter +>; + +export const rangeFilterFunction: ExpressionFunctionRangeFilter = { + name: 'rangeFilter', + type: 'kibana_filter', + inputTypes: ['null'], + help: i18n.translate('data.search.functions.rangeFilter.help', { + defaultMessage: 'Create kibana range filter', + }), + args: { + field: { + types: ['kibana_field'], + required: true, + help: i18n.translate('data.search.functions.rangeFilter.field.help', { + defaultMessage: 'Specify the field you want to filter on. Use `field` function.', + }), + }, + range: { + types: ['kibana_range'], + required: true, + help: i18n.translate('data.search.functions.rangeFilter.range.help', { + defaultMessage: 'Specify the range, use `range` function.', + }), + }, + negate: { + types: ['boolean'], + default: false, + help: i18n.translate('data.search.functions.rangeFilter.negate.help', { + defaultMessage: 'Should the filter be negated', + }), + }, + }, + + fn(input, args) { + return { + type: 'kibana_filter', + ...buildFilter( + ({} as any) as IndexPattern, + args.field.spec, + FILTERS.RANGE, + args.negate || false, + false, + { from: args.range.gt || args.range.gte, to: args.range.lt || args.range.lte }, + null + ), + }; + }, +}; diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 8eb73ba62244f3..94fa5b7230f69f 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -28,6 +28,12 @@ import { kibanaTimerangeFunction, luceneFunction, kqlFunction, + fieldFunction, + rangeFunction, + existsFilterFunction, + rangeFilterFunction, + kibanaFilterFunction, + phraseFilterFunction, } from '../../common/search'; import { getCallMsearch } from './legacy'; import { AggsService, AggsStartDependencies } from './aggs'; @@ -108,6 +114,12 @@ export class SearchService implements Plugin { expressions.registerFunction(luceneFunction); expressions.registerFunction(kqlFunction); expressions.registerFunction(kibanaTimerangeFunction); + expressions.registerFunction(fieldFunction); + expressions.registerFunction(rangeFunction); + expressions.registerFunction(kibanaFilterFunction); + expressions.registerFunction(existsFilterFunction); + expressions.registerFunction(rangeFilterFunction); + expressions.registerFunction(phraseFilterFunction); expressions.registerType(kibanaContext); expressions.registerFunction(esdsl); diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index ab9fc84d511879..69710e82b73b42 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -43,6 +43,8 @@ import { registerUsageCollector } from './collectors/register'; import { usageProvider } from './collectors/usage'; import { searchTelemetry } from '../saved_objects'; import { + existsFilterFunction, + fieldFunction, IEsSearchRequest, IEsSearchResponse, IKibanaSearchRequest, @@ -52,11 +54,15 @@ import { kibanaContext, kibanaContextFunction, kibanaTimerangeFunction, + kibanaFilterFunction, kqlFunction, luceneFunction, + rangeFilterFunction, + rangeFunction, SearchSourceDependencies, searchSourceRequiredUiSettings, SearchSourceService, + phraseFilterFunction, } from '../../common/search'; import { getEsaggs } from './expressions'; import { @@ -149,6 +155,12 @@ export class SearchService implements Plugin { expressions.registerFunction(kqlFunction); expressions.registerFunction(kibanaTimerangeFunction); expressions.registerFunction(kibanaContextFunction); + expressions.registerFunction(fieldFunction); + expressions.registerFunction(rangeFunction); + expressions.registerFunction(kibanaFilterFunction); + expressions.registerFunction(existsFilterFunction); + expressions.registerFunction(rangeFilterFunction); + expressions.registerFunction(phraseFilterFunction); expressions.registerType(kibanaContext); const aggs = this.aggsService.setup({ registerFunction: expressions.registerFunction }); diff --git a/src/plugins/expressions/common/expression_types/index.ts b/src/plugins/expressions/common/expression_types/index.ts index 169dd434cf4301..c7f0340a52fad9 100644 --- a/src/plugins/expressions/common/expression_types/index.ts +++ b/src/plugins/expressions/common/expression_types/index.ts @@ -11,3 +11,4 @@ export * from './get_type'; export * from './serialize_provider'; export * from './expression_type'; export * from './specs'; +export * from './unbox_expression_value'; diff --git a/src/plugins/expressions/common/expression_types/unbox_expression_value.test.ts b/src/plugins/expressions/common/expression_types/unbox_expression_value.test.ts new file mode 100644 index 00000000000000..ace99d4925e9f7 --- /dev/null +++ b/src/plugins/expressions/common/expression_types/unbox_expression_value.test.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { unboxExpressionValue } from './unbox_expression_value'; + +describe('unboxExpressionValue()', () => { + it('should remove type property from a boxed value', () => { + const expressionValue = { type: 'something', value: 'something' }; + + expect(unboxExpressionValue(expressionValue)).toEqual({ value: 'something' }); + }); +}); diff --git a/src/plugins/expressions/common/expression_types/unbox_expression_value.ts b/src/plugins/expressions/common/expression_types/unbox_expression_value.ts new file mode 100644 index 00000000000000..bb41efdf15609f --- /dev/null +++ b/src/plugins/expressions/common/expression_types/unbox_expression_value.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionValueBoxed } from './types'; + +export function unboxExpressionValue({ + type, + ...value +}: ExpressionValueBoxed): T { + return value as T; +} diff --git a/src/plugins/expressions/common/util/index.ts b/src/plugins/expressions/common/util/index.ts index 5f83d962d5aea8..470dfc3c2d436b 100644 --- a/src/plugins/expressions/common/util/index.ts +++ b/src/plugins/expressions/common/util/index.ts @@ -10,3 +10,4 @@ export * from './create_error'; export * from './get_by_alias'; export * from './tables_adapter'; export * from './expressions_inspector_adapter'; +export * from './test_utils'; diff --git a/src/plugins/expressions/common/util/test_utils.ts b/src/plugins/expressions/common/util/test_utils.ts new file mode 100644 index 00000000000000..59bd0a4235d9b3 --- /dev/null +++ b/src/plugins/expressions/common/util/test_utils.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExecutionContext } from '../execution'; + +export const createMockContext = () => { + return { + getSearchContext: () => ({}), + getSearchSessionId: () => undefined, + types: {}, + variables: {}, + abortSignal: {} as any, + inspectorAdapters: {} as any, + } as ExecutionContext; +}; diff --git a/src/plugins/visualizations/public/embeddable/to_ast.ts b/src/plugins/visualizations/public/embeddable/to_ast.ts index 7ccff9394943af..95b64fe4e6e176 100644 --- a/src/plugins/visualizations/public/embeddable/to_ast.ts +++ b/src/plugins/visualizations/public/embeddable/to_ast.ts @@ -10,7 +10,7 @@ import { ExpressionFunctionKibana, ExpressionFunctionKibanaContext } from '../.. import { buildExpression, buildExpressionFunction } from '../../../expressions/public'; import { VisToExpressionAst } from '../types'; -import { queryToAst } from '../../../data/common'; +import { queryToAst, filtersToAst } from '../../../data/common'; /** * Creates an ast expression for a visualization based on kibana context (query, filters, timerange) @@ -22,12 +22,15 @@ import { queryToAst } from '../../../data/common'; export const toExpressionAst: VisToExpressionAst = async (vis, params) => { const { savedSearchId, searchSource } = vis.data; const query = searchSource?.getField('query'); - const filters = searchSource?.getField('filter'); + let filters = searchSource?.getField('filter'); + if (typeof filters === 'function') { + filters = filters(); + } const kibana = buildExpressionFunction('kibana', {}); const kibanaContext = buildExpressionFunction('kibana_context', { q: query && queryToAst(query), - filters: filters && JSON.stringify(filters), + filters: filters && filtersToAst(filters), savedSearchId, }); From 8a42049acbe8c08c9b6ea821212c5dde0273dae1 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 23 Mar 2021 13:36:46 -0400 Subject: [PATCH 33/93] [SECURITY SOLUTIONS] Bug top-n alerts (#94920) * Associate timeline filter/query/dataprovider to top-n for alerts events * fix pinned view when opening details panel * fix top-n to bring the right raw/all indices * review + do not add filter/query/dataprovider on Correlation/Pinned tab for topN Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../draggable_wrapper_hover_content.tsx | 3 +- .../common/components/top_n/index.test.tsx | 1 - .../public/common/components/top_n/index.tsx | 16 ++- .../common/components/top_n/selectors.tsx | 38 ++++++ .../common/components/top_n/top_n.test.tsx | 1 - .../public/common/components/top_n/top_n.tsx | 14 +- .../alerts_histogram_panel/index.test.tsx | 124 +++++++++++++++++- .../alerts_histogram_panel/index.tsx | 50 +++++-- .../components/signals_by_category/index.tsx | 3 + .../timeline/pinned_tab_content/index.tsx | 2 +- 10 files changed, 225 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/top_n/selectors.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx index 4295d64b439f2d..880f0b4e18acab 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx @@ -134,7 +134,7 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ ) ? SourcererScopeName.detections : SourcererScopeName.default; - const { browserFields, indexPattern, selectedPatterns } = useSourcererScope(activeScope); + const { browserFields, indexPattern } = useSourcererScope(activeScope); const handleStartDragToTimeline = useCallback(() => { startDragToTimeline(); if (closePopOver != null) { @@ -365,7 +365,6 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ browserFields={browserFields} field={field} indexPattern={indexPattern} - indexNames={selectedPatterns} onFilterAdded={onFilterAdded} timelineId={timelineId ?? undefined} toggleTopN={toggleTopN} diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx index 7ed35f0ff08f15..0b5e07488ff2ad 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx @@ -168,7 +168,6 @@ const store = createStore( let testProps = { browserFields: mockBrowserFields, field, - indexNames: [], indexPattern: mockIndexPattern, timelineId: TimelineId.hostsPageExternalAlerts, toggleTopN: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx index c01a13a7aac42a..a325285ad9c1fd 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx @@ -25,7 +25,7 @@ import { combineQueries } from '../../../timelines/components/timeline/helpers'; import { getOptions } from './helpers'; import { TopN } from './top_n'; -import { TimelineId } from '../../../../common/types/timeline'; +import { TimelineId, TimelineTabs } from '../../../../common/types/timeline'; const EMPTY_FILTERS: Filter[] = []; const EMPTY_QUERY: Query = { query: '', language: 'kuery' }; @@ -47,11 +47,16 @@ const makeMapStateToProps = () => { return { activeTimelineEventType: activeTimeline.eventType, - activeTimelineFilters, + activeTimelineFilters: + activeTimeline.activeTab === TimelineTabs.query ? activeTimelineFilters : EMPTY_FILTERS, activeTimelineFrom: activeTimelineInput.timerange.from, - activeTimelineKqlQueryExpression: getKqlQueryTimeline(state, TimelineId.active), + activeTimelineKqlQueryExpression: + activeTimeline.activeTab === TimelineTabs.query + ? getKqlQueryTimeline(state, TimelineId.active) + : null, activeTimelineTo: activeTimelineInput.timerange.to, - dataProviders: activeTimeline.dataProviders, + dataProviders: + activeTimeline.activeTab === TimelineTabs.query ? activeTimeline.dataProviders : [], globalQuery: getGlobalQuerySelector(state), globalFilters: getGlobalFiltersQuerySelector(state), kqlMode: activeTimeline.kqlMode, @@ -72,7 +77,6 @@ interface OwnProps { browserFields: BrowserFields; field: string; indexPattern: IIndexPattern; - indexNames: string[]; timelineId?: string; toggleTopN: () => void; onFilterAdded?: () => void; @@ -91,7 +95,6 @@ const StatefulTopNComponent: React.FC = ({ dataProviders, field, indexPattern, - indexNames, globalFilters = EMPTY_FILTERS, globalQuery = EMPTY_QUERY, kqlMode, @@ -154,7 +157,6 @@ const StatefulTopNComponent: React.FC = ({ filters={timelineId === TimelineId.active ? EMPTY_FILTERS : globalFilters} from={timelineId === TimelineId.active ? activeTimelineFrom : from} indexPattern={indexPattern} - indexNames={indexNames} options={options} query={timelineId === TimelineId.active ? EMPTY_QUERY : globalQuery} setAbsoluteRangeDatePickerTarget={timelineId === TimelineId.active ? 'timeline' : 'global'} diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/selectors.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/selectors.tsx new file mode 100644 index 00000000000000..f13a85da6baed4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/top_n/selectors.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { State } from '../../store'; +import { sourcererSelectors } from '../../store/selectors'; + +export interface IndicesSelector { + all: string[]; + raw: string[]; +} + +export const getIndicesSelector = () => { + const getkibanaIndexPatternsSelector = sourcererSelectors.kibanaIndexPatternsSelector(); + const getConfigIndexPatternsSelector = sourcererSelectors.configIndexPatternsSelector(); + const getSignalIndexNameSelector = sourcererSelectors.signalIndexNameSelector(); + + const mapStateToProps = (state: State): IndicesSelector => { + const rawIndices = new Set(getConfigIndexPatternsSelector(state)); + const kibanaIndexPatterns = getkibanaIndexPatternsSelector(state); + const alertIndexName = getSignalIndexNameSelector(state); + kibanaIndexPatterns.forEach(({ title }) => { + if (title !== alertIndexName) { + rawIndices.add(title); + } + }); + + return { + all: alertIndexName != null ? [...rawIndices, alertIndexName] : [...rawIndices], + raw: [...rawIndices], + }; + }; + + return mapStateToProps; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx index b1ccecdebb6c0c..cee4254fd7358b 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx @@ -101,7 +101,6 @@ describe('TopN', () => { field, filters: [], from: '2020-04-14T00:31:47.695Z', - indexNames: [], indexPattern: mockIndexPattern, options: defaultOptions, query, diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx index 76607fb9c0aca6..cc9be327cc4985 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx @@ -6,7 +6,9 @@ */ import { EuiButtonIcon, EuiSuperSelect } from '@elastic/eui'; +import deepEqual from 'fast-deep-equal'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; import styled from 'styled-components'; import { GlobalTimeArgs } from '../../containers/use_global_time'; @@ -18,6 +20,8 @@ import { TimelineEventsType } from '../../../../common/types/timeline'; import { TopNOption } from './helpers'; import * as i18n from './translations'; +import { getIndicesSelector, IndicesSelector } from './selectors'; +import { State } from '../../store'; const TopNContainer = styled.div` width: 600px; @@ -49,7 +53,6 @@ export interface Props extends Pick = ({ field, from, indexPattern, - indexNames, options, query, setAbsoluteRangeDatePickerTarget, @@ -80,6 +82,11 @@ const TopNComponent: React.FC = ({ const onViewSelected = useCallback((value: string) => setView(value as TimelineEventsType), [ setView, ]); + const indicesSelector = useMemo(getIndicesSelector, []); + const { all: allIndices, raw: rawIndices } = useSelector( + (state) => indicesSelector(state), + deepEqual + ); useEffect(() => { setView(defaultView); @@ -116,7 +123,7 @@ const TopNComponent: React.FC = ({ from={from} headerChildren={headerChildren} indexPattern={indexPattern} - indexNames={indexNames} + indexNames={view === 'raw' ? rawIndices : allIndices} onlyField={field} query={query} setAbsoluteRangeDatePickerTarget={setAbsoluteRangeDatePickerTarget} @@ -127,6 +134,7 @@ const TopNComponent: React.FC = ({ /> ) : ( { const originalModule = jest.requireActual('react-router-dom'); @@ -104,4 +105,125 @@ describe('AlertsHistogramPanel', () => { }); }); }); + + describe('CombinedQueries', () => { + jest.mock('./helpers'); + const mockGetAlertsHistogramQuery = jest.spyOn(helpers, 'getAlertsHistogramQuery'); + beforeEach(() => { + mockGetAlertsHistogramQuery.mockReset(); + }); + + it('combinedQueries props is valid, alerts query include combinedQueries', async () => { + const props = { + ...defaultProps, + query: { query: 'host.name: "', language: 'kql' }, + combinedQueries: + '{"bool":{"must":[],"filter":[{"match_all":{}},{"exists":{"field":"process.name"}}],"should":[],"must_not":[]}}', + }; + mount( + + + + ); + await waitFor(() => { + expect(mockGetAlertsHistogramQuery.mock.calls[0]).toEqual([ + 'signal.rule.name', + '2020-07-07T08:20:18.966Z', + '2020-07-08T08:20:18.966Z', + [ + { + bool: { + filter: [{ match_all: {} }, { exists: { field: 'process.name' } }], + must: [], + must_not: [], + should: [], + }, + }, + ], + ]); + }); + }); + }); + + describe('parseCombinedQueries', () => { + it('return empty object when variables is undefined', async () => { + expect(parseCombinedQueries(undefined)).toEqual({}); + }); + + it('return empty object when variables is empty string', async () => { + expect(parseCombinedQueries('')).toEqual({}); + }); + + it('return empty object when variables is NOT a valid stringify json object', async () => { + expect(parseCombinedQueries('hello world')).toEqual({}); + }); + + it('return a valid json object when variables is a valid json stringify', async () => { + expect( + parseCombinedQueries( + '{"bool":{"must":[],"filter":[{"match_all":{}},{"exists":{"field":"process.name"}}],"should":[],"must_not":[]}}' + ) + ).toMatchInlineSnapshot(` + Object { + "bool": Object { + "filter": Array [ + Object { + "match_all": Object {}, + }, + Object { + "exists": Object { + "field": "process.name", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + } + `); + }); + }); + + describe('buildCombinedQueries', () => { + it('return empty array when variables is undefined', async () => { + expect(buildCombinedQueries(undefined)).toEqual([]); + }); + + it('return empty array when variables is empty string', async () => { + expect(buildCombinedQueries('')).toEqual([]); + }); + + it('return array with empty object when variables is NOT a valid stringify json object', async () => { + expect(buildCombinedQueries('hello world')).toEqual([{}]); + }); + + it('return a valid json object when variables is a valid json stringify', async () => { + expect( + buildCombinedQueries( + '{"bool":{"must":[],"filter":[{"match_all":{}},{"exists":{"field":"process.name"}}],"should":[],"must_not":[]}}' + ) + ).toMatchInlineSnapshot(` + Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "match_all": Object {}, + }, + Object { + "exists": Object { + "field": "process.name", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + ] + `); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx index da75ffd5969bd4..e51fdc0094a69b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx @@ -58,6 +58,7 @@ const ViewAlertsFlexItem = styled(EuiFlexItem)` interface AlertsHistogramPanelProps extends Pick { chartHeight?: number; + combinedQueries?: string; defaultStackByOption?: AlertsHistogramOption; filters?: Filter[]; headerChildren?: React.ReactNode; @@ -86,9 +87,26 @@ const DEFAULT_STACK_BY = 'signal.rule.name'; const getDefaultStackByOption = (): AlertsHistogramOption => alertsHistogramOptions.find(({ text }) => text === DEFAULT_STACK_BY) ?? alertsHistogramOptions[0]; +export const parseCombinedQueries = (query?: string) => { + try { + return query != null && !isEmpty(query) ? JSON.parse(query) : {}; + } catch { + return {}; + } +}; + +export const buildCombinedQueries = (query?: string) => { + try { + return isEmpty(query) ? [] : [parseCombinedQueries(query)]; + } catch { + return []; + } +}; + export const AlertsHistogramPanel = memo( ({ chartHeight, + combinedQueries, defaultStackByOption = getDefaultStackByOption(), deleteQuery, filters, @@ -124,7 +142,12 @@ export const AlertsHistogramPanel = memo( request, refetch, } = useQueryAlerts<{}, AlertsAggregation>( - getAlertsHistogramQuery(selectedStackByOption.value, from, to, []), + getAlertsHistogramQuery( + selectedStackByOption.value, + from, + to, + buildCombinedQueries(combinedQueries) + ), signalIndexName ); const kibana = useKibana(); @@ -223,15 +246,20 @@ export const AlertsHistogramPanel = memo( useEffect(() => { try { - const converted = esQuery.buildEsQuery( - undefined, - query != null ? [query] : [], - filters?.filter((f) => f.meta.disabled === false) ?? [], - { - ...esQuery.getEsQueryConfig(kibana.services.uiSettings), - dateFormatTZ: undefined, - } - ); + let converted = null; + if (combinedQueries != null) { + converted = parseCombinedQueries(combinedQueries); + } else { + converted = esQuery.buildEsQuery( + undefined, + query != null ? [query] : [], + filters?.filter((f) => f.meta.disabled === false) ?? [], + { + ...esQuery.getEsQueryConfig(kibana.services.uiSettings), + dateFormatTZ: undefined, + } + ); + } setAlertsQuery( getAlertsHistogramQuery( @@ -245,7 +273,7 @@ export const AlertsHistogramPanel = memo( setAlertsQuery(getAlertsHistogramQuery(selectedStackByOption.value, from, to, [])); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedStackByOption.value, from, to, query, filters]); + }, [selectedStackByOption.value, from, to, query, filters, combinedQueries]); const linkButton = useMemo(() => { if (showLinkToAlerts) { diff --git a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx index 6e0b9317a4f183..a7823a1a6b98d2 100644 --- a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx @@ -19,6 +19,7 @@ import { UpdateDateRange } from '../../../common/components/charts/common'; import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; interface Props extends Pick { + combinedQueries?: string; filters?: Filter[]; headerChildren?: React.ReactNode; /** Override all defaults, and only display this field */ @@ -29,6 +30,7 @@ interface Props extends Pick = ({ + combinedQueries, deleteQuery, filters, from, @@ -61,6 +63,7 @@ const SignalsByCategoryComponent: React.FC = ({ return ( = ({ return ( <> - + {timelineFullScreen && setTimelineFullScreen != null && ( From 97a03479e37ffa320c7f31abef0f3f80c74ab0f8 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Tue, 23 Mar 2021 13:37:17 -0400 Subject: [PATCH 34/93] [App Search] Fix Relevance Tuning bugs (#95069) --- .../boosts/boost_item.test.tsx | 59 +++++++++++++++++++ .../relevance_tuning/boosts/boost_item.tsx | 1 + .../boost_item_content.test.tsx | 1 + .../value_boost_form.test.tsx | 14 ----- .../boost_item_content/value_boost_form.tsx | 2 +- .../relevance_tuning/boosts/boosts.test.tsx | 1 + .../components/relevance_tuning/constants.ts | 1 + .../relevance_tuning_form.test.tsx | 2 + .../relevance_tuning_item.test.tsx | 2 + .../relevance_tuning_item_content.test.tsx | 1 + .../relevance_tuning_logic.test.ts | 13 +++- .../relevance_tuning_logic.ts | 12 +++- .../components/relevance_tuning/types.ts | 2 +- .../components/relevance_tuning/utils.test.ts | 33 ++++++++++- .../components/relevance_tuning/utils.ts | 40 ++++++++++++- 15 files changed, 162 insertions(+), 22 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.test.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.test.tsx new file mode 100644 index 00000000000000..7ee921eef0e096 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.test.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiAccordion } from '@elastic/eui'; + +import { BoostIcon } from '../boost_icon'; +import { BoostType } from '../types'; + +import { ValueBadge } from '../value_badge'; + +import { BoostItem } from './boost_item'; +import { BoostItemContent } from './boost_item_content'; + +describe('BoostItem', () => { + const boost = { + factor: 2, + type: BoostType.Value, + newBoost: true, + value: [''], + }; + + let wrapper: ShallowWrapper; + let accordian: ShallowWrapper; + + beforeAll(() => { + wrapper = shallow(); + accordian = wrapper.find(EuiAccordion) as ShallowWrapper; + }); + + it('renders an accordion as open if it is a newly created boost', () => { + expect(accordian.prop('initialIsOpen')).toEqual(boost.newBoost); + }); + + it('renders an accordion button which shows a summary of the boost', () => { + const buttonContent = shallow( + accordian.prop('buttonContent') as React.ReactElement + ) as ShallowWrapper; + + expect(buttonContent.find(BoostIcon).prop('type')).toEqual('value'); + expect(buttonContent.find(ValueBadge).children().text()).toEqual('2'); + }); + + it('renders boost content inside of the accordion', () => { + const content = wrapper.find(BoostItemContent); + expect(content.props()).toEqual({ + boost, + index: 1, + name: 'foo', + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.tsx index 1dea62b2fd4785..98d83619866d18 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.tsx @@ -32,6 +32,7 @@ export const BoostItem: React.FC = ({ id, boost, index, name }) => { id={id} className="boosts__item" buttonContentClassName="boosts__itemButton" + initialIsOpen={!!boost.newBoost} buttonContent={ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.test.tsx index a16620e75412d1..e21f46c6748dde 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.test.tsx @@ -35,6 +35,7 @@ describe('BoostItemContent', () => { const boost = { factor: 2, type: 'value' as BoostType, + value: [''], }; const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.test.tsx index 32d27ff013a0e8..6fbf90e6a2000a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.test.tsx @@ -50,20 +50,6 @@ describe('ValueBoostForm', () => { expect(valueInput(wrapper, 2).prop('value')).toEqual('baz'); }); - it('renders a single empty text box if the boost has no value', () => { - const wrapper = shallow( - - ); - expect(valueInput(wrapper, 0).prop('value')).toEqual(''); - }); - it('updates the corresponding value in state whenever a user changes the value in a text input', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx index cd65f2842a5f7f..48d9749029a7e7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx @@ -30,7 +30,7 @@ interface Props { export const ValueBoostForm: React.FC = ({ boost, index, name }) => { const { updateBoostValue, removeBoostValue, addBoostValue } = useActions(RelevanceTuningLogic); - const values = boost.value || ['']; + const values = boost.value; return ( <> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx index 9f6e194f927356..897639fe9e6bce 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx @@ -108,6 +108,7 @@ describe('Boosts', () => { const boost1 = { factor: 2, type: 'value' as BoostType, + value: [''], }; const boost2 = { factor: 10, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts index 181ecad9e99903..796bc9242dd98b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts @@ -83,6 +83,7 @@ export const BOOST_TYPE_TO_ICON_MAP = { const EMPTY_VALUE_BOOST: ValueBoost = { type: BoostType.Value, factor: 1, + value: [''], newBoost: true, function: undefined, operation: undefined, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx index 2857b227749449..68d1b7439be5c5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx @@ -34,6 +34,7 @@ describe('RelevanceTuningForm', () => { { factor: 2, type: BoostType.Value, + value: [], }, ], }, @@ -85,6 +86,7 @@ describe('RelevanceTuningForm', () => { { factor: 2, type: BoostType.Value, + value: [], }, ]); expect(relevantTuningItems.at(1).prop('boosts')).toBeUndefined(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx index 674bb91929a764..97913ed49fefd9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx @@ -25,6 +25,7 @@ describe('RelevanceTuningItem', () => { { factor: 2, type: BoostType.Value, + value: [''], }, ], field: { @@ -54,6 +55,7 @@ describe('RelevanceTuningItem', () => { { factor: 2, type: BoostType.Value, + value: [''], }, { factor: 3, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx index 18a75766cd67be..65a42216e17ff8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx @@ -24,6 +24,7 @@ describe('RelevanceTuningItemContent', () => { { factor: 2, type: BoostType.Value, + value: [''], }, ], field: { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index f0fe98f3f0a878..ca9b0a886fdd1b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -23,6 +23,7 @@ describe('RelevanceTuningLogic', () => { { type: BoostType.Value, factor: 5, + value: [], }, ], }, @@ -224,7 +225,7 @@ describe('RelevanceTuningLogic', () => { describe('listeners', () => { const { http } = mockHttpValues; - const { flashAPIErrors, setSuccessMessage } = mockFlashMessageHelpers; + const { flashAPIErrors, setSuccessMessage, clearFlashMessages } = mockFlashMessageHelpers; let scrollToSpy: jest.SpyInstance; let confirmSpy: jest.SpyInstance; @@ -316,7 +317,7 @@ describe('RelevanceTuningLogic', () => { jest.useRealTimers(); }); - it('should make an API call and set state based on the response', async () => { + it('should make an API call, set state based on the response, and clear flash messages', async () => { const searchSettingsWithNewBoostProp = { boosts: { foo: [ @@ -324,6 +325,7 @@ describe('RelevanceTuningLogic', () => { type: BoostType.Value, factor: 5, newBoost: true, // This should be deleted before sent to the server + value: ['test'], }, ], }, @@ -341,6 +343,7 @@ describe('RelevanceTuningLogic', () => { { type: BoostType.Value, factor: 5, + value: ['test'], }, ], }, @@ -373,6 +376,7 @@ describe('RelevanceTuningLogic', () => { } ); expect(RelevanceTuningLogic.actions.setSearchResults).toHaveBeenCalledWith(searchResults); + expect(clearFlashMessages).toHaveBeenCalled(); }); it("won't send boosts or search_fields on the API call if there are none", async () => { @@ -481,6 +485,7 @@ describe('RelevanceTuningLogic', () => { type: BoostType.Value, factor: 5, newBoost: true, // This should be deleted before sent to the server + value: [''], }, ], }, @@ -492,6 +497,7 @@ describe('RelevanceTuningLogic', () => { { type: BoostType.Value, factor: 5, + value: [''], }, ], }, @@ -698,6 +704,7 @@ describe('RelevanceTuningLogic', () => { { factor: 2, type: BoostType.Value, + value: [''], }, ], }, @@ -714,6 +721,7 @@ describe('RelevanceTuningLogic', () => { { factor: 2, type: BoostType.Value, + value: [''], }, { factor: 1, @@ -771,6 +779,7 @@ describe('RelevanceTuningLogic', () => { { factor: 2, type: BoostType.Value, + value: [''], }, ], }, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts index 588b100416d10c..b87fef91c7d21e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts @@ -8,7 +8,11 @@ import { kea, MakeLogicType } from 'kea'; import { omit, cloneDeep, isEmpty } from 'lodash'; -import { setSuccessMessage, flashAPIErrors } from '../../../shared/flash_messages'; +import { + setSuccessMessage, + flashAPIErrors, + clearFlashMessages, +} from '../../../shared/flash_messages'; import { HttpLogic } from '../../../shared/http'; import { Schema, SchemaConflicts } from '../../../shared/types'; @@ -28,6 +32,7 @@ import { parseBoostCenter, removeBoostStateProps, normalizeBoostValues, + removeEmptyValueBoosts, } from './utils'; interface RelevanceTuningProps { @@ -273,18 +278,21 @@ export const RelevanceTuningLogic = kea< actions.setResultsLoading(true); + const filteredBoosts = removeEmptyValueBoosts(boosts); + try { const response = await http.post(url, { query: { query, }, body: JSON.stringify({ - boosts: isEmpty(boosts) ? undefined : boosts, + boosts: isEmpty(filteredBoosts) ? undefined : filteredBoosts, search_fields: isEmpty(searchFields) ? undefined : searchFields, }), }); actions.setSearchResults(response.results); + clearFlashMessages(); } catch (e) { flashAPIErrors(e); } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts index ec42df218878f9..58e589d606e4b7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts @@ -45,7 +45,7 @@ export interface RawBoost extends Omit { } export interface ValueBoost extends Boost { - value?: string[]; + value: string[]; operation: undefined; function: undefined; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts index 1694015ed68615..b5df8bf0c667a4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts @@ -4,12 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { BoostType } from './types'; +import { Boost, BoostType } from './types'; import { filterIfTerm, normalizeBoostValues, removeBoostStateProps, parseBoostCenter, + removeEmptyValueBoosts, } from './utils'; describe('filterIfTerm', () => { @@ -42,6 +43,7 @@ describe('removeBoostStateProps', () => { type: BoostType.Value, factor: 5, newBoost: true, + value: [''], }, ], }, @@ -58,6 +60,7 @@ describe('removeBoostStateProps', () => { { type: BoostType.Value, factor: 5, + value: [''], }, ], }, @@ -152,3 +155,31 @@ describe('normalizeBoostValues', () => { }); }); }); + +describe('removeEmptyValueBoosts', () => { + const boosts: Record = { + bar: [ + { factor: 9.5, type: BoostType.Proximity }, + { type: BoostType.Functional, factor: 5 }, + ], + foo: [ + { factor: 9.5, type: BoostType.Value, value: ['1'] }, + { factor: 9.5, type: BoostType.Value, value: ['1', '', ' '] }, + { factor: 9.5, type: BoostType.Value, value: [] }, + { factor: 9.5, type: BoostType.Value, value: ['', '1'] }, + ], + baz: [{ factor: 9.5, type: BoostType.Value, value: [''] }], + }; + + expect(removeEmptyValueBoosts(boosts)).toEqual({ + bar: [ + { factor: 9.5, type: BoostType.Proximity }, + { type: BoostType.Functional, factor: 5 }, + ], + foo: [ + { factor: 9.5, type: BoostType.Value, value: ['1'] }, + { factor: 9.5, type: BoostType.Value, value: ['1'] }, + { factor: 9.5, type: BoostType.Value, value: ['1'] }, + ], + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.ts index e2fd0f0bbd6569..5aaab80778e02b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.ts @@ -10,7 +10,7 @@ import { cloneDeep, omit } from 'lodash'; import { NUMBER } from '../../../shared/constants/field_types'; import { SchemaTypes } from '../../../shared/types'; -import { RawBoost, Boost, SearchSettings, BoostType } from './types'; +import { RawBoost, Boost, SearchSettings, BoostType, ValueBoost } from './types'; // If the user hasn't entered a filter, then we can skip filtering the array entirely export const filterIfTerm = (array: string[], filterTerm: string): string[] => { @@ -61,3 +61,41 @@ export const normalizeBoostValues = (boosts: Record): Record [fieldName]: boostList.map(normalizeBoostValue), }; }, {}); + +// Our model allows for empty values to be added to boosts. However, the server will not accept +// empty strings in values. To avoid that, we filter out empty values before sending them to the server. + +// I.e., the server will not accept any of the following, so we need to filter them out +// value: undefined +// value: [] +// value: [''] +// value: ['foo', ''] +export const removeEmptyValueBoosts = ( + boosts: Record +): Record => { + // before: + // { foo: { values: ['a', '', ' '] } } + // { foo: { values: [''] } } + // after: + // { foo: { values: ['a'] } } + const filterEmptyValueBoosts = (fieldBoosts: Boost[]) => { + return fieldBoosts.filter((boost: Boost) => { + if (boost.type !== BoostType.Value) return true; + + const valueBoost = boost as ValueBoost; + const filteredValues = valueBoost.value.filter((value) => value.trim() !== ''); + + if (filteredValues.length) { + boost.value = filteredValues; + return true; + } else { + return false; + } + }); + }; + + return Object.entries(boosts).reduce((acc, [fieldName, fieldBoosts]) => { + const updatedBoosts = filterEmptyValueBoosts(fieldBoosts); + return updatedBoosts.length ? { ...acc, [fieldName]: updatedBoosts } : acc; + }, {}); +}; From dc1f7d51d42cd198b50b68dce1cb8a4f349fd2e7 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 23 Mar 2021 13:14:05 -0500 Subject: [PATCH 35/93] [Metrics UI] Add anomalies table to flyout (#90305) * Add anomalies table to flyout * Fix type * Update i18n * use anomalyThreshold from configuration * use EuiBasicTable and format date * add actions menu * update text * change host/k8s toggle to EuiComboBox * add open in inventory to actions * add search functionality back * debounce api call * loading messaging * change types, cleanup * query with partition field instead of influencers to prevent long query ES server errors * show text when no jobs enabled * adjust date * close flyout after clicking show in inventory * fix callout Co-authored-by: neptunian Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../results/metrics_hosts_anomalies.ts | 29 +- .../infra_ml/results/metrics_k8s_anomalies.ts | 31 +- .../infra/public/hooks/use_sorting.tsx | 18 + .../anomalies_table/annomaly_summary.tsx | 55 ++ .../anomalies_table/anomalies_table.tsx | 483 ++++++++++++++++++ .../anomalies_table/pagination.tsx | 57 +++ .../anomaly_detection_flyout.tsx | 1 + .../ml/anomaly_detection/flyout_home.tsx | 128 ++--- .../hooks/use_metrics_hosts_anomalies.ts | 21 +- .../hooks/use_metrics_k8s_anomalies.ts | 13 +- .../metric_anomaly/evaluate_condition.ts | 16 +- .../infra/server/lib/infra_ml/common.ts | 2 + .../lib/infra_ml/metrics_hosts_anomalies.ts | 82 +-- .../lib/infra_ml/metrics_k8s_anomalies.ts | 59 ++- .../server/lib/infra_ml/queries/common.ts | 8 + .../queries/metrics_host_anomalies.test.ts | 1 + .../queries/metrics_hosts_anomalies.ts | 12 +- .../queries/metrics_k8s_anomalies.test.ts | 1 + .../infra_ml/queries/metrics_k8s_anomalies.ts | 12 +- .../results/metrics_hosts_anomalies.ts | 10 +- .../infra_ml/results/metrics_k8s_anomalies.ts | 10 +- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 23 files changed, 888 insertions(+), 165 deletions(-) create mode 100644 x-pack/plugins/infra/public/hooks/use_sorting.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/annomaly_summary.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/pagination.tsx diff --git a/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_hosts_anomalies.ts index 0b70b65b7069e7..7c6a0f6d2ada78 100644 --- a/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_hosts_anomalies.ts +++ b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_hosts_anomalies.ts @@ -13,17 +13,23 @@ import { anomalyTypeRT, paginationCursorRT, sortRT, paginationRT, metricRT } fro export const INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH = '/api/infra/infra_ml/results/metrics_hosts_anomalies'; -const metricsHostAnomalyCommonFieldsRT = rt.type({ - id: rt.string, - anomalyScore: rt.number, - typical: rt.number, - actual: rt.number, - type: anomalyTypeRT, - influencers: rt.array(rt.string), - duration: rt.number, - startTime: rt.number, - jobId: rt.string, -}); +const metricsHostAnomalyCommonFieldsRT = rt.intersection([ + rt.type({ + id: rt.string, + anomalyScore: rt.number, + typical: rt.number, + actual: rt.number, + type: anomalyTypeRT, + influencers: rt.array(rt.string), + duration: rt.number, + startTime: rt.number, + jobId: rt.string, + }), + rt.partial({ + partitionFieldName: rt.string, + partitionFieldValue: rt.string, + }), +]); const metricsHostsAnomalyRT = metricsHostAnomalyCommonFieldsRT; export type MetricsHostsAnomaly = rt.TypeOf; @@ -67,6 +73,7 @@ export const getMetricsHostsAnomaliesRequestPayloadRT = rt.type({ timeRange: timeRangeRT, }), rt.partial({ + query: rt.string, metric: metricRT, // Pagination properties pagination: paginationRT, diff --git a/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_k8s_anomalies.ts index 3ee6189dcbf9ae..2ad57c74ca2acc 100644 --- a/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_k8s_anomalies.ts +++ b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_k8s_anomalies.ts @@ -13,17 +13,23 @@ import { paginationCursorRT, anomalyTypeRT, sortRT, paginationRT, metricRT } fro export const INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH = '/api/infra/infra_ml/results/metrics_k8s_anomalies'; -const metricsK8sAnomalyCommonFieldsRT = rt.type({ - id: rt.string, - anomalyScore: rt.number, - typical: rt.number, - actual: rt.number, - type: anomalyTypeRT, - influencers: rt.array(rt.string), - duration: rt.number, - startTime: rt.number, - jobId: rt.string, -}); +const metricsK8sAnomalyCommonFieldsRT = rt.intersection([ + rt.type({ + id: rt.string, + anomalyScore: rt.number, + typical: rt.number, + actual: rt.number, + type: anomalyTypeRT, + influencers: rt.array(rt.string), + duration: rt.number, + startTime: rt.number, + jobId: rt.string, + }), + rt.partial({ + partitionFieldName: rt.string, + partitionFieldValue: rt.string, + }), +]); const metricsK8sAnomalyRT = metricsK8sAnomalyCommonFieldsRT; export type MetricsK8sAnomaly = rt.TypeOf; @@ -67,13 +73,12 @@ export const getMetricsK8sAnomaliesRequestPayloadRT = rt.type({ timeRange: timeRangeRT, }), rt.partial({ + query: rt.string, metric: metricRT, // Pagination properties pagination: paginationRT, // Sort properties sort: sortRT, - // Dataset filters - datasets: rt.array(rt.string), }), ]), }); diff --git a/x-pack/plugins/infra/public/hooks/use_sorting.tsx b/x-pack/plugins/infra/public/hooks/use_sorting.tsx new file mode 100644 index 00000000000000..a11586766ab9ee --- /dev/null +++ b/x-pack/plugins/infra/public/hooks/use_sorting.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useState } from 'react'; +import { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table'; + +export function useSorting(defaultSorting: CriteriaWithPagination['sort']) { + const [sorting, setSorting] = useState['sort']>(defaultSorting); + + return { + sorting, + setSorting, + }; +} diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/annomaly_summary.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/annomaly_summary.tsx new file mode 100644 index 00000000000000..09c8beeb2a1be4 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/annomaly_summary.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { MetricsHostsAnomaly } from '../../../../../../../../common/http_api/infra_ml/results'; +import { formatOneDecimalPlace } from '../../../../../../../../common/log_analysis'; + +export const AnomalySummary = ({ anomaly }: { anomaly: MetricsHostsAnomaly }) => { + const { actual, typical } = anomaly; + + const moreThanExpectedAnomalyMessage = i18n.translate( + 'xpack.infra.ml.anomalyFlyout.anomaliesTableMoreThanExpectedAnomalyMessage', + { + defaultMessage: 'more', + } + ); + + const fewerThanExpectedAnomalyMessage = i18n.translate( + 'xpack.infra.ml.anomalyFlyout.anomaliesTableFewerThanExpectedAnomalyMessage', + { + defaultMessage: 'fewer', + } + ); + + const isMore = actual > typical; + const message = isMore ? moreThanExpectedAnomalyMessage : fewerThanExpectedAnomalyMessage; + const ratio = isMore ? actual / typical : typical / actual; + const icon = isMore ? 'sortUp' : 'sortDown'; + // Edge case scenarios where actual and typical might sit at 0. + const useRatio = ratio !== Infinity; + const ratioMessage = useRatio ? `${formatOneDecimalPlace(ratio)}x` : ''; + + return ( + + + + + + {`${ratioMessage} ${message}`} + {/* {anomaly.categoryRegex && ( + <> + {': '} + + + )} */} + + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx new file mode 100644 index 00000000000000..7f0424cf487584 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx @@ -0,0 +1,483 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'; +import { EuiSuperDatePicker } from '@elastic/eui'; +import { + EuiFlexItem, + EuiSpacer, + EuiFieldSearch, + EuiBasicTable, + EuiFlexGroup, + EuiTableFieldDataColumnType, + EuiTableActionsColumnType, + Criteria, + EuiContextMenuItem, + EuiComboBox, + EuiButtonIcon, + EuiPopover, + EuiContextMenuPanel, + EuiIcon, + EuiText, + OnTimeChangeProps, +} from '@elastic/eui'; +import { FormattedDate, FormattedMessage } from 'react-intl'; +import { datemathToEpochMillis } from '../../../../../../../utils/datemath'; +import { SnapshotMetricType } from '../../../../../../../../common/inventory_models/types'; +import { withTheme } from '../../../../../../../../../../../src/plugins/kibana_react/common'; +import { PrefilledAnomalyAlertFlyout } from '../../../../../../../alerting/metric_anomaly/components/alert_flyout'; +import { useLinkProps } from '../../../../../../../hooks/use_link_props'; +import { useSorting } from '../../../../../../../hooks/use_sorting'; +import { useMetricsK8sAnomaliesResults } from '../../../../hooks/use_metrics_k8s_anomalies'; +import { useMetricsHostsAnomaliesResults } from '../../../../hooks/use_metrics_hosts_anomalies'; +import { + Metric, + MetricsHostsAnomaly, + Sort, +} from '../../../../../../../../common/http_api/infra_ml/results'; +import { PaginationControls } from './pagination'; +import { AnomalySummary } from './annomaly_summary'; +import { AnomalySeverityIndicator } from '../../../../../../../components/logging/log_analysis_results/anomaly_severity_indicator'; +import { useSourceContext } from '../../../../../../../containers/source'; +import { createResultsUrl } from '../flyout_home'; +import { useWaffleViewState, WaffleViewState } from '../../../../hooks/use_waffle_view_state'; +type JobType = 'k8s' | 'hosts'; +type SortField = 'anomalyScore' | 'startTime'; +interface JobOption { + id: JobType; + label: string; +} +const AnomalyActionMenu = ({ + jobId, + type, + startTime, + closeFlyout, + partitionFieldName, + partitionFieldValue, +}: { + jobId: string; + type: string; + startTime: number; + closeFlyout: () => void; + partitionFieldName?: string; + partitionFieldValue?: string; +}) => { + const [isOpen, setIsOpen] = useState(false); + const [isAlertOpen, setIsAlertOpen] = useState(false); + const close = useCallback(() => setIsOpen(false), [setIsOpen]); + const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); + const openAlert = useCallback(() => setIsAlertOpen(true), [setIsAlertOpen]); + const closeAlert = useCallback(() => setIsAlertOpen(false), [setIsAlertOpen]); + const { onViewChange } = useWaffleViewState(); + + const showInInventory = useCallback(() => { + const metricTypeMap: { [key in Metric]: SnapshotMetricType } = { + memory_usage: 'memory', + network_in: 'rx', + network_out: 'tx', + }; + // parse the anomaly job id for metric type + const jobIdParts = jobId.split('-'); + const jobIdMetric = jobIdParts[jobIdParts.length - 1]; + const metricType = metricTypeMap[jobIdMetric.replace(/hosts_|k8s_/, '') as Metric]; + const anomalyViewParams: WaffleViewState = { + metric: { type: metricType }, + sort: { by: 'name', direction: 'desc' }, + groupBy: [], + nodeType: type === 'metrics_k8s' ? 'pod' : 'host', + view: 'map', + customOptions: [], + customMetrics: [], + boundsOverride: { max: 1, min: 0 }, + autoBounds: true, + accountId: '', + region: '', + autoReload: false, + filterQuery: { + expression: + partitionFieldName && partitionFieldValue + ? `${partitionFieldName}: "${partitionFieldValue}"` + : ``, + kind: 'kuery', + }, + legend: { palette: 'cool', reverseColors: false, steps: 10 }, + time: startTime, + }; + onViewChange(anomalyViewParams); + closeFlyout(); + }, [jobId, onViewChange, startTime, type, partitionFieldName, partitionFieldValue, closeFlyout]); + + const anomaliesUrl = useLinkProps({ + app: 'ml', + pathname: `/explorer?_g=${createResultsUrl([jobId.toString()])}`, + }); + + const items = [ + + + , + + + , + + + , + ]; + + return ( + <> + + } + isOpen={isOpen && !isAlertOpen} + closePopover={close} + > + + + {isAlertOpen && } + + ); +}; +export const NoAnomaliesFound = withTheme(({ theme }) => ( + + +

+ +

+

+ +

+ + + + +
+)); +interface Props { + closeFlyout(): void; +} +export const AnomaliesTable = (props: Props) => { + const { closeFlyout } = props; + const [search, setSearch] = useState(''); + const [start, setStart] = useState('now-30d'); + const [end, setEnd] = useState('now'); + const [timeRange, setTimeRange] = useState<{ start: number; end: number }>({ + start: datemathToEpochMillis(start) || 0, + end: datemathToEpochMillis(end, 'up') || 0, + }); + const { sorting, setSorting } = useSorting({ + field: 'startTime', + direction: 'desc', + }); + const jobOptions = [ + { + id: `hosts` as JobType, + label: i18n.translate('xpack.infra.ml.anomalyFlyout.hostBtn', { + defaultMessage: 'Hosts', + }), + }, + { + id: `k8s` as JobType, + label: i18n.translate('xpack.infra.ml.anomalyFlyout.podsBtn', { + defaultMessage: 'Kubernetes Pods', + }), + }, + ]; + const [jobType, setJobType] = useState('hosts'); + const [selectedJobType, setSelectedJobType] = useState([ + jobOptions.find((item) => item.id === 'hosts') || jobOptions[0], + ]); + const { source } = useSourceContext(); + const anomalyThreshold = source?.configuration.anomalyThreshold; + + const onTimeChange = useCallback( + ({ isInvalid, start: startChange, end: endChange }: OnTimeChangeProps) => { + if (!isInvalid) { + setStart(startChange); + setEnd(endChange); + setTimeRange({ + start: datemathToEpochMillis(startChange)!, + end: datemathToEpochMillis(endChange, 'up')!, + }); + } + }, + [] + ); + + const anomalyParams = useMemo( + () => ({ + sourceId: 'default', + anomalyThreshold: anomalyThreshold || 0, + startTime: timeRange.start, + endTime: timeRange.end, + defaultSortOptions: { + direction: sorting?.direction || 'desc', + field: (sorting?.field || 'startTime') as SortField, + }, + defaultPaginationOptions: { pageSize: 10 }, + }), + [timeRange, sorting?.field, sorting?.direction, anomalyThreshold] + ); + + const { + metricsHostsAnomalies, + getMetricsHostsAnomalies, + page: hostPage, + changeSortOptions: hostChangeSort, + fetchNextPage: hostFetchNextPage, + fetchPreviousPage: hostFetchPrevPage, + isLoadingMetricsHostsAnomalies: hostLoading, + } = useMetricsHostsAnomaliesResults(anomalyParams); + const { + metricsK8sAnomalies, + getMetricsK8sAnomalies, + page: k8sPage, + changeSortOptions: k8sChangeSort, + fetchNextPage: k8sFetchNextPage, + fetchPreviousPage: k8sPreviousPage, + isLoadingMetricsK8sAnomalies: k8sLoading, + } = useMetricsK8sAnomaliesResults(anomalyParams); + const page = useMemo(() => (jobType === 'hosts' ? hostPage : k8sPage), [ + jobType, + hostPage, + k8sPage, + ]); + const isLoading = useMemo(() => (jobType === 'hosts' ? hostLoading : k8sLoading), [ + jobType, + hostLoading, + k8sLoading, + ]); + const fetchNextPage = useMemo( + () => (jobType === 'hosts' ? hostFetchNextPage : k8sFetchNextPage), + [jobType, hostFetchNextPage, k8sFetchNextPage] + ); + const fetchPreviousPage = useMemo( + () => (jobType === 'hosts' ? hostFetchPrevPage : k8sPreviousPage), + [jobType, hostFetchPrevPage, k8sPreviousPage] + ); + + const getAnomalies = useMemo(() => { + if (jobType === 'hosts') { + return getMetricsHostsAnomalies; + } else if (jobType === 'k8s') { + return getMetricsK8sAnomalies; + } + }, [jobType, getMetricsK8sAnomalies, getMetricsHostsAnomalies]); + + const results = useMemo(() => { + if (jobType === 'hosts') { + return metricsHostsAnomalies; + } else { + return metricsK8sAnomalies; + } + }, [jobType, metricsHostsAnomalies, metricsK8sAnomalies]); + + const onSearchChange = useCallback((e: ChangeEvent) => { + setSearch(e.target.value); + }, []); + + const changeJobType = useCallback((selectedOptions) => { + setSelectedJobType(selectedOptions); + setJobType(selectedOptions[0].id); + }, []); + + const changeSortOptions = useCallback( + (nextSortOptions: Sort) => { + if (jobType === 'hosts') { + hostChangeSort(nextSortOptions); + } else { + k8sChangeSort(nextSortOptions); + } + }, + [hostChangeSort, k8sChangeSort, jobType] + ); + + const onTableChange = (criteria: Criteria) => { + setSorting(criteria.sort); + changeSortOptions({ + field: (criteria?.sort?.field || 'startTime') as SortField, + direction: criteria?.sort?.direction || 'desc', + }); + }; + + const columns: Array< + | EuiTableFieldDataColumnType + | EuiTableActionsColumnType + > = [ + { + field: 'startTime', + name: i18n.translate('xpack.infra.ml.anomalyFlyout.columnTime', { + defaultMessage: 'Time', + }), + width: '15%', + sortable: true, + textOnly: true, + truncateText: true, + render: (startTime: number) => ( + + ), + }, + { + field: 'jobId', + name: i18n.translate('xpack.infra.ml.anomalyFlyout.columnJob', { + defaultMessage: 'Job', + }), + width: '25%', + render: (jobId: string) => jobId, + }, + { + field: 'anomalyScore', + name: i18n.translate('xpack.infra.ml.anomalyFlyout.columnSeverit', { + defaultMessage: 'Severity', + }), + width: '15%', + sortable: true, + render: (anomalyScore: number) => , + }, + { + field: 'typical', + name: i18n.translate('xpack.infra.ml.anomalyFlyout.columnSummary', { + defaultMessage: 'Summary', + }), + width: '15%', + textOnly: true, + render: (typical: number, item: MetricsHostsAnomaly) => , + }, + { + field: 'influencers', + name: i18n.translate('xpack.infra.ml.anomalyFlyout.columnInfluencerName', { + defaultMessage: 'Node name', + }), + width: '20%', + textOnly: true, + truncateText: true, + render: (influencers: string[]) => influencers.join(','), + }, + { + name: i18n.translate('xpack.infra.ml.anomalyFlyout.columnActionsName', { + defaultMessage: 'Actions', + }), + width: '10%', + actions: [ + { + render: (anomaly: MetricsHostsAnomaly) => { + return ( + + ); + }, + }, + ], + }, + ]; + + useEffect(() => { + if (getAnomalies) { + getAnomalies(undefined, search); + } + }, [getAnomalies, search]); + + return ( +
+ + + + + + + + + + + + + + + + + + + columns={columns} + items={results} + sorting={{ sort: sorting }} + onChange={onTableChange} + hasActions={true} + loading={isLoading} + noItemsMessage={ + isLoading ? ( + + ) : ( + + ) + } + /> + + +
+ ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/pagination.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/pagination.tsx new file mode 100644 index 00000000000000..322a29a129a123 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/pagination.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +const previousPageLabel = i18n.translate( + 'xpack.infra.logs.analysis.anomaliesTablePreviousPageLabel', + { + defaultMessage: 'Previous page', + } +); + +const nextPageLabel = i18n.translate('xpack.infra.logs.analysis.anomaliesTableNextPageLabel', { + defaultMessage: 'Next page', +}); + +export const PaginationControls = ({ + fetchPreviousPage, + fetchNextPage, + page, + isLoading, +}: { + fetchPreviousPage?: () => void; + fetchNextPage?: () => void; + page: number; + isLoading: boolean; +}) => { + return ( + + + + + + {page} + + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx index ba7ea0aa8d5d0f..326689e945e1d9 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx @@ -75,6 +75,7 @@ export const AnomalyDetectionFlyout = () => { )} {screenName === 'setup' && ( diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx index bc08827b271891..f7cad1b02a8dc6 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx @@ -14,6 +14,8 @@ import { EuiCallOut } from '@elastic/eui'; import { EuiButton } from '@elastic/eui'; import { EuiButtonEmpty } from '@elastic/eui'; import moment from 'moment'; +import { EuiTabs } from '@elastic/eui'; +import { EuiTab } from '@elastic/eui'; import { SubscriptionSplashContent } from '../../../../../../components/subscription_splash_content'; import { useInfraMLCapabilitiesContext } from '../../../../../../containers/ml/infra_ml_capabilities'; import { @@ -24,15 +26,18 @@ import { useMetricHostsModuleContext } from '../../../../../../containers/ml/mod import { useMetricK8sModuleContext } from '../../../../../../containers/ml/modules/metrics_k8s/module'; import { LoadingPage } from '../../../../../../components/loading_page'; import { useLinkProps } from '../../../../../../hooks/use_link_props'; +import { AnomaliesTable } from './anomalies_table/anomalies_table'; interface Props { hasSetupCapabilities: boolean; goToSetup(type: 'hosts' | 'kubernetes'): void; + closeFlyout(): void; } +type Tab = 'jobs' | 'anomalies'; export const FlyoutHome = (props: Props) => { - const [tab] = useState<'jobs' | 'anomalies'>('jobs'); - const { goToSetup } = props; + const [tab, setTab] = useState('jobs'); + const { goToSetup, closeFlyout } = props; const { fetchJobStatus: fetchHostJobStatus, setupStatus: hostSetupStatus, @@ -69,6 +74,12 @@ export const FlyoutHome = (props: Props) => { } }, [fetchK8sJobStatus, fetchHostJobStatus, hasInfraMLReadCapabilities]); + const hasJobs = hostJobSummaries.length > 0 || k8sJobSummaries.length > 0; + const manageJobsLinkProps = useLinkProps({ + app: 'ml', + pathname: '/jobs', + }); + if (!hasInfraMLCapabilities) { return ; } else if (!hasInfraMLReadCapabilities) { @@ -97,38 +108,66 @@ export const FlyoutHome = (props: Props) => { - -
- -

- -

-
-
+ + setTab('jobs')}> + Jobs + + setTab('anomalies')}> + Anomalies + + - - {(hostJobSummaries.length > 0 || k8sJobSummaries.length > 0) && ( - <> + 0} hasK8sJobs={k8sJobSummaries.length > 0} jobIds={jobIds} /> + ) + } + > + {tab === 'jobs' && ( + <> + {hasJobs && ( + <> + + + + + + + + + + )} + +

Create ML Jobs

+

+ +

+
+ + 0} + hasK8sJobs={k8sJobSummaries.length > 0} + hasSetupCapabilities={props.hasSetupCapabilities} + createHosts={createHosts} + createK8s={createK8s} + /> )} - {tab === 'jobs' && ( - 0} - hasK8sJobs={k8sJobSummaries.length > 0} - hasSetupCapabilities={props.hasSetupCapabilities} - createHosts={createHosts} - createK8s={createK8s} - /> - )} + + {tab === 'anomalies' && }
); @@ -158,39 +197,8 @@ const JobsEnabledCallout = (props: CalloutProps) => { }); } - const manageJobsLinkProps = useLinkProps({ - app: 'ml', - pathname: '/jobs', - }); - - const anomaliesUrl = useLinkProps({ - app: 'ml', - pathname: `/explorer?_g=${createResultsUrl(props.jobIds)}`, - }); - return ( <> - - - - - - - - - - - - - - - { } + icon={} // title="Hosts" title={ { } + icon={} title={ { ); }; -function createResultsUrl(jobIds: string[], mode = 'absolute') { +export const createResultsUrl = (jobIds: string[], mode = 'absolute') => { const idString = jobIds.map((j) => `'${j}'`).join(','); let path = ''; @@ -318,4 +326,4 @@ function createResultsUrl(jobIds: string[], mode = 'absolute') { path += "))&_a=(query:(query_string:(analyze_wildcard:!t,query:'*')))"; return path; -} +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts index 25afd05633fa53..5ef6c9429c4b34 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts @@ -174,13 +174,14 @@ export const useMetricsHostsAnomaliesResults = ({ const [getMetricsHostsAnomaliesRequest, getMetricsHostsAnomalies] = useTrackedPromise( { cancelPreviousOn: 'creation', - createPromise: async (metric: Metric) => { + createPromise: async (metric?: Metric, query?: string) => { const { timeRange: { start: queryStartTime, end: queryEndTime }, sortOptions, paginationOptions, paginationCursor, } = reducerState; + return await callGetMetricHostsAnomaliesAPI( { sourceId, @@ -188,6 +189,7 @@ export const useMetricsHostsAnomaliesResults = ({ startTime: queryStartTime, endTime: queryEndTime, metric, + query, sort: sortOptions, pagination: { ...paginationOptions, @@ -205,6 +207,7 @@ export const useMetricsHostsAnomaliesResults = ({ payload: { lastReceivedCursors: requestCursors }, }); } + // Check if we have more "next" entries. "Page" covers the "previous" scenario, // since we need to know the page we're on anyway. if (!paginationCursor || (paginationCursor && 'searchAfter' in paginationCursor)) { @@ -295,6 +298,7 @@ export const useMetricsHostsAnomaliesResults = ({ fetchPreviousPage: reducerState.page > 1 ? handleFetchPreviousPage : undefined, fetchNextPage: reducerState.hasNextPage ? handleFetchNextPage : undefined, page: reducerState.page, + timeRange: reducerState.timeRange, }; }; @@ -303,7 +307,8 @@ interface RequestArgs { anomalyThreshold: number; startTime: number; endTime: number; - metric: Metric; + metric?: Metric; + query?: string; sort: Sort; pagination: Pagination; } @@ -312,7 +317,16 @@ export const callGetMetricHostsAnomaliesAPI = async ( requestArgs: RequestArgs, fetch: HttpHandler ) => { - const { sourceId, anomalyThreshold, startTime, endTime, metric, sort, pagination } = requestArgs; + const { + sourceId, + anomalyThreshold, + startTime, + endTime, + metric, + sort, + pagination, + query, + } = requestArgs; const response = await fetch(INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH, { method: 'POST', body: JSON.stringify( @@ -324,6 +338,7 @@ export const callGetMetricHostsAnomaliesAPI = async ( startTime, endTime, }, + query, metric, sort, pagination, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts index c135a2c5e6661d..ad26c14df32b47 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts @@ -174,13 +174,12 @@ export const useMetricsK8sAnomaliesResults = ({ const [getMetricsK8sAnomaliesRequest, getMetricsK8sAnomalies] = useTrackedPromise( { cancelPreviousOn: 'creation', - createPromise: async (metric: Metric) => { + createPromise: async (metric?: Metric, query?: string) => { const { timeRange: { start: queryStartTime, end: queryEndTime }, sortOptions, paginationOptions, paginationCursor, - filteredDatasets: queryFilteredDatasets, } = reducerState; return await callGetMetricsK8sAnomaliesAPI( { @@ -189,12 +188,12 @@ export const useMetricsK8sAnomaliesResults = ({ startTime: queryStartTime, endTime: queryEndTime, metric, + query, sort: sortOptions, pagination: { ...paginationOptions, cursor: paginationCursor, }, - datasets: queryFilteredDatasets, }, services.http.fetch ); @@ -305,10 +304,10 @@ interface RequestArgs { anomalyThreshold: number; startTime: number; endTime: number; - metric: Metric; + metric?: Metric; + query?: string; sort: Sort; pagination: Pagination; - datasets?: string[]; } export const callGetMetricsK8sAnomaliesAPI = async ( @@ -321,9 +320,9 @@ export const callGetMetricsK8sAnomaliesAPI = async ( startTime, endTime, metric, + query, sort, pagination, - datasets, } = requestArgs; const response = await fetch(INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, { method: 'POST', @@ -337,9 +336,9 @@ export const callGetMetricsK8sAnomaliesAPI = async ( endTime, }, metric, + query, sort, pagination, - datasets, }, }) ), diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/evaluate_condition.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/evaluate_condition.ts index b7ef8ec7d23125..362cf0bc5a0731 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/evaluate_condition.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/evaluate_condition.ts @@ -31,21 +31,21 @@ export const evaluateCondition = async ({ }: ConditionParams) => { const getAnomalies = nodeType === 'k8s' ? getMetricK8sAnomalies : getMetricsHostsAnomalies; - const result = await getAnomalies( - { + const result = await getAnomalies({ + context: { spaceId, mlSystem, mlAnomalyDetectors, }, - sourceId ?? 'default', - threshold, + sourceId: sourceId ?? 'default', + anomalyThreshold: threshold, startTime, endTime, metric, - { field: 'anomalyScore', direction: 'desc' }, - { pageSize: 100 }, - influencerFilter - ); + sort: { field: 'anomalyScore', direction: 'desc' }, + pagination: { pageSize: 100 }, + influencerFilter, + }); return result; }; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/common.ts b/x-pack/plugins/infra/server/lib/infra_ml/common.ts index 686f27d714cc16..aba38ce4165c0d 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/common.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/common.ts @@ -27,6 +27,8 @@ export interface MappedAnomalyHit { duration: number; influencers: string[]; categoryId?: string; + partitionFieldName?: string; + partitionFieldValue?: string; } export interface InfluencerFilter { diff --git a/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts index f6e11f5294191b..1e307dae8e5b79 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts @@ -11,7 +11,7 @@ import { fetchMlJob, MappedAnomalyHit, InfluencerFilter } from './common'; import { getJobId, metricsHostsJobTypes, ANOMALY_THRESHOLD } from '../../../common/infra_ml'; import { Sort, Pagination } from '../../../common/http_api/infra_ml'; import type { MlSystem, MlAnomalyDetectors } from '../../types'; -import { InsufficientAnomalyMlJobsConfigured, isMlPrivilegesError } from './errors'; +import { isMlPrivilegesError } from './errors'; import { decodeOrThrow } from '../../../common/runtime_types'; import { metricsHostsAnomaliesResponseRT, @@ -60,17 +60,29 @@ async function getCompatibleAnomaliesJobIds( }; } -export async function getMetricsHostsAnomalies( - context: Required, - sourceId: string, - anomalyThreshold: ANOMALY_THRESHOLD, - startTime: number, - endTime: number, - metric: 'memory_usage' | 'network_in' | 'network_out' | undefined, - sort: Sort, - pagination: Pagination, - influencerFilter?: InfluencerFilter -) { +export async function getMetricsHostsAnomalies({ + context, + sourceId, + anomalyThreshold, + startTime, + endTime, + metric, + sort, + pagination, + influencerFilter, + query, +}: { + context: Required; + sourceId: string; + anomalyThreshold: ANOMALY_THRESHOLD; + startTime: number; + endTime: number; + metric: 'memory_usage' | 'network_in' | 'network_out' | undefined; + sort: Sort; + pagination: Pagination; + influencerFilter?: InfluencerFilter; + query?: string; +}) { const finalizeMetricsHostsAnomaliesSpan = startTracingSpan('get metrics hosts entry anomalies'); const { @@ -84,9 +96,11 @@ export async function getMetricsHostsAnomalies( ); if (jobIds.length === 0) { - throw new InsufficientAnomalyMlJobsConfigured( - 'Metrics Hosts ML jobs need to be configured to search anomalies' - ); + return { + data: [], + hasMoreEntries: false, + timimg: { spans: [] }, + }; } try { @@ -103,7 +117,8 @@ export async function getMetricsHostsAnomalies( endTime, sort, pagination, - influencerFilter + influencerFilter, + query ); const data = anomalies.map((anomaly) => { @@ -136,6 +151,8 @@ const parseAnomalyResult = (anomaly: MappedAnomalyHit, jobId: string) => { duration, influencers, startTime: anomalyStartTime, + partitionFieldName, + partitionFieldValue, } = anomaly; return { @@ -148,6 +165,8 @@ const parseAnomalyResult = (anomaly: MappedAnomalyHit, jobId: string) => { startTime: anomalyStartTime, type: 'metrics_hosts' as const, jobId, + partitionFieldName, + partitionFieldValue, }; }; @@ -159,7 +178,8 @@ async function fetchMetricsHostsAnomalies( endTime: number, sort: Sort, pagination: Pagination, - influencerFilter?: InfluencerFilter + influencerFilter?: InfluencerFilter, + query?: string ) { // We'll request 1 extra entry on top of our pageSize to determine if there are // more entries to be fetched. This avoids scenarios where the client side can't @@ -168,20 +188,18 @@ async function fetchMetricsHostsAnomalies( const expandedPagination = { ...pagination, pageSize: pagination.pageSize + 1 }; const finalizeFetchLogEntryAnomaliesSpan = startTracingSpan('fetch metrics hosts anomalies'); - + const hostQuery = createMetricsHostsAnomaliesQuery({ + jobIds, + anomalyThreshold, + startTime, + endTime, + sort, + pagination: expandedPagination, + influencerFilter, + jobQuery: query, + }); const results = decodeOrThrow(metricsHostsAnomaliesResponseRT)( - await mlSystem.mlAnomalySearch( - createMetricsHostsAnomaliesQuery({ - jobIds, - anomalyThreshold, - startTime, - endTime, - sort, - pagination: expandedPagination, - influencerFilter, - }), - jobIds - ) + await mlSystem.mlAnomalySearch(hostQuery, jobIds) ); const { @@ -219,6 +237,8 @@ async function fetchMetricsHostsAnomalies( bucket_span: duration, timestamp: anomalyStartTime, by_field_value: categoryId, + partition_field_value: partitionFieldValue, + partition_field_name: partitionFieldName, } = result._source; const hostInfluencers = influencers.filter((i) => i.influencer_field_name === 'host.name'); @@ -236,6 +256,8 @@ async function fetchMetricsHostsAnomalies( startTime: anomalyStartTime, duration: duration * 1000, categoryId, + partitionFieldName, + partitionFieldValue, }; }); diff --git a/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts index 34039e9107f007..00635caee96fc8 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts @@ -11,7 +11,7 @@ import { fetchMlJob, MappedAnomalyHit, InfluencerFilter } from './common'; import { getJobId, metricsK8SJobTypes, ANOMALY_THRESHOLD } from '../../../common/infra_ml'; import { Sort, Pagination } from '../../../common/http_api/infra_ml'; import type { MlSystem, MlAnomalyDetectors } from '../../types'; -import { InsufficientAnomalyMlJobsConfigured, isMlPrivilegesError } from './errors'; +import { isMlPrivilegesError } from './errors'; import { decodeOrThrow } from '../../../common/runtime_types'; import { metricsK8sAnomaliesResponseRT, @@ -60,17 +60,29 @@ async function getCompatibleAnomaliesJobIds( }; } -export async function getMetricK8sAnomalies( - context: Required, - sourceId: string, - anomalyThreshold: ANOMALY_THRESHOLD, - startTime: number, - endTime: number, - metric: 'memory_usage' | 'network_in' | 'network_out' | undefined, - sort: Sort, - pagination: Pagination, - influencerFilter?: InfluencerFilter -) { +export async function getMetricK8sAnomalies({ + context, + sourceId, + anomalyThreshold, + startTime, + endTime, + metric, + sort, + pagination, + influencerFilter, + query, +}: { + context: Required; + sourceId: string; + anomalyThreshold: ANOMALY_THRESHOLD; + startTime: number; + endTime: number; + metric: 'memory_usage' | 'network_in' | 'network_out' | undefined; + sort: Sort; + pagination: Pagination; + influencerFilter?: InfluencerFilter; + query?: string; +}) { const finalizeMetricsK8sAnomaliesSpan = startTracingSpan('get metrics k8s entry anomalies'); const { @@ -84,9 +96,11 @@ export async function getMetricK8sAnomalies( ); if (jobIds.length === 0) { - throw new InsufficientAnomalyMlJobsConfigured( - 'Log rate or categorisation ML jobs need to be configured to search anomalies' - ); + return { + data: [], + hasMoreEntries: false, + timimg: { spans: [] }, + }; } const { @@ -102,7 +116,8 @@ export async function getMetricK8sAnomalies( endTime, sort, pagination, - influencerFilter + influencerFilter, + query ); const data = anomalies.map((anomaly) => { @@ -132,6 +147,8 @@ const parseAnomalyResult = (anomaly: MappedAnomalyHit, jobId: string) => { duration, influencers, startTime: anomalyStartTime, + partitionFieldName, + partitionFieldValue, } = anomaly; return { @@ -144,6 +161,8 @@ const parseAnomalyResult = (anomaly: MappedAnomalyHit, jobId: string) => { influencers, type: 'metrics_k8s' as const, jobId, + partitionFieldName, + partitionFieldValue, }; }; @@ -155,7 +174,8 @@ async function fetchMetricK8sAnomalies( endTime: number, sort: Sort, pagination: Pagination, - influencerFilter?: InfluencerFilter | undefined + influencerFilter?: InfluencerFilter | undefined, + query?: string ) { // We'll request 1 extra entry on top of our pageSize to determine if there are // more entries to be fetched. This avoids scenarios where the client side can't @@ -175,6 +195,7 @@ async function fetchMetricK8sAnomalies( sort, pagination: expandedPagination, influencerFilter, + jobQuery: query, }), jobIds ) @@ -215,6 +236,8 @@ async function fetchMetricK8sAnomalies( timestamp: anomalyStartTime, by_field_value: categoryId, influencers, + partition_field_value: partitionFieldValue, + partition_field_name: partitionFieldName, } = result._source; const podInfluencers = influencers.filter( @@ -233,6 +256,8 @@ async function fetchMetricK8sAnomalies( startTime: anomalyStartTime, duration: duration * 1000, categoryId, + partitionFieldValue, + partitionFieldName, }; }); diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts index 6f996a672a44ad..f4088c56303e23 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts @@ -22,6 +22,14 @@ export const createJobIdFilters = (jobId: string) => [ }, ]; +export const createJobIdsQuery = (query: string) => [ + { + wildcard: { + job_id: `*${query}*`, + }, + }, +]; + export const createJobIdsFilters = (jobIds: string[]) => [ { terms: { diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_host_anomalies.test.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_host_anomalies.test.ts index 4c3e0ca8bc26f8..7c281963e5717c 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_host_anomalies.test.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_host_anomalies.test.ts @@ -49,6 +49,7 @@ describe('createMetricsHostAnomaliesQuery', () => { 'record_score', 'typical', 'actual', + 'partition_field_name', 'partition_field_value', 'timestamp', 'bucket_span', diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts index 7808851508a7c3..ab50986c3b3d5b 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts @@ -15,6 +15,7 @@ import { defaultRequestParameters, createAnomalyScoreFilter, createInfluencerFilter, + createJobIdsQuery, } from './common'; import { InfluencerFilter } from '../common'; import { Sort, Pagination } from '../../../../common/http_api/infra_ml'; @@ -36,6 +37,7 @@ export const createMetricsHostsAnomaliesQuery = ({ sort, pagination, influencerFilter, + jobQuery, }: { jobIds: string[]; anomalyThreshold: ANOMALY_THRESHOLD; @@ -44,16 +46,19 @@ export const createMetricsHostsAnomaliesQuery = ({ sort: Sort; pagination: Pagination; influencerFilter?: InfluencerFilter; + jobQuery?: string; }) => { const { field } = sort; const { pageSize } = pagination; - - const filters = [ + let filters: any = [ ...createJobIdsFilters(jobIds), ...createAnomalyScoreFilter(anomalyThreshold), ...createTimeRangeFilters(startTime, endTime), ...createResultTypeFilters(['record']), ]; + if (jobQuery) { + filters = [...filters, ...createJobIdsQuery(jobQuery)]; + } const influencerQuery = influencerFilter ? { must: createInfluencerFilter(influencerFilter) } @@ -64,6 +69,7 @@ export const createMetricsHostsAnomaliesQuery = ({ 'record_score', 'typical', 'actual', + 'partition_field_name', 'partition_field_value', 'timestamp', 'bucket_span', @@ -118,6 +124,8 @@ export const metricsHostsAnomalyHitRT = rt.type({ timestamp: rt.number, }), rt.partial({ + partition_field_name: rt.string, + partition_field_value: rt.string, by_field_value: rt.string, }), ]), diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.test.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.test.ts index 81dcb390dff56e..61efb896a6c891 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.test.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.test.ts @@ -49,6 +49,7 @@ describe('createMetricsK8sAnomaliesQuery', () => { 'record_score', 'typical', 'actual', + 'partition_field_name', 'partition_field_value', 'timestamp', 'bucket_span', diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts index 54eea067177edf..8fb8df5eef3d75 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts @@ -15,6 +15,7 @@ import { defaultRequestParameters, createAnomalyScoreFilter, createInfluencerFilter, + createJobIdsQuery, } from './common'; import { InfluencerFilter } from '../common'; import { Sort, Pagination } from '../../../../common/http_api/infra_ml'; @@ -36,6 +37,7 @@ export const createMetricsK8sAnomaliesQuery = ({ sort, pagination, influencerFilter, + jobQuery, }: { jobIds: string[]; anomalyThreshold: ANOMALY_THRESHOLD; @@ -44,17 +46,20 @@ export const createMetricsK8sAnomaliesQuery = ({ sort: Sort; pagination: Pagination; influencerFilter?: InfluencerFilter; + jobQuery?: string; }) => { const { field } = sort; const { pageSize } = pagination; - const filters = [ + let filters: any = [ ...createJobIdsFilters(jobIds), ...createAnomalyScoreFilter(anomalyThreshold), ...createTimeRangeFilters(startTime, endTime), ...createResultTypeFilters(['record']), ]; - + if (jobQuery) { + filters = [...filters, ...createJobIdsQuery(jobQuery)]; + } const influencerQuery = influencerFilter ? { must: createInfluencerFilter(influencerFilter) } : {}; @@ -64,6 +69,7 @@ export const createMetricsK8sAnomaliesQuery = ({ 'record_score', 'typical', 'actual', + 'partition_field_name', 'partition_field_value', 'timestamp', 'bucket_span', @@ -116,6 +122,8 @@ export const metricsK8sAnomalyHitRT = rt.type({ timestamp: rt.number, }), rt.partial({ + partition_field_name: rt.string, + partition_field_value: rt.string, by_field_value: rt.string, }), ]), diff --git a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts index 6e227cfc12d113..15ff6c0834a757 100644 --- a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts +++ b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts @@ -39,6 +39,7 @@ export const initGetHostsAnomaliesRoute = ({ framework }: InfraBackendLibs) => { sort: sortParam, pagination: paginationParam, metric, + query, }, } = request.body; @@ -52,16 +53,17 @@ export const initGetHostsAnomaliesRoute = ({ framework }: InfraBackendLibs) => { paginationCursors, hasMoreEntries, timing, - } = await getMetricsHostsAnomalies( - requestContext.infra, + } = await getMetricsHostsAnomalies({ + context: requestContext.infra, sourceId, anomalyThreshold, startTime, endTime, metric, + query, sort, - pagination - ); + pagination, + }); return response.ok({ body: getMetricsHostsAnomaliesSuccessReponsePayloadRT.encode({ diff --git a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts index 1c2c4947a02ea3..667760d1d409e3 100644 --- a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts +++ b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts @@ -38,6 +38,7 @@ export const initGetK8sAnomaliesRoute = ({ framework }: InfraBackendLibs) => { sort: sortParam, pagination: paginationParam, metric, + query, }, } = request.body; @@ -51,16 +52,17 @@ export const initGetK8sAnomaliesRoute = ({ framework }: InfraBackendLibs) => { paginationCursors, hasMoreEntries, timing, - } = await getMetricK8sAnomalies( - requestContext.infra, + } = await getMetricK8sAnomalies({ + context: requestContext.infra, sourceId, anomalyThreshold, startTime, endTime, metric, + query, sort, - pagination - ); + pagination, + }); return response.ok({ body: getMetricsK8sAnomaliesSuccessReponsePayloadRT.encode({ diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ad3cf86081e156..a72585185faac8 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -11019,9 +11019,7 @@ "xpack.infra.metricsHeaderAddDataButtonLabel": "データの追加", "xpack.infra.missingEmebeddableFactoryCallout": "{embeddableType}埋め込み可能な項目がありません。これは、埋め込み可能プラグインが有効でない場合に、発生することがあります。", "xpack.infra.ml.anomalyDetectionButton": "異常検知", - "xpack.infra.ml.anomalyFlyout.anomaliesTabLabel": "異常を表示", "xpack.infra.ml.anomalyFlyout.create.createButton": "有効にする", - "xpack.infra.ml.anomalyFlyout.create.description": "異常検知は機械学習によって実現されています。次のリソースタイプでは、機械学習ジョブを使用できます。このようなジョブを有効にすると、インフラストラクチャメトリックで異常の検出を開始します。", "xpack.infra.ml.anomalyFlyout.create.hostDescription": "ホストのメモリー使用状況とネットワークトラフィックの異常を検出します。", "xpack.infra.ml.anomalyFlyout.create.hostSuccessTitle": "ホスト", "xpack.infra.ml.anomalyFlyout.create.hostTitle": "ホスト", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index babbccfab4ddec..3d864494a7d533 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11169,9 +11169,7 @@ "xpack.infra.metricsHeaderAddDataButtonLabel": "添加数据", "xpack.infra.missingEmebeddableFactoryCallout": "{embeddableType} 可嵌入对象不可用。如果可嵌入插件未启用,便可能会发生此问题。", "xpack.infra.ml.anomalyDetectionButton": "异常检测", - "xpack.infra.ml.anomalyFlyout.anomaliesTabLabel": "查看异常", "xpack.infra.ml.anomalyFlyout.create.createButton": "启用", - "xpack.infra.ml.anomalyFlyout.create.description": "异常检测由 Machine Learning 提供支持。Machine Learning 作业适用于以下资源类型。启用这些作业以开始检测基础架构指标中的异常。", "xpack.infra.ml.anomalyFlyout.create.hostDescription": "检测主机上的内存使用情况和网络流量异常。", "xpack.infra.ml.anomalyFlyout.create.hostSuccessTitle": "主机", "xpack.infra.ml.anomalyFlyout.create.hostTitle": "主机", From 2ed2cfe52f8322f7a3d3d5c331c934269722a6e1 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Tue, 23 Mar 2021 14:31:08 -0400 Subject: [PATCH 36/93] update api docs (#95154) --- api_docs/actions.json | 69 + api_docs/alerting.json | 84 +- api_docs/charts.json | 8 +- api_docs/core.json | 14 +- api_docs/core_application.json | 4 +- api_docs/data.json | 360 +-- api_docs/data_enhanced.json | 100 +- api_docs/data_index_patterns.json | 56 +- api_docs/data_search.json | 3443 +++++++++++++++------- api_docs/data_search.mdx | 3 - api_docs/data_ui.json | 32 +- api_docs/discover.json | 20 +- api_docs/expressions.json | 26 +- api_docs/file_upload.json | 2368 ++++++++++----- api_docs/fleet.json | 659 ++++- api_docs/global_search.json | 4 +- api_docs/kibana_react.json | 2 +- api_docs/lens.json | 2 +- api_docs/lists.json | 2 +- api_docs/management.json | 6 +- api_docs/maps.json | 1573 +++++++++- api_docs/maps.mdx | 17 + api_docs/maps_ems.json | 1157 ++++++++ api_docs/maps_ems.mdx | 46 + api_docs/maps_legacy.json | 277 -- api_docs/maps_legacy.mdx | 26 - api_docs/ml.json | 121 +- api_docs/monitoring.json | 24 +- api_docs/observability.json | 55 +- api_docs/presentation_util.json | 2 +- api_docs/reporting.json | 18 +- api_docs/saved_objects.json | 4 +- api_docs/saved_objects_management.json | 4 +- api_docs/saved_objects_tagging.json | 2 +- api_docs/security_solution.json | 501 ++-- api_docs/telemetry_collection_xpack.json | 44 +- api_docs/triggers_actions_ui.json | 12 +- api_docs/vis_type_timeseries.json | 2 +- api_docs/visualizations.json | 8 +- 39 files changed, 8188 insertions(+), 2967 deletions(-) create mode 100644 api_docs/maps_ems.json create mode 100644 api_docs/maps_ems.mdx delete mode 100644 api_docs/maps_legacy.json delete mode 100644 api_docs/maps_legacy.mdx diff --git a/api_docs/actions.json b/api_docs/actions.json index ec2bd86581f32d..f3f7f284c046c4 100644 --- a/api_docs/actions.json +++ b/api_docs/actions.json @@ -1646,6 +1646,21 @@ ], "enums": [], "misc": [ + { + "id": "def-common.AsApiContract", + "type": "Type", + "label": "AsApiContract", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/actions/common/rewrite_request_case.ts", + "lineNumber": 14 + }, + "signature": [ + "{ [K in keyof T as CamelToSnake>>]: T[K]; }" + ], + "initialIsOpen": false + }, { "tags": [], "id": "def-common.BASE_ACTION_API_PATH", @@ -1660,6 +1675,60 @@ "\"/api/actions\"" ], "initialIsOpen": false + }, + { + "id": "def-common.RewriteRequestCase", + "type": "Type", + "label": "RewriteRequestCase", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/actions/common/rewrite_request_case.ts", + "lineNumber": 18 + }, + "signature": [ + "(requested: ", + { + "pluginId": "actions", + "scope": "common", + "docId": "kibActionsPluginApi", + "section": "def-common.AsApiContract", + "text": "AsApiContract" + }, + ") => T" + ], + "initialIsOpen": false + }, + { + "id": "def-common.RewriteResponseCase", + "type": "Type", + "label": "RewriteResponseCase", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/actions/common/rewrite_request_case.ts", + "lineNumber": 19 + }, + "signature": [ + "(responded: T) => T extends (infer Item)[] ? ", + { + "pluginId": "actions", + "scope": "common", + "docId": "kibActionsPluginApi", + "section": "def-common.AsApiContract", + "text": "AsApiContract" + }, + "[] : ", + { + "pluginId": "actions", + "scope": "common", + "docId": "kibActionsPluginApi", + "section": "def-common.AsApiContract", + "text": "AsApiContract" + }, + "" + ], + "initialIsOpen": false } ], "objects": [] diff --git a/api_docs/alerting.json b/api_docs/alerting.json index 40f8e9c06ef159..5550798c4316f2 100644 --- a/api_docs/alerting.json +++ b/api_docs/alerting.json @@ -222,7 +222,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 92 + "lineNumber": 85 } }, { @@ -233,7 +233,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 93 + "lineNumber": 86 }, "signature": [ "Date" @@ -247,7 +247,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 94 + "lineNumber": 87 }, "signature": [ "Date | null" @@ -261,7 +261,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 95 + "lineNumber": 88 }, "signature": [ { @@ -282,7 +282,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 96 + "lineNumber": 89 }, "signature": [ "Params" @@ -296,7 +296,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 97 + "lineNumber": 90 }, "signature": [ "State" @@ -310,7 +310,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 98 + "lineNumber": 91 } }, { @@ -321,7 +321,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 99 + "lineNumber": 92 }, "signature": [ "string | undefined" @@ -335,7 +335,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 100 + "lineNumber": 93 } }, { @@ -346,7 +346,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 101 + "lineNumber": 94 }, "signature": [ "string[]" @@ -360,7 +360,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 102 + "lineNumber": 95 }, "signature": [ "string | null" @@ -374,7 +374,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 103 + "lineNumber": 96 }, "signature": [ "string | null" @@ -383,7 +383,7 @@ ], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 85 + "lineNumber": 78 }, "initialIsOpen": false }, @@ -404,7 +404,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 48 + "lineNumber": 46 }, "signature": [ "() => ", @@ -425,7 +425,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 49 + "lineNumber": 47 }, "signature": [ "() => Set<", @@ -447,7 +447,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 50 + "lineNumber": 48 }, "signature": [ "() => Promise<", @@ -464,7 +464,7 @@ ], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 47 + "lineNumber": 45 }, "initialIsOpen": false }, @@ -483,7 +483,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 226 + "lineNumber": 219 }, "signature": [ { @@ -503,7 +503,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 227 + "lineNumber": 220 }, "signature": [ { @@ -518,7 +518,7 @@ ], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 225 + "lineNumber": 218 }, "initialIsOpen": false }, @@ -548,7 +548,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 80 + "lineNumber": 73 }, "signature": [ "(id: string) => Pick<", @@ -565,7 +565,7 @@ ], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 75 + "lineNumber": 68 }, "initialIsOpen": false }, @@ -594,7 +594,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 127 + "lineNumber": 120 } }, { @@ -605,7 +605,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 128 + "lineNumber": 121 } }, { @@ -616,7 +616,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 129 + "lineNumber": 122 }, "signature": [ "{ params?: ", @@ -638,7 +638,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 132 + "lineNumber": 125 }, "signature": [ { @@ -659,7 +659,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 133 + "lineNumber": 126 }, "signature": [ "ActionGroupIds" @@ -673,7 +673,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 134 + "lineNumber": 127 }, "signature": [ { @@ -694,7 +694,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 135 + "lineNumber": 128 }, "signature": [ { @@ -723,7 +723,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 146 + "lineNumber": 139 } }, { @@ -734,7 +734,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 147 + "lineNumber": 140 }, "signature": [ "{ context?: ", @@ -772,7 +772,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 152 + "lineNumber": 145 }, "signature": [ "\"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\"" @@ -781,7 +781,7 @@ ], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 119 + "lineNumber": 112 }, "initialIsOpen": false }, @@ -905,7 +905,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 112 + "lineNumber": 111 } } ], @@ -913,13 +913,13 @@ "returnComment": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 104 + "lineNumber": 103 } } ], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 103 + "lineNumber": 102 }, "initialIsOpen": false }, @@ -938,7 +938,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 124 + "lineNumber": 123 }, "signature": [ "() => Set<", @@ -994,7 +994,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 125 + "lineNumber": 124 } } ], @@ -1002,7 +1002,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 125 + "lineNumber": 124 } }, { @@ -1013,7 +1013,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 126 + "lineNumber": 125 }, "signature": [ "() => Promise<", @@ -1030,7 +1030,7 @@ ], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 123 + "lineNumber": 122 }, "initialIsOpen": false } @@ -1177,7 +1177,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/types.ts", - "lineNumber": 185 + "lineNumber": 178 }, "signature": [ "Pick, \"id\"> & Partial, \"enabled\" | \"name\" | \"params\" | \"actions\" | \"muteAll\" | \"apiKey\" | \"tags\" | \"alertTypeId\" | \"consumer\" | \"schedule\" | \"scheduledTaskId\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"apiKeyOwner\" | \"throttle\" | \"notifyWhen\" | \"mutedInstanceIds\" | \"executionStatus\">>" diff --git a/api_docs/charts.json b/api_docs/charts.json index f063a2271aec7d..5c4008d0f25bcf 100644 --- a/api_docs/charts.json +++ b/api_docs/charts.json @@ -9,7 +9,7 @@ "children": [ { "type": "Object", - "label": "{\n onChange,\n color: selectedColor,\n label,\n useLegacyColors = true,\n colorIsOverwritten = true,\n}", + "label": "{\n onChange,\n color: selectedColor,\n label,\n useLegacyColors = true,\n colorIsOverwritten = true,\n onKeyDown,\n}", "isRequired": true, "signature": [ "ColorPickerProps" @@ -17,18 +17,18 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/components/color_picker.tsx", - "lineNumber": 108 + "lineNumber": 111 } } ], "signature": [ - "({ onChange, color: selectedColor, label, useLegacyColors, colorIsOverwritten, }: ColorPickerProps) => JSX.Element" + "({ onChange, color: selectedColor, label, useLegacyColors, colorIsOverwritten, onKeyDown, }: ColorPickerProps) => JSX.Element" ], "description": [], "label": "ColorPicker", "source": { "path": "src/plugins/charts/public/static/components/color_picker.tsx", - "lineNumber": 108 + "lineNumber": 111 }, "tags": [], "returnComment": [], diff --git a/api_docs/core.json b/api_docs/core.json index 7129c2cff14de0..e02bd33d6671b4 100644 --- a/api_docs/core.json +++ b/api_docs/core.json @@ -1431,7 +1431,7 @@ "description": [], "source": { "path": "src/core/public/doc_links/doc_links_service.ts", - "lineNumber": 283 + "lineNumber": 300 } }, { @@ -1442,7 +1442,7 @@ "description": [], "source": { "path": "src/core/public/doc_links/doc_links_service.ts", - "lineNumber": 284 + "lineNumber": 301 } }, { @@ -1453,16 +1453,16 @@ "description": [], "source": { "path": "src/core/public/doc_links/doc_links_service.ts", - "lineNumber": 285 + "lineNumber": 302 }, "signature": [ - "{ readonly dashboard: { readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; readonly urlDrilldownVariables: string; }; readonly discover: Record; readonly filebeat: { readonly base: string; readonly installation: string; readonly configuration: string; readonly elasticsearchOutput: string; readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; }; readonly auditbeat: { readonly base: string; }; readonly metricbeat: { readonly base: string; readonly configure: string; readonly httpEndpoint: string; readonly install: string; readonly start: string; }; readonly enterpriseSearch: { readonly base: string; readonly appSearchBase: string; readonly workplaceSearchBase: string; }; readonly heartbeat: { readonly base: string; }; readonly logstash: { readonly base: string; }; readonly functionbeat: { readonly base: string; }; readonly winlogbeat: { readonly base: string; }; readonly aggs: { readonly composite: string; readonly composite_missing_bucket: string; readonly date_histogram: string; readonly date_range: string; readonly date_format_pattern: string; readonly filter: string; readonly filters: string; readonly geohash_grid: string; readonly histogram: string; readonly ip_range: string; readonly range: string; readonly significant_terms: string; readonly terms: string; readonly avg: string; readonly avg_bucket: string; readonly max_bucket: string; readonly min_bucket: string; readonly sum_bucket: string; readonly cardinality: string; readonly count: string; readonly cumulative_sum: string; readonly derivative: string; readonly geo_bounds: string; readonly geo_centroid: string; readonly max: string; readonly median: string; readonly min: string; readonly moving_avg: string; readonly percentile_ranks: string; readonly serial_diff: string; readonly std_dev: string; readonly sum: string; readonly top_hits: string; }; readonly runtimeFields: string; readonly scriptedFields: { readonly scriptFields: string; readonly scriptAggs: string; readonly painless: string; readonly painlessApi: string; readonly painlessLangSpec: string; readonly painlessSyntax: string; readonly painlessWalkthrough: string; readonly luceneExpressions: string; }; readonly indexPatterns: { readonly loadingData: string; readonly introduction: string; }; readonly addData: string; readonly kibana: string; readonly elasticsearch: Record; readonly siem: { readonly guide: string; readonly gettingStarted: string; }; readonly query: { readonly eql: string; readonly luceneQuerySyntax: string; readonly queryDsl: string; readonly kueryQuerySyntax: string; }; readonly date: { readonly dateMath: string; readonly dateMathIndexNames: string; }; readonly management: Record; readonly ml: Record; readonly transforms: Record; readonly visualize: Record; readonly apis: Readonly<{ createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; createApiKey: string; createPipeline: string; createTransformRequest: string; cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; putWatch: string; updateTransform: string; }>; readonly observability: Record; readonly alerting: Record; readonly maps: Record; readonly monitoring: Record; readonly security: Readonly<{ apiKeyServiceSettings: string; clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; mappingRoles: string; mappingRolesFieldRules: string; runAsPrivilege: string; }>; readonly watcher: Record; readonly ccs: Record; readonly plugins: Record; readonly snapshotRestore: Record; }" + "{ readonly dashboard: { readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; readonly urlDrilldownVariables: string; }; readonly discover: Record; readonly filebeat: { readonly base: string; readonly installation: string; readonly configuration: string; readonly elasticsearchOutput: string; readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; }; readonly auditbeat: { readonly base: string; }; readonly metricbeat: { readonly base: string; readonly configure: string; readonly httpEndpoint: string; readonly install: string; readonly start: string; }; readonly enterpriseSearch: { readonly base: string; readonly appSearchBase: string; readonly workplaceSearchBase: string; }; readonly heartbeat: { readonly base: string; }; readonly logstash: { readonly base: string; }; readonly functionbeat: { readonly base: string; }; readonly winlogbeat: { readonly base: string; }; readonly aggs: { readonly composite: string; readonly composite_missing_bucket: string; readonly date_histogram: string; readonly date_range: string; readonly date_format_pattern: string; readonly filter: string; readonly filters: string; readonly geohash_grid: string; readonly histogram: string; readonly ip_range: string; readonly range: string; readonly significant_terms: string; readonly terms: string; readonly avg: string; readonly avg_bucket: string; readonly max_bucket: string; readonly min_bucket: string; readonly sum_bucket: string; readonly cardinality: string; readonly count: string; readonly cumulative_sum: string; readonly derivative: string; readonly geo_bounds: string; readonly geo_centroid: string; readonly max: string; readonly median: string; readonly min: string; readonly moving_avg: string; readonly percentile_ranks: string; readonly serial_diff: string; readonly std_dev: string; readonly sum: string; readonly top_hits: string; }; readonly runtimeFields: string; readonly scriptedFields: { readonly scriptFields: string; readonly scriptAggs: string; readonly painless: string; readonly painlessApi: string; readonly painlessLangSpec: string; readonly painlessSyntax: string; readonly painlessWalkthrough: string; readonly luceneExpressions: string; }; readonly indexPatterns: { readonly introduction: string; readonly fieldFormattersNumber: string; readonly fieldFormattersString: string; }; readonly addData: string; readonly kibana: string; readonly elasticsearch: Record; readonly siem: { readonly guide: string; readonly gettingStarted: string; }; readonly query: { readonly eql: string; readonly luceneQuerySyntax: string; readonly queryDsl: string; readonly kueryQuerySyntax: string; }; readonly date: { readonly dateMath: string; readonly dateMathIndexNames: string; }; readonly management: Record; readonly ml: Record; readonly transforms: Record; readonly visualize: Record; readonly apis: Readonly<{ createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; createApiKey: string; createPipeline: string; createTransformRequest: string; cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; putWatch: string; updateTransform: string; }>; readonly observability: Record; readonly alerting: Record; readonly maps: Record; readonly monitoring: Record; readonly security: Readonly<{ apiKeyServiceSettings: string; clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; mappingRoles: string; mappingRolesFieldRules: string; runAsPrivilege: string; }>; readonly watcher: Record; readonly ccs: Record; readonly plugins: Record; readonly snapshotRestore: Record; readonly ingest: Record; }" ] } ], "source": { "path": "src/core/public/doc_links/doc_links_service.ts", - "lineNumber": 282 + "lineNumber": 299 }, "initialIsOpen": false }, @@ -5819,7 +5819,7 @@ "lineNumber": 33 }, "signature": [ - "Pick & { title?: string | MountPoint | undefined; text?: string | MountPoint | undefined; } & { id: string; }" + "Pick & { title?: string | MountPoint | undefined; text?: string | MountPoint | undefined; } & { id: string; }" ], "initialIsOpen": false }, @@ -5864,7 +5864,7 @@ "lineNumber": 28 }, "signature": [ - "Pick & { title?: string | MountPoint | undefined; text?: string | MountPoint | undefined; }" + "Pick & { title?: string | MountPoint | undefined; text?: string | MountPoint | undefined; }" ], "initialIsOpen": false }, diff --git a/api_docs/core_application.json b/api_docs/core_application.json index e5bbbf1ca3b580..c15b47dcb0982e 100644 --- a/api_docs/core_application.json +++ b/api_docs/core_application.json @@ -1824,7 +1824,7 @@ "lineNumber": 322 }, "signature": [ - "Pick, \"status\" | \"id\" | \"title\" | \"order\" | \"category\" | \"navLinkStatus\" | \"defaultPath\" | \"tooltip\" | \"euiIconType\" | \"icon\" | \"capabilities\" | \"chromeless\" | \"appRoute\" | \"exactRoute\"> & { status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; meta: PublicAppMetaInfo; }" + "Pick, \"status\" | \"title\" | \"id\" | \"order\" | \"category\" | \"navLinkStatus\" | \"defaultPath\" | \"tooltip\" | \"euiIconType\" | \"icon\" | \"capabilities\" | \"chromeless\" | \"appRoute\" | \"exactRoute\"> & { status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; meta: PublicAppMetaInfo; }" ], "initialIsOpen": false }, @@ -1862,7 +1862,7 @@ "lineNumber": 277 }, "signature": [ - "Pick & { searchDeepLinks: PublicAppSearchDeepLinkInfo[]; keywords: string[]; }" + "Pick & { searchDeepLinks: PublicAppSearchDeepLinkInfo[]; keywords: string[]; }" ], "initialIsOpen": false } diff --git a/api_docs/data.json b/api_docs/data.json index 13e2b402a4afd9..a78aec92b1fa50 100644 --- a/api_docs/data.json +++ b/api_docs/data.json @@ -5492,7 +5492,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 464 + "lineNumber": 465 } } ], @@ -5513,7 +5513,7 @@ "label": "get", "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 464 + "lineNumber": 465 }, "tags": [], "returnComment": [] @@ -5561,7 +5561,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } }, { @@ -5574,7 +5574,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } } ], @@ -5584,7 +5584,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } }, { @@ -5630,7 +5630,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -5645,7 +5645,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -5660,7 +5660,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } } ], @@ -5668,7 +5668,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -5714,7 +5714,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } }, { @@ -5729,7 +5729,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } } ], @@ -5737,7 +5737,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } }, { @@ -5775,7 +5775,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 547 + "lineNumber": 548 } }, { @@ -5788,7 +5788,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 548 + "lineNumber": 549 } }, { @@ -5801,7 +5801,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 549 + "lineNumber": 550 } } ], @@ -5809,7 +5809,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 546 + "lineNumber": 547 } }, { @@ -5835,7 +5835,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 632 + "lineNumber": 633 } } ], @@ -5843,7 +5843,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 632 + "lineNumber": 633 } } ], @@ -11594,7 +11594,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 23 + "lineNumber": 25 }, "signature": [ "ExpressionFunctionDefinition<\"kibana_context\", ", @@ -11866,7 +11866,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 638 + "lineNumber": 639 }, "signature": [ "{ get: (id: string) => Promise; delete: (indexPatternId: string) => Promise<{}>; create: (spec: IndexPatternSpec, skipFetchFields?: boolean) => Promise; ensureDefaultIndexPattern: EnsureDefaultIndexPattern; getIds: (refresh?: boolean) => Promise; getTitles: (refresh?: boolean) => Promise; find: (search: string, size?: number) => Promise; getIdsWithTitle: (refresh?: boolean) => Promise<{ id: string; title: string; }[]>; clearCache: (id?: string | undefined) => void; getCache: () => Promise[] | null | undefined>; getDefault: () => Promise; setDefault: (id: string, force?: boolean) => Promise; getFieldsForWildcard: (options: GetFieldsOptions) => Promise; getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise; refreshFields: (indexPattern: IndexPattern) => Promise; fieldArrayToMap: (fields: FieldSpec[], fieldAttrs?: FieldAttrs | undefined) => Record; savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec; createAndSave: (spec: IndexPatternSpec, override?: boolean, skipFetchFields?: boolean) => Promise; createSavedObject: (indexPattern: IndexPattern, override?: boolean) => Promise; updateSavedObject: (indexPattern: IndexPattern, saveAttempts?: number, ignoreErrors?: boolean) => Promise; }" @@ -11946,7 +11946,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 27 + "lineNumber": 29 }, "signature": [ "{ type: \"kibana_context\"; } & ExecutionContextSearch" @@ -12150,7 +12150,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 56 + "lineNumber": 57 }, "signature": [ "(props: ", @@ -12164,6 +12164,28 @@ ") => JSX.Element" ] }, + { + "tags": [], + "id": "def-public.esFilters.FilterItem", + "type": "Function", + "label": "FilterItem", + "description": [], + "source": { + "path": "src/plugins/data/public/index.ts", + "lineNumber": 58 + }, + "signature": [ + "(props: ", + { + "pluginId": "data", + "scope": "public", + "docId": "kibDataUiPluginApi", + "section": "def-public.FilterItemProps", + "text": "FilterItemProps" + }, + ") => JSX.Element" + ] + }, { "tags": [], "id": "def-public.esFilters.FILTERS", @@ -12172,7 +12194,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 58 + "lineNumber": 60 }, "signature": [ "typeof ", @@ -12193,7 +12215,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 59 + "lineNumber": 61 }, "signature": [ "typeof ", @@ -12214,7 +12236,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 61 + "lineNumber": 63 }, "signature": [ "(isPinned: boolean, index?: string | undefined) => ", @@ -12235,7 +12257,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 62 + "lineNumber": 64 }, "signature": [ "(field: ", @@ -12272,7 +12294,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 63 + "lineNumber": 65 }, "signature": [ "(field: ", @@ -12309,7 +12331,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 64 + "lineNumber": 66 }, "signature": [ "(field: ", @@ -12346,7 +12368,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 65 + "lineNumber": 67 }, "signature": [ "(query: any, index: string, alias: string) => ", @@ -12367,7 +12389,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 66 + "lineNumber": 68 }, "signature": [ "(field: ", @@ -12412,7 +12434,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 68 + "lineNumber": 70 }, "signature": [ "(filter: any) => filter is ", @@ -12433,7 +12455,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 69 + "lineNumber": 71 }, "signature": [ "(filter: any) => filter is ", @@ -12454,7 +12476,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 70 + "lineNumber": 72 }, "signature": [ "(filter: any) => filter is ", @@ -12475,7 +12497,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 71 + "lineNumber": 73 }, "signature": [ "(filter: any) => filter is ", @@ -12496,7 +12518,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 72 + "lineNumber": 74 }, "signature": [ "(filter: any) => filter is ", @@ -12517,7 +12539,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 73 + "lineNumber": 75 }, "signature": [ "(filter: any) => filter is ", @@ -12538,7 +12560,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 74 + "lineNumber": 76 }, "signature": [ "(filter: any) => filter is ", @@ -12559,7 +12581,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 75 + "lineNumber": 77 }, "signature": [ "(filter: ", @@ -12581,7 +12603,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 77 + "lineNumber": 79 }, "signature": [ "(filter: ", @@ -12611,7 +12633,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 78 + "lineNumber": 80 }, "signature": [ "(filter: ", @@ -12640,7 +12662,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 79 + "lineNumber": 81 }, "signature": [ "(filter: ", @@ -12662,7 +12684,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 80 + "lineNumber": 82 }, "signature": [ "(filter: ", @@ -12684,7 +12706,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 81 + "lineNumber": 83 }, "signature": [ "typeof ", @@ -12705,7 +12727,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 83 + "lineNumber": 85 }, "signature": [ "(first: ", @@ -12758,7 +12780,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 84 + "lineNumber": 86 }, "signature": [ { @@ -12778,7 +12800,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 85 + "lineNumber": 87 }, "signature": [ "typeof ", @@ -12799,7 +12821,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 86 + "lineNumber": 88 }, "signature": [ "(newFilters?: ", @@ -12829,7 +12851,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 88 + "lineNumber": 90 }, "signature": [ "typeof ", @@ -12850,7 +12872,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 89 + "lineNumber": 91 }, "signature": [ "typeof ", @@ -12871,7 +12893,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 90 + "lineNumber": 92 }, "signature": [ "(filters: ", @@ -12901,7 +12923,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 91 + "lineNumber": 93 }, "signature": [ "typeof ", @@ -12922,7 +12944,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 92 + "lineNumber": 94 }, "signature": [ "typeof ", @@ -12940,7 +12962,7 @@ "label": "esFilters", "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 55 + "lineNumber": 56 }, "initialIsOpen": false }, @@ -12957,7 +12979,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 122 + "lineNumber": 124 }, "signature": [ { @@ -12977,7 +12999,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 123 + "lineNumber": 125 }, "signature": [ "(expression: any, parseOptions?: Partial<", @@ -13006,7 +13028,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 124 + "lineNumber": 126 }, "signature": [ "(node: ", @@ -13040,7 +13062,7 @@ "label": "esKuery", "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 121 + "lineNumber": 123 }, "initialIsOpen": false }, @@ -13057,7 +13079,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 128 + "lineNumber": 130 }, "signature": [ "typeof ", @@ -13078,7 +13100,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 129 + "lineNumber": 131 }, "signature": [ "typeof ", @@ -13099,7 +13121,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 130 + "lineNumber": 132 }, "signature": [ "(filters: ", @@ -13145,7 +13167,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 131 + "lineNumber": 133 }, "signature": [ "typeof ", @@ -13166,7 +13188,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 132 + "lineNumber": 134 }, "signature": [ "typeof ", @@ -13184,7 +13206,7 @@ "label": "esQuery", "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 127 + "lineNumber": 129 }, "initialIsOpen": false }, @@ -13201,7 +13223,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 212 + "lineNumber": 214 }, "signature": [ "typeof ", @@ -13222,7 +13244,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 213 + "lineNumber": 215 } } ], @@ -13230,7 +13252,7 @@ "label": "exporters", "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 211 + "lineNumber": 213 }, "initialIsOpen": false }, @@ -13247,7 +13269,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 169 + "lineNumber": 171 }, "signature": [ "typeof ", @@ -13268,7 +13290,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 170 + "lineNumber": 172 }, "signature": [ "typeof ", @@ -13289,7 +13311,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 172 + "lineNumber": 174 }, "signature": [ "{ range: string; regex: string; text: string; background: string; }" @@ -13303,7 +13325,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 173 + "lineNumber": 175 }, "signature": [ { @@ -13323,7 +13345,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 174 + "lineNumber": 176 }, "signature": [ { @@ -13343,7 +13365,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 175 + "lineNumber": 177 }, "signature": [ "typeof ", @@ -13364,7 +13386,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 177 + "lineNumber": 179 }, "signature": [ "typeof ", @@ -13385,7 +13407,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 178 + "lineNumber": 180 }, "signature": [ "typeof ", @@ -13406,7 +13428,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 179 + "lineNumber": 181 }, "signature": [ "typeof ", @@ -13427,7 +13449,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 180 + "lineNumber": 182 }, "signature": [ "typeof ", @@ -13448,7 +13470,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 181 + "lineNumber": 183 }, "signature": [ "typeof ", @@ -13469,7 +13491,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 182 + "lineNumber": 184 }, "signature": [ "typeof ", @@ -13490,7 +13512,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 183 + "lineNumber": 185 }, "signature": [ "typeof ", @@ -13511,7 +13533,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 184 + "lineNumber": 186 }, "signature": [ "typeof ", @@ -13532,7 +13554,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 185 + "lineNumber": 187 }, "signature": [ "typeof ", @@ -13553,7 +13575,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 186 + "lineNumber": 188 }, "signature": [ "typeof ", @@ -13574,7 +13596,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 187 + "lineNumber": 189 }, "signature": [ "typeof ", @@ -13595,7 +13617,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 188 + "lineNumber": 190 }, "signature": [ "typeof ", @@ -13616,7 +13638,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 189 + "lineNumber": 191 }, "signature": [ "typeof ", @@ -13637,7 +13659,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 190 + "lineNumber": 192 }, "signature": [ "typeof ", @@ -13658,7 +13680,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 191 + "lineNumber": 193 }, "signature": [ "typeof ", @@ -13679,7 +13701,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 192 + "lineNumber": 194 }, "signature": [ "typeof ", @@ -13697,7 +13719,7 @@ "label": "fieldFormats", "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 168 + "lineNumber": 170 }, "initialIsOpen": false }, @@ -13714,7 +13736,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 237 + "lineNumber": 239 } }, { @@ -13725,7 +13747,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 238 + "lineNumber": 240 } }, { @@ -13736,7 +13758,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 239 + "lineNumber": 241 }, "signature": [ "string[]" @@ -13750,7 +13772,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 240 + "lineNumber": 242 }, "signature": [ "string[]" @@ -13764,7 +13786,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 241 + "lineNumber": 243 }, "signature": [ "(indexPattern: ", @@ -13786,7 +13808,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 242 + "lineNumber": 244 }, "signature": [ "typeof ", @@ -13807,7 +13829,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 243 + "lineNumber": 245 }, "signature": [ "typeof ", @@ -13828,7 +13850,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 244 + "lineNumber": 246 }, "signature": [ "typeof ", @@ -13849,7 +13871,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 245 + "lineNumber": 247 }, "signature": [ "typeof ", @@ -13870,7 +13892,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 246 + "lineNumber": 248 }, "signature": [ "typeof ", @@ -13888,7 +13910,7 @@ "label": "indexPatterns", "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 236 + "lineNumber": 238 }, "initialIsOpen": false }, @@ -13910,7 +13932,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 403 + "lineNumber": 405 }, "signature": [ "typeof ", @@ -13931,7 +13953,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 404 + "lineNumber": 406 }, "signature": [ "typeof ", @@ -13952,7 +13974,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 405 + "lineNumber": 407 }, "signature": [ "({ display: string; val: string; enabled(agg: ", @@ -13974,7 +13996,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 406 + "lineNumber": 408 }, "signature": [ "typeof ", @@ -13995,7 +14017,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 407 + "lineNumber": 409 }, "signature": [ "typeof ", @@ -14016,7 +14038,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 408 + "lineNumber": 410 }, "signature": [ "typeof ", @@ -14037,7 +14059,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 409 + "lineNumber": 411 }, "signature": [ "typeof ", @@ -14058,7 +14080,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 410 + "lineNumber": 412 }, "signature": [ "(agg: ", @@ -14080,7 +14102,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 411 + "lineNumber": 413 }, "signature": [ "(agg: ", @@ -14102,7 +14124,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 412 + "lineNumber": 414 }, "signature": [ "(...types: string[]) => (agg: ", @@ -14124,7 +14146,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 413 + "lineNumber": 415 }, "signature": [ "typeof ", @@ -14145,7 +14167,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 414 + "lineNumber": 416 }, "signature": [ "typeof ", @@ -14166,7 +14188,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 415 + "lineNumber": 417 } }, { @@ -14177,7 +14199,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 416 + "lineNumber": 418 }, "signature": [ "typeof ", @@ -14198,7 +14220,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 417 + "lineNumber": 419 }, "signature": [ "typeof ", @@ -14219,7 +14241,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 418 + "lineNumber": 420 }, "signature": [ "typeof ", @@ -14240,7 +14262,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 419 + "lineNumber": 421 } }, { @@ -14251,7 +14273,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 420 + "lineNumber": 422 }, "signature": [ "string[]" @@ -14265,7 +14287,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 421 + "lineNumber": 423 }, "signature": [ "typeof ", @@ -14286,7 +14308,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 422 + "lineNumber": 424 }, "signature": [ "({ bound: number; interval: moment.Duration; boundLabel: string; intervalLabel: string; } | { bound: moment.Duration; interval: moment.Duration; boundLabel: string; intervalLabel: string; })[]" @@ -14300,7 +14322,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 423 + "lineNumber": 425 }, "signature": [ "(column: ", @@ -14319,7 +14341,7 @@ "label": "aggs", "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 402 + "lineNumber": 404 } }, { @@ -14330,7 +14352,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 425 + "lineNumber": 427 }, "signature": [ "typeof ", @@ -14351,7 +14373,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 426 + "lineNumber": 428 }, "signature": [ "typeof ", @@ -14372,7 +14394,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 427 + "lineNumber": 429 }, "signature": [ "typeof ", @@ -14393,7 +14415,7 @@ "description": [], "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 428 + "lineNumber": 430 }, "signature": [ "typeof ", @@ -14411,7 +14433,7 @@ "label": "search", "source": { "path": "src/plugins/data/public/index.ts", - "lineNumber": 401 + "lineNumber": 403 }, "initialIsOpen": false }, @@ -15154,7 +15176,7 @@ "description": [], "source": { "path": "src/plugins/data/server/plugin.ts", - "lineNumber": 104 + "lineNumber": 107 } } ], @@ -15162,7 +15184,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/server/plugin.ts", - "lineNumber": 104 + "lineNumber": 107 } }, { @@ -15178,7 +15200,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/server/plugin.ts", - "lineNumber": 118 + "lineNumber": 121 } } ], @@ -17056,7 +17078,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 464 + "lineNumber": 465 } } ], @@ -17077,7 +17099,7 @@ "label": "get", "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 464 + "lineNumber": 465 }, "tags": [], "returnComment": [] @@ -17125,7 +17147,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } }, { @@ -17138,7 +17160,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } } ], @@ -17148,7 +17170,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } }, { @@ -17194,7 +17216,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -17209,7 +17231,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -17224,7 +17246,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } } ], @@ -17232,7 +17254,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -17278,7 +17300,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } }, { @@ -17293,7 +17315,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } } ], @@ -17301,7 +17323,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } }, { @@ -17339,7 +17361,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 547 + "lineNumber": 548 } }, { @@ -17352,7 +17374,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 548 + "lineNumber": 549 } }, { @@ -17365,7 +17387,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 549 + "lineNumber": 550 } } ], @@ -17373,7 +17395,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 546 + "lineNumber": 547 } }, { @@ -17399,7 +17421,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 632 + "lineNumber": 633 } } ], @@ -17407,7 +17429,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 632 + "lineNumber": 633 } } ], @@ -18117,7 +18139,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 464 + "lineNumber": 465 } } ], @@ -18138,7 +18160,7 @@ "label": "get", "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 464 + "lineNumber": 465 }, "tags": [], "returnComment": [] @@ -18186,7 +18208,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } }, { @@ -18199,7 +18221,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } } ], @@ -18209,7 +18231,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } }, { @@ -18255,7 +18277,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -18270,7 +18292,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -18285,7 +18307,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } } ], @@ -18293,7 +18315,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -18339,7 +18361,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } }, { @@ -18354,7 +18376,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } } ], @@ -18362,7 +18384,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } }, { @@ -18400,7 +18422,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 547 + "lineNumber": 548 } }, { @@ -18413,7 +18435,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 548 + "lineNumber": 549 } }, { @@ -18426,7 +18448,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 549 + "lineNumber": 550 } } ], @@ -18434,7 +18456,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 546 + "lineNumber": 547 } }, { @@ -18460,7 +18482,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 632 + "lineNumber": 633 } } ], @@ -18468,7 +18490,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 632 + "lineNumber": 633 } } ], @@ -20477,7 +20499,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 23 + "lineNumber": 25 }, "signature": [ "ExpressionFunctionDefinition<\"kibana_context\", ", @@ -20700,7 +20722,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 27 + "lineNumber": 29 }, "signature": [ "{ type: \"kibana_context\"; } & ExecutionContextSearch" @@ -27594,4 +27616,4 @@ } ] } -} +} \ No newline at end of file diff --git a/api_docs/data_enhanced.json b/api_docs/data_enhanced.json index 0505e87e796f42..5bd7a970f9b73a 100644 --- a/api_docs/data_enhanced.json +++ b/api_docs/data_enhanced.json @@ -531,8 +531,8 @@ "\nID of the async search request" ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 72 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 73 } }, { @@ -544,8 +544,8 @@ "\nSearch strategy used to submit the search request" ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 76 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 77 } }, { @@ -557,8 +557,8 @@ "\nstatus" ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 80 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 81 } }, { @@ -570,8 +570,8 @@ "\nAn optional error. Set if status is set to error." ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 84 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 85 }, "signature": [ "string | undefined" @@ -579,8 +579,8 @@ } ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 68 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 69 }, "initialIsOpen": false }, @@ -598,8 +598,8 @@ "label": "sessionId", "description": [], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 12 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 13 } }, { @@ -611,8 +611,8 @@ "\nUser-facing session name to be displayed in session management" ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 16 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 17 }, "signature": [ "string | undefined" @@ -627,8 +627,8 @@ "\nApp that created the session. e.g 'discover'" ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 20 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 21 }, "signature": [ "string | undefined" @@ -643,8 +643,8 @@ "\nCreation time of the session" ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 24 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 25 } }, { @@ -656,8 +656,8 @@ "\nLast touch time of the session" ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 28 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 29 } }, { @@ -669,8 +669,8 @@ "\nExpiration time of the session. Expiration itself is managed by Elasticsearch." ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 32 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 33 } }, { @@ -682,14 +682,14 @@ "\nstatus" ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 36 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 37 }, "signature": [ { - "pluginId": "dataEnhanced", + "pluginId": "data", "scope": "common", - "docId": "kibDataEnhancedPluginApi", + "docId": "kibDataSearchPluginApi", "section": "def-common.SearchSessionStatus", "text": "SearchSessionStatus" } @@ -704,8 +704,8 @@ "\nurlGeneratorId" ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 40 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 41 }, "signature": [ "string | undefined" @@ -720,8 +720,8 @@ "\nThe application state that was used to create the session.\nShould be used, for example, to re-load an expired search session." ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 45 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 46 }, "signature": [ "Record | undefined" @@ -736,8 +736,8 @@ "\nApplication state that should be used to restore the session.\nFor example, relative dates are conveted to absolute ones." ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 50 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 51 }, "signature": [ "Record | undefined" @@ -752,15 +752,15 @@ "\nMapping of search request hashes to their corresponsing info (async search id, etc.)" ], "source": { - "path": "x-pack/plugins/data_enhanced/common/search/session/types.ts", - "lineNumber": 54 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 55 }, "signature": [ "Record, { expressions }: ", + ">, { logger, expressions }: ", { "pluginId": "data", "scope": "server", @@ -413,12 +413,12 @@ "description": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 47 + "lineNumber": 49 } }, { "type": "Object", - "label": "{ expressions }", + "label": "{ logger, expressions }", "isRequired": true, "signature": [ { @@ -432,7 +432,7 @@ "description": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 48 + "lineNumber": 50 } } ], @@ -440,7 +440,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 46 + "lineNumber": 48 } }, { @@ -507,7 +507,7 @@ "description": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 58 + "lineNumber": 76 } }, { @@ -526,7 +526,7 @@ "description": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 58 + "lineNumber": 76 } } ], @@ -534,13 +534,13 @@ "returnComment": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 58 + "lineNumber": 76 } } ], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 45 + "lineNumber": 47 }, "initialIsOpen": false } @@ -3455,7 +3455,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 464 + "lineNumber": 465 } } ], @@ -3476,7 +3476,7 @@ "label": "get", "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 464 + "lineNumber": 465 }, "tags": [], "returnComment": [] @@ -3524,7 +3524,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } }, { @@ -3537,7 +3537,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } } ], @@ -3547,7 +3547,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 483 + "lineNumber": 484 } }, { @@ -3593,7 +3593,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -3608,7 +3608,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -3623,7 +3623,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } } ], @@ -3631,7 +3631,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 508 + "lineNumber": 509 } }, { @@ -3677,7 +3677,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } }, { @@ -3692,7 +3692,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } } ], @@ -3700,7 +3700,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 521 + "lineNumber": 522 } }, { @@ -3738,7 +3738,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 547 + "lineNumber": 548 } }, { @@ -3751,7 +3751,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 548 + "lineNumber": 549 } }, { @@ -3764,7 +3764,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 549 + "lineNumber": 550 } } ], @@ -3772,7 +3772,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 546 + "lineNumber": 547 } }, { @@ -3798,7 +3798,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 632 + "lineNumber": 633 } } ], @@ -3806,7 +3806,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 632 + "lineNumber": 633 } } ], @@ -6710,7 +6710,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 638 + "lineNumber": 639 }, "signature": [ "{ get: (id: string) => Promise; delete: (indexPatternId: string) => Promise<{}>; create: (spec: IndexPatternSpec, skipFetchFields?: boolean) => Promise; ensureDefaultIndexPattern: EnsureDefaultIndexPattern; getIds: (refresh?: boolean) => Promise; getTitles: (refresh?: boolean) => Promise; find: (search: string, size?: number) => Promise; getIdsWithTitle: (refresh?: boolean) => Promise<{ id: string; title: string; }[]>; clearCache: (id?: string | undefined) => void; getCache: () => Promise[] | null | undefined>; getDefault: () => Promise; setDefault: (id: string, force?: boolean) => Promise; getFieldsForWildcard: (options: GetFieldsOptions) => Promise; getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise; refreshFields: (indexPattern: IndexPattern) => Promise; fieldArrayToMap: (fields: FieldSpec[], fieldAttrs?: FieldAttrs | undefined) => Record; savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec; createAndSave: (spec: IndexPatternSpec, override?: boolean, skipFetchFields?: boolean) => Promise; createSavedObject: (indexPattern: IndexPattern, override?: boolean) => Promise; updateSavedObject: (indexPattern: IndexPattern, saveAttempts?: number, ignoreErrors?: boolean) => Promise; }" diff --git a/api_docs/data_search.json b/api_docs/data_search.json index d0eb07083c2f6b..68cf4a1123bdb3 100644 --- a/api_docs/data_search.json +++ b/api_docs/data_search.json @@ -734,7 +734,7 @@ "section": "def-public.SessionService", "text": "SessionService" }, - ", \"start\" | \"destroy\" | \"state$\" | \"hasAccess\" | \"trackSearch\" | \"getSessionId\" | \"getSession$\" | \"isStored\" | \"isRestore\" | \"restore\" | \"clear\" | \"cancel\" | \"save\" | \"isCurrentSession\" | \"getSearchOptions\" | \"enableStorage\" | \"isSessionStorageReady\" | \"getSearchSessionIndicatorUiConfig\">" + ", \"start\" | \"destroy\" | \"state$\" | \"searchSessionName$\" | \"hasAccess\" | \"trackSearch\" | \"getSessionId\" | \"getSession$\" | \"isStored\" | \"isRestore\" | \"restore\" | \"clear\" | \"cancel\" | \"save\" | \"renameCurrentSession\" | \"isCurrentSession\" | \"getSearchOptions\" | \"enableStorage\" | \"isSessionStorageReady\" | \"getSearchSessionIndicatorUiConfig\">" ] }, { @@ -758,7 +758,7 @@ "section": "def-public.SessionsClient", "text": "SessionsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"extend\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"rename\" | \"extend\">" ] } ], @@ -898,7 +898,7 @@ "section": "def-public.SessionService", "text": "SessionService" }, - ", \"start\" | \"destroy\" | \"state$\" | \"hasAccess\" | \"trackSearch\" | \"getSessionId\" | \"getSession$\" | \"isStored\" | \"isRestore\" | \"restore\" | \"clear\" | \"cancel\" | \"save\" | \"isCurrentSession\" | \"getSearchOptions\" | \"enableStorage\" | \"isSessionStorageReady\" | \"getSearchSessionIndicatorUiConfig\">" + ", \"start\" | \"destroy\" | \"state$\" | \"searchSessionName$\" | \"hasAccess\" | \"trackSearch\" | \"getSessionId\" | \"getSession$\" | \"isStored\" | \"isRestore\" | \"restore\" | \"clear\" | \"cancel\" | \"save\" | \"renameCurrentSession\" | \"isCurrentSession\" | \"getSearchOptions\" | \"enableStorage\" | \"isSessionStorageReady\" | \"getSearchSessionIndicatorUiConfig\">" ] }, { @@ -922,7 +922,7 @@ "section": "def-public.SessionsClient", "text": "SessionsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"extend\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"rename\" | \"extend\">" ] } ], @@ -1083,7 +1083,7 @@ "section": "def-public.SessionService", "text": "SessionService" }, - ", \"start\" | \"destroy\" | \"state$\" | \"hasAccess\" | \"trackSearch\" | \"getSessionId\" | \"getSession$\" | \"isStored\" | \"isRestore\" | \"restore\" | \"clear\" | \"cancel\" | \"save\" | \"isCurrentSession\" | \"getSearchOptions\" | \"enableStorage\" | \"isSessionStorageReady\" | \"getSearchSessionIndicatorUiConfig\">" + ", \"start\" | \"destroy\" | \"state$\" | \"searchSessionName$\" | \"hasAccess\" | \"trackSearch\" | \"getSessionId\" | \"getSession$\" | \"isStored\" | \"isRestore\" | \"restore\" | \"clear\" | \"cancel\" | \"save\" | \"renameCurrentSession\" | \"isCurrentSession\" | \"getSearchOptions\" | \"enableStorage\" | \"isSessionStorageReady\" | \"getSearchSessionIndicatorUiConfig\">" ] } ], @@ -1122,12 +1122,28 @@ ], "source": { "path": "src/plugins/data/public/search/session/session_service.ts", - "lineNumber": 39 + "lineNumber": 45 }, "signature": [ "() => Promise" ] }, + { + "tags": [], + "id": "def-public.SearchSessionInfoProvider.appendSessionStartTimeToName", + "type": "CompoundType", + "label": "appendSessionStartTimeToName", + "description": [ + "\nAppend session start time to a session name,\n`true` by default" + ], + "source": { + "path": "src/plugins/data/public/search/session/session_service.ts", + "lineNumber": 51 + }, + "signature": [ + "boolean | undefined" + ] + }, { "tags": [], "id": "def-public.SearchSessionInfoProvider.getUrlGeneratorData", @@ -1136,7 +1152,7 @@ "description": [], "source": { "path": "src/plugins/data/public/search/session/session_service.ts", - "lineNumber": 40 + "lineNumber": 53 }, "signature": [ "() => Promise<{ urlGeneratorId: ID; initialState: ", @@ -1161,7 +1177,7 @@ ], "source": { "path": "src/plugins/data/public/search/session/session_service.ts", - "lineNumber": 34 + "lineNumber": 40 }, "initialIsOpen": false } @@ -1179,7 +1195,7 @@ ], "source": { "path": "src/plugins/data/public/search/session/search_session_state.ts", - "lineNumber": 19 + "lineNumber": 20 }, "initialIsOpen": false }, @@ -1268,10 +1284,10 @@ "description": [], "source": { "path": "src/plugins/data/public/search/session/sessions_client.ts", - "lineNumber": 13 + "lineNumber": 18 }, "signature": [ - "{ get: (sessionId: string) => Promise>; delete: (sessionId: string) => Promise; create: ({ name, appId, urlGeneratorId, initialState, restoreState, sessionId, }: { name: string; appId: string; initialState: Record; restoreState: Record; urlGeneratorId: string; sessionId: string; }) => Promise>; find: (options: Pick) => Promise>; update: (sessionId: string, attributes: unknown) => Promise>; extend: (sessionId: string, expires: string) => Promise>; }" + "{ get: (sessionId: string) => Promise; delete: (sessionId: string) => Promise; create: ({ name, appId, urlGeneratorId, initialState, restoreState, sessionId, }: { name: string; appId: string; initialState: Record; restoreState: Record; urlGeneratorId: string; sessionId: string; }) => Promise; find: (options: Pick) => Promise>; update: (sessionId: string, attributes: unknown) => Promise>; rename: (sessionId: string, newName: string) => Promise>>; extend: (sessionId: string, expires: string) => Promise>; }" ], "initialIsOpen": false }, @@ -1283,10 +1299,10 @@ "description": [], "source": { "path": "src/plugins/data/public/search/session/session_service.ts", - "lineNumber": 25 + "lineNumber": 31 }, "signature": [ - "{ start: () => string; destroy: () => void; readonly state$: Observable; hasAccess: () => boolean; trackSearch: (searchDescriptor: TrackSearchDescriptor) => () => void; getSessionId: () => string | undefined; getSession$: () => Observable; isStored: () => boolean; isRestore: () => boolean; restore: (sessionId: string) => void; clear: () => void; cancel: () => Promise; save: () => Promise; isCurrentSession: (sessionId?: string | undefined) => boolean; getSearchOptions: (sessionId?: string | undefined) => Required> | null; enableStorage: (searchSessionInfoProvider: SearchSessionInfoProvider, searchSessionIndicatorUiConfig?: SearchSessionIndicatorUiConfig | undefined) => void; isSessionStorageReady: () => boolean; getSearchSessionIndicatorUiConfig: () => SearchSessionIndicatorUiConfig; }" + "{ start: () => string; destroy: () => void; readonly state$: Observable; readonly searchSessionName$: Observable; hasAccess: () => boolean; trackSearch: (searchDescriptor: TrackSearchDescriptor) => () => void; getSessionId: () => string | undefined; getSession$: () => Observable; isStored: () => boolean; isRestore: () => boolean; restore: (sessionId: string) => void; clear: () => void; cancel: () => Promise; save: () => Promise; renameCurrentSession: (newName: string) => Promise; isCurrentSession: (sessionId?: string | undefined) => boolean; getSearchOptions: (sessionId?: string | undefined) => Required> | null; enableStorage: (searchSessionInfoProvider: SearchSessionInfoProvider, searchSessionIndicatorUiConfig?: SearchSessionIndicatorUiConfig | undefined) => void; isSessionStorageReady: () => boolean; getSearchSessionIndicatorUiConfig: () => SearchSessionIndicatorUiConfig; }" ], "initialIsOpen": false }, @@ -1605,7 +1621,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 90 + "lineNumber": 88 }, "signature": [ "(sessionId: string, attributes: Partial) => Promise<", @@ -1627,7 +1643,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 91 + "lineNumber": 89 }, "signature": [ "(sessionId: string) => Promise<", @@ -1649,7 +1665,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 92 + "lineNumber": 90 }, "signature": [ "(options: Pick<", @@ -1679,7 +1695,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 93 + "lineNumber": 91 }, "signature": [ "(sessionId: string, attributes: Partial) => Promise<", @@ -1701,7 +1717,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 94 + "lineNumber": 92 }, "signature": [ "(sessionId: string) => Promise<{}>" @@ -1715,7 +1731,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 95 + "lineNumber": 93 }, "signature": [ "(sessionId: string) => Promise<{}>" @@ -1729,7 +1745,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 96 + "lineNumber": 94 }, "signature": [ "(sessionId: string, expires: Date) => Promise<", @@ -1746,7 +1762,7 @@ ], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 89 + "lineNumber": 87 }, "initialIsOpen": false }, @@ -1827,7 +1843,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 43 + "lineNumber": 41 }, "signature": [ { @@ -1849,7 +1865,7 @@ ], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 48 + "lineNumber": 46 }, "signature": [ " ", @@ -1993,7 +2009,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 112 + "lineNumber": 110 }, "signature": [ "(request: ", @@ -2022,7 +2038,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 113 + "lineNumber": 111 }, "signature": [ "{ asScoped: (request: ", @@ -2047,7 +2063,7 @@ ], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 99 + "lineNumber": 97 }, "initialIsOpen": false }, @@ -2078,7 +2094,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 75 + "lineNumber": 73 }, "signature": [ "(request: SearchStrategyRequest, options: ", @@ -2110,7 +2126,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 80 + "lineNumber": 78 }, "signature": [ "((id: string, options: ", @@ -2140,7 +2156,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 81 + "lineNumber": 79 }, "signature": [ "((id: string, keepAlive: string, options: ", @@ -2165,7 +2181,7 @@ ], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 71 + "lineNumber": 69 }, "initialIsOpen": false }, @@ -2184,7 +2200,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 36 + "lineNumber": 34 }, "signature": [ "Pick<", @@ -2206,7 +2222,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 37 + "lineNumber": 35 }, "signature": [ { @@ -2226,7 +2242,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 38 + "lineNumber": 36 }, "signature": [ { @@ -2246,7 +2262,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 39 + "lineNumber": 37 }, "signature": [ { @@ -2262,7 +2278,7 @@ ], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 35 + "lineNumber": 33 }, "initialIsOpen": false }, @@ -2328,23 +2344,7 @@ } ], "enums": [], - "misc": [ - { - "id": "def-server.SearchRequestHandlerContext", - "type": "Type", - "label": "SearchRequestHandlerContext", - "tags": [], - "description": [], - "source": { - "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 118 - }, - "signature": [ - "IScopedSearchClient" - ], - "initialIsOpen": false - } - ], + "misc": [], "objects": [] }, "common": { @@ -10920,6 +10920,58 @@ }, "initialIsOpen": false }, + { + "id": "def-common.queryToAst", + "type": "Function", + "children": [ + { + "type": "Object", + "label": "query", + "isRequired": true, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + } + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/query_to_ast.ts", + "lineNumber": 14 + } + } + ], + "signature": [ + "(query: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + ") => ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionAstExpressionBuilder", + "text": "ExpressionAstExpressionBuilder" + } + ], + "description": [], + "label": "queryToAst", + "source": { + "path": "src/plugins/data/common/search/expressions/query_to_ast.ts", + "lineNumber": 14 + }, + "tags": [], + "returnComment": [], + "initialIsOpen": false + }, { "id": "def-common.splitStringInterval", "type": "Function", @@ -11300,6 +11352,67 @@ }, "initialIsOpen": false }, + { + "id": "def-common.timerangeToAst", + "type": "Function", + "children": [ + { + "type": "Object", + "label": "timerange", + "isRequired": true, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + } + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange_to_ast.ts", + "lineNumber": 13 + } + } + ], + "signature": [ + "(timerange: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + ") => ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionAstFunctionBuilder", + "text": "ExpressionAstFunctionBuilder" + }, + "<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExpressionFunctionKibanaTimerange", + "text": "ExpressionFunctionKibanaTimerange" + }, + ">" + ], + "description": [], + "label": "timerangeToAst", + "source": { + "path": "src/plugins/data/common/search/expressions/timerange_to_ast.ts", + "lineNumber": 13 + }, + "tags": [], + "returnComment": [], + "initialIsOpen": false + }, { "id": "def-common.toAbsoluteDates", "type": "Function", @@ -16306,74 +16419,49 @@ "initialIsOpen": false }, { - "id": "def-common.SearchSourceDependencies", + "id": "def-common.SearchSessionFindOptions", "type": "Interface", - "label": "SearchSourceDependencies", - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSourceDependencies", - "text": "SearchSourceDependencies" - }, - " extends ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.FetchHandlers", - "text": "FetchHandlers" - } - ], + "label": "SearchSessionFindOptions", "description": [], "tags": [], "children": [ { "tags": [], - "id": "def-common.SearchSourceDependencies.search", - "type": "Function", - "label": "search", + "id": "def-common.SearchSessionFindOptions.page", + "type": "number", + "label": "page", "description": [], "source": { - "path": "src/plugins/data/common/search/search_source/search_source.ts", - "lineNumber": 99 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 89 }, "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ISearchGeneric", - "text": "ISearchGeneric" - } + "number | undefined" ] - } - ], - "source": { - "path": "src/plugins/data/common/search/search_source/search_source.ts", - "lineNumber": 98 - }, - "initialIsOpen": false - }, - { - "id": "def-common.SearchSourceFields", - "type": "Interface", - "label": "SearchSourceFields", - "description": [ - "\nsearch source fields" - ], - "tags": [], - "children": [ + }, { "tags": [], - "id": "def-common.SearchSourceFields.type", + "id": "def-common.SearchSessionFindOptions.perPage", + "type": "number", + "label": "perPage", + "description": [], + "source": { + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 90 + }, + "signature": [ + "number | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SearchSessionFindOptions.sortField", "type": "string", - "label": "type", + "label": "sortField", "description": [], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 62 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 91 }, "signature": [ "string | undefined" @@ -16381,525 +16469,366 @@ }, { "tags": [], - "id": "def-common.SearchSourceFields.query", - "type": "Object", - "label": "query", - "description": [ - "\n{@link Query}" - ], + "id": "def-common.SearchSessionFindOptions.sortOrder", + "type": "string", + "label": "sortOrder", + "description": [], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 66 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 92 }, "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - " | undefined" + "string | undefined" ] }, { "tags": [], - "id": "def-common.SearchSourceFields.filter", - "type": "CompoundType", + "id": "def-common.SearchSessionFindOptions.filter", + "type": "string", "label": "filter", - "description": [ - "\n{@link Filter}" - ], + "description": [], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 70 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 93 }, "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - " | ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[] | (() => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - " | ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[] | undefined) | undefined" + "string | undefined" ] + } + ], + "source": { + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 88 + }, + "initialIsOpen": false + }, + { + "id": "def-common.SearchSessionRequestInfo", + "type": "Interface", + "label": "SearchSessionRequestInfo", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.SearchSessionRequestInfo.id", + "type": "string", + "label": "id", + "description": [ + "\nID of the async search request" + ], + "source": { + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 73 + } }, { "tags": [], - "id": "def-common.SearchSourceFields.sort", - "type": "CompoundType", - "label": "sort", + "id": "def-common.SearchSessionRequestInfo.strategy", + "type": "string", + "label": "strategy", "description": [ - "\n{@link EsQuerySortValue}" + "\nSearch strategy used to submit the search request" ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 74 - }, - "signature": [ - "Record | Record[] | undefined" - ] + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 77 + } }, { "tags": [], - "id": "def-common.SearchSourceFields.highlight", - "type": "Any", - "label": "highlight", - "description": [], + "id": "def-common.SearchSessionRequestInfo.status", + "type": "string", + "label": "status", + "description": [ + "\nstatus" + ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 75 - }, - "signature": [ - "any" - ] + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 81 + } }, { "tags": [], - "id": "def-common.SearchSourceFields.highlightAll", - "type": "CompoundType", - "label": "highlightAll", - "description": [], + "id": "def-common.SearchSessionRequestInfo.error", + "type": "string", + "label": "error", + "description": [ + "\nAn optional error. Set if status is set to error." + ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 76 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 85 }, "signature": [ - "boolean | undefined" + "string | undefined" ] - }, + } + ], + "source": { + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 69 + }, + "initialIsOpen": false + }, + { + "id": "def-common.SearchSessionSavedObjectAttributes", + "type": "Interface", + "label": "SearchSessionSavedObjectAttributes", + "description": [], + "tags": [], + "children": [ { "tags": [], - "id": "def-common.SearchSourceFields.trackTotalHits", - "type": "CompoundType", - "label": "trackTotalHits", + "id": "def-common.SearchSessionSavedObjectAttributes.sessionId", + "type": "string", + "label": "sessionId", "description": [], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 77 - }, - "signature": [ - "number | boolean | undefined" - ] + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 13 + } }, { "tags": [], - "id": "def-common.SearchSourceFields.aggs", - "type": "Any", - "label": "aggs", + "id": "def-common.SearchSessionSavedObjectAttributes.name", + "type": "string", + "label": "name", "description": [ - "\n{@link AggConfigs}" + "\nUser-facing session name to be displayed in session management" ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 81 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 17 }, "signature": [ - "any" + "string | undefined" ] }, { "tags": [], - "id": "def-common.SearchSourceFields.from", - "type": "number", - "label": "from", - "description": [], + "id": "def-common.SearchSessionSavedObjectAttributes.appId", + "type": "string", + "label": "appId", + "description": [ + "\nApp that created the session. e.g 'discover'" + ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 82 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 21 }, "signature": [ - "number | undefined" + "string | undefined" ] }, { "tags": [], - "id": "def-common.SearchSourceFields.size", - "type": "number", - "label": "size", - "description": [], + "id": "def-common.SearchSessionSavedObjectAttributes.created", + "type": "string", + "label": "created", + "description": [ + "\nCreation time of the session" + ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 83 - }, - "signature": [ - "number | undefined" - ] + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 25 + } }, { "tags": [], - "id": "def-common.SearchSourceFields.source", - "type": "CompoundType", - "label": "source", - "description": [], + "id": "def-common.SearchSessionSavedObjectAttributes.touched", + "type": "string", + "label": "touched", + "description": [ + "\nLast touch time of the session" + ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 84 - }, - "signature": [ - "string | boolean | string[] | undefined" - ] + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 29 + } }, { "tags": [], - "id": "def-common.SearchSourceFields.version", - "type": "CompoundType", - "label": "version", - "description": [], + "id": "def-common.SearchSessionSavedObjectAttributes.expires", + "type": "string", + "label": "expires", + "description": [ + "\nExpiration time of the session. Expiration itself is managed by Elasticsearch." + ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 85 - }, - "signature": [ - "boolean | undefined" - ] + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 33 + } }, { "tags": [], - "id": "def-common.SearchSourceFields.fields", - "type": "Array", - "label": "fields", + "id": "def-common.SearchSessionSavedObjectAttributes.status", + "type": "Enum", + "label": "status", "description": [ - "\nRetrieve fields via the search Fields API" + "\nstatus" ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 89 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 37 }, "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchFieldValue", - "text": "SearchFieldValue" - }, - "[] | undefined" + "section": "def-common.SearchSessionStatus", + "text": "SearchSessionStatus" + } ] }, { - "tags": [ - "deprecated" - ], - "id": "def-common.SearchSourceFields.fieldsFromSource", - "type": "CompoundType", - "label": "fieldsFromSource", + "tags": [], + "id": "def-common.SearchSessionSavedObjectAttributes.urlGeneratorId", + "type": "string", + "label": "urlGeneratorId", "description": [ - "\nRetreive fields directly from _source (legacy behavior)\n" + "\nurlGeneratorId" ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 95 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 41 }, "signature": [ - "string | boolean | string[] | undefined" + "string | undefined" ] }, { "tags": [], - "id": "def-common.SearchSourceFields.index", + "id": "def-common.SearchSessionSavedObjectAttributes.initialState", "type": "Object", - "label": "index", + "label": "initialState", "description": [ - "\n{@link IndexPatternService}" + "\nThe application state that was used to create the session.\nShould be used, for example, to re-load an expired search session." ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 99 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 46 }, "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataIndexPatternsPluginApi", - "section": "def-common.IndexPattern", - "text": "IndexPattern" - }, - " | undefined" + "Record | undefined" ] }, { "tags": [], - "id": "def-common.SearchSourceFields.searchAfter", + "id": "def-common.SearchSessionSavedObjectAttributes.restoreState", "type": "Object", - "label": "searchAfter", - "description": [], - "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 100 - }, - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.EsQuerySearchAfter", - "text": "EsQuerySearchAfter" - }, - " | undefined" - ] - }, - { - "tags": [], - "id": "def-common.SearchSourceFields.timeout", - "type": "string", - "label": "timeout", - "description": [], - "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 101 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-common.SearchSourceFields.terminate_after", - "type": "number", - "label": "terminate_after", - "description": [], + "label": "restoreState", + "description": [ + "\nApplication state that should be used to restore the session.\nFor example, relative dates are conveted to absolute ones." + ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 102 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 51 }, "signature": [ - "number | undefined" + "Record | undefined" ] }, { "tags": [], - "id": "def-common.SearchSourceFields.parent", + "id": "def-common.SearchSessionSavedObjectAttributes.idMapping", "type": "Object", - "label": "parent", - "description": [], + "label": "idMapping", + "description": [ + "\nMapping of search request hashes to their corresponsing info (async search id, etc.)" + ], "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 104 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 55 }, "signature": [ + "Record" ] - } - ], - "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 107 - }, - "initialIsOpen": false - }, - { - "id": "def-common.SearchStrategyProvider", - "type": "Interface", - "label": "SearchStrategyProvider", - "description": [], - "tags": [], - "children": [ + }, { "tags": [], - "id": "def-common.SearchStrategyProvider.id", - "type": "string", - "label": "id", - "description": [], + "id": "def-common.SearchSessionSavedObjectAttributes.persisted", + "type": "boolean", + "label": "persisted", + "description": [ + "\nThis value is true if the session was actively stored by the user. If it is false, the session may be purged by the system." + ], "source": { - "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 49 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 60 } }, { "tags": [], - "id": "def-common.SearchStrategyProvider.search", - "type": "Function", - "label": "search", - "description": [], + "id": "def-common.SearchSessionSavedObjectAttributes.realmType", + "type": "string", + "label": "realmType", + "description": [ + "\nThe realm type/name & username uniquely identifies the user who created this search session" + ], "source": { - "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 50 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 64 }, "signature": [ - "(params: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchStrategySearchParams", - "text": "SearchStrategySearchParams" - }, - ") => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchStrategyResponse", - "text": "SearchStrategyResponse" - }, - "" + "string | undefined" ] - } - ], - "source": { - "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 48 - }, - "initialIsOpen": false - }, - { - "id": "def-common.SearchStrategyResponse", - "type": "Interface", - "label": "SearchStrategyResponse", - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchStrategyResponse", - "text": "SearchStrategyResponse" }, - "" - ], - "description": [], - "tags": [], - "children": [ { "tags": [], - "id": "def-common.SearchStrategyResponse.searching", - "type": "Object", - "label": "searching", + "id": "def-common.SearchSessionSavedObjectAttributes.realmName", + "type": "string", + "label": "realmName", "description": [], "source": { - "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 54 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 65 }, "signature": [ - "Promise<", - "SearchResponse", - "[]>" + "string | undefined" ] }, { "tags": [], - "id": "def-common.SearchStrategyResponse.abort", - "type": "Function", - "label": "abort", + "id": "def-common.SearchSessionSavedObjectAttributes.username", + "type": "string", + "label": "username", "description": [], "source": { - "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 55 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 66 }, "signature": [ - "() => void" + "string | undefined" ] } ], "source": { - "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 53 + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 12 }, "initialIsOpen": false }, { - "id": "def-common.SearchStrategySearchParams", + "id": "def-common.SearchSourceDependencies", "type": "Interface", - "label": "SearchStrategySearchParams", + "label": "SearchSourceDependencies", "signature": [ { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchStrategySearchParams", - "text": "SearchStrategySearchParams" + "section": "def-common.SearchSourceDependencies", + "text": "SearchSourceDependencies" }, " extends ", { @@ -16915,234 +16844,281 @@ "children": [ { "tags": [], - "id": "def-common.SearchStrategySearchParams.searchRequests", - "type": "Array", - "label": "searchRequests", + "id": "def-common.SearchSourceDependencies.search", + "type": "Function", + "label": "search", "description": [], "source": { - "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 44 + "path": "src/plugins/data/common/search/search_source/search_source.ts", + "lineNumber": 99 }, "signature": [ - "Record[]" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ISearchGeneric", + "text": "ISearchGeneric" + } ] } ], "source": { - "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 43 + "path": "src/plugins/data/common/search/search_source/search_source.ts", + "lineNumber": 98 }, "initialIsOpen": false }, { - "id": "def-common.ShardFailure", + "id": "def-common.SearchSourceFields", "type": "Interface", - "label": "ShardFailure", - "description": [], + "label": "SearchSourceFields", + "description": [ + "\nsearch source fields" + ], "tags": [], "children": [ { "tags": [], - "id": "def-common.ShardFailure.index", + "id": "def-common.SearchSourceFields.type", "type": "string", - "label": "index", + "label": "type", "description": [], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 142 - } - }, - { - "tags": [], - "id": "def-common.ShardFailure.node", - "type": "string", - "label": "node", - "description": [], - "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 143 - } + "lineNumber": 62 + }, + "signature": [ + "string | undefined" + ] }, { "tags": [], - "id": "def-common.ShardFailure.reason", + "id": "def-common.SearchSourceFields.query", "type": "Object", - "label": "reason", - "description": [], + "label": "query", + "description": [ + "\n{@link Query}" + ], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 144 + "lineNumber": 66 }, "signature": [ - "{ caused_by: { reason: string; type: string; }; reason: string; lang?: string | undefined; script?: string | undefined; script_stack?: string[] | undefined; type: string; }" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | undefined" ] }, { "tags": [], - "id": "def-common.ShardFailure.shard", - "type": "number", - "label": "shard", - "description": [], + "id": "def-common.SearchSourceFields.filter", + "type": "CompoundType", + "label": "filter", + "description": [ + "\n{@link Filter}" + ], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 155 - } - } - ], - "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 141 - }, - "initialIsOpen": false - }, - { - "id": "def-common.SortDirectionNumeric", - "type": "Interface", - "label": "SortDirectionNumeric", - "description": [], - "tags": [], - "children": [ + "lineNumber": 70 + }, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + " | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[] | (() => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + " | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[] | undefined) | undefined" + ] + }, { "tags": [], - "id": "def-common.SortDirectionNumeric.order", - "type": "Enum", - "label": "order", - "description": [], + "id": "def-common.SearchSourceFields.sort", + "type": "CompoundType", + "label": "sort", + "description": [ + "\n{@link EsQuerySortValue}" + ], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 45 + "lineNumber": 74 }, "signature": [ + "Record | Record[] | undefined" ] }, { "tags": [], - "id": "def-common.SortDirectionNumeric.numeric_type", - "type": "CompoundType", - "label": "numeric_type", + "id": "def-common.SearchSourceFields.highlight", + "type": "Any", + "label": "highlight", "description": [], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 46 + "lineNumber": 75 }, "signature": [ - "\"date\" | \"long\" | \"double\" | \"date_nanos\" | undefined" + "any" ] - } - ], - "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 44 - }, - "initialIsOpen": false - }, - { - "id": "def-common.SortOptions", - "type": "Interface", - "label": "SortOptions", - "description": [], - "tags": [], - "children": [ + }, { "tags": [], - "id": "def-common.SortOptions.mode", + "id": "def-common.SearchSourceFields.highlightAll", "type": "CompoundType", - "label": "mode", + "label": "highlightAll", "description": [], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 112 + "lineNumber": 76 }, "signature": [ - "\"max\" | \"min\" | \"sum\" | \"avg\" | \"median\" | undefined" + "boolean | undefined" ] }, { "tags": [], - "id": "def-common.SortOptions.type", + "id": "def-common.SearchSourceFields.trackTotalHits", "type": "CompoundType", - "label": "type", + "label": "trackTotalHits", "description": [], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 113 + "lineNumber": 77 }, "signature": [ - "\"date\" | \"long\" | \"double\" | \"date_nanos\" | undefined" + "number | boolean | undefined" ] }, { "tags": [], - "id": "def-common.SortOptions.nested", - "type": "Uncategorized", - "label": "nested", - "description": [], + "id": "def-common.SearchSourceFields.aggs", + "type": "Any", + "label": "aggs", + "description": [ + "\n{@link AggConfigs}" + ], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 114 + "lineNumber": 81 }, "signature": [ - "object | undefined" + "any" ] }, { "tags": [], - "id": "def-common.SortOptions.unmapped_type", - "type": "string", - "label": "unmapped_type", + "id": "def-common.SearchSourceFields.from", + "type": "number", + "label": "from", "description": [], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 115 + "lineNumber": 82 }, "signature": [ - "string | undefined" + "number | undefined" ] }, { "tags": [], - "id": "def-common.SortOptions.distance_type", - "type": "CompoundType", - "label": "distance_type", + "id": "def-common.SearchSourceFields.size", + "type": "number", + "label": "size", "description": [], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 116 + "lineNumber": 83 }, "signature": [ - "\"arc\" | \"plane\" | undefined" + "number | undefined" ] }, { "tags": [], - "id": "def-common.SortOptions.unit", - "type": "string", - "label": "unit", + "id": "def-common.SearchSourceFields.source", + "type": "CompoundType", + "label": "source", "description": [], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 117 + "lineNumber": 84 }, "signature": [ - "string | undefined" + "string | boolean | string[] | undefined" ] }, { "tags": [], - "id": "def-common.SortOptions.ignore_unmapped", + "id": "def-common.SearchSourceFields.version", "type": "CompoundType", - "label": "ignore_unmapped", + "label": "version", "description": [], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 118 + "lineNumber": 85 }, "signature": [ "boolean | undefined" @@ -17150,22 +17126,559 @@ }, { "tags": [], - "id": "def-common.SortOptions._script", - "type": "Uncategorized", - "label": "_script", - "description": [], + "id": "def-common.SearchSourceFields.fields", + "type": "Array", + "label": "fields", + "description": [ + "\nRetrieve fields via the search Fields API" + ], "source": { "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 119 + "lineNumber": 89 }, "signature": [ - "object | undefined" + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchFieldValue", + "text": "SearchFieldValue" + }, + "[] | undefined" ] - } - ], - "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 111 + }, + { + "tags": [ + "deprecated" + ], + "id": "def-common.SearchSourceFields.fieldsFromSource", + "type": "CompoundType", + "label": "fieldsFromSource", + "description": [ + "\nRetreive fields directly from _source (legacy behavior)\n" + ], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 95 + }, + "signature": [ + "string | boolean | string[] | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SearchSourceFields.index", + "type": "Object", + "label": "index", + "description": [ + "\n{@link IndexPatternService}" + ], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 99 + }, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataIndexPatternsPluginApi", + "section": "def-common.IndexPattern", + "text": "IndexPattern" + }, + " | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SearchSourceFields.searchAfter", + "type": "Object", + "label": "searchAfter", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 100 + }, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.EsQuerySearchAfter", + "text": "EsQuerySearchAfter" + }, + " | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SearchSourceFields.timeout", + "type": "string", + "label": "timeout", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 101 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SearchSourceFields.terminate_after", + "type": "number", + "label": "terminate_after", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 102 + }, + "signature": [ + "number | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SearchSourceFields.parent", + "type": "Object", + "label": "parent", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 104 + }, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchSourceFields", + "text": "SearchSourceFields" + }, + " | undefined" + ] + } + ], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 61 + }, + "initialIsOpen": false + }, + { + "id": "def-common.SearchSourceOptions", + "type": "Interface", + "label": "SearchSourceOptions", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.SearchSourceOptions.callParentStartHandlers", + "type": "CompoundType", + "label": "callParentStartHandlers", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 108 + }, + "signature": [ + "boolean | undefined" + ] + } + ], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 107 + }, + "initialIsOpen": false + }, + { + "id": "def-common.SearchStrategyProvider", + "type": "Interface", + "label": "SearchStrategyProvider", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.SearchStrategyProvider.id", + "type": "string", + "label": "id", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/legacy/types.ts", + "lineNumber": 49 + } + }, + { + "tags": [], + "id": "def-common.SearchStrategyProvider.search", + "type": "Function", + "label": "search", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/legacy/types.ts", + "lineNumber": 50 + }, + "signature": [ + "(params: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchStrategySearchParams", + "text": "SearchStrategySearchParams" + }, + ") => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchStrategyResponse", + "text": "SearchStrategyResponse" + }, + "" + ] + } + ], + "source": { + "path": "src/plugins/data/common/search/search_source/legacy/types.ts", + "lineNumber": 48 + }, + "initialIsOpen": false + }, + { + "id": "def-common.SearchStrategyResponse", + "type": "Interface", + "label": "SearchStrategyResponse", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchStrategyResponse", + "text": "SearchStrategyResponse" + }, + "" + ], + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.SearchStrategyResponse.searching", + "type": "Object", + "label": "searching", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/legacy/types.ts", + "lineNumber": 54 + }, + "signature": [ + "Promise<", + "SearchResponse", + "[]>" + ] + }, + { + "tags": [], + "id": "def-common.SearchStrategyResponse.abort", + "type": "Function", + "label": "abort", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/legacy/types.ts", + "lineNumber": 55 + }, + "signature": [ + "() => void" + ] + } + ], + "source": { + "path": "src/plugins/data/common/search/search_source/legacy/types.ts", + "lineNumber": 53 + }, + "initialIsOpen": false + }, + { + "id": "def-common.SearchStrategySearchParams", + "type": "Interface", + "label": "SearchStrategySearchParams", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchStrategySearchParams", + "text": "SearchStrategySearchParams" + }, + " extends ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.FetchHandlers", + "text": "FetchHandlers" + } + ], + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.SearchStrategySearchParams.searchRequests", + "type": "Array", + "label": "searchRequests", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/legacy/types.ts", + "lineNumber": 44 + }, + "signature": [ + "Record[]" + ] + } + ], + "source": { + "path": "src/plugins/data/common/search/search_source/legacy/types.ts", + "lineNumber": 43 + }, + "initialIsOpen": false + }, + { + "id": "def-common.ShardFailure", + "type": "Interface", + "label": "ShardFailure", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.ShardFailure.index", + "type": "string", + "label": "index", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 142 + } + }, + { + "tags": [], + "id": "def-common.ShardFailure.node", + "type": "string", + "label": "node", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 143 + } + }, + { + "tags": [], + "id": "def-common.ShardFailure.reason", + "type": "Object", + "label": "reason", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 144 + }, + "signature": [ + "{ caused_by: { reason: string; type: string; }; reason: string; lang?: string | undefined; script?: string | undefined; script_stack?: string[] | undefined; type: string; }" + ] + }, + { + "tags": [], + "id": "def-common.ShardFailure.shard", + "type": "number", + "label": "shard", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 155 + } + } + ], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 141 + }, + "initialIsOpen": false + }, + { + "id": "def-common.SortDirectionNumeric", + "type": "Interface", + "label": "SortDirectionNumeric", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.SortDirectionNumeric.order", + "type": "Enum", + "label": "order", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 45 + }, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SortDirection", + "text": "SortDirection" + } + ] + }, + { + "tags": [], + "id": "def-common.SortDirectionNumeric.numeric_type", + "type": "CompoundType", + "label": "numeric_type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 46 + }, + "signature": [ + "\"date\" | \"long\" | \"double\" | \"date_nanos\" | undefined" + ] + } + ], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 44 + }, + "initialIsOpen": false + }, + { + "id": "def-common.SortOptions", + "type": "Interface", + "label": "SortOptions", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.SortOptions.mode", + "type": "CompoundType", + "label": "mode", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 112 + }, + "signature": [ + "\"max\" | \"min\" | \"sum\" | \"avg\" | \"median\" | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SortOptions.type", + "type": "CompoundType", + "label": "type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 113 + }, + "signature": [ + "\"date\" | \"long\" | \"double\" | \"date_nanos\" | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SortOptions.nested", + "type": "Uncategorized", + "label": "nested", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 114 + }, + "signature": [ + "object | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SortOptions.unmapped_type", + "type": "string", + "label": "unmapped_type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 115 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SortOptions.distance_type", + "type": "CompoundType", + "label": "distance_type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 116 + }, + "signature": [ + "\"arc\" | \"plane\" | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SortOptions.unit", + "type": "string", + "label": "unit", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 117 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SortOptions.ignore_unmapped", + "type": "CompoundType", + "label": "ignore_unmapped", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 118 + }, + "signature": [ + "boolean | undefined" + ] + }, + { + "tags": [], + "id": "def-common.SortOptions._script", + "type": "Uncategorized", + "label": "_script", + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 119 + }, + "signature": [ + "object | undefined" + ] + } + ], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 111 }, "initialIsOpen": false } @@ -17207,6 +17720,18 @@ }, "initialIsOpen": false }, + { + "id": "def-common.SearchSessionStatus", + "type": "Enum", + "label": "SearchSessionStatus", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/session/status.ts", + "lineNumber": 9 + }, + "initialIsOpen": false + }, { "id": "def-common.SortDirection", "type": "Enum", @@ -17981,7 +18506,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 23 + "lineNumber": 25 }, "signature": [ "ExpressionFunctionDefinition<\"kibana_context\", ", @@ -18004,6 +18529,135 @@ ], "initialIsOpen": false }, + { + "id": "def-common.ExpressionFunctionKibanaTimerange", + "type": "Type", + "label": "ExpressionFunctionKibanaTimerange", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 15 + }, + "signature": [ + "ExpressionFunctionDefinition<\"timerange\", null, TimeRange, ExpressionValueBoxed<\"timerange\", TimeRange>, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableState", + ">>" + ], + "initialIsOpen": false + }, + { + "id": "def-common.ExpressionFunctionKql", + "type": "Type", + "label": "ExpressionFunctionKql", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 17 + }, + "signature": [ + "ExpressionFunctionDefinition<\"kql\", null, Arguments, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "<\"kibana_query\", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + ">, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableState" + ], + "initialIsOpen": false + }, + { + "id": "def-common.ExpressionFunctionLucene", + "type": "Type", + "label": "ExpressionFunctionLucene", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 17 + }, + "signature": [ + "ExpressionFunctionDefinition<\"lucene\", null, Arguments, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "<\"kibana_query\", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + ">, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableState" + ], + "initialIsOpen": false + }, { "id": "def-common.ExpressionValueSearchContext", "type": "Type", @@ -18383,7 +19037,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 26 + "lineNumber": 28 }, "signature": [ "\"kibana_context\"" @@ -18391,181 +19045,521 @@ "initialIsOpen": false }, { - "id": "def-common.KibanaContext", - "type": "Type", - "label": "KibanaContext", + "id": "def-common.KibanaContext", + "type": "Type", + "label": "KibanaContext", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 29 + }, + "signature": [ + "{ type: \"kibana_context\"; } & ExecutionContextSearch" + ], + "initialIsOpen": false + }, + { + "id": "def-common.KibanaQueryOutput", + "type": "Type", + "label": "KibanaQueryOutput", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 25 + }, + "signature": [ + "{ type: \"kibana_query\"; } & Query" + ], + "initialIsOpen": false + }, + { + "id": "def-common.KibanaTimerangeOutput", + "type": "Type", + "label": "KibanaTimerangeOutput", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 13 + }, + "signature": [ + "{ type: \"timerange\"; } & TimeRange" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.parentPipelineType", + "type": "string", + "label": "parentPipelineType", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", + "lineNumber": 26 + }, + "initialIsOpen": false + }, + { + "id": "def-common.ParsedInterval", + "type": "Type", + "label": "ParsedInterval", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_es_interval.ts", + "lineNumber": 18 + }, + "signature": [ + "{ value: number; unit: Unit; type: \"calendar\" | \"fixed\"; }" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.SEARCH_SESSION_TYPE", + "type": "string", + "label": "SEARCH_SESSION_TYPE", + "description": [], + "source": { + "path": "src/plugins/data/common/search/session/types.ts", + "lineNumber": 11 + }, + "signature": [ + "\"search-session\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.SEARCH_SESSIONS_TABLE_ID", + "type": "string", + "label": "SEARCH_SESSIONS_TABLE_ID", + "description": [], + "source": { + "path": "src/plugins/data/common/search/session/index.ts", + "lineNumber": 12 + }, + "signature": [ + "\"searchSessionsMgmtUiTable\"" + ], + "initialIsOpen": false + }, + { + "id": "def-common.SearchFieldValue", + "type": "Type", + "label": "SearchFieldValue", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/search_source/types.ts", + "lineNumber": 56 + }, + "signature": [ + "string | SearchField" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.siblingPipelineType", + "type": "string", + "label": "siblingPipelineType", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts", + "lineNumber": 33 + }, + "initialIsOpen": false + }, + { "tags": [], + "id": "def-common.termsAggFilter", + "type": "Array", + "label": "termsAggFilter", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 27 + "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", + "lineNumber": 32 }, "signature": [ - "{ type: \"kibana_context\"; } & ExecutionContextSearch" + "string[]" ], "initialIsOpen": false - }, + } + ], + "objects": [ { + "id": "def-common.AggGroupLabels", + "type": "Object", "tags": [], - "id": "def-common.parentPipelineType", - "type": "string", - "label": "parentPipelineType", + "children": [ + { + "tags": [], + "id": "def-common.AggGroupLabels.[AggGroupNames.Buckets]", + "type": "string", + "label": "[AggGroupNames.Buckets]", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/agg_groups.ts", + "lineNumber": 21 + } + }, + { + "tags": [], + "id": "def-common.AggGroupLabels.[AggGroupNames.Metrics]", + "type": "string", + "label": "[AggGroupNames.Metrics]", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/agg_groups.ts", + "lineNumber": 24 + } + }, + { + "tags": [], + "id": "def-common.AggGroupLabels.[AggGroupNames.None]", + "type": "string", + "label": "[AggGroupNames.None]", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/agg_groups.ts", + "lineNumber": 27 + } + } + ], "description": [], + "label": "AggGroupLabels", "source": { - "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", - "lineNumber": 26 + "path": "src/plugins/data/common/search/aggs/agg_groups.ts", + "lineNumber": 20 }, "initialIsOpen": false }, { - "id": "def-common.ParsedInterval", - "type": "Type", - "label": "ParsedInterval", "tags": [], + "id": "def-common.AggGroupNames", + "type": "Object", + "label": "AggGroupNames", "description": [], "source": { - "path": "src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_es_interval.ts", - "lineNumber": 18 + "path": "src/plugins/data/common/search/aggs/agg_groups.ts", + "lineNumber": 12 }, "signature": [ - "{ value: number; unit: Unit; type: \"calendar\" | \"fixed\"; }" + "Readonly<{ Buckets: \"buckets\"; Metrics: \"metrics\"; None: \"none\"; }>" ], "initialIsOpen": false }, { - "id": "def-common.SearchFieldValue", - "type": "Type", - "label": "SearchFieldValue", + "id": "def-common.kibana", + "type": "Object", "tags": [], - "description": [], - "source": { - "path": "src/plugins/data/common/search/search_source/types.ts", - "lineNumber": 56 - }, - "signature": [ - "string | SearchField" + "children": [ + { + "tags": [], + "id": "def-common.kibana.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 27 + }, + "signature": [ + "\"kibana\"" + ] + }, + { + "tags": [], + "id": "def-common.kibana.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 28 + }, + "signature": [ + "\"kibana_context\"" + ] + }, + { + "tags": [], + "id": "def-common.kibana.inputTypes", + "type": "Array", + "label": "inputTypes", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 30 + }, + "signature": [ + "(\"kibana_context\" | \"null\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibana.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 32 + } + }, + { + "id": "def-common.kibana.args", + "type": "Object", + "tags": [], + "children": [], + "description": [], + "label": "args", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 36 + } + }, + { + "id": "def-common.kibana.fn", + "type": "Function", + "label": "fn", + "signature": [ + "(input: Input, _: object, { getSearchContext }: ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExecutionContextSearch", + "text": "ExecutionContextSearch" + }, + ">) => ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "<\"kibana_context\", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExecutionContextSearch", + "text": "ExecutionContextSearch" + } + ], + "description": [], + "children": [ + { + "type": "CompoundType", + "label": "input", + "isRequired": false, + "signature": [ + "Input" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 38 + } + }, + { + "type": "Uncategorized", + "label": "_", + "isRequired": true, + "signature": [ + "object" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 38 + } + }, + { + "type": "Object", + "label": "{ getSearchContext }", + "isRequired": true, + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExecutionContextSearch", + "text": "ExecutionContextSearch" + }, + ">" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 38 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 38 + } + } ], - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-common.siblingPipelineType", - "type": "string", - "label": "siblingPipelineType", "description": [], + "label": "kibana", "source": { - "path": "src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts", - "lineNumber": 33 + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 26 }, "initialIsOpen": false }, { - "tags": [], - "id": "def-common.termsAggFilter", - "type": "Array", - "label": "termsAggFilter", - "description": [], - "source": { - "path": "src/plugins/data/common/search/aggs/buckets/terms.ts", - "lineNumber": 32 - }, - "signature": [ - "string[]" - ], - "initialIsOpen": false - } - ], - "objects": [ - { - "id": "def-common.AggGroupLabels", + "id": "def-common.kibanaContext", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.AggGroupLabels.[AggGroupNames.Buckets]", + "id": "def-common.kibanaContext.name", "type": "string", - "label": "[AggGroupNames.Buckets]", + "label": "name", "description": [], "source": { - "path": "src/plugins/data/common/search/aggs/agg_groups.ts", - "lineNumber": 21 + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 32 } }, { + "id": "def-common.kibanaContext.from", + "type": "Object", "tags": [], - "id": "def-common.AggGroupLabels.[AggGroupNames.Metrics]", - "type": "string", - "label": "[AggGroupNames.Metrics]", + "children": [ + { + "id": "def-common.kibanaContext.from.null", + "type": "Function", + "children": [], + "signature": [ + "() => { type: string; }" + ], + "description": [], + "label": "null", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 34 + }, + "tags": [], + "returnComment": [] + } + ], "description": [], + "label": "from", "source": { - "path": "src/plugins/data/common/search/aggs/agg_groups.ts", - "lineNumber": 24 + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 33 } }, { + "id": "def-common.kibanaContext.to", + "type": "Object", "tags": [], - "id": "def-common.AggGroupLabels.[AggGroupNames.None]", - "type": "string", - "label": "[AggGroupNames.None]", + "children": [ + { + "id": "def-common.kibanaContext.to.null", + "type": "Function", + "children": [], + "signature": [ + "() => { type: string; }" + ], + "description": [], + "label": "null", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 41 + }, + "tags": [], + "returnComment": [] + } + ], "description": [], + "label": "to", "source": { - "path": "src/plugins/data/common/search/aggs/agg_groups.ts", - "lineNumber": 27 + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 40 } } ], "description": [], - "label": "AggGroupLabels", - "source": { - "path": "src/plugins/data/common/search/aggs/agg_groups.ts", - "lineNumber": 20 - }, - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-common.AggGroupNames", - "type": "Object", - "label": "AggGroupNames", - "description": [], + "label": "kibanaContext", "source": { - "path": "src/plugins/data/common/search/aggs/agg_groups.ts", - "lineNumber": 12 + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 31 }, - "signature": [ - "Readonly<{ Buckets: \"buckets\"; Metrics: \"metrics\"; None: \"none\"; }>" - ], "initialIsOpen": false }, { - "id": "def-common.kibana", + "id": "def-common.kibanaContextFunction", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibana.name", + "id": "def-common.kibanaContextFunction.name", "type": "string", "label": "name", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 27 + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 43 }, "signature": [ - "\"kibana\"" + "\"kibana_context\"" ] }, { "tags": [], - "id": "def-common.kibana.type", + "id": "def-common.kibanaContextFunction.type", "type": "string", "label": "type", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 28 + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 44 }, "signature": [ "\"kibana_context\"" @@ -18573,13 +19567,13 @@ }, { "tags": [], - "id": "def-common.kibana.inputTypes", + "id": "def-common.kibanaContextFunction.inputTypes", "type": "Array", "label": "inputTypes", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 30 + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 45 }, "signature": [ "(\"kibana_context\" | \"null\")[]" @@ -18587,33 +19581,253 @@ }, { "tags": [], - "id": "def-common.kibana.help", + "id": "def-common.kibanaContextFunction.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 32 + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 46 } }, { - "id": "def-common.kibana.args", + "id": "def-common.kibanaContextFunction.args", "type": "Object", "tags": [], - "children": [], + "children": [ + { + "id": "def-common.kibanaContextFunction.args.q", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.q.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 51 + }, + "signature": [ + "(\"null\" | \"kibana_query\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.q.aliases", + "type": "Array", + "label": "aliases", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 52 + }, + "signature": [ + "string[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.q.default", + "type": "Uncategorized", + "label": "default", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 53 + }, + "signature": [ + "null" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.q.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 54 + } + } + ], + "description": [], + "label": "q", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 50 + } + }, + { + "id": "def-common.kibanaContextFunction.args.filters", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.filters.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 59 + }, + "signature": [ + "(\"string\" | \"null\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.filters.default", + "type": "string", + "label": "default", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 60 + } + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.filters.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 61 + } + } + ], + "description": [], + "label": "filters", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 58 + } + }, + { + "id": "def-common.kibanaContextFunction.args.timeRange", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.timeRange.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 66 + }, + "signature": [ + "(\"null\" | \"timerange\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.timeRange.default", + "type": "Uncategorized", + "label": "default", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 67 + }, + "signature": [ + "null" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.timeRange.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 68 + } + } + ], + "description": [], + "label": "timeRange", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 65 + } + }, + { + "id": "def-common.kibanaContextFunction.args.savedSearchId", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.savedSearchId.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 73 + }, + "signature": [ + "(\"string\" | \"null\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.savedSearchId.default", + "type": "Uncategorized", + "label": "default", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 74 + }, + "signature": [ + "null" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.savedSearchId.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 75 + } + } + ], + "description": [], + "label": "savedSearchId", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 72 + } + } + ], "description": [], "label": "args", "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 36 + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 49 } }, { - "id": "def-common.kibana.fn", + "id": "def-common.kibanaContextFunction.fn", "type": "Function", "label": "fn", "signature": [ - "(input: Input, _: object, { getSearchContext }: ", + "(input: Input, args: Arguments, { getSavedObject }: ", { "pluginId": "expressions", "scope": "common", @@ -18637,21 +19851,21 @@ "section": "def-common.ExecutionContextSearch", "text": "ExecutionContextSearch" }, - ">) => ", + ">) => Promise<{ type: \"kibana_context\"; query: ", { - "pluginId": "expressions", + "pluginId": "data", "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExpressionValueBoxed", - "text": "ExpressionValueBoxed" + "docId": "kibDataQueryPluginApi", + "section": "def-common.Query", + "text": "Query" }, - "<\"kibana_context\", ", + "[]; filters: ", { "pluginId": "data", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExecutionContextSearch", - "text": "ExecutionContextSearch" + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" } ], "description": [], @@ -18665,508 +19879,715 @@ ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 38 - } - }, - { - "type": "Uncategorized", - "label": "_", - "isRequired": true, - "signature": [ - "object" - ], - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 38 + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 81 } }, { - "type": "Object", - "label": "{ getSearchContext }", - "isRequired": true, - "signature": [ - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExecutionContext", - "text": "ExecutionContext" - }, - "<", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - }, - ", ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExecutionContextSearch", - "text": "ExecutionContextSearch" - }, - ">" - ], - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 38 - } - } - ], - "tags": [], - "returnComment": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 38 - } - } - ], - "description": [], - "label": "kibana", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 26 - }, - "initialIsOpen": false - }, - { - "id": "def-common.kibanaContext", - "type": "Object", - "tags": [], - "children": [ - { - "tags": [], - "id": "def-common.kibanaContext.name", - "type": "string", - "label": "name", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 30 - } - }, - { - "id": "def-common.kibanaContext.from", - "type": "Object", - "tags": [], - "children": [ - { - "id": "def-common.kibanaContext.from.null", - "type": "Function", - "children": [], - "signature": [ - "() => { type: string; }" - ], - "description": [], - "label": "null", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 32 - }, - "tags": [], - "returnComment": [] - } - ], - "description": [], - "label": "from", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 31 - } - }, - { - "id": "def-common.kibanaContext.to", - "type": "Object", - "tags": [], - "children": [ + "type": "Object", + "label": "args", + "isRequired": true, + "signature": [ + "Arguments" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 81 + } + }, { - "id": "def-common.kibanaContext.to.null", - "type": "Function", - "children": [], + "type": "Object", + "label": "{ getSavedObject }", + "isRequired": true, "signature": [ - "() => { type: string; }" + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExecutionContextSearch", + "text": "ExecutionContextSearch" + }, + ">" ], "description": [], - "label": "null", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 39 - }, - "tags": [], - "returnComment": [] + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 81 + } } ], - "description": [], - "label": "to", + "tags": [], + "returnComment": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 38 + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 81 } } ], "description": [], - "label": "kibanaContext", + "label": "kibanaContextFunction", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 29 + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 42 }, "initialIsOpen": false }, { - "id": "def-common.kibanaContextFunction", + "id": "def-common.kibanaTimerangeFunction", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaContextFunction.name", + "id": "def-common.kibanaTimerangeFunction.name", "type": "string", "label": "name", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 41 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 23 }, "signature": [ - "\"kibana_context\"" + "\"timerange\"" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.type", + "id": "def-common.kibanaTimerangeFunction.type", "type": "string", "label": "type", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 42 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 24 }, "signature": [ - "\"kibana_context\"" + "\"timerange\"" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.inputTypes", + "id": "def-common.kibanaTimerangeFunction.inputTypes", "type": "Array", "label": "inputTypes", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 43 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 25 }, "signature": [ - "(\"kibana_context\" | \"null\")[]" + "\"null\"[]" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.help", + "id": "def-common.kibanaTimerangeFunction.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 44 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 26 } }, { - "id": "def-common.kibanaContextFunction.args", + "id": "def-common.kibanaTimerangeFunction.args", "type": "Object", "tags": [], "children": [ { - "id": "def-common.kibanaContextFunction.args.q", + "id": "def-common.kibanaTimerangeFunction.args.from", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaContextFunction.args.q.types", + "id": "def-common.kibanaTimerangeFunction.args.from.types", "type": "Array", "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 49 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 31 }, "signature": [ - "(\"string\" | \"null\")[]" + "\"string\"[]" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.q.aliases", + "id": "def-common.kibanaTimerangeFunction.args.from.required", + "type": "boolean", + "label": "required", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 32 + }, + "signature": [ + "true" + ] + }, + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.args.from.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 33 + } + } + ], + "description": [], + "label": "from", + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 30 + } + }, + { + "id": "def-common.kibanaTimerangeFunction.args.to", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.args.to.types", "type": "Array", - "label": "aliases", + "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 50 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 38 }, "signature": [ - "string[]" + "\"string\"[]" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.q.default", - "type": "Uncategorized", - "label": "default", + "id": "def-common.kibanaTimerangeFunction.args.to.required", + "type": "boolean", + "label": "required", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 51 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 39 }, "signature": [ - "null" + "true" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.q.help", + "id": "def-common.kibanaTimerangeFunction.args.to.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 52 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 40 } } ], "description": [], - "label": "q", + "label": "to", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 48 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 37 } }, { - "id": "def-common.kibanaContextFunction.args.filters", + "id": "def-common.kibanaTimerangeFunction.args.mode", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaContextFunction.args.filters.types", + "id": "def-common.kibanaTimerangeFunction.args.mode.types", "type": "Array", "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 57 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 45 }, "signature": [ - "(\"string\" | \"null\")[]" + "\"string\"[]" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.filters.default", - "type": "string", - "label": "default", + "id": "def-common.kibanaTimerangeFunction.args.mode.options", + "type": "Array", + "label": "options", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 58 - } + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 46 + }, + "signature": [ + "(\"absolute\" | \"relative\")[]" + ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.filters.help", + "id": "def-common.kibanaTimerangeFunction.args.mode.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 59 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 47 } } ], "description": [], - "label": "filters", + "label": "mode", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 56 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 44 + } + } + ], + "description": [], + "label": "args", + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 29 + } + }, + { + "id": "def-common.kibanaTimerangeFunction.fn", + "type": "Function", + "label": "fn", + "signature": [ + "(input: null, args: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + ") => { type: \"timerange\"; from: string; to: string; mode: \"absolute\" | \"relative\" | undefined; }" + ], + "description": [], + "children": [ + { + "type": "Uncategorized", + "label": "input", + "isRequired": true, + "signature": [ + "null" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 53 } }, { - "id": "def-common.kibanaContextFunction.args.timeRange", + "type": "Object", + "label": "args", + "isRequired": true, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + } + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 53 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 53 + } + } + ], + "description": [], + "label": "kibanaTimerangeFunction", + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 22 + }, + "initialIsOpen": false + }, + { + "id": "def-common.kqlFunction", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kqlFunction.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 25 + }, + "signature": [ + "\"kql\"" + ] + }, + { + "tags": [], + "id": "def-common.kqlFunction.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 26 + }, + "signature": [ + "\"kibana_query\"" + ] + }, + { + "tags": [], + "id": "def-common.kqlFunction.inputTypes", + "type": "Array", + "label": "inputTypes", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 27 + }, + "signature": [ + "\"null\"[]" + ] + }, + { + "tags": [], + "id": "def-common.kqlFunction.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 28 + } + }, + { + "id": "def-common.kqlFunction.args", + "type": "Object", + "tags": [], + "children": [ + { + "id": "def-common.kqlFunction.args.q", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaContextFunction.args.timeRange.types", + "id": "def-common.kqlFunction.args.q.types", "type": "Array", "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 64 + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 33 }, "signature": [ - "(\"string\" | \"null\")[]" + "\"string\"[]" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.timeRange.default", - "type": "Uncategorized", - "label": "default", + "id": "def-common.kqlFunction.args.q.required", + "type": "boolean", + "label": "required", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 65 + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 34 }, "signature": [ - "null" + "true" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.timeRange.help", + "id": "def-common.kqlFunction.args.q.aliases", + "type": "Array", + "label": "aliases", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 35 + }, + "signature": [ + "string[]" + ] + }, + { + "tags": [], + "id": "def-common.kqlFunction.args.q.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 66 + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 36 } } ], "description": [], - "label": "timeRange", + "label": "q", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 63 + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 32 + } + } + ], + "description": [], + "label": "args", + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 31 + } + }, + { + "id": "def-common.kqlFunction.fn", + "type": "Function", + "label": "fn", + "signature": [ + "(input: null, args: Arguments) => { type: \"kibana_query\"; language: string; query: string; }" + ], + "description": [], + "children": [ + { + "type": "Uncategorized", + "label": "input", + "isRequired": true, + "signature": [ + "null" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 42 } }, { - "id": "def-common.kibanaContextFunction.args.savedSearchId", + "type": "Object", + "label": "args", + "isRequired": true, + "signature": [ + "Arguments" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 42 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 42 + } + } + ], + "description": [], + "label": "kqlFunction", + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 24 + }, + "initialIsOpen": false + }, + { + "id": "def-common.luceneFunction", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.luceneFunction.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 25 + }, + "signature": [ + "\"lucene\"" + ] + }, + { + "tags": [], + "id": "def-common.luceneFunction.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 26 + }, + "signature": [ + "\"kibana_query\"" + ] + }, + { + "tags": [], + "id": "def-common.luceneFunction.inputTypes", + "type": "Array", + "label": "inputTypes", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 27 + }, + "signature": [ + "\"null\"[]" + ] + }, + { + "tags": [], + "id": "def-common.luceneFunction.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 28 + } + }, + { + "id": "def-common.luceneFunction.args", + "type": "Object", + "tags": [], + "children": [ + { + "id": "def-common.luceneFunction.args.q", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaContextFunction.args.savedSearchId.types", + "id": "def-common.luceneFunction.args.q.types", "type": "Array", "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 71 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 33 }, "signature": [ - "(\"string\" | \"null\")[]" + "\"string\"[]" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.savedSearchId.default", - "type": "Uncategorized", - "label": "default", + "id": "def-common.luceneFunction.args.q.required", + "type": "boolean", + "label": "required", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 72 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 34 }, "signature": [ - "null" + "true" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.savedSearchId.help", + "id": "def-common.luceneFunction.args.q.aliases", + "type": "Array", + "label": "aliases", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 35 + }, + "signature": [ + "string[]" + ] + }, + { + "tags": [], + "id": "def-common.luceneFunction.args.q.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 73 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 36 } } ], "description": [], - "label": "savedSearchId", + "label": "q", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 70 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 32 } } ], "description": [], "label": "args", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 47 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 31 } }, { - "id": "def-common.kibanaContextFunction.fn", + "id": "def-common.luceneFunction.fn", "type": "Function", "label": "fn", "signature": [ - "(input: Input, args: Arguments, { getSavedObject }: ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExecutionContext", - "text": "ExecutionContext" - }, - "<", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - }, - ", ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExecutionContextSearch", - "text": "ExecutionContextSearch" - }, - ">) => Promise<{ type: \"kibana_context\"; query: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - "[]; filters: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.Filter", - "text": "Filter" - } + "(input: null, args: Arguments) => { type: \"kibana_query\"; language: string; query: any; }" ], "description": [], "children": [ { - "type": "CompoundType", + "type": "Uncategorized", "label": "input", - "isRequired": false, + "isRequired": true, "signature": [ - "Input" + "null" ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 79 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 42 } }, { @@ -19178,60 +20599,24 @@ ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 79 - } - }, - { - "type": "Object", - "label": "{ getSavedObject }", - "isRequired": true, - "signature": [ - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExecutionContext", - "text": "ExecutionContext" - }, - "<", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - }, - ", ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExecutionContextSearch", - "text": "ExecutionContextSearch" - }, - ">" - ], - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 79 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 42 } } ], "tags": [], "returnComment": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 79 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 42 } } ], "description": [], - "label": "kibanaContextFunction", + "label": "luceneFunction", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 40 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 24 }, "initialIsOpen": false }, @@ -19470,4 +20855,4 @@ } ] } -} +} \ No newline at end of file diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 370bd2ffd101e8..70cbd231252480 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -36,9 +36,6 @@ import dataSearchObj from './data_search.json'; ### Interfaces -### Consts, variables and types - - ## Common ### Objects diff --git a/api_docs/data_ui.json b/api_docs/data_ui.json index cf0528bc7fafff..52d51002373949 100644 --- a/api_docs/data_ui.json +++ b/api_docs/data_ui.json @@ -290,6 +290,20 @@ ") => void) | undefined" ] }, + { + "tags": [], + "id": "def-public.QueryStringInputProps.submitOnBlur", + "type": "CompoundType", + "label": "submitOnBlur", + "description": [], + "source": { + "path": "src/plugins/data/public/ui/query_string_input/query_string_input.tsx", + "lineNumber": 56 + }, + "signature": [ + "boolean | undefined" + ] + }, { "tags": [], "id": "def-public.QueryStringInputProps.dataTestSubj", @@ -298,7 +312,7 @@ "description": [], "source": { "path": "src/plugins/data/public/ui/query_string_input/query_string_input.tsx", - "lineNumber": 56 + "lineNumber": 57 }, "signature": [ "string | undefined" @@ -312,7 +326,7 @@ "description": [], "source": { "path": "src/plugins/data/public/ui/query_string_input/query_string_input.tsx", - "lineNumber": 57 + "lineNumber": 58 }, "signature": [ "\"s\" | \"l\" | undefined" @@ -326,7 +340,7 @@ "description": [], "source": { "path": "src/plugins/data/public/ui/query_string_input/query_string_input.tsx", - "lineNumber": 58 + "lineNumber": 59 }, "signature": [ "string | undefined" @@ -340,7 +354,7 @@ "description": [], "source": { "path": "src/plugins/data/public/ui/query_string_input/query_string_input.tsx", - "lineNumber": 59 + "lineNumber": 60 }, "signature": [ "boolean | undefined" @@ -354,7 +368,7 @@ "description": [], "source": { "path": "src/plugins/data/public/ui/query_string_input/query_string_input.tsx", - "lineNumber": 60 + "lineNumber": 61 }, "signature": [ "boolean | undefined" @@ -368,7 +382,7 @@ "description": [], "source": { "path": "src/plugins/data/public/ui/query_string_input/query_string_input.tsx", - "lineNumber": 61 + "lineNumber": 62 }, "signature": [ "string | React.ComponentClass<{}, any> | React.FunctionComponent<{}> | undefined" @@ -382,7 +396,7 @@ "description": [], "source": { "path": "src/plugins/data/public/ui/query_string_input/query_string_input.tsx", - "lineNumber": 67 + "lineNumber": 68 }, "signature": [ "\"text\" | \"lucene\" | undefined" @@ -396,7 +410,7 @@ "description": [], "source": { "path": "src/plugins/data/public/ui/query_string_input/query_string_input.tsx", - "lineNumber": 68 + "lineNumber": 69 }, "signature": [ "string | undefined" @@ -423,7 +437,7 @@ "lineNumber": 17 }, "signature": [ - "Pick, \"children\" | \"onClick\" | \"color\" | \"id\" | \"title\" | \"placeholder\" | \"isClearable\" | \"async\" | \"compressed\" | \"fullWidth\" | \"singleSelection\" | \"prepend\" | \"append\" | \"sortMatchesBy\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"data-test-subj\" | \"customOptionText\" | \"onCreateOption\" | \"renderOption\" | \"inputRef\" | \"isDisabled\" | \"isInvalid\" | \"noSuggestions\" | \"rowHeight\" | \"delimiter\">, \"children\" | \"onClick\" | \"color\" | \"id\" | \"title\" | \"isClearable\" | \"async\" | \"compressed\" | \"fullWidth\" | \"singleSelection\" | \"prepend\" | \"append\" | \"sortMatchesBy\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"data-test-subj\" | \"customOptionText\" | \"onCreateOption\" | \"renderOption\" | \"inputRef\" | \"isDisabled\" | \"isInvalid\" | \"noSuggestions\" | \"rowHeight\" | \"delimiter\"> & globalThis.Required, \"children\" | \"onClick\" | \"color\" | \"id\" | \"title\" | \"placeholder\" | \"isClearable\" | \"async\" | \"compressed\" | \"fullWidth\" | \"singleSelection\" | \"prepend\" | \"append\" | \"sortMatchesBy\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"data-test-subj\" | \"customOptionText\" | \"onCreateOption\" | \"renderOption\" | \"inputRef\" | \"isDisabled\" | \"isInvalid\" | \"noSuggestions\" | \"rowHeight\" | \"delimiter\">, \"placeholder\">> & { onChange: (indexPatternId?: string | undefined) => void; indexPatternId: string; fieldTypes?: string[] | undefined; onNoIndexPatterns?: (() => void) | undefined; maxIndexPatterns?: number | undefined; }" + "Pick, \"children\" | \"onClick\" | \"color\" | \"onKeyDown\" | \"title\" | \"id\" | \"placeholder\" | \"isClearable\" | \"async\" | \"compressed\" | \"fullWidth\" | \"singleSelection\" | \"prepend\" | \"append\" | \"sortMatchesBy\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"data-test-subj\" | \"customOptionText\" | \"onCreateOption\" | \"renderOption\" | \"inputRef\" | \"isDisabled\" | \"isInvalid\" | \"noSuggestions\" | \"rowHeight\" | \"delimiter\">, \"children\" | \"onClick\" | \"color\" | \"onKeyDown\" | \"title\" | \"id\" | \"isClearable\" | \"async\" | \"compressed\" | \"fullWidth\" | \"singleSelection\" | \"prepend\" | \"append\" | \"sortMatchesBy\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"data-test-subj\" | \"customOptionText\" | \"onCreateOption\" | \"renderOption\" | \"inputRef\" | \"isDisabled\" | \"isInvalid\" | \"noSuggestions\" | \"rowHeight\" | \"delimiter\"> & globalThis.Required, \"children\" | \"onClick\" | \"color\" | \"onKeyDown\" | \"title\" | \"id\" | \"placeholder\" | \"isClearable\" | \"async\" | \"compressed\" | \"fullWidth\" | \"singleSelection\" | \"prepend\" | \"append\" | \"sortMatchesBy\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"data-test-subj\" | \"customOptionText\" | \"onCreateOption\" | \"renderOption\" | \"inputRef\" | \"isDisabled\" | \"isInvalid\" | \"noSuggestions\" | \"rowHeight\" | \"delimiter\">, \"placeholder\">> & { onChange: (indexPatternId?: string | undefined) => void; indexPatternId: string; fieldTypes?: string[] | undefined; onNoIndexPatterns?: (() => void) | undefined; maxIndexPatterns?: number | undefined; }" ], "initialIsOpen": false }, diff --git a/api_docs/discover.json b/api_docs/discover.json index 267669692051f8..a785c254efeab5 100644 --- a/api_docs/discover.json +++ b/api_docs/discover.json @@ -807,8 +807,8 @@ "label": "docViews", "description": [], "source": { - "path": "src/plugins/discover/public/plugin.ts", - "lineNumber": 75 + "path": "src/plugins/discover/public/plugin.tsx", + "lineNumber": 76 }, "signature": [ "{ addDocView(docViewRaw: ", @@ -832,8 +832,8 @@ } ], "source": { - "path": "src/plugins/discover/public/plugin.ts", - "lineNumber": 74 + "path": "src/plugins/discover/public/plugin.tsx", + "lineNumber": 75 }, "lifecycle": "setup", "initialIsOpen": true @@ -852,8 +852,8 @@ "label": "savedSearchLoader", "description": [], "source": { - "path": "src/plugins/discover/public/plugin.ts", - "lineNumber": 86 + "path": "src/plugins/discover/public/plugin.tsx", + "lineNumber": 87 }, "signature": [ { @@ -874,8 +874,8 @@ "\n`share` plugin URL generator for Discover app. Use it to generate links into\nDiscover application, example:\n\n```ts\nconst url = await plugins.discover.urlGenerator.createUrl({\n savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d',\n indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002',\n timeRange: {\n to: 'now',\n from: 'now-15m',\n mode: 'relative',\n },\n});\n```" ], "source": { - "path": "src/plugins/discover/public/plugin.ts", - "lineNumber": 104 + "path": "src/plugins/discover/public/plugin.tsx", + "lineNumber": 105 }, "signature": [ { @@ -890,8 +890,8 @@ } ], "source": { - "path": "src/plugins/discover/public/plugin.ts", - "lineNumber": 85 + "path": "src/plugins/discover/public/plugin.tsx", + "lineNumber": 86 }, "lifecycle": "start", "initialIsOpen": true diff --git a/api_docs/expressions.json b/api_docs/expressions.json index ee496cc7c06a33..f80d9b6c4cdd38 100644 --- a/api_docs/expressions.json +++ b/api_docs/expressions.json @@ -5364,7 +5364,7 @@ "section": "def-common.InferFunctionDefinition", "text": "InferFunctionDefinition" }, - "[\"name\"], initialArgs: { [K in keyof FunctionArgs]: ", + "[\"name\"], initialArgs: { [K in keyof FunctionArgs]: FunctionArgs[K] | ", { "pluginId": "expressions", "scope": "common", @@ -5372,7 +5372,7 @@ "section": "def-common.ExpressionAstExpressionBuilder", "text": "ExpressionAstExpressionBuilder" }, - " | FunctionArgs[K] | ", + " | ", { "pluginId": "expressions", "scope": "common", @@ -5421,7 +5421,7 @@ "label": "initialArgs", "isRequired": true, "signature": [ - "{ [K in keyof FunctionArgs]: ", + "{ [K in keyof FunctionArgs]: FunctionArgs[K] | ", { "pluginId": "expressions", "scope": "common", @@ -5429,7 +5429,7 @@ "section": "def-common.ExpressionAstExpressionBuilder", "text": "ExpressionAstExpressionBuilder" }, - " | FunctionArgs[K] | ", + " | ", { "pluginId": "expressions", "scope": "common", @@ -13485,7 +13485,7 @@ "section": "def-common.InferFunctionDefinition", "text": "InferFunctionDefinition" }, - "[\"name\"], initialArgs: { [K in keyof FunctionArgs]: ", + "[\"name\"], initialArgs: { [K in keyof FunctionArgs]: FunctionArgs[K] | ", { "pluginId": "expressions", "scope": "common", @@ -13493,7 +13493,7 @@ "section": "def-common.ExpressionAstExpressionBuilder", "text": "ExpressionAstExpressionBuilder" }, - " | FunctionArgs[K] | ", + " | ", { "pluginId": "expressions", "scope": "common", @@ -13542,7 +13542,7 @@ "label": "initialArgs", "isRequired": true, "signature": [ - "{ [K in keyof FunctionArgs]: ", + "{ [K in keyof FunctionArgs]: FunctionArgs[K] | ", { "pluginId": "expressions", "scope": "common", @@ -13550,7 +13550,7 @@ "section": "def-common.ExpressionAstExpressionBuilder", "text": "ExpressionAstExpressionBuilder" }, - " | FunctionArgs[K] | ", + " | ", { "pluginId": "expressions", "scope": "common", @@ -21552,7 +21552,7 @@ "section": "def-common.InferFunctionDefinition", "text": "InferFunctionDefinition" }, - "[\"name\"], initialArgs: { [K in keyof FunctionArgs]: ", + "[\"name\"], initialArgs: { [K in keyof FunctionArgs]: FunctionArgs[K] | ", { "pluginId": "expressions", "scope": "common", @@ -21560,7 +21560,7 @@ "section": "def-common.ExpressionAstExpressionBuilder", "text": "ExpressionAstExpressionBuilder" }, - " | FunctionArgs[K] | ", + " | ", { "pluginId": "expressions", "scope": "common", @@ -21609,7 +21609,7 @@ "label": "initialArgs", "isRequired": true, "signature": [ - "{ [K in keyof FunctionArgs]: ", + "{ [K in keyof FunctionArgs]: FunctionArgs[K] | ", { "pluginId": "expressions", "scope": "common", @@ -21617,7 +21617,7 @@ "section": "def-common.ExpressionAstExpressionBuilder", "text": "ExpressionAstExpressionBuilder" }, - " | FunctionArgs[K] | ", + " | ", { "pluginId": "expressions", "scope": "common", @@ -33883,4 +33883,4 @@ } ] } -} +} \ No newline at end of file diff --git a/api_docs/file_upload.json b/api_docs/file_upload.json index 350bd1aa067f3e..6a624f87bfcd29 100644 --- a/api_docs/file_upload.json +++ b/api_docs/file_upload.json @@ -4,6 +4,61 @@ "classes": [], "functions": [], "interfaces": [ + { + "id": "def-public.AnalysisResult", + "type": "Interface", + "label": "AnalysisResult", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.AnalysisResult.results", + "type": "Object", + "label": "results", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 21 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.FindFileStructureResponse", + "text": "FindFileStructureResponse" + } + ] + }, + { + "tags": [], + "id": "def-public.AnalysisResult.overrides", + "type": "CompoundType", + "label": "overrides", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 22 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.FormattedOverrides", + "text": "FormattedOverrides" + }, + " | undefined" + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 20 + }, + "initialIsOpen": false + }, { "id": "def-public.CreateDocsResponse", "type": "Interface", @@ -90,13 +145,13 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 26 + "lineNumber": 93 } } ], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 25 + "lineNumber": 92 }, "initialIsOpen": false }, @@ -212,335 +267,240 @@ "initialIsOpen": false }, { - "id": "def-public.IImporter", + "id": "def-public.FindFileStructureResponse", "type": "Interface", - "label": "IImporter", + "label": "FindFileStructureResponse", "description": [], "tags": [], "children": [ { - "id": "def-public.IImporter.read", - "type": "Function", - "label": "read", + "tags": [], + "id": "def-public.FindFileStructureResponse.charset", + "type": "string", + "label": "charset", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 26 + } + }, + { + "tags": [], + "id": "def-public.FindFileStructureResponse.has_header_row", + "type": "boolean", + "label": "has_header_row", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 27 + } + }, + { + "tags": [], + "id": "def-public.FindFileStructureResponse.has_byte_order_marker", + "type": "boolean", + "label": "has_byte_order_marker", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 28 + } + }, + { + "tags": [], + "id": "def-public.FindFileStructureResponse.format", + "type": "string", + "label": "format", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 29 + } + }, + { + "tags": [], + "id": "def-public.FindFileStructureResponse.field_stats", + "type": "Object", + "label": "field_stats", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 30 + }, "signature": [ - "(data: ArrayBuffer) => { success: boolean; }" - ], + "{ [fieldName: string]: { count: number; cardinality: number; top_hits: { count: number; value: any; }[]; mean_value?: number | undefined; median_value?: number | undefined; max_value?: number | undefined; min_value?: number | undefined; earliest?: string | undefined; latest?: string | undefined; }; }" + ] + }, + { + "tags": [], + "id": "def-public.FindFileStructureResponse.sample_start", + "type": "string", + "label": "sample_start", "description": [], - "children": [ - { - "type": "Object", - "label": "data", - "isRequired": true, - "signature": [ - "ArrayBuffer" - ], - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 44 - } - } - ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 43 + } + }, + { "tags": [], - "returnComment": [], + "id": "def-public.FindFileStructureResponse.num_messages_analyzed", + "type": "number", + "label": "num_messages_analyzed", + "description": [], "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "path": "x-pack/plugins/file_upload/common/types.ts", "lineNumber": 44 } }, { - "id": "def-public.IImporter.initializeImport", - "type": "Function", - "label": "initializeImport", + "tags": [], + "id": "def-public.FindFileStructureResponse.mappings", + "type": "Object", + "label": "mappings", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 45 + }, "signature": [ - "(index: string, settings: ", + "{ properties: { [fieldName: string]: { type: ", { - "pluginId": "fileUpload", + "pluginId": "data", "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.Settings", - "text": "Settings" + "docId": "kibDataPluginApi", + "section": "def-common.ES_FIELD_TYPES", + "text": "ES_FIELD_TYPES" }, - ", mappings: ", + ".STRING | ", { - "pluginId": "fileUpload", + "pluginId": "data", "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.Mappings", - "text": "Mappings" + "docId": "kibDataPluginApi", + "section": "def-common.ES_FIELD_TYPES", + "text": "ES_FIELD_TYPES" }, - ", pipeline: ", + ".TEXT | ", { - "pluginId": "fileUpload", + "pluginId": "data", "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.IngestPipeline", - "text": "IngestPipeline" + "docId": "kibDataPluginApi", + "section": "def-common.ES_FIELD_TYPES", + "text": "ES_FIELD_TYPES" }, - ") => Promise<", + ".KEYWORD | ", { - "pluginId": "fileUpload", + "pluginId": "data", "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.ImportResponse", - "text": "ImportResponse" - }, - ">" - ], - "description": [], - "children": [ - { - "type": "string", - "label": "index", - "isRequired": true, - "signature": [ - "string" - ], - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 46 - } - }, - { - "type": "Object", - "label": "settings", - "isRequired": true, - "signature": [ - { - "pluginId": "fileUpload", - "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.Settings", - "text": "Settings" - } - ], - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 47 - } - }, - { - "type": "Object", - "label": "mappings", - "isRequired": true, - "signature": [ - { - "pluginId": "fileUpload", - "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.Mappings", - "text": "Mappings" - } - ], - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 48 - } + "docId": "kibDataPluginApi", + "section": "def-common.ES_FIELD_TYPES", + "text": "ES_FIELD_TYPES" }, + ".BOOLEAN | ", { - "type": "Object", - "label": "pipeline", - "isRequired": true, - "signature": [ - { - "pluginId": "fileUpload", - "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.IngestPipeline", - "text": "IngestPipeline" - } - ], - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 49 - } + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.ES_FIELD_TYPES", + "text": "ES_FIELD_TYPES" } - ], + ] + }, + { "tags": [], - "returnComment": [], + "id": "def-public.FindFileStructureResponse.quote", + "type": "string", + "label": "quote", + "description": [], "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 45 + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 58 } }, { - "id": "def-public.IImporter.import", - "type": "Function", - "label": "import", - "signature": [ - "(id: string, index: string, pipelineId: string | undefined, setImportProgress: (progress: number) => void) => Promise<", - { - "pluginId": "fileUpload", - "scope": "public", - "docId": "kibFileUploadPluginApi", - "section": "def-public.ImportResults", - "text": "ImportResults" - }, - ">" - ], - "description": [], - "children": [ - { - "type": "string", - "label": "id", - "isRequired": true, - "signature": [ - "string" - ], - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 52 - } - }, - { - "type": "string", - "label": "index", - "isRequired": true, - "signature": [ - "string" - ], - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 53 - } - }, - { - "type": "string", - "label": "pipelineId", - "isRequired": false, - "signature": [ - "string | undefined" - ], - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 54 - } - }, - { - "type": "Function", - "label": "setImportProgress", - "isRequired": true, - "signature": [ - "(progress: number) => void" - ], - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 55 - } - } - ], "tags": [], - "returnComment": [], + "id": "def-public.FindFileStructureResponse.delimiter", + "type": "string", + "label": "delimiter", + "description": [], "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 51 + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 59 } - } - ], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 43 - }, - "initialIsOpen": false - }, - { - "id": "def-public.ImportConfig", - "type": "Interface", - "label": "ImportConfig", - "description": [], - "tags": [], - "children": [ + }, { "tags": [], - "id": "def-public.ImportConfig.settings", - "type": "Object", - "label": "settings", + "id": "def-public.FindFileStructureResponse.need_client_timezone", + "type": "boolean", + "label": "need_client_timezone", "description": [], "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 18 + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 60 + } + }, + { + "tags": [], + "id": "def-public.FindFileStructureResponse.num_lines_analyzed", + "type": "number", + "label": "num_lines_analyzed", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 61 + } + }, + { + "tags": [], + "id": "def-public.FindFileStructureResponse.column_names", + "type": "Array", + "label": "column_names", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 62 }, "signature": [ - { - "pluginId": "fileUpload", - "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.Settings", - "text": "Settings" - } + "string[] | undefined" ] }, { "tags": [], - "id": "def-public.ImportConfig.mappings", - "type": "Object", - "label": "mappings", + "id": "def-public.FindFileStructureResponse.explanation", + "type": "Array", + "label": "explanation", "description": [], "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 19 + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 63 }, "signature": [ - { - "pluginId": "fileUpload", - "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.Mappings", - "text": "Mappings" - } + "string[] | undefined" ] }, { "tags": [], - "id": "def-public.ImportConfig.pipeline", - "type": "Object", - "label": "pipeline", + "id": "def-public.FindFileStructureResponse.grok_pattern", + "type": "string", + "label": "grok_pattern", "description": [], "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 20 + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 64 }, "signature": [ - { - "pluginId": "fileUpload", - "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.IngestPipeline", - "text": "IngestPipeline" - } + "string | undefined" ] - } - ], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 17 - }, - "initialIsOpen": false - }, - { - "id": "def-public.ImportFactoryOptions", - "type": "Interface", - "label": "ImportFactoryOptions", - "description": [], - "tags": [], - "children": [ + }, { "tags": [], - "id": "def-public.ImportFactoryOptions.excludeLinesPattern", + "id": "def-public.FindFileStructureResponse.multiline_start_pattern", "type": "string", - "label": "excludeLinesPattern", + "label": "multiline_start_pattern", "description": [], "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 38 + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 65 }, "signature": [ "string | undefined" @@ -548,13 +508,13 @@ }, { "tags": [], - "id": "def-public.ImportFactoryOptions.multilineStartPattern", + "id": "def-public.FindFileStructureResponse.exclude_lines_pattern", "type": "string", - "label": "multilineStartPattern", + "label": "exclude_lines_pattern", "description": [], "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 39 + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 66 }, "signature": [ "string | undefined" @@ -562,427 +522,1435 @@ }, { "tags": [], - "id": "def-public.ImportFactoryOptions.importConfig", - "type": "Object", - "label": "importConfig", + "id": "def-public.FindFileStructureResponse.java_timestamp_formats", + "type": "Array", + "label": "java_timestamp_formats", "description": [], "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 40 + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 67 }, "signature": [ - { - "pluginId": "fileUpload", - "scope": "public", - "docId": "kibFileUploadPluginApi", - "section": "def-public.ImportConfig", - "text": "ImportConfig" - } + "string[] | undefined" ] - } - ], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 37 - }, - "initialIsOpen": false - }, - { - "id": "def-public.ImportFailure", - "type": "Interface", - "label": "ImportFailure", - "description": [], - "tags": [], - "children": [ + }, { "tags": [], - "id": "def-public.ImportFailure.item", - "type": "number", - "label": "item", + "id": "def-public.FindFileStructureResponse.joda_timestamp_formats", + "type": "Array", + "label": "joda_timestamp_formats", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 20 - } + "lineNumber": 68 + }, + "signature": [ + "string[] | undefined" + ] }, { "tags": [], - "id": "def-public.ImportFailure.reason", + "id": "def-public.FindFileStructureResponse.timestamp_field", "type": "string", - "label": "reason", + "label": "timestamp_field", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 21 - } + "lineNumber": 69 + }, + "signature": [ + "string | undefined" + ] }, { "tags": [], - "id": "def-public.ImportFailure.doc", + "id": "def-public.FindFileStructureResponse.should_trim_fields", "type": "CompoundType", - "label": "doc", + "label": "should_trim_fields", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 22 + "lineNumber": 70 }, "signature": [ - { - "pluginId": "fileUpload", - "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.ImportDoc", - "text": "ImportDoc" - } + "boolean | undefined" ] } ], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 19 + "lineNumber": 25 }, "initialIsOpen": false }, { - "id": "def-public.ImportResponse", + "id": "def-public.IImporter", "type": "Interface", - "label": "ImportResponse", + "label": "IImporter", "description": [], "tags": [], "children": [ + { + "id": "def-public.IImporter.read", + "type": "Function", + "label": "read", + "signature": [ + "(data: ArrayBuffer) => { success: boolean; }" + ], + "description": [], + "children": [ + { + "type": "Object", + "label": "data", + "isRequired": true, + "signature": [ + "ArrayBuffer" + ], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 44 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 44 + } + }, + { + "id": "def-public.IImporter.initializeImport", + "type": "Function", + "label": "initializeImport", + "signature": [ + "(index: string, settings: ", + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.Settings", + "text": "Settings" + }, + ", mappings: ", + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.Mappings", + "text": "Mappings" + }, + ", pipeline: ", + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.IngestPipeline", + "text": "IngestPipeline" + }, + ") => Promise<", + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.ImportResponse", + "text": "ImportResponse" + }, + ">" + ], + "description": [], + "children": [ + { + "type": "string", + "label": "index", + "isRequired": true, + "signature": [ + "string" + ], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 46 + } + }, + { + "type": "Object", + "label": "settings", + "isRequired": true, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.Settings", + "text": "Settings" + } + ], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 47 + } + }, + { + "type": "Object", + "label": "mappings", + "isRequired": true, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.Mappings", + "text": "Mappings" + } + ], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 48 + } + }, + { + "type": "Object", + "label": "pipeline", + "isRequired": true, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.IngestPipeline", + "text": "IngestPipeline" + } + ], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 49 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 45 + } + }, + { + "id": "def-public.IImporter.import", + "type": "Function", + "label": "import", + "signature": [ + "(id: string, index: string, pipelineId: string | undefined, setImportProgress: (progress: number) => void) => Promise<", + { + "pluginId": "fileUpload", + "scope": "public", + "docId": "kibFileUploadPluginApi", + "section": "def-public.ImportResults", + "text": "ImportResults" + }, + ">" + ], + "description": [], + "children": [ + { + "type": "string", + "label": "id", + "isRequired": true, + "signature": [ + "string" + ], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 52 + } + }, + { + "type": "string", + "label": "index", + "isRequired": true, + "signature": [ + "string" + ], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 53 + } + }, + { + "type": "string", + "label": "pipelineId", + "isRequired": false, + "signature": [ + "string | undefined" + ], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 54 + } + }, + { + "type": "Function", + "label": "setImportProgress", + "isRequired": true, + "signature": [ + "(progress: number) => void" + ], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 55 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 51 + } + } + ], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 43 + }, + "initialIsOpen": false + }, + { + "id": "def-public.ImportConfig", + "type": "Interface", + "label": "ImportConfig", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.ImportConfig.settings", + "type": "Object", + "label": "settings", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 18 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.Settings", + "text": "Settings" + } + ] + }, + { + "tags": [], + "id": "def-public.ImportConfig.mappings", + "type": "Object", + "label": "mappings", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 19 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.Mappings", + "text": "Mappings" + } + ] + }, + { + "tags": [], + "id": "def-public.ImportConfig.pipeline", + "type": "Object", + "label": "pipeline", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 20 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.IngestPipeline", + "text": "IngestPipeline" + } + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 17 + }, + "initialIsOpen": false + }, + { + "id": "def-public.ImportFactoryOptions", + "type": "Interface", + "label": "ImportFactoryOptions", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.ImportFactoryOptions.excludeLinesPattern", + "type": "string", + "label": "excludeLinesPattern", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 38 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-public.ImportFactoryOptions.multilineStartPattern", + "type": "string", + "label": "multilineStartPattern", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 39 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-public.ImportFactoryOptions.importConfig", + "type": "Object", + "label": "importConfig", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 40 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "public", + "docId": "kibFileUploadPluginApi", + "section": "def-public.ImportConfig", + "text": "ImportConfig" + } + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 37 + }, + "initialIsOpen": false + }, + { + "id": "def-public.ImportFailure", + "type": "Interface", + "label": "ImportFailure", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.ImportFailure.item", + "type": "number", + "label": "item", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 87 + } + }, + { + "tags": [], + "id": "def-public.ImportFailure.reason", + "type": "string", + "label": "reason", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 88 + } + }, + { + "tags": [], + "id": "def-public.ImportFailure.doc", + "type": "CompoundType", + "label": "doc", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 89 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.ImportDoc", + "text": "ImportDoc" + } + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 86 + }, + "initialIsOpen": false + }, + { + "id": "def-public.ImportResponse", + "type": "Interface", + "label": "ImportResponse", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.ImportResponse.success", + "type": "boolean", + "label": "success", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 76 + } + }, + { + "tags": [], + "id": "def-public.ImportResponse.id", + "type": "string", + "label": "id", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 77 + } + }, + { + "tags": [], + "id": "def-public.ImportResponse.index", + "type": "string", + "label": "index", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 78 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-public.ImportResponse.pipelineId", + "type": "string", + "label": "pipelineId", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 79 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-public.ImportResponse.docCount", + "type": "number", + "label": "docCount", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 80 + } + }, + { + "tags": [], + "id": "def-public.ImportResponse.failures", + "type": "Array", + "label": "failures", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 81 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.ImportFailure", + "text": "ImportFailure" + }, + "[]" + ] + }, + { + "tags": [], + "id": "def-public.ImportResponse.error", + "type": "Any", + "label": "error", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 82 + }, + "signature": [ + "any" + ] + }, + { + "tags": [], + "id": "def-public.ImportResponse.ingestError", + "type": "CompoundType", + "label": "ingestError", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 83 + }, + "signature": [ + "boolean | undefined" + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 75 + }, + "initialIsOpen": false + }, + { + "id": "def-public.ImportResults", + "type": "Interface", + "label": "ImportResults", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.ImportResults.success", + "type": "boolean", + "label": "success", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 24 + } + }, + { + "tags": [], + "id": "def-public.ImportResults.failures", + "type": "Array", + "label": "failures", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 25 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.ImportFailure", + "text": "ImportFailure" + }, + "[] | undefined" + ] + }, + { + "tags": [], + "id": "def-public.ImportResults.docCount", + "type": "number", + "label": "docCount", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 26 + }, + "signature": [ + "number | undefined" + ] + }, + { + "tags": [], + "id": "def-public.ImportResults.error", + "type": "Any", + "label": "error", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 27 + }, + "signature": [ + "any" + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/public/importer/types.ts", + "lineNumber": 23 + }, + "initialIsOpen": false + }, + { + "id": "def-public.IngestPipeline", + "type": "Interface", + "label": "IngestPipeline", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.IngestPipeline.description", + "type": "string", + "label": "description", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 120 + } + }, + { + "tags": [], + "id": "def-public.IngestPipeline.processors", + "type": "Array", + "label": "processors", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 121 + }, + "signature": [ + "any[]" + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 119 + }, + "initialIsOpen": false + }, + { + "id": "def-public.IngestPipelineWrapper", + "type": "Interface", + "label": "IngestPipelineWrapper", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.IngestPipelineWrapper.id", + "type": "string", + "label": "id", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 115 + } + }, + { + "tags": [], + "id": "def-public.IngestPipelineWrapper.pipeline", + "type": "Object", + "label": "pipeline", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 116 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.IngestPipeline", + "text": "IngestPipeline" + } + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 114 + }, + "initialIsOpen": false + }, + { + "id": "def-public.InputOverrides", + "type": "Interface", + "label": "InputOverrides", + "description": [], + "tags": [], + "children": [ + { + "id": "def-public.InputOverrides.Unnamed", + "type": "Any", + "label": "Unnamed", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 11 + }, + "signature": [ + "any" + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 10 + }, + "initialIsOpen": false + }, + { + "id": "def-public.Mappings", + "type": "Interface", + "label": "Mappings", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.Mappings._meta", + "type": "Object", + "label": "_meta", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 106 + }, + "signature": [ + "{ created_by: string; } | undefined" + ] + }, + { + "tags": [], + "id": "def-public.Mappings.properties", + "type": "Object", + "label": "properties", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 109 + }, + "signature": [ + "{ [key: string]: any; }" + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 105 + }, + "initialIsOpen": false + }, + { + "id": "def-public.Settings", + "type": "Interface", + "label": "Settings", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.Settings.pipeline", + "type": "string", + "label": "pipeline", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 99 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-public.Settings.index", + "type": "string", + "label": "index", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 100 + } + }, + { + "tags": [], + "id": "def-public.Settings.body", + "type": "Array", + "label": "body", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 101 + }, + "signature": [ + "any[]" + ] + }, + { + "id": "def-public.Settings.Unnamed", + "type": "Any", + "label": "Unnamed", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 102 + }, + "signature": [ + "any" + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 98 + }, + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "tags": [], + "id": "def-public.ABSOLUTE_MAX_FILE_SIZE_BYTES", + "type": "number", + "label": "ABSOLUTE_MAX_FILE_SIZE_BYTES", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/constants.ts", + "lineNumber": 14 + }, + "signature": [ + "1073741274" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-public.FILE_SIZE_DISPLAY_FORMAT", + "type": "string", + "label": "FILE_SIZE_DISPLAY_FORMAT", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/constants.ts", + "lineNumber": 15 + }, + "signature": [ + "\"0,0.[0] b\"" + ], + "initialIsOpen": false + }, + { + "id": "def-public.FormattedOverrides", + "type": "Type", + "label": "FormattedOverrides", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 14 + }, + "signature": [ + "InputOverrides & { column_names: string[]; has_header_row: boolean; should_trim_fields: boolean; }" + ], + "initialIsOpen": false + }, + { + "id": "def-public.ImportDoc", + "type": "Type", + "label": "ImportDoc", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 96 + }, + "signature": [ + "string | object | ", + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.Doc", + "text": "Doc" + } + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-public.INDEX_META_DATA_CREATED_BY", + "type": "string", + "label": "INDEX_META_DATA_CREATED_BY", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/constants.ts", + "lineNumber": 19 + }, + "signature": [ + "\"ml-file-data-visualizer\"" + ], + "initialIsOpen": false + }, + { + "id": "def-public.InputData", + "type": "Type", + "label": "InputData", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 73 + }, + "signature": [ + "any[]" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-public.MAX_FILE_SIZE", + "type": "string", + "label": "MAX_FILE_SIZE", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/constants.ts", + "lineNumber": 11 + }, + "signature": [ + "\"100MB\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-public.MAX_FILE_SIZE_BYTES", + "type": "number", + "label": "MAX_FILE_SIZE_BYTES", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/constants.ts", + "lineNumber": 12 + }, + "signature": [ + "104857600" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-public.MB", + "type": "number", + "label": "MB", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/constants.ts", + "lineNumber": 10 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-public.UI_SETTING_MAX_FILE_SIZE", + "type": "string", + "label": "UI_SETTING_MAX_FILE_SIZE", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/constants.ts", + "lineNumber": 8 + }, + "signature": [ + "\"fileUpload:maxFileSize\"" + ], + "initialIsOpen": false + } + ], + "objects": [], + "start": { + "id": "def-public.FileUploadPluginStart", + "type": "Type", + "label": "FileUploadPluginStart", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/public/plugin.ts", + "lineNumber": 21 + }, + "signature": [ + "FileUploadStartApi" + ], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ + { + "id": "def-common.AnalysisResult", + "type": "Interface", + "label": "AnalysisResult", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.AnalysisResult.results", + "type": "Object", + "label": "results", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 21 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.FindFileStructureResponse", + "text": "FindFileStructureResponse" + } + ] + }, + { + "tags": [], + "id": "def-common.AnalysisResult.overrides", + "type": "CompoundType", + "label": "overrides", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 22 + }, + "signature": [ + { + "pluginId": "fileUpload", + "scope": "common", + "docId": "kibFileUploadPluginApi", + "section": "def-common.FormattedOverrides", + "text": "FormattedOverrides" + }, + " | undefined" + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 20 + }, + "initialIsOpen": false + }, + { + "id": "def-common.Doc", + "type": "Interface", + "label": "Doc", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.Doc.message", + "type": "string", + "label": "message", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 93 + } + } + ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 92 + }, + "initialIsOpen": false + }, + { + "id": "def-common.FindFileStructureResponse", + "type": "Interface", + "label": "FindFileStructureResponse", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.FindFileStructureResponse.charset", + "type": "string", + "label": "charset", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 26 + } + }, { "tags": [], - "id": "def-public.ImportResponse.success", + "id": "def-common.FindFileStructureResponse.has_header_row", "type": "boolean", - "label": "success", + "label": "has_header_row", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 9 + "lineNumber": 27 } }, { "tags": [], - "id": "def-public.ImportResponse.id", - "type": "string", - "label": "id", + "id": "def-common.FindFileStructureResponse.has_byte_order_marker", + "type": "boolean", + "label": "has_byte_order_marker", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 10 + "lineNumber": 28 } }, { "tags": [], - "id": "def-public.ImportResponse.index", + "id": "def-common.FindFileStructureResponse.format", "type": "string", - "label": "index", + "label": "format", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 11 + "lineNumber": 29 + } + }, + { + "tags": [], + "id": "def-common.FindFileStructureResponse.field_stats", + "type": "Object", + "label": "field_stats", + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 30 }, "signature": [ - "string | undefined" + "{ [fieldName: string]: { count: number; cardinality: number; top_hits: { count: number; value: any; }[]; mean_value?: number | undefined; median_value?: number | undefined; max_value?: number | undefined; min_value?: number | undefined; earliest?: string | undefined; latest?: string | undefined; }; }" ] }, { "tags": [], - "id": "def-public.ImportResponse.pipelineId", + "id": "def-common.FindFileStructureResponse.sample_start", "type": "string", - "label": "pipelineId", + "label": "sample_start", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 12 - }, - "signature": [ - "string | undefined" - ] + "lineNumber": 43 + } }, { "tags": [], - "id": "def-public.ImportResponse.docCount", + "id": "def-common.FindFileStructureResponse.num_messages_analyzed", "type": "number", - "label": "docCount", + "label": "num_messages_analyzed", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 13 + "lineNumber": 44 } }, { "tags": [], - "id": "def-public.ImportResponse.failures", - "type": "Array", - "label": "failures", + "id": "def-common.FindFileStructureResponse.mappings", + "type": "Object", + "label": "mappings", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 14 + "lineNumber": 45 }, "signature": [ + "{ properties: { [fieldName: string]: { type: ", { - "pluginId": "fileUpload", + "pluginId": "data", "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.ImportFailure", - "text": "ImportFailure" + "docId": "kibDataPluginApi", + "section": "def-common.ES_FIELD_TYPES", + "text": "ES_FIELD_TYPES" }, - "[]" + ".STRING | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.ES_FIELD_TYPES", + "text": "ES_FIELD_TYPES" + }, + ".TEXT | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.ES_FIELD_TYPES", + "text": "ES_FIELD_TYPES" + }, + ".KEYWORD | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.ES_FIELD_TYPES", + "text": "ES_FIELD_TYPES" + }, + ".BOOLEAN | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.ES_FIELD_TYPES", + "text": "ES_FIELD_TYPES" + } ] }, { "tags": [], - "id": "def-public.ImportResponse.error", - "type": "Any", - "label": "error", + "id": "def-common.FindFileStructureResponse.quote", + "type": "string", + "label": "quote", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 15 - }, - "signature": [ - "any" - ] + "lineNumber": 58 + } }, { "tags": [], - "id": "def-public.ImportResponse.ingestError", - "type": "CompoundType", - "label": "ingestError", + "id": "def-common.FindFileStructureResponse.delimiter", + "type": "string", + "label": "delimiter", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 16 - }, - "signature": [ - "boolean | undefined" - ] - } - ], - "source": { - "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 8 - }, - "initialIsOpen": false - }, - { - "id": "def-public.ImportResults", - "type": "Interface", - "label": "ImportResults", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-public.ImportResults.success", - "type": "boolean", - "label": "success", - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 24 + "lineNumber": 59 } }, { "tags": [], - "id": "def-public.ImportResults.failures", - "type": "Array", - "label": "failures", + "id": "def-common.FindFileStructureResponse.need_client_timezone", + "type": "boolean", + "label": "need_client_timezone", "description": [], "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 25 - }, - "signature": [ - { - "pluginId": "fileUpload", - "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.ImportFailure", - "text": "ImportFailure" - }, - "[] | undefined" - ] + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 60 + } }, { "tags": [], - "id": "def-public.ImportResults.docCount", + "id": "def-common.FindFileStructureResponse.num_lines_analyzed", "type": "number", - "label": "docCount", - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 26 - }, - "signature": [ - "number | undefined" - ] - }, - { - "tags": [], - "id": "def-public.ImportResults.error", - "type": "Any", - "label": "error", - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 27 - }, - "signature": [ - "any" - ] - } - ], - "source": { - "path": "x-pack/plugins/file_upload/public/importer/types.ts", - "lineNumber": 23 - }, - "initialIsOpen": false - }, - { - "id": "def-public.IngestPipeline", - "type": "Interface", - "label": "IngestPipeline", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-public.IngestPipeline.description", - "type": "string", - "label": "description", + "label": "num_lines_analyzed", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 53 + "lineNumber": 61 } }, { "tags": [], - "id": "def-public.IngestPipeline.processors", + "id": "def-common.FindFileStructureResponse.column_names", "type": "Array", - "label": "processors", + "label": "column_names", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 54 + "lineNumber": 62 }, "signature": [ - "any[]" + "string[] | undefined" ] - } - ], - "source": { - "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 52 - }, - "initialIsOpen": false - }, - { - "id": "def-public.IngestPipelineWrapper", - "type": "Interface", - "label": "IngestPipelineWrapper", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-public.IngestPipelineWrapper.id", - "type": "string", - "label": "id", - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 48 - } }, { "tags": [], - "id": "def-public.IngestPipelineWrapper.pipeline", - "type": "Object", - "label": "pipeline", + "id": "def-common.FindFileStructureResponse.explanation", + "type": "Array", + "label": "explanation", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 49 + "lineNumber": 63 }, "signature": [ - { - "pluginId": "fileUpload", - "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.IngestPipeline", - "text": "IngestPipeline" - } + "string[] | undefined" ] - } - ], - "source": { - "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 47 - }, - "initialIsOpen": false - }, - { - "id": "def-public.Mappings", - "type": "Interface", - "label": "Mappings", - "description": [], - "tags": [], - "children": [ + }, { "tags": [], - "id": "def-public.Mappings._meta", - "type": "Object", - "label": "_meta", + "id": "def-common.FindFileStructureResponse.grok_pattern", + "type": "string", + "label": "grok_pattern", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 39 + "lineNumber": 64 }, "signature": [ - "{ created_by: string; } | undefined" + "string | undefined" ] }, { "tags": [], - "id": "def-public.Mappings.properties", - "type": "Object", - "label": "properties", + "id": "def-common.FindFileStructureResponse.multiline_start_pattern", + "type": "string", + "label": "multiline_start_pattern", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 42 + "lineNumber": 65 }, "signature": [ - "{ [key: string]: any; }" + "string | undefined" ] - } - ], - "source": { - "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 38 - }, - "initialIsOpen": false - }, - { - "id": "def-public.Settings", - "type": "Interface", - "label": "Settings", - "description": [], - "tags": [], - "children": [ + }, { "tags": [], - "id": "def-public.Settings.pipeline", + "id": "def-common.FindFileStructureResponse.exclude_lines_pattern", "type": "string", - "label": "pipeline", + "label": "exclude_lines_pattern", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 32 + "lineNumber": 66 }, "signature": [ "string | undefined" @@ -990,225 +1958,59 @@ }, { "tags": [], - "id": "def-public.Settings.index", - "type": "string", - "label": "index", + "id": "def-common.FindFileStructureResponse.java_timestamp_formats", + "type": "Array", + "label": "java_timestamp_formats", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 33 - } + "lineNumber": 67 + }, + "signature": [ + "string[] | undefined" + ] }, { "tags": [], - "id": "def-public.Settings.body", + "id": "def-common.FindFileStructureResponse.joda_timestamp_formats", "type": "Array", - "label": "body", + "label": "joda_timestamp_formats", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 34 + "lineNumber": 68 }, "signature": [ - "any[]" + "string[] | undefined" ] }, { - "id": "def-public.Settings.Unnamed", - "type": "Any", - "label": "Unnamed", "tags": [], + "id": "def-common.FindFileStructureResponse.timestamp_field", + "type": "string", + "label": "timestamp_field", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 35 + "lineNumber": 69 }, "signature": [ - "any" + "string | undefined" ] - } - ], - "source": { - "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 31 - }, - "initialIsOpen": false - } - ], - "enums": [], - "misc": [ - { - "tags": [], - "id": "def-public.ABSOLUTE_MAX_FILE_SIZE_BYTES", - "type": "number", - "label": "ABSOLUTE_MAX_FILE_SIZE_BYTES", - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/common/constants.ts", - "lineNumber": 14 - }, - "signature": [ - "1073741274" - ], - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-public.FILE_SIZE_DISPLAY_FORMAT", - "type": "string", - "label": "FILE_SIZE_DISPLAY_FORMAT", - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/common/constants.ts", - "lineNumber": 15 - }, - "signature": [ - "\"0,0.[0] b\"" - ], - "initialIsOpen": false - }, - { - "id": "def-public.ImportDoc", - "type": "Type", - "label": "ImportDoc", - "tags": [], - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 29 - }, - "signature": [ - "string | object | ", - { - "pluginId": "fileUpload", - "scope": "common", - "docId": "kibFileUploadPluginApi", - "section": "def-common.Doc", - "text": "Doc" - } - ], - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-public.INDEX_META_DATA_CREATED_BY", - "type": "string", - "label": "INDEX_META_DATA_CREATED_BY", - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/common/constants.ts", - "lineNumber": 19 - }, - "signature": [ - "\"ml-file-data-visualizer\"" - ], - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-public.MAX_FILE_SIZE", - "type": "string", - "label": "MAX_FILE_SIZE", - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/common/constants.ts", - "lineNumber": 11 - }, - "signature": [ - "\"100MB\"" - ], - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-public.MAX_FILE_SIZE_BYTES", - "type": "number", - "label": "MAX_FILE_SIZE_BYTES", - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/common/constants.ts", - "lineNumber": 12 - }, - "signature": [ - "104857600" - ], - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-public.MB", - "type": "number", - "label": "MB", - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/common/constants.ts", - "lineNumber": 10 - }, - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-public.UI_SETTING_MAX_FILE_SIZE", - "type": "string", - "label": "UI_SETTING_MAX_FILE_SIZE", - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/common/constants.ts", - "lineNumber": 8 - }, - "signature": [ - "\"fileUpload:maxFileSize\"" - ], - "initialIsOpen": false - } - ], - "objects": [], - "start": { - "id": "def-public.FileUploadPluginStart", - "type": "Type", - "label": "FileUploadPluginStart", - "tags": [], - "description": [], - "source": { - "path": "x-pack/plugins/file_upload/public/plugin.ts", - "lineNumber": 21 - }, - "signature": [ - "FileUploadStartApi" - ], - "lifecycle": "start", - "initialIsOpen": true - } - }, - "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "common": { - "classes": [], - "functions": [], - "interfaces": [ - { - "id": "def-common.Doc", - "type": "Interface", - "label": "Doc", - "description": [], - "tags": [], - "children": [ + }, { "tags": [], - "id": "def-common.Doc.message", - "type": "string", - "label": "message", + "id": "def-common.FindFileStructureResponse.should_trim_fields", + "type": "CompoundType", + "label": "should_trim_fields", "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 26 - } + "lineNumber": 70 + }, + "signature": [ + "boolean | undefined" + ] } ], "source": { @@ -1232,7 +2034,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 20 + "lineNumber": 87 } }, { @@ -1243,7 +2045,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 21 + "lineNumber": 88 } }, { @@ -1254,7 +2056,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 22 + "lineNumber": 89 }, "signature": [ { @@ -1269,7 +2071,7 @@ ], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 19 + "lineNumber": 86 }, "initialIsOpen": false }, @@ -1288,7 +2090,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 9 + "lineNumber": 76 } }, { @@ -1299,7 +2101,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 10 + "lineNumber": 77 } }, { @@ -1310,7 +2112,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 11 + "lineNumber": 78 }, "signature": [ "string | undefined" @@ -1324,7 +2126,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 12 + "lineNumber": 79 }, "signature": [ "string | undefined" @@ -1338,7 +2140,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 13 + "lineNumber": 80 } }, { @@ -1349,7 +2151,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 14 + "lineNumber": 81 }, "signature": [ { @@ -1370,7 +2172,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 15 + "lineNumber": 82 }, "signature": [ "any" @@ -1384,7 +2186,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 16 + "lineNumber": 83 }, "signature": [ "boolean | undefined" @@ -1393,7 +2195,7 @@ ], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 8 + "lineNumber": 75 }, "initialIsOpen": false }, @@ -1412,7 +2214,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 53 + "lineNumber": 120 } }, { @@ -1423,7 +2225,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 54 + "lineNumber": 121 }, "signature": [ "any[]" @@ -1432,7 +2234,7 @@ ], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 52 + "lineNumber": 119 }, "initialIsOpen": false }, @@ -1451,7 +2253,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 48 + "lineNumber": 115 } }, { @@ -1462,7 +2264,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 49 + "lineNumber": 116 }, "signature": [ { @@ -1477,7 +2279,35 @@ ], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 47 + "lineNumber": 114 + }, + "initialIsOpen": false + }, + { + "id": "def-common.InputOverrides", + "type": "Interface", + "label": "InputOverrides", + "description": [], + "tags": [], + "children": [ + { + "id": "def-common.InputOverrides.Unnamed", + "type": "Any", + "label": "Unnamed", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 11 + }, + "signature": [ + "any" + ] + } + ], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 10 }, "initialIsOpen": false }, @@ -1496,7 +2326,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 39 + "lineNumber": 106 }, "signature": [ "{ created_by: string; } | undefined" @@ -1510,7 +2340,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 42 + "lineNumber": 109 }, "signature": [ "{ [key: string]: any; }" @@ -1519,7 +2349,7 @@ ], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 38 + "lineNumber": 105 }, "initialIsOpen": false }, @@ -1538,7 +2368,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 32 + "lineNumber": 99 }, "signature": [ "string | undefined" @@ -1552,7 +2382,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 33 + "lineNumber": 100 } }, { @@ -1563,7 +2393,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 34 + "lineNumber": 101 }, "signature": [ "any[]" @@ -1577,7 +2407,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 35 + "lineNumber": 102 }, "signature": [ "any" @@ -1586,7 +2416,7 @@ ], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 31 + "lineNumber": 98 }, "initialIsOpen": false } @@ -1623,6 +2453,21 @@ ], "initialIsOpen": false }, + { + "id": "def-common.FormattedOverrides", + "type": "Type", + "label": "FormattedOverrides", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 14 + }, + "signature": [ + "InputOverrides & { column_names: string[]; has_header_row: boolean; should_trim_fields: boolean; }" + ], + "initialIsOpen": false + }, { "id": "def-common.ImportDoc", "type": "Type", @@ -1631,7 +2476,7 @@ "description": [], "source": { "path": "x-pack/plugins/file_upload/common/types.ts", - "lineNumber": 29 + "lineNumber": 96 }, "signature": [ "string | object | ", @@ -1660,6 +2505,21 @@ ], "initialIsOpen": false }, + { + "id": "def-common.InputData", + "type": "Type", + "label": "InputData", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/file_upload/common/types.ts", + "lineNumber": 73 + }, + "signature": [ + "any[]" + ], + "initialIsOpen": false + }, { "tags": [], "id": "def-common.MAX_FILE_SIZE", diff --git a/api_docs/fleet.json b/api_docs/fleet.json index a4c8dce90a0627..d9774d14e4c961 100644 --- a/api_docs/fleet.json +++ b/api_docs/fleet.json @@ -1699,7 +1699,7 @@ "children": [], "source": { "path": "x-pack/plugins/fleet/server/errors/index.ts", - "lineNumber": 34 + "lineNumber": 37 }, "initialIsOpen": false } @@ -1721,6 +1721,45 @@ "tags": [], "returnComment": [], "initialIsOpen": false + }, + { + "id": "def-server.relativeDownloadUrlFromArtifact", + "type": "Function", + "children": [ + { + "type": "Uncategorized", + "label": "{\n identifier,\n decodedSha256,\n}", + "isRequired": true, + "signature": [ + "T" + ], + "description": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/mappings.ts", + "lineNumber": 66 + } + } + ], + "signature": [ + ">({ identifier, decodedSha256, }: T) => string" + ], + "description": [], + "label": "relativeDownloadUrlFromArtifact", + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/mappings.ts", + "lineNumber": 64 + }, + "tags": [], + "returnComment": [], + "initialIsOpen": false } ], "interfaces": [ @@ -1877,8 +1916,8 @@ "pluginId": "fleet", "scope": "server", "docId": "kibFleetPluginApi", - "section": "def-server.getAgent", - "text": "getAgent" + "section": "def-server.getAgentById", + "text": "getAgentById" } ] }, @@ -2049,8 +2088,8 @@ "pluginId": "fleet", "scope": "server", "docId": "kibFleetPluginApi", - "section": "def-server.listAgents", - "text": "listAgents" + "section": "def-server.getAgentsByKuery", + "text": "getAgentsByKuery" } ] } @@ -2061,6 +2100,318 @@ }, "initialIsOpen": false }, + { + "id": "def-server.Artifact", + "type": "Interface", + "label": "Artifact", + "signature": [ + { + "pluginId": "fleet", + "scope": "server", + "docId": "kibFleetPluginApi", + "section": "def-server.Artifact", + "text": "Artifact" + }, + " extends ", + "NewArtifact" + ], + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-server.Artifact.id", + "type": "string", + "label": "id", + "description": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 34 + } + }, + { + "tags": [], + "id": "def-server.Artifact.created", + "type": "string", + "label": "created", + "description": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 35 + } + } + ], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 33 + }, + "initialIsOpen": false + }, + { + "id": "def-server.ArtifactsClientInterface", + "type": "Interface", + "label": "ArtifactsClientInterface", + "description": [ + "\nThe interface exposed out of Fleet's Artifact service via the client class" + ], + "tags": [], + "children": [ + { + "id": "def-server.ArtifactsClientInterface.getArtifact", + "type": "Function", + "label": "getArtifact", + "signature": [ + "(id: string) => Promise<", + { + "pluginId": "fleet", + "scope": "server", + "docId": "kibFleetPluginApi", + "section": "def-server.Artifact", + "text": "Artifact" + }, + " | undefined>" + ], + "description": [], + "children": [ + { + "type": "string", + "label": "id", + "isRequired": true, + "signature": [ + "string" + ], + "description": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 79 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 79 + } + }, + { + "id": "def-server.ArtifactsClientInterface.createArtifact", + "type": "Function", + "label": "createArtifact", + "signature": [ + "(options: ", + { + "pluginId": "fleet", + "scope": "server", + "docId": "kibFleetPluginApi", + "section": "def-server.ArtifactsClientCreateOptions", + "text": "ArtifactsClientCreateOptions" + }, + ") => Promise<", + { + "pluginId": "fleet", + "scope": "server", + "docId": "kibFleetPluginApi", + "section": "def-server.Artifact", + "text": "Artifact" + }, + ">" + ], + "description": [], + "children": [ + { + "type": "CompoundType", + "label": "options", + "isRequired": true, + "signature": [ + { + "pluginId": "fleet", + "scope": "server", + "docId": "kibFleetPluginApi", + "section": "def-server.ArtifactsClientCreateOptions", + "text": "ArtifactsClientCreateOptions" + } + ], + "description": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 81 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 81 + } + }, + { + "id": "def-server.ArtifactsClientInterface.deleteArtifact", + "type": "Function", + "label": "deleteArtifact", + "signature": [ + "(id: string) => Promise" + ], + "description": [], + "children": [ + { + "type": "string", + "label": "id", + "isRequired": true, + "signature": [ + "string" + ], + "description": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 83 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 83 + } + }, + { + "id": "def-server.ArtifactsClientInterface.listArtifacts", + "type": "Function", + "label": "listArtifacts", + "signature": [ + "(options?: ", + { + "pluginId": "fleet", + "scope": "server", + "docId": "kibFleetPluginApi", + "section": "def-server.ListArtifactsProps", + "text": "ListArtifactsProps" + }, + " | undefined) => Promise<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.ListResult", + "text": "ListResult" + }, + "<", + { + "pluginId": "fleet", + "scope": "server", + "docId": "kibFleetPluginApi", + "section": "def-server.Artifact", + "text": "Artifact" + }, + ">>" + ], + "description": [], + "children": [ + { + "type": "CompoundType", + "label": "options", + "isRequired": false, + "signature": [ + { + "pluginId": "fleet", + "scope": "server", + "docId": "kibFleetPluginApi", + "section": "def-server.ListArtifactsProps", + "text": "ListArtifactsProps" + }, + " | undefined" + ], + "description": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 85 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 85 + } + }, + { + "id": "def-server.ArtifactsClientInterface.encodeContent", + "type": "Function", + "label": "encodeContent", + "signature": [ + "(content: string) => Promise>" + ], + "description": [], + "children": [ + { + "type": "string", + "label": "content", + "isRequired": true, + "signature": [ + "string" + ], + "description": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 87 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 87 + } + }, + { + "id": "def-server.ArtifactsClientInterface.generateHash", + "type": "Function", + "label": "generateHash", + "signature": [ + "(content: string) => string" + ], + "description": [], + "children": [ + { + "type": "string", + "label": "content", + "isRequired": true, + "signature": [ + "string" + ], + "description": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 89 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 89 + } + } + ], + "source": { + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "lineNumber": 78 + }, + "initialIsOpen": false + }, { "id": "def-server.ESIndexPatternService", "type": "Interface", @@ -2164,7 +2515,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 91 + "lineNumber": 94 }, "signature": [ { @@ -2184,7 +2535,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 92 + "lineNumber": 95 }, "signature": [ { @@ -2205,7 +2556,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 93 + "lineNumber": 96 }, "signature": [ { @@ -2226,7 +2577,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 94 + "lineNumber": 97 }, "signature": [ { @@ -2246,7 +2597,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 95 + "lineNumber": 98 }, "signature": [ { @@ -2267,7 +2618,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 96 + "lineNumber": 99 }, "signature": [ "Pick<", @@ -2284,7 +2635,7 @@ ], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 90 + "lineNumber": 93 }, "initialIsOpen": false }, @@ -2384,7 +2735,7 @@ ], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 134 + "lineNumber": 137 }, "signature": [ "[\"packagePolicyCreate\", (newPackagePolicy: ", @@ -2438,7 +2789,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/index.ts", - "lineNumber": 76 + "lineNumber": 78 }, "signature": [ "any" @@ -2486,7 +2837,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 119 + "lineNumber": 122 }, "signature": [ "void" @@ -2511,7 +2862,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 158 + "lineNumber": 161 }, "signature": [ { @@ -2531,7 +2882,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 159 + "lineNumber": 162 }, "signature": [ { @@ -2551,7 +2902,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 160 + "lineNumber": 163 }, "signature": [ { @@ -2573,7 +2924,7 @@ ], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 164 + "lineNumber": 167 }, "signature": [ { @@ -2593,7 +2944,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 165 + "lineNumber": 168 }, "signature": [ { @@ -2615,7 +2966,7 @@ ], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 170 + "lineNumber": 173 }, "signature": [ "(...args: ", @@ -2628,11 +2979,34 @@ }, ") => void" ] + }, + { + "tags": [], + "id": "def-server.FleetStartContract.createArtifactsClient", + "type": "Function", + "label": "createArtifactsClient", + "description": [ + "\nCreate a Fleet Artifact Client instance" + ], + "source": { + "path": "x-pack/plugins/fleet/server/plugin.ts", + "lineNumber": 179 + }, + "signature": [ + "(packageName: string) => ", + { + "pluginId": "fleet", + "scope": "server", + "docId": "kibFleetPluginApi", + "section": "def-server.FleetArtifactsClient", + "text": "FleetArtifactsClient" + } + ] } ], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 157 + "lineNumber": 160 }, "lifecycle": "start", "initialIsOpen": true @@ -3370,7 +3744,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 148 + "lineNumber": 152 } }, { @@ -3381,7 +3755,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 149 + "lineNumber": 153 }, "signature": [ { @@ -3402,7 +3776,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 150 + "lineNumber": 154 }, "signature": [ "string | undefined" @@ -3416,7 +3790,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 151 + "lineNumber": 155 }, "signature": [ "string | undefined" @@ -3430,7 +3804,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 152 + "lineNumber": 156 }, "signature": [ "string[]" @@ -3439,7 +3813,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 147 + "lineNumber": 151 }, "initialIsOpen": false }, @@ -3475,7 +3849,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 45 + "lineNumber": 49 }, "signature": [ { @@ -3495,7 +3869,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 46 + "lineNumber": 50 }, "signature": [ "any" @@ -3509,7 +3883,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 47 + "lineNumber": 51 }, "signature": [ "string | undefined" @@ -3523,7 +3897,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 48 + "lineNumber": 52 } }, { @@ -3534,7 +3908,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 49 + "lineNumber": 53 } }, { @@ -3545,7 +3919,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 50 + "lineNumber": 54 } }, { @@ -3556,7 +3930,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 51 + "lineNumber": 55 }, "signature": [ "any" @@ -3565,7 +3939,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 44 + "lineNumber": 48 }, "initialIsOpen": false }, @@ -3601,13 +3975,13 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 120 + "lineNumber": 124 } } ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 119 + "lineNumber": 123 }, "initialIsOpen": false }, @@ -3626,7 +4000,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 126 + "lineNumber": 130 }, "signature": [ "any" @@ -3635,7 +4009,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 125 + "lineNumber": 129 }, "initialIsOpen": false }, @@ -3800,7 +4174,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 55 + "lineNumber": 59 } }, { @@ -3811,7 +4185,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 56 + "lineNumber": 60 }, "signature": [ { @@ -3831,7 +4205,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 57 + "lineNumber": 61 }, "signature": [ "{ policy: ", @@ -3853,7 +4227,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 60 + "lineNumber": 64 } }, { @@ -3864,7 +4238,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 61 + "lineNumber": 65 } }, { @@ -3875,7 +4249,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 62 + "lineNumber": 66 } }, { @@ -3886,7 +4260,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 63 + "lineNumber": 67 }, "signature": [ "any" @@ -3895,7 +4269,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 54 + "lineNumber": 58 }, "initialIsOpen": false }, @@ -3924,7 +4298,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 156 + "lineNumber": 160 }, "signature": [ "string | undefined" @@ -3938,7 +4312,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 157 + "lineNumber": 161 }, "signature": [ "string[] | undefined" @@ -3947,7 +4321,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 155 + "lineNumber": 159 }, "initialIsOpen": false }, @@ -4048,28 +4422,6 @@ "description": [], "tags": [], "children": [ - { - "tags": [], - "id": "def-common.BaseSettings.agent_auto_upgrade", - "type": "boolean", - "label": "agent_auto_upgrade", - "description": [], - "source": { - "path": "x-pack/plugins/fleet/common/types/models/settings.ts", - "lineNumber": 11 - } - }, - { - "tags": [], - "id": "def-common.BaseSettings.package_auto_upgrade", - "type": "boolean", - "label": "package_auto_upgrade", - "description": [], - "source": { - "path": "x-pack/plugins/fleet/common/types/models/settings.ts", - "lineNumber": 12 - } - }, { "tags": [], "id": "def-common.BaseSettings.kibana_urls", @@ -4078,7 +4430,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/settings.ts", - "lineNumber": 13 + "lineNumber": 11 }, "signature": [ "string[]" @@ -4092,7 +4444,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/settings.ts", - "lineNumber": 14 + "lineNumber": 12 }, "signature": [ "string | undefined" @@ -4106,7 +4458,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/settings.ts", - "lineNumber": 15 + "lineNumber": 13 }, "signature": [ "boolean | undefined" @@ -5184,7 +5536,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 169 + "lineNumber": 173 }, "signature": [ "number | undefined" @@ -5200,7 +5552,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 173 + "lineNumber": 177 }, "signature": [ "string | undefined" @@ -5216,7 +5568,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 177 + "lineNumber": 181 }, "signature": [ { @@ -5238,7 +5590,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 181 + "lineNumber": 185 } }, { @@ -5251,7 +5603,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 185 + "lineNumber": 189 } }, { @@ -5264,7 +5616,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 189 + "lineNumber": 193 }, "signature": [ "string | undefined" @@ -5280,7 +5632,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 193 + "lineNumber": 197 }, "signature": [ "string | undefined" @@ -5296,7 +5648,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 197 + "lineNumber": 201 }, "signature": [ "string | null | undefined" @@ -5312,7 +5664,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 201 + "lineNumber": 205 }, "signature": [ "string | null | undefined" @@ -5328,7 +5680,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 205 + "lineNumber": 209 }, "signature": [ "string | undefined" @@ -5342,7 +5694,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 206 + "lineNumber": 210 }, "signature": [ { @@ -5365,7 +5717,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 210 + "lineNumber": 214 }, "signature": [ { @@ -5387,7 +5739,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 214 + "lineNumber": 218 }, "signature": [ { @@ -5409,7 +5761,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 218 + "lineNumber": 222 }, "signature": [ "string | undefined" @@ -5425,7 +5777,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 222 + "lineNumber": 226 }, "signature": [ "number | null | undefined" @@ -5441,7 +5793,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 226 + "lineNumber": 230 }, "signature": [ "number | undefined" @@ -5457,7 +5809,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 230 + "lineNumber": 234 }, "signature": [ "string | undefined" @@ -5473,7 +5825,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 234 + "lineNumber": 238 }, "signature": [ "string | undefined" @@ -5489,7 +5841,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 238 + "lineNumber": 242 }, "signature": [ "\"online\" | \"error\" | \"updating\" | \"degraded\" | undefined" @@ -5505,7 +5857,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 242 + "lineNumber": 246 }, "signature": [ "string | undefined" @@ -5521,7 +5873,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 246 + "lineNumber": 250 }, "signature": [ "string | undefined" @@ -5537,7 +5889,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 250 + "lineNumber": 254 }, "signature": [ "string | undefined" @@ -5553,7 +5905,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 254 + "lineNumber": 258 }, "signature": [ "string[] | undefined" @@ -5569,7 +5921,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 258 + "lineNumber": 262 }, "signature": [ "number | undefined" @@ -5578,7 +5930,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 165 + "lineNumber": 169 }, "initialIsOpen": false }, @@ -5601,7 +5953,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 282 + "lineNumber": 286 }, "signature": [ "string | undefined" @@ -5617,7 +5969,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 286 + "lineNumber": 290 }, "signature": [ "number | undefined" @@ -5633,7 +5985,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 290 + "lineNumber": 294 }, "signature": [ "string | undefined" @@ -5649,7 +6001,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 294 + "lineNumber": 298 }, "signature": [ "string | undefined" @@ -5665,7 +6017,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 298 + "lineNumber": 302 }, "signature": [ "string | undefined" @@ -5681,7 +6033,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 302 + "lineNumber": 306 }, "signature": [ "string | undefined" @@ -5697,7 +6049,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 306 + "lineNumber": 310 }, "signature": [ "string | undefined" @@ -5713,7 +6065,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 310 + "lineNumber": 314 }, "signature": [ "string[] | undefined" @@ -5729,7 +6081,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 314 + "lineNumber": 318 }, "signature": [ "{ [k: string]: unknown; } | undefined" @@ -5743,7 +6095,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 317 + "lineNumber": 321 }, "signature": [ "any" @@ -5752,7 +6104,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 278 + "lineNumber": 282 }, "initialIsOpen": false }, @@ -5775,7 +6127,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 267 + "lineNumber": 271 } }, { @@ -5788,7 +6140,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 271 + "lineNumber": 275 } }, { @@ -5799,7 +6151,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 272 + "lineNumber": 276 }, "signature": [ "any" @@ -5808,7 +6160,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 263 + "lineNumber": 267 }, "initialIsOpen": false }, @@ -8497,7 +8849,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 39 + "lineNumber": 43 }, "signature": [ { @@ -8517,7 +8869,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 40 + "lineNumber": 44 }, "signature": [ "any" @@ -8531,7 +8883,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 41 + "lineNumber": 45 }, "signature": [ "string | undefined" @@ -8540,7 +8892,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 38 + "lineNumber": 42 }, "initialIsOpen": false }, @@ -8559,7 +8911,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 94 + "lineNumber": 98 }, "signature": [ "\"STATE\" | \"ERROR\" | \"ACTION_RESULT\" | \"ACTION\"" @@ -8573,7 +8925,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 95 + "lineNumber": 99 }, "signature": [ "\"RUNNING\" | \"STARTING\" | \"IN_PROGRESS\" | \"CONFIG\" | \"FAILED\" | \"STOPPING\" | \"STOPPED\" | \"DEGRADED\" | \"UPDATING\" | \"DATA_DUMP\" | \"ACKNOWLEDGED\" | \"UNKNOWN\"" @@ -8587,7 +8939,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 110 + "lineNumber": 114 } }, { @@ -8598,7 +8950,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 111 + "lineNumber": 115 } }, { @@ -8609,7 +8961,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 112 + "lineNumber": 116 }, "signature": [ "any" @@ -8623,7 +8975,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 113 + "lineNumber": 117 } }, { @@ -8634,7 +8986,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 114 + "lineNumber": 118 }, "signature": [ "string | undefined" @@ -8648,7 +9000,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 115 + "lineNumber": 119 }, "signature": [ "string | undefined" @@ -8662,7 +9014,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 116 + "lineNumber": 120 }, "signature": [ "string | undefined" @@ -8671,7 +9023,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 93 + "lineNumber": 97 }, "initialIsOpen": false }, @@ -10803,7 +11155,7 @@ "section": "def-common.Settings", "text": "Settings" }, - ", \"agent_auto_upgrade\" | \"package_auto_upgrade\" | \"kibana_urls\" | \"kibana_ca_sha256\" | \"has_seen_add_data_notice\">>" + ", \"kibana_urls\" | \"kibana_ca_sha256\" | \"has_seen_add_data_notice\">>" ] } ], @@ -11616,13 +11968,13 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/settings.ts", - "lineNumber": 19 + "lineNumber": 17 } } ], "source": { "path": "x-pack/plugins/fleet/common/types/models/settings.ts", - "lineNumber": 18 + "lineNumber": 16 }, "initialIsOpen": false }, @@ -11654,7 +12006,7 @@ "children": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/settings.ts", - "lineNumber": 22 + "lineNumber": 20 }, "initialIsOpen": false }, @@ -11930,7 +12282,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 277 + "lineNumber": 278 }, "initialIsOpen": false } @@ -12211,7 +12563,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 84 + "lineNumber": 88 }, "signature": [ "CommonAgentActionSOAttributes & { agent_id: string; }" @@ -12226,7 +12578,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 30 + "lineNumber": 34 }, "signature": [ "\"POLICY_CHANGE\" | \"UNENROLL\" | \"UPGRADE\" | \"SETTINGS\" | \"INTERNAL_POLICY_REASSIGN\"" @@ -12256,7 +12608,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 123 + "lineNumber": 127 }, "signature": [ "NewAgentEvent" @@ -12271,7 +12623,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 87 + "lineNumber": 91 }, "signature": [ "CommonAgentActionSOAttributes & { policy_id: string; policy_revision: number; }" @@ -12286,7 +12638,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 68 + "lineNumber": 72 }, "signature": [ "Pick & { type: 'CONFIG_CHANGE'; data: { config: FullAgentPolicy;}; }" @@ -12346,7 +12698,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 17 + "lineNumber": 21 }, "signature": [ "\"warning\" | \"offline\" | \"online\" | \"error\" | \"inactive\" | \"enrolling\" | \"unenrolling\" | \"updating\" | \"degraded\"" @@ -12361,7 +12713,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 12 + "lineNumber": 16 }, "signature": [ "\"PERMANENT\" | \"EPHEMERAL\" | \"TEMPORARY\"" @@ -12534,7 +12886,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 91 + "lineNumber": 95 }, "signature": [ { @@ -12804,6 +13156,21 @@ ], "initialIsOpen": false }, + { + "tags": [], + "id": "def-common.FLEET_SERVER_ARTIFACTS_INDEX", + "type": "string", + "label": "FLEET_SERVER_ARTIFACTS_INDEX", + "description": [], + "source": { + "path": "x-pack/plugins/fleet/common/constants/index.ts", + "lineNumber": 27 + }, + "signature": [ + "\".fleet-artifacts\"" + ], + "initialIsOpen": false + }, { "tags": [], "id": "def-common.FLEET_SERVER_INDICES", @@ -12812,7 +13179,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/constants/index.ts", - "lineNumber": 27 + "lineNumber": 29 }, "signature": [ "string[]" @@ -13512,7 +13879,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 289 + "lineNumber": 277 }, "signature": [ "\"string\" | \"text\" | \"password\" | \"integer\" | \"bool\" | \"yaml\"" @@ -13643,7 +14010,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 28 + "lineNumber": 32 }, "signature": [ "\"offline\" | \"inactive\" | \"updating\" | \"healthy\" | \"unhealthy\"" diff --git a/api_docs/global_search.json b/api_docs/global_search.json index 1743ce6e588b17..985abf3417935a 100644 --- a/api_docs/global_search.json +++ b/api_docs/global_search.json @@ -527,7 +527,7 @@ "lineNumber": 72 }, "signature": [ - "Pick & { url: string; }" + "Pick & { url: string; }" ], "initialIsOpen": false } @@ -1242,7 +1242,7 @@ "lineNumber": 72 }, "signature": [ - "Pick & { url: string; }" + "Pick & { url: string; }" ], "initialIsOpen": false } diff --git a/api_docs/kibana_react.json b/api_docs/kibana_react.json index 7541987ba42157..79ea4b3f66132e 100644 --- a/api_docs/kibana_react.json +++ b/api_docs/kibana_react.json @@ -2042,7 +2042,7 @@ }, " extends Pick<", "EuiTokenProps", - ", \"children\" | \"onClick\" | \"onChange\" | \"color\" | \"id\" | \"title\" | \"size\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"data-test-subj\" | \"fill\" | \"shape\">" + ", \"children\" | \"onClick\" | \"onChange\" | \"color\" | \"onKeyDown\" | \"title\" | \"id\" | \"size\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"data-test-subj\" | \"fill\" | \"shape\">" ], "description": [], "tags": [], diff --git a/api_docs/lens.json b/api_docs/lens.json index 235f2021e98235..abebd217ad7d59 100644 --- a/api_docs/lens.json +++ b/api_docs/lens.json @@ -1618,7 +1618,7 @@ "lineNumber": 38 }, "signature": [ - "Pick & { attributes: LensAttributes<'lnsXY', XYState> | LensAttributes<'lnsPie', PieVisualizationState> | LensAttributes<'lnsDatatable', DatatableVisualizationState> | LensAttributes<'lnsMetric', MetricState>; }" + "Pick & { attributes: LensAttributes<'lnsXY', XYState> | LensAttributes<'lnsPie', PieVisualizationState> | LensAttributes<'lnsDatatable', DatatableVisualizationState> | LensAttributes<'lnsMetric', MetricState>; }" ], "initialIsOpen": false }, diff --git a/api_docs/lists.json b/api_docs/lists.json index 3e6a22c538504c..fe06ebe62ce232 100644 --- a/api_docs/lists.json +++ b/api_docs/lists.json @@ -2296,7 +2296,7 @@ "children": [ { "type": "Object", - "label": "{ spaceId, user, config, callCluster }", + "label": "{ spaceId, user, config, esClient }", "isRequired": true, "signature": [ { diff --git a/api_docs/management.json b/api_docs/management.json index 5441f665c7784c..fb3c2ad2c33cf7 100644 --- a/api_docs/management.json +++ b/api_docs/management.json @@ -175,7 +175,7 @@ "section": "def-public.CreateManagementItemArgs", "text": "CreateManagementItemArgs" }, - ", \"title\" | \"order\" | \"euiIconType\" | \"icon\" | \"tip\"> & Pick<{ id: string; }, \"id\"> & Pick<{ id: string; }, never>, \"id\" | \"title\" | \"order\" | \"euiIconType\" | \"icon\" | \"tip\">" + ", \"title\" | \"order\" | \"euiIconType\" | \"icon\" | \"tip\"> & Pick<{ id: string; }, \"id\"> & Pick<{ id: string; }, never>, \"title\" | \"id\" | \"order\" | \"euiIconType\" | \"icon\" | \"tip\">" ], "description": [], "source": { @@ -204,7 +204,7 @@ "section": "def-public.RegisterManagementAppArgs", "text": "RegisterManagementAppArgs" }, - ", \"id\" | \"title\" | \"order\" | \"meta\" | \"mount\" | \"euiIconType\" | \"icon\" | \"tip\">) => ", + ", \"title\" | \"id\" | \"order\" | \"meta\" | \"mount\" | \"euiIconType\" | \"icon\" | \"tip\">) => ", { "pluginId": "management", "scope": "public", @@ -228,7 +228,7 @@ "section": "def-public.RegisterManagementAppArgs", "text": "RegisterManagementAppArgs" }, - ", \"id\" | \"title\" | \"order\" | \"meta\" | \"mount\" | \"euiIconType\" | \"icon\" | \"tip\">" + ", \"title\" | \"id\" | \"order\" | \"meta\" | \"mount\" | \"euiIconType\" | \"icon\" | \"tip\">" ], "description": [], "source": { diff --git a/api_docs/maps.json b/api_docs/maps.json index 6c5c7110bb1e07..7d25efab1f6f59 100644 --- a/api_docs/maps.json +++ b/api_docs/maps.json @@ -16,7 +16,7 @@ "type": "Function", "label": "addFilters", "signature": [ - "(filter: object) => void" + "(filter: object, actionId: string) => void" ], "description": [], "children": [ @@ -32,6 +32,19 @@ "path": "x-pack/plugins/maps/public/classes/tooltips/tooltip_property.ts", "lineNumber": 32 } + }, + { + "type": "string", + "label": "actionId", + "isRequired": true, + "signature": [ + "string" + ], + "description": [], + "source": { + "path": "x-pack/plugins/maps/public/classes/tooltips/tooltip_property.ts", + "lineNumber": 32 + } } ], "tags": [], @@ -409,10 +422,1558 @@ }, "common": { "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] + "functions": [ + { + "id": "def-common.getEditPath", + "type": "Function", + "label": "getEditPath", + "signature": [ + "(id: string) => string" + ], + "description": [], + "children": [ + { + "type": "string", + "label": "id", + "isRequired": true, + "signature": [ + "string" + ], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 64 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 64 + }, + "initialIsOpen": false + }, + { + "id": "def-common.getExistingMapPath", + "type": "Function", + "label": "getExistingMapPath", + "signature": [ + "(id: string) => string" + ], + "description": [], + "children": [ + { + "type": "string", + "label": "id", + "isRequired": true, + "signature": [ + "string" + ], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 61 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 61 + }, + "initialIsOpen": false + }, + { + "id": "def-common.getNewMapPath", + "type": "Function", + "label": "getNewMapPath", + "signature": [ + "() => string" + ], + "description": [], + "children": [], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 58 + }, + "initialIsOpen": false + } + ], + "interfaces": [ + { + "id": "def-common.BodySettings", + "type": "Interface", + "label": "BodySettings", + "description": [], + "tags": [], + "children": [ + { + "id": "def-common.BodySettings.Unnamed", + "type": "Any", + "label": "Unnamed", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/types.ts", + "lineNumber": 23 + }, + "signature": [ + "any" + ] + } + ], + "source": { + "path": "x-pack/plugins/maps/common/types.ts", + "lineNumber": 22 + }, + "initialIsOpen": false + }, + { + "id": "def-common.CreateDocSourceResp", + "type": "Interface", + "label": "CreateDocSourceResp", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.CreateDocSourceResp.success", + "type": "boolean", + "label": "success", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/types.ts", + "lineNumber": 9 + } + }, + { + "tags": [], + "id": "def-common.CreateDocSourceResp.error", + "type": "Object", + "label": "error", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/types.ts", + "lineNumber": 10 + }, + "signature": [ + "Error | undefined" + ] + } + ], + "source": { + "path": "x-pack/plugins/maps/common/types.ts", + "lineNumber": 8 + }, + "initialIsOpen": false + }, + { + "id": "def-common.IndexSourceMappings", + "type": "Interface", + "label": "IndexSourceMappings", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.IndexSourceMappings._meta", + "type": "Object", + "label": "_meta", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/types.ts", + "lineNumber": 14 + }, + "signature": [ + "{ created_by: string; } | undefined" + ] + }, + { + "tags": [], + "id": "def-common.IndexSourceMappings.properties", + "type": "Object", + "label": "properties", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/types.ts", + "lineNumber": 17 + }, + "signature": [ + "{ [key: string]: any; }" + ] + } + ], + "source": { + "path": "x-pack/plugins/maps/common/types.ts", + "lineNumber": 13 + }, + "initialIsOpen": false + } + ], + "enums": [ + { + "id": "def-common.AGG_TYPE", + "type": "Enum", + "label": "AGG_TYPE", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 161 + }, + "initialIsOpen": false + }, + { + "id": "def-common.COLOR_MAP_TYPE", + "type": "Enum", + "label": "COLOR_MAP_TYPE", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 209 + }, + "initialIsOpen": false + }, + { + "id": "def-common.DATA_MAPPING_FUNCTION", + "type": "Enum", + "label": "DATA_MAPPING_FUNCTION", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 290 + }, + "initialIsOpen": false + }, + { + "id": "def-common.DRAW_TYPE", + "type": "Enum", + "label": "DRAW_TYPE", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 154 + }, + "initialIsOpen": false + }, + { + "id": "def-common.ES_GEO_FIELD_TYPE", + "type": "Enum", + "label": "ES_GEO_FIELD_TYPE", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 121 + }, + "initialIsOpen": false + }, + { + "id": "def-common.ES_SPATIAL_RELATIONS", + "type": "Enum", + "label": "ES_SPATIAL_RELATIONS", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 129 + }, + "initialIsOpen": false + }, + { + "id": "def-common.FIELD_ORIGIN", + "type": "Enum", + "label": "FIELD_ORIGIN", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 94 + }, + "initialIsOpen": false + }, + { + "id": "def-common.FORMAT_TYPE", + "type": "Enum", + "label": "FORMAT_TYPE", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 253 + }, + "initialIsOpen": false + }, + { + "id": "def-common.GEO_JSON_TYPE", + "type": "Enum", + "label": "GEO_JSON_TYPE", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 135 + }, + "initialIsOpen": false + }, + { + "id": "def-common.GRID_RESOLUTION", + "type": "Enum", + "label": "GRID_RESOLUTION", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 178 + }, + "initialIsOpen": false + }, + { + "id": "def-common.INITIAL_LOCATION", + "type": "Enum", + "label": "INITIAL_LOCATION", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 265 + }, + "initialIsOpen": false + }, + { + "id": "def-common.LABEL_BORDER_SIZES", + "type": "Enum", + "label": "LABEL_BORDER_SIZES", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 222 + }, + "initialIsOpen": false + }, + { + "id": "def-common.LAYER_STYLE_TYPE", + "type": "Enum", + "label": "LAYER_STYLE_TYPE", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 203 + }, + "initialIsOpen": false + }, + { + "id": "def-common.LAYER_TYPE", + "type": "Enum", + "label": "LAYER_TYPE", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 68 + }, + "initialIsOpen": false + }, + { + "id": "def-common.LAYER_WIZARD_CATEGORY", + "type": "Enum", + "label": "LAYER_WIZARD_CATEGORY", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 272 + }, + "initialIsOpen": false + }, + { + "id": "def-common.MB_LOOKUP_FUNCTION", + "type": "Enum", + "label": "MB_LOOKUP_FUNCTION", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 285 + }, + "initialIsOpen": false + }, + { + "id": "def-common.MVT_FIELD_TYPE", + "type": "Enum", + "label": "MVT_FIELD_TYPE", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 258 + }, + "initialIsOpen": false + }, + { + "id": "def-common.RENDER_AS", + "type": "Enum", + "label": "RENDER_AS", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 172 + }, + "initialIsOpen": false + }, + { + "id": "def-common.SCALING_TYPES", + "type": "Enum", + "label": "SCALING_TYPES", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 246 + }, + "initialIsOpen": false + }, + { + "id": "def-common.SOURCE_TYPES", + "type": "Enum", + "label": "SOURCE_TYPES", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 77 + }, + "initialIsOpen": false + }, + { + "id": "def-common.STYLE_TYPE", + "type": "Enum", + "label": "STYLE_TYPE", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 198 + }, + "initialIsOpen": false + }, + { + "id": "def-common.SYMBOLIZE_AS_TYPES", + "type": "Enum", + "label": "SYMBOLIZE_AS_TYPES", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 217 + }, + "initialIsOpen": false + }, + { + "id": "def-common.VECTOR_SHAPE_TYPE", + "type": "Enum", + "label": "VECTOR_SHAPE_TYPE", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 278 + }, + "initialIsOpen": false + }, + { + "id": "def-common.VECTOR_STYLES", + "type": "Enum", + "label": "VECTOR_STYLES", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 231 + }, + "initialIsOpen": false + } + ], + "misc": [ + { + "tags": [], + "id": "def-common.AGG_DELIMITER", + "type": "string", + "label": "AGG_DELIMITER", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 160 + }, + "signature": [ + "\"_of_\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.API_ROOT_PATH", + "type": "string", + "label": "API_ROOT_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 44 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.APP_ICON", + "type": "string", + "label": "APP_ICON", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 31 + }, + "signature": [ + "\"gisApp\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.APP_ICON_SOLUTION", + "type": "string", + "label": "APP_ICON_SOLUTION", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 32 + }, + "signature": [ + "\"logoKibana\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.APP_ID", + "type": "string", + "label": "APP_ID", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 30 + }, + "signature": [ + "\"maps\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.APP_NAME", + "type": "string", + "label": "APP_NAME", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 33 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.CATEGORICAL_DATA_TYPES", + "type": "Array", + "label": "CATEGORICAL_DATA_TYPES", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 214 + }, + "signature": [ + "string[]" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.COUNT_PROP_LABEL", + "type": "string", + "label": "COUNT_PROP_LABEL", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 192 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.COUNT_PROP_NAME", + "type": "string", + "label": "COUNT_PROP_NAME", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 196 + }, + "signature": [ + "\"doc_count\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.DECIMAL_DEGREES_PRECISION", + "type": "number", + "label": "DECIMAL_DEGREES_PRECISION", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 110 + }, + "signature": [ + "5" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.DEFAULT_ICON", + "type": "string", + "label": "DEFAULT_ICON", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 229 + }, + "signature": [ + "\"marker\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.DEFAULT_MAX_BUCKETS_LIMIT", + "type": "number", + "label": "DEFAULT_MAX_BUCKETS_LIMIT", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 114 + }, + "signature": [ + "65535" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.DEFAULT_MAX_INNER_RESULT_WINDOW", + "type": "number", + "label": "DEFAULT_MAX_INNER_RESULT_WINDOW", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 113 + }, + "signature": [ + "100" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.DEFAULT_MAX_RESULT_WINDOW", + "type": "number", + "label": "DEFAULT_MAX_RESULT_WINDOW", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 112 + }, + "signature": [ + "10000" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.DEFAULT_PERCENTILE", + "type": "number", + "label": "DEFAULT_PERCENTILE", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 190 + }, + "signature": [ + "50" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.DEFAULT_PERCENTILES", + "type": "Array", + "label": "DEFAULT_PERCENTILES", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 294 + }, + "signature": [ + "number[]" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_APP_NAME", + "type": "string", + "label": "EMS_APP_NAME", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 11 + }, + "signature": [ + "\"kibana\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_CATALOGUE_PATH", + "type": "string", + "label": "EMS_CATALOGUE_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 12 + }, + "signature": [ + "\"ems/catalogue\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_FILES_API_PATH", + "type": "string", + "label": "EMS_FILES_API_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 15 + }, + "signature": [ + "\"ems/files\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_FILES_CATALOGUE_PATH", + "type": "string", + "label": "EMS_FILES_CATALOGUE_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 14 + }, + "signature": [ + "\"ems/files\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_FILES_DEFAULT_JSON_PATH", + "type": "string", + "label": "EMS_FILES_DEFAULT_JSON_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 16 + }, + "signature": [ + "\"file\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_GLYPHS_PATH", + "type": "string", + "label": "EMS_GLYPHS_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 17 + }, + "signature": [ + "\"fonts\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_SPRITES_PATH", + "type": "string", + "label": "EMS_SPRITES_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 18 + }, + "signature": [ + "\"sprites\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_TILES_API_PATH", + "type": "string", + "label": "EMS_TILES_API_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 21 + }, + "signature": [ + "\"ems/tiles\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_TILES_CATALOGUE_PATH", + "type": "string", + "label": "EMS_TILES_CATALOGUE_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 20 + }, + "signature": [ + "\"ems/tiles\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_TILES_RASTER_STYLE_PATH", + "type": "string", + "label": "EMS_TILES_RASTER_STYLE_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 22 + }, + "signature": [ + "\"raster/style\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_TILES_RASTER_TILE_PATH", + "type": "string", + "label": "EMS_TILES_RASTER_TILE_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 23 + }, + "signature": [ + "\"raster/tile\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_TILES_VECTOR_SOURCE_PATH", + "type": "string", + "label": "EMS_TILES_VECTOR_SOURCE_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 26 + }, + "signature": [ + "\"vector/source\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_TILES_VECTOR_STYLE_PATH", + "type": "string", + "label": "EMS_TILES_VECTOR_STYLE_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 25 + }, + "signature": [ + "\"vector/style\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.EMS_TILES_VECTOR_TILE_PATH", + "type": "string", + "label": "EMS_TILES_VECTOR_TILE_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 27 + }, + "signature": [ + "\"vector/tile\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.ES_GEO_FIELD_TYPES", + "type": "Array", + "label": "ES_GEO_FIELD_TYPES", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 127 + }, + "signature": [ + "string[]" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.FEATURE_ID_PROPERTY_NAME", + "type": "string", + "label": "FEATURE_ID_PROPERTY_NAME", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 116 + }, + "signature": [ + "\"__kbn__feature_id__\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.FEATURE_VISIBLE_PROPERTY_NAME", + "type": "string", + "label": "FEATURE_VISIBLE_PROPERTY_NAME", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 117 + }, + "signature": [ + "\"__kbn_isvisibleduetojoin__\"" + ], + "initialIsOpen": false + }, + { + "id": "def-common.FieldFormatter", + "type": "Type", + "label": "FieldFormatter", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 298 + }, + "signature": [ + "(value: ", + { + "pluginId": "maps", + "scope": "common", + "docId": "kibMapsPluginApi", + "section": "def-common.RawValue", + "text": "RawValue" + }, + ") => React.ReactText" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.FONTS_API_PATH", + "type": "string", + "label": "FONTS_API_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 42 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.FORMATTERS_DATA_REQUEST_ID_SUFFIX", + "type": "string", + "label": "FORMATTERS_DATA_REQUEST_ID_SUFFIX", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 101 + }, + "signature": [ + "\"formatters\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.GEOCENTROID_AGG_NAME", + "type": "string", + "label": "GEOCENTROID_AGG_NAME", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 187 + }, + "signature": [ + "\"gridCentroid\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.GEOTILE_GRID_AGG_NAME", + "type": "string", + "label": "GEOTILE_GRID_AGG_NAME", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 186 + }, + "signature": [ + "\"gridSplit\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.GIS_API_PATH", + "type": "string", + "label": "GIS_API_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 40 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.INDEX_META_DATA_CREATED_BY", + "type": "string", + "label": "INDEX_META_DATA_CREATED_BY", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 300 + }, + "signature": [ + "\"maps-drawing-data-ingest\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.INDEX_SETTINGS_API_PATH", + "type": "string", + "label": "INDEX_SETTINGS_API_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 41 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.INDEX_SOURCE_API_PATH", + "type": "string", + "label": "INDEX_SOURCE_API_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 43 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.INITIAL_LAYERS_KEY", + "type": "string", + "label": "INITIAL_LAYERS_KEY", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 36 + }, + "signature": [ + "\"initialLayers\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.JOIN_FIELD_NAME_PREFIX", + "type": "string", + "label": "JOIN_FIELD_NAME_PREFIX", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 98 + }, + "signature": [ + "\"__kbnjoin__\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.KBN_IS_CENTROID_FEATURE", + "type": "string", + "label": "KBN_IS_CENTROID_FEATURE", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 55 + }, + "signature": [ + "\"__kbn_is_centroid_feature__\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.KBN_TOO_MANY_FEATURES_IMAGE_ID", + "type": "string", + "label": "KBN_TOO_MANY_FEATURES_IMAGE_ID", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 52 + }, + "signature": [ + "\"__kbn_too_many_features_image_id__\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.KBN_TOO_MANY_FEATURES_PROPERTY", + "type": "string", + "label": "KBN_TOO_MANY_FEATURES_PROPERTY", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 51 + }, + "signature": [ + "\"__kbn_too_many_features__\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.LAT_INDEX", + "type": "number", + "label": "LAT_INDEX", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 147 + }, + "signature": [ + "1" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.LON_INDEX", + "type": "number", + "label": "LON_INDEX", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 146 + }, + "signature": [ + "0" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.MAP_PATH", + "type": "string", + "label": "MAP_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 39 + }, + "signature": [ + "\"map\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.MAP_SAVED_OBJECT_TYPE", + "type": "string", + "label": "MAP_SAVED_OBJECT_TYPE", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 29 + }, + "signature": [ + "\"map\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.MAPS_APP_PATH", + "type": "string", + "label": "MAPS_APP_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 38 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.MAX_ZOOM", + "type": "number", + "label": "MAX_ZOOM", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 108 + }, + "signature": [ + "24" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER", + "type": "string", + "label": "MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 119 + }, + "signature": [ + "\"_\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.META_DATA_REQUEST_ID_SUFFIX", + "type": "string", + "label": "META_DATA_REQUEST_ID_SUFFIX", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 100 + }, + "signature": [ + "\"meta\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.MIN_ZOOM", + "type": "number", + "label": "MIN_ZOOM", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 107 + }, + "signature": [ + "0" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.MVT_GETGRIDTILE_API_PATH", + "type": "string", + "label": "MVT_GETGRIDTILE_API_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 47 + }, + "signature": [ + "\"mvt/getGridTile\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.MVT_GETTILE_API_PATH", + "type": "string", + "label": "MVT_GETTILE_API_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 46 + }, + "signature": [ + "\"mvt/getTile\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.MVT_SOURCE_LAYER_NAME", + "type": "string", + "label": "MVT_SOURCE_LAYER_NAME", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 48 + }, + "signature": [ + "\"source_layer\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.ORDINAL_DATA_TYPES", + "type": "Array", + "label": "ORDINAL_DATA_TYPES", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 215 + }, + "signature": [ + "string[]" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.POLYGON_COORDINATES_EXTERIOR_INDEX", + "type": "number", + "label": "POLYGON_COORDINATES_EXTERIOR_INDEX", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 145 + }, + "signature": [ + "0" + ], + "initialIsOpen": false + }, + { + "id": "def-common.RawValue", + "type": "Type", + "label": "RawValue", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 296 + }, + "signature": [ + "undefined | null | string | number | false | true" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.SOURCE_BOUNDS_DATA_REQUEST_ID", + "type": "string", + "label": "SOURCE_BOUNDS_DATA_REQUEST_ID", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 105 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.SOURCE_DATA_REQUEST_ID", + "type": "string", + "label": "SOURCE_DATA_REQUEST_ID", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 102 + }, + "signature": [ + "\"source\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.SOURCE_FORMATTERS_DATA_REQUEST_ID", + "type": "string", + "label": "SOURCE_FORMATTERS_DATA_REQUEST_ID", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 104 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.SOURCE_META_DATA_REQUEST_ID", + "type": "string", + "label": "SOURCE_META_DATA_REQUEST_ID", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 103 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.SPATIAL_FILTERS_LAYER_ID", + "type": "string", + "label": "SPATIAL_FILTERS_LAYER_ID", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 263 + }, + "signature": [ + "\"SPATIAL_FILTERS_LAYER_ID\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.SUPER_FINE_ZOOM_DELTA", + "type": "number", + "label": "SUPER_FINE_ZOOM_DELTA", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 185 + }, + "signature": [ + "7" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.TOP_TERM_PERCENTAGE_SUFFIX", + "type": "string", + "label": "TOP_TERM_PERCENTAGE_SUFFIX", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 189 + }, + "signature": [ + "\"__percentage\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.ZOOM_PRECISION", + "type": "number", + "label": "ZOOM_PRECISION", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 111 + }, + "signature": [ + "2" + ], + "initialIsOpen": false + } + ], + "objects": [ + { + "id": "def-common.EMPTY_FEATURE_COLLECTION", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.EMPTY_FEATURE_COLLECTION.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 150 + }, + "signature": [ + "\"FeatureCollection\"" + ] + }, + { + "tags": [], + "id": "def-common.EMPTY_FEATURE_COLLECTION.features", + "type": "Array", + "label": "features", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 151 + }, + "signature": [ + "never[]" + ] + } + ], + "description": [], + "label": "EMPTY_FEATURE_COLLECTION", + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 149 + }, + "initialIsOpen": false + } + ] } } \ No newline at end of file diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 3e95ab1f79e985..9799fa17693912 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -22,3 +22,20 @@ import mapsObj from './maps.json'; ### Consts, variables and types +## Common + +### Objects + + +### Functions + + +### Interfaces + + +### Enums + + +### Consts, variables and types + + diff --git a/api_docs/maps_ems.json b/api_docs/maps_ems.json new file mode 100644 index 00000000000000..472f6974207676 --- /dev/null +++ b/api_docs/maps_ems.json @@ -0,0 +1,1157 @@ +{ + "id": "mapsEms", + "client": { + "classes": [], + "functions": [], + "interfaces": [ + { + "id": "def-public.FileLayer", + "type": "Interface", + "label": "FileLayer", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.FileLayer.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 18 + } + }, + { + "tags": [], + "id": "def-public.FileLayer.origin", + "type": "string", + "label": "origin", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 19 + } + }, + { + "tags": [], + "id": "def-public.FileLayer.id", + "type": "string", + "label": "id", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 20 + } + }, + { + "tags": [], + "id": "def-public.FileLayer.format", + "type": "CompoundType", + "label": "format", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 21 + }, + "signature": [ + "string | { type: string; }" + ] + }, + { + "tags": [], + "id": "def-public.FileLayer.fields", + "type": "Array", + "label": "fields", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 22 + }, + "signature": [ + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.FileLayerField", + "text": "FileLayerField" + }, + "[]" + ] + }, + { + "tags": [], + "id": "def-public.FileLayer.url", + "type": "string", + "label": "url", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 23 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-public.FileLayer.layerId", + "type": "string", + "label": "layerId", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 24 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-public.FileLayer.created_at", + "type": "string", + "label": "created_at", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 25 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-public.FileLayer.attribution", + "type": "string", + "label": "attribution", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 26 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-public.FileLayer.meta", + "type": "Object", + "label": "meta", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 27 + }, + "signature": [ + "{ [key: string]: string; } | undefined" + ] + } + ], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 17 + }, + "initialIsOpen": false + }, + { + "id": "def-public.FileLayerField", + "type": "Interface", + "label": "FileLayerField", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.FileLayerField.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 33 + } + }, + { + "tags": [], + "id": "def-public.FileLayerField.description", + "type": "string", + "label": "description", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 34 + } + }, + { + "tags": [], + "id": "def-public.FileLayerField.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 35 + } + } + ], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 32 + }, + "initialIsOpen": false + }, + { + "id": "def-public.IServiceSettings", + "type": "Interface", + "label": "IServiceSettings", + "description": [], + "tags": [], + "children": [ + { + "id": "def-public.IServiceSettings.getEMSHotLink", + "type": "Function", + "label": "getEMSHotLink", + "signature": [ + "(layer: ", + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.FileLayer", + "text": "FileLayer" + }, + ") => Promise" + ], + "description": [], + "children": [ + { + "type": "Object", + "label": "layer", + "isRequired": true, + "signature": [ + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.FileLayer", + "text": "FileLayer" + } + ], + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 44 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 44 + } + }, + { + "id": "def-public.IServiceSettings.getTMSServices", + "type": "Function", + "label": "getTMSServices", + "signature": [ + "() => Promise<", + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.TmsLayer", + "text": "TmsLayer" + }, + "[]>" + ], + "description": [], + "children": [], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 45 + } + }, + { + "id": "def-public.IServiceSettings.getFileLayers", + "type": "Function", + "label": "getFileLayers", + "signature": [ + "() => Promise<", + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.FileLayer", + "text": "FileLayer" + }, + "[]>" + ], + "description": [], + "children": [], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 46 + } + }, + { + "id": "def-public.IServiceSettings.getUrlForRegionLayer", + "type": "Function", + "label": "getUrlForRegionLayer", + "signature": [ + "(layer: ", + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.FileLayer", + "text": "FileLayer" + }, + ") => Promise" + ], + "description": [], + "children": [ + { + "type": "Object", + "label": "layer", + "isRequired": true, + "signature": [ + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.FileLayer", + "text": "FileLayer" + } + ], + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 47 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 47 + } + }, + { + "id": "def-public.IServiceSettings.setQueryParams", + "type": "Function", + "label": "setQueryParams", + "signature": [ + "(params: { [p: string]: string; }) => void" + ], + "description": [], + "children": [ + { + "id": "def-public.IServiceSettings.setQueryParams.params", + "type": "Object", + "label": "params", + "tags": [], + "description": [], + "children": [ + { + "id": "def-public.IServiceSettings.setQueryParams.params.Unnamed", + "type": "Any", + "label": "Unnamed", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 48 + }, + "signature": [ + "any" + ] + } + ], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 48 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 48 + } + }, + { + "id": "def-public.IServiceSettings.enableZoomMessage", + "type": "Function", + "label": "enableZoomMessage", + "signature": [ + "() => void" + ], + "description": [], + "children": [], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 49 + } + }, + { + "id": "def-public.IServiceSettings.disableZoomMessage", + "type": "Function", + "label": "disableZoomMessage", + "signature": [ + "() => void" + ], + "description": [], + "children": [], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 50 + } + }, + { + "id": "def-public.IServiceSettings.getAttributesForTMSLayer", + "type": "Function", + "label": "getAttributesForTMSLayer", + "signature": [ + "(tmsServiceConfig: ", + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.TmsLayer", + "text": "TmsLayer" + }, + ", isDesaturated: boolean, isDarkMode: boolean) => any" + ], + "description": [], + "children": [ + { + "type": "Object", + "label": "tmsServiceConfig", + "isRequired": true, + "signature": [ + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.TmsLayer", + "text": "TmsLayer" + } + ], + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 52 + } + }, + { + "type": "boolean", + "label": "isDesaturated", + "isRequired": true, + "signature": [ + "boolean" + ], + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 53 + } + }, + { + "type": "boolean", + "label": "isDarkMode", + "isRequired": true, + "signature": [ + "boolean" + ], + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 54 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 51 + } + } + ], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 43 + }, + "initialIsOpen": false + }, + { + "id": "def-public.TmsLayer", + "type": "Interface", + "label": "TmsLayer", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.TmsLayer.id", + "type": "string", + "label": "id", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 10 + } + }, + { + "tags": [], + "id": "def-public.TmsLayer.origin", + "type": "string", + "label": "origin", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 11 + } + }, + { + "tags": [], + "id": "def-public.TmsLayer.minZoom", + "type": "number", + "label": "minZoom", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 12 + } + }, + { + "tags": [], + "id": "def-public.TmsLayer.maxZoom", + "type": "number", + "label": "maxZoom", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 13 + } + }, + { + "tags": [], + "id": "def-public.TmsLayer.attribution", + "type": "string", + "label": "attribution", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 14 + } + } + ], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 9 + }, + "initialIsOpen": false + }, + { + "id": "def-public.VectorLayer", + "type": "Interface", + "label": "VectorLayer", + "signature": [ + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.VectorLayer", + "text": "VectorLayer" + }, + " extends ", + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.FileLayer", + "text": "FileLayer" + } + ], + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.VectorLayer.layerId", + "type": "string", + "label": "layerId", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 39 + } + }, + { + "tags": [], + "id": "def-public.VectorLayer.isEMS", + "type": "boolean", + "label": "isEMS", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 40 + } + } + ], + "source": { + "path": "src/plugins/maps_ems/public/service_settings/service_settings_types.ts", + "lineNumber": 38 + }, + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "tags": [], + "id": "def-public.DEFAULT_EMS_FILE_API_URL", + "type": "string", + "label": "DEFAULT_EMS_FILE_API_URL", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/ems_defaults.ts", + "lineNumber": 10 + }, + "signature": [ + "\"https://vector.maps.elastic.co\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-public.DEFAULT_EMS_FONT_LIBRARY_URL", + "type": "string", + "label": "DEFAULT_EMS_FONT_LIBRARY_URL", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/ems_defaults.ts", + "lineNumber": 13 + }, + "signature": [ + "\"https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-public.DEFAULT_EMS_LANDING_PAGE_URL", + "type": "string", + "label": "DEFAULT_EMS_LANDING_PAGE_URL", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/ems_defaults.ts", + "lineNumber": 12 + }, + "signature": [ + "\"https://maps.elastic.co/v7.12\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-public.DEFAULT_EMS_TILE_API_URL", + "type": "string", + "label": "DEFAULT_EMS_TILE_API_URL", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/ems_defaults.ts", + "lineNumber": 11 + }, + "signature": [ + "\"https://tiles.maps.elastic.co\"" + ], + "initialIsOpen": false + }, + { + "id": "def-public.LayerConfig", + "type": "Type", + "label": "LayerConfig", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/maps_ems/config.ts", + "lineNumber": 54 + }, + "signature": [ + "{ readonly name: string; readonly format: Readonly<{} & { type: string; }>; readonly fields: Readonly<{} & { description: string; name: string; }>[]; readonly meta: Readonly<{} & { feature_collection_path: string; }>; readonly url: string; readonly attribution: string; }" + ], + "initialIsOpen": false + }, + { + "id": "def-public.MapsEmsConfig", + "type": "Type", + "label": "MapsEmsConfig", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/maps_ems/config.ts", + "lineNumber": 86 + }, + "signature": [ + "{ readonly includeElasticMapsService: boolean; readonly proxyElasticMapsServiceInMaps: boolean; readonly regionmap: Readonly<{} & { includeElasticMapsService: boolean; layers: Readonly<{} & { name: string; format: Readonly<{} & { type: string; }>; fields: Readonly<{} & { description: string; name: string; }>[]; meta: Readonly<{} & { feature_collection_path: string; }>; url: string; attribution: string; }>[]; }>; readonly tilemap: Readonly<{ url?: string | undefined; } & { options: Readonly<{ default?: boolean | undefined; tileSize?: number | undefined; subdomains?: string[] | undefined; errorTileUrl?: string | undefined; tms?: boolean | undefined; reuseTiles?: boolean | undefined; bounds?: number[] | undefined; } & { attribution: string; minZoom: number; maxZoom: number; }>; }>; readonly manifestServiceUrl: string; readonly emsUrl: string; readonly emsFileApiUrl: string; readonly emsTileApiUrl: string; readonly emsLandingPageUrl: string; readonly emsFontLibraryUrl: string; readonly emsTileLayerId: Readonly<{} & { bright: string; desaturated: string; dark: string; }>; }" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-public.TMS_IN_YML_ID", + "type": "string", + "label": "TMS_IN_YML_ID", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/index.ts", + "lineNumber": 9 + }, + "signature": [ + "\"TMS in config/kibana.yml\"" + ], + "initialIsOpen": false + } + ], + "objects": [ + { + "id": "def-public.ORIGIN", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.ORIGIN.EMS", + "type": "string", + "label": "EMS", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/origin.ts", + "lineNumber": 10 + } + }, + { + "tags": [], + "id": "def-public.ORIGIN.KIBANA_YML", + "type": "string", + "label": "KIBANA_YML", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/origin.ts", + "lineNumber": 11 + } + } + ], + "description": [], + "label": "ORIGIN", + "source": { + "path": "src/plugins/maps_ems/common/origin.ts", + "lineNumber": 9 + }, + "initialIsOpen": false + } + ], + "setup": { + "id": "def-public.MapsEmsPluginSetup", + "type": "Interface", + "label": "MapsEmsPluginSetup", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.MapsEmsPluginSetup.config", + "type": "Object", + "label": "config", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/index.ts", + "lineNumber": 32 + }, + "signature": [ + "Readonly<{} & { includeElasticMapsService: boolean; proxyElasticMapsServiceInMaps: boolean; regionmap: Readonly<{} & { includeElasticMapsService: boolean; layers: Readonly<{} & { name: string; format: Readonly<{} & { type: string; }>; fields: Readonly<{} & { description: string; name: string; }>[]; meta: Readonly<{} & { feature_collection_path: string; }>; url: string; attribution: string; }>[]; }>; tilemap: Readonly<{ url?: string | undefined; } & { options: Readonly<{ default?: boolean | undefined; tileSize?: number | undefined; subdomains?: string[] | undefined; errorTileUrl?: string | undefined; tms?: boolean | undefined; reuseTiles?: boolean | undefined; bounds?: number[] | undefined; } & { attribution: string; minZoom: number; maxZoom: number; }>; }>; manifestServiceUrl: string; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { bright: string; desaturated: string; dark: string; }>; }>" + ] + }, + { + "tags": [], + "id": "def-public.MapsEmsPluginSetup.getServiceSettings", + "type": "Function", + "label": "getServiceSettings", + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/index.ts", + "lineNumber": 33 + }, + "signature": [ + "() => Promise<", + { + "pluginId": "mapsEms", + "scope": "public", + "docId": "kibMapsEmsPluginApi", + "section": "def-public.IServiceSettings", + "text": "IServiceSettings" + }, + ">" + ] + } + ], + "source": { + "path": "src/plugins/maps_ems/public/index.ts", + "lineNumber": 31 + }, + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "id": "def-public.MapsEmsPluginStart", + "type": "Type", + "label": "MapsEmsPluginStart", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/maps_ems/public/index.ts", + "lineNumber": 35 + }, + "signature": [ + "void" + ], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [ + { + "id": "def-server.MapsEmsPlugin", + "type": "Class", + "tags": [], + "label": "MapsEmsPlugin", + "description": [], + "signature": [ + { + "pluginId": "mapsEms", + "scope": "server", + "docId": "kibMapsEmsPluginApi", + "section": "def-server.MapsEmsPlugin", + "text": "MapsEmsPlugin" + }, + " implements ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.Plugin", + "text": "Plugin" + }, + "<", + { + "pluginId": "mapsEms", + "scope": "server", + "docId": "kibMapsEmsPluginApi", + "section": "def-server.MapsEmsPluginSetup", + "text": "MapsEmsPluginSetup" + }, + ", void, object, object>" + ], + "children": [ + { + "tags": [], + "id": "def-server.MapsEmsPlugin._initializerContext", + "type": "Object", + "label": "_initializerContext", + "description": [], + "source": { + "path": "src/plugins/maps_ems/server/index.ts", + "lineNumber": 39 + }, + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.PluginInitializerContext", + "text": "PluginInitializerContext" + }, + "; fields: Readonly<{} & { description: string; name: string; }>[]; meta: Readonly<{} & { feature_collection_path: string; }>; url: string; attribution: string; }>[]; }>; tilemap: Readonly<{ url?: string | undefined; } & { options: Readonly<{ default?: boolean | undefined; tileSize?: number | undefined; subdomains?: string[] | undefined; errorTileUrl?: string | undefined; tms?: boolean | undefined; reuseTiles?: boolean | undefined; bounds?: number[] | undefined; } & { attribution: string; minZoom: number; maxZoom: number; }>; }>; manifestServiceUrl: string; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { bright: string; desaturated: string; dark: string; }>; }>>" + ] + }, + { + "id": "def-server.MapsEmsPlugin.Unnamed", + "type": "Function", + "label": "Constructor", + "signature": [ + "any" + ], + "description": [], + "children": [ + { + "type": "Object", + "label": "initializerContext", + "isRequired": true, + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.PluginInitializerContext", + "text": "PluginInitializerContext" + }, + "; fields: Readonly<{} & { description: string; name: string; }>[]; meta: Readonly<{} & { feature_collection_path: string; }>; url: string; attribution: string; }>[]; }>; tilemap: Readonly<{ url?: string | undefined; } & { options: Readonly<{ default?: boolean | undefined; tileSize?: number | undefined; subdomains?: string[] | undefined; errorTileUrl?: string | undefined; tms?: boolean | undefined; reuseTiles?: boolean | undefined; bounds?: number[] | undefined; } & { attribution: string; minZoom: number; maxZoom: number; }>; }>; manifestServiceUrl: string; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { bright: string; desaturated: string; dark: string; }>; }>>" + ], + "description": [], + "source": { + "path": "src/plugins/maps_ems/server/index.ts", + "lineNumber": 41 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/maps_ems/server/index.ts", + "lineNumber": 41 + } + }, + { + "id": "def-server.MapsEmsPlugin.setup", + "type": "Function", + "label": "setup", + "signature": [ + "(core: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.CoreSetup", + "text": "CoreSetup" + }, + ") => { config: Readonly<{} & { includeElasticMapsService: boolean; proxyElasticMapsServiceInMaps: boolean; regionmap: Readonly<{} & { includeElasticMapsService: boolean; layers: Readonly<{} & { name: string; format: Readonly<{} & { type: string; }>; fields: Readonly<{} & { description: string; name: string; }>[]; meta: Readonly<{} & { feature_collection_path: string; }>; url: string; attribution: string; }>[]; }>; tilemap: Readonly<{ url?: string | undefined; } & { options: Readonly<{ default?: boolean | undefined; tileSize?: number | undefined; subdomains?: string[] | undefined; errorTileUrl?: string | undefined; tms?: boolean | undefined; reuseTiles?: boolean | undefined; bounds?: number[] | undefined; } & { attribution: string; minZoom: number; maxZoom: number; }>; }>; manifestServiceUrl: string; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { bright: string; desaturated: string; dark: string; }>; }>; }" + ], + "description": [], + "children": [ + { + "type": "Object", + "label": "core", + "isRequired": true, + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.CoreSetup", + "text": "CoreSetup" + }, + "" + ], + "description": [], + "source": { + "path": "src/plugins/maps_ems/server/index.ts", + "lineNumber": 45 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/maps_ems/server/index.ts", + "lineNumber": 45 + } + }, + { + "id": "def-server.MapsEmsPlugin.start", + "type": "Function", + "label": "start", + "signature": [ + "() => void" + ], + "description": [], + "children": [], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/maps_ems/server/index.ts", + "lineNumber": 52 + } + } + ], + "source": { + "path": "src/plugins/maps_ems/server/index.ts", + "lineNumber": 38 + }, + "initialIsOpen": false + } + ], + "functions": [], + "interfaces": [ + { + "id": "def-server.MapsEmsPluginSetup", + "type": "Interface", + "label": "MapsEmsPluginSetup", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-server.MapsEmsPluginSetup.config", + "type": "Object", + "label": "config", + "description": [], + "source": { + "path": "src/plugins/maps_ems/server/index.ts", + "lineNumber": 35 + }, + "signature": [ + "Readonly<{} & { includeElasticMapsService: boolean; proxyElasticMapsServiceInMaps: boolean; regionmap: Readonly<{} & { includeElasticMapsService: boolean; layers: Readonly<{} & { name: string; format: Readonly<{} & { type: string; }>; fields: Readonly<{} & { description: string; name: string; }>[]; meta: Readonly<{} & { feature_collection_path: string; }>; url: string; attribution: string; }>[]; }>; tilemap: Readonly<{ url?: string | undefined; } & { options: Readonly<{ default?: boolean | undefined; tileSize?: number | undefined; subdomains?: string[] | undefined; errorTileUrl?: string | undefined; tms?: boolean | undefined; reuseTiles?: boolean | undefined; bounds?: number[] | undefined; } & { attribution: string; minZoom: number; maxZoom: number; }>; }>; manifestServiceUrl: string; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { bright: string; desaturated: string; dark: string; }>; }>" + ] + } + ], + "source": { + "path": "src/plugins/maps_ems/server/index.ts", + "lineNumber": 34 + }, + "initialIsOpen": false + } + ], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "tags": [], + "id": "def-common.DEFAULT_EMS_FILE_API_URL", + "type": "string", + "label": "DEFAULT_EMS_FILE_API_URL", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/ems_defaults.ts", + "lineNumber": 10 + }, + "signature": [ + "\"https://vector.maps.elastic.co\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.DEFAULT_EMS_FONT_LIBRARY_URL", + "type": "string", + "label": "DEFAULT_EMS_FONT_LIBRARY_URL", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/ems_defaults.ts", + "lineNumber": 13 + }, + "signature": [ + "\"https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.DEFAULT_EMS_LANDING_PAGE_URL", + "type": "string", + "label": "DEFAULT_EMS_LANDING_PAGE_URL", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/ems_defaults.ts", + "lineNumber": 12 + }, + "signature": [ + "\"https://maps.elastic.co/v7.12\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.DEFAULT_EMS_TILE_API_URL", + "type": "string", + "label": "DEFAULT_EMS_TILE_API_URL", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/ems_defaults.ts", + "lineNumber": 11 + }, + "signature": [ + "\"https://tiles.maps.elastic.co\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.TMS_IN_YML_ID", + "type": "string", + "label": "TMS_IN_YML_ID", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/index.ts", + "lineNumber": 9 + }, + "signature": [ + "\"TMS in config/kibana.yml\"" + ], + "initialIsOpen": false + } + ], + "objects": [ + { + "id": "def-common.ORIGIN", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.ORIGIN.EMS", + "type": "string", + "label": "EMS", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/origin.ts", + "lineNumber": 10 + } + }, + { + "tags": [], + "id": "def-common.ORIGIN.KIBANA_YML", + "type": "string", + "label": "KIBANA_YML", + "description": [], + "source": { + "path": "src/plugins/maps_ems/common/origin.ts", + "lineNumber": 11 + } + } + ], + "description": [], + "label": "ORIGIN", + "source": { + "path": "src/plugins/maps_ems/common/origin.ts", + "lineNumber": 9 + }, + "initialIsOpen": false + } + ] + } +} \ No newline at end of file diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx new file mode 100644 index 00000000000000..5907add0c54659 --- /dev/null +++ b/api_docs/maps_ems.mdx @@ -0,0 +1,46 @@ +--- +id: kibMapsEmsPluginApi +slug: /kibana-dev-docs/mapsEmsPluginApi +title: mapsEms +image: https://source.unsplash.com/400x175/?github +summary: API docs for the mapsEms plugin +date: 2020-11-16 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] +warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. +--- + +import mapsEmsObj from './maps_ems.json'; + +## Client + +### Setup + + +### Start + + +### Objects + + +### Interfaces + + +### Consts, variables and types + + +## Server + +### Classes + + +### Interfaces + + +## Common + +### Objects + + +### Consts, variables and types + + diff --git a/api_docs/maps_legacy.json b/api_docs/maps_legacy.json deleted file mode 100644 index 09c94af4e0367c..00000000000000 --- a/api_docs/maps_legacy.json +++ /dev/null @@ -1,277 +0,0 @@ -{ - "id": "mapsLegacy", - "client": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "server": { - "classes": [ - { - "id": "def-server.MapsLegacyPlugin", - "type": "Class", - "tags": [], - "label": "MapsLegacyPlugin", - "description": [], - "signature": [ - { - "pluginId": "mapsLegacy", - "scope": "server", - "docId": "kibMapsLegacyPluginApi", - "section": "def-server.MapsLegacyPlugin", - "text": "MapsLegacyPlugin" - }, - " implements ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, - "<", - { - "pluginId": "mapsLegacy", - "scope": "server", - "docId": "kibMapsLegacyPluginApi", - "section": "def-server.MapsLegacyPluginSetup", - "text": "MapsLegacyPluginSetup" - }, - ", void, object, object>" - ], - "children": [ - { - "tags": [], - "id": "def-server.MapsLegacyPlugin._initializerContext", - "type": "Object", - "label": "_initializerContext", - "description": [], - "source": { - "path": "src/plugins/maps_legacy/server/index.ts", - "lineNumber": 36 - }, - "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, - "; }>; regionmap: Readonly<{} & { includeElasticMapsService: boolean; layers: Readonly<{} & { name: string; format: Readonly<{} & { type: string; }>; fields: Readonly<{} & { description: string; name: string; }>[]; meta: Readonly<{} & { feature_collection_path: string; }>; url: string; attribution: string; }>[]; }>; manifestServiceUrl: string; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { bright: string; desaturated: string; dark: string; }>; }>>" - ] - }, - { - "id": "def-server.MapsLegacyPlugin.Unnamed", - "type": "Function", - "label": "Constructor", - "signature": [ - "any" - ], - "description": [], - "children": [ - { - "type": "Object", - "label": "initializerContext", - "isRequired": true, - "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, - "; }>; regionmap: Readonly<{} & { includeElasticMapsService: boolean; layers: Readonly<{} & { name: string; format: Readonly<{} & { type: string; }>; fields: Readonly<{} & { description: string; name: string; }>[]; meta: Readonly<{} & { feature_collection_path: string; }>; url: string; attribution: string; }>[]; }>; manifestServiceUrl: string; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { bright: string; desaturated: string; dark: string; }>; }>>" - ], - "description": [], - "source": { - "path": "src/plugins/maps_legacy/server/index.ts", - "lineNumber": 38 - } - } - ], - "tags": [], - "returnComment": [], - "source": { - "path": "src/plugins/maps_legacy/server/index.ts", - "lineNumber": 38 - } - }, - { - "id": "def-server.MapsLegacyPlugin.setup", - "type": "Function", - "label": "setup", - "signature": [ - "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, - ") => { config: Readonly<{} & { includeElasticMapsService: boolean; proxyElasticMapsServiceInMaps: boolean; tilemap: Readonly<{ url?: string | undefined; } & { options: Readonly<{ default?: boolean | undefined; tileSize?: number | undefined; subdomains?: string[] | undefined; errorTileUrl?: string | undefined; tms?: boolean | undefined; reuseTiles?: boolean | undefined; bounds?: number[] | undefined; } & { attribution: string; minZoom: number; maxZoom: number; }>; }>; regionmap: Readonly<{} & { includeElasticMapsService: boolean; layers: Readonly<{} & { name: string; format: Readonly<{} & { type: string; }>; fields: Readonly<{} & { description: string; name: string; }>[]; meta: Readonly<{} & { feature_collection_path: string; }>; url: string; attribution: string; }>[]; }>; manifestServiceUrl: string; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { bright: string; desaturated: string; dark: string; }>; }>; }" - ], - "description": [], - "children": [ - { - "type": "Object", - "label": "core", - "isRequired": true, - "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, - "" - ], - "description": [], - "source": { - "path": "src/plugins/maps_legacy/server/index.ts", - "lineNumber": 42 - } - } - ], - "tags": [], - "returnComment": [], - "source": { - "path": "src/plugins/maps_legacy/server/index.ts", - "lineNumber": 42 - } - }, - { - "id": "def-server.MapsLegacyPlugin.start", - "type": "Function", - "label": "start", - "signature": [ - "() => void" - ], - "description": [], - "children": [], - "tags": [], - "returnComment": [], - "source": { - "path": "src/plugins/maps_legacy/server/index.ts", - "lineNumber": 51 - } - } - ], - "source": { - "path": "src/plugins/maps_legacy/server/index.ts", - "lineNumber": 35 - }, - "initialIsOpen": false - } - ], - "functions": [], - "interfaces": [ - { - "id": "def-server.MapsLegacyPluginSetup", - "type": "Interface", - "label": "MapsLegacyPluginSetup", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.MapsLegacyPluginSetup.config", - "type": "Object", - "label": "config", - "description": [], - "source": { - "path": "src/plugins/maps_legacy/server/index.ts", - "lineNumber": 32 - }, - "signature": [ - "Readonly<{} & { includeElasticMapsService: boolean; proxyElasticMapsServiceInMaps: boolean; tilemap: Readonly<{ url?: string | undefined; } & { options: Readonly<{ default?: boolean | undefined; tileSize?: number | undefined; subdomains?: string[] | undefined; errorTileUrl?: string | undefined; tms?: boolean | undefined; reuseTiles?: boolean | undefined; bounds?: number[] | undefined; } & { attribution: string; minZoom: number; maxZoom: number; }>; }>; regionmap: Readonly<{} & { includeElasticMapsService: boolean; layers: Readonly<{} & { name: string; format: Readonly<{} & { type: string; }>; fields: Readonly<{} & { description: string; name: string; }>[]; meta: Readonly<{} & { feature_collection_path: string; }>; url: string; attribution: string; }>[]; }>; manifestServiceUrl: string; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { bright: string; desaturated: string; dark: string; }>; }>" - ] - } - ], - "source": { - "path": "src/plugins/maps_legacy/server/index.ts", - "lineNumber": 31 - }, - "initialIsOpen": false - } - ], - "enums": [], - "misc": [], - "objects": [] - }, - "common": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [ - { - "tags": [], - "id": "def-common.DEFAULT_EMS_FILE_API_URL", - "type": "string", - "label": "DEFAULT_EMS_FILE_API_URL", - "description": [], - "source": { - "path": "src/plugins/maps_legacy/common/ems_defaults.ts", - "lineNumber": 10 - }, - "signature": [ - "\"https://vector.maps.elastic.co\"" - ], - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-common.DEFAULT_EMS_FONT_LIBRARY_URL", - "type": "string", - "label": "DEFAULT_EMS_FONT_LIBRARY_URL", - "description": [], - "source": { - "path": "src/plugins/maps_legacy/common/ems_defaults.ts", - "lineNumber": 13 - }, - "signature": [ - "\"https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf\"" - ], - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-common.DEFAULT_EMS_LANDING_PAGE_URL", - "type": "string", - "label": "DEFAULT_EMS_LANDING_PAGE_URL", - "description": [], - "source": { - "path": "src/plugins/maps_legacy/common/ems_defaults.ts", - "lineNumber": 12 - }, - "signature": [ - "\"https://maps.elastic.co/v7.12\"" - ], - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-common.DEFAULT_EMS_TILE_API_URL", - "type": "string", - "label": "DEFAULT_EMS_TILE_API_URL", - "description": [], - "source": { - "path": "src/plugins/maps_legacy/common/ems_defaults.ts", - "lineNumber": 11 - }, - "signature": [ - "\"https://tiles.maps.elastic.co\"" - ], - "initialIsOpen": false - } - ], - "objects": [] - } -} \ No newline at end of file diff --git a/api_docs/maps_legacy.mdx b/api_docs/maps_legacy.mdx deleted file mode 100644 index ade3ebfd39b055..00000000000000 --- a/api_docs/maps_legacy.mdx +++ /dev/null @@ -1,26 +0,0 @@ ---- -id: kibMapsLegacyPluginApi -slug: /kibana-dev-docs/mapsLegacyPluginApi -title: mapsLegacy -image: https://source.unsplash.com/400x175/?github -summary: API docs for the mapsLegacy plugin -date: 2020-11-16 -tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsLegacy'] -warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. ---- - -import mapsLegacyObj from './maps_legacy.json'; - -## Server - -### Classes - - -### Interfaces - - -## Common - -### Consts, variables and types - - diff --git a/api_docs/ml.json b/api_docs/ml.json index efa66ac9c9e8f7..fa9bd613195a67 100644 --- a/api_docs/ml.json +++ b/api_docs/ml.json @@ -786,7 +786,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/capabilities.ts", - "lineNumber": 147 + "lineNumber": 151 }, "signature": [ { @@ -806,7 +806,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/capabilities.ts", - "lineNumber": 148 + "lineNumber": 152 } }, { @@ -817,7 +817,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/capabilities.ts", - "lineNumber": 149 + "lineNumber": 153 } }, { @@ -828,13 +828,13 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/capabilities.ts", - "lineNumber": 150 + "lineNumber": 154 } } ], "source": { "path": "x-pack/plugins/ml/common/types/capabilities.ts", - "lineNumber": 146 + "lineNumber": 150 }, "initialIsOpen": false }, @@ -1178,7 +1178,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/public/application/components/data_grid/types.ts", - "lineNumber": 78 + "lineNumber": 83 }, "signature": [ { @@ -1193,7 +1193,7 @@ ], "source": { "path": "x-pack/plugins/ml/public/application/components/data_grid/types.ts", - "lineNumber": 52 + "lineNumber": 57 }, "initialIsOpen": false } @@ -1266,7 +1266,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/public/application/components/data_grid/types.ts", - "lineNumber": 48 + "lineNumber": 53 }, "signature": [ "Dictionary<{ order: 'asc' | 'desc'; }>" @@ -1297,10 +1297,12 @@ "description": [], "source": { "path": "x-pack/plugins/ml/public/application/components/data_grid/types.ts", - "lineNumber": 38 + "lineNumber": 43 }, "signature": [ - "(__0: { rowIndex: number; columnId: string; setCellProps: any; }) => any" + "(__0: { rowIndex: number; columnId: string; setCellProps: (props: ", + "CommonProps", + " & React.HTMLAttributes) => void; }) => any" ], "initialIsOpen": false } @@ -3455,7 +3457,14 @@ "lineNumber": 28 }, "signature": [ - "IndicesOptions | undefined" + { + "pluginId": "ml", + "scope": "common", + "docId": "kibMlPluginApi", + "section": "def-common.IndicesOptions", + "text": "IndicesOptions" + }, + " | undefined" ] } ], @@ -3805,6 +3814,76 @@ }, "initialIsOpen": false }, + { + "id": "def-server.IndicesOptions", + "type": "Interface", + "label": "IndicesOptions", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-server.IndicesOptions.expand_wildcards", + "type": "CompoundType", + "label": "expand_wildcards", + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", + "lineNumber": 49 + }, + "signature": [ + "\"all\" | \"none\" | \"hidden\" | \"open\" | \"closed\" | undefined" + ] + }, + { + "tags": [], + "id": "def-server.IndicesOptions.ignore_unavailable", + "type": "CompoundType", + "label": "ignore_unavailable", + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", + "lineNumber": 50 + }, + "signature": [ + "boolean | undefined" + ] + }, + { + "tags": [], + "id": "def-server.IndicesOptions.allow_no_indices", + "type": "CompoundType", + "label": "allow_no_indices", + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", + "lineNumber": 51 + }, + "signature": [ + "boolean | undefined" + ] + }, + { + "tags": [], + "id": "def-server.IndicesOptions.ignore_throttled", + "type": "CompoundType", + "label": "ignore_throttled", + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", + "lineNumber": 52 + }, + "signature": [ + "boolean | undefined" + ] + } + ], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", + "lineNumber": 48 + }, + "initialIsOpen": false + }, { "id": "def-server.Influencer", "type": "Interface", @@ -5424,7 +5503,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/server/plugin.ts", - "lineNumber": 64 + "lineNumber": 63 }, "signature": [ "JobServiceProvider", @@ -5448,7 +5527,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/server/plugin.ts", - "lineNumber": 65 + "lineNumber": 64 }, "signature": [ "void" @@ -5498,11 +5577,17 @@ "type": "Function", "children": [ { - "type": "Any", + "type": "CompoundType", "label": "error", - "isRequired": true, + "isRequired": false, "signature": [ - "any" + { + "pluginId": "ml", + "scope": "common", + "docId": "kibMlPluginApi", + "section": "def-common.ErrorType", + "text": "ErrorType" + } ], "description": [], "source": { @@ -5512,7 +5597,9 @@ } ], "signature": [ - "(error: any) => string" + "(error: ", + "ErrorType", + ") => string" ], "description": [], "label": "extractErrorMessage", diff --git a/api_docs/monitoring.json b/api_docs/monitoring.json index b24cdd89e424f6..d8b8b60495f8f9 100644 --- a/api_docs/monitoring.json +++ b/api_docs/monitoring.json @@ -27,7 +27,7 @@ "description": [], "source": { "path": "x-pack/plugins/monitoring/server/types.ts", - "lineNumber": 93 + "lineNumber": 94 }, "signature": [ "() => any" @@ -41,7 +41,7 @@ "description": [], "source": { "path": "x-pack/plugins/monitoring/server/types.ts", - "lineNumber": 94 + "lineNumber": 95 }, "signature": [ "() => void" @@ -55,10 +55,18 @@ "description": [], "source": { "path": "x-pack/plugins/monitoring/server/types.ts", - "lineNumber": 95 + "lineNumber": 96 }, "signature": [ - "() => void" + "(esClient: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ") => void" ] }, { @@ -69,7 +77,7 @@ "description": [], "source": { "path": "x-pack/plugins/monitoring/server/types.ts", - "lineNumber": 96 + "lineNumber": 97 }, "signature": [ "() => void" @@ -78,7 +86,7 @@ ], "source": { "path": "x-pack/plugins/monitoring/server/types.ts", - "lineNumber": 92 + "lineNumber": 93 }, "initialIsOpen": false } @@ -134,7 +142,7 @@ "description": [], "source": { "path": "x-pack/plugins/monitoring/server/types.ts", - "lineNumber": 100 + "lineNumber": 101 }, "signature": [ "() => any" @@ -143,7 +151,7 @@ ], "source": { "path": "x-pack/plugins/monitoring/server/types.ts", - "lineNumber": 99 + "lineNumber": 100 }, "lifecycle": "setup", "initialIsOpen": true diff --git a/api_docs/observability.json b/api_docs/observability.json index 81032f62ee3177..a3d1bc950cb537 100644 --- a/api_docs/observability.json +++ b/api_docs/observability.json @@ -51,6 +51,45 @@ }, "initialIsOpen": false }, + { + "id": "def-public.FieldValueSuggestions", + "type": "Function", + "label": "FieldValueSuggestions", + "signature": [ + "(props: ", + "FieldValueSuggestionsProps", + ") => JSX.Element" + ], + "description": [], + "children": [ + { + "type": "Object", + "label": "props", + "isRequired": true, + "signature": [ + { + "pluginId": "observability", + "scope": "public", + "docId": "kibObservabilityPluginApi", + "section": "def-public.FieldValueSuggestionsProps", + "text": "FieldValueSuggestionsProps" + } + ], + "description": [], + "source": { + "path": "x-pack/plugins/observability/public/components/shared/index.tsx", + "lineNumber": 34 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "x-pack/plugins/observability/public/components/shared/index.tsx", + "lineNumber": 34 + }, + "initialIsOpen": false + }, { "id": "def-public.getApmTraceUrl", "type": "Function", @@ -142,7 +181,7 @@ "description": [], "source": { "path": "x-pack/plugins/observability/public/components/shared/index.tsx", - "lineNumber": 13 + "lineNumber": 14 } } ], @@ -150,7 +189,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/observability/public/components/shared/index.tsx", - "lineNumber": 13 + "lineNumber": 14 }, "initialIsOpen": false }, @@ -181,7 +220,7 @@ "description": [], "source": { "path": "x-pack/plugins/observability/public/components/shared/index.tsx", - "lineNumber": 23 + "lineNumber": 24 } } ], @@ -189,7 +228,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/observability/public/components/shared/index.tsx", - "lineNumber": 23 + "lineNumber": 24 }, "initialIsOpen": false }, @@ -249,7 +288,7 @@ "signature": [ "({ children, ...props }: { children?: React.ReactNode; } & ", "CommonProps", - " & Pick, \"children\" | \"onClick\" | \"onChange\" | \"id\" | \"title\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\"> & { bordered?: boolean | undefined; flush?: boolean | undefined; gutterSize?: \"m\" | \"s\" | \"none\" | undefined; listItems?: ", + " & Pick, \"children\" | \"onClick\" | \"onChange\" | \"onKeyDown\" | \"title\" | \"id\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\"> & { bordered?: boolean | undefined; flush?: boolean | undefined; gutterSize?: \"m\" | \"s\" | \"none\" | undefined; listItems?: ", "EuiListGroupItemProps", "[] | undefined; color?: \"text\" | \"primary\" | \"inherit\" | \"ghost\" | \"subdued\" | undefined; size?: \"m\" | \"s\" | \"l\" | \"xs\" | undefined; maxWidth?: string | number | boolean | undefined; showToolTips?: boolean | undefined; wrapText?: boolean | undefined; ariaLabelledby?: string | undefined; }) => JSX.Element" ], @@ -262,7 +301,7 @@ "signature": [ "{ children?: React.ReactNode; } & ", "CommonProps", - " & Pick, \"children\" | \"onClick\" | \"onChange\" | \"id\" | \"title\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\"> & { bordered?: boolean | undefined; flush?: boolean | undefined; gutterSize?: \"m\" | \"s\" | \"none\" | undefined; listItems?: ", + " & Pick, \"children\" | \"onClick\" | \"onChange\" | \"onKeyDown\" | \"title\" | \"id\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\"> & { bordered?: boolean | undefined; flush?: boolean | undefined; gutterSize?: \"m\" | \"s\" | \"none\" | undefined; listItems?: ", "EuiListGroupItemProps", "[] | undefined; color?: \"text\" | \"primary\" | \"inherit\" | \"ghost\" | \"subdued\" | undefined; size?: \"m\" | \"s\" | \"l\" | \"xs\" | undefined; maxWidth?: string | number | boolean | undefined; showToolTips?: boolean | undefined; wrapText?: boolean | undefined; ariaLabelledby?: string | undefined; }" ], @@ -1864,9 +1903,9 @@ "DisambiguateSet", "<(", "DisambiguateSet", - ", Pick, \"children\" | \"type\" | \"onClick\" | \"onChange\" | \"color\" | \"id\" | \"title\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"media\" | \"target\" | \"download\" | \"ping\" | \"hrefLang\" | \"rel\" | \"referrerPolicy\">> & Pick, \"children\" | \"type\" | \"onClick\" | \"onChange\" | \"color\" | \"id\" | \"title\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"media\" | \"target\" | \"download\" | \"ping\" | \"hrefLang\" | \"rel\" | \"referrerPolicy\">) | (", + ", Pick, \"children\" | \"type\" | \"onClick\" | \"onChange\" | \"color\" | \"onKeyDown\" | \"title\" | \"id\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"media\" | \"target\" | \"download\" | \"ping\" | \"hrefLang\" | \"rel\" | \"referrerPolicy\">> & Pick, \"children\" | \"type\" | \"onClick\" | \"onChange\" | \"color\" | \"onKeyDown\" | \"title\" | \"id\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"media\" | \"target\" | \"download\" | \"ping\" | \"hrefLang\" | \"rel\" | \"referrerPolicy\">) | (", "DisambiguateSet", - ", \"children\" | \"type\" | \"onClick\" | \"onChange\" | \"color\" | \"id\" | \"title\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"media\" | \"target\" | \"download\" | \"ping\" | \"hrefLang\" | \"rel\" | \"referrerPolicy\">, React.ButtonHTMLAttributes> & React.ButtonHTMLAttributes), React.HTMLAttributes> & React.HTMLAttributes) | (", + ", \"children\" | \"type\" | \"onClick\" | \"onChange\" | \"color\" | \"onKeyDown\" | \"title\" | \"id\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"media\" | \"target\" | \"download\" | \"ping\" | \"hrefLang\" | \"rel\" | \"referrerPolicy\">, React.ButtonHTMLAttributes> & React.ButtonHTMLAttributes), React.HTMLAttributes> & React.HTMLAttributes) | (", "DisambiguateSet" ], "initialIsOpen": false diff --git a/api_docs/presentation_util.json b/api_docs/presentation_util.json index c0bff9d0447ec6..55204d129800e3 100644 --- a/api_docs/presentation_util.json +++ b/api_docs/presentation_util.json @@ -154,7 +154,7 @@ "section": "def-public.OnSaveProps", "text": "OnSaveProps" }, - " & { dashboardId: string | null; }) => void" + " & { dashboardId: string | null; addToLibrary: boolean; }) => void" ] }, { diff --git a/api_docs/reporting.json b/api_docs/reporting.json index 44050591f71cb8..29d0d485452da4 100644 --- a/api_docs/reporting.json +++ b/api_docs/reporting.json @@ -1823,7 +1823,7 @@ "description": [], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 30 + "lineNumber": 31 } } ], @@ -1831,7 +1831,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 30 + "lineNumber": 31 } }, { @@ -1876,7 +1876,7 @@ "description": [], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 36 + "lineNumber": 37 } }, { @@ -1895,7 +1895,7 @@ "description": [], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 36 + "lineNumber": 37 } } ], @@ -1903,7 +1903,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 36 + "lineNumber": 37 } }, { @@ -1947,7 +1947,7 @@ "description": [], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 106 + "lineNumber": 86 } }, { @@ -1966,7 +1966,7 @@ "description": [], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 106 + "lineNumber": 86 } } ], @@ -1974,13 +1974,13 @@ "returnComment": [], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 106 + "lineNumber": 86 } } ], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 24 + "lineNumber": 25 }, "initialIsOpen": false } diff --git a/api_docs/saved_objects.json b/api_docs/saved_objects.json index a3bc4b059c712f..0842bd216383ca 100644 --- a/api_docs/saved_objects.json +++ b/api_docs/saved_objects.json @@ -777,7 +777,7 @@ "section": "def-public.SavedObject", "text": "SavedObject" }, - ", \"id\" | \"title\" | \"getDisplayName\" | \"lastSavedTitle\" | \"copyOnSave\" | \"getEsType\">, isTitleDuplicateConfirmed: boolean, onTitleDuplicate: (() => void) | undefined, services: Pick<", + ", \"title\" | \"id\" | \"getDisplayName\" | \"lastSavedTitle\" | \"copyOnSave\" | \"getEsType\">, isTitleDuplicateConfirmed: boolean, onTitleDuplicate: (() => void) | undefined, services: Pick<", "SavedObjectKibanaServices", ", \"savedObjectsClient\" | \"overlays\">) => Promise" ], @@ -798,7 +798,7 @@ "section": "def-public.SavedObject", "text": "SavedObject" }, - ", \"id\" | \"title\" | \"getDisplayName\" | \"lastSavedTitle\" | \"copyOnSave\" | \"getEsType\">" + ", \"title\" | \"id\" | \"getDisplayName\" | \"lastSavedTitle\" | \"copyOnSave\" | \"getEsType\">" ], "description": [], "source": { diff --git a/api_docs/saved_objects_management.json b/api_docs/saved_objects_management.json index 04164f2849a4b0..8cd47981d4558d 100644 --- a/api_docs/saved_objects_management.json +++ b/api_docs/saved_objects_management.json @@ -332,7 +332,7 @@ "section": "def-server.SavedObjectsImportFailure", "text": "SavedObjectsImportFailure" }, - ", \"type\" | \"id\" | \"title\" | \"meta\" | \"overwrite\">" + ", \"type\" | \"title\" | \"id\" | \"meta\" | \"overwrite\">" ] }, { @@ -837,7 +837,7 @@ "section": "def-common.SavedObjectsManagementRecord", "text": "SavedObjectsManagementRecord" }, - ">, \"children\" | \"headers\" | \"onClick\" | \"onChange\" | \"color\" | \"id\" | \"description\" | \"title\" | \"name\" | \"field\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"data-test-subj\" | \"width\" | \"render\" | \"align\" | \"abbr\" | \"footer\" | \"colSpan\" | \"rowSpan\" | \"scope\" | \"valign\" | \"dataType\" | \"isExpander\" | \"textOnly\" | \"truncateText\" | \"isMobileHeader\" | \"mobileOptions\" | \"hideForMobile\">" + ">, \"children\" | \"headers\" | \"onClick\" | \"onChange\" | \"color\" | \"onKeyDown\" | \"description\" | \"title\" | \"id\" | \"name\" | \"field\" | \"placeholder\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"data-test-subj\" | \"width\" | \"render\" | \"align\" | \"abbr\" | \"footer\" | \"colSpan\" | \"rowSpan\" | \"scope\" | \"valign\" | \"dataType\" | \"isExpander\" | \"textOnly\" | \"truncateText\" | \"isMobileHeader\" | \"mobileOptions\" | \"hideForMobile\">" ] } ], diff --git a/api_docs/saved_objects_tagging.json b/api_docs/saved_objects_tagging.json index 0372b39ba242de..5b09e25344537a 100644 --- a/api_docs/saved_objects_tagging.json +++ b/api_docs/saved_objects_tagging.json @@ -717,7 +717,7 @@ "lineNumber": 20 }, "signature": [ - "Partial>" + "Partial>" ] } ], diff --git a/api_docs/security_solution.json b/api_docs/security_solution.json index cbcd660749f2dd..ae208eb4facc74 100644 --- a/api_docs/security_solution.json +++ b/api_docs/security_solution.json @@ -362,7 +362,7 @@ "label": "config", "isRequired": true, "signature": [ - "Readonly<{} & { enabled: boolean; signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; endpointResultListDefaultFirstPageIndex: number; endpointResultListDefaultPageSize: number; alertResultListDefaultDateRange: Readonly<{} & { from: string; to: string; }>; packagerTaskInterval: string; validateArtifactDownloads: boolean; }>" + "Readonly<{} & { enabled: boolean; signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; enableExperimental: string[]; endpointResultListDefaultFirstPageIndex: number; endpointResultListDefaultPageSize: number; alertResultListDefaultDateRange: Readonly<{} & { from: string; to: string; }>; packagerTaskInterval: string; validateArtifactDownloads: boolean; }>" ], "description": [], "source": { @@ -451,7 +451,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 144 + "lineNumber": 145 } } ], @@ -459,7 +459,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 144 + "lineNumber": 145 } }, { @@ -536,7 +536,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 156 + "lineNumber": 157 } }, { @@ -555,7 +555,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 156 + "lineNumber": 157 } } ], @@ -563,7 +563,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 156 + "lineNumber": 157 } }, { @@ -607,7 +607,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 337 + "lineNumber": 338 } }, { @@ -626,7 +626,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 337 + "lineNumber": 338 } } ], @@ -634,7 +634,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 337 + "lineNumber": 338 } }, { @@ -650,13 +650,13 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 398 + "lineNumber": 404 } } ], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 128 + "lineNumber": 129 }, "initialIsOpen": false } @@ -795,18 +795,17 @@ "type": "Function", "children": [ { - "type": "Function", - "label": "callWithRequest", + "type": "CompoundType", + "label": "esClient", "isRequired": true, "signature": [ { - "pluginId": "security", - "scope": "common", - "docId": "kibSecurityPluginApi", - "section": "def-common.CallWithRequest", - "text": "CallWithRequest" - }, - "<{ path: string; method: \"PUT\"; body: unknown; }, boolean>" + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + } ], "description": [], "source": { @@ -829,9 +828,15 @@ } ], "signature": [ - "(callWithRequest: ", - "CallWithRequest", - "<{ path: string; method: \"PUT\"; body: unknown; }, boolean>, index: string) => Promise" + "(esClient: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", index: string) => Promise" ], "description": [], "label": "createBootstrapIndex", @@ -848,25 +853,22 @@ "type": "Function", "children": [ { - "type": "Function", - "label": "callWithRequest", + "type": "CompoundType", + "label": "esClient", "isRequired": true, "signature": [ { - "pluginId": "security", - "scope": "common", - "docId": "kibSecurityPluginApi", - "section": "def-common.CallWithRequest", - "text": "CallWithRequest" - }, - "<", - "IndicesDeleteParams", - ", Promise>" + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + } ], "description": [], "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts", - "lineNumber": 12 + "lineNumber": 11 } }, { @@ -879,7 +881,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts", - "lineNumber": 13 + "lineNumber": 12 } }, { @@ -892,22 +894,26 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts", - "lineNumber": 14 + "lineNumber": 13 } } ], "signature": [ - "(callWithRequest: ", - "CallWithRequest", - "<", - "IndicesDeleteParams", - ", Promise>, pattern: string, maxAttempts?: number) => Promise" + "(esClient: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", pattern: string, maxAttempts?: number) => Promise" ], "description": [], "label": "deleteAllIndex", "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts", - "lineNumber": 11 + "lineNumber": 10 }, "tags": [], "returnComment": [], @@ -918,18 +924,17 @@ "type": "Function", "children": [ { - "type": "Function", - "label": "callWithRequest", + "type": "CompoundType", + "label": "esClient", "isRequired": true, "signature": [ { - "pluginId": "security", - "scope": "common", - "docId": "kibSecurityPluginApi", - "section": "def-common.CallWithRequest", - "text": "CallWithRequest" - }, - "<{ path: string; method: \"DELETE\"; }, unknown>" + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + } ], "description": [], "source": { @@ -952,9 +957,15 @@ } ], "signature": [ - "(callWithRequest: ", - "CallWithRequest", - "<{ path: string; method: \"DELETE\"; }, unknown>, policy: string) => Promise" + "(esClient: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", policy: string) => Promise" ], "description": [], "label": "deletePolicy", @@ -971,25 +982,22 @@ "type": "Function", "children": [ { - "type": "Function", - "label": "callWithRequest", + "type": "CompoundType", + "label": "esClient", "isRequired": true, "signature": [ { - "pluginId": "security", - "scope": "common", - "docId": "kibSecurityPluginApi", - "section": "def-common.CallWithRequest", - "text": "CallWithRequest" - }, - "<", - "IndicesDeleteTemplateParams", - ", unknown>" + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + } ], "description": [], "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_template.ts", - "lineNumber": 12 + "lineNumber": 10 } }, { @@ -1002,22 +1010,26 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_template.ts", - "lineNumber": 13 + "lineNumber": 11 } } ], "signature": [ - "(callWithRequest: ", - "CallWithRequest", - "<", - "IndicesDeleteTemplateParams", - ", unknown>, name: string) => Promise" + "(esClient: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", name: string) => Promise" ], "description": [], "label": "deleteTemplate", "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_template.ts", - "lineNumber": 11 + "lineNumber": 9 }, "tags": [], "returnComment": [], @@ -1028,18 +1040,17 @@ "type": "Function", "children": [ { - "type": "Function", - "label": "callWithRequest", + "type": "CompoundType", + "label": "esClient", "isRequired": true, "signature": [ { - "pluginId": "security", - "scope": "common", - "docId": "kibSecurityPluginApi", - "section": "def-common.CallWithRequest", - "text": "CallWithRequest" - }, - "<{ index: string; size: number; terminate_after: number; allow_no_indices: boolean; }, { _shards: { total: number; }; }>" + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + } ], "description": [], "source": { @@ -1057,14 +1068,20 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/get_index_exists.ts", - "lineNumber": 15 + "lineNumber": 12 } } ], "signature": [ - "(callWithRequest: ", - "CallWithRequest", - "<{ index: string; size: number; terminate_after: number; allow_no_indices: boolean; }, { _shards: { total: number; }; }>, index: string) => Promise" + "(esClient: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", index: string) => Promise" ], "description": [], "label": "getIndexExists", @@ -1081,18 +1098,17 @@ "type": "Function", "children": [ { - "type": "Function", - "label": "callWithRequest", + "type": "CompoundType", + "label": "esClient", "isRequired": true, "signature": [ { - "pluginId": "security", - "scope": "common", - "docId": "kibSecurityPluginApi", - "section": "def-common.CallWithRequest", - "text": "CallWithRequest" - }, - "<{ path: string; method: \"GET\"; }, unknown>" + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + } ], "description": [], "source": { @@ -1115,9 +1131,15 @@ } ], "signature": [ - "(callWithRequest: ", - "CallWithRequest", - "<{ path: string; method: \"GET\"; }, unknown>, policy: string) => Promise" + "(esClient: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", policy: string) => Promise" ], "description": [], "label": "getPolicyExists", @@ -1134,25 +1156,22 @@ "type": "Function", "children": [ { - "type": "Function", - "label": "callWithRequest", + "type": "CompoundType", + "label": "esClient", "isRequired": true, "signature": [ { - "pluginId": "security", - "scope": "common", - "docId": "kibSecurityPluginApi", - "section": "def-common.CallWithRequest", - "text": "CallWithRequest" - }, - "<", - "IndicesExistsTemplateParams", - ", boolean>" + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + } ], "description": [], "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/get_template_exists.ts", - "lineNumber": 12 + "lineNumber": 11 } }, { @@ -1165,22 +1184,26 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/get_template_exists.ts", - "lineNumber": 13 + "lineNumber": 12 } } ], "signature": [ - "(callWithRequest: ", - "CallWithRequest", - "<", - "IndicesExistsTemplateParams", - ", boolean>, template: string) => Promise" + "(esClient: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", template: string) => Promise" ], "description": [], "label": "getTemplateExists", "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/get_template_exists.ts", - "lineNumber": 11 + "lineNumber": 10 }, "tags": [], "returnComment": [], @@ -1244,18 +1267,17 @@ "type": "Function", "children": [ { - "type": "Function", - "label": "callWithRequest", + "type": "CompoundType", + "label": "esClient", "isRequired": true, "signature": [ { - "pluginId": "security", - "scope": "common", - "docId": "kibSecurityPluginApi", - "section": "def-common.CallWithRequest", - "text": "CallWithRequest" - }, - "<{ path: string; method: \"PUT\"; body: unknown; }, unknown>" + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + } ], "description": [], "source": { @@ -1277,11 +1299,11 @@ } }, { - "type": "Unknown", + "type": "Object", "label": "body", "isRequired": true, "signature": [ - "unknown" + "Record" ], "description": [], "source": { @@ -1291,9 +1313,15 @@ } ], "signature": [ - "(callWithRequest: ", - "CallWithRequest", - "<{ path: string; method: \"PUT\"; body: unknown; }, unknown>, policy: string, body: unknown) => Promise" + "(esClient: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", policy: string, body: Record) => Promise" ], "description": [], "label": "setPolicy", @@ -1310,25 +1338,22 @@ "type": "Function", "children": [ { - "type": "Function", - "label": "callWithRequest", + "type": "CompoundType", + "label": "esClient", "isRequired": true, "signature": [ { - "pluginId": "security", - "scope": "common", - "docId": "kibSecurityPluginApi", - "section": "def-common.CallWithRequest", - "text": "CallWithRequest" - }, - "<", - "IndicesPutTemplateParams", - ", unknown>" + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + } ], "description": [], "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/set_template.ts", - "lineNumber": 12 + "lineNumber": 11 } }, { @@ -1341,35 +1366,39 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/set_template.ts", - "lineNumber": 13 + "lineNumber": 12 } }, { - "type": "Unknown", + "type": "Object", "label": "body", "isRequired": true, "signature": [ - "unknown" + "Record" ], "description": [], "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/set_template.ts", - "lineNumber": 14 + "lineNumber": 13 } } ], "signature": [ - "(callWithRequest: ", - "CallWithRequest", - "<", - "IndicesPutTemplateParams", - ", unknown>, name: string, body: unknown) => Promise" + "(esClient: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", name: string, body: Record) => Promise" ], "description": [], "label": "setTemplate", "source": { "path": "x-pack/plugins/security_solution/server/lib/detection_engine/index/set_template.ts", - "lineNumber": 11 + "lineNumber": 10 }, "tags": [], "returnComment": [], @@ -1463,10 +1492,10 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/config.ts", - "lineNumber": 43 + "lineNumber": 75 }, "signature": [ - "{ readonly enabled: boolean; readonly signalsIndex: string; readonly maxRuleImportExportSize: number; readonly maxRuleImportPayloadBytes: number; readonly maxTimelineImportExportSize: number; readonly maxTimelineImportPayloadBytes: number; readonly endpointResultListDefaultFirstPageIndex: number; readonly endpointResultListDefaultPageSize: number; readonly alertResultListDefaultDateRange: Readonly<{} & { from: string; to: string; }>; readonly packagerTaskInterval: string; readonly validateArtifactDownloads: boolean; }" + "{ readonly enabled: boolean; readonly signalsIndex: string; readonly maxRuleImportExportSize: number; readonly maxRuleImportPayloadBytes: number; readonly maxTimelineImportExportSize: number; readonly maxTimelineImportPayloadBytes: number; readonly enableExperimental: string[]; readonly endpointResultListDefaultFirstPageIndex: number; readonly endpointResultListDefaultPageSize: number; readonly alertResultListDefaultDateRange: Readonly<{} & { from: string; to: string; }>; readonly packagerTaskInterval: string; readonly validateArtifactDownloads: boolean; }" ], "initialIsOpen": false } @@ -1481,7 +1510,7 @@ "children": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 105 + "lineNumber": 106 }, "lifecycle": "setup", "initialIsOpen": true @@ -1495,7 +1524,7 @@ "children": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 107 + "lineNumber": 108 }, "lifecycle": "start", "initialIsOpen": true @@ -1725,162 +1754,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "id": "def-common.migratePackagePolicyToV7110", - "type": "Function", - "children": [ - { - "type": "CompoundType", - "label": "packagePolicyDoc", - "isRequired": true, - "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectUnsanitizedDoc", - "text": "SavedObjectUnsanitizedDoc" - }, - "<", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicy", - "text": "PackagePolicy" - }, - ">" - ], - "description": [], - "source": { - "path": "x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.ts", - "lineNumber": 13 - } - } - ], - "signature": [ - "(packagePolicyDoc: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectUnsanitizedDoc", - "text": "SavedObjectUnsanitizedDoc" - }, - "<", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicy", - "text": "PackagePolicy" - }, - ">) => ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectUnsanitizedDoc", - "text": "SavedObjectUnsanitizedDoc" - }, - "<", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicy", - "text": "PackagePolicy" - }, - ">" - ], - "description": [], - "label": "migratePackagePolicyToV7110", - "source": { - "path": "x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.ts", - "lineNumber": 12 - }, - "tags": [], - "returnComment": [], - "initialIsOpen": false - }, - { - "id": "def-common.migratePackagePolicyToV7120", - "type": "Function", - "children": [ - { - "type": "CompoundType", - "label": "packagePolicyDoc", - "isRequired": true, - "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectUnsanitizedDoc", - "text": "SavedObjectUnsanitizedDoc" - }, - "<", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicy", - "text": "PackagePolicy" - }, - ">" - ], - "description": [], - "source": { - "path": "x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts", - "lineNumber": 14 - } - } - ], - "signature": [ - "(packagePolicyDoc: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectUnsanitizedDoc", - "text": "SavedObjectUnsanitizedDoc" - }, - "<", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicy", - "text": "PackagePolicy" - }, - ">) => ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectUnsanitizedDoc", - "text": "SavedObjectUnsanitizedDoc" - }, - "<", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicy", - "text": "PackagePolicy" - }, - ">" - ], - "description": [], - "label": "migratePackagePolicyToV7120", - "source": { - "path": "x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts", - "lineNumber": 13 - }, - "tags": [], - "returnComment": [], - "initialIsOpen": false - }, { "id": "def-common.removeExternalLinkText", "type": "Function", diff --git a/api_docs/telemetry_collection_xpack.json b/api_docs/telemetry_collection_xpack.json index 6f651ecf212f40..cf1b1a5998553b 100644 --- a/api_docs/telemetry_collection_xpack.json +++ b/api_docs/telemetry_collection_xpack.json @@ -41,6 +41,17 @@ "lineNumber": 13 } }, + { + "tags": [], + "id": "def-server.ESLicense.hkey", + "type": "string", + "label": "hkey", + "description": [], + "source": { + "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", + "lineNumber": 14 + } + }, { "tags": [], "id": "def-server.ESLicense.type", @@ -49,7 +60,7 @@ "description": [], "source": { "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 14 + "lineNumber": 15 } }, { @@ -60,7 +71,7 @@ "description": [], "source": { "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 15 + "lineNumber": 16 } }, { @@ -71,7 +82,7 @@ "description": [], "source": { "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 16 + "lineNumber": 17 } }, { @@ -82,18 +93,18 @@ "description": [], "source": { "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 17 + "lineNumber": 18 } }, { "tags": [], - "id": "def-server.ESLicense.expirty_date_in_millis", + "id": "def-server.ESLicense.expiry_date_in_millis", "type": "number", - "label": "expirty_date_in_millis", + "label": "expiry_date_in_millis", "description": [], "source": { "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 18 + "lineNumber": 19 } }, { @@ -104,7 +115,7 @@ "description": [], "source": { "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 19 + "lineNumber": 20 } }, { @@ -115,7 +126,7 @@ "description": [], "source": { "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 20 + "lineNumber": 21 } }, { @@ -126,7 +137,7 @@ "description": [], "source": { "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 21 + "lineNumber": 22 } }, { @@ -137,7 +148,18 @@ "description": [], "source": { "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 22 + "lineNumber": 23 + } + }, + { + "tags": [], + "id": "def-server.ESLicense.max_resource_units", + "type": "number", + "label": "max_resource_units", + "description": [], + "source": { + "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", + "lineNumber": 24 } } ], diff --git a/api_docs/triggers_actions_ui.json b/api_docs/triggers_actions_ui.json index 147abd0831e9b5..245ead8a4d0e4c 100644 --- a/api_docs/triggers_actions_ui.json +++ b/api_docs/triggers_actions_ui.json @@ -746,8 +746,8 @@ "label": "http", "description": [], "source": { - "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts", - "lineNumber": 13 + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connector_types.ts", + "lineNumber": 29 }, "signature": [ { @@ -761,16 +761,16 @@ } ], "source": { - "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts", - "lineNumber": 13 + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connector_types.ts", + "lineNumber": 29 } } ], "tags": [], "returnComment": [], "source": { - "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts", - "lineNumber": 13 + "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connector_types.ts", + "lineNumber": 29 }, "initialIsOpen": false }, diff --git a/api_docs/vis_type_timeseries.json b/api_docs/vis_type_timeseries.json index 907ced500294ac..6f349cb3dfb936 100644 --- a/api_docs/vis_type_timeseries.json +++ b/api_docs/vis_type_timeseries.json @@ -37,7 +37,7 @@ { "pluginId": "data", "scope": "server", - "docId": "kibDataSearchPluginApi", + "docId": "kibDataPluginApi", "section": "def-server.DataRequestHandlerContext", "text": "DataRequestHandlerContext" }, diff --git a/api_docs/visualizations.json b/api_docs/visualizations.json index d66aa8b8cbe679..b85d424a97bdeb 100644 --- a/api_docs/visualizations.json +++ b/api_docs/visualizations.json @@ -514,7 +514,7 @@ "section": "def-common.VisParams", "text": "VisParams" }, - ">, \"type\" | \"id\" | \"description\" | \"title\" | \"params\" | \"uiState\"> & Pick<{ data: Partial<", + ">, \"type\" | \"description\" | \"title\" | \"id\" | \"params\" | \"uiState\"> & Pick<{ data: Partial<", { "pluginId": "visualizations", "scope": "public", @@ -530,7 +530,7 @@ "section": "def-public.SerializedVisData", "text": "SerializedVisData" }, - ">; }, never>, \"type\" | \"id\" | \"data\" | \"description\" | \"title\" | \"params\" | \"uiState\">) => Promise" + ">; }, never>, \"type\" | \"data\" | \"description\" | \"title\" | \"id\" | \"params\" | \"uiState\">) => Promise" ], "description": [], "children": [ @@ -555,7 +555,7 @@ "section": "def-common.VisParams", "text": "VisParams" }, - ">, \"type\" | \"id\" | \"description\" | \"title\" | \"params\" | \"uiState\"> & Pick<{ data: Partial<", + ">, \"type\" | \"description\" | \"title\" | \"id\" | \"params\" | \"uiState\"> & Pick<{ data: Partial<", { "pluginId": "visualizations", "scope": "public", @@ -571,7 +571,7 @@ "section": "def-public.SerializedVisData", "text": "SerializedVisData" }, - ">; }, never>, \"type\" | \"id\" | \"data\" | \"description\" | \"title\" | \"params\" | \"uiState\">" + ">; }, never>, \"type\" | \"data\" | \"description\" | \"title\" | \"id\" | \"params\" | \"uiState\">" ], "description": [], "source": { From d886979e3b5a841f0d839a22fdf02cbc4bae9ec8 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Tue, 23 Mar 2021 11:34:20 -0700 Subject: [PATCH 37/93] [Fleet] Remove `upgradePackage` and consolidate it with `installPackage`, optimize calls to create index patterns (#94490) * Add data plugin to server app context * First attempt at switching to indexPatternService for EPM index pattern creation & deletion, instead of interacting directly with index pattern SOs * Simplify bulk install package, remove upgradePackage() method in favor of consolidating with installPackage(), use installPackage() for bulk install instead * Update tests * Change cache logging of objects to trace level * Install index patterns as a post-package installation operation, for bulk package installation, install index pattern only if one or more packages are actually new installs, add debug logging * Allow getAsSavedObjectBody to return non-scripted fields when allowNoIndex is true * Allow `getFieldsForWildcard` to return fields saved on index pattern when allowNoIndices is true * Bring back passing custom ID for index pattern SO * Fix tests * Revert "Merge branch 'index-pattern/allow-no-index' into epm/missing-index-patterns" This reverts commit 8e712e9c0087fc7f9e2b3dc0c729a23f0265f243, reversing changes made to af0fb0eaa84301ffe8514b89acc1a45ca2c5f7cb. * Allow getAsSavedObjectBody to return non-scripted fields when allowNoIndex is true (cherry picked from commit 69b93da1807cc9a613be1ad75b37df6bcee8d676) * Update API docs * Run post-install ops for install by upload too * Remove allowedInstallTypes param * Adjust force install conditions * Revert "Update API docs" This reverts commit b9770fdc5645b6d331bbbb3f0029ae039fbb1871. * Revert "Allow getAsSavedObjectBody to return non-scripted fields when allowNoIndex is true" This reverts commit afc91ce32f4d7c418be8a85d796ac5f71d6028eb. * Go back to using SO client for index patterns :( * Stringify attributes again for SO client saving * Fix condition for reinstall same pkg version * Remove unused type * Adjust comment * Update snapshot Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/common/constants/epm.ts | 1 - .../fleet/common/types/rest_spec/epm.ts | 11 +- .../plugins/fleet/server/constants/index.ts | 1 - x-pack/plugins/fleet/server/mocks/index.ts | 2 + x-pack/plugins/fleet/server/plugin.ts | 4 + .../fleet/server/routes/epm/handlers.ts | 31 +-- .../fleet/server/services/app_context.ts | 10 + .../server/services/epm/archive/cache.ts | 18 +- .../__snapshots__/install.test.ts.snap | 218 ++++++---------- .../epm/kibana/index_pattern/install.test.ts | 45 +--- .../epm/kibana/index_pattern/install.ts | 122 ++++----- .../kibana/index_pattern/tests/test_data.ts | 20 +- .../services/epm/packages/_install_package.ts | 15 +- .../epm/packages/bulk_install_packages.ts | 88 ++++--- .../ensure_installed_default_packages.test.ts | 25 +- .../server/services/epm/packages/install.ts | 239 ++++++++++-------- .../server/services/epm/packages/remove.ts | 2 +- x-pack/plugins/fleet/server/types/index.tsx | 1 + .../apis/epm/bulk_upgrade.ts | 15 +- 19 files changed, 371 insertions(+), 497 deletions(-) diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index aa17b16b3763ce..faa1127cfe1dad 100644 --- a/x-pack/plugins/fleet/common/constants/epm.ts +++ b/x-pack/plugins/fleet/common/constants/epm.ts @@ -7,7 +7,6 @@ export const PACKAGES_SAVED_OBJECT_TYPE = 'epm-packages'; export const ASSETS_SAVED_OBJECT_TYPE = 'epm-packages-assets'; -export const INDEX_PATTERN_SAVED_OBJECT_TYPE = 'index-pattern'; export const MAX_TIME_COMPLETE_INSTALL = 60000; export const FLEET_SERVER_PACKAGE = 'fleet_server'; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts index 3a9c9a0cfae9f8..3c7a32265d20a6 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts @@ -82,12 +82,15 @@ export interface IBulkInstallPackageHTTPError { error: string | Error; } +export interface InstallResult { + assets: AssetReference[]; + status: 'installed' | 'already_installed'; +} + export interface BulkInstallPackageInfo { name: string; - newVersion: string; - // this will be null if no package was present before the upgrade (aka it was an install) - oldVersion: string | null; - assets: AssetReference[]; + version: string; + result: InstallResult; } export interface BulkInstallPackagesResponse { diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index fa6051fa7d35b7..23df18d5e377d2 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -43,7 +43,6 @@ export { OUTPUT_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, ASSETS_SAVED_OBJECT_TYPE, - INDEX_PATTERN_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, // Defaults diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index d4b6f007feb4d1..4bc2bea1e58b69 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -10,6 +10,7 @@ import { savedObjectsServiceMock, coreMock, } from '../../../../../src/core/server/mocks'; +import { dataPluginMock } from '../../../../../src/plugins/data/server/mocks'; import { licensingMock } from '../../../../plugins/licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; import { securityMock } from '../../../security/server/mocks'; @@ -23,6 +24,7 @@ export * from '../services/artifacts/mocks'; export const createAppContextStartContractMock = (): FleetAppContext => { return { elasticsearch: elasticsearchServiceMock.createStart(), + data: dataPluginMock.createStartContract(), encryptedSavedObjectsStart: encryptedSavedObjectsMock.createStart(), savedObjects: savedObjectsServiceMock.createStartContract(), security: securityMock.createStart(), diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index a62da8eb41a995..477e6c39599510 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -23,6 +23,7 @@ import type { import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; +import type { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server'; import type { LicensingPluginSetup, ILicense } from '../../licensing/server'; import type { EncryptedSavedObjectsPluginStart, @@ -100,12 +101,14 @@ export interface FleetSetupDeps { } export interface FleetStartDeps { + data: DataPluginStart; encryptedSavedObjects: EncryptedSavedObjectsPluginStart; security?: SecurityPluginStart; } export interface FleetAppContext { elasticsearch: ElasticsearchServiceStart; + data: DataPluginStart; encryptedSavedObjectsStart?: EncryptedSavedObjectsPluginStart; encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup; security?: SecurityPluginStart; @@ -293,6 +296,7 @@ export class FleetPlugin public async start(core: CoreStart, plugins: FleetStartDeps): Promise { await appContextService.start({ elasticsearch: core.elasticsearch, + data: plugins.data, encryptedSavedObjectsStart: plugins.encryptedSavedObjects, encryptedSavedObjectsSetup: this.encryptedSavedObjectsSetup, security: plugins.security, diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 293a3e9e28237d..3ac951f7987f83 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -40,12 +40,10 @@ import { getPackages, getFile, getPackageInfo, - handleInstallPackageFailure, isBulkInstallError, installPackage, removeInstallation, getLimitedPackages, - getInstallationObject, getInstallation, } from '../../services/epm/packages'; import type { BulkInstallResponse } from '../../services/epm/packages'; @@ -228,15 +226,7 @@ export const installPackageFromRegistryHandler: RequestHandler< const savedObjectsClient = context.core.savedObjects.client; const esClient = context.core.elasticsearch.client.asCurrentUser; const { pkgkey } = request.params; - - let pkgName: string | undefined; - let pkgVersion: string | undefined; - try { - const parsedPkgKey = splitPkgKey(pkgkey); - pkgName = parsedPkgKey.pkgName; - pkgVersion = parsedPkgKey.pkgVersion; - const res = await installPackage({ installSource: 'registry', savedObjectsClient, @@ -245,24 +235,11 @@ export const installPackageFromRegistryHandler: RequestHandler< force: request.body?.force, }); const body: InstallPackageResponse = { - response: res, + response: res.assets, }; return response.ok({ body }); } catch (e) { - const defaultResult = await defaultIngestErrorHandler({ error: e, response }); - if (pkgName && pkgVersion) { - const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName }); - await handleInstallPackageFailure({ - savedObjectsClient, - error: e, - pkgName, - pkgVersion, - installedPkg, - esClient, - }); - } - - return defaultResult; + return await defaultIngestErrorHandler({ error: e, response }); } }; @@ -291,7 +268,7 @@ export const bulkInstallPackagesFromRegistryHandler: RequestHandler< const bulkInstalledResponses = await bulkInstallPackages({ savedObjectsClient, esClient, - packagesToUpgrade: request.body.packages, + packagesToInstall: request.body.packages, }); const payload = bulkInstalledResponses.map(bulkInstallServiceResponseToHttpEntry); const body: BulkInstallPackagesResponse = { @@ -324,7 +301,7 @@ export const installPackageByUploadHandler: RequestHandler< contentType, }); const body: InstallPackageResponse = { - response: res, + response: res.assets, }; return response.ok({ body }); } catch (error) { diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index 21b519565e758d..954308a9808613 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -17,6 +17,7 @@ import type { Logger, } from 'src/core/server'; +import type { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; import type { EncryptedSavedObjectsClient, EncryptedSavedObjectsPluginSetup, @@ -29,6 +30,7 @@ import type { CloudSetup } from '../../../cloud/server'; class AppContextService { private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined; private encryptedSavedObjectsSetup: EncryptedSavedObjectsPluginSetup | undefined; + private data: DataPluginStart | undefined; private esClient: ElasticsearchClient | undefined; private security: SecurityPluginStart | undefined; private config$?: Observable; @@ -43,6 +45,7 @@ class AppContextService { private externalCallbacks: ExternalCallbacksStorage = new Map(); public async start(appContext: FleetAppContext) { + this.data = appContext.data; this.esClient = appContext.elasticsearch.client.asInternalUser; this.encryptedSavedObjects = appContext.encryptedSavedObjectsStart?.getClient(); this.encryptedSavedObjectsSetup = appContext.encryptedSavedObjectsSetup; @@ -67,6 +70,13 @@ class AppContextService { this.externalCallbacks.clear(); } + public getData() { + if (!this.data) { + throw new Error('Data start service not set.'); + } + return this.data; + } + public getEncryptedSavedObjects() { if (!this.encryptedSavedObjects) { throw new Error('Encrypted saved object start service not set.'); diff --git a/x-pack/plugins/fleet/server/services/epm/archive/cache.ts b/x-pack/plugins/fleet/server/services/epm/archive/cache.ts index 6958f76c17b701..7f479dc5d6b63b 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/cache.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/cache.ts @@ -28,13 +28,9 @@ export const getArchiveFilelist = (keyArgs: SharedKey) => archiveFilelistCache.get(sharedKey(keyArgs)); export const setArchiveFilelist = (keyArgs: SharedKey, paths: string[]) => { - appContextService - .getLogger() - .debug( - `setting file list to the cache for ${keyArgs.name}-${keyArgs.version}:\n${JSON.stringify( - paths - )}` - ); + const logger = appContextService.getLogger(); + logger.debug(`setting file list to the cache for ${keyArgs.name}-${keyArgs.version}`); + logger.trace(JSON.stringify(paths)); return archiveFilelistCache.set(sharedKey(keyArgs), paths); }; @@ -63,12 +59,10 @@ export const setPackageInfo = ({ version, packageInfo, }: SharedKey & { packageInfo: ArchivePackage | RegistryPackage }) => { + const logger = appContextService.getLogger(); const key = sharedKey({ name, version }); - appContextService - .getLogger() - .debug( - `setting package info to the cache for ${name}-${version}:\n${JSON.stringify(packageInfo)}` - ); + logger.debug(`setting package info to the cache for ${name}-${version}`); + logger.trace(JSON.stringify(packageInfo)); return packageInfoCache.set(key, packageInfo); }; diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap index 862e82589b9bc9..da870290329a88 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap @@ -40,7 +40,7 @@ exports[`creating index patterns from yaml fields createIndexPattern function cr { "title": "logs-*", "timeFieldName": "@timestamp", - "fields": "[{\\"name\\":\\"coredns.id\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.allParams\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"number\\"},{\\"name\\":\\"coredns.query.length\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"number\\"},{\\"name\\":\\"coredns.query.size\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"number\\"},{\\"name\\":\\"coredns.query.class\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.query.name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.query.type\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.response.code\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.response.flags\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.response.size\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"number\\"},{\\"name\\":\\"coredns.dnssec_ok\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"boolean\\"},{\\"name\\":\\"@timestamp\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"date\\"},{\\"name\\":\\"labels\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"message\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":false,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"tags\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"agent.ephemeral_id\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"agent.id\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"agent.name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"agent.type\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"agent.version\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"as.number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"number\\"},{\\"name\\":\\"as.organization.name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"nginx.access.remote_ip_list\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.body_sent.bytes\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.method\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.url\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.http_version\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.response_code\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.referrer\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.agent\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.device\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.os\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.os_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.original\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.continent_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":false,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"nginx.access.geoip.country_iso_code\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.location\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.region_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.city_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.region_iso_code\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"source.geo.continent_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":false,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"country\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"country.keyword\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"country.text\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":false,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"}]", + "fields": "[{\\"name\\":\\"coredns.id\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.allParams\\",\\"type\\":\\"number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.query.length\\",\\"type\\":\\"number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.query.size\\",\\"type\\":\\"number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.query.class\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.query.name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.query.type\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.response.code\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.response.flags\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.response.size\\",\\"type\\":\\"number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.dnssec_ok\\",\\"type\\":\\"boolean\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"@timestamp\\",\\"type\\":\\"date\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"labels\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"message\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":false,\\"readFromDocValues\\":true},{\\"name\\":\\"tags\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"agent.ephemeral_id\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"agent.id\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"agent.name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"agent.type\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"agent.version\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"as.number\\",\\"type\\":\\"number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"as.organization.name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.remote_ip_list\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.body_sent.bytes\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.method\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.url\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.http_version\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.response_code\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.referrer\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.agent\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.device\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.os\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.os_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.original\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.continent_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":false,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.country_iso_code\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.location\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.region_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.city_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.region_iso_code\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"source.geo.continent_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":false,\\"readFromDocValues\\":true},{\\"name\\":\\"country\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"country.keyword\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"country.text\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":false,\\"readFromDocValues\\":true}]", "fieldFormatMap": "{\\"coredns.allParams\\":{\\"id\\":\\"bytes\\",\\"params\\":{\\"pattern\\":\\"patternValQueryWeight\\",\\"inputFormat\\":\\"inputFormatVal,\\",\\"outputFormat\\":\\"outputFormalVal,\\",\\"outputPrecision\\":\\"3,\\",\\"labelTemplate\\":\\"labelTemplateVal,\\",\\"urlTemplate\\":\\"urlTemplateVal,\\"}},\\"coredns.query.length\\":{\\"params\\":{\\"pattern\\":\\"patternValQueryLength\\"}},\\"coredns.query.size\\":{\\"id\\":\\"bytes\\",\\"params\\":{\\"pattern\\":\\"patternValQuerySize\\"}},\\"coredns.response.size\\":{\\"id\\":\\"bytes\\"}}", "allowNoIndex": true } @@ -51,535 +51,463 @@ exports[`creating index patterns from yaml fields createIndexPatternFields funct "indexPatternFields": [ { "name": "coredns.id", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "coredns.allParams", + "type": "number", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "number" + "readFromDocValues": true }, { "name": "coredns.query.length", + "type": "number", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "number" + "readFromDocValues": true }, { "name": "coredns.query.size", + "type": "number", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "number" + "readFromDocValues": true }, { "name": "coredns.query.class", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "coredns.query.name", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "coredns.query.type", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "coredns.response.code", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "coredns.response.flags", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "coredns.response.size", + "type": "number", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "number" + "readFromDocValues": true }, { "name": "coredns.dnssec_ok", + "type": "boolean", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "boolean" + "readFromDocValues": true }, { "name": "@timestamp", + "type": "date", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "date" + "readFromDocValues": true }, { "name": "labels", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "message", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": false, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "tags", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "agent.ephemeral_id", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "agent.id", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "agent.name", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "agent.type", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "agent.version", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "as.number", + "type": "number", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "number" + "readFromDocValues": true }, { "name": "as.organization.name", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "nginx.access.remote_ip_list", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.body_sent.bytes", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.user_name", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.method", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.url", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.http_version", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.response_code", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.referrer", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.agent", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.user_agent.device", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.user_agent.name", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.user_agent.os", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.user_agent.os_name", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.user_agent.original", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.geoip.continent_name", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": false, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "nginx.access.geoip.country_iso_code", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.geoip.location", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.geoip.region_name", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.geoip.city_name", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "nginx.access.geoip.region_iso_code", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, "readFromDocValues": true }, { "name": "source.geo.continent_name", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": false, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "country", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "country.keyword", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": true, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true }, { "name": "country.text", + "type": "string", "count": 0, "scripted": false, "indexed": true, - "analyzed": false, "searchable": true, "aggregatable": false, - "doc_values": true, - "readFromDocValues": true, - "type": "string" + "readFromDocValues": true } ], "fieldFormatMap": { diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.test.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.test.ts index a0eaed04d649e8..dfdaa66a7b43e7 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.test.ts @@ -11,6 +11,8 @@ import { readFileSync } from 'fs'; import glob from 'glob'; import { safeLoad } from 'js-yaml'; +import type { FieldSpec } from 'src/plugins/data/common'; + import type { Fields, Field } from '../../fields/field'; import { @@ -22,7 +24,6 @@ import { createIndexPatternFields, createIndexPattern, } from './install'; -import type { IndexPatternField } from './install'; import { dupeFields } from './tests/test_data'; // Add our own serialiser to just do JSON.stringify @@ -93,7 +94,6 @@ describe('creating index patterns from yaml fields', () => { const mergedField = deduped.find((field) => field.name === '1'); expect(mergedField?.searchable).toBe(true); expect(mergedField?.aggregatable).toBe(true); - expect(mergedField?.analyzed).toBe(true); expect(mergedField?.count).toBe(0); }); }); @@ -156,7 +156,7 @@ describe('creating index patterns from yaml fields', () => { { fields: [{ name: 'testField', type: 'short' }], expect: 'number' }, { fields: [{ name: 'testField', type: 'byte' }], expect: 'number' }, { fields: [{ name: 'testField', type: 'keyword' }], expect: 'string' }, - { fields: [{ name: 'testField', type: 'invalidType' }], expect: undefined }, + { fields: [{ name: 'testField', type: 'invalidType' }], expect: 'string' }, { fields: [{ name: 'testField', type: 'text' }], expect: 'string' }, { fields: [{ name: 'testField', type: 'date' }], expect: 'date' }, { fields: [{ name: 'testField', type: 'geo_point' }], expect: 'geo_point' }, @@ -171,7 +171,7 @@ describe('creating index patterns from yaml fields', () => { test('transformField changes values based on other values', () => { interface TestWithAttr extends Test { - attr: keyof IndexPatternField; + attr: keyof FieldSpec; } const tests: TestWithAttr[] = [ @@ -211,43 +211,6 @@ describe('creating index patterns from yaml fields', () => { attr: 'aggregatable', }, - // analyzed - { fields: [{ name }], expect: false, attr: 'analyzed' }, - { fields: [{ name, analyzed: true }], expect: true, attr: 'analyzed' }, - { fields: [{ name, analyzed: false }], expect: false, attr: 'analyzed' }, - { fields: [{ name, type: 'binary' }], expect: false, attr: 'analyzed' }, - { fields: [{ name, analyzed: true, type: 'binary' }], expect: false, attr: 'analyzed' }, - { - fields: [{ name, analyzed: true, type: 'object', enabled: false }], - expect: false, - attr: 'analyzed', - }, - - // doc_values always set to true except for meta fields - { fields: [{ name }], expect: true, attr: 'doc_values' }, - { fields: [{ name, doc_values: true }], expect: true, attr: 'doc_values' }, - { fields: [{ name, doc_values: false }], expect: false, attr: 'doc_values' }, - { fields: [{ name, script: 'doc[]' }], expect: false, attr: 'doc_values' }, - { fields: [{ name, doc_values: true, script: 'doc[]' }], expect: false, attr: 'doc_values' }, - { fields: [{ name, type: 'binary' }], expect: false, attr: 'doc_values' }, - { fields: [{ name, doc_values: true, type: 'binary' }], expect: true, attr: 'doc_values' }, - { - fields: [{ name, doc_values: true, type: 'object', enabled: false }], - expect: false, - attr: 'doc_values', - }, - - // enabled - only applies to objects (and only if set) - { fields: [{ name, type: 'binary', enabled: false }], expect: undefined, attr: 'enabled' }, - { fields: [{ name, type: 'binary', enabled: true }], expect: undefined, attr: 'enabled' }, - { fields: [{ name, type: 'object', enabled: true }], expect: true, attr: 'enabled' }, - { fields: [{ name, type: 'object', enabled: false }], expect: false, attr: 'enabled' }, - { - fields: [{ name, type: 'object', enabled: false }], - expect: false, - attr: 'doc_values', - }, - // indexed { fields: [{ name, type: 'binary' }], expect: false, attr: 'indexed' }, { diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts index 3ec9d2c65a6da2..61d6f6ed8818a4 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { SavedObjectsClientContract } from 'src/core/server'; +import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; +import type { FieldSpec } from 'src/plugins/data/common'; -import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../../constants'; import { loadFieldsFromYaml } from '../../fields/field'; import type { Fields, Field } from '../../fields/field'; import { dataTypes, installationStatuses } from '../../../../../common/constants'; @@ -17,6 +17,7 @@ import type { InstallSource, ValueOf, } from '../../../../../common/types'; +import { appContextService } from '../../../../services'; import type { RegistryPackage, DataType } from '../../../../types'; import { getInstallation, getPackageFromSource, getPackageSavedObjects } from '../../packages/get'; @@ -59,29 +60,29 @@ const typeMap: TypeMap = { constant_keyword: 'string', }; -export interface IndexPatternField { - name: string; - type?: string; - count: number; - scripted: boolean; - indexed: boolean; - analyzed: boolean; - searchable: boolean; - aggregatable: boolean; - doc_values: boolean; - enabled?: boolean; - script?: string; - lang?: string; - readFromDocValues: boolean; -} +const INDEX_PATTERN_SAVED_OBJECT_TYPE = 'index-pattern'; export const indexPatternTypes = Object.values(dataTypes); -export async function installIndexPatterns( - savedObjectsClient: SavedObjectsClientContract, - pkgName?: string, - pkgVersion?: string, - installSource?: InstallSource -) { +export async function installIndexPatterns({ + savedObjectsClient, + pkgName, + pkgVersion, + installSource, +}: { + savedObjectsClient: SavedObjectsClientContract; + esClient: ElasticsearchClient; + pkgName?: string; + pkgVersion?: string; + installSource?: InstallSource; +}) { + const logger = appContextService.getLogger(); + + logger.debug( + `kicking off installation of index patterns for ${ + pkgName && pkgVersion ? `${pkgName}-${pkgVersion}` : 'no specific package' + }` + ); + // get all user installed packages const installedPackagesRes = await getPackageSavedObjects(savedObjectsClient); const installedPackagesSavedObjects = installedPackagesRes.saved_objects.filter( @@ -115,6 +116,7 @@ export async function installIndexPatterns( }); } } + // get each package's registry info const packagesToFetchPromise = packagesToFetch.map((pkg) => getPackageFromSource({ @@ -125,27 +127,33 @@ export async function installIndexPatterns( }) ); const packages = await Promise.all(packagesToFetchPromise); + // for each index pattern type, create an index pattern - indexPatternTypes.forEach(async (indexPatternType) => { - // if this is an update because a package is being uninstalled (no pkgkey argument passed) and no other packages are installed, remove the index pattern - if (!pkgName && installedPackagesSavedObjects.length === 0) { - try { - await savedObjectsClient.delete(INDEX_PATTERN_SAVED_OBJECT_TYPE, `${indexPatternType}-*`); - } catch (err) { - // index pattern was probably deleted by the user already + return Promise.all( + indexPatternTypes.map(async (indexPatternType) => { + // if this is an update because a package is being uninstalled (no pkgkey argument passed) and no other packages are installed, remove the index pattern + if (!pkgName && installedPackagesSavedObjects.length === 0) { + try { + logger.debug(`deleting index pattern ${indexPatternType}-*`); + await savedObjectsClient.delete(INDEX_PATTERN_SAVED_OBJECT_TYPE, `${indexPatternType}-*`); + } catch (err) { + // index pattern was probably deleted by the user already + } + return; } - return; - } - const packagesWithInfo = packages.map((pkg) => pkg.packageInfo); - // get all data stream fields from all installed packages - const fields = await getAllDataStreamFieldsByType(packagesWithInfo, indexPatternType); - const kibanaIndexPattern = createIndexPattern(indexPatternType, fields); - // create or overwrite the index pattern - await savedObjectsClient.create(INDEX_PATTERN_SAVED_OBJECT_TYPE, kibanaIndexPattern, { - id: `${indexPatternType}-*`, - overwrite: true, - }); - }); + const packagesWithInfo = packages.map((pkg) => pkg.packageInfo); + // get all data stream fields from all installed packages + const fields = await getAllDataStreamFieldsByType(packagesWithInfo, indexPatternType); + const kibanaIndexPattern = createIndexPattern(indexPatternType, fields); + + // create or overwrite the index pattern + await savedObjectsClient.create(INDEX_PATTERN_SAVED_OBJECT_TYPE, kibanaIndexPattern, { + id: `${indexPatternType}-*`, + overwrite: true, + }); + logger.debug(`created index pattern ${kibanaIndexPattern.title}`); + }) + ); } // loops through all given packages and returns an array @@ -189,7 +197,7 @@ export const createIndexPattern = (indexPatternType: string, fields: Fields) => // and also returns the fieldFormatMap export const createIndexPatternFields = ( fields: Fields -): { indexPatternFields: IndexPatternField[]; fieldFormatMap: FieldFormatMap } => { +): { indexPatternFields: FieldSpec[]; fieldFormatMap: FieldFormatMap } => { const flattenedFields = flattenFields(fields); const fieldFormatMap = createFieldFormatMap(flattenedFields); const transformedFields = flattenedFields.map(transformField); @@ -198,8 +206,8 @@ export const createIndexPatternFields = ( }; // merges fields that are duplicates with the existing taking precedence -export const dedupeFields = (fields: IndexPatternField[]) => { - const uniqueObj = fields.reduce<{ [name: string]: IndexPatternField }>((acc, field) => { +export const dedupeFields = (fields: FieldSpec[]) => { + const uniqueObj = fields.reduce<{ [name: string]: FieldSpec }>((acc, field) => { // if field doesn't exist yet if (!acc[field.name]) { acc[field.name] = field; @@ -251,34 +259,20 @@ const getField = (fields: Fields, pathNames: string[]): Field | undefined => { return undefined; }; -export const transformField = (field: Field, i: number, fields: Fields): IndexPatternField => { - const newField: IndexPatternField = { +export const transformField = (field: Field, i: number, fields: Fields): FieldSpec => { + const newField: FieldSpec = { name: field.name, + type: field.type && typeMap[field.type] ? typeMap[field.type] : 'string', count: field.count ?? 0, scripted: false, indexed: field.index ?? true, - analyzed: field.analyzed ?? false, searchable: field.searchable ?? true, aggregatable: field.aggregatable ?? true, - doc_values: field.doc_values ?? true, readFromDocValues: field.doc_values ?? true, }; - // if type exists, check if it exists in the map - if (field.type) { - // if no type match type is not set (undefined) - if (typeMap[field.type]) { - newField.type = typeMap[field.type]; - } - // if type isn't set, default to string - } else { - newField.type = 'string'; - } - if (newField.type === 'binary') { newField.aggregatable = false; - newField.analyzed = false; - newField.doc_values = field.doc_values ?? false; newField.readFromDocValues = field.doc_values ?? false; newField.indexed = false; newField.searchable = false; @@ -286,11 +280,8 @@ export const transformField = (field: Field, i: number, fields: Fields): IndexPa if (field.type === 'object' && field.hasOwnProperty('enabled')) { const enabled = field.enabled ?? true; - newField.enabled = enabled; if (!enabled) { newField.aggregatable = false; - newField.analyzed = false; - newField.doc_values = false; newField.readFromDocValues = false; newField.indexed = false; newField.searchable = false; @@ -305,7 +296,6 @@ export const transformField = (field: Field, i: number, fields: Fields): IndexPa newField.scripted = true; newField.script = field.script; newField.lang = 'painless'; - newField.doc_values = false; newField.readFromDocValues = false; } diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/test_data.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/test_data.ts index 49a32de7037763..d9bcf366510813 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/test_data.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/test_data.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { IndexPatternField } from '../install'; +import type { FieldSpec } from 'src/plugins/data/common'; -export const dupeFields: IndexPatternField[] = [ +export const dupeFields: FieldSpec[] = [ { name: '1', type: 'integer', @@ -15,10 +15,8 @@ export const dupeFields: IndexPatternField[] = [ aggregatable: true, count: 0, indexed: true, - doc_values: true, readFromDocValues: true, scripted: false, - analyzed: true, }, { name: '2', @@ -27,10 +25,8 @@ export const dupeFields: IndexPatternField[] = [ aggregatable: true, count: 0, indexed: true, - doc_values: true, readFromDocValues: true, scripted: false, - analyzed: true, }, { name: '3', @@ -39,10 +35,8 @@ export const dupeFields: IndexPatternField[] = [ aggregatable: true, count: 0, indexed: true, - doc_values: true, readFromDocValues: true, scripted: false, - analyzed: true, }, { name: '1', @@ -51,10 +45,8 @@ export const dupeFields: IndexPatternField[] = [ aggregatable: false, count: 2, indexed: true, - doc_values: true, readFromDocValues: true, scripted: false, - analyzed: true, }, { name: '1.1', @@ -63,10 +55,8 @@ export const dupeFields: IndexPatternField[] = [ aggregatable: false, count: 0, indexed: true, - doc_values: true, readFromDocValues: true, scripted: false, - analyzed: true, }, { name: '4', @@ -75,10 +65,8 @@ export const dupeFields: IndexPatternField[] = [ aggregatable: false, count: 0, indexed: true, - doc_values: true, readFromDocValues: true, scripted: false, - analyzed: true, }, { name: '2', @@ -87,10 +75,8 @@ export const dupeFields: IndexPatternField[] = [ aggregatable: false, count: 0, indexed: true, - doc_values: true, readFromDocValues: true, scripted: false, - analyzed: true, }, { name: '1', @@ -99,9 +85,7 @@ export const dupeFields: IndexPatternField[] = [ aggregatable: false, count: 1, indexed: true, - doc_values: true, readFromDocValues: true, scripted: false, - analyzed: false, }, ]; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index ca9d4906096363..65d71ac5fdc179 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -12,7 +12,6 @@ import type { InstallablePackage, InstallSource, PackageAssetReference } from '. import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; import { ElasticsearchAssetType } from '../../../types'; import type { AssetReference, Installation, InstallType } from '../../../types'; -import { installIndexPatterns } from '../kibana/index_pattern/install'; import { installTemplates } from '../elasticsearch/template/install'; import { installPipelines, deletePreviousPipelines } from '../elasticsearch/ingest_pipeline/'; import { installILMPolicy } from '../elasticsearch/ilm/install'; @@ -81,11 +80,11 @@ export async function _installPackage({ }); } - // kick off `installIndexPatterns` & `installKibanaAssets` as early as possible because they're the longest running operations + // kick off `installKibanaAssets` as early as possible because they're the longest running operations // we don't `await` here because we don't want to delay starting the many other `install*` functions // however, without an `await` or a `.catch` we haven't defined how to handle a promise rejection // we define it many lines and potentially seconds of wall clock time later in - // `await Promise.all([installKibanaAssetsPromise, installIndexPatternPromise]);` + // `await installKibanaAssetsPromise` // if we encounter an error before we there, we'll have an "unhandled rejection" which causes its own problems // the program will log something like this _and exit/crash_ // Unhandled Promise rejection detected: @@ -96,13 +95,6 @@ export async function _installPackage({ // add a `.catch` to prevent the "unhandled rejection" case // in that `.catch`, set something that indicates a failure // check for that failure later and act accordingly (throw, ignore, return) - let installIndexPatternError; - const installIndexPatternPromise = installIndexPatterns( - savedObjectsClient, - pkgName, - pkgVersion, - installSource - ).catch((reason) => (installIndexPatternError = reason)); const kibanaAssets = await getKibanaAssets(paths); if (installedPkg) await deleteKibanaSavedObjectsAssets( @@ -184,9 +176,8 @@ export async function _installPackage({ })); // make sure the assets are installed (or didn't error) - if (installIndexPatternError) throw installIndexPatternError; if (installKibanaAssetsError) throw installKibanaAssetsError; - await Promise.all([installKibanaAssetsPromise, installIndexPatternPromise]); + await installKibanaAssetsPromise; const packageAssetResults = await saveArchiveEntries({ savedObjectsClient, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts index b726c60fc1e5ea..7323263d4a70f5 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts @@ -7,58 +7,72 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; +import { appContextService } from '../../app_context'; import * as Registry from '../registry'; +import { installIndexPatterns } from '../kibana/index_pattern/install'; -import { getInstallationObject } from './index'; -import { upgradePackage } from './install'; +import { installPackage } from './install'; import type { BulkInstallResponse, IBulkInstallPackageError } from './install'; interface BulkInstallPackagesParams { savedObjectsClient: SavedObjectsClientContract; - packagesToUpgrade: string[]; + packagesToInstall: string[]; esClient: ElasticsearchClient; } export async function bulkInstallPackages({ savedObjectsClient, - packagesToUpgrade, + packagesToInstall, esClient, }: BulkInstallPackagesParams): Promise { - const installedAndLatestPromises = packagesToUpgrade.map((pkgToUpgrade) => - Promise.all([ - getInstallationObject({ savedObjectsClient, pkgName: pkgToUpgrade }), - Registry.fetchFindLatestPackage(pkgToUpgrade), - ]) + const logger = appContextService.getLogger(); + const installSource = 'registry'; + const latestPackagesResults = await Promise.allSettled( + packagesToInstall.map((packageName) => Registry.fetchFindLatestPackage(packageName)) ); - const installedAndLatestResults = await Promise.allSettled(installedAndLatestPromises); - const installResponsePromises = installedAndLatestResults.map(async (result, index) => { - const pkgToUpgrade = packagesToUpgrade[index]; - if (result.status === 'fulfilled') { - const [installedPkg, latestPkg] = result.value; - return upgradePackage({ - savedObjectsClient, - esClient, - installedPkg, - latestPkg, - pkgToUpgrade, - }); - } else { - return { name: pkgToUpgrade, error: result.reason }; - } - }); - const installResults = await Promise.allSettled(installResponsePromises); - const installResponses = installResults.map((result, index) => { - const pkgToUpgrade = packagesToUpgrade[index]; - if (result.status === 'fulfilled') { - return result.value; - } else { - return { name: pkgToUpgrade, error: result.reason }; - } - }); - return installResponses; + logger.debug(`kicking off bulk install of ${packagesToInstall.join(', ')} from registry`); + const installResults = await Promise.allSettled( + latestPackagesResults.map(async (result, index) => { + const packageName = packagesToInstall[index]; + if (result.status === 'fulfilled') { + const latestPackage = result.value; + return { + name: packageName, + version: latestPackage.version, + result: await installPackage({ + savedObjectsClient, + esClient, + pkgkey: Registry.pkgToPkgKey(latestPackage), + installSource, + skipPostInstall: true, + }), + }; + } + return { name: packageName, error: result.reason }; + }) + ); + + // only install index patterns if we completed install for any package-version for the + // first time, aka fresh installs or upgrades + if ( + installResults.find( + (result) => result.status === 'fulfilled' && result.value.result?.status === 'installed' + ) + ) { + await installIndexPatterns({ savedObjectsClient, esClient, installSource }); + } + + return installResults.map((result, index) => { + const packageName = packagesToInstall[index]; + return result.status === 'fulfilled' + ? result.value + : { name: packageName, error: result.reason }; + }); } -export function isBulkInstallError(test: any): test is IBulkInstallPackageError { - return 'error' in test && test.error instanceof Error; +export function isBulkInstallError( + installResponse: any +): installResponse is IBulkInstallPackageError { + return 'error' in installResponse && installResponse.error instanceof Error; } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts index e01af7b64c0e3b..fa2ea9e2209edf 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts @@ -77,9 +77,8 @@ describe('ensureInstalledDefaultPackages', () => { return [ { name: mockInstallation.attributes.name, - assets: [], - newVersion: '', - oldVersion: '', + result: { assets: [], status: 'installed' }, + version: '', statusCode: 200, }, ]; @@ -96,16 +95,14 @@ describe('ensureInstalledDefaultPackages', () => { return [ { name: 'success one', - assets: [], - newVersion: '', - oldVersion: '', + result: { assets: [], status: 'installed' }, + version: '', statusCode: 200, }, { name: 'success two', - assets: [], - newVersion: '', - oldVersion: '', + result: { assets: [], status: 'installed' }, + version: '', statusCode: 200, }, { @@ -114,9 +111,8 @@ describe('ensureInstalledDefaultPackages', () => { }, { name: 'success three', - assets: [], - newVersion: '', - oldVersion: '', + result: { assets: [], status: 'installed' }, + version: '', statusCode: 200, }, { @@ -138,9 +134,8 @@ describe('ensureInstalledDefaultPackages', () => { return [ { name: 'undefined package', - assets: [], - newVersion: '', - oldVersion: '', + result: { assets: [], status: 'installed' }, + version: '', statusCode: 200, }, ]; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 011e77fb7e89b9..1a6b41976af98a 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -5,10 +5,8 @@ * 2.0. */ -import semverGt from 'semver/functions/gt'; import semverLt from 'semver/functions/lt'; import type Boom from '@hapi/boom'; -import type { UnwrapPromise } from '@kbn/utility-types'; import type { ElasticsearchClient, SavedObject, SavedObjectsClientContract } from 'src/core/server'; import { generateESIndexPatterns } from '../elasticsearch/template/template'; @@ -28,12 +26,14 @@ import type { AssetType, EsAssetReference, InstallType, + InstallResult, } from '../../../types'; import { appContextService } from '../../app_context'; import * as Registry from '../registry'; import { setPackageInfo, parseAndVerifyArchiveEntries, unpackBufferToCache } from '../archive'; import { toAssetReference } from '../kibana/assets/install'; import type { ArchiveAsset } from '../kibana/assets/install'; +import { installIndexPatterns } from '../kibana/index_pattern/install'; import { isRequiredPackage, @@ -63,7 +63,7 @@ export async function installLatestPackage(options: { savedObjectsClient, pkgkey, esClient, - }); + }).then(({ assets }) => assets); } catch (err) { throw err; } @@ -76,7 +76,7 @@ export async function ensureInstalledDefaultPackages( const installations = []; const bulkResponse = await bulkInstallPackages({ savedObjectsClient, - packagesToUpgrade: Object.values(defaultPackages), + packagesToInstall: Object.values(defaultPackages), esClient, }); @@ -164,6 +164,7 @@ export async function handleInstallPackageFailure({ savedObjectsClient, pkgkey: prevVersion, esClient, + force: true, }); } } catch (e) { @@ -177,64 +178,6 @@ export interface IBulkInstallPackageError { } export type BulkInstallResponse = BulkInstallPackageInfo | IBulkInstallPackageError; -interface UpgradePackageParams { - savedObjectsClient: SavedObjectsClientContract; - esClient: ElasticsearchClient; - installedPkg: UnwrapPromise>; - latestPkg: UnwrapPromise>; - pkgToUpgrade: string; -} -export async function upgradePackage({ - savedObjectsClient, - esClient, - installedPkg, - latestPkg, - pkgToUpgrade, -}: UpgradePackageParams): Promise { - if (!installedPkg || semverGt(latestPkg.version, installedPkg.attributes.version)) { - const pkgkey = Registry.pkgToPkgKey({ - name: latestPkg.name, - version: latestPkg.version, - }); - - try { - const assets = await installPackage({ - installSource: 'registry', - savedObjectsClient, - pkgkey, - esClient, - }); - return { - name: pkgToUpgrade, - newVersion: latestPkg.version, - oldVersion: installedPkg?.attributes.version ?? null, - assets, - }; - } catch (installFailed) { - await handleInstallPackageFailure({ - savedObjectsClient, - error: installFailed, - pkgName: latestPkg.name, - pkgVersion: latestPkg.version, - installedPkg, - esClient, - }); - return { name: pkgToUpgrade, error: installFailed }; - } - } else { - // package was already at the latest version - return { - name: pkgToUpgrade, - newVersion: latestPkg.version, - oldVersion: latestPkg.version, - assets: [ - ...installedPkg.attributes.installed_es, - ...installedPkg.attributes.installed_kibana, - ], - }; - } -} - interface InstallRegistryPackageParams { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; @@ -247,32 +190,81 @@ async function installPackageFromRegistry({ pkgkey, esClient, force = false, -}: InstallRegistryPackageParams): Promise { +}: InstallRegistryPackageParams): Promise { + const logger = appContextService.getLogger(); // TODO: change epm API to /packageName/version so we don't need to do this const { pkgName, pkgVersion } = Registry.splitPkgKey(pkgkey); + // get the currently installed package const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName }); const installType = getInstallType({ pkgVersion, installedPkg }); + + // get latest package version + const latestPackage = await Registry.fetchFindLatestPackage(pkgName); + // let the user install if using the force flag or needing to reinstall or install a previous version due to failed update const installOutOfDateVersionOk = - installType === 'reinstall' || installType === 'reupdate' || installType === 'rollback'; + force || ['reinstall', 'reupdate', 'rollback'].includes(installType); + + // if the requested version is the same as installed version, check if we allow it based on + // current installed package status and force flag, if we don't allow it, + // just return the asset references from the existing installation + if ( + installedPkg?.attributes.version === pkgVersion && + installedPkg?.attributes.install_status === 'installed' + ) { + if (!force) { + logger.debug(`${pkgkey} is already installed, skipping installation`); + return { + assets: [ + ...installedPkg.attributes.installed_es, + ...installedPkg.attributes.installed_kibana, + ], + status: 'already_installed', + }; + } + } - const latestPackage = await Registry.fetchFindLatestPackage(pkgName); - if (semverLt(pkgVersion, latestPackage.version) && !force && !installOutOfDateVersionOk) { - throw new PackageOutdatedError(`${pkgkey} is out-of-date and cannot be installed or updated`); + // if the requested version is out-of-date of the latest package version, check if we allow it + // if we don't allow it, return an error + if (semverLt(pkgVersion, latestPackage.version)) { + if (!installOutOfDateVersionOk) { + throw new PackageOutdatedError(`${pkgkey} is out-of-date and cannot be installed or updated`); + } + logger.debug( + `${pkgkey} is out-of-date, installing anyway due to ${ + force ? 'force flag' : `install type ${installType}` + }` + ); } + // get package info const { paths, packageInfo } = await Registry.getRegistryPackage(pkgName, pkgVersion); - return _installPackage({ - savedObjectsClient, - esClient, - installedPkg, - paths, - packageInfo, - installType, - installSource: 'registry', - }); + // try installing the package, if there was an error, call error handler and rethrow + try { + return _installPackage({ + savedObjectsClient, + esClient, + installedPkg, + paths, + packageInfo, + installType, + installSource: 'registry', + }).then((assets) => { + return { assets, status: 'installed' }; + }); + } catch (e) { + await handleInstallPackageFailure({ + savedObjectsClient, + error: e, + pkgName, + pkgVersion, + installedPkg, + esClient, + }); + throw e; + } } interface InstallUploadedArchiveParams { @@ -282,16 +274,12 @@ interface InstallUploadedArchiveParams { contentType: string; } -export type InstallPackageParams = - | ({ installSource: Extract } & InstallRegistryPackageParams) - | ({ installSource: Extract } & InstallUploadedArchiveParams); - async function installPackageByUpload({ savedObjectsClient, esClient, archiveBuffer, contentType, -}: InstallUploadedArchiveParams): Promise { +}: InstallUploadedArchiveParams): Promise { const { packageInfo } = await parseAndVerifyArchiveEntries(archiveBuffer, contentType); const installedPkg = await getInstallationObject({ @@ -329,32 +317,68 @@ async function installPackageByUpload({ packageInfo, installType, installSource, + }).then((assets) => { + return { assets, status: 'installed' }; }); } +export type InstallPackageParams = { + skipPostInstall?: boolean; +} & ( + | ({ installSource: Extract } & InstallRegistryPackageParams) + | ({ installSource: Extract } & InstallUploadedArchiveParams) +); + export async function installPackage(args: InstallPackageParams) { if (!('installSource' in args)) { throw new Error('installSource is required'); } + const logger = appContextService.getLogger(); + const { savedObjectsClient, esClient, skipPostInstall = false, installSource } = args; if (args.installSource === 'registry') { - const { savedObjectsClient, pkgkey, esClient, force } = args; - - return installPackageFromRegistry({ + const { pkgkey, force } = args; + const { pkgName, pkgVersion } = Registry.splitPkgKey(pkgkey); + logger.debug(`kicking off install of ${pkgkey} from registry`); + const response = installPackageFromRegistry({ savedObjectsClient, pkgkey, esClient, force, + }).then(async (installResult) => { + if (skipPostInstall) { + return installResult; + } + logger.debug(`install of ${pkgkey} finished, running post-install`); + return installIndexPatterns({ + savedObjectsClient, + esClient, + pkgName, + pkgVersion, + installSource, + }).then(() => installResult); }); + return response; } else if (args.installSource === 'upload') { - const { savedObjectsClient, esClient, archiveBuffer, contentType } = args; - - return installPackageByUpload({ + const { archiveBuffer, contentType } = args; + logger.debug(`kicking off install of uploaded package`); + const response = installPackageByUpload({ savedObjectsClient, esClient, archiveBuffer, contentType, + }).then(async (installResult) => { + if (skipPostInstall) { + return installResult; + } + logger.debug(`install of uploaded package finished, running post-install`); + return installIndexPatterns({ + savedObjectsClient, + esClient, + installSource, + }).then(() => installResult); }); + return response; } // @ts-expect-error s/b impossibe b/c `never` by this point, but just in case throw new Error(`Unknown installSource: ${args.installSource}`); @@ -451,26 +475,27 @@ export async function ensurePackagesCompletedInstall( searchFields: ['install_status'], search: 'installing', }); - const installingPromises = installingPackages.saved_objects.reduce< - Array> - >((acc, pkg) => { - const startDate = pkg.attributes.install_started_at; - const nowDate = new Date().toISOString(); - const elapsedTime = Date.parse(nowDate) - Date.parse(startDate); - const pkgkey = `${pkg.attributes.name}-${pkg.attributes.install_version}`; - // reinstall package - if (elapsedTime > MAX_TIME_COMPLETE_INSTALL) { - acc.push( - installPackage({ - installSource: 'registry', - savedObjectsClient, - pkgkey, - esClient, - }) - ); - } - return acc; - }, []); + const installingPromises = installingPackages.saved_objects.reduce>>( + (acc, pkg) => { + const startDate = pkg.attributes.install_started_at; + const nowDate = new Date().toISOString(); + const elapsedTime = Date.parse(nowDate) - Date.parse(startDate); + const pkgkey = `${pkg.attributes.name}-${pkg.attributes.install_version}`; + // reinstall package + if (elapsedTime > MAX_TIME_COMPLETE_INSTALL) { + acc.push( + installPackage({ + installSource: 'registry', + savedObjectsClient, + pkgkey, + esClient, + }) + ); + } + return acc; + }, + [] + ); await Promise.all(installingPromises); return installingPackages; } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts index da407c1d4cfa03..21e4e31be2bd0d 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts @@ -63,7 +63,7 @@ export async function removeInstallation(options: { // recreate or delete index patterns when a package is uninstalled // this must be done after deleting the saved object for the current package otherwise it will retrieve the package // from the registry again and reinstall the index patterns - await installIndexPatterns(savedObjectsClient); + await installIndexPatterns({ savedObjectsClient, esClient }); // remove the package archive and its contents from the cache so that a reinstall fetches // a fresh copy from the registry diff --git a/x-pack/plugins/fleet/server/types/index.tsx b/x-pack/plugins/fleet/server/types/index.tsx index 885809d7673239..c25b047c0e1ad6 100644 --- a/x-pack/plugins/fleet/server/types/index.tsx +++ b/x-pack/plugins/fleet/server/types/index.tsx @@ -72,6 +72,7 @@ export { SettingsSOAttributes, InstallType, InstallSource, + InstallResult, // Agent Request types PostAgentEnrollRequest, PostAgentCheckinRequest, diff --git a/x-pack/test/fleet_api_integration/apis/epm/bulk_upgrade.ts b/x-pack/test/fleet_api_integration/apis/epm/bulk_upgrade.ts index 546493a4c6f390..80d0f67f9e7fb1 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/bulk_upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/bulk_upgrade.ts @@ -51,8 +51,7 @@ export default function (providerContext: FtrProviderContext) { expect(body.response.length).equal(1); expect(body.response[0].name).equal('multiple_versions'); const entry = body.response[0] as BulkInstallPackageInfo; - expect(entry.oldVersion).equal('0.1.0'); - expect(entry.newVersion).equal('0.3.0'); + expect(entry.version).equal('0.3.0'); }); it('should return an error for packages that do not exist', async function () { const { body }: { body: BulkInstallPackagesResponse } = await supertest @@ -63,8 +62,7 @@ export default function (providerContext: FtrProviderContext) { expect(body.response.length).equal(2); expect(body.response[0].name).equal('multiple_versions'); const entry = body.response[0] as BulkInstallPackageInfo; - expect(entry.oldVersion).equal('0.1.0'); - expect(entry.newVersion).equal('0.3.0'); + expect(entry.version).equal('0.3.0'); const err = body.response[1] as IBulkInstallPackageHTTPError; expect(err.statusCode).equal(404); @@ -79,12 +77,10 @@ export default function (providerContext: FtrProviderContext) { expect(body.response.length).equal(2); expect(body.response[0].name).equal('multiple_versions'); let entry = body.response[0] as BulkInstallPackageInfo; - expect(entry.oldVersion).equal('0.1.0'); - expect(entry.newVersion).equal('0.3.0'); + expect(entry.version).equal('0.3.0'); entry = body.response[1] as BulkInstallPackageInfo; - expect(entry.oldVersion).equal(null); - expect(entry.newVersion).equal('0.1.0'); + expect(entry.version).equal('0.1.0'); expect(entry.name).equal('overrides'); }); }); @@ -103,8 +99,7 @@ export default function (providerContext: FtrProviderContext) { expect(body.response.length).equal(1); expect(body.response[0].name).equal('multiple_versions'); const entry = body.response[0] as BulkInstallPackageInfo; - expect(entry.oldVersion).equal(null); - expect(entry.newVersion).equal('0.3.0'); + expect(entry.version).equal('0.3.0'); }); }); }); From 685aa20ba6bd86493e2c7a53e2227a1e4aa23859 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 23 Mar 2021 20:45:26 +0200 Subject: [PATCH 38/93] Set initial palette for new series (#95177) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/application/components/lib/new_series_fn.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/new_series_fn.js b/src/plugins/vis_type_timeseries/public/application/components/lib/new_series_fn.js index 36e52fc21732f5..9064cd1afc3f4f 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/new_series_fn.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/new_series_fn.js @@ -17,6 +17,10 @@ export const newSeriesFn = (obj = {}) => { id: uuid.v1(), color: '#68BC00', split_mode: 'everything', + palette: { + type: 'palette', + name: 'default', + }, metrics: [newMetricAggFn()], separate_axis: 0, axis_position: 'right', From 29ee309dd880b273fa8810401f1f3d93699a5b55 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 23 Mar 2021 14:18:11 -0500 Subject: [PATCH 39/93] [App Search] Role mappings migration part 3 (#94763) * Remove validaition of ID property in route body The ID is inferred from the param in the URL. This was fixed in the logic file but the server route was never updated * Add RoleMappings component - ROLE_MAPPINGS_TITLE was moved to a shared constant in an earlier PR - Also removing redundant exports of interface * Add RoleMapping component - Also removing redundant export of interface from AppLogic * Add RoleMappingsRouter ROLE_MAPPINGS_TITLE was moved to a shared constant in an earlier PR # Conflicts: # x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/index.ts * Add route and update link in navigation * Remove unused translations * Change casing Co-authored-by: Constance * Change casing Co-authored-by: Constance * Change casing Co-authored-by: Constance * Add ability test * Refactor conditional constants * Refactor role type constants * Remove EuiPageContent * Refactor action mocks Co-authored-by: Constance Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../applications/app_search/app_logic.ts | 2 +- .../components/role_mappings/constants.ts | 172 +++++++++++- .../components/role_mappings/index.ts | 2 +- .../role_mappings/role_mapping.test.tsx | 106 ++++++++ .../components/role_mappings/role_mapping.tsx | 246 ++++++++++++++++++ .../role_mappings/role_mappings.test.tsx | 88 +++++++ .../role_mappings/role_mappings.tsx | 139 ++++++++++ .../role_mappings/role_mappings_logic.ts | 4 +- .../role_mappings_router.test.tsx | 26 ++ .../role_mappings/role_mappings_router.tsx | 29 +++ .../applications/app_search/index.test.tsx | 19 +- .../public/applications/app_search/index.tsx | 14 +- .../public/applications/app_search/types.ts | 2 +- .../app_search/utils/role/types.ts | 5 + .../routes/app_search/role_mappings.test.ts | 7 +- .../server/routes/app_search/role_mappings.ts | 5 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 18 files changed, 841 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_router.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_router.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts index 5f7dc683d93b42..44416b596e6ef9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts @@ -13,7 +13,7 @@ import { ConfiguredLimits, Account, Role } from './types'; import { getRoleAbilities } from './utils/role'; -export interface AppValues { +interface AppValues { ilmEnabled: boolean; configuredLimits: ConfiguredLimits; account: Account; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts index 6232ba0fb4668c..1fed750a86dc4f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts @@ -7,9 +7,32 @@ import { i18n } from '@kbn/i18n'; -export const ROLE_MAPPINGS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.roleMappings.title', - { defaultMessage: 'Role Mappings' } +import { AdvanceRoleType } from '../../types'; + +export const SAVE_ROLE_MAPPING = i18n.translate( + 'xpack.enterpriseSearch.appSearch.roleMapping.saveRoleMappingButtonLabel', + { defaultMessage: 'Save role mapping' } +); +export const UPDATE_ROLE_MAPPING = i18n.translate( + 'xpack.enterpriseSearch.appSearch.roleMapping.updateRoleMappingButtonLabel', + { defaultMessage: 'Update role mapping' } +); + +export const ADD_ROLE_MAPPING_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.roleMapping.newRoleMappingTitle', + { defaultMessage: 'Add role mapping' } +); +export const MANAGE_ROLE_MAPPING_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.roleMapping.manageRoleMappingTitle', + { defaultMessage: 'Manage role mapping' } +); + +export const EMPTY_ROLE_MAPPINGS_BODY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.roleMapping.emptyRoleMappingsBody', + { + defaultMessage: + 'All users who successfully authenticate will be assigned the Owner role and have access to all engines. Add a new role to override the default.', + } ); export const DELETE_ROLE_MAPPING_MESSAGE = i18n.translate( @@ -40,3 +63,146 @@ export const ROLE_MAPPING_UPDATED_MESSAGE = i18n.translate( defaultMessage: 'Role mapping successfully updated.', } ); + +export const ROLE_MAPPINGS_ENGINE_ACCESS_HEADING = i18n.translate( + 'xpack.enterpriseSearch.appSearch.roleMappingsEngineAccessHeading', + { + defaultMessage: 'Engine access', + } +); + +export const ROLE_MAPPINGS_RESET_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.appSearch.roleMappingsResetButton', + { + defaultMessage: 'Reset mappings', + } +); + +export const ROLE_MAPPINGS_RESET_CONFIRM_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.roleMappingsResetConfirmTitle', + { + defaultMessage: 'Are you sure you want to reset role mappings?', + } +); + +export const ROLE_MAPPINGS_RESET_CONFIRM_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.appSearch.roleMappingsResetConfirmButton', + { + defaultMessage: 'Reset role mappings', + } +); + +export const ROLE_MAPPINGS_RESET_CANCEL_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.appSearch.roleMappingsResetCancelButton', + { + defaultMessage: 'Cancel', + } +); + +export const DEV_ROLE_TYPE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.DEV_ROLE_TYPE_DESCRIPTION', + { + defaultMessage: 'Devs can manage all aspects of an engine.', + } +); + +export const EDITOR_ROLE_TYPE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.editorRoleTypeDescription', + { + defaultMessage: 'Editors can manage search settings.', + } +); + +export const ANALYST_ROLE_TYPE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.analystRoleTypeDescription', + { + defaultMessage: 'Analysts can only view documents, query tester, and analytics.', + } +); + +export const OWNER_ROLE_TYPE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.ownerRoleTypeDescription', + { + defaultMessage: + 'Owners can do anything. There can be many owners on the account, but there must be at least one owner at any time.', + } +); + +export const ADMIN_ROLE_TYPE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.adminRoleTypeDescription', + { + defaultMessage: 'Admins can do anything, except manage account settings.', + } +); + +export const ADVANCED_ROLE_SELECTORS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.advancedRoleSelectorsTitle', + { + defaultMessage: 'Full or limited engine access', + } +); + +export const ROLE_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.roleTitle', { + defaultMessage: 'Role', +}); + +export const FULL_ENGINE_ACCESS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.fullEngineAccessTitle', + { + defaultMessage: 'Full engine access', + } +); + +export const FULL_ENGINE_ACCESS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.fullEngineAccessDescription', + { + defaultMessage: 'Access to all current and future engines.', + } +); + +export const LIMITED_ENGINE_ACCESS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.limitedEngineAccessTitle', + { + defaultMessage: 'Limited engine access', + } +); + +export const LIMITED_ENGINE_ACCESS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.limitedEngineAccessDescription', + { + defaultMessage: 'Limit user access to specific engines:', + } +); + +export const ENGINE_ACCESS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engineAccessTitle', + { + defaultMessage: 'Engine access', + } +); + +export const ADVANCED_ROLE_TYPES = [ + { + type: 'dev', + description: DEV_ROLE_TYPE_DESCRIPTION, + }, + { + type: 'editor', + description: EDITOR_ROLE_TYPE_DESCRIPTION, + }, + { + type: 'analyst', + description: ANALYST_ROLE_TYPE_DESCRIPTION, + }, +] as AdvanceRoleType[]; + +export const STANDARD_ROLE_TYPES = [ + { + type: 'owner', + description: OWNER_ROLE_TYPE_DESCRIPTION, + }, + { + type: 'admin', + description: ADMIN_ROLE_TYPE_DESCRIPTION, + }, +] as AdvanceRoleType[]; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/index.ts index 91159ea9646ead..ce4b1de6e399d8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { ROLE_MAPPINGS_TITLE } from './constants'; +export { RoleMappingsRouter } from './role_mappings_router'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.test.tsx new file mode 100644 index 00000000000000..f50fc21d5ba58d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.test.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import '../../../__mocks__/shallow_useeffect.mock'; +import { DEFAULT_INITIAL_APP_DATA } from '../../../../../common/__mocks__'; +import { setMockActions, setMockValues } from '../../../__mocks__'; +import { engines } from '../../__mocks__/engines.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiCheckbox } from '@elastic/eui'; + +import { Loading } from '../../../shared/loading'; +import { + AttributeSelector, + DeleteMappingCallout, + RoleSelector, +} from '../../../shared/role_mapping'; +import { asRoleMapping } from '../../../shared/role_mapping/__mocks__/roles'; + +import { RoleMapping } from './role_mapping'; + +describe('RoleMapping', () => { + const mockRole = DEFAULT_INITIAL_APP_DATA.appSearch.role; + const actions = { + initializeRoleMappings: jest.fn(), + initializeRoleMapping: jest.fn(), + handleSaveMapping: jest.fn(), + handleEngineSelectionChange: jest.fn(), + handleAccessAllEnginesChange: jest.fn(), + handleAttributeValueChange: jest.fn(), + handleAttributeSelectorChange: jest.fn(), + handleDeleteMapping: jest.fn(), + handleRoleChange: jest.fn(), + handleAuthProviderChange: jest.fn(), + resetState: jest.fn(), + }; + + const mockValues = { + attributes: [], + elasticsearchRoles: [], + hasAdvancedRoles: true, + dataLoading: false, + roleType: 'admin', + roleMappings: [asRoleMapping], + attributeValue: '', + attributeName: 'username', + availableEngines: engines, + selectedEngines: new Set(), + accessAllEngines: false, + availableAuthProviders: [], + multipleAuthProvidersConfig: true, + selectedAuthProviders: [], + myRole: { + availableRoleTypes: mockRole.ability.availableRoleTypes, + }, + }; + + beforeEach(() => { + setMockActions(actions); + setMockValues(mockValues); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(AttributeSelector)).toHaveLength(1); + expect(wrapper.find(RoleSelector)).toHaveLength(5); + }); + + it('returns Loading when loading', () => { + setMockValues({ ...mockValues, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('renders DeleteMappingCallout for existing mapping', () => { + setMockValues({ ...mockValues, roleMapping: asRoleMapping }); + const wrapper = shallow(); + + expect(wrapper.find(DeleteMappingCallout)).toHaveLength(1); + }); + + it('hides DeleteMappingCallout for new mapping', () => { + const wrapper = shallow(); + + expect(wrapper.find(DeleteMappingCallout)).toHaveLength(0); + }); + + it('handles engine checkbox click', () => { + const wrapper = shallow(); + wrapper + .find(EuiCheckbox) + .first() + .simulate('change', { target: { checked: true } }); + + expect(actions.handleEngineSelectionChange).toHaveBeenCalledWith(engines[0].name, true); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx new file mode 100644 index 00000000000000..bfa3fefb2732d3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx @@ -0,0 +1,246 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import { useParams } from 'react-router-dom'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiCheckbox, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPageContentBody, + EuiPageHeader, + EuiPanel, + EuiRadio, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; + +import { FlashMessages } from '../../../shared/flash_messages'; +import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { Loading } from '../../../shared/loading'; +import { + AttributeSelector, + DeleteMappingCallout, + RoleSelector, +} from '../../../shared/role_mapping'; +import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants'; +import { AppLogic } from '../../app_logic'; + +import { roleHasScopedEngines } from '../../utils/role/has_scoped_engines'; +import { Engine } from '../engine/types'; + +import { + SAVE_ROLE_MAPPING, + UPDATE_ROLE_MAPPING, + ADD_ROLE_MAPPING_TITLE, + MANAGE_ROLE_MAPPING_TITLE, + ADVANCED_ROLE_TYPES, + STANDARD_ROLE_TYPES, + ADVANCED_ROLE_SELECTORS_TITLE, + ROLE_TITLE, + FULL_ENGINE_ACCESS_TITLE, + FULL_ENGINE_ACCESS_DESCRIPTION, + LIMITED_ENGINE_ACCESS_TITLE, + LIMITED_ENGINE_ACCESS_DESCRIPTION, + ENGINE_ACCESS_TITLE, +} from './constants'; +import { RoleMappingsLogic } from './role_mappings_logic'; + +interface RoleMappingProps { + isNew?: boolean; +} + +export const RoleMapping: React.FC = ({ isNew }) => { + const { roleId } = useParams() as { roleId: string }; + const { myRole } = useValues(AppLogic); + + const { + handleAccessAllEnginesChange, + handleAttributeSelectorChange, + handleAttributeValueChange, + handleAuthProviderChange, + handleDeleteMapping, + handleEngineSelectionChange, + handleRoleChange, + handleSaveMapping, + initializeRoleMapping, + resetState, + } = useActions(RoleMappingsLogic); + + const { + accessAllEngines, + attributeName, + attributeValue, + attributes, + availableAuthProviders, + availableEngines, + dataLoading, + elasticsearchRoles, + hasAdvancedRoles, + multipleAuthProvidersConfig, + roleMapping, + roleType, + selectedEngines, + selectedAuthProviders, + } = useValues(RoleMappingsLogic); + + useEffect(() => { + initializeRoleMapping(roleId); + return resetState; + }, []); + + if (dataLoading) return ; + + const SAVE_ROLE_MAPPING_LABEL = isNew ? SAVE_ROLE_MAPPING : UPDATE_ROLE_MAPPING; + const TITLE = isNew ? ADD_ROLE_MAPPING_TITLE : MANAGE_ROLE_MAPPING_TITLE; + + const saveRoleMappingButton = ( + + {SAVE_ROLE_MAPPING_LABEL} + + ); + + const engineSelector = (engine: Engine) => ( + { + handleEngineSelectionChange(engine.name, e.target.checked); + }} + label={engine.name} + /> + ); + + const advancedRoleSelectors = ( + <> + + +

{ADVANCED_ROLE_SELECTORS_TITLE}

+
+ + {ADVANCED_ROLE_TYPES.map(({ type, description }) => ( + + ))} + + ); + + return ( + <> + + + + + + + + + + + +

{ROLE_TITLE}

+
+ + +

{FULL_ENGINE_ACCESS_TITLE}

+
+ + export{' '} + {STANDARD_ROLE_TYPES.map(({ type, description }) => ( + + ))} + {hasAdvancedRoles && advancedRoleSelectors} +
+
+ {hasAdvancedRoles && ( + + + +

{ENGINE_ACCESS_TITLE}

+
+ + + + +

{FULL_ENGINE_ACCESS_TITLE}

+
+

{FULL_ENGINE_ACCESS_DESCRIPTION}

+ + } + /> +
+ + <> + + +

{LIMITED_ENGINE_ACCESS_TITLE}

+
+

{LIMITED_ENGINE_ACCESS_DESCRIPTION}

+ + } + /> + {!accessAllEngines && ( +
+ {availableEngines.map((engine) => engineSelector(engine))} +
+ )} + +
+
+
+ )} +
+ + {roleMapping && } +
+ + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.test.tsx new file mode 100644 index 00000000000000..9275ba0cd16dbd --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.test.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import '../../../__mocks__/shallow_useeffect.mock'; +import { setMockActions, setMockValues } from '../../../__mocks__'; + +import React, { MouseEvent } from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiEmptyPrompt, EuiConfirmModal, EuiPageHeader } from '@elastic/eui'; + +import { Loading } from '../../../shared/loading'; +import { RoleMappingsTable } from '../../../shared/role_mapping'; +import { wsRoleMapping } from '../../../shared/role_mapping/__mocks__/roles'; + +import { RoleMappings } from './role_mappings'; + +describe('RoleMappings', () => { + const initializeRoleMappings = jest.fn(); + const handleResetMappings = jest.fn(); + const mockValues = { + roleMappings: [wsRoleMapping], + dataLoading: false, + multipleAuthProvidersConfig: false, + }; + + beforeEach(() => { + setMockActions({ + initializeRoleMappings, + handleResetMappings, + }); + setMockValues(mockValues); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(RoleMappingsTable)).toHaveLength(1); + }); + + it('returns Loading when loading', () => { + setMockValues({ ...mockValues, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('renders empty state', () => { + setMockValues({ ...mockValues, roleMappings: [] }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); + }); + + describe('resetMappingsWarningModal', () => { + let wrapper: ShallowWrapper; + + beforeEach(() => { + wrapper = shallow(); + const button = wrapper.find(EuiPageHeader).prop('rightSideItems')![0] as any; + button.props.onClick(); + }); + + it('renders reset warnings modal', () => { + expect(wrapper.find(EuiConfirmModal)).toHaveLength(1); + }); + + it('hides reset warnings modal', () => { + const modal = wrapper.find(EuiConfirmModal); + modal.prop('onCancel')(); + + expect(wrapper.find(EuiConfirmModal)).toHaveLength(0); + }); + + it('resets when confirmed', () => { + const event = {} as MouseEvent; + const modal = wrapper.find(EuiConfirmModal); + modal.prop('onConfirm')!(event); + + expect(handleResetMappings).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx new file mode 100644 index 00000000000000..e31f5c04bdb457 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiConfirmModal, + EuiEmptyPrompt, + EuiOverlayMask, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { FlashMessages } from '../../../shared/flash_messages'; +import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { Loading } from '../../../shared/loading'; +import { AddRoleMappingButton, RoleMappingsTable } from '../../../shared/role_mapping'; +import { + EMPTY_ROLE_MAPPINGS_TITLE, + ROLE_MAPPINGS_TITLE, + ROLE_MAPPINGS_DESCRIPTION, +} from '../../../shared/role_mapping/constants'; + +import { ROLE_MAPPING_NEW_PATH } from '../../routes'; + +import { + ROLE_MAPPINGS_ENGINE_ACCESS_HEADING, + EMPTY_ROLE_MAPPINGS_BODY, + ROLE_MAPPINGS_RESET_BUTTON, + ROLE_MAPPINGS_RESET_CONFIRM_TITLE, + ROLE_MAPPINGS_RESET_CONFIRM_BUTTON, + ROLE_MAPPINGS_RESET_CANCEL_BUTTON, +} from './constants'; +import { RoleMappingsLogic } from './role_mappings_logic'; +import { generateRoleMappingPath } from './utils'; + +export const RoleMappings: React.FC = () => { + const { initializeRoleMappings, handleResetMappings, resetState } = useActions(RoleMappingsLogic); + const { roleMappings, multipleAuthProvidersConfig, dataLoading } = useValues(RoleMappingsLogic); + + useEffect(() => { + initializeRoleMappings(); + return resetState; + }, []); + + const [isResetWarningVisible, setResetWarningVisibility] = useState(false); + const showWarning = () => setResetWarningVisibility(true); + const hideWarning = () => setResetWarningVisibility(false); + + if (dataLoading) return ; + + const RESET_MAPPINGS_WARNING_MODAL_BODY = ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.resetMappingsWarningModalBodyBold', { + defaultMessage: 'All role mappings will be deleted', + })} + + ), + }} + /> + ); + + const addMappingButton = ; + + const roleMappingEmptyState = ( + {EMPTY_ROLE_MAPPINGS_TITLE}} + body={

{EMPTY_ROLE_MAPPINGS_BODY}

} + actions={addMappingButton} + /> + ); + + const roleMappingsTable = ( + + ); + + const resetMappings = ( + + {ROLE_MAPPINGS_RESET_BUTTON} + + ); + + const resetMappingsWarningModal = isResetWarningVisible ? ( + + handleResetMappings(hideWarning)} + title={ROLE_MAPPINGS_RESET_CONFIRM_TITLE} + cancelButtonText={ROLE_MAPPINGS_RESET_CANCEL_BUTTON} + confirmButtonText={ROLE_MAPPINGS_RESET_CONFIRM_BUTTON} + buttonColor="danger" + maxWidth={640} + > +

{RESET_MAPPINGS_WARNING_MODAL_BODY}

+
+
+ ) : null; + + return ( + <> + + + + + + {roleMappings.length === 0 ? roleMappingEmptyState : roleMappingsTable} + + + {resetMappingsWarningModal} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts index f1b81a59779ac2..d6d56773863305 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts @@ -48,7 +48,7 @@ const getFirstAttributeName = (roleMapping: ASRoleMapping) => const getFirstAttributeValue = (roleMapping: ASRoleMapping) => Object.entries(roleMapping.rules)[0][1] as AttributeName; -export interface RoleMappingsActions { +interface RoleMappingsActions { handleAccessAllEnginesChange(): void; handleAuthProviderChange(value: string[]): { value: string[] }; handleAttributeSelectorChange( @@ -74,7 +74,7 @@ export interface RoleMappingsActions { setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails; } -export interface RoleMappingsValues { +interface RoleMappingsValues { accessAllEngines: boolean; attributeName: AttributeName; attributeValue: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_router.test.tsx new file mode 100644 index 00000000000000..e9fc40ba1dbb43 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_router.test.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; + +import { shallow } from 'enzyme'; + +import { RoleMapping } from './role_mapping'; +import { RoleMappings } from './role_mappings'; +import { RoleMappingsRouter } from './role_mappings_router'; + +describe('RoleMappingsRouter', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(Switch)).toHaveLength(1); + expect(wrapper.find(Route)).toHaveLength(3); + expect(wrapper.find(RoleMapping)).toHaveLength(2); + expect(wrapper.find(RoleMappings)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_router.tsx new file mode 100644 index 00000000000000..7aa8b4067d9e5b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_router.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { Route, Switch } from 'react-router-dom'; + +import { ROLE_MAPPING_NEW_PATH, ROLE_MAPPING_PATH, ROLE_MAPPINGS_PATH } from '../../routes'; + +import { RoleMapping } from './role_mapping'; +import { RoleMappings } from './role_mappings'; + +export const RoleMappingsRouter: React.FC = () => ( + + + + + + + + + + + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx index 4da71ec9a135be..62a0ccc01f29a7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx @@ -25,6 +25,7 @@ import { EngineCreation } from './components/engine_creation'; import { EnginesOverview } from './components/engines'; import { ErrorConnecting } from './components/error_connecting'; import { MetaEngineCreation } from './components/meta_engine_creation'; +import { RoleMappingsRouter } from './components/role_mappings'; import { SetupGuide } from './components/setup_guide'; import { AppSearch, AppSearchUnconfigured, AppSearchConfigured, AppSearchNav } from './'; @@ -88,6 +89,20 @@ describe('AppSearchConfigured', () => { }); describe('ability checks', () => { + describe('canViewRoleMappings', () => { + it('renders RoleMappings when canViewRoleMappings is true', () => { + setMockValues({ myRole: { canViewRoleMappings: true } }); + rerender(wrapper); + expect(wrapper.find(RoleMappingsRouter)).toHaveLength(1); + }); + + it('does not render RoleMappings when user canViewRoleMappings is false', () => { + setMockValues({ myRole: { canManageEngines: false } }); + rerender(wrapper); + expect(wrapper.find(RoleMappingsRouter)).toHaveLength(0); + }); + }); + describe('canManageEngines', () => { it('renders EngineCreation when user canManageEngines is true', () => { setMockValues({ myRole: { canManageEngines: true } }); @@ -155,8 +170,6 @@ describe('AppSearchNav', () => { setMockValues({ myRole: { canViewRoleMappings: true } }); const wrapper = shallow(); - expect(wrapper.find(SideNavLink).last().prop('to')).toEqual( - 'http://localhost:3002/as/role_mappings' - ); + expect(wrapper.find(SideNavLink).last().prop('to')).toEqual('/role_mappings'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index 88db4004ea9e2a..3a46a90d20d665 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -12,12 +12,13 @@ import { useValues } from 'kea'; import { APP_SEARCH_PLUGIN } from '../../../common/constants'; import { InitialAppData } from '../../../common/types'; -import { getAppSearchUrl } from '../shared/enterprise_search_url'; import { HttpLogic } from '../shared/http'; import { KibanaLogic } from '../shared/kibana'; import { Layout, SideNav, SideNavLink } from '../shared/layout'; import { NotFound } from '../shared/not_found'; +import { ROLE_MAPPINGS_TITLE } from '../shared/role_mapping/constants'; + import { AppLogic } from './app_logic'; import { Credentials, CREDENTIALS_TITLE } from './components/credentials'; import { EngineNav, EngineRouter } from './components/engine'; @@ -26,7 +27,7 @@ import { EnginesOverview, ENGINES_TITLE } from './components/engines'; import { ErrorConnecting } from './components/error_connecting'; import { Library } from './components/library'; import { MetaEngineCreation } from './components/meta_engine_creation'; -import { ROLE_MAPPINGS_TITLE } from './components/role_mappings'; +import { RoleMappingsRouter } from './components/role_mappings'; import { Settings, SETTINGS_TITLE } from './components/settings'; import { SetupGuide } from './components/setup_guide'; import { @@ -64,7 +65,7 @@ export const AppSearchUnconfigured: React.FC = () => ( export const AppSearchConfigured: React.FC> = (props) => { const { - myRole: { canManageEngines, canManageMetaEngines }, + myRole: { canManageEngines, canManageMetaEngines, canViewRoleMappings }, } = useValues(AppLogic(props)); const { errorConnecting, readOnlyMode } = useValues(HttpLogic); @@ -101,6 +102,11 @@ export const AppSearchConfigured: React.FC> = (props) = + {canViewRoleMappings && ( + + + + )} {canManageEngines && ( @@ -141,7 +147,7 @@ export const AppSearchNav: React.FC = ({ subNav }) => { {CREDENTIALS_TITLE} )} {canViewRoleMappings && ( - + {ROLE_MAPPINGS_TITLE} )} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts index e763264a041de0..ca3e67129846bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts @@ -6,5 +6,5 @@ */ export * from '../../../common/types/app_search'; -export { Role, RoleTypes, AbilityTypes, ASRoleMapping } from './utils/role'; +export { Role, RoleTypes, AbilityTypes, ASRoleMapping, AdvanceRoleType } from './utils/role'; export { Engine } from './components/engine/types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/types.ts index 0fa94b493ed31f..0c3abd6909390d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/types.ts @@ -53,3 +53,8 @@ export interface ASRoleMapping extends RoleMapping { content: string; }; } + +export interface AdvanceRoleType { + type: RoleTypes; + description: string; +} diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts index 53368035af2257..856004add0f736 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts @@ -128,12 +128,7 @@ describe('role mappings routes', () => { describe('validates', () => { it('correctly', () => { - const request = { - body: { - ...roleMappingBaseSchema, - id: '123', - }, - }; + const request = { body: roleMappingBaseSchema }; mockRouter.shouldValidate(request); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts index 4b77c8614a52c8..3bd3b3d904280a 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts @@ -66,10 +66,7 @@ export function registerRoleMappingRoute({ { path: '/api/app_search/role_mappings/{id}', validate: { - body: schema.object({ - ...roleMappingBaseSchema, - id: schema.string(), - }), + body: schema.object(roleMappingBaseSchema), params: schema.object({ id: schema.string(), }), diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a72585185faac8..346dbe55e0f22e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7609,7 +7609,6 @@ "xpack.enterpriseSearch.appSearch.result.documentDetailLink": "ドキュメントの詳細を表示", "xpack.enterpriseSearch.appSearch.result.hideAdditionalFields": "追加フィールドを非表示", "xpack.enterpriseSearch.appSearch.result.title": "ドキュメント{id}", - "xpack.enterpriseSearch.appSearch.roleMappings.title": "ロールマッピング", "xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.label": "分析ログ", "xpack.enterpriseSearch.appSearch.settings.logRetention.api.label": "API ログ", "xpack.enterpriseSearch.appSearch.settings.logRetention.description": "API ログと分析のデフォルト書き込み設定を管理します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3d864494a7d533..cfd00024cd76a2 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7676,7 +7676,6 @@ "xpack.enterpriseSearch.appSearch.result.hideAdditionalFields": "隐藏其他字段", "xpack.enterpriseSearch.appSearch.result.showAdditionalFields": "显示其他 {numberOfAdditionalFields, number} 个{numberOfAdditionalFields, plural, other {字段}}", "xpack.enterpriseSearch.appSearch.result.title": "文档 {id}", - "xpack.enterpriseSearch.appSearch.roleMappings.title": "角色映射", "xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.label": "分析日志", "xpack.enterpriseSearch.appSearch.settings.logRetention.api.label": "API 日志", "xpack.enterpriseSearch.appSearch.settings.logRetention.description": "管理 API 日志和分析的默认写入设置。", From 55e513364a7a096be106c4b700d60c14fb92244d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Tue, 23 Mar 2021 20:23:23 +0100 Subject: [PATCH 40/93] isClusterOptedIn should fallback to true when not found (#94980) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/encryption/encrypt.test.ts | 11 ++ .../server/plugin.test.ts | 140 ++++++++++++++---- .../server/plugin.ts | 3 +- .../server/util.ts | 11 -- .../apis/telemetry/telemetry_local.ts | 54 +++---- .../get_stats_with_xpack.ts | 7 +- .../is_cluster_opted_in.test.ts | 15 +- .../is_cluster_opted_in.ts | 14 ++ 8 files changed, 174 insertions(+), 81 deletions(-) delete mode 100644 src/plugins/telemetry_collection_manager/server/util.ts rename src/plugins/telemetry_collection_manager/server/util.test.ts => x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/is_cluster_opted_in.test.ts (69%) create mode 100644 x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/is_cluster_opted_in.ts diff --git a/src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.ts b/src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.ts index c1a1a32e3c7f07..be990f4b89e04e 100644 --- a/src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.ts +++ b/src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.ts @@ -22,6 +22,11 @@ describe('getKID', () => { const kid = getKID(useProdKey); expect(kid).toBe('kibana1'); }); + + it(`should fallback to development`, async () => { + const kid = getKID(); + expect(kid).toBe('kibana_dev1'); + }); }); describe('encryptTelemetry', () => { @@ -46,4 +51,10 @@ describe('encryptTelemetry', () => { await encryptTelemetry(payload, { useProdKey: false }); expect(mockEncrypt).toBeCalledWith('kibana_dev1', payload); }); + + it('should fallback to { useProdKey: false }', async () => { + const payload = { some: 'value' }; + await encryptTelemetry(payload); + expect(mockEncrypt).toBeCalledWith('kibana_dev1', payload); + }); }); diff --git a/src/plugins/telemetry_collection_manager/server/plugin.test.ts b/src/plugins/telemetry_collection_manager/server/plugin.test.ts index ac3904ca58b0ff..d05799f82c3544 100644 --- a/src/plugins/telemetry_collection_manager/server/plugin.test.ts +++ b/src/plugins/telemetry_collection_manager/server/plugin.test.ts @@ -9,7 +9,7 @@ import { coreMock, httpServerMock } from '../../../core/server/mocks'; import { usageCollectionPluginMock } from '../../usage_collection/server/mocks'; import { TelemetryCollectionManagerPlugin } from './plugin'; -import { CollectionStrategyConfig, StatsGetterConfig } from './types'; +import type { BasicStatsPayload, CollectionStrategyConfig, StatsGetterConfig } from './types'; import { TelemetrySavedObjectsClient } from './telemetry_saved_objects_client'; function createCollectionStrategy(priority: number): jest.Mocked { @@ -87,6 +87,15 @@ describe('Telemetry Collection Manager', () => { }); describe(`after start`, () => { + const basicStats: BasicStatsPayload = { + cluster_uuid: 'clusterUuid', + cluster_name: 'clusterName', + timestamp: new Date().toISOString(), + cluster_stats: {}, + stack_stats: {}, + version: 'version', + }; + beforeAll(() => { telemetryCollectionManager.start(coreMock.createStart()); }); @@ -97,19 +106,59 @@ describe('Telemetry Collection Manager', () => { describe('unencrypted: false', () => { const config: StatsGetterConfig = { unencrypted: false }; - test('getStats returns empty because clusterDetails returns empty, and the soClient is an instance of the TelemetrySavedObjectsClient', async () => { - collectionStrategy.clusterDetailsGetter.mockResolvedValue([]); - await expect(setupApi.getStats(config)).resolves.toStrictEqual([]); - expect(collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient).toBeInstanceOf( - TelemetrySavedObjectsClient - ); + describe('getStats', () => { + test('returns empty because clusterDetails returns empty, and the soClient is an instance of the TelemetrySavedObjectsClient', async () => { + collectionStrategy.clusterDetailsGetter.mockResolvedValue([]); + await expect(setupApi.getStats(config)).resolves.toStrictEqual([]); + expect( + collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient + ).toBeInstanceOf(TelemetrySavedObjectsClient); + }); + + test('returns encrypted payload', async () => { + collectionStrategy.clusterDetailsGetter.mockResolvedValue([ + { clusterUuid: 'clusterUuid' }, + ]); + collectionStrategy.statsGetter.mockResolvedValue([basicStats]); + await expect(setupApi.getStats(config)).resolves.toStrictEqual([expect.any(String)]); + expect( + collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient + ).toBeInstanceOf(TelemetrySavedObjectsClient); + }); }); - test('getOptInStats returns empty', async () => { - collectionStrategy.clusterDetailsGetter.mockResolvedValue([]); - await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([]); - expect(collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient).toBeInstanceOf( - TelemetrySavedObjectsClient - ); + + describe('getOptInStats', () => { + test('returns empty', async () => { + collectionStrategy.clusterDetailsGetter.mockResolvedValue([]); + await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([]); + expect( + collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient + ).toBeInstanceOf(TelemetrySavedObjectsClient); + }); + + test('returns encrypted results for opt-in true', async () => { + collectionStrategy.clusterDetailsGetter.mockResolvedValue([ + { clusterUuid: 'clusterUuid' }, + ]); + await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([ + expect.any(String), + ]); + expect( + collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient + ).toBeInstanceOf(TelemetrySavedObjectsClient); + }); + + test('returns encrypted results for opt-in false', async () => { + collectionStrategy.clusterDetailsGetter.mockResolvedValue([ + { clusterUuid: 'clusterUuid' }, + ]); + await expect(setupApi.getOptInStats(false, config)).resolves.toStrictEqual([ + expect.any(String), + ]); + expect( + collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient + ).toBeInstanceOf(TelemetrySavedObjectsClient); + }); }); }); describe('unencrypted: true', () => { @@ -118,19 +167,60 @@ describe('Telemetry Collection Manager', () => { request: httpServerMock.createKibanaRequest(), }; - test('getStats returns empty because clusterDetails returns empty, and the soClient is not an instance of the TelemetrySavedObjectsClient', async () => { - collectionStrategy.clusterDetailsGetter.mockResolvedValue([]); - await expect(setupApi.getStats(config)).resolves.toStrictEqual([]); - expect( - collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient - ).not.toBeInstanceOf(TelemetrySavedObjectsClient); + describe('getStats', () => { + test('getStats returns empty because clusterDetails returns empty, and the soClient is not an instance of the TelemetrySavedObjectsClient', async () => { + collectionStrategy.clusterDetailsGetter.mockResolvedValue([]); + await expect(setupApi.getStats(config)).resolves.toStrictEqual([]); + expect( + collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient + ).not.toBeInstanceOf(TelemetrySavedObjectsClient); + }); + test('returns encrypted payload (assumes opted-in when no explicitly opted-out)', async () => { + collectionStrategy.clusterDetailsGetter.mockResolvedValue([ + { clusterUuid: 'clusterUuid' }, + ]); + collectionStrategy.statsGetter.mockResolvedValue([basicStats]); + await expect(setupApi.getStats(config)).resolves.toStrictEqual([ + { ...basicStats, collectionSource: 'test_collection' }, + ]); + expect( + collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient + ).not.toBeInstanceOf(TelemetrySavedObjectsClient); + }); }); - test('getOptInStats returns empty', async () => { - collectionStrategy.clusterDetailsGetter.mockResolvedValue([]); - await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([]); - expect( - collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient - ).not.toBeInstanceOf(TelemetrySavedObjectsClient); + + describe('getOptInStats', () => { + test('returns empty', async () => { + collectionStrategy.clusterDetailsGetter.mockResolvedValue([]); + await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([]); + expect( + collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient + ).not.toBeInstanceOf(TelemetrySavedObjectsClient); + }); + + test('returns results for opt-in true', async () => { + collectionStrategy.clusterDetailsGetter.mockResolvedValue([ + { clusterUuid: 'clusterUuid' }, + ]); + await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([ + { cluster_uuid: 'clusterUuid', opt_in_status: true }, + ]); + expect( + collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient + ).not.toBeInstanceOf(TelemetrySavedObjectsClient); + }); + + test('returns results for opt-in false', async () => { + collectionStrategy.clusterDetailsGetter.mockResolvedValue([ + { clusterUuid: 'clusterUuid' }, + ]); + await expect(setupApi.getOptInStats(false, config)).resolves.toStrictEqual([ + { cluster_uuid: 'clusterUuid', opt_in_status: false }, + ]); + expect( + collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient + ).not.toBeInstanceOf(TelemetrySavedObjectsClient); + }); }); }); }); diff --git a/src/plugins/telemetry_collection_manager/server/plugin.ts b/src/plugins/telemetry_collection_manager/server/plugin.ts index 0fec225d5c424a..692d91b963d9d0 100644 --- a/src/plugins/telemetry_collection_manager/server/plugin.ts +++ b/src/plugins/telemetry_collection_manager/server/plugin.ts @@ -30,7 +30,6 @@ import { UsageStatsPayload, StatsCollectionContext, } from './types'; -import { isClusterOptedIn } from './util'; import { encryptTelemetry } from './encryption'; import { TelemetrySavedObjectsClient } from './telemetry_saved_objects_client'; @@ -233,7 +232,7 @@ export class TelemetryCollectionManagerPlugin return usageData; } - return encryptTelemetry(usageData.filter(isClusterOptedIn), { + return await encryptTelemetry(usageData, { useProdKey: this.isDistributable, }); } diff --git a/src/plugins/telemetry_collection_manager/server/util.ts b/src/plugins/telemetry_collection_manager/server/util.ts deleted file mode 100644 index 226d788e09e48a..00000000000000 --- a/src/plugins/telemetry_collection_manager/server/util.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export const isClusterOptedIn = (clusterUsage: any): boolean => { - return clusterUsage?.stack_stats?.kibana?.plugins?.telemetry?.opt_in_status === true; -}; diff --git a/test/api_integration/apis/telemetry/telemetry_local.ts b/test/api_integration/apis/telemetry/telemetry_local.ts index 211f2eb85e4e36..a7b4da566b1434 100644 --- a/test/api_integration/apis/telemetry/telemetry_local.ts +++ b/test/api_integration/apis/telemetry/telemetry_local.ts @@ -7,13 +7,27 @@ */ import expect from '@kbn/expect'; +import supertestAsPromised from 'supertest-as-promised'; import { basicUiCounters } from './__fixtures__/ui_counters'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { SavedObject } from '../../../../src/core/server'; +import type { FtrProviderContext } from '../../ftr_provider_context'; +import type { SavedObject } from '../../../../src/core/server'; import ossRootTelemetrySchema from '../../../../src/plugins/telemetry/schema/oss_root.json'; import ossPluginsTelemetrySchema from '../../../../src/plugins/telemetry/schema/oss_plugins.json'; import { assertTelemetryPayload, flatKeys } from './utils'; +async function retrieveTelemetry( + supertest: supertestAsPromised.SuperTest +) { + const { body } = await supertest + .post('/api/telemetry/v2/clusters/_stats') + .set('kbn-xsrf', 'xxx') + .send({ unencrypted: true }) + .expect(200); + + expect(body.length).to.be(1); + return body[0]; +} + export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const es = getService('es'); @@ -35,14 +49,7 @@ export default function ({ getService }: FtrProviderContext) { let stats: Record; before('pull local stats', async () => { - const { body } = await supertest - .post('/api/telemetry/v2/clusters/_stats') - .set('kbn-xsrf', 'xxx') - .send({ unencrypted: true }) - .expect(200); - - expect(body.length).to.be(1); - stats = body[0]; + stats = await retrieveTelemetry(supertest); }); it('should pass the schema validation', () => { @@ -141,14 +148,7 @@ export default function ({ getService }: FtrProviderContext) { before('Add UI Counters saved objects', () => esArchiver.load('saved_objects/ui_counters')); after('cleanup saved objects changes', () => esArchiver.unload('saved_objects/ui_counters')); it('returns ui counters aggregated by day', async () => { - const { body } = await supertest - .post('/api/telemetry/v2/clusters/_stats') - .set('kbn-xsrf', 'xxx') - .send({ unencrypted: true }) - .expect(200); - - expect(body.length).to.be(1); - const stats = body[0]; + const stats = await retrieveTelemetry(supertest); expect(stats.stack_stats.kibana.plugins.ui_counters).to.eql(basicUiCounters); }); }); @@ -191,14 +191,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return application_usage data', async () => { - const { body } = await supertest - .post('/api/telemetry/v2/clusters/_stats') - .set('kbn-xsrf', 'xxx') - .send({ unencrypted: true }) - .expect(200); - - expect(body.length).to.be(1); - const stats = body[0]; + const stats = await retrieveTelemetry(supertest); expect(stats.stack_stats.kibana.plugins.application_usage).to.eql({ 'test-app': { appId: 'test-app', @@ -262,14 +255,7 @@ export default function ({ getService }: FtrProviderContext) { // flaky https://github.com/elastic/kibana/issues/94513 it.skip("should only use the first 10k docs for the application_usage data (they'll be rolled up in a later process)", async () => { - const { body } = await supertest - .post('/api/telemetry/v2/clusters/_stats') - .set('kbn-xsrf', 'xxx') - .send({ unencrypted: true }) - .expect(200); - - expect(body.length).to.be(1); - const stats = body[0]; + const stats = await retrieveTelemetry(supertest); expect(stats.stack_stats.kibana.plugins.application_usage).to.eql({ 'test-app': { appId: 'test-app', diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts index 32e59e01b123d5..30bcd19007c0d5 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts @@ -9,6 +9,7 @@ import { StatsGetter } from 'src/plugins/telemetry_collection_manager/server'; import { TelemetryLocalStats, getLocalStats } from '../../../../../src/plugins/telemetry/server'; import { getXPackUsage } from './get_xpack'; import { ESLicense, getLicenseFromLocalOrMaster } from './get_license'; +import { isClusterOptedIn } from './is_cluster_opted_in'; export type TelemetryAggregatedStats = TelemetryLocalStats & { stack_stats: { xpack?: object }; @@ -48,6 +49,10 @@ export const getStatsWithXpack: StatsGetter = async fu if (monitoringTelemetry) { delete stats.stack_stats.kibana!.plugins.monitoringTelemetry; } - return [...acc, stats, ...(monitoringTelemetry || [])]; + + // From the monitoring-sourced telemetry, we need to filter out the clusters that are opted-out. + const onlyOptedInMonitoringClusters = (monitoringTelemetry || []).filter(isClusterOptedIn); + + return [...acc, stats, ...onlyOptedInMonitoringClusters]; }, [] as TelemetryAggregatedStats[]); }; diff --git a/src/plugins/telemetry_collection_manager/server/util.test.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/is_cluster_opted_in.test.ts similarity index 69% rename from src/plugins/telemetry_collection_manager/server/util.test.ts rename to x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/is_cluster_opted_in.test.ts index 12e67c466d4d55..5fa7584879f077 100644 --- a/src/plugins/telemetry_collection_manager/server/util.test.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/is_cluster_opted_in.test.ts @@ -1,12 +1,11 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ -import { isClusterOptedIn } from './util'; +import { isClusterOptedIn } from './is_cluster_opted_in'; const createMockClusterUsage = (plugins: any) => { return { @@ -32,9 +31,9 @@ describe('isClusterOptedIn', () => { const result = isClusterOptedIn(mockClusterUsage); expect(result).toBe(false); }); - it('returns false if cluster stats is malformed', () => { - expect(isClusterOptedIn(createMockClusterUsage({}))).toBe(false); - expect(isClusterOptedIn({})).toBe(false); - expect(isClusterOptedIn(undefined)).toBe(false); + it('returns true if kibana.plugins.telemetry does not exist', () => { + expect(isClusterOptedIn(createMockClusterUsage({}))).toBe(true); + expect(isClusterOptedIn({})).toBe(true); + expect(isClusterOptedIn(undefined)).toBe(true); }); }); diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/is_cluster_opted_in.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/is_cluster_opted_in.ts new file mode 100644 index 00000000000000..4bc35a238152b5 --- /dev/null +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/is_cluster_opted_in.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const isClusterOptedIn = (clusterUsage: any): boolean => { + return ( + clusterUsage?.stack_stats?.kibana?.plugins?.telemetry?.opt_in_status === true || + // If stack_stats.kibana.plugins.telemetry does not exist, assume opted-in for BWC + !clusterUsage?.stack_stats?.kibana?.plugins?.telemetry + ); +}; From 3998a83871202d5559683abdf9e4c36ea6e87ea7 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Tue, 23 Mar 2021 15:23:58 -0400 Subject: [PATCH 41/93] [Security Solution][Endpoint][Admin] Refactor policy details protections (#94970) --- .../public/management/pages/policy/types.ts | 6 + .../components/protection_radio.tsx | 91 +++++ .../components/protection_switch.tsx | 100 ++++++ .../policy_forms/components/radio_buttons.tsx | 96 +++++ .../supported_version.tsx | 2 +- .../components/user_notification.tsx | 170 +++++++++ .../view/policy_forms/protections/malware.tsx | 327 +----------------- .../policy_forms/protections/ransomware.tsx | 308 +---------------- .../translations/translations/ja-JP.json | 10 - .../translations/translations/zh-CN.json | 10 - 10 files changed, 488 insertions(+), 632 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/protection_radio.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/protection_switch.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/radio_buttons.tsx rename x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/{protections => components}/supported_version.tsx (91%) create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/user_notification.tsx diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts index f7e054729c7b91..b2b95e2765bd86 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts @@ -120,6 +120,12 @@ export type RansomwareProtectionOSes = KeysByValueCriteria< { ransomware: ProtectionFields } >; +export type PolicyProtection = + | keyof Pick + | keyof Pick; + +export type MacPolicyProtection = keyof Pick; + export interface GetPolicyListResponse extends GetPackagePoliciesResponse { items: PolicyData[]; } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/protection_radio.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/protection_radio.tsx new file mode 100644 index 00000000000000..8394b557207afb --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/protection_radio.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; +import { cloneDeep } from 'lodash'; +import { htmlIdGenerator, EuiRadio } from '@elastic/eui'; +import { + ImmutableArray, + ProtectionModes, + UIPolicyConfig, +} from '../../../../../../../common/endpoint/types'; +import { MacPolicyProtection, PolicyProtection } from '../../../types'; +import { usePolicyDetailsSelector } from '../../policy_hooks'; +import { policyConfig } from '../../../store/policy_details/selectors'; +import { AppAction } from '../../../../../../common/store/actions'; +import { useLicense } from '../../../../../../common/hooks/use_license'; + +export const ProtectionRadio = React.memo( + ({ + protection, + protectionMode, + osList, + label, + }: { + protection: PolicyProtection; + protectionMode: ProtectionModes; + osList: ImmutableArray>; + label: string; + }) => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const dispatch = useDispatch<(action: AppAction) => void>(); + const radioButtonId = useMemo(() => htmlIdGenerator()(), []); + const selected = policyDetailsConfig && policyDetailsConfig.windows[protection].mode; + const isPlatinumPlus = useLicense().isPlatinumPlus(); + + const handleRadioChange = useCallback(() => { + if (policyDetailsConfig) { + const newPayload = cloneDeep(policyDetailsConfig); + for (const os of osList) { + if (os === 'windows') { + newPayload[os][protection].mode = protectionMode; + } else if (os === 'mac') { + newPayload[os][protection as MacPolicyProtection].mode = protectionMode; + } + if (isPlatinumPlus) { + if (os === 'windows') { + if (protectionMode === ProtectionModes.prevent) { + newPayload[os].popup[protection].enabled = true; + } else { + newPayload[os].popup[protection].enabled = false; + } + } else if (os === 'mac') { + if (protectionMode === ProtectionModes.prevent) { + newPayload[os].popup[protection as MacPolicyProtection].enabled = true; + } else { + newPayload[os].popup[protection as MacPolicyProtection].enabled = false; + } + } + } + } + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload }, + }); + } + }, [dispatch, protectionMode, policyDetailsConfig, isPlatinumPlus, osList, protection]); + + /** + * Passing an arbitrary id because EuiRadio + * requires an id if label is passed + */ + + return ( + + ); + } +); + +ProtectionRadio.displayName = 'ProtectionRadio'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/protection_switch.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/protection_switch.tsx new file mode 100644 index 00000000000000..cbe118e8dfa368 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/protection_switch.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import { i18n } from '@kbn/i18n'; +import { EuiSwitch } from '@elastic/eui'; +import { cloneDeep } from 'lodash'; +import { useLicense } from '../../../../../../common/hooks/use_license'; +import { policyConfig } from '../../../store/policy_details/selectors'; +import { usePolicyDetailsSelector } from '../../policy_hooks'; +import { AppAction } from '../../../../../../common/store/actions'; +import { + ImmutableArray, + ProtectionModes, + UIPolicyConfig, +} from '../../../../../../../common/endpoint/types'; +import { PolicyProtection, MacPolicyProtection } from '../../../types'; + +export const ProtectionSwitch = React.memo( + ({ + protection, + osList, + }: { + protection: PolicyProtection; + osList: ImmutableArray>; + }) => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const isPlatinumPlus = useLicense().isPlatinumPlus(); + const dispatch = useDispatch<(action: AppAction) => void>(); + const selected = policyDetailsConfig && policyDetailsConfig.windows[protection].mode; + + const handleSwitchChange = useCallback( + (event) => { + if (policyDetailsConfig) { + const newPayload = cloneDeep(policyDetailsConfig); + if (event.target.checked === false) { + for (const os of osList) { + if (os === 'windows') { + newPayload[os][protection].mode = ProtectionModes.off; + } else if (os === 'mac') { + newPayload[os][protection as MacPolicyProtection].mode = ProtectionModes.off; + } + if (isPlatinumPlus) { + if (os === 'windows') { + newPayload[os].popup[protection].enabled = event.target.checked; + } else if (os === 'mac') { + newPayload[os].popup[protection as MacPolicyProtection].enabled = + event.target.checked; + } + } + } + } else { + for (const os of osList) { + if (os === 'windows') { + newPayload[os][protection].mode = ProtectionModes.prevent; + } else if (os === 'mac') { + newPayload[os][protection as MacPolicyProtection].mode = ProtectionModes.prevent; + } + if (isPlatinumPlus) { + if (os === 'windows') { + newPayload[os].popup[protection].enabled = event.target.checked; + } else if (os === 'mac') { + newPayload[os].popup[protection as MacPolicyProtection].enabled = + event.target.checked; + } + } + } + } + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload }, + }); + } + }, + [dispatch, policyDetailsConfig, isPlatinumPlus, protection, osList] + ); + + return ( + + ); + } +); + +ProtectionSwitch.displayName = 'ProtectionSwitch'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/radio_buttons.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/radio_buttons.tsx new file mode 100644 index 00000000000000..793c24a0c4d0cb --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/radio_buttons.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import styled from 'styled-components'; +import { EuiSpacer, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { + Immutable, + ImmutableArray, + ProtectionModes, + UIPolicyConfig, +} from '../../../../../../../common/endpoint/types'; +import { PolicyProtection } from '../../../types'; +import { ConfigFormHeading } from '../../components/config_form'; +import { ProtectionRadio } from './protection_radio'; + +export const RadioFlexGroup = styled(EuiFlexGroup)` + .no-right-margin-radio { + margin-right: 0; + } + .no-horizontal-margin-radio { + margin: ${(props) => props.theme.eui.ruleMargins.marginSmall} 0; + } +`; + +export const RadioButtons = React.memo( + ({ + protection, + osList, + }: { + protection: PolicyProtection; + osList: ImmutableArray>; + }) => { + const radios: Immutable< + Array<{ + id: ProtectionModes; + label: string; + }> + > = useMemo(() => { + return [ + { + id: ProtectionModes.detect, + label: i18n.translate('xpack.securitySolution.endpoint.policy.details.detect', { + defaultMessage: 'Detect', + }), + }, + { + id: ProtectionModes.prevent, + label: i18n.translate('xpack.securitySolution.endpoint.policy.details.prevent', { + defaultMessage: 'Prevent', + }), + }, + ]; + }, []); + + return ( + <> + + + + + + + + + + + + + + ); + } +); + +RadioButtons.displayName = 'RadioButtons'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/supported_version.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/supported_version.tsx similarity index 91% rename from x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/supported_version.tsx rename to x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/supported_version.tsx index 5985a5fe03ec35..b8418004206b9c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/supported_version.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/supported_version.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText } from '@elastic/eui'; -import { popupVersionsMap } from './popup_options_to_versions'; +import { popupVersionsMap } from '../protections/popup_options_to_versions'; export const SupportedVersionNotice = ({ optionName }: { optionName: string }) => { const version = popupVersionsMap.get(optionName); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/user_notification.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/user_notification.tsx new file mode 100644 index 00000000000000..def9e78e994b05 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/user_notification.tsx @@ -0,0 +1,170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { cloneDeep } from 'lodash'; +import { + EuiSpacer, + EuiFlexItem, + EuiFlexGroup, + EuiCheckbox, + EuiIconTip, + EuiText, + EuiTextArea, +} from '@elastic/eui'; +import { + ImmutableArray, + ProtectionModes, + UIPolicyConfig, +} from '../../../../../../../common/endpoint/types'; +import { PolicyProtection, MacPolicyProtection } from '../../../types'; +import { ConfigFormHeading } from '../../components/config_form'; +import { usePolicyDetailsSelector } from '../../policy_hooks'; +import { policyConfig } from '../../../store/policy_details/selectors'; +import { AppAction } from '../../../../../../common/store/actions'; +import { SupportedVersionNotice } from './supported_version'; + +export const UserNotification = React.memo( + ({ + protection, + osList, + }: { + protection: PolicyProtection; + osList: ImmutableArray>; + }) => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const dispatch = useDispatch<(action: AppAction) => void>(); + const selected = policyDetailsConfig && policyDetailsConfig.windows[protection].mode; + const userNotificationSelected = + policyDetailsConfig && policyDetailsConfig.windows.popup[protection].enabled; + const userNotificationMessage = + policyDetailsConfig && policyDetailsConfig.windows.popup[protection].message; + + const handleUserNotificationCheckbox = useCallback( + (event) => { + if (policyDetailsConfig) { + const newPayload = cloneDeep(policyDetailsConfig); + for (const os of osList) { + if (os === 'windows') { + newPayload[os].popup[protection].enabled = event.target.checked; + } else if (os === 'mac') { + newPayload[os].popup[protection as MacPolicyProtection].enabled = + event.target.checked; + } + } + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload }, + }); + } + }, + [policyDetailsConfig, dispatch, protection, osList] + ); + + const handleCustomUserNotification = useCallback( + (event) => { + if (policyDetailsConfig) { + const newPayload = cloneDeep(policyDetailsConfig); + for (const os of osList) { + if (os === 'windows') { + newPayload[os].popup[protection].message = event.target.value; + } else if (os === 'mac') { + newPayload[os].popup[protection as MacPolicyProtection].message = event.target.value; + } + } + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload }, + }); + } + }, + [policyDetailsConfig, dispatch, protection, osList] + ); + + return ( + <> + + + + + + + + {userNotificationSelected && ( + <> + + + + +

+ +

+
+
+ + + + + + + } + /> + +
+ + + + )} + + ); + } +); + +UserNotification.displayName = 'UserNotification'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx index a5be095abfc598..03cd587ca7e5c5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx @@ -5,333 +5,29 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; -import { useDispatch } from 'react-redux'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiCallOut, - EuiCheckbox, - EuiRadio, - EuiSpacer, - EuiSwitch, - EuiText, - EuiTextArea, - htmlIdGenerator, - EuiFlexGroup, - EuiFlexItem, - EuiIconTip, -} from '@elastic/eui'; -import { cloneDeep } from 'lodash'; -import styled from 'styled-components'; +import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { APP_ID } from '../../../../../../../common/constants'; import { SecurityPageName } from '../../../../../../app/types'; -import { - Immutable, - OperatingSystem, - ProtectionModes, -} from '../../../../../../../common/endpoint/types'; +import { Immutable, OperatingSystem } from '../../../../../../../common/endpoint/types'; import { MalwareProtectionOSes, OS } from '../../../types'; -import { ConfigForm, ConfigFormHeading } from '../../components/config_form'; -import { policyConfig } from '../../../store/policy_details/selectors'; -import { usePolicyDetailsSelector } from '../../policy_hooks'; +import { ConfigForm } from '../../components/config_form'; import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app'; import { useLicense } from '../../../../../../common/hooks/use_license'; -import { AppAction } from '../../../../../../common/store/actions'; -import { SupportedVersionNotice } from './supported_version'; - -export const RadioFlexGroup = styled(EuiFlexGroup)` - .no-right-margin-radio { - margin-right: 0; - } - .no-horizontal-margin-radio { - margin: ${(props) => props.theme.eui.ruleMargins.marginSmall} 0; - } -`; - -const OSes: Immutable = [OS.windows, OS.mac]; -const protection = 'malware'; - -const ProtectionRadio = React.memo( - ({ protectionMode, label }: { protectionMode: ProtectionModes; label: string }) => { - const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); - const dispatch = useDispatch<(action: AppAction) => void>(); - const radioButtonId = useMemo(() => htmlIdGenerator()(), []); - // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value - const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode; - const isPlatinumPlus = useLicense().isPlatinumPlus(); - - const handleRadioChange = useCallback(() => { - if (policyDetailsConfig) { - const newPayload = cloneDeep(policyDetailsConfig); - for (const os of OSes) { - newPayload[os][protection].mode = protectionMode; - if (isPlatinumPlus) { - if (protectionMode === ProtectionModes.prevent) { - newPayload[os].popup[protection].enabled = true; - } else { - newPayload[os].popup[protection].enabled = false; - } - } - } - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: newPayload }, - }); - } - }, [dispatch, protectionMode, policyDetailsConfig, isPlatinumPlus]); - - /** - * Passing an arbitrary id because EuiRadio - * requires an id if label is passed - */ - - return ( - - ); - } -); - -ProtectionRadio.displayName = 'ProtectionRadio'; +import { RadioButtons } from '../components/radio_buttons'; +import { UserNotification } from '../components/user_notification'; +import { ProtectionSwitch } from '../components/protection_switch'; /** The Malware Protections form for policy details * which will configure for all relevant OSes. */ export const MalwareProtections = React.memo(() => { - const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); - const dispatch = useDispatch<(action: AppAction) => void>(); - // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value - const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode; - const userNotificationSelected = - policyDetailsConfig && policyDetailsConfig.windows.popup.malware.enabled; - const userNotificationMessage = - policyDetailsConfig && policyDetailsConfig.windows.popup.malware.message; + const OSes: Immutable = [OS.windows, OS.mac]; + const protection = 'malware'; const isPlatinumPlus = useLicense().isPlatinumPlus(); - const radios: Immutable< - Array<{ - id: ProtectionModes; - label: string; - protection: 'malware'; - }> - > = useMemo(() => { - return [ - { - id: ProtectionModes.detect, - label: i18n.translate('xpack.securitySolution.endpoint.policy.details.detect', { - defaultMessage: 'Detect', - }), - protection: 'malware', - }, - { - id: ProtectionModes.prevent, - label: i18n.translate('xpack.securitySolution.endpoint.policy.details.prevent', { - defaultMessage: 'Prevent', - }), - protection: 'malware', - }, - ]; - }, []); - - const handleSwitchChange = useCallback( - (event) => { - if (policyDetailsConfig) { - const newPayload = cloneDeep(policyDetailsConfig); - if (event.target.checked === false) { - for (const os of OSes) { - newPayload[os][protection].mode = ProtectionModes.off; - if (isPlatinumPlus) { - newPayload[os].popup[protection].enabled = event.target.checked; - } - } - } else { - for (const os of OSes) { - newPayload[os][protection].mode = ProtectionModes.prevent; - if (isPlatinumPlus) { - newPayload[os].popup[protection].enabled = event.target.checked; - } - } - } - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: newPayload }, - }); - } - }, - [dispatch, policyDetailsConfig, isPlatinumPlus] - ); - - const handleUserNotificationCheckbox = useCallback( - (event) => { - if (policyDetailsConfig) { - const newPayload = cloneDeep(policyDetailsConfig); - for (const os of OSes) { - newPayload[os].popup[protection].enabled = event.target.checked; - } - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: newPayload }, - }); - } - }, - [policyDetailsConfig, dispatch] - ); - - const handleCustomUserNotification = useCallback( - (event) => { - if (policyDetailsConfig) { - const newPayload = cloneDeep(policyDetailsConfig); - for (const os of OSes) { - newPayload[os].popup[protection].message = event.target.value; - } - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: newPayload }, - }); - } - }, - [policyDetailsConfig, dispatch] - ); - - const radioButtons = useMemo(() => { - return ( - <> - - - - - - - - - - - - - {isPlatinumPlus && ( - <> - - - - - - - - - )} - {isPlatinumPlus && userNotificationSelected && ( - <> - - - - -

- -

-
-
- - - - - - - } - /> - -
- - - - )} - - ); - }, [ - radios, - selected, - isPlatinumPlus, - handleUserNotificationCheckbox, - userNotificationSelected, - userNotificationMessage, - handleCustomUserNotification, - ]); - - const protectionSwitch = useMemo(() => { - return ( - - ); - }, [handleSwitchChange, selected]); - return ( { })} supportedOss={[OperatingSystem.WINDOWS, OperatingSystem.MAC]} dataTestSubj="malwareProtectionsForm" - rightCorner={protectionSwitch} + rightCorner={} > - {radioButtons} + + {isPlatinumPlus && } = [OS.windows]; -const protection = 'ransomware'; - -const ProtectionRadio = React.memo( - ({ protectionMode, label }: { protectionMode: ProtectionModes; label: string }) => { - const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); - const dispatch = useDispatch<(action: AppAction) => void>(); - const radioButtonId = useMemo(() => htmlIdGenerator()(), []); - const selected = policyDetailsConfig && policyDetailsConfig.windows.ransomware.mode; - - const handleRadioChange: EuiRadioProps['onChange'] = useCallback(() => { - if (policyDetailsConfig) { - const newPayload = cloneDeep(policyDetailsConfig); - for (const os of OSes) { - newPayload[os][protection].mode = protectionMode; - if (protectionMode === ProtectionModes.prevent) { - newPayload[os].popup[protection].enabled = true; - } else { - newPayload[os].popup[protection].enabled = false; - } - } - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: newPayload }, - }); - } - }, [dispatch, protectionMode, policyDetailsConfig]); - - /** - * Passing an arbitrary id because EuiRadio - * requires an id if label is passed - */ - - return ( - - ); - } -); - -ProtectionRadio.displayName = 'ProtectionRadio'; +import { RadioButtons } from '../components/radio_buttons'; +import { UserNotification } from '../components/user_notification'; +import { ProtectionSwitch } from '../components/protection_switch'; /** The Ransomware Protections form for policy details * which will configure for all relevant OSes. */ export const Ransomware = React.memo(() => { - const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); - const dispatch = useDispatch<(action: AppAction) => void>(); - const selected = policyDetailsConfig && policyDetailsConfig.windows.ransomware.mode; - const userNotificationSelected = - policyDetailsConfig && policyDetailsConfig.windows.popup.ransomware.enabled; - const userNotificationMessage = - policyDetailsConfig && policyDetailsConfig.windows.popup.ransomware.message; - - const radios: Immutable< - Array<{ - id: ProtectionModes; - label: string; - protection: 'ransomware'; - }> - > = useMemo(() => { - return [ - { - id: ProtectionModes.detect, - label: i18n.translate('xpack.securitySolution.endpoint.policy.details.detect', { - defaultMessage: 'Detect', - }), - protection: 'ransomware', - }, - { - id: ProtectionModes.prevent, - label: i18n.translate('xpack.securitySolution.endpoint.policy.details.prevent', { - defaultMessage: 'Prevent', - }), - protection: 'ransomware', - }, - ]; - }, []); - - const handleSwitchChange: EuiSwitchProps['onChange'] = useCallback( - (event) => { - if (policyDetailsConfig) { - const newPayload = cloneDeep(policyDetailsConfig); - if (event.target.checked === false) { - for (const os of OSes) { - newPayload[os][protection].mode = ProtectionModes.off; - newPayload[os].popup[protection].enabled = event.target.checked; - } - } else { - for (const os of OSes) { - newPayload[os][protection].mode = ProtectionModes.prevent; - newPayload[os].popup[protection].enabled = event.target.checked; - } - } - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: newPayload }, - }); - } - }, - [dispatch, policyDetailsConfig] - ); - - const handleUserNotificationCheckbox: EuiCheckboxProps['onChange'] = useCallback( - (event) => { - if (policyDetailsConfig) { - const newPayload = cloneDeep(policyDetailsConfig); - for (const os of OSes) { - newPayload[os].popup[protection].enabled = event.target.checked; - } - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: newPayload }, - }); - } - }, - [policyDetailsConfig, dispatch] - ); - - const handleCustomUserNotification = useCallback( - (event) => { - if (policyDetailsConfig) { - const newPayload = cloneDeep(policyDetailsConfig); - for (const os of OSes) { - newPayload[os].popup[protection].message = event.target.value; - } - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: newPayload }, - }); - } - }, - [policyDetailsConfig, dispatch] - ); - - const radioButtons = useMemo(() => { - return ( - <> - - - - - - - - - - - - - - - - - - - - {userNotificationSelected && ( - <> - - - - -

- -

-
-
- - - - - - - } - /> - -
- - - - )} - - ); - }, [ - radios, - selected, - handleUserNotificationCheckbox, - userNotificationSelected, - userNotificationMessage, - handleCustomUserNotification, - ]); - - const protectionSwitch = useMemo(() => { - return ( - - ); - }, [handleSwitchChange, selected]); + const OSes: Immutable = [OS.windows]; + const protection = 'ransomware'; return ( { })} supportedOss={[OperatingSystem.WINDOWS]} dataTestSubj="ransomwareProtectionsForm" - rightCorner={protectionSwitch} + rightCorner={} > - {radioButtons} + + Date: Tue, 23 Mar 2021 15:24:24 -0400 Subject: [PATCH 42/93] [Fleet] Add `fleetServerEnabled` config setting and use it in SO migration for Endpoint Policies (#95204) * Add `agents.fleetServerEnabled` to plugin configuration * Use feature flag in Endpoint package policy SO migration --- x-pack/plugins/fleet/common/types/index.ts | 1 + .../fleet/mock/plugin_configuration.ts | 1 + x-pack/plugins/fleet/server/index.ts | 2 +- x-pack/plugins/fleet/server/plugin.ts | 6 +++-- .../security_solution/to_v7_13_0.test.ts | 23 +++++++++++++++++++ .../security_solution/to_v7_13_0.ts | 22 ++++++++++-------- .../saved_objects/migrations/to_v7_13_0.ts | 12 ++++++---- .../fleet/server/services/app_context.ts | 5 ++++ 8 files changed, 56 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/index.ts b/x-pack/plugins/fleet/common/types/index.ts index 4223697703a8d5..5c385f938a69e3 100644 --- a/x-pack/plugins/fleet/common/types/index.ts +++ b/x-pack/plugins/fleet/common/types/index.ts @@ -14,6 +14,7 @@ export interface FleetConfigType { registryProxyUrl?: string; agents: { enabled: boolean; + fleetServerEnabled: boolean; tlsCheckDisabled: boolean; pollingRequestTimeout: number; maxConcurrentConnections: number; diff --git a/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts b/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts index 5d53425607361d..81ef6a6703c343 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts @@ -14,6 +14,7 @@ export const createConfigurationMock = (): FleetConfigType => { registryProxyUrl: '', agents: { enabled: true, + fleetServerEnabled: false, tlsCheckDisabled: true, pollingRequestTimeout: 1000, maxConcurrentConnections: 100, diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index 73a8b419a869d8..8bad868b813ac6 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -38,7 +38,6 @@ export const config: PluginConfigDescriptor = { deprecations: ({ renameFromRoot, unused }) => [ renameFromRoot('xpack.ingestManager', 'xpack.fleet'), renameFromRoot('xpack.fleet.fleet', 'xpack.fleet.agents'), - unused('agents.fleetServerEnabled'), ], schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), @@ -46,6 +45,7 @@ export const config: PluginConfigDescriptor = { registryProxyUrl: schema.maybe(schema.uri({ scheme: ['http', 'https'] })), agents: schema.object({ enabled: schema.boolean({ defaultValue: true }), + fleetServerEnabled: schema.boolean({ defaultValue: false }), tlsCheckDisabled: schema.boolean({ defaultValue: false }), pollingRequestTimeout: schema.number({ defaultValue: AGENT_POLLING_REQUEST_TIMEOUT_MS, diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 477e6c39599510..5d7b05c5eddcbd 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -209,6 +209,10 @@ export class FleetPlugin this.encryptedSavedObjectsSetup = deps.encryptedSavedObjects; this.cloud = deps.cloud; + const config = await this.config$.pipe(first()).toPromise(); + + appContextService.fleetServerEnabled = config.agents.fleetServerEnabled; + registerSavedObjects(core.savedObjects, deps.encryptedSavedObjects); registerEncryptedSavedObjects(deps.encryptedSavedObjects); @@ -248,8 +252,6 @@ export class FleetPlugin const router = core.http.createRouter(); - const config = await this.config$.pipe(first()).toPromise(); - // Register usage collection registerFleetUsageCollector(core, config, deps.usageCollection); diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.test.ts index 75e2922bd51499..6897efbe94110b 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.test.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.test.ts @@ -12,6 +12,8 @@ import type { PackagePolicy } from '../../../../common'; import { migrationMocks } from '../../../../../../../src/core/server/mocks'; +import { appContextService } from '../../../services'; + import { migrateEndpointPackagePolicyToV7130 } from './to_v7_13_0'; describe('7.13.0 Endpoint Package Policy migration', () => { @@ -126,6 +128,16 @@ describe('7.13.0 Endpoint Package Policy migration', () => { const migrationContext = migrationMocks.createContext(); + beforeEach(() => { + // set `fleetServerEnabled` flag to true + appContextService.fleetServerEnabled = true; + }); + + afterEach(() => { + // set `fleetServerEnabled` flag back to false + appContextService.fleetServerEnabled = false; + }); + it('should adjust the relative url for all artifact manifests', () => { expect( migrateEndpointPackagePolicyToV7130(createOldPackagePolicySO(), migrationContext) @@ -142,4 +154,15 @@ describe('7.13.0 Endpoint Package Policy migration', () => { unchangedPackagePolicySo ); }); + + it('should NOT migrate artifact relative_url if fleetServerEnabled is false', () => { + const packagePolicySo = createOldPackagePolicySO(); + const unchangedPackagePolicySo = cloneDeep(packagePolicySo); + + appContextService.fleetServerEnabled = false; + + expect(migrateEndpointPackagePolicyToV7130(packagePolicySo, migrationContext)).toEqual( + unchangedPackagePolicySo + ); + }); }); diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.ts index 655ce37b4faaf2..5eb0c7a6e31416 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_13_0.ts @@ -10,6 +10,7 @@ import type { SavedObjectMigrationFn } from 'kibana/server'; import type { PackagePolicy } from '../../../../common'; import { relativeDownloadUrlFromArtifact } from '../../../services/artifacts/mappings'; import type { ArtifactElasticsearchProperties } from '../../../services'; +import { appContextService } from '../../../services'; type ArtifactManifestList = Record< string, @@ -21,16 +22,19 @@ export const migrateEndpointPackagePolicyToV7130: SavedObjectMigrationFn< PackagePolicy > = (packagePolicyDoc) => { if (packagePolicyDoc.attributes.package?.name === 'endpoint') { - // Adjust all artifact URLs so that they point at fleet-server - const artifactList: ArtifactManifestList = - packagePolicyDoc.attributes?.inputs[0]?.config?.artifact_manifest.value.artifacts; + // Feature condition check here is temporary until v7.13 ships + if (appContextService.fleetServerEnabled) { + // Adjust all artifact URLs so that they point at fleet-server + const artifactList: ArtifactManifestList = + packagePolicyDoc.attributes?.inputs[0]?.config?.artifact_manifest.value.artifacts; - if (artifactList) { - for (const [identifier, artifactManifest] of Object.entries(artifactList)) { - artifactManifest.relative_url = relativeDownloadUrlFromArtifact({ - identifier, - decodedSha256: artifactManifest.decoded_sha256, - }); + if (artifactList) { + for (const [identifier, artifactManifest] of Object.entries(artifactList)) { + artifactManifest.relative_url = relativeDownloadUrlFromArtifact({ + identifier, + decodedSha256: artifactManifest.decoded_sha256, + }); + } } } } diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_13_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_13_0.ts index 1cc2394a8e5fe0..e4ba7ce56e847a 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_13_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_13_0.ts @@ -32,11 +32,15 @@ export const migratePackagePolicyToV7130: SavedObjectMigrationFn { + let updatedPackagePolicyDoc = packagePolicyDoc; + // Endpoint specific migrations - // FIXME:PT remove `-OFF` from below once ready to be released - if (packagePolicyDoc.attributes.package?.name === 'endpoint-OFF') { - return migrateEndpointPackagePolicyToV7130(packagePolicyDoc, migrationContext); + if (packagePolicyDoc.attributes.package?.name === 'endpoint') { + updatedPackagePolicyDoc = migrateEndpointPackagePolicyToV7130( + packagePolicyDoc, + migrationContext + ); } - return packagePolicyDoc; + return updatedPackagePolicyDoc; }; diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index 954308a9808613..c49e536435027d 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -44,6 +44,11 @@ class AppContextService { private httpSetup?: HttpServiceSetup; private externalCallbacks: ExternalCallbacksStorage = new Map(); + /** + * Temporary flag until v7.13 ships + */ + public fleetServerEnabled: boolean = false; + public async start(appContext: FleetAppContext) { this.data = appContext.data; this.esClient = appContext.elasticsearch.client.asInternalUser; From ba21c315c9e7292273a5bb5a4af6a6768a17a2f8 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 23 Mar 2021 15:47:14 -0400 Subject: [PATCH 43/93] Document spaces telemetry fields (#95087) --- .../spaces_usage_collector.ts | 294 +++++++++++++++--- .../schema/xpack_plugins.json | 200 +++++++++--- 2 files changed, 413 insertions(+), 81 deletions(-) diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts index 60a2acc5319df3..c0cf71fab05584 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts @@ -179,50 +179,262 @@ export function getSpacesUsageCollector( type: 'spaces', isReady: () => true, schema: { - usesFeatureControls: { type: 'boolean' }, + usesFeatureControls: { + type: 'boolean', + _meta: { + description: + 'Indicates if at least one feature is disabled in at least one space. This is a signal that space-level feature controls are in use. This does not account for role-based (security) feature controls.', + }, + }, disabledFeatures: { // "feature": number; - DYNAMIC_KEY: { type: 'long' }, + DYNAMIC_KEY: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, // Known registered features - stackAlerts: { type: 'long' }, - actions: { type: 'long' }, - enterpriseSearch: { type: 'long' }, - fleet: { type: 'long' }, - savedObjectsTagging: { type: 'long' }, - indexPatterns: { type: 'long' }, - discover: { type: 'long' }, - canvas: { type: 'long' }, - maps: { type: 'long' }, - siem: { type: 'long' }, - monitoring: { type: 'long' }, - graph: { type: 'long' }, - uptime: { type: 'long' }, - savedObjectsManagement: { type: 'long' }, - timelion: { type: 'long' }, - dev_tools: { type: 'long' }, - advancedSettings: { type: 'long' }, - infrastructure: { type: 'long' }, - visualize: { type: 'long' }, - logs: { type: 'long' }, - dashboard: { type: 'long' }, - ml: { type: 'long' }, - apm: { type: 'long' }, - }, - available: { type: 'boolean' }, - enabled: { type: 'boolean' }, - count: { type: 'long' }, - 'apiCalls.copySavedObjects.total': { type: 'long' }, - 'apiCalls.copySavedObjects.kibanaRequest.yes': { type: 'long' }, - 'apiCalls.copySavedObjects.kibanaRequest.no': { type: 'long' }, - 'apiCalls.copySavedObjects.createNewCopiesEnabled.yes': { type: 'long' }, - 'apiCalls.copySavedObjects.createNewCopiesEnabled.no': { type: 'long' }, - 'apiCalls.copySavedObjects.overwriteEnabled.yes': { type: 'long' }, - 'apiCalls.copySavedObjects.overwriteEnabled.no': { type: 'long' }, - 'apiCalls.resolveCopySavedObjectsErrors.total': { type: 'long' }, - 'apiCalls.resolveCopySavedObjectsErrors.kibanaRequest.yes': { type: 'long' }, - 'apiCalls.resolveCopySavedObjectsErrors.kibanaRequest.no': { type: 'long' }, - 'apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.yes': { type: 'long' }, - 'apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.no': { type: 'long' }, + stackAlerts: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + actions: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + enterpriseSearch: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + fleet: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + savedObjectsTagging: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + indexPatterns: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + discover: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + canvas: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + maps: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + siem: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + monitoring: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + graph: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + uptime: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + savedObjectsManagement: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + timelion: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + dev_tools: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + advancedSettings: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + infrastructure: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + visualize: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + logs: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + dashboard: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + ml: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + apm: { + type: 'long', + _meta: { + description: 'The number of spaces which have this feature disabled.', + }, + }, + }, + available: { + type: 'boolean', + _meta: { + description: 'Indicates if the spaces feature is available in this installation.', + }, + }, + enabled: { + type: 'boolean', + _meta: { + description: 'Indicates if the spaces feature is enabled in this installation.', + }, + }, + count: { + type: 'long', + _meta: { + description: 'The number of spaces in this installation.', + }, + }, + 'apiCalls.copySavedObjects.total': { + type: 'long', + _meta: { + description: 'The number of times the "Copy Saved Objects" API has been called.', + }, + }, + 'apiCalls.copySavedObjects.kibanaRequest.yes': { + type: 'long', + _meta: { + description: + 'The number of times the "Copy Saved Objects" API has been called via the Kibana client.', + }, + }, + 'apiCalls.copySavedObjects.kibanaRequest.no': { + type: 'long', + _meta: { + description: + 'The number of times the "Copy Saved Objects" API has been called via an API consumer (e.g. curl).', + }, + }, + 'apiCalls.copySavedObjects.createNewCopiesEnabled.yes': { + type: 'long', + _meta: { + description: + 'The number of times the "Copy Saved Objects" API has been called with "createNewCopies" set to true.', + }, + }, + 'apiCalls.copySavedObjects.createNewCopiesEnabled.no': { + type: 'long', + _meta: { + description: + 'The number of times the "Copy Saved Objects" API has been called with "createNewCopies" set to false.', + }, + }, + 'apiCalls.copySavedObjects.overwriteEnabled.yes': { + type: 'long', + _meta: { + description: + 'The number of times the "Copy Saved Objects" API has been called with "overwrite" set to true.', + }, + }, + 'apiCalls.copySavedObjects.overwriteEnabled.no': { + type: 'long', + _meta: { + description: + 'The number of times the "Copy Saved Objects" API has been called with "overwrite" set to false.', + }, + }, + 'apiCalls.resolveCopySavedObjectsErrors.total': { + type: 'long', + _meta: { + description: + 'The number of times the "Resolve Copy Saved Objects Errors" API has been called.', + }, + }, + 'apiCalls.resolveCopySavedObjectsErrors.kibanaRequest.yes': { + type: 'long', + _meta: { + description: + 'The number of times the "Resolve Copy Saved Objects Errors" API has been called via the Kibana client.', + }, + }, + 'apiCalls.resolveCopySavedObjectsErrors.kibanaRequest.no': { + type: 'long', + _meta: { + description: + 'The number of times the "Resolve Copy Saved Objects Errors" API has been called via an API consumer (e.g. curl).', + }, + }, + 'apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.yes': { + type: 'long', + _meta: { + description: + 'The number of times the "Resolve Copy Saved Objects Errors" API has been called with "createNewCopies" set to true.', + }, + }, + 'apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.no': { + type: 'long', + _meta: { + description: + 'The number of times the "Resolve Copy Saved Objects Errors" API has been called with "createNewCopies" set to false.', + }, + }, }, fetch: async ({ esClient }: CollectorFetchContext) => { const { licensing, kibanaIndexConfig$, features, usageStatsServicePromise } = deps; diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 81a7030fe0edd5..ed8e44072b9145 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -3766,128 +3766,248 @@ "spaces": { "properties": { "usesFeatureControls": { - "type": "boolean" + "type": "boolean", + "_meta": { + "description": "Indicates if at least one feature is disabled in at least one space. This is a signal that space-level feature controls are in use. This does not account for role-based (security) feature controls." + } }, "disabledFeatures": { "properties": { "DYNAMIC_KEY": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "stackAlerts": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "actions": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "enterpriseSearch": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "fleet": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "savedObjectsTagging": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "indexPatterns": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "discover": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "canvas": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "maps": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "siem": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "monitoring": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "graph": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "uptime": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "savedObjectsManagement": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "timelion": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "dev_tools": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "advancedSettings": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "infrastructure": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "visualize": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "logs": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "dashboard": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "ml": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } }, "apm": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces which have this feature disabled." + } } } }, "available": { - "type": "boolean" + "type": "boolean", + "_meta": { + "description": "Indicates if the spaces feature is available in this installation." + } }, "enabled": { - "type": "boolean" + "type": "boolean", + "_meta": { + "description": "Indicates if the spaces feature is enabled in this installation." + } }, "count": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of spaces in this installation." + } }, "apiCalls.copySavedObjects.total": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Copy Saved Objects\" API has been called." + } }, "apiCalls.copySavedObjects.kibanaRequest.yes": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Copy Saved Objects\" API has been called via the Kibana client." + } }, "apiCalls.copySavedObjects.kibanaRequest.no": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Copy Saved Objects\" API has been called via an API consumer (e.g. curl)." + } }, "apiCalls.copySavedObjects.createNewCopiesEnabled.yes": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Copy Saved Objects\" API has been called with \"createNewCopies\" set to true." + } }, "apiCalls.copySavedObjects.createNewCopiesEnabled.no": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Copy Saved Objects\" API has been called with \"createNewCopies\" set to false." + } }, "apiCalls.copySavedObjects.overwriteEnabled.yes": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Copy Saved Objects\" API has been called with \"overwrite\" set to true." + } }, "apiCalls.copySavedObjects.overwriteEnabled.no": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Copy Saved Objects\" API has been called with \"overwrite\" set to false." + } }, "apiCalls.resolveCopySavedObjectsErrors.total": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Resolve Copy Saved Objects Errors\" API has been called." + } }, "apiCalls.resolveCopySavedObjectsErrors.kibanaRequest.yes": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Resolve Copy Saved Objects Errors\" API has been called via the Kibana client." + } }, "apiCalls.resolveCopySavedObjectsErrors.kibanaRequest.no": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Resolve Copy Saved Objects Errors\" API has been called via an API consumer (e.g. curl)." + } }, "apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.yes": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Resolve Copy Saved Objects Errors\" API has been called with \"createNewCopies\" set to true." + } }, "apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.no": { - "type": "long" + "type": "long", + "_meta": { + "description": "The number of times the \"Resolve Copy Saved Objects Errors\" API has been called with \"createNewCopies\" set to false." + } } } }, From 3cfb4f061ee6fda6f8e5e601dafccd99120db4b9 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Tue, 23 Mar 2021 13:44:51 -0700 Subject: [PATCH 44/93] Warns usage collection is internal only (#95121) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/usage_collection/README.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/usage_collection/README.mdx b/src/plugins/usage_collection/README.mdx index e6759b7dc6c7cc..04e1e0fbb50065 100644 --- a/src/plugins/usage_collection/README.mdx +++ b/src/plugins/usage_collection/README.mdx @@ -7,10 +7,13 @@ date: 2021-02-24 tags: ['kibana','dev', 'contributor', 'api docs'] --- + # Kibana Usage Collection Service The Usage Collection Service defines a set of APIs for other plugins to report the usage of their features. At the same time, it provides necessary the APIs for other services (i.e.: telemetry, monitoring, ...) to consume that usage data. +IMPORTANT: Usage collection and telemetry applies to internal Elastic Kibana developers only. + ## How to report my plugin's usage? The way to report the usage of any feature depends on whether the actions to track occur in the UI, or the usage depends on any server-side data. For that reason, the set of APIs exposed in the `public` and `server` contexts are different. From 3ff76fd02217ffc3f07ca5233e48df920cbbe280 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 23 Mar 2021 17:02:36 -0500 Subject: [PATCH 45/93] [Workplace Search] Fix redirect and state for personal oAuth plugin (#95238) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move source added route to top-level index component * Use state passed back from oAuth app to determine context The previous tests weren’t actually using this state so they have been updated with actual state data for proper testing --- .../workplace_search/index.test.tsx | 7 +++++++ .../applications/workplace_search/index.tsx | 5 +++++ .../add_source/add_source_logic.test.ts | 21 +++++++++++++------ .../components/add_source/add_source_logic.ts | 3 ++- .../components/source_added.tsx | 10 ++++++++- .../content_sources/sources_router.test.tsx | 2 +- .../views/content_sources/sources_router.tsx | 6 ------ 7 files changed, 39 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx index ceb1a824461325..48bdcd6551b650 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx @@ -16,6 +16,7 @@ import { shallow } from 'enzyme'; import { Layout } from '../shared/layout'; import { WorkplaceSearchHeaderActions } from './components/layout'; +import { SourceAdded } from './views/content_sources/components/source_added'; import { ErrorState } from './views/error_state'; import { Overview as OverviewMVP } from './views/overview_mvp'; import { SetupGuide } from './views/setup_guide'; @@ -94,4 +95,10 @@ describe('WorkplaceSearchConfigured', () => { expect(wrapper.find(Layout).first().prop('readOnlyMode')).toEqual(true); }); + + it('renders SourceAdded', () => { + const wrapper = shallow(); + + expect(wrapper.find(SourceAdded)).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx index 656c93053e22bb..c269a987dc0927 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx @@ -24,12 +24,14 @@ import { GROUPS_PATH, SETUP_GUIDE_PATH, SOURCES_PATH, + SOURCE_ADDED_PATH, PERSONAL_SOURCES_PATH, ORG_SETTINGS_PATH, ROLE_MAPPINGS_PATH, SECURITY_PATH, } from './routes'; import { SourcesRouter } from './views/content_sources'; +import { SourceAdded } from './views/content_sources/components/source_added'; import { SourceSubNav } from './views/content_sources/components/source_sub_nav'; import { PrivateSourcesLayout } from './views/content_sources/private_sources_layout'; import { ErrorState } from './views/error_state'; @@ -82,6 +84,9 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { + + + {errorConnecting ? : } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts index ed67eb9994bc89..d0ab40399fa597 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts @@ -275,12 +275,12 @@ describe('AddSourceLogic', () => { describe('saveSourceParams', () => { const params = { code: 'code123', - state: '"{"state": "foo"}"', - session_state: 'session123', + state: + '{"action":"create","context":"organization","service_type":"gmail","csrf_token":"token==","index_permissions":false}', }; const queryString = - 'code=code123&state=%22%7B%22state%22%3A%20%22foo%22%7D%22&session_state=session123'; + '?state=%7B%22action%22:%22create%22,%22context%22:%22organization%22,%22service_type%22:%22gmail%22,%22csrf_token%22:%22token%3D%3D%22,%22index_permissions%22:false%7D&code=code123'; const response = { serviceName: 'name', indexPermissions: false, serviceType: 'zendesk' }; @@ -303,9 +303,18 @@ describe('AddSourceLogic', () => { await nextTick(); expect(setAddedSourceSpy).toHaveBeenCalledWith(serviceName, indexPermissions, serviceType); - expect(navigateToUrl).toHaveBeenCalledWith( - getSourcesPath(SOURCES_PATH, AppLogic.values.isOrganization) - ); + expect(navigateToUrl).toHaveBeenCalledWith(getSourcesPath(SOURCES_PATH, true)); + }); + + it('redirects to private dashboard when account context', async () => { + const accountQueryString = + '?state=%7B%22action%22:%22create%22,%22context%22:%22account%22,%22service_type%22:%22gmail%22,%22csrf_token%22:%22token%3D%3D%22,%22index_permissions%22:false%7D&code=code'; + + AddSourceLogic.actions.saveSourceParams(accountQueryString); + + await nextTick(); + + expect(navigateToUrl).toHaveBeenCalledWith(getSourcesPath(SOURCES_PATH, false)); }); it('handles error', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts index 4e996aff6f5b02..e1f554d87551dc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts @@ -470,12 +470,13 @@ export const AddSourceLogic = kea { const { http } = HttpLogic.values; - const { isOrganization } = AppLogic.values; const { navigateToUrl } = KibanaLogic.values; const { setAddedSource } = SourcesLogic.actions; const params = (parseQueryParams(search) as unknown) as OauthParams; const query = { ...params, kibana_host: kibanaHost }; const route = '/api/workplace_search/sources/create'; + const state = JSON.parse(params.state); + const isOrganization = state.context !== 'account'; try { const response = await http.get(route, { query }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx index 5f1d2ed0c81c31..7c4e81d8e0755c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx @@ -11,6 +11,8 @@ import { useLocation } from 'react-router-dom'; import { Location } from 'history'; import { useActions } from 'kea'; +import { EuiPage, EuiPageBody } from '@elastic/eui'; + import { Loading } from '../../../../shared/loading'; import { AddSourceLogic } from './add_source/add_source_logic'; @@ -28,5 +30,11 @@ export const SourceAdded: React.FC = () => { saveSourceParams(search); }, []); - return ; + return ( + + + + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx index 2438061c677591..eac1bd7d3ea277 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx @@ -34,7 +34,7 @@ describe('SourcesRouter', () => { }); it('renders sources routes', () => { - const TOTAL_ROUTES = 62; + const TOTAL_ROUTES = 61; const wrapper = shallow(); expect(wrapper.find(Switch)).toHaveLength(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx index b7857cf4612a21..f4a56c8a0beaaf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx @@ -19,7 +19,6 @@ import { AppLogic } from '../../app_logic'; import { NAV } from '../../constants'; import { ADD_SOURCE_PATH, - SOURCE_ADDED_PATH, SOURCE_DETAILS_PATH, PERSONAL_SOURCES_PATH, SOURCES_PATH, @@ -27,7 +26,6 @@ import { } from '../../routes'; import { AddSource, AddSourceList } from './components/add_source'; -import { SourceAdded } from './components/source_added'; import { OrganizationSources } from './organization_sources'; import { PrivateSources } from './private_sources'; import { staticSourceData } from './source_data'; @@ -115,10 +113,6 @@ export const SourcesRouter: React.FC = () => {
- - - - From 80b05b914ac7e521155feb2a6f0bb124337ae76a Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 23 Mar 2021 17:39:57 -0500 Subject: [PATCH 46/93] [Workplace Search] Add missing tests to get 100% coverage (#95240) --- .../workplace_search/app_logic.test.ts | 13 +++- .../workplace_search/routes.test.tsx | 60 ++++++++++++++++++- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts index 8ba94e83d26cfb..82fc00923202f2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts @@ -48,7 +48,7 @@ describe('AppLogic', () => { expect(AppLogic.values).toEqual(DEFAULT_VALUES); }); - describe('initializeAppData()', () => { + describe('initializeAppData', () => { it('sets values based on passed props', () => { AppLogic.actions.initializeAppData(DEFAULT_INITIAL_APP_DATA); @@ -66,11 +66,20 @@ describe('AppLogic', () => { }); }); - describe('setContext()', () => { + describe('setContext', () => { it('sets context', () => { AppLogic.actions.setContext(true); expect(AppLogic.values.isOrganization).toEqual(true); }); }); + + describe('setSourceRestriction', () => { + it('sets property', () => { + mount(DEFAULT_INITIAL_APP_DATA); + AppLogic.actions.setSourceRestriction(true); + + expect(AppLogic.values.account.canCreatePersonalSources).toEqual(true); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx index 68bec94270a01e..7d3e19dfe626a6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx @@ -13,8 +13,15 @@ import { EuiLink } from '@elastic/eui'; import { getContentSourcePath, + getGroupPath, + getGroupSourcePrioritizationPath, + getReindexJobRoute, + getRoleMappingPath, + getSourcesPath, + GROUPS_PATH, SOURCES_PATH, PERSONAL_SOURCES_PATH, + ROLE_MAPPINGS_PATH, SOURCE_DETAILS_PATH, } from './routes'; @@ -24,17 +31,66 @@ const TestComponent = ({ id, isOrg }: { id: string; isOrg?: boolean }) => { }; describe('getContentSourcePath', () => { - it('should format org route', () => { + it('should format org path', () => { const wrapper = shallow(); const path = wrapper.find(EuiLink).prop('href'); expect(path).toEqual(`${SOURCES_PATH}/123`); }); - it('should format user route', () => { + it('should format user path', () => { const wrapper = shallow(); const path = wrapper.find(EuiLink).prop('href'); expect(path).toEqual(`${PERSONAL_SOURCES_PATH}/123`); }); }); + +describe('getGroupPath', () => { + it('should format path', () => { + expect(getGroupPath('123')).toEqual(`${GROUPS_PATH}/123`); + }); +}); + +describe('getRoleMappingPath', () => { + it('should format path', () => { + expect(getRoleMappingPath('123')).toEqual(`${ROLE_MAPPINGS_PATH}/123`); + }); +}); + +describe('getGroupSourcePrioritizationPath', () => { + it('should format path', () => { + expect(getGroupSourcePrioritizationPath('123')).toEqual( + `${GROUPS_PATH}/123/source_prioritization` + ); + }); +}); + +describe('getSourcesPath', () => { + const PATH = '/foo/123'; + + it('should format org path', () => { + expect(getSourcesPath(PATH, true)).toEqual(PATH); + }); + + it('should format user path', () => { + expect(getSourcesPath(PATH, false)).toEqual(`/p${PATH}`); + }); +}); + +describe('getReindexJobRoute', () => { + const SOURCE_ID = '234'; + const REINDEX_ID = '345'; + + it('should format org path', () => { + expect(getReindexJobRoute(SOURCE_ID, REINDEX_ID, true)).toEqual( + `/sources/${SOURCE_ID}/schema_errors/${REINDEX_ID}` + ); + }); + + it('should format user path', () => { + expect(getReindexJobRoute(SOURCE_ID, REINDEX_ID, false)).toEqual( + `/p/sources/${SOURCE_ID}/schema_errors/${REINDEX_ID}` + ); + }); +}); From 1fc50005ccc14ee94d88c7becb939402eced43b6 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Tue, 23 Mar 2021 18:54:03 -0400 Subject: [PATCH 47/93] Create best practices doc in developer guide (#94981) * Create best_practies.mdx * Update best_practices.mdx Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- dev_docs/assets/product_stages.png | Bin 0 -> 205603 bytes dev_docs/best_practices.mdx | 148 +++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 dev_docs/assets/product_stages.png create mode 100644 dev_docs/best_practices.mdx diff --git a/dev_docs/assets/product_stages.png b/dev_docs/assets/product_stages.png new file mode 100644 index 0000000000000000000000000000000000000000..d62c5570d0dde067b14422a38827a35fa65a7f79 GIT binary patch literal 205603 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

5mor-GOe{}m>D7|UhI-hkAe$}b1qNo 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 and check out all the available +platform that can simplify plugin development. + +## Performance + +Build with scalability in mind. + +- Consider data with many fields +- Consider data with high cardinality fields +- Consider large data sets, that span a long time range +- Consider slow internet and low bandwidth environments + +## Accessibility + +Did you know Kibana makes a public statement about our commitment to creating an accessible product for people +with disabilities? [We do](https://www.elastic.co/guide/en/kibana/master/accessibility.html)! It's very important +all of our apps are accessible. + +- Learn how [EUI tackles accessibility](https://elastic.github.io/eui/#/guidelines/accessibility) +- If you don't use EUI, follow the same EUI accessibility standards + + + Elasticians, check out the #accessibility channel to ask questions and receive guidance. + + +## Localization + +Kibana is translated into other languages. Use our i18n utilities to ensure your public facing strings will be translated to ensure all Kibana apps are localized. Read and adhere to our [i18n guidelines](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/GUIDELINE.md) + + + Elasticians, check out the #kibana-localization channel to ask questions and receive guidance. + + +## Styleguide + +We use es-lint rules when possible, but please review our [styleguide](https://github.com/elastic/kibana/blob/master/STYLEGUIDE.md), which includes recommendations that can't be linted on. + +Es-lint overrides on a per-plugin level are discouraged. + +## Plugin best practices + +Don't export without reason. Make your public APIs as small as possible. You will have to maintain them, and consider backward compatibility when making changes. + +Add `README.md` to all your plugins and services and include contact information. + +## Re-inventing the wheel + +Over-refactoring can be a problem in it's own right, but it's still important to be aware of the existing services that are out there and use them when it makes sense. Check out our to see what high-level services are at your disposal. In addition, our lists additional services. + +## Feature development + +### Timing + + + +Try not to put your PR in review mode, or merge large changes, right before Feature Freeze. It's inevitably one of the most volatile times for the +Kibana code base, try not to contribute to this volatility. Doing this can: + +- increase the likelyhood of conflicts from other features being merged at the last minute +- means your feature has less QA time +- means your feature gets less careful review as reviewers are often swamped at this time + +All of the above contributes to more bugs being found in the QA cycle and can cause a delay in the release. Prefer instead to merge +your large change right _after_ feature freeze. If you are worried about missing your initial release version goals, review our +[release train philophy](https://github.com/elastic/dev/blob/master/shared/time-based-releases.md). It's okay! + + + +### Size + +When possible, build features with incrementals sets of small and focused PRs, but don't check in unused code, and don't expose any feature on master that you would not be comfortable releasing. + +![product_stages](./assets/product_stages.png) + +If your feature cannot be broken down into smaller components, or multiple engineers will be contributing, you have a few other options to consider. + +**1. Hide your feature behind a feature flag** + +Features can be merged behind a flag if you are not ready to make them the default experience, but all code should still be tested, complete and bug free. + +A good question to ask yourself is, how will you feel if a customer turns this feature on? Is it usable, even if not up to the +level of something we would market? It should have some level of minimal utility. + +Another question to ask yourself is, if this feature gets cancelled, how difficult will it be to remove? + +**2. Develop on a feature branch** + +This option is useful if you have more than one contributor working on a large feature. The downside is handling code conflicts when rebasing with the main branch. + +Consider how you want to handle the PR review. Reviewing each PR going into the feature branch can lighten the review process when merging into the main branch. + +**3. Use an example plugin** + +If you are building a service for developers, create an [example plugin](https://github.com/elastic/kibana/tree/master/examples) to showcase and test intended usage. This is a great way for reviewers and PMs to play around with a feature through the UI, before the production UI is ready. This can also help developers consuming your services get hands on. + +## Embrace the monorepo + +Kibana uses a monorepo and our processes and tooling are built around this decision. Utilizing a monorepo allows us to have a consistent peer review process and enforce the same code quality standards across all of Kibana's code. It also eliminates the necessity to have separate versioning strategies and constantly coordinate changes across repos. + +When experimenting with code, it's completely fine to create a separate GitHub repo to use for initial development. Once the code has matured to a point where it's ready to be used within Kibana, it should be integrated into the Kibana GitHub repo. + +There are some exceptions where a separate repo makes sense. However, they are exceptions to the rule. A separate repo has proven beneficial when there's a dedicated team collaborating on a package which has multiple consumers, for example [EUI](https://github.com/elastic/eui). + +It may be tempting to get caught up in the dream of writing the next package which is published to npm and downloaded millions of times a week. Knowing the quality of developers that are working on Kibana, this is a real possibility. However, knowing which packages will see mass adoption is impossible to predict. Instead of jumping directly to writing code in a separate repo and accepting all of the complications that come along with it, prefer keeping code inside the Kibana repo. A [Kibana package](https://github.com/elastic/kibana/tree/master/packages) can be used to publish a package to npm, while still keeping the code inside the Kibana repo. Move code to an external repo only when there is a good reason, for example to enable external contributions. + +## Hardening + +Review the following items related to vulnerability and security risks. + +- XSS + - Check for usages of `dangerouslySetInnerHtml`, `Element.innerHTML`, `Element.outerHTML` + - Ensure all user input is properly escaped. + - Ensure any input in `$.html`, `$.append`, `$.appendTo`, $.prepend`, `$.prependTo`is escaped. Instead use`$.text`, or don't use jQuery at all. +- CSRF + - Ensure all APIs are running inside the Kibana HTTP service. +- RCE + - Ensure no usages of `eval` + - Ensure no usages of dynamic requires + - Check for template injection + - Check for usages of templating libraries, including `_.template`, and ensure that user provided input isn't influencing the template and is only used as data for rendering the template. + - Check for possible prototype pollution. +- Prototype Pollution - more info [here](https://docs.google.com/document/d/19V-d9sb6IF-fbzF4iyiPpAropQNydCnoJApzSX5FdcI/edit?usp=sharing) + - Check for instances of `anObject[a][b] = c` where a, b, and c are user defined. This includes code paths where the following logical code steps could be performed in separate files by completely different operations, or recursively using dynamic operations. + - Validate any user input, including API url-parameters/query-parameters/payloads, preferable against a schema which only allows specific keys/values. At a very minimum, black-list `__proto__` and `prototype.constructor` for use within keys + - When calling APIs which spawn new processes or potentially perform code generation from strings, defensively protect against Prototype Pollution by checking `Object.hasOwnProperty` if the arguments to the APIs originate from an Object. An example is the Code app's [spawnProcess](https://github.com/elastic/kibana/blob/b49192626a8528af5d888545fb14cd1ce66a72e7/x-pack/legacy/plugins/code/server/lsp/workspace_command.ts#L40-L44). + - Common Node.js offenders: `child_process.spawn`, `child_process.exec`, `eval`, `Function('some string')`, `vm.runIn*Context(x)` + - Common Client-side offenders: `eval`, `Function('some string')`, `setTimeout('some string', num)`, `setInterval('some string', num)` +- Check for accidental reveal of sensitive information + - The biggest culprit is errors which contain stack traces or other sensitive information which end up in the HTTP Response +- Checked for Mishandled API requests + - Ensure no sensitive cookies are forwarded to external resources. + - Ensure that all user controllable variables that are used in constructing a URL are escaped properly. This is relevant when using `transport.request` with the Elasticsearch client as no automatic escaping is performed. +- Reverse tabnabbing - https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTML5_Security_Cheat_Sheet.md#tabnabbing + - When there are user controllable links or hard-coded links to third-party domains that specify target="\_blank" or target="\_window", the `a` tag should have the rel="noreferrer noopener" attribute specified. + - Allowing users to input markdown is a common culprit, a custom link renderer should be used +- SSRF - https://www.owasp.org/index.php/Server_Side_Request_Forgery + - All network requests made from the Kibana server should use an explicit configuration or white-list specified in the `kibana.yml` From 52d0fc044adc1f705cdaef0538763d3e1503e047 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Tue, 23 Mar 2021 22:52:44 -0500 Subject: [PATCH 48/93] update codeowners (#95249) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dde90bf1bc47d0..2f2f260addb35e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -53,6 +53,7 @@ /src/plugins/navigation/ @elastic/kibana-app-services /src/plugins/share/ @elastic/kibana-app-services /src/plugins/ui_actions/ @elastic/kibana-app-services +/src/plugins/index_pattern_field_editor @elastic/kibana-app-services /x-pack/examples/ui_actions_enhanced_examples/ @elastic/kibana-app-services /x-pack/plugins/data_enhanced/ @elastic/kibana-app-services /x-pack/plugins/embeddable_enhanced/ @elastic/kibana-app-services From 585f6f2c5c9e5d8d77aa6962555bbc3828a01071 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 24 Mar 2021 09:29:38 +0100 Subject: [PATCH 49/93] Make sure color mapping setting is respected for legacy palette (#95164) --- docs/management/advanced-options.asciidoc | 2 +- src/plugins/charts/public/mocks.ts | 4 +- src/plugins/charts/public/plugin.ts | 2 +- .../public/services/legacy_colors/mock.ts | 1 + .../services/palettes/palettes.test.tsx | 55 +++++++++++++++++-- .../public/services/palettes/palettes.tsx | 7 +-- .../public/services/palettes/service.ts | 5 +- src/plugins/charts/server/plugin.ts | 2 +- 8 files changed, 61 insertions(+), 17 deletions(-) diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 5c27a7bdacdeee..446b6a2cfd8519 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -457,7 +457,7 @@ Enables the legacy charts library for aggregation-based area, line, and bar char [[visualization-colormapping]]`visualization:colorMapping`:: **This setting is deprecated and will not be supported as of 8.0.** -Maps values to specific colors in *Visualize* charts and *TSVB*. This setting does not apply to *Lens*. +Maps values to specific colors in charts using the *Compatibility* palette. [[visualization-dimmingopacity]]`visualization:dimmingOpacity`:: The opacity of the chart items that are dimmed when highlighting another element diff --git a/src/plugins/charts/public/mocks.ts b/src/plugins/charts/public/mocks.ts index a6cde79057be82..c85a91a1ef5636 100644 --- a/src/plugins/charts/public/mocks.ts +++ b/src/plugins/charts/public/mocks.ts @@ -17,13 +17,13 @@ export type Start = jest.Mocked>; const createSetupContract = (): Setup => ({ legacyColors: colorsServiceMock, theme: themeServiceMock, - palettes: paletteServiceMock.setup({} as any, {} as any), + palettes: paletteServiceMock.setup({} as any), }); const createStartContract = (): Start => ({ legacyColors: colorsServiceMock, theme: themeServiceMock, - palettes: paletteServiceMock.setup({} as any, {} as any), + palettes: paletteServiceMock.setup({} as any), }); export { colorMapsMock } from './static/color_maps/mock'; diff --git a/src/plugins/charts/public/plugin.ts b/src/plugins/charts/public/plugin.ts index a21264703f6c43..5bc0b8c84560f4 100644 --- a/src/plugins/charts/public/plugin.ts +++ b/src/plugins/charts/public/plugin.ts @@ -43,7 +43,7 @@ export class ChartsPlugin implements Plugin { - const palettes: Record = buildPalettes( - coreMock.createStart().uiSettings, - colorsServiceMock - ); + const palettes: Record = buildPalettes(colorsServiceMock); describe('default palette', () => { describe('syncColors: false', () => { it('should return different colors based on behind text flag', () => { @@ -302,6 +298,7 @@ describe('palettes', () => { beforeEach(() => { (colorsServiceMock.mappedColors.mapKeys as jest.Mock).mockClear(); + (colorsServiceMock.mappedColors.getColorFromConfig as jest.Mock).mockReset(); (colorsServiceMock.mappedColors.get as jest.Mock).mockClear(); }); @@ -323,6 +320,30 @@ describe('palettes', () => { expect(colorsServiceMock.mappedColors.get).not.toHaveBeenCalled(); }); + it('should respect the advanced settings color mapping', () => { + const configColorGetter = colorsServiceMock.mappedColors.getColorFromConfig as jest.Mock; + configColorGetter.mockImplementation(() => 'blue'); + const result = palette.getColor( + [ + { + name: 'abc', + rankAtDepth: 2, + totalSeriesAtDepth: 10, + }, + { + name: 'def', + rankAtDepth: 0, + totalSeriesAtDepth: 10, + }, + ], + { + syncColors: false, + } + ); + expect(result).toEqual('blue'); + expect(configColorGetter).toHaveBeenCalledWith('abc'); + }); + it('should return a color from the legacy palette based on position of first series', () => { const result = palette.getColor( [ @@ -363,6 +384,30 @@ describe('palettes', () => { expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledWith('abc'); }); + it('should respect the advanced settings color mapping', () => { + const configColorGetter = colorsServiceMock.mappedColors.getColorFromConfig as jest.Mock; + configColorGetter.mockImplementation(() => 'blue'); + const result = palette.getColor( + [ + { + name: 'abc', + rankAtDepth: 2, + totalSeriesAtDepth: 10, + }, + { + name: 'def', + rankAtDepth: 0, + totalSeriesAtDepth: 10, + }, + ], + { + syncColors: false, + } + ); + expect(result).toEqual('blue'); + expect(configColorGetter).toHaveBeenCalledWith('abc'); + }); + it('should always use root series', () => { palette.getColor( [ diff --git a/src/plugins/charts/public/services/palettes/palettes.tsx b/src/plugins/charts/public/services/palettes/palettes.tsx index 8a1dee72139edb..b11d598c1c1cbc 100644 --- a/src/plugins/charts/public/services/palettes/palettes.tsx +++ b/src/plugins/charts/public/services/palettes/palettes.tsx @@ -9,7 +9,6 @@ // @ts-ignore import chroma from 'chroma-js'; import { i18n } from '@kbn/i18n'; -import { IUiSettingsClient } from 'src/core/public'; import { euiPaletteColorBlind, euiPaletteCool, @@ -130,7 +129,8 @@ function buildSyncedKibanaPalette( colors.mappedColors.mapKeys([series[0].name]); outputColor = colors.mappedColors.get(series[0].name); } else { - outputColor = staticColors[series[0].rankAtDepth % staticColors.length]; + const configColor = colors.mappedColors.getColorFromConfig(series[0].name); + outputColor = configColor || staticColors[series[0].rankAtDepth % staticColors.length]; } if (!chartConfiguration.maxDepth || chartConfiguration.maxDepth === 1) { @@ -199,9 +199,8 @@ function buildCustomPalette(): PaletteDefinition { } export const buildPalettes: ( - uiSettings: IUiSettingsClient, legacyColorsService: LegacyColorsService -) => Record = (uiSettings, legacyColorsService) => { +) => Record = (legacyColorsService) => { return { default: { title: i18n.translate('charts.palettes.defaultPaletteLabel', { diff --git a/src/plugins/charts/public/services/palettes/service.ts b/src/plugins/charts/public/services/palettes/service.ts index 6090bfc0fd1408..bb9000e896742e 100644 --- a/src/plugins/charts/public/services/palettes/service.ts +++ b/src/plugins/charts/public/services/palettes/service.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { CoreSetup } from 'kibana/public'; import { ExpressionsSetup } from '../../../../../../src/plugins/expressions/public'; import { ChartsPluginSetup, @@ -24,12 +23,12 @@ export class PaletteService { private palettes: Record> | undefined = undefined; constructor() {} - public setup(core: CoreSetup, colorsService: LegacyColorsService) { + public setup(colorsService: LegacyColorsService) { return { getPalettes: async (): Promise => { if (!this.palettes) { const { buildPalettes } = await import('./palettes'); - this.palettes = buildPalettes(core.uiSettings, colorsService); + this.palettes = buildPalettes(colorsService); } return { get: (name: string) => { diff --git a/src/plugins/charts/server/plugin.ts b/src/plugins/charts/server/plugin.ts index 39a93962832f3a..63b703e6b75384 100644 --- a/src/plugins/charts/server/plugin.ts +++ b/src/plugins/charts/server/plugin.ts @@ -31,7 +31,7 @@ export class ChartsServerPlugin implements Plugin { type: 'json', description: i18n.translate('charts.advancedSettings.visualization.colorMappingText', { defaultMessage: - 'Maps values to specific colors in Visualize charts and TSVB. This setting does not apply to Lens.', + 'Maps values to specific colors in charts using the Compatibility palette.', }), deprecation: { message: i18n.translate( From 3f3cc8ee359d03675176cb0df29f6c9eea00032a Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Wed, 24 Mar 2021 09:54:08 +0100 Subject: [PATCH 50/93] Expose session invalidation API. (#92376) --- docs/api/session-management.asciidoc | 9 + .../session-management/invalidate.asciidoc | 114 ++++++ docs/user/api.asciidoc | 1 + .../security/authentication/index.asciidoc | 8 + .../user/security/session-management.asciidoc | 2 + .../authentication/authenticator.test.ts | 50 +-- .../server/authentication/authenticator.ts | 22 +- .../server/routes/session_management/index.ts | 2 + .../session_management/invalidate.test.ts | 155 ++++++++ .../routes/session_management/invalidate.ts | 49 +++ .../server/session_management/session.mock.ts | 2 +- .../server/session_management/session.test.ts | 115 ++++-- .../server/session_management/session.ts | 93 +++-- .../session_management/session_index.mock.ts | 2 +- .../session_management/session_index.test.ts | 140 ++++++- .../session_management/session_index.ts | 70 +++- x-pack/scripts/functional_tests.js | 1 + .../session_invalidate.config.ts | 55 +++ .../tests/session_invalidate/index.ts | 16 + .../tests/session_invalidate/invalidate.ts | 350 ++++++++++++++++++ 20 files changed, 1163 insertions(+), 93 deletions(-) create mode 100644 docs/api/session-management.asciidoc create mode 100644 docs/api/session-management/invalidate.asciidoc create mode 100644 x-pack/plugins/security/server/routes/session_management/invalidate.test.ts create mode 100644 x-pack/plugins/security/server/routes/session_management/invalidate.ts create mode 100644 x-pack/test/security_api_integration/session_invalidate.config.ts create mode 100644 x-pack/test/security_api_integration/tests/session_invalidate/index.ts create mode 100644 x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts diff --git a/docs/api/session-management.asciidoc b/docs/api/session-management.asciidoc new file mode 100644 index 00000000000000..7ac8b9dddcdbbb --- /dev/null +++ b/docs/api/session-management.asciidoc @@ -0,0 +1,9 @@ +[role="xpack"] +[[session-management-api]] +== User session management APIs + +The following <> management APIs are available: + +* <> to invalidate user sessions + +include::session-management/invalidate.asciidoc[] diff --git a/docs/api/session-management/invalidate.asciidoc b/docs/api/session-management/invalidate.asciidoc new file mode 100644 index 00000000000000..c3dced17e72b7d --- /dev/null +++ b/docs/api/session-management/invalidate.asciidoc @@ -0,0 +1,114 @@ +[[session-management-api-invalidate]] +=== Invalidate user sessions API +++++ +Invalidate user sessions +++++ + +experimental[] Invalidates user sessions that match provided query. + +[[session-management-api-invalidate-prereqs]] +==== Prerequisite + +To use the invalidate user sessions API, you must be a `superuser`. + +[[session-management-api-invalidate-request]] +==== Request + +`POST :/api/security/session/_invalidate` + +[role="child_attributes"] +[[session-management-api-invalidate-request-body]] +==== Request body + +`match`:: +(Required, string) Specifies how {kib} determines which sessions to invalidate. Can either be `all` to invalidate all existing sessions, or `query` to only invalidate sessions that match the query specified in the additional `query` parameter. + +`query`:: +(Optional, object) Specifies the query that {kib} uses to match the sessions to invalidate when the `match` parameter is set to `query`. You cannot use this parameter if `match` is set to `all`. ++ +.Properties of `query` +[%collapsible%open] +===== +`provider` ::: +(Required, object) Describes the <> for which to invalidate sessions. + +`type` :::: +(Required, string) The authentication provider `type`. + +`name` :::: +(Optional, string) The authentication provider `name`. + +`username` ::: +(Optional, string) The username for which to invalidate sessions. +===== + +[[session-management-api-invalidate-response-body]] +==== Response body + +`total`:: +(number) The number of successfully invalidated sessions. + +[[session-management-api-invalidate-response-codes]] +==== Response codes + +`200`:: + Indicates a successful call. + +`403`:: + Indicates that the user may not be authorized to invalidate sessions for other users. Refer to <>. + +==== Examples + +Invalidate all existing sessions: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/security/session/_invalidate +{ + "match" : "all" +} +-------------------------------------------------- +// KIBANA + +Invalidate sessions that were created by any <>: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/security/session/_invalidate +{ + "match" : "query", + "query": { + "provider" : { "type": "saml" } + } +} +-------------------------------------------------- +// KIBANA + +Invalidate sessions that were created by the <> with the name `saml1`: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/security/session/_invalidate +{ + "match" : "query", + "query": { + "provider" : { "type": "saml", "name": "saml1" } + } +} +-------------------------------------------------- +// KIBANA + +Invalidate sessions that were created by any <> for the user with the username `user@my-oidc-sso.com`: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/security/session/_invalidate +{ + "match" : "query", + "query": { + "provider" : { "type": "oidc" }, + "username": "user@my-oidc-sso.com" + } +} +-------------------------------------------------- +// KIBANA diff --git a/docs/user/api.asciidoc b/docs/user/api.asciidoc index 459dbbdd34b279..c41f3d8a829e4b 100644 --- a/docs/user/api.asciidoc +++ b/docs/user/api.asciidoc @@ -97,6 +97,7 @@ curl -X POST \ include::{kib-repo-dir}/api/features.asciidoc[] include::{kib-repo-dir}/api/spaces-management.asciidoc[] include::{kib-repo-dir}/api/role-management.asciidoc[] +include::{kib-repo-dir}/api/session-management.asciidoc[] include::{kib-repo-dir}/api/saved-objects.asciidoc[] include::{kib-repo-dir}/api/alerts.asciidoc[] include::{kib-repo-dir}/api/actions-and-connectors.asciidoc[] diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc index b2d85de91b9fcd..a4acc93310e5db 100644 --- a/docs/user/security/authentication/index.asciidoc +++ b/docs/user/security/authentication/index.asciidoc @@ -397,6 +397,14 @@ NOTE: *Public URL* is available only when anonymous access is configured and you + For more information, refer to <>. +[float] +[[anonymous-access-session]] +===== Anonymous access session + +{kib} maintains a separate <> for every anonymous user, as it does for all other authentication mechanisms. + +You can configure <> and <> for anonymous sessions the same as you do for any other session with the exception that idle timeout is explicitly disabled for anonymous sessions by default. The global <> setting doesn't affect anonymous sessions. To change the idle timeout for anonymous sessions, you must configure the provider-level <.session.idleTimeout`>> setting. + [[http-authentication]] ==== HTTP authentication diff --git a/docs/user/security/session-management.asciidoc b/docs/user/security/session-management.asciidoc index 0df5b3b31a2033..ac7a777eb05807 100644 --- a/docs/user/security/session-management.asciidoc +++ b/docs/user/security/session-management.asciidoc @@ -6,6 +6,8 @@ When you log in, {kib} creates a session that is used to authenticate subsequent When your session expires, or you log out, {kib} will invalidate your cookie and remove session information from the index. {kib} also periodically invalidates and removes any expired sessions that weren't explicitly invalidated. +To manage user sessions programmatically, {kib} exposes <>. + [[session-idle-timeout]] ==== Session idle timeout diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index e0b9144f6a66f8..be53caffc066de 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -585,8 +585,8 @@ describe('Authenticator', () => { expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).toHaveBeenCalledTimes(1); - expect(mockOptions.session.clear).toHaveBeenCalledWith(request); + expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); }); it('clears session if provider asked to do so in `succeeded` result.', async () => { @@ -605,8 +605,8 @@ describe('Authenticator', () => { expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).toHaveBeenCalledTimes(1); - expect(mockOptions.session.clear).toHaveBeenCalledWith(request); + expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); }); it('clears session if provider asked to do so in `redirected` result.', async () => { @@ -624,8 +624,8 @@ describe('Authenticator', () => { expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).toHaveBeenCalledTimes(1); - expect(mockOptions.session.clear).toHaveBeenCalledWith(request); + expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); }); describe('with Access Agreement', () => { @@ -1191,7 +1191,7 @@ describe('Authenticator', () => { expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); }); it('extends session for non-system API calls.', async () => { @@ -1213,7 +1213,7 @@ describe('Authenticator', () => { expect(mockOptions.session.extend).toHaveBeenCalledWith(request, mockSessVal); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); }); it('does not touch session for system API calls if authentication fails with non-401 reason.', async () => { @@ -1234,7 +1234,7 @@ describe('Authenticator', () => { expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); }); it('does not touch session for non-system API calls if authentication fails with non-401 reason.', async () => { @@ -1255,7 +1255,7 @@ describe('Authenticator', () => { expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); }); it('replaces existing session with the one returned by authentication provider for system API requests', async () => { @@ -1281,7 +1281,7 @@ describe('Authenticator', () => { }); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); }); it('replaces existing session with the one returned by authentication provider for non-system API requests', async () => { @@ -1307,7 +1307,7 @@ describe('Authenticator', () => { }); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); }); it('clears session if provider failed to authenticate system API request with 401 with active session.', async () => { @@ -1324,8 +1324,8 @@ describe('Authenticator', () => { AuthenticationResult.failed(Boom.unauthorized()) ); - expect(mockOptions.session.clear).toHaveBeenCalledTimes(1); - expect(mockOptions.session.clear).toHaveBeenCalledWith(request); + expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); @@ -1345,8 +1345,8 @@ describe('Authenticator', () => { AuthenticationResult.failed(Boom.unauthorized()) ); - expect(mockOptions.session.clear).toHaveBeenCalledTimes(1); - expect(mockOptions.session.clear).toHaveBeenCalledWith(request); + expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); @@ -1364,8 +1364,8 @@ describe('Authenticator', () => { AuthenticationResult.redirectTo('some-url', { state: null }) ); - expect(mockOptions.session.clear).toHaveBeenCalledTimes(1); - expect(mockOptions.session.clear).toHaveBeenCalledWith(request); + expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); @@ -1382,7 +1382,7 @@ describe('Authenticator', () => { AuthenticationResult.notHandled() ); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); @@ -1399,7 +1399,7 @@ describe('Authenticator', () => { AuthenticationResult.notHandled() ); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); @@ -1789,7 +1789,7 @@ describe('Authenticator', () => { DeauthenticationResult.notHandled() ); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); }); it('clears session and returns whatever authentication provider returns.', async () => { @@ -1804,7 +1804,7 @@ describe('Authenticator', () => { ); expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1); - expect(mockOptions.session.clear).toHaveBeenCalled(); + expect(mockOptions.session.invalidate).toHaveBeenCalled(); }); it('if session does not exist but provider name is valid, returns whatever authentication provider returns.', async () => { @@ -1823,7 +1823,7 @@ describe('Authenticator', () => { expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1); expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledWith(request, null); - expect(mockOptions.session.clear).toHaveBeenCalled(); + expect(mockOptions.session.invalidate).toHaveBeenCalled(); }); it('if session does not exist and provider name is not available, returns whatever authentication provider returns.', async () => { @@ -1840,7 +1840,7 @@ describe('Authenticator', () => { expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1); expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledWith(request); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); }); it('returns `notHandled` if session does not exist and provider name is invalid', async () => { @@ -1852,7 +1852,7 @@ describe('Authenticator', () => { ); expect(mockBasicAuthenticationProvider.logout).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).toHaveBeenCalled(); + expect(mockOptions.session.invalidate).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index ff6f3ff0c2ae73..f86ff54963da9a 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -396,7 +396,7 @@ export class Authenticator { sessionValue?.provider.name ?? request.url.searchParams.get(LOGOUT_PROVIDER_QUERY_STRING_PARAMETER); if (suggestedProviderName) { - await this.session.clear(request); + await this.invalidateSessionValue(request); // Provider name may be passed in a query param and sourced from the browser's local storage; // hence, we can't assume that this provider exists, so we have to check it. @@ -522,7 +522,7 @@ export class Authenticator { this.logger.warn( `Attempted to retrieve session for the "${existingSessionValue.provider.type}/${existingSessionValue.provider.name}" provider, but it is not configured.` ); - await this.session.clear(request); + await this.invalidateSessionValue(request); return null; } @@ -556,7 +556,7 @@ export class Authenticator { // attempt didn't fail. if (authenticationResult.shouldClearState()) { this.logger.debug('Authentication provider requested to invalidate existing session.'); - await this.session.clear(request); + await this.invalidateSessionValue(request); return null; } @@ -570,7 +570,7 @@ export class Authenticator { if (authenticationResult.failed()) { if (ownsSession && getErrorStatusCode(authenticationResult.error) === 401) { this.logger.debug('Authentication attempt failed, existing session will be invalidated.'); - await this.session.clear(request); + await this.invalidateSessionValue(request); } return null; } @@ -608,17 +608,17 @@ export class Authenticator { this.logger.debug( 'Authentication provider has changed, existing session will be invalidated.' ); - await this.session.clear(request); + await this.invalidateSessionValue(request); existingSessionValue = null; } else if (sessionHasBeenAuthenticated) { this.logger.debug( 'Session is authenticated, existing unauthenticated session will be invalidated.' ); - await this.session.clear(request); + await this.invalidateSessionValue(request); existingSessionValue = null; } else if (usernameHasChanged) { this.logger.debug('Username has changed, existing session will be invalidated.'); - await this.session.clear(request); + await this.invalidateSessionValue(request); existingSessionValue = null; } @@ -651,6 +651,14 @@ export class Authenticator { }; } + /** + * Invalidates session value associated with the specified request. + * @param request Request instance. + */ + private async invalidateSessionValue(request: KibanaRequest) { + await this.session.invalidate(request, { match: 'current' }); + } + /** * Checks whether request should be redirected to the Login Selector UI. * @param request Request instance. diff --git a/x-pack/plugins/security/server/routes/session_management/index.ts b/x-pack/plugins/security/server/routes/session_management/index.ts index 1348179386ce08..16cda7b7b409df 100644 --- a/x-pack/plugins/security/server/routes/session_management/index.ts +++ b/x-pack/plugins/security/server/routes/session_management/index.ts @@ -8,8 +8,10 @@ import type { RouteDefinitionParams } from '../'; import { defineSessionExtendRoutes } from './extend'; import { defineSessionInfoRoutes } from './info'; +import { defineInvalidateSessionsRoutes } from './invalidate'; export function defineSessionManagementRoutes(params: RouteDefinitionParams) { defineSessionInfoRoutes(params); defineSessionExtendRoutes(params); + defineInvalidateSessionsRoutes(params); } diff --git a/x-pack/plugins/security/server/routes/session_management/invalidate.test.ts b/x-pack/plugins/security/server/routes/session_management/invalidate.test.ts new file mode 100644 index 00000000000000..215a66a3364e51 --- /dev/null +++ b/x-pack/plugins/security/server/routes/session_management/invalidate.test.ts @@ -0,0 +1,155 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ObjectType } from '@kbn/config-schema'; +import type { PublicMethodsOf } from '@kbn/utility-types'; + +import type { RequestHandler, RouteConfig } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; +import { httpServerMock } from '../../../../../../src/core/server/mocks'; +import type { Session } from '../../session_management'; +import { sessionMock } from '../../session_management/session.mock'; +import type { SecurityRequestHandlerContext, SecurityRouter } from '../../types'; +import { routeDefinitionParamsMock } from '../index.mock'; +import { defineInvalidateSessionsRoutes } from './invalidate'; + +describe('Invalidate sessions routes', () => { + let router: jest.Mocked; + let session: jest.Mocked>; + beforeEach(() => { + const routeParamsMock = routeDefinitionParamsMock.create(); + router = routeParamsMock.router; + + session = sessionMock.create(); + routeParamsMock.getSession.mockReturnValue(session); + + defineInvalidateSessionsRoutes(routeParamsMock); + }); + + describe('invalidate sessions', () => { + let routeHandler: RequestHandler; + let routeConfig: RouteConfig; + beforeEach(() => { + const [extendRouteConfig, extendRouteHandler] = router.post.mock.calls.find( + ([{ path }]) => path === '/api/security/session/_invalidate' + )!; + + routeConfig = extendRouteConfig; + routeHandler = extendRouteHandler; + }); + + it('correctly defines route.', () => { + expect(routeConfig.options).toEqual({ tags: ['access:sessionManagement'] }); + + const bodySchema = (routeConfig.validate as any).body as ObjectType; + expect(() => bodySchema.validate({})).toThrowErrorMatchingInlineSnapshot( + `"[match]: expected at least one defined value but got [undefined]"` + ); + expect(() => bodySchema.validate({ match: 'current' })).toThrowErrorMatchingInlineSnapshot(` + "[match]: types that failed validation: + - [match.0]: expected value to equal [all] + - [match.1]: expected value to equal [query]" + `); + expect(() => + bodySchema.validate({ match: 'all', query: { provider: { type: 'basic' } } }) + ).toThrowErrorMatchingInlineSnapshot(`"[query]: a value wasn't expected to be present"`); + expect(() => bodySchema.validate({ match: 'query' })).toThrowErrorMatchingInlineSnapshot( + `"[query.provider.type]: expected value of type [string] but got [undefined]"` + ); + expect(() => + bodySchema.validate({ match: 'query', query: { username: 'user' } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[query.provider.type]: expected value of type [string] but got [undefined]"` + ); + expect(() => + bodySchema.validate({ + match: 'query', + query: { provider: { name: 'basic1' }, username: 'user' }, + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[query.provider.type]: expected value of type [string] but got [undefined]"` + ); + + expect(bodySchema.validate({ match: 'all' })).toEqual({ match: 'all' }); + expect( + bodySchema.validate({ match: 'query', query: { provider: { type: 'basic' } } }) + ).toEqual({ + match: 'query', + query: { provider: { type: 'basic' } }, + }); + expect( + bodySchema.validate({ + match: 'query', + query: { provider: { type: 'basic', name: 'basic1' } }, + }) + ).toEqual({ match: 'query', query: { provider: { type: 'basic', name: 'basic1' } } }); + expect( + bodySchema.validate({ + match: 'query', + query: { provider: { type: 'basic' }, username: 'user' }, + }) + ).toEqual({ match: 'query', query: { provider: { type: 'basic' }, username: 'user' } }); + expect( + bodySchema.validate({ + match: 'query', + query: { provider: { type: 'basic', name: 'basic1' }, username: 'user' }, + }) + ).toEqual({ + match: 'query', + query: { provider: { type: 'basic', name: 'basic1' }, username: 'user' }, + }); + }); + + it('properly constructs `query` match filter.', async () => { + session.invalidate.mockResolvedValue(30); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { + match: 'query', + query: { provider: { type: 'basic', name: 'basic1' }, username: 'user' }, + }, + }); + await expect( + routeHandler( + ({} as unknown) as SecurityRequestHandlerContext, + mockRequest, + kibanaResponseFactory + ) + ).resolves.toEqual({ + status: 200, + options: { body: { total: 30 } }, + payload: { total: 30 }, + }); + + expect(session.invalidate).toHaveBeenCalledTimes(1); + expect(session.invalidate).toHaveBeenCalledWith(mockRequest, { + match: 'query', + query: { provider: { type: 'basic', name: 'basic1' }, username: 'user' }, + }); + }); + + it('properly constructs `all` match filter.', async () => { + session.invalidate.mockResolvedValue(30); + + const mockRequest = httpServerMock.createKibanaRequest({ body: { match: 'all' } }); + await expect( + routeHandler( + ({} as unknown) as SecurityRequestHandlerContext, + mockRequest, + kibanaResponseFactory + ) + ).resolves.toEqual({ + status: 200, + options: { body: { total: 30 } }, + payload: { total: 30 }, + }); + + expect(session.invalidate).toHaveBeenCalledTimes(1); + expect(session.invalidate).toHaveBeenCalledWith(mockRequest, { match: 'all' }); + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/session_management/invalidate.ts b/x-pack/plugins/security/server/routes/session_management/invalidate.ts new file mode 100644 index 00000000000000..3416be3dd29657 --- /dev/null +++ b/x-pack/plugins/security/server/routes/session_management/invalidate.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import type { RouteDefinitionParams } from '..'; + +/** + * Defines routes required for session invalidation. + */ +export function defineInvalidateSessionsRoutes({ router, getSession }: RouteDefinitionParams) { + router.post( + { + path: '/api/security/session/_invalidate', + validate: { + body: schema.object({ + match: schema.oneOf([schema.literal('all'), schema.literal('query')]), + query: schema.conditional( + schema.siblingRef('match'), + schema.literal('query'), + schema.object({ + provider: schema.object({ + type: schema.string(), + name: schema.maybe(schema.string()), + }), + username: schema.maybe(schema.string()), + }), + schema.never() + ), + }), + }, + options: { tags: ['access:sessionManagement'] }, + }, + async (_context, request, response) => { + return response.ok({ + body: { + total: await getSession().invalidate(request, { + match: request.body.match, + query: request.body.query, + }), + }, + }); + } + ); +} diff --git a/x-pack/plugins/security/server/session_management/session.mock.ts b/x-pack/plugins/security/server/session_management/session.mock.ts index dfe1293f57e927..65ae43e5fa705b 100644 --- a/x-pack/plugins/security/server/session_management/session.mock.ts +++ b/x-pack/plugins/security/server/session_management/session.mock.ts @@ -18,7 +18,7 @@ export const sessionMock = { create: jest.fn(), update: jest.fn(), extend: jest.fn(), - clear: jest.fn(), + invalidate: jest.fn(), }), createValue: (sessionValue: Partial = {}): SessionValue => ({ diff --git a/x-pack/plugins/security/server/session_management/session.test.ts b/x-pack/plugins/security/server/session_management/session.test.ts index bd94c9483d5612..dfe6ba343ca3c0 100644 --- a/x-pack/plugins/security/server/session_management/session.test.ts +++ b/x-pack/plugins/security/server/session_management/session.test.ts @@ -103,7 +103,7 @@ describe('Session', () => { await expect(session.get(httpServerMock.createKibanaRequest())).resolves.toBeNull(); expect(mockSessionCookie.clear).toHaveBeenCalledTimes(1); - expect(mockSessionIndex.clear).toHaveBeenCalledTimes(1); + expect(mockSessionIndex.invalidate).toHaveBeenCalledTimes(1); }); it('clears session value if session is expired because of lifespan', async () => { @@ -122,7 +122,7 @@ describe('Session', () => { await expect(session.get(httpServerMock.createKibanaRequest())).resolves.toBeNull(); expect(mockSessionCookie.clear).toHaveBeenCalledTimes(1); - expect(mockSessionIndex.clear).toHaveBeenCalledTimes(1); + expect(mockSessionIndex.invalidate).toHaveBeenCalledTimes(1); }); it('clears session value if session cookie does not have corresponding session index value', async () => { @@ -151,7 +151,7 @@ describe('Session', () => { await expect(session.get(httpServerMock.createKibanaRequest())).resolves.toBeNull(); expect(mockSessionCookie.clear).toHaveBeenCalledTimes(1); - expect(mockSessionIndex.clear).toHaveBeenCalledTimes(1); + expect(mockSessionIndex.invalidate).toHaveBeenCalledTimes(1); }); it('clears session value if session index value content cannot be decrypted because of wrong AAD', async () => { @@ -170,7 +170,7 @@ describe('Session', () => { await expect(session.get(httpServerMock.createKibanaRequest())).resolves.toBeNull(); expect(mockSessionCookie.clear).toHaveBeenCalledTimes(1); - expect(mockSessionIndex.clear).toHaveBeenCalledTimes(1); + expect(mockSessionIndex.invalidate).toHaveBeenCalledTimes(1); }); it('returns session value with decrypted content', async () => { @@ -199,7 +199,7 @@ describe('Session', () => { username: 'some-user', }); expect(mockSessionCookie.clear).not.toHaveBeenCalled(); - expect(mockSessionIndex.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); }); }); @@ -279,7 +279,7 @@ describe('Session', () => { const mockRequest = httpServerMock.createKibanaRequest(); await expect(session.update(mockRequest, sessionMock.createValue())).resolves.toBeNull(); - expect(mockSessionIndex.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); expect(mockSessionCookie.clear).toHaveBeenCalledTimes(1); expect(mockSessionCookie.clear).toHaveBeenCalledWith(mockRequest); }); @@ -432,7 +432,7 @@ describe('Session', () => { }) ); - expect(mockSessionIndex.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); expect(mockSessionCookie.clear).not.toHaveBeenCalled(); } @@ -485,7 +485,7 @@ describe('Session', () => { ); expect(mockSessionIndex.update).not.toHaveBeenCalled(); expect(mockSessionCookie.set).not.toHaveBeenCalled(); - expect(mockSessionIndex.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); expect(mockSessionCookie.clear).not.toHaveBeenCalled(); }); @@ -563,7 +563,7 @@ describe('Session', () => { mockRequest, expect.objectContaining({ idleTimeoutExpiration: expectedNewExpiration }) ); - expect(mockSessionIndex.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); expect(mockSessionCookie.clear).not.toHaveBeenCalled(); }); @@ -582,7 +582,7 @@ describe('Session', () => { ) ).resolves.toBeNull(); - expect(mockSessionIndex.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); expect(mockSessionCookie.clear).toHaveBeenCalledTimes(1); expect(mockSessionCookie.clear).toHaveBeenCalledWith(mockRequest); }); @@ -625,7 +625,7 @@ describe('Session', () => { mockRequest, expect.objectContaining({ idleTimeoutExpiration: expectedNewExpiration }) ); - expect(mockSessionIndex.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); expect(mockSessionCookie.clear).not.toHaveBeenCalled(); }); @@ -653,7 +653,7 @@ describe('Session', () => { mockRequest, expect.objectContaining({ idleTimeoutExpiration: expectedNewExpiration }) ); - expect(mockSessionIndex.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); expect(mockSessionCookie.clear).not.toHaveBeenCalled(); }); @@ -696,7 +696,7 @@ describe('Session', () => { mockRequest, expect.objectContaining({ idleTimeoutExpiration: expectedNewExpiration }) ); - expect(mockSessionIndex.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); expect(mockSessionCookie.clear).not.toHaveBeenCalled(); }); }); @@ -764,7 +764,7 @@ describe('Session', () => { ); } - expect(mockSessionIndex.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); expect(mockSessionCookie.clear).not.toHaveBeenCalled(); } @@ -786,27 +786,98 @@ describe('Session', () => { }); }); - describe('#clear', () => { - it('does not clear anything if session does not exist', async () => { + describe('#invalidate', () => { + beforeEach(() => { + mockSessionCookie.get.mockResolvedValue(sessionCookieMock.createValue()); + mockSessionIndex.invalidate.mockResolvedValue(10); + }); + + it('[match=current] does not clear anything if session does not exist', async () => { mockSessionCookie.get.mockResolvedValue(null); - await session.clear(httpServerMock.createKibanaRequest()); + await session.invalidate(httpServerMock.createKibanaRequest(), { match: 'current' }); - expect(mockSessionIndex.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); expect(mockSessionCookie.clear).not.toHaveBeenCalled(); }); - it('clears both session cookie and session index', async () => { + it('[match=current] clears both session cookie and session index', async () => { mockSessionCookie.get.mockResolvedValue(sessionCookieMock.createValue()); const mockRequest = httpServerMock.createKibanaRequest(); - await session.clear(mockRequest); + await session.invalidate(mockRequest, { match: 'current' }); - expect(mockSessionIndex.clear).toHaveBeenCalledTimes(1); - expect(mockSessionIndex.clear).toHaveBeenCalledWith('some-long-sid'); + expect(mockSessionIndex.invalidate).toHaveBeenCalledTimes(1); + expect(mockSessionIndex.invalidate).toHaveBeenCalledWith({ + match: 'sid', + sid: 'some-long-sid', + }); expect(mockSessionCookie.clear).toHaveBeenCalledTimes(1); expect(mockSessionCookie.clear).toHaveBeenCalledWith(mockRequest); }); + + it('[match=all] clears all sessions even if current initiator request does not have a session', async () => { + mockSessionCookie.get.mockResolvedValue(null); + + await expect( + session.invalidate(httpServerMock.createKibanaRequest(), { match: 'all' }) + ).resolves.toBe(10); + + expect(mockSessionCookie.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).toHaveBeenCalledTimes(1); + expect(mockSessionIndex.invalidate).toHaveBeenCalledWith({ match: 'all' }); + }); + + it('[match=query] properly forwards filter with the provider type to the session index', async () => { + await expect( + session.invalidate(httpServerMock.createKibanaRequest(), { + match: 'query', + query: { provider: { type: 'basic' } }, + }) + ).resolves.toBe(10); + + expect(mockSessionCookie.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).toHaveBeenCalledTimes(1); + expect(mockSessionIndex.invalidate).toHaveBeenCalledWith({ + match: 'query', + query: { provider: { type: 'basic' } }, + }); + }); + + it('[match=query] properly forwards filter with the provider type and provider name to the session index', async () => { + await expect( + session.invalidate(httpServerMock.createKibanaRequest(), { + match: 'query', + query: { provider: { type: 'basic', name: 'basic1' } }, + }) + ).resolves.toBe(10); + + expect(mockSessionCookie.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).toHaveBeenCalledTimes(1); + expect(mockSessionIndex.invalidate).toHaveBeenCalledWith({ + match: 'query', + query: { provider: { type: 'basic', name: 'basic1' } }, + }); + }); + + it('[match=query] properly forwards filter with the provider type, provider name, and username hash to the session index', async () => { + await expect( + session.invalidate(httpServerMock.createKibanaRequest(), { + match: 'query', + query: { provider: { type: 'basic', name: 'basic1' }, username: 'elastic' }, + }) + ).resolves.toBe(10); + + expect(mockSessionCookie.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).toHaveBeenCalledTimes(1); + expect(mockSessionIndex.invalidate).toHaveBeenCalledWith({ + match: 'query', + query: { + provider: { type: 'basic', name: 'basic1' }, + usernameHash: 'eb28536c8ead72bf81a0a9226e38fc9bad81f5e07c2081bb801b2a5c8842924e', + }, + }); + }); }); }); diff --git a/x-pack/plugins/security/server/session_management/session.ts b/x-pack/plugins/security/server/session_management/session.ts index 7fada4d1730cd0..8d2b56b4d2b7a4 100644 --- a/x-pack/plugins/security/server/session_management/session.ts +++ b/x-pack/plugins/security/server/session_management/session.ts @@ -79,6 +79,18 @@ export interface SessionValueContentToEncrypt { state: unknown; } +/** + * Filter provided for the `Session.invalidate` method that determines which session values should + * be invalidated. It can have three possible types: + * - `all` means that all existing active and inactive sessions should be invalidated. + * - `current` means that session associated with the current request should be invalidated. + * - `query` means that only sessions that match specified query should be invalidated. + */ +export type InvalidateSessionsFilter = + | { match: 'all' } + | { match: 'current' } + | { match: 'query'; query: { provider: { type: string; name?: string }; username?: string } }; + /** * The SIDs and AAD must be unpredictable to prevent guessing attacks, where an attacker is able to * guess or predict the ID of a valid session through statistical analysis techniques. That's why we @@ -133,7 +145,7 @@ export class Session { (sessionCookieValue.lifespanExpiration && sessionCookieValue.lifespanExpiration < now) ) { sessionLogger.debug('Session has expired and will be invalidated.'); - await this.clear(request); + await this.invalidate(request, { match: 'current' }); return null; } @@ -155,7 +167,7 @@ export class Session { sessionLogger.warn( `Unable to decrypt session content, session will be invalidated: ${err.message}` ); - await this.clear(request); + await this.invalidate(request, { match: 'current' }); return null; } @@ -194,7 +206,7 @@ export class Session { ...publicSessionValue, ...sessionExpirationInfo, sid, - usernameHash: username && createHash('sha3-256').update(username).digest('hex'), + usernameHash: username && Session.getUsernameHash(username), content: await this.crypto.encrypt(JSON.stringify({ username, state }), aad), }); @@ -230,7 +242,7 @@ export class Session { ...sessionValue.metadata.index, ...publicSessionInfo, ...sessionExpirationInfo, - usernameHash: username && createHash('sha3-256').update(username).digest('hex'), + usernameHash: username && Session.getUsernameHash(username), content: await this.crypto.encrypt( JSON.stringify({ username, state }), sessionCookieValue.aad @@ -358,24 +370,53 @@ export class Session { } /** - * Clears session value for the specified request. - * @param request Request instance to clear session value for. + * Invalidates sessions that match the specified filter. + * @param request Request instance initiated invalidation. + * @param filter Filter that narrows down the list of the sessions that should be invalidated. */ - async clear(request: KibanaRequest) { + async invalidate(request: KibanaRequest, filter: InvalidateSessionsFilter) { + // We don't require request to have the associated session, but nevertheless we still want to + // log the SID if session is available. const sessionCookieValue = await this.options.sessionCookie.get(request); - if (!sessionCookieValue) { - return; - } - - const sessionLogger = this.getLoggerForSID(sessionCookieValue.sid); - sessionLogger.debug('Invalidating session.'); + const sessionLogger = this.getLoggerForSID(sessionCookieValue?.sid); + + // We clear session cookie only when the current session should be invalidated since it's the + // only case when this action is explicitly and unequivocally requested. This behavior doesn't + // introduce any risk since even if the current session has been affected the session cookie + // will be automatically invalidated as soon as client attempts to re-use it due to missing + // underlying session index value. + let invalidateIndexValueFilter; + if (filter.match === 'current') { + if (!sessionCookieValue) { + return; + } - await Promise.all([ - this.options.sessionCookie.clear(request), - this.options.sessionIndex.clear(sessionCookieValue.sid), - ]); + sessionLogger.debug('Invalidating current session.'); + await this.options.sessionCookie.clear(request); + invalidateIndexValueFilter = { match: 'sid' as 'sid', sid: sessionCookieValue.sid }; + } else if (filter.match === 'all') { + sessionLogger.debug('Invalidating all sessions.'); + invalidateIndexValueFilter = filter; + } else { + sessionLogger.debug( + `Invalidating sessions that match query: ${JSON.stringify( + filter.query.username ? { ...filter.query, username: '[REDACTED]' } : filter.query + )}.` + ); + invalidateIndexValueFilter = filter.query.username + ? { + ...filter, + query: { + provider: filter.query.provider, + usernameHash: Session.getUsernameHash(filter.query.username), + }, + } + : filter; + } - sessionLogger.debug('Successfully invalidated session.'); + const invalidatedCount = await this.options.sessionIndex.invalidate(invalidateIndexValueFilter); + sessionLogger.debug(`Successfully invalidated ${invalidatedCount} session(s).`); + return invalidatedCount; } private calculateExpiry( @@ -414,9 +455,19 @@ export class Session { /** * Creates logger scoped to a specified session ID. - * @param sid Session ID to create logger for. + * @param [sid] Session ID to create logger for. + */ + private getLoggerForSID(sid?: string) { + return this.options.logger.get(sid?.slice(-10) ?? 'no_session'); + } + + /** + * Generates a sha3-256 hash for the specified `username`. The hash is intended to be stored in + * the session index to allow querying user specific sessions and don't expose the original + * `username` at the same time. + * @param username Username string to generate hash for. */ - private getLoggerForSID(sid: string) { - return this.options.logger.get(sid?.slice(-10)); + private static getUsernameHash(username: string) { + return createHash('sha3-256').update(username).digest('hex'); } } diff --git a/x-pack/plugins/security/server/session_management/session_index.mock.ts b/x-pack/plugins/security/server/session_management/session_index.mock.ts index 56049a3ae92055..9498b60d916a29 100644 --- a/x-pack/plugins/security/server/session_management/session_index.mock.ts +++ b/x-pack/plugins/security/server/session_management/session_index.mock.ts @@ -14,7 +14,7 @@ export const sessionIndexMock = { get: jest.fn(), create: jest.fn(), update: jest.fn(), - clear: jest.fn(), + invalidate: jest.fn(), initialize: jest.fn(), cleanUp: jest.fn(), }), diff --git a/x-pack/plugins/security/server/session_management/session_index.test.ts b/x-pack/plugins/security/server/session_management/session_index.test.ts index 2b3ec0adeb51ea..b5b4f644389020 100644 --- a/x-pack/plugins/security/server/session_management/session_index.test.ts +++ b/x-pack/plugins/security/server/session_management/session_index.test.ts @@ -162,7 +162,7 @@ describe('Session index', () => { }); }); - describe('cleanUp', () => { + describe('#cleanUp', () => { const now = 123456; beforeEach(() => { mockElasticsearchClient.deleteByQuery.mockResolvedValue( @@ -797,18 +797,26 @@ describe('Session index', () => { }); }); - describe('#clear', () => { - it('throws if call to Elasticsearch fails', async () => { + describe('#invalidate', () => { + beforeEach(() => { + mockElasticsearchClient.deleteByQuery.mockResolvedValue( + securityMock.createApiResponse({ body: { deleted: 10 } }) + ); + }); + + it('[match=sid] throws if call to Elasticsearch fails', async () => { const failureReason = new errors.ResponseError( securityMock.createApiResponse(securityMock.createApiResponse({ body: { type: 'Uh oh.' } })) ); mockElasticsearchClient.delete.mockRejectedValue(failureReason); - await expect(sessionIndex.clear('some-long-sid')).rejects.toBe(failureReason); + await expect(sessionIndex.invalidate({ match: 'sid', sid: 'some-long-sid' })).rejects.toBe( + failureReason + ); }); - it('properly removes session value from the index', async () => { - await sessionIndex.clear('some-long-sid'); + it('[match=sid] properly removes session value from the index', async () => { + await sessionIndex.invalidate({ match: 'sid', sid: 'some-long-sid' }); expect(mockElasticsearchClient.delete).toHaveBeenCalledTimes(1); expect(mockElasticsearchClient.delete).toHaveBeenCalledWith( @@ -816,5 +824,125 @@ describe('Session index', () => { { ignore: [404] } ); }); + + it('[match=all] throws if call to Elasticsearch fails', async () => { + const failureReason = new errors.ResponseError( + securityMock.createApiResponse(securityMock.createApiResponse({ body: { type: 'Uh oh.' } })) + ); + mockElasticsearchClient.deleteByQuery.mockRejectedValue(failureReason); + + await expect(sessionIndex.invalidate({ match: 'all' })).rejects.toBe(failureReason); + }); + + it('[match=all] properly constructs query', async () => { + await expect(sessionIndex.invalidate({ match: 'all' })).resolves.toBe(10); + + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledWith({ + index: indexName, + refresh: true, + body: { query: { match_all: {} } }, + }); + }); + + it('[match=query] throws if call to Elasticsearch fails', async () => { + const failureReason = new errors.ResponseError( + securityMock.createApiResponse(securityMock.createApiResponse({ body: { type: 'Uh oh.' } })) + ); + mockElasticsearchClient.deleteByQuery.mockRejectedValue(failureReason); + + await expect( + sessionIndex.invalidate({ match: 'query', query: { provider: { type: 'basic' } } }) + ).rejects.toBe(failureReason); + }); + + it('[match=query] when only provider type is specified', async () => { + await expect( + sessionIndex.invalidate({ match: 'query', query: { provider: { type: 'basic' } } }) + ).resolves.toBe(10); + + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledWith({ + index: indexName, + refresh: true, + body: { query: { bool: { must: [{ term: { 'provider.type': 'basic' } }] } } }, + }); + }); + + it('[match=query] when both provider type and provider name are specified', async () => { + await expect( + sessionIndex.invalidate({ + match: 'query', + query: { provider: { type: 'basic', name: 'basic1' } }, + }) + ).resolves.toBe(10); + + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledWith({ + index: indexName, + refresh: true, + body: { + query: { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic1' } }, + ], + }, + }, + }, + }); + }); + + it('[match=query] when both provider type and username hash are specified', async () => { + await expect( + sessionIndex.invalidate({ + match: 'query', + query: { provider: { type: 'basic' }, usernameHash: 'some-hash' }, + }) + ).resolves.toBe(10); + + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledWith({ + index: indexName, + refresh: true, + body: { + query: { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { usernameHash: 'some-hash' } }, + ], + }, + }, + }, + }); + }); + + it('[match=query] when provider type, provider name, and username hash are specified', async () => { + await expect( + sessionIndex.invalidate({ + match: 'query', + query: { provider: { type: 'basic', name: 'basic1' }, usernameHash: 'some-hash' }, + }) + ).resolves.toBe(10); + + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledWith({ + index: indexName, + refresh: true, + body: { + query: { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic1' } }, + { term: { usernameHash: 'some-hash' } }, + ], + }, + }, + }, + }); + }); }); }); diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index 828c8fa11acdd4..1b5c820ec47106 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -17,6 +17,18 @@ export interface SessionIndexOptions { readonly logger: Logger; } +/** + * Filter provided for the `SessionIndex.invalidate` method that determines which session index + * values should be invalidated (removed from the index). It can have three possible types: + * - `all` means that all existing active and inactive sessions should be invalidated. + * - `sid` means that only session with the specified SID should be invalidated. + * - `query` means that only sessions that match specified query should be invalidated. + */ +export type InvalidateSessionsFilter = + | { match: 'all' } + | { match: 'sid'; sid: string } + | { match: 'query'; query: { provider: { type: string; name?: string }; usernameHash?: string } }; + /** * Version of the current session index template. */ @@ -237,19 +249,57 @@ export class SessionIndex { } /** - * Clears session value with the specified ID. - * @param sid Session ID to clear. + * Clears session value(s) determined by the specified filter. + * @param filter Filter that narrows down the list of the session values that should be cleared. */ - async clear(sid: string) { + async invalidate(filter: InvalidateSessionsFilter) { + if (filter.match === 'sid') { + try { + // We don't specify primary term and sequence number as delete should always take precedence + // over any updates that could happen in the meantime. + const { statusCode } = await this.options.elasticsearchClient.delete( + { id: filter.sid, index: this.indexName, refresh: 'wait_for' }, + { ignore: [404] } + ); + + // 404 means the session with such SID wasn't found and hence nothing was removed. + return statusCode !== 404 ? 1 : 0; + } catch (err) { + this.options.logger.error(`Failed to clear session value: ${err.message}`); + throw err; + } + } + + // If filter is specified we should clear only session values that are matched by the filter. + // Otherwise all session values should be cleared. + let deleteQuery; + if (filter.match === 'query') { + deleteQuery = { + bool: { + must: [ + { term: { 'provider.type': filter.query.provider.type } }, + ...(filter.query.provider.name + ? [{ term: { 'provider.name': filter.query.provider.name } }] + : []), + ...(filter.query.usernameHash + ? [{ term: { usernameHash: filter.query.usernameHash } }] + : []), + ], + }, + }; + } else { + deleteQuery = { match_all: {} }; + } + try { - // We don't specify primary term and sequence number as delete should always take precedence - // over any updates that could happen in the meantime. - await this.options.elasticsearchClient.delete( - { id: sid, index: this.indexName, refresh: 'wait_for' }, - { ignore: [404] } - ); + const { body: response } = await this.options.elasticsearchClient.deleteByQuery({ + index: this.indexName, + refresh: true, + body: { query: deleteQuery }, + }); + return response.deleted as number; } catch (err) { - this.options.logger.error(`Failed to clear session value: ${err.message}`); + this.options.logger.error(`Failed to clear session value(s): ${err.message}`); throw err; } } diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 132915922fcea2..88c4410fde9415 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -40,6 +40,7 @@ const onlyNotInCoverageTests = [ require.resolve('../test/plugin_api_integration/config.ts'), require.resolve('../test/security_api_integration/saml.config.ts'), require.resolve('../test/security_api_integration/session_idle.config.ts'), + require.resolve('../test/security_api_integration/session_invalidate.config.ts'), require.resolve('../test/security_api_integration/session_lifespan.config.ts'), require.resolve('../test/security_api_integration/login_selector.config.ts'), require.resolve('../test/security_api_integration/audit.config.ts'), diff --git a/x-pack/test/security_api_integration/session_invalidate.config.ts b/x-pack/test/security_api_integration/session_invalidate.config.ts new file mode 100644 index 00000000000000..82510062035a95 --- /dev/null +++ b/x-pack/test/security_api_integration/session_invalidate.config.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { resolve } from 'path'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { services } from './services'; + +// the default export of config files must be a config provider +// that returns an object with the projects config values +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); + + const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port'); + const idpPath = resolve(__dirname, './fixtures/saml/idp_metadata.xml'); + + return { + testFiles: [resolve(__dirname, './tests/session_invalidate')], + services, + servers: xPackAPITestsConfig.get('servers'), + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + serverArgs: [ + ...xPackAPITestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.authc.token.enabled=true', + 'xpack.security.authc.realms.native.native1.order=0', + 'xpack.security.authc.realms.saml.saml1.order=1', + `xpack.security.authc.realms.saml.saml1.idp.metadata.path=${idpPath}`, + 'xpack.security.authc.realms.saml.saml1.idp.entity_id=http://www.elastic.co/saml1', + `xpack.security.authc.realms.saml.saml1.sp.entity_id=http://localhost:${kibanaPort}`, + `xpack.security.authc.realms.saml.saml1.sp.logout=http://localhost:${kibanaPort}/logout`, + `xpack.security.authc.realms.saml.saml1.sp.acs=http://localhost:${kibanaPort}/api/security/saml/callback`, + 'xpack.security.authc.realms.saml.saml1.attributes.principal=urn:oid:0.0.7', + ], + }, + + kbnTestServer: { + ...xPackAPITestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), + `--xpack.security.authc.providers=${JSON.stringify({ + basic: { basic1: { order: 0 } }, + saml: { saml1: { order: 1, realm: 'saml1' } }, + })}`, + ], + }, + + junit: { + reportName: 'X-Pack Security API Integration Tests (Session Invalidate)', + }, + }; +} diff --git a/x-pack/test/security_api_integration/tests/session_invalidate/index.ts b/x-pack/test/security_api_integration/tests/session_invalidate/index.ts new file mode 100644 index 00000000000000..6408e4cfbd43d3 --- /dev/null +++ b/x-pack/test/security_api_integration/tests/session_invalidate/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('security APIs - Session Invalidate', function () { + this.tags('ciGroup6'); + + loadTestFile(require.resolve('./invalidate')); + }); +} diff --git a/x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts b/x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts new file mode 100644 index 00000000000000..60605c88ce45e7 --- /dev/null +++ b/x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts @@ -0,0 +1,350 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import request, { Cookie } from 'request'; +import expect from '@kbn/expect'; +import { adminTestUser } from '@kbn/test'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; +import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertestWithoutAuth'); + const es = getService('es'); + const security = getService('security'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const config = getService('config'); + const randomness = getService('randomness'); + const kibanaServerConfig = config.get('servers.kibana'); + const notSuperuserTestUser = { username: 'test_user', password: 'changeme' }; + + async function checkSessionCookie( + sessionCookie: Cookie, + username: string, + provider: AuthenticationProvider + ) { + const apiResponse = await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', sessionCookie.cookieString()) + .expect(200); + + expect(apiResponse.body.username).to.be(username); + expect(apiResponse.body.authentication_provider).to.eql(provider); + + return Array.isArray(apiResponse.headers['set-cookie']) + ? request.cookie(apiResponse.headers['set-cookie'][0])! + : undefined; + } + + async function loginWithSAML() { + const handshakeResponse = await supertest + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ providerType: 'saml', providerName: 'saml1', currentURL: '' }) + .expect(200); + + const authenticationResponse = await supertest + .post('/api/security/saml/callback') + .set('kbn-xsrf', 'xxx') + .set('Cookie', request.cookie(handshakeResponse.headers['set-cookie'][0])!.cookieString()) + .send({ + SAMLResponse: await getSAMLResponse({ + destination: `http://localhost:${kibanaServerConfig.port}/api/security/saml/callback`, + sessionIndex: String(randomness.naturalNumber()), + inResponseTo: await getSAMLRequestId(handshakeResponse.body.location), + }), + }) + .expect(302); + + const cookie = request.cookie(authenticationResponse.headers['set-cookie'][0])!; + await checkSessionCookie(cookie, 'a@b.c', { type: 'saml', name: 'saml1' }); + return cookie; + } + + async function loginWithBasic(credentials: { username: string; password: string }) { + const authenticationResponse = await supertest + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ + providerType: 'basic', + providerName: 'basic1', + currentURL: '/', + params: credentials, + }) + .expect(200); + + const cookie = request.cookie(authenticationResponse.headers['set-cookie'][0])!; + await checkSessionCookie(cookie, credentials.username, { type: 'basic', name: 'basic1' }); + return cookie; + } + + describe('Session Invalidate', () => { + beforeEach(async () => { + await es.cluster.health({ index: '.kibana_security_session*', wait_for_status: 'green' }); + await esDeleteAllIndices('.kibana_security_session*'); + await security.testUser.setRoles(['kibana_admin']); + }); + + it('should be able to invalidate all sessions at once', async function () { + const basicSessionCookie = await loginWithBasic(notSuperuserTestUser); + const samlSessionCookie = await loginWithSAML(); + + // Invalidate all sessions and make sure neither of the sessions is active now. + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ match: 'all' }) + .expect(200, { total: 2 }); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', basicSessionCookie.cookieString()) + .expect(401); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', samlSessionCookie.cookieString()) + .expect(401); + }); + + it('should do nothing if specified provider type is not configured', async function () { + const basicSessionCookie = await loginWithBasic(notSuperuserTestUser); + const samlSessionCookie = await loginWithSAML(); + + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ match: 'query', query: { provider: { type: 'oidc' } } }) + .expect(200, { total: 0 }); + await checkSessionCookie(basicSessionCookie, notSuperuserTestUser.username, { + type: 'basic', + name: 'basic1', + }); + await checkSessionCookie(samlSessionCookie, 'a@b.c', { type: 'saml', name: 'saml1' }); + }); + + it('should be able to invalidate session only for a specific provider type', async function () { + const basicSessionCookie = await loginWithBasic(notSuperuserTestUser); + const samlSessionCookie = await loginWithSAML(); + + // Invalidate `basic` session and make sure that only `saml` session is still active. + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ match: 'query', query: { provider: { type: 'basic' } } }) + .expect(200, { total: 1 }); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', basicSessionCookie.cookieString()) + .expect(401); + await checkSessionCookie(samlSessionCookie, 'a@b.c', { type: 'saml', name: 'saml1' }); + + // Invalidate `saml` session and make sure neither of the sessions is active now. + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ match: 'query', query: { provider: { type: 'saml' } } }) + .expect(200, { total: 1 }); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', samlSessionCookie.cookieString()) + .expect(401); + }); + + it('should do nothing if specified provider name is not configured', async function () { + const basicSessionCookie = await loginWithBasic(notSuperuserTestUser); + const samlSessionCookie = await loginWithSAML(); + + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ match: 'query', query: { provider: { type: 'basic', name: 'basic2' } } }) + .expect(200, { total: 0 }); + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ match: 'query', query: { provider: { type: 'saml', name: 'saml2' } } }) + .expect(200, { total: 0 }); + await checkSessionCookie(basicSessionCookie, notSuperuserTestUser.username, { + type: 'basic', + name: 'basic1', + }); + await checkSessionCookie(samlSessionCookie, 'a@b.c', { type: 'saml', name: 'saml1' }); + }); + + it('should be able to invalidate session only for a specific provider name', async function () { + const basicSessionCookie = await loginWithBasic(notSuperuserTestUser); + const samlSessionCookie = await loginWithSAML(); + + // Invalidate `saml1` session and make sure that only `basic1` session is still active. + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ match: 'query', query: { provider: { type: 'saml', name: 'saml1' } } }) + .expect(200, { total: 1 }); + await checkSessionCookie(basicSessionCookie, notSuperuserTestUser.username, { + type: 'basic', + name: 'basic1', + }); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', samlSessionCookie.cookieString()) + .expect(401); + + // Invalidate `basic1` session and make sure neither of the sessions is active now. + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ match: 'query', query: { provider: { type: 'basic', name: 'basic1' } } }) + .expect(200, { total: 1 }); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', basicSessionCookie.cookieString()) + .expect(401); + }); + + it('should do nothing if specified username does not have session', async function () { + const basicSessionCookie = await loginWithBasic(notSuperuserTestUser); + const samlSessionCookie = await loginWithSAML(); + + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ + match: 'query', + query: { + provider: { type: 'basic', name: 'basic1' }, + username: `_${notSuperuserTestUser.username}`, + }, + }) + .expect(200, { total: 0 }); + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ + match: 'query', + query: { provider: { type: 'saml', name: 'saml1' }, username: '_a@b.c' }, + }) + .expect(200, { total: 0 }); + await checkSessionCookie(basicSessionCookie, notSuperuserTestUser.username, { + type: 'basic', + name: 'basic1', + }); + await checkSessionCookie(samlSessionCookie, 'a@b.c', { type: 'saml', name: 'saml1' }); + }); + + it('should be able to invalidate session only for a specific user', async function () { + const basicSessionCookie = await loginWithBasic(notSuperuserTestUser); + const samlSessionCookie = await loginWithSAML(); + + // Invalidate session for `test_user` and make sure that only session of `a@b.c` is still active. + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ + match: 'query', + query: { + provider: { type: 'basic', name: 'basic1' }, + username: notSuperuserTestUser.username, + }, + }) + .expect(200, { total: 1 }); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', basicSessionCookie.cookieString()) + .expect(401); + await checkSessionCookie(samlSessionCookie, 'a@b.c', { type: 'saml', name: 'saml1' }); + + // Invalidate session for `a@b.c` and make sure neither of the sessions is active now. + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(adminTestUser.username, adminTestUser.password) + .send({ + match: 'query', + query: { provider: { type: 'saml', name: 'saml1' }, username: 'a@b.c' }, + }) + .expect(200, { total: 1 }); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', samlSessionCookie.cookieString()) + .expect(401); + }); + + it('only super users should be able to invalidate sessions', async function () { + const basicSessionCookie = await loginWithBasic(notSuperuserTestUser); + const samlSessionCookie = await loginWithSAML(); + + // User without a superuser role shouldn't be able to invalidate sessions. + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(notSuperuserTestUser.username, notSuperuserTestUser.password) + .send({ match: 'all' }) + .expect(403); + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(notSuperuserTestUser.username, notSuperuserTestUser.password) + .send({ match: 'query', query: { provider: { type: 'basic' } } }) + .expect(403); + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(notSuperuserTestUser.username, notSuperuserTestUser.password) + .send({ + match: 'query', + query: { provider: { type: 'basic' }, username: notSuperuserTestUser.username }, + }) + .expect(403); + + await checkSessionCookie(basicSessionCookie, notSuperuserTestUser.username, { + type: 'basic', + name: 'basic1', + }); + await checkSessionCookie(samlSessionCookie, 'a@b.c', { type: 'saml', name: 'saml1' }); + + // With superuser role, it should be possible now. + await security.testUser.setRoles(['superuser']); + + await supertest + .post('/api/security/session/_invalidate') + .set('kbn-xsrf', 'xxx') + .auth(notSuperuserTestUser.username, notSuperuserTestUser.password) + .send({ match: 'all' }) + .expect(200, { total: 2 }); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', basicSessionCookie.cookieString()) + .expect(401); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', samlSessionCookie.cookieString()) + .expect(401); + }); + }); +} From fdda564a84da90e54c259e89e7082ddfe307308d Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 24 Mar 2021 10:29:46 +0000 Subject: [PATCH 51/93] [ML] Adding additional runtime mapping checks (#94760) * [ML] Adding additional runtime mapping checks * adding functional test for datafeed preview * renaming findFieldsInAgg * updating query check * always use runtime mappings if present in agg field exists check * changes based on review * updating tests based on review * fixing permission check on endpoint and test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../job_creator/advanced_job_creator.ts | 1 + .../util/filter_runtime_mappings.ts | 31 +- .../services/ml_api_service/index.ts | 5 +- .../polled_data_checker.js | 4 +- .../models/data_visualizer/data_visualizer.ts | 8 +- .../models/fields_service/fields_service.ts | 3 + .../ml/server/models/job_service/datafeeds.ts | 1 + .../models/job_validation/job_validation.ts | 8 +- .../ml/server/routes/fields_service.ts | 4 +- .../plugins/ml/server/routes/job_service.ts | 2 +- .../routes/schemas/fields_service_schema.ts | 1 + .../apis/ml/jobs/datafeed_preview.ts | 272 ++++++++++++++++++ .../api_integration/apis/ml/jobs/index.ts | 1 + 13 files changed, 327 insertions(+), 14 deletions(-) create mode 100644 x-pack/test/api_integration/apis/ml/jobs/datafeed_preview.ts diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts index da5cfc53b79505..2ca95a14fb8124 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts @@ -181,6 +181,7 @@ export class AdvancedJobCreator extends JobCreator { index: this._indexPatternTitle, timeFieldName: this.timeFieldName, query: this.query, + runtimeMappings: this.datafeedConfig.runtime_mappings, indicesOptions: this.datafeedConfig.indices_options, }); this.setTimeRange(start.epoch, end.epoch); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts index 5319cd3c3aabc9..bfed2d811e2062 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts @@ -13,6 +13,7 @@ import type { RuntimeMappings } from '../../../../../../../common/types/fields'; import type { Datafeed, Job } from '../../../../../../../common/types/anomaly_detection_jobs'; +import { isPopulatedObject } from '../../../../../../../common/util/object_utils'; interface Response { runtime_mappings: RuntimeMappings; @@ -20,7 +21,10 @@ interface Response { } export function filterRuntimeMappings(job: Job, datafeed: Datafeed): Response { - if (datafeed.runtime_mappings === undefined) { + if ( + datafeed.runtime_mappings === undefined || + isPopulatedObject(datafeed.runtime_mappings) === false + ) { return { runtime_mappings: {}, discarded_mappings: {}, @@ -71,13 +75,18 @@ function findFieldsInJob(job: Job, datafeed: Datafeed) { findFieldsInAgg(aggs).forEach((f) => usedFields.add(f)); } + const query = datafeed.query; + if (query !== undefined) { + findFieldsInQuery(query).forEach((f) => usedFields.add(f)); + } + return [...usedFields]; } -function findFieldsInAgg(obj: Record) { +function findFieldsInAgg(obj: Record) { const fields: string[] = []; Object.entries(obj).forEach(([key, val]) => { - if (typeof val === 'object' && val !== null) { + if (isPopulatedObject(val)) { fields.push(...findFieldsInAgg(val)); } else if (typeof val === 'string' && key === 'field') { fields.push(val); @@ -86,6 +95,22 @@ function findFieldsInAgg(obj: Record) { return fields; } +function findFieldsInQuery(obj: object) { + const fields: string[] = []; + Object.entries(obj).forEach(([key, val]) => { + // return all nested keys in the object + // most will not be fields, but better to catch everything + // and not accidentally remove a used runtime field. + if (isPopulatedObject(val)) { + fields.push(key); + fields.push(...findFieldsInQuery(val)); + } else { + fields.push(key); + } + }); + return fields; +} + function createMappings(rm: RuntimeMappings, usedFieldNames: string[]) { return { runtimeMappings: usedFieldNames.reduce((acc, cur) => { diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts index e6d0d93cade1f0..4acb7fca09d0d3 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts @@ -25,6 +25,7 @@ import { import { MlCapabilitiesResponse } from '../../../../common/types/capabilities'; import { Calendar, CalendarId, UpdateCalendar } from '../../../../common/types/calendars'; import { BucketSpanEstimatorData } from '../../../../common/types/job_service'; +import { RuntimeMappings } from '../../../../common/types/fields'; import { Job, JobStats, @@ -690,14 +691,16 @@ export function mlApiServicesProvider(httpService: HttpService) { index, timeFieldName, query, + runtimeMappings, indicesOptions, }: { index: string; timeFieldName?: string; query: any; + runtimeMappings?: RuntimeMappings; indicesOptions?: IndicesOptions; }) { - const body = JSON.stringify({ index, timeFieldName, query, indicesOptions }); + const body = JSON.stringify({ index, timeFieldName, query, runtimeMappings, indicesOptions }); return httpService.http({ path: `${basePath()}/fields_service/time_field_range`, diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js index 8a40787f444905..5fe783e1fc1d55 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js @@ -15,11 +15,12 @@ import { get } from 'lodash'; export function polledDataCheckerFactory({ asCurrentUser }) { class PolledDataChecker { - constructor(index, timeField, duration, query, indicesOptions) { + constructor(index, timeField, duration, query, runtimeMappings, indicesOptions) { this.index = index; this.timeField = timeField; this.duration = duration; this.query = query; + this.runtimeMappings = runtimeMappings; this.indicesOptions = indicesOptions; this.isPolled = false; @@ -62,6 +63,7 @@ export function polledDataCheckerFactory({ asCurrentUser }) { }, }, }, + ...this.runtimeMappings, }; return search; diff --git a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index 4db8295d939975..2a820e0629b757 100644 --- a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -625,15 +625,13 @@ export class DataVisualizer { cardinalityField = aggs[`${safeFieldName}_cardinality`] = { cardinality: { script: datafeedConfig?.script_fields[field].script }, }; - } else if (datafeedConfig?.runtime_mappings?.hasOwnProperty(field)) { - cardinalityField = { - cardinality: { field }, - }; - runtimeMappings.runtime_mappings = datafeedConfig.runtime_mappings; } else { cardinalityField = { cardinality: { field }, }; + if (datafeedConfig !== undefined && isPopulatedObject(datafeedConfig?.runtime_mappings)) { + runtimeMappings.runtime_mappings = datafeedConfig.runtime_mappings; + } } aggs[`${safeFieldName}_cardinality`] = cardinalityField; }); diff --git a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts index 1270cc6f08e23f..8e4dbaf23212fe 100644 --- a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts +++ b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts @@ -14,6 +14,7 @@ import { AggCardinality } from '../../../common/types/fields'; import { isValidAggregationField } from '../../../common/util/validation_utils'; import { getDatafeedAggregations } from '../../../common/util/datafeed_utils'; import { Datafeed, IndicesOptions } from '../../../common/types/anomaly_detection_jobs'; +import { RuntimeMappings } from '../../../common/types/fields'; /** * Service for carrying out queries to obtain data @@ -212,6 +213,7 @@ export function fieldsServiceProvider({ asCurrentUser }: IScopedClusterClient) { index: string[] | string, timeFieldName: string, query: any, + runtimeMappings?: RuntimeMappings, indicesOptions?: IndicesOptions ): Promise<{ success: boolean; @@ -239,6 +241,7 @@ export function fieldsServiceProvider({ asCurrentUser }: IScopedClusterClient) { }, }, }, + ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}), }, ...(indicesOptions ?? {}), }); diff --git a/x-pack/plugins/ml/server/models/job_service/datafeeds.ts b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts index cb651a0a410afd..cf9b2027225c9c 100644 --- a/x-pack/plugins/ml/server/models/job_service/datafeeds.ts +++ b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts @@ -219,6 +219,7 @@ export function datafeedsProvider(client: IScopedClusterClient, mlClient: MlClie datafeed.indices, job.data_description.time_field, query, + datafeed.runtime_mappings, datafeed.indices_options ); diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts index 31d98753f0bd1e..2beade7f5dbc46 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts @@ -64,7 +64,13 @@ export async function validateJob( const fs = fieldsServiceProvider(client); const index = job.datafeed_config.indices.join(','); const timeField = job.data_description.time_field; - const timeRange = await fs.getTimeFieldRange(index, timeField, job.datafeed_config.query); + const timeRange = await fs.getTimeFieldRange( + index, + timeField, + job.datafeed_config.query, + job.datafeed_config.runtime_mappings, + job.datafeed_config.indices_options + ); duration = { start: timeRange.start.epoch, diff --git a/x-pack/plugins/ml/server/routes/fields_service.ts b/x-pack/plugins/ml/server/routes/fields_service.ts index c087b86172fa97..dc43d915e87a2e 100644 --- a/x-pack/plugins/ml/server/routes/fields_service.ts +++ b/x-pack/plugins/ml/server/routes/fields_service.ts @@ -22,8 +22,8 @@ function getCardinalityOfFields(client: IScopedClusterClient, payload: any) { function getTimeFieldRange(client: IScopedClusterClient, payload: any) { const fs = fieldsServiceProvider(client); - const { index, timeFieldName, query, indicesOptions } = payload; - return fs.getTimeFieldRange(index, timeFieldName, query, indicesOptions); + const { index, timeFieldName, query, runtimeMappings, indicesOptions } = payload; + return fs.getTimeFieldRange(index, timeFieldName, query, runtimeMappings, indicesOptions); } /** diff --git a/x-pack/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts index b3aa9f956895a2..1f755c27db8713 100644 --- a/x-pack/plugins/ml/server/routes/job_service.ts +++ b/x-pack/plugins/ml/server/routes/job_service.ts @@ -791,7 +791,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { body: datafeedPreviewSchema, }, options: { - tags: ['access:ml:canGetJobs'], + tags: ['access:ml:canPreviewDatafeed'], }, }, routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response }) => { diff --git a/x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts index db827b26fe73a3..76a307e710dc83 100644 --- a/x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts @@ -31,5 +31,6 @@ export const getTimeFieldRangeSchema = schema.object({ /** Query to match documents in the index(es). */ query: schema.maybe(schema.any()), /** Additional search options. */ + runtimeMappings: schema.maybe(schema.any()), indicesOptions: indicesOptionsSchema, }); diff --git a/x-pack/test/api_integration/apis/ml/jobs/datafeed_preview.ts b/x-pack/test/api_integration/apis/ml/jobs/datafeed_preview.ts new file mode 100644 index 00000000000000..2fcf75f99ff17d --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/jobs/datafeed_preview.ts @@ -0,0 +1,272 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + const jobId = `fq_datafeed_preview_${Date.now()}`; + + const job = { + job_id: `${jobId}_1`, + description: '', + groups: ['automated', 'farequote'], + analysis_config: { + bucket_span: '30m', + detectors: [{ function: 'distinct_count', field_name: 'airline' }], + influencers: [], + }, + data_description: { time_field: '@timestamp' }, + analysis_limits: { model_memory_limit: '11MB' }, + }; + + function isUpperCase(str: string) { + return /^[A-Z]+$/.test(str); + } + + function isLowerCase(str: string) { + return !/[A-Z]+/.test(str); + } + + describe('Datafeed preview', function () { + before(async () => { + await esArchiver.loadIfNeeded('ml/farequote'); + await ml.testResources.setKibanaTimeZoneToUTC(); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + it(`should return a normal datafeed preview`, async () => { + const datafeed = { + datafeed_id: '', + job_id: '', + indices: ['ft_farequote'], + query: { + bool: { + must: [ + { + match_all: {}, + }, + ], + }, + }, + runtime_mappings: {}, + }; + + const { body } = await supertest + .post('/api/ml/jobs/datafeed_preview') + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .send({ job, datafeed }) + .expect(200); + + expect(body.hits.total.value).to.eql(3207, 'Response body total hits should be 3207'); + expect(Array.isArray(body.hits?.hits[0]?.fields?.airline)).to.eql( + true, + 'Response body airlines should be an array' + ); + + const airlines: string[] = body.hits.hits.map((a: any) => a.fields.airline[0]); + expect(airlines.length).to.not.eql(0, 'airlines length should not be 0'); + expect(airlines.every((a) => isUpperCase(a))).to.eql( + true, + 'Response body airlines should all be upper case' + ); + }); + + it(`should return a datafeed preview using custom query`, async () => { + const datafeed = { + datafeed_id: '', + job_id: '', + indices: ['ft_farequote'], + query: { + bool: { + should: [ + { + match: { + airline: 'AAL', + }, + }, + ], + }, + }, + runtime_mappings: {}, + }; + + const { body } = await supertest + .post('/api/ml/jobs/datafeed_preview') + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .send({ job, datafeed }) + .expect(200); + + expect(body.hits.total.value).to.eql(300, 'Response body total hits should be 300'); + expect(Array.isArray(body.hits?.hits[0]?.fields?.airline)).to.eql( + true, + 'Response body airlines should be an array' + ); + + const airlines: string[] = body.hits.hits.map((a: any) => a.fields.airline[0]); + expect(airlines.length).to.not.eql(0, 'airlines length should not be 0'); + expect(airlines.every((a) => a === 'AAL')).to.eql( + true, + 'Response body airlines should all be AAL' + ); + }); + + it(`should return a datafeed preview using runtime mappings`, async () => { + const datafeed = { + datafeed_id: '', + job_id: '', + indices: ['ft_farequote'], + query: { + bool: { + must: [ + { + match_all: {}, + }, + ], + }, + }, + runtime_mappings: { + lowercase_airline: { + type: 'keyword', + script: { + source: 'emit(params._source.airline.toLowerCase())', + }, + }, + }, + }; + + const { body } = await supertest + .post('/api/ml/jobs/datafeed_preview') + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .send({ job, datafeed }) + .expect(200); + + expect(body.hits.total.value).to.eql(3207, 'Response body total hits should be 3207'); + expect(Array.isArray(body.hits?.hits[0]?.fields?.lowercase_airline)).to.eql( + true, + 'Response body airlines should be an array' + ); + + const airlines: string[] = body.hits.hits.map((a: any) => a.fields.lowercase_airline[0]); + expect(airlines.length).to.not.eql(0, 'airlines length should not be 0'); + expect(isLowerCase(airlines[0])).to.eql( + true, + 'Response body airlines should all be lower case' + ); + }); + + it(`should return a datafeed preview using custom query and runtime mappings which override the field name`, async () => { + const datafeed = { + datafeed_id: '', + job_id: '', + indices: ['ft_farequote'], + query: { + bool: { + should: [ + { + match: { + airline: 'aal', + }, + }, + ], + }, + }, + runtime_mappings: { + // override the airline field name + airline: { + type: 'keyword', + script: { + source: 'emit(params._source.airline.toLowerCase())', + }, + }, + }, + }; + + const { body } = await supertest + .post('/api/ml/jobs/datafeed_preview') + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .send({ job, datafeed }) + .expect(200); + + expect(body.hits.total.value).to.eql(300, 'Response body total hits should be 300'); + expect(Array.isArray(body.hits?.hits[0]?.fields?.airline)).to.eql( + true, + 'Response body airlines should be an array' + ); + + const airlines: string[] = body.hits.hits.map((a: any) => a.fields.airline[0]); + expect(airlines.length).to.not.eql(0, 'airlines length should not be 0'); + expect(isLowerCase(airlines[0])).to.eql( + true, + 'Response body airlines should all be lower case' + ); + }); + + it(`should return not a datafeed preview for ML viewer user`, async () => { + const datafeed = { + datafeed_id: '', + job_id: '', + indices: ['ft_farequote'], + query: { + bool: { + must: [ + { + match_all: {}, + }, + ], + }, + }, + runtime_mappings: {}, + }; + + await supertest + .post('/api/ml/jobs/datafeed_preview') + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .send({ job, datafeed }) + .expect(403); + }); + + it(`should return not a datafeed preview for unauthorized user`, async () => { + const datafeed = { + datafeed_id: '', + job_id: '', + indices: ['ft_farequote'], + query: { + bool: { + must: [ + { + match_all: {}, + }, + ], + }, + }, + runtime_mappings: {}, + }; + + await supertest + .post('/api/ml/jobs/datafeed_preview') + .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) + .set(COMMON_REQUEST_HEADERS) + .send({ job, datafeed }) + .expect(403); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/jobs/index.ts b/x-pack/test/api_integration/apis/ml/jobs/index.ts index bea6d5972e7a6c..4c52f2ef862c3e 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/index.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/index.ts @@ -17,5 +17,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./jobs_exist_spaces')); loadTestFile(require.resolve('./close_jobs_spaces')); loadTestFile(require.resolve('./delete_jobs_spaces')); + loadTestFile(require.resolve('./datafeed_preview')); }); } From f07b1722cb71609663b70dd889545d2d4aeec028 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 24 Mar 2021 11:44:23 +0100 Subject: [PATCH 52/93] [Lens] Implement filtered metric (#92589) --- api_docs/charts.json | 42 +- api_docs/data.json | 345 +- api_docs/data_search.json | 4404 +++++++++++++---- api_docs/expressions.json | 81 + api_docs/fleet.json | 177 +- api_docs/lens.json | 18 +- api_docs/observability.json | 4 +- ...c.aggfunctionsmapping.aggfilteredmetric.md | 11 + ...plugins-data-public.aggfunctionsmapping.md | 1 + ...plugin-plugins-data-public.metric_types.md | 1 + ...r.aggfunctionsmapping.aggfilteredmetric.md | 11 + ...plugins-data-server.aggfunctionsmapping.md | 1 + ...plugin-plugins-data-server.metric_types.md | 1 + .../data/common/search/aggs/agg_config.ts | 4 +- .../data/common/search/aggs/agg_configs.ts | 20 +- .../data/common/search/aggs/agg_type.ts | 8 + .../data/common/search/aggs/agg_types.ts | 2 + .../common/search/aggs/aggs_service.test.ts | 2 + .../data/common/search/aggs/buckets/filter.ts | 27 +- .../search/aggs/buckets/filter_fn.test.ts | 21 + .../common/search/aggs/buckets/filter_fn.ts | 19 +- .../aggs/metrics/filtered_metric.test.ts | 72 + .../search/aggs/metrics/filtered_metric.ts | 56 + .../aggs/metrics/filtered_metric_fn.test.ts | 51 + .../search/aggs/metrics/filtered_metric_fn.ts | 94 + .../data/common/search/aggs/metrics/index.ts | 2 + .../metrics/lib/parent_pipeline_agg_helper.ts | 1 + .../lib/sibling_pipeline_agg_helper.ts | 8 +- .../search/aggs/metrics/metric_agg_types.ts | 1 + src/plugins/data/common/search/aggs/types.ts | 4 + src/plugins/data/public/public.api.md | 14 +- .../public/search/aggs/aggs_service.test.ts | 4 +- src/plugins/data/server/server.api.md | 10 +- .../vis_type_metric/public/metric_vis_type.ts | 1 + .../public/legacy/table_vis_legacy_type.ts | 2 +- .../vis_type_table/public/table_vis_type.ts | 2 +- .../public/tag_cloud_type.ts | 1 + src/plugins/vis_type_vislib/public/gauge.ts | 1 + src/plugins/vis_type_vislib/public/goal.ts | 1 + src/plugins/vis_type_vislib/public/heatmap.ts | 1 + .../vis_type_xy/public/vis_types/area.ts | 2 +- .../vis_type_xy/public/vis_types/histogram.ts | 2 +- .../public/vis_types/horizontal_bar.ts | 2 +- .../vis_type_xy/public/vis_types/line.ts | 2 +- .../dimension_panel/advanced_options.tsx | 82 + .../dimension_panel/dimension_editor.tsx | 69 +- .../dimension_panel/dimension_panel.test.tsx | 227 +- .../dimension_panel/filtering.tsx | 131 + .../dimension_panel/time_scaling.tsx | 65 +- .../indexpattern.test.ts | 117 + .../definitions/calculations/counter_rate.tsx | 2 + .../calculations/cumulative_sum.tsx | 2 + .../definitions/calculations/derivative.tsx | 2 + .../calculations/moving_average.tsx | 2 + .../operations/definitions/cardinality.tsx | 2 + .../operations/definitions/column_types.ts | 2 + .../operations/definitions/count.tsx | 2 + .../filters/filter_popover.test.tsx | 3 +- .../definitions/filters/filter_popover.tsx | 56 +- .../operations/definitions/index.ts | 1 + .../operations/definitions/last_value.tsx | 2 + .../operations/definitions/metrics.tsx | 2 + .../operations/layer_helpers.test.ts | 43 + .../operations/layer_helpers.ts | 13 +- .../operations/mocks.ts | 1 + .../indexpattern_datasource/query_input.tsx | 66 + .../indexpattern_datasource/to_expression.ts | 53 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 69 files changed, 4976 insertions(+), 1505 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilteredmetric.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilteredmetric.md create mode 100644 src/plugins/data/common/search/aggs/metrics/filtered_metric.test.ts create mode 100644 src/plugins/data/common/search/aggs/metrics/filtered_metric.ts create mode 100644 src/plugins/data/common/search/aggs/metrics/filtered_metric_fn.test.ts create mode 100644 src/plugins/data/common/search/aggs/metrics/filtered_metric_fn.ts create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/advanced_options.tsx create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/filtering.tsx create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx diff --git a/api_docs/charts.json b/api_docs/charts.json index 5c4008d0f25bcf..70bf2166de7c84 100644 --- a/api_docs/charts.json +++ b/api_docs/charts.json @@ -1597,7 +1597,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 3160 + "lineNumber": 174 }, "signature": [ { @@ -1816,7 +1816,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 558 + "lineNumber": 56 }, "signature": [ { @@ -1837,7 +1837,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 559 + "lineNumber": 57 } }, { @@ -1848,7 +1848,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 562 + "lineNumber": 60 }, "signature": [ "[number, number[]][]" @@ -1859,7 +1859,7 @@ "label": "[ColorSchemas.Greens]", "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 557 + "lineNumber": 55 } }, { @@ -1875,7 +1875,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 1078 + "lineNumber": 74 }, "signature": [ { @@ -1896,7 +1896,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 1079 + "lineNumber": 75 } }, { @@ -1907,7 +1907,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 1082 + "lineNumber": 78 }, "signature": [ "[number, number[]][]" @@ -1918,7 +1918,7 @@ "label": "[ColorSchemas.Greys]", "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 1077 + "lineNumber": 73 } }, { @@ -1934,7 +1934,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 1598 + "lineNumber": 92 }, "signature": [ { @@ -1955,7 +1955,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 1599 + "lineNumber": 93 } }, { @@ -1966,7 +1966,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 1602 + "lineNumber": 96 }, "signature": [ "[number, number[]][]" @@ -1977,7 +1977,7 @@ "label": "[ColorSchemas.Reds]", "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 1597 + "lineNumber": 91 } }, { @@ -1993,7 +1993,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 2118 + "lineNumber": 110 }, "signature": [ { @@ -2014,7 +2014,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 2119 + "lineNumber": 111 } }, { @@ -2025,7 +2025,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 2122 + "lineNumber": 114 }, "signature": [ "[number, number[]][]" @@ -2036,7 +2036,7 @@ "label": "[ColorSchemas.YellowToRed]", "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 2117 + "lineNumber": 109 } }, { @@ -2052,7 +2052,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 2639 + "lineNumber": 129 }, "signature": [ { @@ -2073,7 +2073,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 2640 + "lineNumber": 130 } }, { @@ -2084,7 +2084,7 @@ "description": [], "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 2643 + "lineNumber": 133 }, "signature": [ "[number, number[]][]" @@ -2095,7 +2095,7 @@ "label": "[ColorSchemas.GreenToRed]", "source": { "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", - "lineNumber": 2638 + "lineNumber": 128 } } ], diff --git a/api_docs/data.json b/api_docs/data.json index a78aec92b1fa50..a9ef03d881ce88 100644 --- a/api_docs/data.json +++ b/api_docs/data.json @@ -608,7 +608,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 263 + "lineNumber": 265 } }, { @@ -634,7 +634,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 294 + "lineNumber": 296 } }, { @@ -654,7 +654,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 305 + "lineNumber": 307 } }, { @@ -680,7 +680,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 314 + "lineNumber": 316 } }, { @@ -712,7 +712,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 369 + "lineNumber": 371 } }, { @@ -736,7 +736,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 373 + "lineNumber": 375 } }, { @@ -760,7 +760,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 377 + "lineNumber": 379 } }, { @@ -782,7 +782,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 381 + "lineNumber": 383 } } ], @@ -790,7 +790,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 381 + "lineNumber": 383 } }, { @@ -812,7 +812,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 385 + "lineNumber": 387 } }, { @@ -825,7 +825,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 385 + "lineNumber": 387 } } ], @@ -833,7 +833,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 385 + "lineNumber": 387 } }, { @@ -849,7 +849,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 393 + "lineNumber": 395 } }, { @@ -865,7 +865,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 399 + "lineNumber": 401 } }, { @@ -883,7 +883,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 408 + "lineNumber": 410 } }, { @@ -905,7 +905,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 412 + "lineNumber": 414 } } ], @@ -913,7 +913,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 412 + "lineNumber": 414 } }, { @@ -936,7 +936,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 426 + "lineNumber": 428 } }, { @@ -960,7 +960,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 430 + "lineNumber": 432 } }, { @@ -976,7 +976,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 434 + "lineNumber": 436 } }, { @@ -992,7 +992,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 439 + "lineNumber": 441 } }, { @@ -1003,7 +1003,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 446 + "lineNumber": 448 }, "signature": [ { @@ -1023,7 +1023,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 450 + "lineNumber": 452 }, "signature": [ { @@ -1068,7 +1068,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 480 + "lineNumber": 482 } } ], @@ -1076,7 +1076,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 480 + "lineNumber": 482 } } ], @@ -1101,7 +1101,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 58 + "lineNumber": 65 }, "signature": [ { @@ -1121,7 +1121,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 59 + "lineNumber": 66 }, "signature": [ { @@ -1142,7 +1142,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 62 + "lineNumber": 69 }, "signature": [ { @@ -1180,7 +1180,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 65 + "lineNumber": 72 } }, { @@ -1217,7 +1217,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 66 + "lineNumber": 73 } }, { @@ -1236,7 +1236,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 67 + "lineNumber": 74 } } ], @@ -1244,7 +1244,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 64 + "lineNumber": 71 } }, { @@ -1280,7 +1280,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 79 + "lineNumber": 86 } } ], @@ -1288,7 +1288,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 79 + "lineNumber": 86 } }, { @@ -1317,7 +1317,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 97 + "lineNumber": 104 } } ], @@ -1325,7 +1325,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 97 + "lineNumber": 104 } }, { @@ -1366,7 +1366,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 111 + "lineNumber": 118 } }, { @@ -1379,7 +1379,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 112 + "lineNumber": 119 } } ], @@ -1429,7 +1429,7 @@ "label": "createAggConfig", "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 110 + "lineNumber": 117 }, "tags": [], "returnComment": [] @@ -1472,7 +1472,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 153 + "lineNumber": 160 } } ], @@ -1480,7 +1480,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 153 + "lineNumber": 160 } }, { @@ -1502,7 +1502,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 165 + "lineNumber": 172 } } ], @@ -1510,7 +1510,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 165 + "lineNumber": 172 } }, { @@ -1534,7 +1534,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 227 + "lineNumber": 241 } }, { @@ -1563,7 +1563,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 231 + "lineNumber": 245 } } ], @@ -1571,7 +1571,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 231 + "lineNumber": 245 } }, { @@ -1601,7 +1601,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 235 + "lineNumber": 249 } } ], @@ -1609,7 +1609,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 235 + "lineNumber": 249 } }, { @@ -1639,7 +1639,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 239 + "lineNumber": 253 } } ], @@ -1647,7 +1647,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 239 + "lineNumber": 253 } }, { @@ -1677,7 +1677,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 243 + "lineNumber": 257 } } ], @@ -1685,7 +1685,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 243 + "lineNumber": 257 } }, { @@ -1715,7 +1715,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 247 + "lineNumber": 261 } } ], @@ -1723,7 +1723,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 247 + "lineNumber": 261 } }, { @@ -1753,7 +1753,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 251 + "lineNumber": 265 } } ], @@ -1761,7 +1761,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 251 + "lineNumber": 265 } }, { @@ -1785,7 +1785,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 255 + "lineNumber": 269 } }, { @@ -1815,7 +1815,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 269 + "lineNumber": 283 } } ], @@ -1823,7 +1823,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 269 + "lineNumber": 283 } }, { @@ -1851,7 +1851,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 284 + "lineNumber": 298 } }, { @@ -1885,7 +1885,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 298 + "lineNumber": 312 } } ], @@ -1895,7 +1895,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 298 + "lineNumber": 312 } }, { @@ -1941,7 +1941,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 307 + "lineNumber": 321 } }, { @@ -1961,7 +1961,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 307 + "lineNumber": 321 } } ], @@ -1969,13 +1969,13 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 307 + "lineNumber": 321 } } ], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 57 + "lineNumber": 64 }, "initialIsOpen": false }, @@ -6059,7 +6059,7 @@ "type": "Function", "label": "setField", "signature": [ - "(field: K, value: ", + "(field: K, value: ", { "pluginId": "data", "scope": "common", @@ -6123,7 +6123,7 @@ "type": "Function", "label": "removeField", "signature": [ - "(field: K) => this" + "(field: K) => this" ], "description": [ "\nremove field" @@ -6250,7 +6250,7 @@ "type": "Function", "label": "getField", "signature": [ - "(field: K, recurse?: boolean) => ", + "(field: K, recurse?: boolean) => ", { "pluginId": "data", "scope": "common", @@ -6303,7 +6303,7 @@ "type": "Function", "label": "getOwnField", "signature": [ - "(field: K) => ", + "(field: K) => ", { "pluginId": "data", "scope": "common", @@ -7635,7 +7635,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 204 + "lineNumber": 207 }, "signature": [ "FunctionDefinition" @@ -7649,7 +7649,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 205 + "lineNumber": 208 }, "signature": [ "FunctionDefinition" @@ -7663,7 +7663,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 206 + "lineNumber": 209 }, "signature": [ "FunctionDefinition" @@ -7677,7 +7677,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 207 + "lineNumber": 210 }, "signature": [ "FunctionDefinition" @@ -7691,7 +7691,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 208 + "lineNumber": 211 }, "signature": [ "FunctionDefinition" @@ -7705,7 +7705,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 209 + "lineNumber": 212 }, "signature": [ "FunctionDefinition" @@ -7719,7 +7719,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 210 + "lineNumber": 213 }, "signature": [ "FunctionDefinition" @@ -7733,7 +7733,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 211 + "lineNumber": 214 }, "signature": [ "FunctionDefinition" @@ -7747,7 +7747,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 212 + "lineNumber": 215 }, "signature": [ "FunctionDefinition" @@ -7761,7 +7761,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 213 + "lineNumber": 216 }, "signature": [ "FunctionDefinition" @@ -7775,7 +7775,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 214 + "lineNumber": 217 }, "signature": [ "FunctionDefinition" @@ -7789,7 +7789,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 215 + "lineNumber": 218 }, "signature": [ "FunctionDefinition" @@ -7803,7 +7803,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 216 + "lineNumber": 219 }, "signature": [ "FunctionDefinition" @@ -7817,7 +7817,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 217 + "lineNumber": 220 }, "signature": [ "FunctionDefinition" @@ -7831,7 +7831,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 218 + "lineNumber": 221 }, "signature": [ "FunctionDefinition" @@ -7845,7 +7845,21 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 219 + "lineNumber": 222 + }, + "signature": [ + "FunctionDefinition" + ] + }, + { + "tags": [], + "id": "def-public.AggFunctionsMapping.aggFilteredMetric", + "type": "Object", + "label": "aggFilteredMetric", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/types.ts", + "lineNumber": 223 }, "signature": [ "FunctionDefinition" @@ -7859,7 +7873,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 220 + "lineNumber": 224 }, "signature": [ "FunctionDefinition" @@ -7873,7 +7887,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 221 + "lineNumber": 225 }, "signature": [ "FunctionDefinition" @@ -7887,7 +7901,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 222 + "lineNumber": 226 }, "signature": [ "FunctionDefinition" @@ -7901,7 +7915,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 223 + "lineNumber": 227 }, "signature": [ "FunctionDefinition" @@ -7915,7 +7929,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 224 + "lineNumber": 228 }, "signature": [ "FunctionDefinition" @@ -7929,7 +7943,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 225 + "lineNumber": 229 }, "signature": [ "FunctionDefinition" @@ -7943,7 +7957,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 226 + "lineNumber": 230 }, "signature": [ "FunctionDefinition" @@ -7957,7 +7971,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 227 + "lineNumber": 231 }, "signature": [ "FunctionDefinition" @@ -7971,7 +7985,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 228 + "lineNumber": 232 }, "signature": [ "FunctionDefinition" @@ -7985,7 +7999,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 229 + "lineNumber": 233 }, "signature": [ "FunctionDefinition" @@ -7999,7 +8013,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 230 + "lineNumber": 234 }, "signature": [ "FunctionDefinition" @@ -8013,7 +8027,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 231 + "lineNumber": 235 }, "signature": [ "FunctionDefinition" @@ -8027,7 +8041,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 232 + "lineNumber": 236 }, "signature": [ "FunctionDefinition" @@ -8041,7 +8055,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 233 + "lineNumber": 237 }, "signature": [ "FunctionDefinition" @@ -8055,7 +8069,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 234 + "lineNumber": 238 }, "signature": [ "FunctionDefinition" @@ -8069,7 +8083,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 235 + "lineNumber": 239 }, "signature": [ "FunctionDefinition" @@ -8078,7 +8092,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 203 + "lineNumber": 206 }, "initialIsOpen": false }, @@ -11407,7 +11421,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 139 + "lineNumber": 141 }, "signature": [ "{ calculateAutoTimeExpression: (range: TimeRange) => string | undefined; getDateMetaByDatatableColumn: (column: DatatableColumn) => Promise<{ timeZone: string; timeRange?: TimeRange | undefined; interval: string; } | undefined>; datatableUtilities: { getIndexPattern: (column: DatatableColumn) => Promise; getAggConfig: (column: DatatableColumn) => Promise; isFilterable: (column: DatatableColumn) => boolean; }; createAggConfigs: (indexPattern: IndexPattern, configStates?: Pick>" @@ -11946,7 +11960,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 29 + "lineNumber": 32 }, "signature": [ "{ type: \"kibana_context\"; } & ExecutionContextSearch" @@ -18807,7 +18821,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 204 + "lineNumber": 207 }, "signature": [ "FunctionDefinition" @@ -18821,7 +18835,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 205 + "lineNumber": 208 }, "signature": [ "FunctionDefinition" @@ -18835,7 +18849,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 206 + "lineNumber": 209 }, "signature": [ "FunctionDefinition" @@ -18849,7 +18863,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 207 + "lineNumber": 210 }, "signature": [ "FunctionDefinition" @@ -18863,7 +18877,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 208 + "lineNumber": 211 }, "signature": [ "FunctionDefinition" @@ -18877,7 +18891,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 209 + "lineNumber": 212 }, "signature": [ "FunctionDefinition" @@ -18891,7 +18905,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 210 + "lineNumber": 213 }, "signature": [ "FunctionDefinition" @@ -18905,7 +18919,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 211 + "lineNumber": 214 }, "signature": [ "FunctionDefinition" @@ -18919,7 +18933,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 212 + "lineNumber": 215 }, "signature": [ "FunctionDefinition" @@ -18933,7 +18947,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 213 + "lineNumber": 216 }, "signature": [ "FunctionDefinition" @@ -18947,7 +18961,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 214 + "lineNumber": 217 }, "signature": [ "FunctionDefinition" @@ -18961,7 +18975,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 215 + "lineNumber": 218 }, "signature": [ "FunctionDefinition" @@ -18975,7 +18989,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 216 + "lineNumber": 219 }, "signature": [ "FunctionDefinition" @@ -18989,7 +19003,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 217 + "lineNumber": 220 }, "signature": [ "FunctionDefinition" @@ -19003,7 +19017,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 218 + "lineNumber": 221 }, "signature": [ "FunctionDefinition" @@ -19017,7 +19031,21 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 219 + "lineNumber": 222 + }, + "signature": [ + "FunctionDefinition" + ] + }, + { + "tags": [], + "id": "def-server.AggFunctionsMapping.aggFilteredMetric", + "type": "Object", + "label": "aggFilteredMetric", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/types.ts", + "lineNumber": 223 }, "signature": [ "FunctionDefinition" @@ -19031,7 +19059,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 220 + "lineNumber": 224 }, "signature": [ "FunctionDefinition" @@ -19045,7 +19073,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 221 + "lineNumber": 225 }, "signature": [ "FunctionDefinition" @@ -19059,7 +19087,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 222 + "lineNumber": 226 }, "signature": [ "FunctionDefinition" @@ -19073,7 +19101,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 223 + "lineNumber": 227 }, "signature": [ "FunctionDefinition" @@ -19087,7 +19115,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 224 + "lineNumber": 228 }, "signature": [ "FunctionDefinition" @@ -19101,7 +19129,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 225 + "lineNumber": 229 }, "signature": [ "FunctionDefinition" @@ -19115,7 +19143,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 226 + "lineNumber": 230 }, "signature": [ "FunctionDefinition" @@ -19129,7 +19157,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 227 + "lineNumber": 231 }, "signature": [ "FunctionDefinition" @@ -19143,7 +19171,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 228 + "lineNumber": 232 }, "signature": [ "FunctionDefinition" @@ -19157,7 +19185,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 229 + "lineNumber": 233 }, "signature": [ "FunctionDefinition" @@ -19171,7 +19199,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 230 + "lineNumber": 234 }, "signature": [ "FunctionDefinition" @@ -19185,7 +19213,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 231 + "lineNumber": 235 }, "signature": [ "FunctionDefinition" @@ -19199,7 +19227,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 232 + "lineNumber": 236 }, "signature": [ "FunctionDefinition" @@ -19213,7 +19241,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 233 + "lineNumber": 237 }, "signature": [ "FunctionDefinition" @@ -19227,7 +19255,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 234 + "lineNumber": 238 }, "signature": [ "FunctionDefinition" @@ -19241,7 +19269,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 235 + "lineNumber": 239 }, "signature": [ "FunctionDefinition" @@ -19250,7 +19278,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 203 + "lineNumber": 206 }, "initialIsOpen": false }, @@ -20453,7 +20481,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 14 + "lineNumber": 15 }, "signature": [ "{ filters?: Filter[] | undefined; query?: Query | Query[] | undefined; timeRange?: TimeRange | undefined; }" @@ -20499,7 +20527,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 25 + "lineNumber": 26 }, "signature": [ "ExpressionFunctionDefinition<\"kibana_context\", ", @@ -20530,7 +20558,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 20 + "lineNumber": 21 }, "signature": [ "{ type: \"kibana_context\"; } & ExecutionContextSearch" @@ -20593,7 +20621,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 58 + "lineNumber": 59 }, "signature": [ "AggType>" @@ -20722,7 +20750,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 29 + "lineNumber": 32 }, "signature": [ "{ type: \"kibana_context\"; } & ExecutionContextSearch" @@ -22654,7 +22682,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/filters/build_filters.ts", - "lineNumber": 40 + "lineNumber": 42 } }, { @@ -22667,7 +22695,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/filters/build_filters.ts", - "lineNumber": 41 + "lineNumber": 43 } }, { @@ -22680,7 +22708,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/filters/build_filters.ts", - "lineNumber": 42 + "lineNumber": 44 } }, { @@ -22693,7 +22721,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/filters/build_filters.ts", - "lineNumber": 43 + "lineNumber": 45 } }, { @@ -22706,7 +22734,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/filters/build_filters.ts", - "lineNumber": 44 + "lineNumber": 46 } }, { @@ -22725,7 +22753,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/filters/build_filters.ts", - "lineNumber": 45 + "lineNumber": 47 } } ], @@ -22733,7 +22761,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/es_query/filters/build_filters.ts", - "lineNumber": 39 + "lineNumber": 41 }, "initialIsOpen": false }, @@ -23062,7 +23090,7 @@ "section": "def-common.FilterStateStore", "text": "FilterStateStore" }, - ") => ", + " | undefined) => ", { "pluginId": "data", "scope": "common", @@ -23183,9 +23211,9 @@ } }, { - "type": "Enum", + "type": "CompoundType", "label": "store", - "isRequired": true, + "isRequired": false, "signature": [ { "pluginId": "data", @@ -23193,7 +23221,8 @@ "docId": "kibDataPluginApi", "section": "def-common.FilterStateStore", "text": "FilterStateStore" - } + }, + " | undefined" ], "description": [], "source": { diff --git a/api_docs/data_search.json b/api_docs/data_search.json index 68cf4a1123bdb3..a75b669cbd288d 100644 --- a/api_docs/data_search.json +++ b/api_docs/data_search.json @@ -2955,7 +2955,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 263 + "lineNumber": 265 } }, { @@ -2981,7 +2981,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 294 + "lineNumber": 296 } }, { @@ -3001,7 +3001,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 305 + "lineNumber": 307 } }, { @@ -3027,7 +3027,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 314 + "lineNumber": 316 } }, { @@ -3059,7 +3059,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 369 + "lineNumber": 371 } }, { @@ -3083,7 +3083,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 373 + "lineNumber": 375 } }, { @@ -3107,7 +3107,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 377 + "lineNumber": 379 } }, { @@ -3129,7 +3129,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 381 + "lineNumber": 383 } } ], @@ -3137,7 +3137,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 381 + "lineNumber": 383 } }, { @@ -3159,7 +3159,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 385 + "lineNumber": 387 } }, { @@ -3172,7 +3172,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 385 + "lineNumber": 387 } } ], @@ -3180,7 +3180,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 385 + "lineNumber": 387 } }, { @@ -3196,7 +3196,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 393 + "lineNumber": 395 } }, { @@ -3212,7 +3212,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 399 + "lineNumber": 401 } }, { @@ -3230,7 +3230,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 408 + "lineNumber": 410 } }, { @@ -3252,7 +3252,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 412 + "lineNumber": 414 } } ], @@ -3260,7 +3260,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 412 + "lineNumber": 414 } }, { @@ -3283,7 +3283,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 426 + "lineNumber": 428 } }, { @@ -3307,7 +3307,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 430 + "lineNumber": 432 } }, { @@ -3323,7 +3323,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 434 + "lineNumber": 436 } }, { @@ -3339,7 +3339,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 439 + "lineNumber": 441 } }, { @@ -3350,7 +3350,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 446 + "lineNumber": 448 }, "signature": [ { @@ -3370,7 +3370,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 450 + "lineNumber": 452 }, "signature": [ { @@ -3415,7 +3415,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 480 + "lineNumber": 482 } } ], @@ -3423,7 +3423,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 480 + "lineNumber": 482 } } ], @@ -3448,7 +3448,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 58 + "lineNumber": 65 }, "signature": [ { @@ -3468,7 +3468,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 59 + "lineNumber": 66 }, "signature": [ { @@ -3489,7 +3489,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 62 + "lineNumber": 69 }, "signature": [ { @@ -3527,7 +3527,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 65 + "lineNumber": 72 } }, { @@ -3564,7 +3564,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 66 + "lineNumber": 73 } }, { @@ -3583,7 +3583,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 67 + "lineNumber": 74 } } ], @@ -3591,7 +3591,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 64 + "lineNumber": 71 } }, { @@ -3627,7 +3627,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 79 + "lineNumber": 86 } } ], @@ -3635,7 +3635,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 79 + "lineNumber": 86 } }, { @@ -3664,7 +3664,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 97 + "lineNumber": 104 } } ], @@ -3672,7 +3672,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 97 + "lineNumber": 104 } }, { @@ -3713,7 +3713,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 111 + "lineNumber": 118 } }, { @@ -3726,7 +3726,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 112 + "lineNumber": 119 } } ], @@ -3776,7 +3776,7 @@ "label": "createAggConfig", "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 110 + "lineNumber": 117 }, "tags": [], "returnComment": [] @@ -3819,7 +3819,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 153 + "lineNumber": 160 } } ], @@ -3827,7 +3827,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 153 + "lineNumber": 160 } }, { @@ -3849,7 +3849,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 165 + "lineNumber": 172 } } ], @@ -3857,7 +3857,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 165 + "lineNumber": 172 } }, { @@ -3881,7 +3881,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 227 + "lineNumber": 241 } }, { @@ -3910,7 +3910,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 231 + "lineNumber": 245 } } ], @@ -3918,7 +3918,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 231 + "lineNumber": 245 } }, { @@ -3948,7 +3948,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 235 + "lineNumber": 249 } } ], @@ -3956,7 +3956,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 235 + "lineNumber": 249 } }, { @@ -3986,7 +3986,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 239 + "lineNumber": 253 } } ], @@ -3994,7 +3994,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 239 + "lineNumber": 253 } }, { @@ -4024,7 +4024,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 243 + "lineNumber": 257 } } ], @@ -4032,7 +4032,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 243 + "lineNumber": 257 } }, { @@ -4062,7 +4062,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 247 + "lineNumber": 261 } } ], @@ -4070,7 +4070,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 247 + "lineNumber": 261 } }, { @@ -4100,7 +4100,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 251 + "lineNumber": 265 } } ], @@ -4108,7 +4108,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 251 + "lineNumber": 265 } }, { @@ -4132,7 +4132,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 255 + "lineNumber": 269 } }, { @@ -4162,7 +4162,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 269 + "lineNumber": 283 } } ], @@ -4170,7 +4170,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 269 + "lineNumber": 283 } }, { @@ -4198,7 +4198,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 284 + "lineNumber": 298 } }, { @@ -4232,7 +4232,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 298 + "lineNumber": 312 } } ], @@ -4242,7 +4242,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 298 + "lineNumber": 312 } }, { @@ -4288,7 +4288,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 307 + "lineNumber": 321 } }, { @@ -4308,7 +4308,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 307 + "lineNumber": 321 } } ], @@ -4316,13 +4316,13 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 307 + "lineNumber": 321 } } ], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 57 + "lineNumber": 64 }, "initialIsOpen": false }, @@ -4572,7 +4572,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 70 + "lineNumber": 71 } }, { @@ -4583,7 +4583,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 72 + "lineNumber": 73 } }, { @@ -4594,7 +4594,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 73 + "lineNumber": 74 }, "signature": [ "string | undefined" @@ -4613,7 +4613,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 80 + "lineNumber": 81 } }, { @@ -4629,7 +4629,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 87 + "lineNumber": 88 } }, { @@ -4645,7 +4645,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 94 + "lineNumber": 95 } }, { @@ -4658,7 +4658,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 99 + "lineNumber": 100 }, "signature": [ "\"string\" | \"number\" | \"boolean\" | \"object\" | \"date\" | \"ip\" | \"_source\" | \"attachment\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"unknown\" | \"conflict\" | \"nested\" | \"histogram\" | \"null\" | undefined" @@ -4676,7 +4676,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 108 + "lineNumber": 109 }, "signature": [ "((aggConfig: TAggConfig) => string) | (() => string)" @@ -4695,7 +4695,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 123 + "lineNumber": 124 }, "signature": [ "any" @@ -4713,7 +4713,22 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 131 + "lineNumber": 132 + } + }, + { + "tags": [ + "type" + ], + "id": "def-common.AggType.hasNoDslParams", + "type": "boolean", + "label": "hasNoDslParams", + "description": [ + "\nFlag that prevents params from this aggregation from being included in the dsl. Sibling and parent aggs are still written.\n" + ], + "source": { + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "lineNumber": 138 } }, { @@ -4726,7 +4741,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 138 + "lineNumber": 145 }, "signature": [ "((aggConfig: TAggConfig, key: any, params?: any) => any) | undefined" @@ -4745,7 +4760,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 145 + "lineNumber": 152 }, "signature": [ "TParam[]" @@ -4763,7 +4778,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 155 + "lineNumber": 162 }, "signature": [ "((aggConfig: TAggConfig) => TAggConfig[]) | (() => void | TAggConfig[])" @@ -4781,7 +4796,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 166 + "lineNumber": 173 }, "signature": [ "((aggConfig: TAggConfig) => TAggConfig[]) | (() => void | TAggConfig[])" @@ -4797,7 +4812,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 171 + "lineNumber": 178 }, "signature": [ "() => any" @@ -4815,7 +4830,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 184 + "lineNumber": 191 }, "signature": [ "(resp: any, aggConfigs: ", @@ -4857,7 +4872,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 202 + "lineNumber": 209 }, "signature": [ "(agg: TAggConfig) => ", @@ -4879,7 +4894,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 204 + "lineNumber": 211 }, "signature": [ "(agg: TAggConfig, bucket: any) => any" @@ -4893,7 +4908,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 206 + "lineNumber": 213 }, "signature": [ "((bucket: any, key: any, agg: TAggConfig) => any) | undefined" @@ -4913,7 +4928,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 208 + "lineNumber": 215 } } ], @@ -4924,7 +4939,7 @@ "label": "paramByName", "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 208 + "lineNumber": 215 }, "tags": [], "returnComment": [] @@ -4943,7 +4958,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 212 + "lineNumber": 219 } } ], @@ -4954,7 +4969,7 @@ "label": "getValueBucketPath", "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 212 + "lineNumber": 219 }, "tags": [], "returnComment": [] @@ -4997,7 +5012,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 225 + "lineNumber": 232 } } ], @@ -5008,13 +5023,13 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 225 + "lineNumber": 232 } } ], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 60 + "lineNumber": 61 }, "initialIsOpen": false }, @@ -6450,7 +6465,7 @@ "type": "Function", "label": "setField", "signature": [ - "(field: K, value: ", + "(field: K, value: ", { "pluginId": "data", "scope": "common", @@ -6514,7 +6529,7 @@ "type": "Function", "label": "removeField", "signature": [ - "(field: K) => this" + "(field: K) => this" ], "description": [ "\nremove field" @@ -6641,7 +6656,7 @@ "type": "Function", "label": "getField", "signature": [ - "(field: K, recurse?: boolean) => ", + "(field: K, recurse?: boolean) => ", { "pluginId": "data", "scope": "common", @@ -6694,7 +6709,7 @@ "type": "Function", "label": "getOwnField", "signature": [ - "(field: K) => ", + "(field: K) => ", { "pluginId": "data", "scope": "common", @@ -7625,6 +7640,23 @@ "returnComment": [], "initialIsOpen": false }, + { + "id": "def-common.aggFilteredMetric", + "type": "Function", + "children": [], + "signature": [ + "() => FunctionDefinition" + ], + "description": [], + "label": "aggFilteredMetric", + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric_fn.ts", + "lineNumber": 30 + }, + "tags": [], + "returnComment": [], + "initialIsOpen": false + }, { "id": "def-common.aggFilters", "type": "Function", @@ -8457,6 +8489,76 @@ }, "initialIsOpen": false }, + { + "id": "def-common.filtersToAst", + "type": "Function", + "children": [ + { + "type": "CompoundType", + "label": "filters", + "isRequired": true, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + " | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/filters_to_ast.ts", + "lineNumber": 13 + } + } + ], + "signature": [ + "(filters: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + " | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionAstExpressionBuilder", + "text": "ExpressionAstExpressionBuilder" + }, + "[]" + ], + "description": [], + "label": "filtersToAst", + "source": { + "path": "src/plugins/data/common/search/expressions/filters_to_ast.ts", + "lineNumber": 13 + }, + "tags": [], + "returnComment": [], + "initialIsOpen": false + }, { "id": "def-common.functionWrapper", "type": "Function", @@ -8983,9 +9085,37 @@ { "id": "def-common.getFilterBucketAgg", "type": "Function", - "children": [], + "children": [ + { + "id": "def-common.getFilterBucketAgg.{-getConfig }", + "type": "Object", + "label": "{ getConfig }", + "tags": [], + "description": [], + "children": [ + { + "tags": [], + "id": "def-common.getFilterBucketAgg.{-getConfig }.getConfig", + "type": "Function", + "label": "getConfig", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", + "lineNumber": 27 + }, + "signature": [ + "(key: string) => any" + ] + } + ], + "source": { + "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", + "lineNumber": 27 + } + } + ], "signature": [ - "() => ", + "({ getConfig }: { getConfig: (key: string) => any; }) => ", { "pluginId": "data", "scope": "common", @@ -9007,7 +9137,40 @@ "label": "getFilterBucketAgg", "source": { "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", - "lineNumber": 24 + "lineNumber": 27 + }, + "tags": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "id": "def-common.getFilteredMetricAgg", + "type": "Function", + "children": [], + "signature": [ + "() => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.MetricAggType", + "text": "MetricAggType" + }, + "<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.IMetricAggConfig", + "text": "IMetricAggConfig" + }, + ">" + ], + "description": [], + "label": "getFilteredMetricAgg", + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", + "lineNumber": 30 }, "tags": [], "returnComment": [], @@ -11475,7 +11638,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 38 + "lineNumber": 45 }, "signature": [ { @@ -11490,7 +11653,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/agg_configs.ts", - "lineNumber": 37 + "lineNumber": 44 }, "initialIsOpen": false }, @@ -11511,7 +11674,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 204 + "lineNumber": 207 }, "signature": [ "FunctionDefinition" @@ -11525,7 +11688,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 205 + "lineNumber": 208 }, "signature": [ "FunctionDefinition" @@ -11539,7 +11702,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 206 + "lineNumber": 209 }, "signature": [ "FunctionDefinition" @@ -11553,7 +11716,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 207 + "lineNumber": 210 }, "signature": [ "FunctionDefinition" @@ -11567,7 +11730,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 208 + "lineNumber": 211 }, "signature": [ "FunctionDefinition" @@ -11581,7 +11744,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 209 + "lineNumber": 212 }, "signature": [ "FunctionDefinition" @@ -11595,7 +11758,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 210 + "lineNumber": 213 }, "signature": [ "FunctionDefinition" @@ -11609,7 +11772,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 211 + "lineNumber": 214 }, "signature": [ "FunctionDefinition" @@ -11623,7 +11786,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 212 + "lineNumber": 215 }, "signature": [ "FunctionDefinition" @@ -11637,7 +11800,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 213 + "lineNumber": 216 }, "signature": [ "FunctionDefinition" @@ -11651,7 +11814,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 214 + "lineNumber": 217 }, "signature": [ "FunctionDefinition" @@ -11665,7 +11828,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 215 + "lineNumber": 218 }, "signature": [ "FunctionDefinition" @@ -11679,7 +11842,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 216 + "lineNumber": 219 }, "signature": [ "FunctionDefinition" @@ -11693,7 +11856,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 217 + "lineNumber": 220 }, "signature": [ "FunctionDefinition" @@ -11707,7 +11870,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 218 + "lineNumber": 221 }, "signature": [ "FunctionDefinition" @@ -11721,7 +11884,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 219 + "lineNumber": 222 }, "signature": [ "FunctionDefinition" @@ -11729,13 +11892,13 @@ }, { "tags": [], - "id": "def-common.AggFunctionsMapping.aggCardinality", + "id": "def-common.AggFunctionsMapping.aggFilteredMetric", "type": "Object", - "label": "aggCardinality", + "label": "aggFilteredMetric", "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 220 + "lineNumber": 223 }, "signature": [ "FunctionDefinition" @@ -11743,13 +11906,27 @@ }, { "tags": [], - "id": "def-common.AggFunctionsMapping.aggCount", + "id": "def-common.AggFunctionsMapping.aggCardinality", "type": "Object", - "label": "aggCount", + "label": "aggCardinality", "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 221 + "lineNumber": 224 + }, + "signature": [ + "FunctionDefinition" + ] + }, + { + "tags": [], + "id": "def-common.AggFunctionsMapping.aggCount", + "type": "Object", + "label": "aggCount", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/types.ts", + "lineNumber": 225 }, "signature": [ "FunctionDefinition" @@ -11763,7 +11940,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 222 + "lineNumber": 226 }, "signature": [ "FunctionDefinition" @@ -11777,7 +11954,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 223 + "lineNumber": 227 }, "signature": [ "FunctionDefinition" @@ -11791,7 +11968,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 224 + "lineNumber": 228 }, "signature": [ "FunctionDefinition" @@ -11805,7 +11982,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 225 + "lineNumber": 229 }, "signature": [ "FunctionDefinition" @@ -11819,7 +11996,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 226 + "lineNumber": 230 }, "signature": [ "FunctionDefinition" @@ -11833,7 +12010,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 227 + "lineNumber": 231 }, "signature": [ "FunctionDefinition" @@ -11847,7 +12024,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 228 + "lineNumber": 232 }, "signature": [ "FunctionDefinition" @@ -11861,7 +12038,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 229 + "lineNumber": 233 }, "signature": [ "FunctionDefinition" @@ -11875,7 +12052,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 230 + "lineNumber": 234 }, "signature": [ "FunctionDefinition" @@ -11889,7 +12066,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 231 + "lineNumber": 235 }, "signature": [ "FunctionDefinition" @@ -11903,7 +12080,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 232 + "lineNumber": 236 }, "signature": [ "FunctionDefinition" @@ -11917,7 +12094,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 233 + "lineNumber": 237 }, "signature": [ "FunctionDefinition" @@ -11931,7 +12108,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 234 + "lineNumber": 238 }, "signature": [ "FunctionDefinition" @@ -11945,7 +12122,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 235 + "lineNumber": 239 }, "signature": [ "FunctionDefinition" @@ -11954,7 +12131,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 203 + "lineNumber": 206 }, "initialIsOpen": false }, @@ -12823,7 +13000,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", - "lineNumber": 21 + "lineNumber": 24 }, "signature": [ "Partial<{ top_left: GeoPoint; top_right: GeoPoint; bottom_right: GeoPoint; bottom_left: GeoPoint; }> | { wkt: string; } | GeoBox | undefined" @@ -12832,7 +13009,76 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/buckets/filter.ts", - "lineNumber": 20 + "lineNumber": 23 + }, + "initialIsOpen": false + }, + { + "id": "def-common.AggParamsFilteredMetric", + "type": "Interface", + "label": "AggParamsFilteredMetric", + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.AggParamsFilteredMetric", + "text": "AggParamsFilteredMetric" + }, + " extends ", + "BaseAggParams" + ], + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.AggParamsFilteredMetric.customMetric", + "type": "Object", + "label": "customMetric", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", + "lineNumber": 18 + }, + "signature": [ + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SerializableState", + "text": "SerializableState" + }, + " | undefined; schema?: string | undefined; } | undefined" + ] + }, + { + "tags": [], + "id": "def-common.AggParamsFilteredMetric.customBucket", + "type": "Object", + "label": "customBucket", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", + "lineNumber": 19 + }, + "signature": [ + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SerializableState", + "text": "SerializableState" + }, + " | undefined; schema?: string | undefined; } | undefined" + ] + } + ], + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric.ts", + "lineNumber": 17 }, "initialIsOpen": false }, @@ -14347,6 +14593,20 @@ "boolean | undefined" ] }, + { + "tags": [], + "id": "def-common.AggTypeConfig.hasNoDslParams", + "type": "CompoundType", + "label": "hasNoDslParams", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "lineNumber": 35 + }, + "signature": [ + "boolean | undefined" + ] + }, { "tags": [], "id": "def-common.AggTypeConfig.params", @@ -14355,7 +14615,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 35 + "lineNumber": 36 }, "signature": [ "Partial[] | undefined" @@ -14369,7 +14629,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 36 + "lineNumber": 37 }, "signature": [ "\"string\" | \"number\" | \"boolean\" | \"object\" | \"date\" | \"ip\" | \"_source\" | \"attachment\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"unknown\" | \"conflict\" | \"nested\" | \"histogram\" | \"null\" | undefined" @@ -14383,7 +14643,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 37 + "lineNumber": 38 }, "signature": [ "((aggConfig: TAggConfig) => TAggConfig[]) | (() => void | TAggConfig[]) | undefined" @@ -14397,7 +14657,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 38 + "lineNumber": 39 }, "signature": [ "((aggConfig: TAggConfig) => TAggConfig[]) | (() => void | TAggConfig[]) | undefined" @@ -14411,7 +14671,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 39 + "lineNumber": 40 }, "signature": [ "boolean | undefined" @@ -14425,7 +14685,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 40 + "lineNumber": 41 }, "signature": [ "boolean | undefined" @@ -14439,7 +14699,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 41 + "lineNumber": 42 }, "signature": [ "(() => any) | undefined" @@ -14453,7 +14713,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 42 + "lineNumber": 43 }, "signature": [ "((resp: any, aggConfigs: ", @@ -14491,7 +14751,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 51 + "lineNumber": 52 }, "signature": [ "((agg: TAggConfig) => ", @@ -14513,7 +14773,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 52 + "lineNumber": 53 }, "signature": [ "((agg: TAggConfig, bucket: any) => any) | undefined" @@ -14527,7 +14787,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 53 + "lineNumber": 54 }, "signature": [ "((bucket: any, key: any, agg: TAggConfig) => any) | undefined" @@ -14541,7 +14801,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 54 + "lineNumber": 55 }, "signature": [ "((agg: TAggConfig) => string) | undefined" @@ -17926,6 +18186,21 @@ ], "initialIsOpen": false }, + { + "tags": [], + "id": "def-common.aggFilteredMetricFnName", + "type": "string", + "label": "aggFilteredMetricFnName", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/filtered_metric_fn.ts", + "lineNumber": 14 + }, + "signature": [ + "\"aggFilteredMetric\"" + ], + "initialIsOpen": false + }, { "tags": [], "id": "def-common.aggFilterFnName", @@ -18223,7 +18498,7 @@ ], "source": { "path": "src/plugins/data/common/search/aggs/types.ts", - "lineNumber": 139 + "lineNumber": 141 }, "signature": [ "{ calculateAutoTimeExpression: (range: TimeRange) => string | undefined; getDateMetaByDatatableColumn: (column: DatatableColumn) => Promise<{ timeZone: string; timeRange?: TimeRange | undefined; interval: string; } | undefined>; datatableUtilities: { getIndexPattern: (column: DatatableColumn) => Promise; getAggConfig: (column: DatatableColumn) => Promise; isFilterable: (column: DatatableColumn) => boolean; }; createAggConfigs: (indexPattern: IndexPattern, configStates?: Pick | null, object, ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExpressionValueBoxed", - "text": "ExpressionValueBoxed" - }, - "<\"kibana_context\", ExecutionContextSearch>, ExecutionContext>" - ], - "initialIsOpen": false - }, - { - "id": "def-common.ExpressionFunctionKibanaContext", + "id": "def-common.ExpressionFunctionExistsFilter", "type": "Type", - "label": "ExpressionFunctionKibanaContext", + "label": "ExpressionFunctionExistsFilter", "tags": [], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 25 + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 20 }, "signature": [ - "ExpressionFunctionDefinition<\"kibana_context\", ", + "ExpressionFunctionDefinition<\"existsFilter\", null, Arguments, ", { "pluginId": "expressions", "scope": "common", @@ -18517,30 +18761,15 @@ "section": "def-common.ExpressionValueBoxed", "text": "ExpressionValueBoxed" }, - "<\"kibana_context\", ExecutionContextSearch> | null, Arguments, Promise<", + "<\"kibana_filter\", ", { - "pluginId": "expressions", + "pluginId": "data", "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExpressionValueBoxed", - "text": "ExpressionValueBoxed" + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" }, - "<\"kibana_context\", ExecutionContextSearch>>, ExecutionContext>" - ], - "initialIsOpen": false - }, - { - "id": "def-common.ExpressionFunctionKibanaTimerange", - "type": "Type", - "label": "ExpressionFunctionKibanaTimerange", - "tags": [], - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 15 - }, - "signature": [ - "ExpressionFunctionDefinition<\"timerange\", null, TimeRange, ExpressionValueBoxed<\"timerange\", TimeRange>, ", + ">, ", { "pluginId": "expressions", "scope": "common", @@ -18557,23 +18786,22 @@ "text": "Adapters" }, ", ", - "SerializableState", - ">>" + "SerializableState" ], "initialIsOpen": false }, { - "id": "def-common.ExpressionFunctionKql", + "id": "def-common.ExpressionFunctionField", "type": "Type", - "label": "ExpressionFunctionKql", + "label": "ExpressionFunctionField", "tags": [], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 17 + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 19 }, "signature": [ - "ExpressionFunctionDefinition<\"kql\", null, Arguments, ", + "ExpressionFunctionDefinition<\"field\", null, Arguments, ", { "pluginId": "expressions", "scope": "common", @@ -18581,13 +18809,13 @@ "section": "def-common.ExpressionValueBoxed", "text": "ExpressionValueBoxed" }, - "<\"kibana_query\", ", + "<\"kibana_field\", ", { "pluginId": "data", - "scope": "common", + "scope": "public", "docId": "kibDataPluginApi", - "section": "def-common.Query", - "text": "Query" + "section": "def-public.IndexPatternField", + "text": "IndexPatternField" }, ">, ", { @@ -18611,17 +18839,17 @@ "initialIsOpen": false }, { - "id": "def-common.ExpressionFunctionLucene", + "id": "def-common.ExpressionFunctionKibana", "type": "Type", - "label": "ExpressionFunctionLucene", + "label": "ExpressionFunctionKibana", "tags": [], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", + "path": "src/plugins/data/common/search/expressions/kibana.ts", "lineNumber": 17 }, "signature": [ - "ExpressionFunctionDefinition<\"lucene\", null, Arguments, ", + "ExpressionFunctionDefinition<\"kibana\", ", { "pluginId": "expressions", "scope": "common", @@ -18629,193 +18857,513 @@ "section": "def-common.ExpressionValueBoxed", "text": "ExpressionValueBoxed" }, - "<\"kibana_query\", ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - ">, ", + "<\"kibana_context\", ExecutionContextSearch> | null, object, ", { "pluginId": "expressions", "scope": "common", "docId": "kibExpressionsPluginApi", - "section": "def-common.ExecutionContext", - "text": "ExecutionContext" + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" }, - "<", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - }, - ", ", - "SerializableState" + "<\"kibana_context\", ExecutionContextSearch>, ExecutionContext>" ], "initialIsOpen": false }, { - "id": "def-common.ExpressionValueSearchContext", + "id": "def-common.ExpressionFunctionKibanaContext", "type": "Type", - "label": "ExpressionValueSearchContext", + "label": "ExpressionFunctionKibanaContext", "tags": [], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 20 + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 26 }, "signature": [ - "{ type: \"kibana_context\"; } & ExecutionContextSearch" + "ExpressionFunctionDefinition<\"kibana_context\", ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "<\"kibana_context\", ExecutionContextSearch> | null, Arguments, Promise<", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "<\"kibana_context\", ExecutionContextSearch>>, ExecutionContext>" ], "initialIsOpen": false }, { - "id": "def-common.FieldTypes", + "id": "def-common.ExpressionFunctionKibanaFilter", "type": "Type", - "label": "FieldTypes", + "label": "ExpressionFunctionKibanaFilter", "tags": [], "description": [], "source": { - "path": "src/plugins/data/common/search/aggs/param_types/field.ts", - "lineNumber": 19 + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 18 }, "signature": [ + "ExpressionFunctionDefinition<\"kibanaFilter\", null, Arguments, ", { - "pluginId": "data", + "pluginId": "expressions", "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.KBN_FIELD_TYPES", - "text": "KBN_FIELD_TYPES" + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" }, - "._SOURCE | ", + "<\"kibana_filter\", ", { "pluginId": "data", - "scope": "common", + "scope": "public", "docId": "kibDataPluginApi", - "section": "def-common.KBN_FIELD_TYPES", - "text": "KBN_FIELD_TYPES" + "section": "def-public.Filter", + "text": "Filter" }, - ".ATTACHMENT | ", + ">, ", { - "pluginId": "data", + "pluginId": "expressions", "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.KBN_FIELD_TYPES", - "text": "KBN_FIELD_TYPES" + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" }, - ".BOOLEAN | ", + "<", { - "pluginId": "data", + "pluginId": "inspector", "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.KBN_FIELD_TYPES", - "text": "KBN_FIELD_TYPES" + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" }, - ".DATE | ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.KBN_FIELD_TYPES", - "text": "KBN_FIELD_TYPES" - } + ", ", + "SerializableState" ], "initialIsOpen": false }, { - "id": "def-common.IAggConfig", + "id": "def-common.ExpressionFunctionKibanaTimerange", "type": "Type", - "label": "IAggConfig", - "tags": [ - "name", - "description" - ], + "label": "ExpressionFunctionKibanaTimerange", + "tags": [], "description": [], "source": { - "path": "src/plugins/data/common/search/aggs/agg_config.ts", - "lineNumber": 53 + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 15 }, "signature": [ - "AggConfig" + "ExpressionFunctionDefinition<\"timerange\", null, TimeRange, ExpressionValueBoxed<\"timerange\", TimeRange>, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableState", + ">>" ], "initialIsOpen": false }, { - "id": "def-common.IAggType", + "id": "def-common.ExpressionFunctionKql", "type": "Type", - "label": "IAggType", + "label": "ExpressionFunctionKql", "tags": [], "description": [], "source": { - "path": "src/plugins/data/common/search/aggs/agg_type.ts", - "lineNumber": 58 + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 17 }, "signature": [ - "AggType>" + "ExpressionFunctionDefinition<\"kql\", null, Arguments, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "<\"kibana_query\", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + ">, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableState" ], "initialIsOpen": false }, { - "id": "def-common.IEsSearchResponse", + "id": "def-common.ExpressionFunctionLucene", "type": "Type", - "label": "IEsSearchResponse", + "label": "ExpressionFunctionLucene", "tags": [], "description": [], "source": { - "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 23 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 17 }, "signature": [ - "IKibanaSearchResponse>" + "ExpressionFunctionDefinition<\"lucene\", null, Arguments, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "<\"kibana_query\", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + ">, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableState" ], "initialIsOpen": false }, { - "id": "def-common.IFieldParamType", + "id": "def-common.ExpressionFunctionPhraseFilter", "type": "Type", - "label": "IFieldParamType", + "label": "ExpressionFunctionPhraseFilter", "tags": [], "description": [], "source": { - "path": "src/plugins/data/common/search/aggs/param_types/field.ts", + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", "lineNumber": 21 }, "signature": [ - "FieldParamType" + "ExpressionFunctionDefinition<\"rangeFilter\", null, Arguments, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "<\"kibana_filter\", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ">, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableState" ], "initialIsOpen": false }, { - "id": "def-common.IMetricAggType", + "id": "def-common.ExpressionFunctionRange", "type": "Type", - "label": "IMetricAggType", - "tags": [], - "description": [], - "source": { - "path": "src/plugins/data/common/search/aggs/metrics/metric_agg_type.ts", - "lineNumber": 35 - }, - "signature": [ - "MetricAggType" - ], - "initialIsOpen": false - }, - { + "label": "ExpressionFunctionRange", "tags": [], - "id": "def-common.intervalOptions", - "type": "Array", - "label": "intervalOptions", "description": [], "source": { - "path": "src/plugins/data/common/search/aggs/buckets/_interval_options.ts", - "lineNumber": 15 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 21 }, "signature": [ - "({ display: string; val: string; enabled(agg: ", + "ExpressionFunctionDefinition<\"range\", null, Arguments, ExpressionValueBoxed<\"kibana_range\", Arguments>, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableState", + ">>" + ], + "initialIsOpen": false + }, + { + "id": "def-common.ExpressionFunctionRangeFilter", + "type": "Type", + "label": "ExpressionFunctionRangeFilter", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 22 + }, + "signature": [ + "ExpressionFunctionDefinition<\"rangeFilter\", null, Arguments, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "<\"kibana_filter\", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ">, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableState" + ], + "initialIsOpen": false + }, + { + "id": "def-common.ExpressionValueSearchContext", + "type": "Type", + "label": "ExpressionValueSearchContext", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 21 + }, + "signature": [ + "{ type: \"kibana_context\"; } & ExecutionContextSearch" + ], + "initialIsOpen": false + }, + { + "id": "def-common.FieldTypes", + "type": "Type", + "label": "FieldTypes", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/param_types/field.ts", + "lineNumber": 19 + }, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.KBN_FIELD_TYPES", + "text": "KBN_FIELD_TYPES" + }, + "._SOURCE | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.KBN_FIELD_TYPES", + "text": "KBN_FIELD_TYPES" + }, + ".ATTACHMENT | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.KBN_FIELD_TYPES", + "text": "KBN_FIELD_TYPES" + }, + ".BOOLEAN | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.KBN_FIELD_TYPES", + "text": "KBN_FIELD_TYPES" + }, + ".DATE | ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.KBN_FIELD_TYPES", + "text": "KBN_FIELD_TYPES" + } + ], + "initialIsOpen": false + }, + { + "id": "def-common.IAggConfig", + "type": "Type", + "label": "IAggConfig", + "tags": [ + "name", + "description" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/agg_config.ts", + "lineNumber": 53 + }, + "signature": [ + "AggConfig" + ], + "initialIsOpen": false + }, + { + "id": "def-common.IAggType", + "type": "Type", + "label": "IAggType", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/agg_type.ts", + "lineNumber": 59 + }, + "signature": [ + "AggType>" + ], + "initialIsOpen": false + }, + { + "id": "def-common.IEsSearchResponse", + "type": "Type", + "label": "IEsSearchResponse", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/es_search/types.ts", + "lineNumber": 23 + }, + "signature": [ + "IKibanaSearchResponse>" + ], + "initialIsOpen": false + }, + { + "id": "def-common.IFieldParamType", + "type": "Type", + "label": "IFieldParamType", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/param_types/field.ts", + "lineNumber": 21 + }, + "signature": [ + "FieldParamType" + ], + "initialIsOpen": false + }, + { + "id": "def-common.IMetricAggType", + "type": "Type", + "label": "IMetricAggType", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/metric_agg_type.ts", + "lineNumber": 35 + }, + "signature": [ + "MetricAggType" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.intervalOptions", + "type": "Array", + "label": "intervalOptions", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/buckets/_interval_options.ts", + "lineNumber": 15 + }, + "signature": [ + "({ display: string; val: string; enabled(agg: ", { "pluginId": "data", "scope": "common", @@ -19037,7 +19585,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 28 + "lineNumber": 31 }, "signature": [ "\"kibana_context\"" @@ -19052,7 +19600,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 29 + "lineNumber": 32 }, "signature": [ "{ type: \"kibana_context\"; } & ExecutionContextSearch" @@ -19060,65 +19608,110 @@ "initialIsOpen": false }, { - "id": "def-common.KibanaQueryOutput", + "id": "def-common.KibanaField", "type": "Type", - "label": "KibanaQueryOutput", + "label": "KibanaField", "tags": [], "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 25 + "lineNumber": 28 }, "signature": [ - "{ type: \"kibana_query\"; } & Query" + "{ type: \"kibana_field\"; } & IndexPatternField" ], "initialIsOpen": false }, { - "id": "def-common.KibanaTimerangeOutput", + "id": "def-common.KibanaFilter", "type": "Type", - "label": "KibanaTimerangeOutput", + "label": "KibanaFilter", "tags": [], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 13 + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 27 }, "signature": [ - "{ type: \"timerange\"; } & TimeRange" + "{ type: \"kibana_filter\"; } & Filter" ], "initialIsOpen": false }, { + "id": "def-common.KibanaQueryOutput", + "type": "Type", + "label": "KibanaQueryOutput", "tags": [], - "id": "def-common.parentPipelineType", - "type": "string", - "label": "parentPipelineType", "description": [], "source": { - "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", "lineNumber": 26 }, + "signature": [ + "{ type: \"kibana_query\"; } & Query" + ], "initialIsOpen": false }, { - "id": "def-common.ParsedInterval", + "id": "def-common.KibanaRange", "type": "Type", - "label": "ParsedInterval", + "label": "KibanaRange", "tags": [], "description": [], "source": { - "path": "src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_es_interval.ts", - "lineNumber": 18 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 19 }, "signature": [ - "{ value: number; unit: Unit; type: \"calendar\" | \"fixed\"; }" + "{ type: \"kibana_range\"; } & Arguments" ], "initialIsOpen": false }, { + "id": "def-common.KibanaTimerangeOutput", + "type": "Type", + "label": "KibanaTimerangeOutput", "tags": [], - "id": "def-common.SEARCH_SESSION_TYPE", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 13 + }, + "signature": [ + "{ type: \"timerange\"; } & TimeRange" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.parentPipelineType", + "type": "string", + "label": "parentPipelineType", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", + "lineNumber": 27 + }, + "initialIsOpen": false + }, + { + "id": "def-common.ParsedInterval", + "type": "Type", + "label": "ParsedInterval", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_es_interval.ts", + "lineNumber": 18 + }, + "signature": [ + "{ value: number; unit: Unit; type: \"calendar\" | \"fixed\"; }" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.SEARCH_SESSION_TYPE", "type": "string", "label": "SEARCH_SESSION_TYPE", "description": [], @@ -19169,7 +19762,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts", - "lineNumber": 33 + "lineNumber": 34 }, "initialIsOpen": false }, @@ -19253,634 +19846,2109 @@ "initialIsOpen": false }, { - "id": "def-common.kibana", + "id": "def-common.existsFilterFunction", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibana.name", + "id": "def-common.existsFilterFunction.name", "type": "string", "label": "name", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 27 + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 28 }, "signature": [ - "\"kibana\"" + "\"existsFilter\"" ] }, { "tags": [], - "id": "def-common.kibana.type", + "id": "def-common.existsFilterFunction.type", "type": "string", "label": "type", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 28 + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 29 }, "signature": [ - "\"kibana_context\"" + "\"kibana_filter\"" ] }, { "tags": [], - "id": "def-common.kibana.inputTypes", + "id": "def-common.existsFilterFunction.inputTypes", "type": "Array", "label": "inputTypes", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", "lineNumber": 30 }, "signature": [ - "(\"kibana_context\" | \"null\")[]" + "\"null\"[]" ] }, { "tags": [], - "id": "def-common.kibana.help", + "id": "def-common.existsFilterFunction.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 32 + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 31 } }, { - "id": "def-common.kibana.args", + "id": "def-common.existsFilterFunction.args", "type": "Object", "tags": [], - "children": [], + "children": [ + { + "id": "def-common.existsFilterFunction.args.field", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.existsFilterFunction.args.field.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 36 + }, + "signature": [ + "\"kibana_field\"[]" + ] + }, + { + "tags": [], + "id": "def-common.existsFilterFunction.args.field.required", + "type": "boolean", + "label": "required", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 37 + }, + "signature": [ + "true" + ] + }, + { + "tags": [], + "id": "def-common.existsFilterFunction.args.field.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 38 + } + } + ], + "description": [], + "label": "field", + "source": { + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 35 + } + }, + { + "id": "def-common.existsFilterFunction.args.negate", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.existsFilterFunction.args.negate.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 43 + }, + "signature": [ + "\"boolean\"[]" + ] + }, + { + "tags": [], + "id": "def-common.existsFilterFunction.args.negate.default", + "type": "boolean", + "label": "default", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 44 + }, + "signature": [ + "false" + ] + }, + { + "tags": [], + "id": "def-common.existsFilterFunction.args.negate.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 45 + } + } + ], + "description": [], + "label": "negate", + "source": { + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 42 + } + } + ], "description": [], "label": "args", "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 36 + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 34 } }, { - "id": "def-common.kibana.fn", + "id": "def-common.existsFilterFunction.fn", "type": "Function", "label": "fn", "signature": [ - "(input: Input, _: object, { getSearchContext }: ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExecutionContext", - "text": "ExecutionContext" - }, - "<", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - }, - ", ", + "(input: null, args: Arguments) => { $state?: ", { "pluginId": "data", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExecutionContextSearch", - "text": "ExecutionContextSearch" - }, - ">) => ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExpressionValueBoxed", - "text": "ExpressionValueBoxed" + "docId": "kibDataPluginApi", + "section": "def-common.FilterState", + "text": "FilterState" }, - "<\"kibana_context\", ", + " | undefined; meta: ", { "pluginId": "data", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExecutionContextSearch", - "text": "ExecutionContextSearch" - } + "docId": "kibDataPluginApi", + "section": "def-common.FilterMeta", + "text": "FilterMeta" + }, + "; query?: any; type: \"kibana_filter\"; }" ], "description": [], "children": [ - { - "type": "CompoundType", - "label": "input", - "isRequired": false, - "signature": [ - "Input" - ], - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 38 - } - }, { "type": "Uncategorized", - "label": "_", + "label": "input", "isRequired": true, "signature": [ - "object" + "null" ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 38 + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 51 } }, { "type": "Object", - "label": "{ getSearchContext }", + "label": "args", "isRequired": true, "signature": [ - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExecutionContext", - "text": "ExecutionContext" - }, - "<", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - }, - ", ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExecutionContextSearch", - "text": "ExecutionContextSearch" - }, - ">" + "Arguments" ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 38 + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 51 } } ], "tags": [], "returnComment": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 38 + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 51 } } ], "description": [], - "label": "kibana", + "label": "existsFilterFunction", "source": { - "path": "src/plugins/data/common/search/expressions/kibana.ts", - "lineNumber": 26 + "path": "src/plugins/data/common/search/expressions/exists_filter.ts", + "lineNumber": 27 }, "initialIsOpen": false }, { - "id": "def-common.kibanaContext", + "id": "def-common.fieldFunction", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaContext.name", + "id": "def-common.fieldFunction.name", "type": "string", "label": "name", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 32 - } + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 27 + }, + "signature": [ + "\"field\"" + ] }, { - "id": "def-common.kibanaContext.from", - "type": "Object", "tags": [], - "children": [ - { + "id": "def-common.fieldFunction.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 28 + }, + "signature": [ + "\"kibana_field\"" + ] + }, + { + "tags": [], + "id": "def-common.fieldFunction.inputTypes", + "type": "Array", + "label": "inputTypes", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 29 + }, + "signature": [ + "\"null\"[]" + ] + }, + { + "tags": [], + "id": "def-common.fieldFunction.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 30 + } + }, + { + "id": "def-common.fieldFunction.args", + "type": "Object", + "tags": [], + "children": [ + { + "id": "def-common.fieldFunction.args.name", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.fieldFunction.args.name.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 35 + }, + "signature": [ + "\"string\"[]" + ] + }, + { + "tags": [], + "id": "def-common.fieldFunction.args.name.required", + "type": "boolean", + "label": "required", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 36 + }, + "signature": [ + "true" + ] + }, + { + "tags": [], + "id": "def-common.fieldFunction.args.name.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 37 + } + } + ], + "description": [], + "label": "name", + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 34 + } + }, + { + "id": "def-common.fieldFunction.args.type", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.fieldFunction.args.type.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 42 + }, + "signature": [ + "\"string\"[]" + ] + }, + { + "tags": [], + "id": "def-common.fieldFunction.args.type.required", + "type": "boolean", + "label": "required", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 43 + }, + "signature": [ + "true" + ] + }, + { + "tags": [], + "id": "def-common.fieldFunction.args.type.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 44 + } + } + ], + "description": [], + "label": "type", + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 41 + } + }, + { + "id": "def-common.fieldFunction.args.script", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.fieldFunction.args.script.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 49 + }, + "signature": [ + "\"string\"[]" + ] + }, + { + "tags": [], + "id": "def-common.fieldFunction.args.script.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 50 + } + } + ], + "description": [], + "label": "script", + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 48 + } + } + ], + "description": [], + "label": "args", + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 33 + } + }, + { + "id": "def-common.fieldFunction.fn", + "type": "Function", + "label": "fn", + "signature": [ + "(input: null, args: Arguments) => ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "<\"kibana_field\", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataIndexPatternsPluginApi", + "section": "def-common.IndexPatternField", + "text": "IndexPatternField" + }, + ">" + ], + "description": [], + "children": [ + { + "type": "Uncategorized", + "label": "input", + "isRequired": true, + "signature": [ + "null" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 56 + } + }, + { + "type": "Object", + "label": "args", + "isRequired": true, + "signature": [ + "Arguments" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 56 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 56 + } + } + ], + "description": [], + "label": "fieldFunction", + "source": { + "path": "src/plugins/data/common/search/expressions/field.ts", + "lineNumber": 26 + }, + "initialIsOpen": false + }, + { + "id": "def-common.kibana", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibana.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 27 + }, + "signature": [ + "\"kibana\"" + ] + }, + { + "tags": [], + "id": "def-common.kibana.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 28 + }, + "signature": [ + "\"kibana_context\"" + ] + }, + { + "tags": [], + "id": "def-common.kibana.inputTypes", + "type": "Array", + "label": "inputTypes", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 30 + }, + "signature": [ + "(\"kibana_context\" | \"null\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibana.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 32 + } + }, + { + "id": "def-common.kibana.args", + "type": "Object", + "tags": [], + "children": [], + "description": [], + "label": "args", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 36 + } + }, + { + "id": "def-common.kibana.fn", + "type": "Function", + "label": "fn", + "signature": [ + "(input: Input, _: object, { getSearchContext }: ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExecutionContextSearch", + "text": "ExecutionContextSearch" + }, + ">) => ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "<\"kibana_context\", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExecutionContextSearch", + "text": "ExecutionContextSearch" + } + ], + "description": [], + "children": [ + { + "type": "CompoundType", + "label": "input", + "isRequired": false, + "signature": [ + "Input" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 38 + } + }, + { + "type": "Uncategorized", + "label": "_", + "isRequired": true, + "signature": [ + "object" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 38 + } + }, + { + "type": "Object", + "label": "{ getSearchContext }", + "isRequired": true, + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExecutionContextSearch", + "text": "ExecutionContextSearch" + }, + ">" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 38 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 38 + } + } + ], + "description": [], + "label": "kibana", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana.ts", + "lineNumber": 26 + }, + "initialIsOpen": false + }, + { + "id": "def-common.kibanaContext", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaContext.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 35 + } + }, + { + "id": "def-common.kibanaContext.from", + "type": "Object", + "tags": [], + "children": [ + { "id": "def-common.kibanaContext.from.null", "type": "Function", "children": [], "signature": [ - "() => { type: string; }" + "() => { type: string; }" + ], + "description": [], + "label": "null", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 37 + }, + "tags": [], + "returnComment": [] + } + ], + "description": [], + "label": "from", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 36 + } + }, + { + "id": "def-common.kibanaContext.to", + "type": "Object", + "tags": [], + "children": [ + { + "id": "def-common.kibanaContext.to.null", + "type": "Function", + "children": [], + "signature": [ + "() => { type: string; }" + ], + "description": [], + "label": "null", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 44 + }, + "tags": [], + "returnComment": [] + } + ], + "description": [], + "label": "to", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 43 + } + } + ], + "description": [], + "label": "kibanaContext", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", + "lineNumber": 34 + }, + "initialIsOpen": false + }, + { + "id": "def-common.kibanaContextFunction", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaContextFunction.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 44 + }, + "signature": [ + "\"kibana_context\"" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 45 + }, + "signature": [ + "\"kibana_context\"" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.inputTypes", + "type": "Array", + "label": "inputTypes", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 46 + }, + "signature": [ + "(\"kibana_context\" | \"null\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 47 + } + }, + { + "id": "def-common.kibanaContextFunction.args", + "type": "Object", + "tags": [], + "children": [ + { + "id": "def-common.kibanaContextFunction.args.q", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.q.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 52 + }, + "signature": [ + "(\"null\" | \"kibana_query\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.q.aliases", + "type": "Array", + "label": "aliases", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 53 + }, + "signature": [ + "string[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.q.default", + "type": "Uncategorized", + "label": "default", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 54 + }, + "signature": [ + "null" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.q.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 55 + } + } + ], + "description": [], + "label": "q", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 51 + } + }, + { + "id": "def-common.kibanaContextFunction.args.filters", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.filters.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 60 + }, + "signature": [ + "(\"null\" | \"kibana_filter\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.filters.multi", + "type": "boolean", + "label": "multi", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 61 + }, + "signature": [ + "true" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.filters.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 62 + } + } + ], + "description": [], + "label": "filters", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 59 + } + }, + { + "id": "def-common.kibanaContextFunction.args.timeRange", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.timeRange.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 67 + }, + "signature": [ + "(\"null\" | \"timerange\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.timeRange.default", + "type": "Uncategorized", + "label": "default", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 68 + }, + "signature": [ + "null" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.timeRange.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 69 + } + } + ], + "description": [], + "label": "timeRange", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 66 + } + }, + { + "id": "def-common.kibanaContextFunction.args.savedSearchId", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.savedSearchId.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 74 + }, + "signature": [ + "(\"string\" | \"null\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.savedSearchId.default", + "type": "Uncategorized", + "label": "default", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 75 + }, + "signature": [ + "null" + ] + }, + { + "tags": [], + "id": "def-common.kibanaContextFunction.args.savedSearchId.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 76 + } + } + ], + "description": [], + "label": "savedSearchId", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 73 + } + } + ], + "description": [], + "label": "args", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 50 + } + }, + { + "id": "def-common.kibanaContextFunction.fn", + "type": "Function", + "label": "fn", + "signature": [ + "(input: Input, args: Arguments, { getSavedObject }: ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExecutionContextSearch", + "text": "ExecutionContextSearch" + }, + ">) => Promise<{ type: \"kibana_context\"; query: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + "[]; filters: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + } + ], + "description": [], + "children": [ + { + "type": "CompoundType", + "label": "input", + "isRequired": false, + "signature": [ + "Input" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 82 + } + }, + { + "type": "Object", + "label": "args", + "isRequired": true, + "signature": [ + "Arguments" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 82 + } + }, + { + "type": "Object", + "label": "{ getSavedObject }", + "isRequired": true, + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExecutionContextSearch", + "text": "ExecutionContextSearch" + }, + ">" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 82 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 82 + } + } + ], + "description": [], + "label": "kibanaContextFunction", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 43 + }, + "initialIsOpen": false + }, + { + "id": "def-common.kibanaFilterFunction", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaFilterFunction.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 26 + }, + "signature": [ + "\"kibanaFilter\"" + ] + }, + { + "tags": [], + "id": "def-common.kibanaFilterFunction.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 27 + }, + "signature": [ + "\"kibana_filter\"" + ] + }, + { + "tags": [], + "id": "def-common.kibanaFilterFunction.inputTypes", + "type": "Array", + "label": "inputTypes", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 28 + }, + "signature": [ + "\"null\"[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaFilterFunction.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 29 + } + }, + { + "id": "def-common.kibanaFilterFunction.args", + "type": "Object", + "tags": [], + "children": [ + { + "id": "def-common.kibanaFilterFunction.args.query", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaFilterFunction.args.query.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 34 + }, + "signature": [ + "\"string\"[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaFilterFunction.args.query.aliases", + "type": "Array", + "label": "aliases", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 35 + }, + "signature": [ + "string[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaFilterFunction.args.query.required", + "type": "boolean", + "label": "required", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 36 + }, + "signature": [ + "true" + ] + }, + { + "tags": [], + "id": "def-common.kibanaFilterFunction.args.query.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 37 + } + } + ], + "description": [], + "label": "query", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 33 + } + }, + { + "id": "def-common.kibanaFilterFunction.args.negate", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaFilterFunction.args.negate.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 42 + }, + "signature": [ + "\"boolean\"[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaFilterFunction.args.negate.default", + "type": "boolean", + "label": "default", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 43 + }, + "signature": [ + "false" + ] + }, + { + "tags": [], + "id": "def-common.kibanaFilterFunction.args.negate.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 44 + } + } + ], + "description": [], + "label": "negate", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 41 + } + } + ], + "description": [], + "label": "args", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 32 + } + }, + { + "id": "def-common.kibanaFilterFunction.fn", + "type": "Function", + "label": "fn", + "signature": [ + "(input: null, args: Arguments) => any" + ], + "description": [], + "children": [ + { + "type": "Uncategorized", + "label": "input", + "isRequired": true, + "signature": [ + "null" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 50 + } + }, + { + "type": "Object", + "label": "args", + "isRequired": true, + "signature": [ + "Arguments" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 50 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 50 + } + } + ], + "description": [], + "label": "kibanaFilterFunction", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_filter.ts", + "lineNumber": 25 + }, + "initialIsOpen": false + }, + { + "id": "def-common.kibanaTimerangeFunction", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 23 + }, + "signature": [ + "\"timerange\"" + ] + }, + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 24 + }, + "signature": [ + "\"timerange\"" + ] + }, + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.inputTypes", + "type": "Array", + "label": "inputTypes", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 25 + }, + "signature": [ + "\"null\"[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 26 + } + }, + { + "id": "def-common.kibanaTimerangeFunction.args", + "type": "Object", + "tags": [], + "children": [ + { + "id": "def-common.kibanaTimerangeFunction.args.from", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.args.from.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 31 + }, + "signature": [ + "\"string\"[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.args.from.required", + "type": "boolean", + "label": "required", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 32 + }, + "signature": [ + "true" + ] + }, + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.args.from.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 33 + } + } + ], + "description": [], + "label": "from", + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 30 + } + }, + { + "id": "def-common.kibanaTimerangeFunction.args.to", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.args.to.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 38 + }, + "signature": [ + "\"string\"[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.args.to.required", + "type": "boolean", + "label": "required", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 39 + }, + "signature": [ + "true" + ] + }, + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.args.to.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 40 + } + } + ], + "description": [], + "label": "to", + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 37 + } + }, + { + "id": "def-common.kibanaTimerangeFunction.args.mode", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.args.mode.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 45 + }, + "signature": [ + "\"string\"[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.args.mode.options", + "type": "Array", + "label": "options", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 46 + }, + "signature": [ + "(\"absolute\" | \"relative\")[]" + ] + }, + { + "tags": [], + "id": "def-common.kibanaTimerangeFunction.args.mode.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 47 + } + } + ], + "description": [], + "label": "mode", + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 44 + } + } + ], + "description": [], + "label": "args", + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 29 + } + }, + { + "id": "def-common.kibanaTimerangeFunction.fn", + "type": "Function", + "label": "fn", + "signature": [ + "(input: null, args: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + ") => { type: \"timerange\"; from: string; to: string; mode: \"absolute\" | \"relative\" | undefined; }" + ], + "description": [], + "children": [ + { + "type": "Uncategorized", + "label": "input", + "isRequired": true, + "signature": [ + "null" ], "description": [], - "label": "null", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 34 - }, + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 53 + } + }, + { + "type": "Object", + "label": "args", + "isRequired": true, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + } + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 53 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 53 + } + } + ], + "description": [], + "label": "kibanaTimerangeFunction", + "source": { + "path": "src/plugins/data/common/search/expressions/timerange.ts", + "lineNumber": 22 + }, + "initialIsOpen": false + }, + { + "id": "def-common.kqlFunction", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.kqlFunction.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 25 + }, + "signature": [ + "\"kql\"" + ] + }, + { + "tags": [], + "id": "def-common.kqlFunction.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 26 + }, + "signature": [ + "\"kibana_query\"" + ] + }, + { + "tags": [], + "id": "def-common.kqlFunction.inputTypes", + "type": "Array", + "label": "inputTypes", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 27 + }, + "signature": [ + "\"null\"[]" + ] + }, + { + "tags": [], + "id": "def-common.kqlFunction.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 28 + } + }, + { + "id": "def-common.kqlFunction.args", + "type": "Object", + "tags": [], + "children": [ + { + "id": "def-common.kqlFunction.args.q", + "type": "Object", "tags": [], - "returnComment": [] + "children": [ + { + "tags": [], + "id": "def-common.kqlFunction.args.q.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 33 + }, + "signature": [ + "\"string\"[]" + ] + }, + { + "tags": [], + "id": "def-common.kqlFunction.args.q.required", + "type": "boolean", + "label": "required", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 34 + }, + "signature": [ + "true" + ] + }, + { + "tags": [], + "id": "def-common.kqlFunction.args.q.aliases", + "type": "Array", + "label": "aliases", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 35 + }, + "signature": [ + "string[]" + ] + }, + { + "tags": [], + "id": "def-common.kqlFunction.args.q.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 36 + } + } + ], + "description": [], + "label": "q", + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 32 + } } ], "description": [], - "label": "from", + "label": "args", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 33 + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 31 } }, { - "id": "def-common.kibanaContext.to", - "type": "Object", - "tags": [], + "id": "def-common.kqlFunction.fn", + "type": "Function", + "label": "fn", + "signature": [ + "(input: null, args: Arguments) => { type: \"kibana_query\"; language: string; query: string; }" + ], + "description": [], "children": [ { - "id": "def-common.kibanaContext.to.null", - "type": "Function", - "children": [], + "type": "Uncategorized", + "label": "input", + "isRequired": true, "signature": [ - "() => { type: string; }" + "null" ], "description": [], - "label": "null", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 41 - }, - "tags": [], - "returnComment": [] + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 42 + } + }, + { + "type": "Object", + "label": "args", + "isRequired": true, + "signature": [ + "Arguments" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 42 + } } ], - "description": [], - "label": "to", + "tags": [], + "returnComment": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 40 + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 42 } } ], "description": [], - "label": "kibanaContext", + "label": "kqlFunction", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", - "lineNumber": 31 + "path": "src/plugins/data/common/search/expressions/kql.ts", + "lineNumber": 24 }, "initialIsOpen": false }, { - "id": "def-common.kibanaContextFunction", + "id": "def-common.luceneFunction", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaContextFunction.name", + "id": "def-common.luceneFunction.name", "type": "string", "label": "name", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 43 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 25 }, "signature": [ - "\"kibana_context\"" + "\"lucene\"" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.type", + "id": "def-common.luceneFunction.type", "type": "string", "label": "type", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 44 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 26 }, "signature": [ - "\"kibana_context\"" + "\"kibana_query\"" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.inputTypes", + "id": "def-common.luceneFunction.inputTypes", "type": "Array", "label": "inputTypes", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 45 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 27 }, "signature": [ - "(\"kibana_context\" | \"null\")[]" + "\"null\"[]" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.help", + "id": "def-common.luceneFunction.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 46 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 28 } }, { - "id": "def-common.kibanaContextFunction.args", + "id": "def-common.luceneFunction.args", "type": "Object", "tags": [], "children": [ { - "id": "def-common.kibanaContextFunction.args.q", - "type": "Object", - "tags": [], - "children": [ - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.q.types", - "type": "Array", - "label": "types", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 51 - }, - "signature": [ - "(\"null\" | \"kibana_query\")[]" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.q.aliases", - "type": "Array", - "label": "aliases", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 52 - }, - "signature": [ - "string[]" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.q.default", - "type": "Uncategorized", - "label": "default", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 53 - }, - "signature": [ - "null" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.q.help", - "type": "string", - "label": "help", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 54 - } - } - ], - "description": [], - "label": "q", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 50 - } - }, - { - "id": "def-common.kibanaContextFunction.args.filters", - "type": "Object", - "tags": [], - "children": [ - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.filters.types", - "type": "Array", - "label": "types", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 59 - }, - "signature": [ - "(\"string\" | \"null\")[]" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.filters.default", - "type": "string", - "label": "default", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 60 - } - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.filters.help", - "type": "string", - "label": "help", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 61 - } - } - ], - "description": [], - "label": "filters", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 58 - } - }, - { - "id": "def-common.kibanaContextFunction.args.timeRange", + "id": "def-common.luceneFunction.args.q", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaContextFunction.args.timeRange.types", + "id": "def-common.luceneFunction.args.q.types", "type": "Array", "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 66 - }, - "signature": [ - "(\"null\" | \"timerange\")[]" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.timeRange.default", - "type": "Uncategorized", - "label": "default", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 67 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 33 }, "signature": [ - "null" + "\"string\"[]" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.timeRange.help", - "type": "string", - "label": "help", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 68 - } - } - ], - "description": [], - "label": "timeRange", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 65 - } - }, - { - "id": "def-common.kibanaContextFunction.args.savedSearchId", - "type": "Object", - "tags": [], - "children": [ - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.savedSearchId.types", - "type": "Array", - "label": "types", + "id": "def-common.luceneFunction.args.q.required", + "type": "boolean", + "label": "required", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 73 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 34 }, "signature": [ - "(\"string\" | \"null\")[]" + "true" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.savedSearchId.default", - "type": "Uncategorized", - "label": "default", + "id": "def-common.luceneFunction.args.q.aliases", + "type": "Array", + "label": "aliases", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 74 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 35 }, "signature": [ - "null" + "string[]" ] }, { "tags": [], - "id": "def-common.kibanaContextFunction.args.savedSearchId.help", + "id": "def-common.luceneFunction.args.q.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 75 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 36 } } ], "description": [], - "label": "savedSearchId", + "label": "q", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 72 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 32 } } ], "description": [], "label": "args", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 49 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 31 } }, { - "id": "def-common.kibanaContextFunction.fn", + "id": "def-common.luceneFunction.fn", "type": "Function", "label": "fn", "signature": [ - "(input: Input, args: Arguments, { getSavedObject }: ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExecutionContext", - "text": "ExecutionContext" - }, - "<", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - }, - ", ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExecutionContextSearch", - "text": "ExecutionContextSearch" - }, - ">) => Promise<{ type: \"kibana_context\"; query: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - "[]; filters: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.Filter", - "text": "Filter" - } + "(input: null, args: Arguments) => { type: \"kibana_query\"; language: string; query: any; }" ], "description": [], "children": [ { - "type": "CompoundType", + "type": "Uncategorized", "label": "input", - "isRequired": false, + "isRequired": true, "signature": [ - "Input" + "null" ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 81 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 42 } }, { @@ -19892,105 +21960,201 @@ ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 81 + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 42 } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 42 + } + } + ], + "description": [], + "label": "luceneFunction", + "source": { + "path": "src/plugins/data/common/search/expressions/lucene.ts", + "lineNumber": 24 + }, + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.migrateIncludeExcludeFormat", + "type": "Object", + "label": "migrateIncludeExcludeFormat", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/buckets/migrate_include_exclude_format.ts", + "lineNumber": 25 + }, + "signature": [ + "Partial<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.BucketAggParam", + "text": "BucketAggParam" + }, + "<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.IBucketAggConfig", + "text": "IBucketAggConfig" + }, + ">>" + ], + "initialIsOpen": false + }, + { + "id": "def-common.parentPipelineAggHelper", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.parentPipelineAggHelper.subtype", + "type": "string", + "label": "subtype", + "description": [], + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", + "lineNumber": 35 + } + }, + { + "id": "def-common.parentPipelineAggHelper.params", + "type": "Function", + "label": "params", + "signature": [ + "() => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.MetricAggParam", + "text": "MetricAggParam" + }, + "<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.IMetricAggConfig", + "text": "IMetricAggConfig" + }, + ">[]" + ], + "description": [], + "children": [], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", + "lineNumber": 36 + } + }, + { + "id": "def-common.parentPipelineAggHelper.getSerializedFormat", + "type": "Function", + "label": "getSerializedFormat", + "signature": [ + "(agg: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.IMetricAggConfig", + "text": "IMetricAggConfig" }, + ") => any" + ], + "description": [], + "children": [ { "type": "Object", - "label": "{ getSavedObject }", + "label": "agg", "isRequired": true, "signature": [ - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExecutionContext", - "text": "ExecutionContext" - }, - "<", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - }, - ", ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.ExecutionContextSearch", - "text": "ExecutionContextSearch" - }, - ">" + "section": "def-common.IMetricAggConfig", + "text": "IMetricAggConfig" + } ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 81 + "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", + "lineNumber": 64 } } ], "tags": [], "returnComment": [], "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 81 + "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", + "lineNumber": 64 } } ], "description": [], - "label": "kibanaContextFunction", + "label": "parentPipelineAggHelper", "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 42 + "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", + "lineNumber": 34 }, "initialIsOpen": false }, { - "id": "def-common.kibanaTimerangeFunction", + "id": "def-common.phraseFilterFunction", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaTimerangeFunction.name", + "id": "def-common.phraseFilterFunction.name", "type": "string", "label": "name", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 23 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 29 }, "signature": [ - "\"timerange\"" + "\"rangeFilter\"" ] }, { "tags": [], - "id": "def-common.kibanaTimerangeFunction.type", + "id": "def-common.phraseFilterFunction.type", "type": "string", "label": "type", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 24 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 30 }, "signature": [ - "\"timerange\"" + "\"kibana_filter\"" ] }, { "tags": [], - "id": "def-common.kibanaTimerangeFunction.inputTypes", + "id": "def-common.phraseFilterFunction.inputTypes", "type": "Array", "label": "inputTypes", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 25 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 31 }, "signature": [ "\"null\"[]" @@ -19998,48 +22162,48 @@ }, { "tags": [], - "id": "def-common.kibanaTimerangeFunction.help", + "id": "def-common.phraseFilterFunction.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 26 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 32 } }, { - "id": "def-common.kibanaTimerangeFunction.args", + "id": "def-common.phraseFilterFunction.args", "type": "Object", "tags": [], "children": [ { - "id": "def-common.kibanaTimerangeFunction.args.from", + "id": "def-common.phraseFilterFunction.args.field", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaTimerangeFunction.args.from.types", + "id": "def-common.phraseFilterFunction.args.field.types", "type": "Array", "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 31 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 37 }, "signature": [ - "\"string\"[]" + "\"kibana_field\"[]" ] }, { "tags": [], - "id": "def-common.kibanaTimerangeFunction.args.from.required", + "id": "def-common.phraseFilterFunction.args.field.required", "type": "boolean", "label": "required", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 32 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 38 }, "signature": [ "true" @@ -20047,37 +22211,37 @@ }, { "tags": [], - "id": "def-common.kibanaTimerangeFunction.args.from.help", + "id": "def-common.phraseFilterFunction.args.field.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 33 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 39 } } ], "description": [], - "label": "from", + "label": "field", "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 30 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 36 } }, { - "id": "def-common.kibanaTimerangeFunction.args.to", + "id": "def-common.phraseFilterFunction.args.phrase", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaTimerangeFunction.args.to.types", + "id": "def-common.phraseFilterFunction.args.phrase.types", "type": "Array", "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 38 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 44 }, "signature": [ "\"string\"[]" @@ -20085,13 +22249,27 @@ }, { "tags": [], - "id": "def-common.kibanaTimerangeFunction.args.to.required", + "id": "def-common.phraseFilterFunction.args.phrase.multi", + "type": "boolean", + "label": "multi", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 45 + }, + "signature": [ + "true" + ] + }, + { + "tags": [], + "id": "def-common.phraseFilterFunction.args.phrase.required", "type": "boolean", "label": "required", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 39 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 46 }, "signature": [ "true" @@ -20099,97 +22277,105 @@ }, { "tags": [], - "id": "def-common.kibanaTimerangeFunction.args.to.help", + "id": "def-common.phraseFilterFunction.args.phrase.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 40 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 47 } } ], "description": [], - "label": "to", + "label": "phrase", "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 37 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 43 } }, { - "id": "def-common.kibanaTimerangeFunction.args.mode", + "id": "def-common.phraseFilterFunction.args.negate", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kibanaTimerangeFunction.args.mode.types", + "id": "def-common.phraseFilterFunction.args.negate.types", "type": "Array", "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 45 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 52 }, "signature": [ - "\"string\"[]" + "\"boolean\"[]" ] }, { "tags": [], - "id": "def-common.kibanaTimerangeFunction.args.mode.options", - "type": "Array", - "label": "options", + "id": "def-common.phraseFilterFunction.args.negate.default", + "type": "boolean", + "label": "default", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 46 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 53 }, "signature": [ - "(\"absolute\" | \"relative\")[]" + "false" ] }, { "tags": [], - "id": "def-common.kibanaTimerangeFunction.args.mode.help", + "id": "def-common.phraseFilterFunction.args.negate.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 47 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 54 } } ], "description": [], - "label": "mode", + "label": "negate", "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 44 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 51 } } ], "description": [], "label": "args", "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 29 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 35 } }, { - "id": "def-common.kibanaTimerangeFunction.fn", + "id": "def-common.phraseFilterFunction.fn", "type": "Function", "label": "fn", "signature": [ - "(input: null, args: ", + "(input: null, args: Arguments) => { $state?: ", { "pluginId": "data", "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" + "docId": "kibDataPluginApi", + "section": "def-common.FilterState", + "text": "FilterState" }, - ") => { type: \"timerange\"; from: string; to: string; mode: \"absolute\" | \"relative\" | undefined; }" + " | undefined; meta: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.FilterMeta", + "text": "FilterMeta" + }, + "; query?: any; type: \"kibana_filter\"; }" ], "description": [], "children": [ @@ -20202,8 +22388,8 @@ ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 53 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 60 } }, { @@ -20211,79 +22397,73 @@ "label": "args", "isRequired": true, "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" - } + "Arguments" ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 53 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 60 } } ], "tags": [], "returnComment": [], "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 53 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 60 } } ], "description": [], - "label": "kibanaTimerangeFunction", + "label": "phraseFilterFunction", "source": { - "path": "src/plugins/data/common/search/expressions/timerange.ts", - "lineNumber": 22 + "path": "src/plugins/data/common/search/expressions/phrase_filter.ts", + "lineNumber": 28 }, "initialIsOpen": false }, { - "id": "def-common.kqlFunction", + "id": "def-common.rangeFilterFunction", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kqlFunction.name", + "id": "def-common.rangeFilterFunction.name", "type": "string", "label": "name", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 25 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 30 }, "signature": [ - "\"kql\"" + "\"rangeFilter\"" ] }, { "tags": [], - "id": "def-common.kqlFunction.type", + "id": "def-common.rangeFilterFunction.type", "type": "string", "label": "type", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 26 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 31 }, "signature": [ - "\"kibana_query\"" + "\"kibana_filter\"" ] }, { "tags": [], - "id": "def-common.kqlFunction.inputTypes", + "id": "def-common.rangeFilterFunction.inputTypes", "type": "Array", "label": "inputTypes", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 27 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 32 }, "signature": [ "\"null\"[]" @@ -20291,48 +22471,48 @@ }, { "tags": [], - "id": "def-common.kqlFunction.help", + "id": "def-common.rangeFilterFunction.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 28 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 33 } }, { - "id": "def-common.kqlFunction.args", + "id": "def-common.rangeFilterFunction.args", "type": "Object", "tags": [], "children": [ { - "id": "def-common.kqlFunction.args.q", + "id": "def-common.rangeFilterFunction.args.field", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.kqlFunction.args.q.types", + "id": "def-common.rangeFilterFunction.args.field.types", "type": "Array", "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 33 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 38 }, "signature": [ - "\"string\"[]" + "\"kibana_field\"[]" ] }, { "tags": [], - "id": "def-common.kqlFunction.args.q.required", + "id": "def-common.rangeFilterFunction.args.field.required", "type": "boolean", "label": "required", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 34 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 39 }, "signature": [ "true" @@ -20340,51 +22520,157 @@ }, { "tags": [], - "id": "def-common.kqlFunction.args.q.aliases", + "id": "def-common.rangeFilterFunction.args.field.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 40 + } + } + ], + "description": [], + "label": "field", + "source": { + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 37 + } + }, + { + "id": "def-common.rangeFilterFunction.args.range", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.rangeFilterFunction.args.range.types", "type": "Array", - "label": "aliases", + "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 35 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 45 }, "signature": [ - "string[]" + "\"kibana_range\"[]" ] }, { "tags": [], - "id": "def-common.kqlFunction.args.q.help", + "id": "def-common.rangeFilterFunction.args.range.required", + "type": "boolean", + "label": "required", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 46 + }, + "signature": [ + "true" + ] + }, + { + "tags": [], + "id": "def-common.rangeFilterFunction.args.range.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 36 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 47 } } ], "description": [], - "label": "q", + "label": "range", "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 32 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 44 + } + }, + { + "id": "def-common.rangeFilterFunction.args.negate", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.rangeFilterFunction.args.negate.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 52 + }, + "signature": [ + "\"boolean\"[]" + ] + }, + { + "tags": [], + "id": "def-common.rangeFilterFunction.args.negate.default", + "type": "boolean", + "label": "default", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 53 + }, + "signature": [ + "false" + ] + }, + { + "tags": [], + "id": "def-common.rangeFilterFunction.args.negate.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 54 + } + } + ], + "description": [], + "label": "negate", + "source": { + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 51 } } ], "description": [], "label": "args", "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 31 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 36 } }, { - "id": "def-common.kqlFunction.fn", + "id": "def-common.rangeFilterFunction.fn", "type": "Function", "label": "fn", "signature": [ - "(input: null, args: Arguments) => { type: \"kibana_query\"; language: string; query: string; }" + "(input: null, args: Arguments) => { $state?: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.FilterState", + "text": "FilterState" + }, + " | undefined; meta: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.FilterMeta", + "text": "FilterMeta" + }, + "; query?: any; type: \"kibana_filter\"; }" ], "description": [], "children": [ @@ -20397,8 +22683,8 @@ ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 42 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 60 } }, { @@ -20410,69 +22696,69 @@ ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 42 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 60 } } ], "tags": [], "returnComment": [], "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 42 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 60 } } ], "description": [], - "label": "kqlFunction", + "label": "rangeFilterFunction", "source": { - "path": "src/plugins/data/common/search/expressions/kql.ts", - "lineNumber": 24 + "path": "src/plugins/data/common/search/expressions/range_filter.ts", + "lineNumber": 29 }, "initialIsOpen": false }, { - "id": "def-common.luceneFunction", + "id": "def-common.rangeFunction", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.luceneFunction.name", + "id": "def-common.rangeFunction.name", "type": "string", "label": "name", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 25 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 29 }, "signature": [ - "\"lucene\"" + "\"range\"" ] }, { "tags": [], - "id": "def-common.luceneFunction.type", + "id": "def-common.rangeFunction.type", "type": "string", "label": "type", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 26 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 30 }, "signature": [ - "\"kibana_query\"" + "\"kibana_range\"" ] }, { "tags": [], - "id": "def-common.luceneFunction.inputTypes", + "id": "def-common.rangeFunction.inputTypes", "type": "Array", "label": "inputTypes", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 27 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 31 }, "signature": [ "\"null\"[]" @@ -20480,100 +22766,186 @@ }, { "tags": [], - "id": "def-common.luceneFunction.help", + "id": "def-common.rangeFunction.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 28 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 32 } }, { - "id": "def-common.luceneFunction.args", + "id": "def-common.rangeFunction.args", "type": "Object", "tags": [], "children": [ { - "id": "def-common.luceneFunction.args.q", + "id": "def-common.rangeFunction.args.gt", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.luceneFunction.args.q.types", + "id": "def-common.rangeFunction.args.gt.types", "type": "Array", "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 33 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 37 }, "signature": [ - "\"string\"[]" + "(\"string\" | \"number\")[]" ] }, { "tags": [], - "id": "def-common.luceneFunction.args.q.required", - "type": "boolean", - "label": "required", + "id": "def-common.rangeFunction.args.gt.help", + "type": "string", + "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 34 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 38 + } + } + ], + "description": [], + "label": "gt", + "source": { + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 36 + } + }, + { + "id": "def-common.rangeFunction.args.lt", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.rangeFunction.args.lt.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 43 }, "signature": [ - "true" + "(\"string\" | \"number\")[]" ] }, { "tags": [], - "id": "def-common.luceneFunction.args.q.aliases", + "id": "def-common.rangeFunction.args.lt.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 44 + } + } + ], + "description": [], + "label": "lt", + "source": { + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 42 + } + }, + { + "id": "def-common.rangeFunction.args.gte", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.rangeFunction.args.gte.types", "type": "Array", - "label": "aliases", + "label": "types", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 35 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 49 }, "signature": [ - "string[]" + "(\"string\" | \"number\")[]" ] }, { "tags": [], - "id": "def-common.luceneFunction.args.q.help", + "id": "def-common.rangeFunction.args.gte.help", "type": "string", "label": "help", "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 36 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 50 } } ], "description": [], - "label": "q", + "label": "gte", "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 32 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 48 + } + }, + { + "id": "def-common.rangeFunction.args.lte", + "type": "Object", + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.rangeFunction.args.lte.types", + "type": "Array", + "label": "types", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 55 + }, + "signature": [ + "(\"string\" | \"number\")[]" + ] + }, + { + "tags": [], + "id": "def-common.rangeFunction.args.lte.help", + "type": "string", + "label": "help", + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 56 + } + } + ], + "description": [], + "label": "lte", + "source": { + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 54 } } ], "description": [], "label": "args", "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 31 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 35 } }, { - "id": "def-common.luceneFunction.fn", + "id": "def-common.rangeFunction.fn", "type": "Function", "label": "fn", "signature": [ - "(input: null, args: Arguments) => { type: \"kibana_query\"; language: string; query: any; }" + "(input: null, args: Arguments) => { gt?: string | number | undefined; lt?: string | number | undefined; gte?: string | number | undefined; lte?: string | number | undefined; type: \"kibana_range\"; }" ], "description": [], "children": [ @@ -20586,8 +22958,8 @@ ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 42 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 62 } }, { @@ -20599,80 +22971,49 @@ ], "description": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 42 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 62 } } ], "tags": [], "returnComment": [], "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 42 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 62 } } ], "description": [], - "label": "luceneFunction", - "source": { - "path": "src/plugins/data/common/search/expressions/lucene.ts", - "lineNumber": 24 - }, - "initialIsOpen": false - }, - { - "tags": [], - "id": "def-common.migrateIncludeExcludeFormat", - "type": "Object", - "label": "migrateIncludeExcludeFormat", - "description": [], + "label": "rangeFunction", "source": { - "path": "src/plugins/data/common/search/aggs/buckets/migrate_include_exclude_format.ts", - "lineNumber": 25 + "path": "src/plugins/data/common/search/expressions/range.ts", + "lineNumber": 28 }, - "signature": [ - "Partial<", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggParam", - "text": "BucketAggParam" - }, - "<", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.IBucketAggConfig", - "text": "IBucketAggConfig" - }, - ">>" - ], "initialIsOpen": false }, { - "id": "def-common.parentPipelineAggHelper", + "id": "def-common.siblingPipelineAggHelper", "type": "Object", "tags": [], "children": [ { "tags": [], - "id": "def-common.parentPipelineAggHelper.subtype", + "id": "def-common.siblingPipelineAggHelper.subtype", "type": "string", "label": "subtype", "description": [], "source": { - "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", - "lineNumber": 34 + "path": "src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts", + "lineNumber": 42 } }, { - "id": "def-common.parentPipelineAggHelper.params", + "id": "def-common.siblingPipelineAggHelper.params", "type": "Function", "label": "params", "signature": [ - "() => ", + "(bucketFilter?: string[]) => ", { "pluginId": "data", "scope": "common", @@ -20691,113 +23032,26 @@ ">[]" ], "description": [], - "children": [], - "tags": [], - "returnComment": [], - "source": { - "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", - "lineNumber": 35 - } - }, - { - "id": "def-common.parentPipelineAggHelper.getSerializedFormat", - "type": "Function", - "label": "getSerializedFormat", - "signature": [ - "(agg: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.IMetricAggConfig", - "text": "IMetricAggConfig" - }, - ") => any" - ], - "description": [], "children": [ { - "type": "Object", - "label": "agg", + "type": "Array", + "label": "bucketFilter", "isRequired": true, "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.IMetricAggConfig", - "text": "IMetricAggConfig" - } + "string[]" ], "description": [], "source": { - "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", - "lineNumber": 63 + "path": "src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts", + "lineNumber": 43 } } ], "tags": [], "returnComment": [], - "source": { - "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", - "lineNumber": 63 - } - } - ], - "description": [], - "label": "parentPipelineAggHelper", - "source": { - "path": "src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts", - "lineNumber": 33 - }, - "initialIsOpen": false - }, - { - "id": "def-common.siblingPipelineAggHelper", - "type": "Object", - "tags": [], - "children": [ - { - "tags": [], - "id": "def-common.siblingPipelineAggHelper.subtype", - "type": "string", - "label": "subtype", - "description": [], - "source": { - "path": "src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts", - "lineNumber": 41 - } - }, - { - "id": "def-common.siblingPipelineAggHelper.params", - "type": "Function", - "label": "params", - "signature": [ - "() => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggParam", - "text": "MetricAggParam" - }, - "<", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.IMetricAggConfig", - "text": "IMetricAggConfig" - }, - ">[]" - ], - "description": [], - "children": [], - "tags": [], - "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts", - "lineNumber": 42 + "lineNumber": 43 } }, { @@ -20833,7 +23087,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts", - "lineNumber": 77 + "lineNumber": 79 } } ], @@ -20841,7 +23095,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts", - "lineNumber": 77 + "lineNumber": 79 } } ], @@ -20849,7 +23103,7 @@ "label": "siblingPipelineAggHelper", "source": { "path": "src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts", - "lineNumber": 40 + "lineNumber": 41 }, "initialIsOpen": false } diff --git a/api_docs/expressions.json b/api_docs/expressions.json index f80d9b6c4cdd38..eefffb009be2ac 100644 --- a/api_docs/expressions.json +++ b/api_docs/expressions.json @@ -21952,6 +21952,41 @@ "returnComment": [], "initialIsOpen": false }, + { + "id": "def-common.createMockContext", + "type": "Function", + "children": [], + "signature": [ + "() => ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + "SerializableState", + ">" + ], + "description": [], + "label": "createMockContext", + "source": { + "path": "src/plugins/expressions/common/util/test_utils.ts", + "lineNumber": 11 + }, + "tags": [], + "returnComment": [], + "initialIsOpen": false + }, { "id": "def-common.format", "type": "Function", @@ -22465,6 +22500,52 @@ "tags": [], "returnComment": [], "initialIsOpen": false + }, + { + "id": "def-common.unboxExpressionValue", + "type": "Function", + "label": "unboxExpressionValue", + "signature": [ + "({\n type,\n ...value\n}: ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + ") => T" + ], + "description": [], + "children": [ + { + "type": "CompoundType", + "label": "{\n type,\n ...value\n}", + "isRequired": true, + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueBoxed", + "text": "ExpressionValueBoxed" + }, + "" + ], + "description": [], + "source": { + "path": "src/plugins/expressions/common/expression_types/unbox_expression_value.ts", + "lineNumber": 11 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/plugins/expressions/common/expression_types/unbox_expression_value.ts", + "lineNumber": 11 + }, + "initialIsOpen": false } ], "interfaces": [ diff --git a/api_docs/fleet.json b/api_docs/fleet.json index d9774d14e4c961..ed51f88ee9d5d9 100644 --- a/api_docs/fleet.json +++ b/api_docs/fleet.json @@ -2515,7 +2515,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 94 + "lineNumber": 95 }, "signature": [ { @@ -2535,7 +2535,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 95 + "lineNumber": 96 }, "signature": [ { @@ -2556,7 +2556,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 96 + "lineNumber": 97 }, "signature": [ { @@ -2577,7 +2577,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 97 + "lineNumber": 98 }, "signature": [ { @@ -2597,7 +2597,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 98 + "lineNumber": 99 }, "signature": [ { @@ -2618,7 +2618,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 99 + "lineNumber": 100 }, "signature": [ "Pick<", @@ -2635,7 +2635,7 @@ ], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 93 + "lineNumber": 94 }, "initialIsOpen": false }, @@ -2735,7 +2735,7 @@ ], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 137 + "lineNumber": 140 }, "signature": [ "[\"packagePolicyCreate\", (newPackagePolicy: ", @@ -2837,7 +2837,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 122 + "lineNumber": 125 }, "signature": [ "void" @@ -2862,7 +2862,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 161 + "lineNumber": 164 }, "signature": [ { @@ -2882,7 +2882,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 162 + "lineNumber": 165 }, "signature": [ { @@ -2902,7 +2902,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 163 + "lineNumber": 166 }, "signature": [ { @@ -2924,7 +2924,7 @@ ], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 167 + "lineNumber": 170 }, "signature": [ { @@ -2944,7 +2944,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 168 + "lineNumber": 171 }, "signature": [ { @@ -2966,7 +2966,7 @@ ], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 173 + "lineNumber": 176 }, "signature": [ "(...args: ", @@ -2990,7 +2990,7 @@ ], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 179 + "lineNumber": 182 }, "signature": [ "(packageName: string) => ", @@ -3006,7 +3006,7 @@ ], "source": { "path": "x-pack/plugins/fleet/server/plugin.ts", - "lineNumber": 160 + "lineNumber": 163 }, "lifecycle": "start", "initialIsOpen": true @@ -3274,7 +3274,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/index.ts", - "lineNumber": 38 + "lineNumber": 39 }, "signature": [ "(o: T) => [keyof T, T[keyof T]][]" @@ -4486,59 +4486,44 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 86 + "lineNumber": 91 } }, { "tags": [], - "id": "def-common.BulkInstallPackageInfo.newVersion", + "id": "def-common.BulkInstallPackageInfo.version", "type": "string", - "label": "newVersion", + "label": "version", "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 87 + "lineNumber": 92 } }, { "tags": [], - "id": "def-common.BulkInstallPackageInfo.oldVersion", - "type": "CompoundType", - "label": "oldVersion", - "description": [], - "source": { - "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 89 - }, - "signature": [ - "string | null" - ] - }, - { - "tags": [], - "id": "def-common.BulkInstallPackageInfo.assets", - "type": "Array", - "label": "assets", + "id": "def-common.BulkInstallPackageInfo.result", + "type": "Object", + "label": "result", "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 90 + "lineNumber": 93 }, "signature": [ { "pluginId": "fleet", "scope": "common", "docId": "kibFleetPluginApi", - "section": "def-common.AssetReference", - "text": "AssetReference" - }, - "[]" + "section": "def-common.InstallResult", + "text": "InstallResult" + } ] } ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 85 + "lineNumber": 90 }, "initialIsOpen": false }, @@ -4557,7 +4542,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 98 + "lineNumber": 101 }, "signature": [ "{ packages: string[]; }" @@ -4566,7 +4551,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 97 + "lineNumber": 100 }, "initialIsOpen": false }, @@ -4585,7 +4570,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 94 + "lineNumber": 97 }, "signature": [ "(", @@ -4610,7 +4595,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 93 + "lineNumber": 96 }, "initialIsOpen": false }, @@ -5235,7 +5220,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 108 + "lineNumber": 111 }, "signature": [ "{ pkgkey: string; }" @@ -5244,7 +5229,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 107 + "lineNumber": 110 }, "initialIsOpen": false }, @@ -5263,7 +5248,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 114 + "lineNumber": 117 }, "signature": [ { @@ -5279,7 +5264,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 113 + "lineNumber": 116 }, "initialIsOpen": false }, @@ -5507,7 +5492,7 @@ "lineNumber": 15 }, "signature": [ - "{ enabled: boolean; tlsCheckDisabled: boolean; pollingRequestTimeout: number; maxConcurrentConnections: number; kibana: { host?: string | string[] | undefined; ca_sha256?: string | undefined; }; elasticsearch: { host?: string | undefined; ca_sha256?: string | undefined; }; agentPolicyRolloutRateLimitIntervalMs: number; agentPolicyRolloutRateLimitRequestPerInterval: number; }" + "{ enabled: boolean; fleetServerEnabled: boolean; tlsCheckDisabled: boolean; pollingRequestTimeout: number; maxConcurrentConnections: number; kibana: { host?: string | string[] | undefined; ca_sha256?: string | undefined; }; elasticsearch: { host?: string | undefined; ca_sha256?: string | undefined; }; agentPolicyRolloutRateLimitIntervalMs: number; agentPolicyRolloutRateLimitRequestPerInterval: number; }" ] } ], @@ -8609,6 +8594,55 @@ }, "initialIsOpen": false }, + { + "id": "def-common.InstallResult", + "type": "Interface", + "label": "InstallResult", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-common.InstallResult.assets", + "type": "Array", + "label": "assets", + "description": [], + "source": { + "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", + "lineNumber": 86 + }, + "signature": [ + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.AssetReference", + "text": "AssetReference" + }, + "[]" + ] + }, + { + "tags": [], + "id": "def-common.InstallResult.status", + "type": "CompoundType", + "label": "status", + "description": [], + "source": { + "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", + "lineNumber": 87 + }, + "signature": [ + "\"installed\" | \"already_installed\"" + ] + } + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", + "lineNumber": 85 + }, + "initialIsOpen": false + }, { "id": "def-common.InstallScriptRequest", "type": "Interface", @@ -8824,13 +8858,13 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 104 + "lineNumber": 107 } } ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 103 + "lineNumber": 106 }, "initialIsOpen": false }, @@ -13209,7 +13243,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/constants/epm.ts", - "lineNumber": 13 + "lineNumber": 12 }, "signature": [ "\"fleet_server\"" @@ -13246,21 +13280,6 @@ ], "initialIsOpen": false }, - { - "tags": [], - "id": "def-common.INDEX_PATTERN_SAVED_OBJECT_TYPE", - "type": "string", - "label": "INDEX_PATTERN_SAVED_OBJECT_TYPE", - "description": [], - "source": { - "path": "x-pack/plugins/fleet/common/constants/epm.ts", - "lineNumber": 10 - }, - "signature": [ - "\"index-pattern\"" - ], - "initialIsOpen": false - }, { "tags": [], "id": "def-common.INSTALL_SCRIPT_API_ROUTES", @@ -13460,7 +13479,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/constants/epm.ts", - "lineNumber": 11 + "lineNumber": 10 }, "signature": [ "60000" @@ -14087,7 +14106,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/index.ts", - "lineNumber": 43 + "lineNumber": 44 }, "signature": [ "T[keyof T]" @@ -14443,7 +14462,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/constants/epm.ts", - "lineNumber": 24 + "lineNumber": 23 }, "signature": [ "{ readonly Input: \"input\"; }" @@ -15151,7 +15170,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/constants/epm.ts", - "lineNumber": 28 + "lineNumber": 27 }, "signature": [ "{ readonly Logs: \"logs\"; readonly Metrics: \"metrics\"; }" @@ -15481,7 +15500,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/constants/epm.ts", - "lineNumber": 22 + "lineNumber": 21 }, "signature": [ "{ readonly System: \"system\"; readonly Endpoint: \"endpoint\"; readonly ElasticAgent: \"elastic_agent\"; }" @@ -16058,7 +16077,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/constants/epm.ts", - "lineNumber": 33 + "lineNumber": 32 }, "signature": [ "{ readonly Installed: \"installed\"; readonly NotInstalled: \"not_installed\"; }" @@ -16416,7 +16435,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/constants/epm.ts", - "lineNumber": 15 + "lineNumber": 14 }, "signature": [ "{ readonly System: \"system\"; readonly Endpoint: \"endpoint\"; readonly ElasticAgent: \"elastic_agent\"; }" diff --git a/api_docs/lens.json b/api_docs/lens.json index abebd217ad7d59..e586016c22fc3f 100644 --- a/api_docs/lens.json +++ b/api_docs/lens.json @@ -288,7 +288,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts", - "lineNumber": 44 + "lineNumber": 46 }, "signature": [ "\"range\" | \"filters\" | \"count\" | \"max\" | \"min\" | \"date_histogram\" | \"sum\" | \"terms\" | \"avg\" | \"median\" | \"cumulative_sum\" | \"derivative\" | \"moving_average\" | \"counter_rate\" | \"cardinality\" | \"percentile\" | \"last_value\" | undefined" @@ -302,7 +302,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts", - "lineNumber": 45 + "lineNumber": 47 }, "signature": [ "string | undefined" @@ -311,7 +311,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts", - "lineNumber": 43 + "lineNumber": 45 }, "initialIsOpen": false }, @@ -1318,7 +1318,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx", - "lineNumber": 125 + "lineNumber": 127 }, "signature": [ "BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"avg\"; }" @@ -1475,7 +1475,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx", - "lineNumber": 127 + "lineNumber": 129 }, "signature": [ "BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"max\"; }" @@ -1490,7 +1490,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx", - "lineNumber": 128 + "lineNumber": 130 }, "signature": [ "BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"median\"; }" @@ -1505,7 +1505,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx", - "lineNumber": 126 + "lineNumber": 128 }, "signature": [ "BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"min\"; }" @@ -1538,7 +1538,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts", - "lineNumber": 405 + "lineNumber": 406 }, "signature": [ "\"range\" | \"filters\" | \"count\" | \"max\" | \"min\" | \"date_histogram\" | \"sum\" | \"terms\" | \"avg\" | \"median\" | \"cumulative_sum\" | \"derivative\" | \"moving_average\" | \"counter_rate\" | \"cardinality\" | \"percentile\" | \"last_value\"" @@ -1598,7 +1598,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx", - "lineNumber": 124 + "lineNumber": 126 }, "signature": [ "BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"sum\"; }" diff --git a/api_docs/observability.json b/api_docs/observability.json index a3d1bc950cb537..439fd18db6469f 100644 --- a/api_docs/observability.json +++ b/api_docs/observability.json @@ -2351,7 +2351,7 @@ "description": [], "source": { "path": "x-pack/plugins/observability/server/plugin.ts", - "lineNumber": 22 + "lineNumber": 23 }, "signature": [ "LazyScopedAnnotationsClientFactory" @@ -2360,7 +2360,7 @@ ], "source": { "path": "x-pack/plugins/observability/server/plugin.ts", - "lineNumber": 21 + "lineNumber": 22 }, "lifecycle": "setup", "initialIsOpen": true diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilteredmetric.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilteredmetric.md new file mode 100644 index 00000000000000..71e3e025b931df --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilteredmetric.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggFunctionsMapping](./kibana-plugin-plugins-data-public.aggfunctionsmapping.md) > [aggFilteredMetric](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilteredmetric.md) + +## AggFunctionsMapping.aggFilteredMetric property + +Signature: + +```typescript +aggFilteredMetric: ReturnType; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggfunctionsmapping.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggfunctionsmapping.md index b175b8d473d34b..05388e2b86d7b5 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggfunctionsmapping.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggfunctionsmapping.md @@ -28,6 +28,7 @@ export interface AggFunctionsMapping | [aggDateRange](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggdaterange.md) | ReturnType<typeof aggDateRange> | | | [aggDerivative](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggderivative.md) | ReturnType<typeof aggDerivative> | | | [aggFilter](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilter.md) | ReturnType<typeof aggFilter> | | +| [aggFilteredMetric](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilteredmetric.md) | ReturnType<typeof aggFilteredMetric> | | | [aggFilters](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilters.md) | ReturnType<typeof aggFilters> | | | [aggGeoBounds](./kibana-plugin-plugins-data-public.aggfunctionsmapping.agggeobounds.md) | ReturnType<typeof aggGeoBounds> | | | [aggGeoCentroid](./kibana-plugin-plugins-data-public.aggfunctionsmapping.agggeocentroid.md) | ReturnType<typeof aggGeoCentroid> | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.metric_types.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.metric_types.md index 637717692a38c1..3b5cecf1a0b82a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.metric_types.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.metric_types.md @@ -20,6 +20,7 @@ export declare enum METRIC_TYPES | COUNT | "count" | | | CUMULATIVE\_SUM | "cumulative_sum" | | | DERIVATIVE | "derivative" | | +| FILTERED\_METRIC | "filtered_metric" | | | GEO\_BOUNDS | "geo_bounds" | | | GEO\_CENTROID | "geo_centroid" | | | MAX | "max" | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilteredmetric.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilteredmetric.md new file mode 100644 index 00000000000000..9885a0afa40c6a --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilteredmetric.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggFunctionsMapping](./kibana-plugin-plugins-data-server.aggfunctionsmapping.md) > [aggFilteredMetric](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilteredmetric.md) + +## AggFunctionsMapping.aggFilteredMetric property + +Signature: + +```typescript +aggFilteredMetric: ReturnType; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggfunctionsmapping.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggfunctionsmapping.md index f059bb2914847e..86bf797572b09b 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggfunctionsmapping.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggfunctionsmapping.md @@ -28,6 +28,7 @@ export interface AggFunctionsMapping | [aggDateRange](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggdaterange.md) | ReturnType<typeof aggDateRange> | | | [aggDerivative](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggderivative.md) | ReturnType<typeof aggDerivative> | | | [aggFilter](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilter.md) | ReturnType<typeof aggFilter> | | +| [aggFilteredMetric](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilteredmetric.md) | ReturnType<typeof aggFilteredMetric> | | | [aggFilters](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilters.md) | ReturnType<typeof aggFilters> | | | [aggGeoBounds](./kibana-plugin-plugins-data-server.aggfunctionsmapping.agggeobounds.md) | ReturnType<typeof aggGeoBounds> | | | [aggGeoCentroid](./kibana-plugin-plugins-data-server.aggfunctionsmapping.agggeocentroid.md) | ReturnType<typeof aggGeoCentroid> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.metric_types.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.metric_types.md index 49df98b6d70a19..250173d11a056d 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.metric_types.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.metric_types.md @@ -20,6 +20,7 @@ export declare enum METRIC_TYPES | COUNT | "count" | | | CUMULATIVE\_SUM | "cumulative_sum" | | | DERIVATIVE | "derivative" | | +| FILTERED\_METRIC | "filtered_metric" | | | GEO\_BOUNDS | "geo_bounds" | | | GEO\_CENTROID | "geo_centroid" | | | MAX | "max" | | diff --git a/src/plugins/data/common/search/aggs/agg_config.ts b/src/plugins/data/common/search/aggs/agg_config.ts index f62fedc13b32a0..0e9cf6aeb1f2f7 100644 --- a/src/plugins/data/common/search/aggs/agg_config.ts +++ b/src/plugins/data/common/search/aggs/agg_config.ts @@ -232,7 +232,9 @@ export class AggConfig { const output = this.write(aggConfigs) as any; const configDsl = {} as any; - configDsl[this.type.dslName || this.type.name] = output.params; + if (!this.type.hasNoDslParams) { + configDsl[this.type.dslName || this.type.name] = output.params; + } // if the config requires subAggs, write them to the dsl as well if (this.subAggs.length) { diff --git a/src/plugins/data/common/search/aggs/agg_configs.ts b/src/plugins/data/common/search/aggs/agg_configs.ts index c9986b7e93bed2..03c702aa72fb51 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.ts @@ -21,7 +21,14 @@ import { TimeRange } from '../../../common'; function removeParentAggs(obj: any) { for (const prop in obj) { if (prop === 'parentAggs') delete obj[prop]; - else if (typeof obj[prop] === 'object') removeParentAggs(obj[prop]); + else if (typeof obj[prop] === 'object') { + const hasParentAggsKey = 'parentAggs' in obj[prop]; + removeParentAggs(obj[prop]); + // delete object if parentAggs was the last key + if (hasParentAggsKey && Object.keys(obj[prop]).length === 0) { + delete obj[prop]; + } + } } } @@ -193,10 +200,12 @@ export class AggConfigs { // advance the cursor and nest under the previous agg, or // put it on the same level if the previous agg doesn't accept // sub aggs - dslLvlCursor = prevDsl.aggs || dslLvlCursor; + dslLvlCursor = prevDsl?.aggs || dslLvlCursor; } - const dsl = (dslLvlCursor[config.id] = config.toDsl(this)); + const dsl = config.type.hasNoDslParams + ? config.toDsl(this) + : (dslLvlCursor[config.id] = config.toDsl(this)); let subAggs: any; parseParentAggs(dslLvlCursor, dsl); @@ -206,6 +215,11 @@ export class AggConfigs { subAggs = dsl.aggs || (dsl.aggs = {}); } + if (subAggs) { + _.each(subAggs, (agg) => { + parseParentAggs(subAggs, agg); + }); + } if (subAggs && nestedMetrics) { nestedMetrics.forEach((agg: any) => { subAggs[agg.config.id] = agg.dsl; diff --git a/src/plugins/data/common/search/aggs/agg_type.ts b/src/plugins/data/common/search/aggs/agg_type.ts index 4583be17478e36..33fdc45a605b71 100644 --- a/src/plugins/data/common/search/aggs/agg_type.ts +++ b/src/plugins/data/common/search/aggs/agg_type.ts @@ -32,6 +32,7 @@ export interface AggTypeConfig< makeLabel?: ((aggConfig: TAggConfig) => string) | (() => string); ordered?: any; hasNoDsl?: boolean; + hasNoDslParams?: boolean; params?: Array>; valueType?: DatatableColumnType; getRequestAggs?: ((aggConfig: TAggConfig) => TAggConfig[]) | (() => TAggConfig[] | void); @@ -129,6 +130,12 @@ export class AggType< * @type {Boolean} */ hasNoDsl: boolean; + /** + * Flag that prevents params from this aggregation from being included in the dsl. Sibling and parent aggs are still written. + * + * @type {Boolean} + */ + hasNoDslParams: boolean; /** * The method to create a filter representation of the bucket * @param {object} aggConfig The instance of the aggConfig @@ -232,6 +239,7 @@ export class AggType< this.makeLabel = config.makeLabel || constant(this.name); this.ordered = config.ordered; this.hasNoDsl = !!config.hasNoDsl; + this.hasNoDslParams = !!config.hasNoDslParams; if (config.createFilter) { this.createFilter = config.createFilter; diff --git a/src/plugins/data/common/search/aggs/agg_types.ts b/src/plugins/data/common/search/aggs/agg_types.ts index a7af68a5ad8b45..d02f8e1fc5af4e 100644 --- a/src/plugins/data/common/search/aggs/agg_types.ts +++ b/src/plugins/data/common/search/aggs/agg_types.ts @@ -44,6 +44,7 @@ export const getAggTypes = () => ({ { name: METRIC_TYPES.SUM_BUCKET, fn: metrics.getBucketSumMetricAgg }, { name: METRIC_TYPES.MIN_BUCKET, fn: metrics.getBucketMinMetricAgg }, { name: METRIC_TYPES.MAX_BUCKET, fn: metrics.getBucketMaxMetricAgg }, + { name: METRIC_TYPES.FILTERED_METRIC, fn: metrics.getFilteredMetricAgg }, { name: METRIC_TYPES.GEO_BOUNDS, fn: metrics.getGeoBoundsMetricAgg }, { name: METRIC_TYPES.GEO_CENTROID, fn: metrics.getGeoCentroidMetricAgg }, ], @@ -80,6 +81,7 @@ export const getAggTypesFunctions = () => [ metrics.aggBucketMax, metrics.aggBucketMin, metrics.aggBucketSum, + metrics.aggFilteredMetric, metrics.aggCardinality, metrics.aggCount, metrics.aggCumulativeSum, diff --git a/src/plugins/data/common/search/aggs/aggs_service.test.ts b/src/plugins/data/common/search/aggs/aggs_service.test.ts index 92e6168b169c6c..bba67640890adf 100644 --- a/src/plugins/data/common/search/aggs/aggs_service.test.ts +++ b/src/plugins/data/common/search/aggs/aggs_service.test.ts @@ -97,6 +97,7 @@ describe('Aggs service', () => { "sum_bucket", "min_bucket", "max_bucket", + "filtered_metric", "geo_bounds", "geo_centroid", ] @@ -142,6 +143,7 @@ describe('Aggs service', () => { "sum_bucket", "min_bucket", "max_bucket", + "filtered_metric", "geo_bounds", "geo_centroid", ] diff --git a/src/plugins/data/common/search/aggs/buckets/filter.ts b/src/plugins/data/common/search/aggs/buckets/filter.ts index 14a8f84c2cb9f4..900848bb9517f7 100644 --- a/src/plugins/data/common/search/aggs/buckets/filter.ts +++ b/src/plugins/data/common/search/aggs/buckets/filter.ts @@ -6,12 +6,15 @@ * Side Public License, v 1. */ +import { cloneDeep } from 'lodash'; import { i18n } from '@kbn/i18n'; import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { GeoBoundingBox } from './lib/geo_point'; import { aggFilterFnName } from './filter_fn'; import { BaseAggParams } from '../types'; +import { Query } from '../../../types'; +import { buildEsQuery, getEsQueryConfig } from '../../../es_query'; const filterTitle = i18n.translate('data.search.aggs.buckets.filterTitle', { defaultMessage: 'Filter', @@ -21,7 +24,7 @@ export interface AggParamsFilter extends BaseAggParams { geo_bounding_box?: GeoBoundingBox; } -export const getFilterBucketAgg = () => +export const getFilterBucketAgg = ({ getConfig }: { getConfig: (key: string) => any }) => new BucketAggType({ name: BUCKET_TYPES.FILTER, expressionName: aggFilterFnName, @@ -31,5 +34,27 @@ export const getFilterBucketAgg = () => { name: 'geo_bounding_box', }, + { + name: 'filter', + write(aggConfig, output) { + const filter: Query = aggConfig.params.filter; + + const input = cloneDeep(filter); + + if (!input) { + return; + } + + const esQueryConfigs = getEsQueryConfig({ get: getConfig }); + const query = buildEsQuery(aggConfig.getIndexPattern(), [input], [], esQueryConfigs); + + if (!query) { + console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console + return; + } + + output.params = query; + }, + }, ], }); diff --git a/src/plugins/data/common/search/aggs/buckets/filter_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/filter_fn.test.ts index 0b9f2915e9aa4b..8b4642bf595cd1 100644 --- a/src/plugins/data/common/search/aggs/buckets/filter_fn.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/filter_fn.test.ts @@ -23,6 +23,7 @@ describe('agg_expression_functions', () => { "id": undefined, "params": Object { "customLabel": undefined, + "filter": undefined, "geo_bounding_box": undefined, "json": undefined, }, @@ -46,6 +47,7 @@ describe('agg_expression_functions', () => { "id": undefined, "params": Object { "customLabel": undefined, + "filter": undefined, "geo_bounding_box": Object { "wkt": "BBOX (-74.1, -71.12, 40.73, 40.01)", }, @@ -57,6 +59,25 @@ describe('agg_expression_functions', () => { `); }); + test('correctly parses filter string argument', () => { + const actual = fn({ + filter: '{ "language": "kuery", "query": "a: b" }', + }); + + expect(actual.value.params.filter).toEqual({ language: 'kuery', query: 'a: b' }); + }); + + test('errors out if geo_bounding_box is used together with filter', () => { + expect(() => + fn({ + filter: '{ "language": "kuery", "query": "a: b" }', + geo_bounding_box: JSON.stringify({ + wkt: 'BBOX (-74.1, -71.12, 40.73, 40.01)', + }), + }) + ).toThrow(); + }); + test('correctly parses json string argument', () => { const actual = fn({ json: '{ "foo": true }', diff --git a/src/plugins/data/common/search/aggs/buckets/filter_fn.ts b/src/plugins/data/common/search/aggs/buckets/filter_fn.ts index 468b0630465493..4c68251f5e42e9 100644 --- a/src/plugins/data/common/search/aggs/buckets/filter_fn.ts +++ b/src/plugins/data/common/search/aggs/buckets/filter_fn.ts @@ -17,7 +17,7 @@ export const aggFilterFnName = 'aggFilter'; type Input = any; type AggArgs = AggExpressionFunctionArgs; -type Arguments = Assign; +type Arguments = Assign; type Output = AggExpressionType; type FunctionDefinition = ExpressionFunctionDefinition< @@ -59,6 +59,13 @@ export const aggFilter = (): FunctionDefinition => ({ defaultMessage: 'Filter results based on a point location within a bounding box', }), }, + filter: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.filter.filter.help', { + defaultMessage: + 'Filter results based on a kql or lucene query. Do not use together with geo_bounding_box', + }), + }, json: { types: ['string'], help: i18n.translate('data.search.aggs.buckets.filter.json.help', { @@ -75,6 +82,13 @@ export const aggFilter = (): FunctionDefinition => ({ fn: (input, args) => { const { id, enabled, schema, ...rest } = args; + const geoBoundingBox = getParsedValue(args, 'geo_bounding_box'); + const filter = getParsedValue(args, 'filter'); + + if (geoBoundingBox && filter) { + throw new Error("filter and geo_bounding_box can't be used together"); + } + return { type: 'agg_type', value: { @@ -84,7 +98,8 @@ export const aggFilter = (): FunctionDefinition => ({ type: BUCKET_TYPES.FILTER, params: { ...rest, - geo_bounding_box: getParsedValue(args, 'geo_bounding_box'), + geo_bounding_box: geoBoundingBox, + filter, }, }, }; diff --git a/src/plugins/data/common/search/aggs/metrics/filtered_metric.test.ts b/src/plugins/data/common/search/aggs/metrics/filtered_metric.test.ts new file mode 100644 index 00000000000000..b27e4dd1494be5 --- /dev/null +++ b/src/plugins/data/common/search/aggs/metrics/filtered_metric.test.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AggConfigs, IAggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; +import { METRIC_TYPES } from './metric_agg_types'; + +describe('filtered metric agg type', () => { + let aggConfigs: IAggConfigs; + + beforeEach(() => { + const typesRegistry = mockAggTypesRegistry(); + const field = { + name: 'bytes', + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: METRIC_TYPES.FILTERED_METRIC, + type: METRIC_TYPES.FILTERED_METRIC, + schema: 'metric', + params: { + customBucket: { + type: 'filter', + params: { + filter: { language: 'kuery', query: 'a: b' }, + }, + }, + customMetric: { + type: 'cardinality', + params: { + field: 'bytes', + }, + }, + }, + }, + ], + { + typesRegistry, + } + ); + }); + + it('converts the response', () => { + const agg = aggConfigs.getResponseAggs()[0]; + + expect( + agg.getValue({ + 'filtered_metric-bucket': { + 'filtered_metric-metric': { + value: 10, + }, + }, + }) + ).toEqual(10); + }); +}); diff --git a/src/plugins/data/common/search/aggs/metrics/filtered_metric.ts b/src/plugins/data/common/search/aggs/metrics/filtered_metric.ts new file mode 100644 index 00000000000000..aa2417bbf84156 --- /dev/null +++ b/src/plugins/data/common/search/aggs/metrics/filtered_metric.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { MetricAggType } from './metric_agg_type'; +import { makeNestedLabel } from './lib/make_nested_label'; +import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; +import { METRIC_TYPES } from './metric_agg_types'; +import { AggConfigSerialized, BaseAggParams } from '../types'; +import { aggFilteredMetricFnName } from './filtered_metric_fn'; + +export interface AggParamsFilteredMetric extends BaseAggParams { + customMetric?: AggConfigSerialized; + customBucket?: AggConfigSerialized; +} + +const filteredMetricLabel = i18n.translate('data.search.aggs.metrics.filteredMetricLabel', { + defaultMessage: 'filtered', +}); + +const filteredMetricTitle = i18n.translate('data.search.aggs.metrics.filteredMetricTitle', { + defaultMessage: 'Filtered metric', +}); + +export const getFilteredMetricAgg = () => { + const { subtype, params, getSerializedFormat } = siblingPipelineAggHelper; + + return new MetricAggType({ + name: METRIC_TYPES.FILTERED_METRIC, + expressionName: aggFilteredMetricFnName, + title: filteredMetricTitle, + makeLabel: (agg) => makeNestedLabel(agg, filteredMetricLabel), + subtype, + params: [...params(['filter'])], + hasNoDslParams: true, + getSerializedFormat, + getValue(agg, bucket) { + const customMetric = agg.getParam('customMetric'); + const customBucket = agg.getParam('customBucket'); + return customMetric.getValue(bucket[customBucket.id]); + }, + getValueBucketPath(agg) { + const customBucket = agg.getParam('customBucket'); + const customMetric = agg.getParam('customMetric'); + if (customMetric.type.name === 'count') { + return customBucket.getValueBucketPath(); + } + return `${customBucket.getValueBucketPath()}>${customMetric.getValueBucketPath()}`; + }, + }); +}; diff --git a/src/plugins/data/common/search/aggs/metrics/filtered_metric_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/filtered_metric_fn.test.ts new file mode 100644 index 00000000000000..22e97fe18b604c --- /dev/null +++ b/src/plugins/data/common/search/aggs/metrics/filtered_metric_fn.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { functionWrapper } from '../test_helpers'; +import { aggFilteredMetric } from './filtered_metric_fn'; + +describe('agg_expression_functions', () => { + describe('aggFilteredMetric', () => { + const fn = functionWrapper(aggFilteredMetric()); + + test('handles customMetric and customBucket as a subexpression', () => { + const actual = fn({ + customMetric: fn({}), + customBucket: fn({}), + }); + + expect(actual.value.params).toMatchInlineSnapshot(` + Object { + "customBucket": Object { + "enabled": true, + "id": undefined, + "params": Object { + "customBucket": undefined, + "customLabel": undefined, + "customMetric": undefined, + }, + "schema": undefined, + "type": "filtered_metric", + }, + "customLabel": undefined, + "customMetric": Object { + "enabled": true, + "id": undefined, + "params": Object { + "customBucket": undefined, + "customLabel": undefined, + "customMetric": undefined, + }, + "schema": undefined, + "type": "filtered_metric", + }, + } + `); + }); + }); +}); diff --git a/src/plugins/data/common/search/aggs/metrics/filtered_metric_fn.ts b/src/plugins/data/common/search/aggs/metrics/filtered_metric_fn.ts new file mode 100644 index 00000000000000..6a7ff5fa5fd40e --- /dev/null +++ b/src/plugins/data/common/search/aggs/metrics/filtered_metric_fn.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { Assign } from '@kbn/utility-types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { AggExpressionType, AggExpressionFunctionArgs, METRIC_TYPES } from '../'; + +export const aggFilteredMetricFnName = 'aggFilteredMetric'; + +type Input = any; +type AggArgs = AggExpressionFunctionArgs; +type Arguments = Assign< + AggArgs, + { customBucket?: AggExpressionType; customMetric?: AggExpressionType } +>; +type Output = AggExpressionType; +type FunctionDefinition = ExpressionFunctionDefinition< + typeof aggFilteredMetricFnName, + Input, + Arguments, + Output +>; + +export const aggFilteredMetric = (): FunctionDefinition => ({ + name: aggFilteredMetricFnName, + help: i18n.translate('data.search.aggs.function.metrics.filtered_metric.help', { + defaultMessage: 'Generates a serialized agg config for a filtered metric agg', + }), + type: 'agg_type', + args: { + id: { + types: ['string'], + help: i18n.translate('data.search.aggs.metrics.filtered_metric.id.help', { + defaultMessage: 'ID for this aggregation', + }), + }, + enabled: { + types: ['boolean'], + default: true, + help: i18n.translate('data.search.aggs.metrics.filtered_metric.enabled.help', { + defaultMessage: 'Specifies whether this aggregation should be enabled', + }), + }, + schema: { + types: ['string'], + help: i18n.translate('data.search.aggs.metrics.filtered_metric.schema.help', { + defaultMessage: 'Schema to use for this aggregation', + }), + }, + customBucket: { + types: ['agg_type'], + help: i18n.translate('data.search.aggs.metrics.filtered_metric.customBucket.help', { + defaultMessage: + 'Agg config to use for building sibling pipeline aggregations. Has to be a filter aggregation', + }), + }, + customMetric: { + types: ['agg_type'], + help: i18n.translate('data.search.aggs.metrics.filtered_metric.customMetric.help', { + defaultMessage: 'Agg config to use for building sibling pipeline aggregations', + }), + }, + customLabel: { + types: ['string'], + help: i18n.translate('data.search.aggs.metrics.filtered_metric.customLabel.help', { + defaultMessage: 'Represents a custom label for this aggregation', + }), + }, + }, + fn: (input, args) => { + const { id, enabled, schema, ...rest } = args; + + return { + type: 'agg_type', + value: { + id, + enabled, + schema, + type: METRIC_TYPES.FILTERED_METRIC, + params: { + ...rest, + customBucket: args.customBucket?.value, + customMetric: args.customMetric?.value, + }, + }, + }; + }, +}); diff --git a/src/plugins/data/common/search/aggs/metrics/index.ts b/src/plugins/data/common/search/aggs/metrics/index.ts index 4ab3b021ef93a5..7038673d5d7c4e 100644 --- a/src/plugins/data/common/search/aggs/metrics/index.ts +++ b/src/plugins/data/common/search/aggs/metrics/index.ts @@ -16,6 +16,8 @@ export * from './bucket_min_fn'; export * from './bucket_min'; export * from './bucket_sum_fn'; export * from './bucket_sum'; +export * from './filtered_metric_fn'; +export * from './filtered_metric'; export * from './cardinality_fn'; export * from './cardinality'; export * from './count'; diff --git a/src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts b/src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts index b72a6ca9f73baa..d51038d8a15e88 100644 --- a/src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts +++ b/src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts @@ -21,6 +21,7 @@ const metricAggFilter = [ '!std_dev', '!geo_bounds', '!geo_centroid', + '!filtered_metric', ]; export const parentPipelineType = i18n.translate( diff --git a/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts b/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts index d91ceae414f35c..c0d1be4f47f9bc 100644 --- a/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts +++ b/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts @@ -27,6 +27,7 @@ const metricAggFilter: string[] = [ '!cumulative_sum', '!geo_bounds', '!geo_centroid', + '!filtered_metric', ]; const bucketAggFilter: string[] = []; @@ -39,12 +40,12 @@ export const siblingPipelineType = i18n.translate( export const siblingPipelineAggHelper = { subtype: siblingPipelineType, - params() { + params(bucketFilter = bucketAggFilter) { return [ { name: 'customBucket', type: 'agg', - allowedAggs: bucketAggFilter, + allowedAggs: bucketFilter, default: null, makeAgg(agg: IMetricAggConfig, state = { type: 'date_histogram' }) { const orderAgg = agg.aggConfigs.createAggConfig(state, { addToAggConfigs: false }); @@ -69,7 +70,8 @@ export const siblingPipelineAggHelper = { modifyAggConfigOnSearchRequestStart: forwardModifyAggConfigOnSearchRequestStart( 'customMetric' ), - write: siblingPipelineAggWriter, + write: (agg: IMetricAggConfig, output: Record) => + siblingPipelineAggWriter(agg, output), }, ] as Array>; }, diff --git a/src/plugins/data/common/search/aggs/metrics/metric_agg_types.ts b/src/plugins/data/common/search/aggs/metrics/metric_agg_types.ts index b49004be2db014..3b6c9d8a0d55dd 100644 --- a/src/plugins/data/common/search/aggs/metrics/metric_agg_types.ts +++ b/src/plugins/data/common/search/aggs/metrics/metric_agg_types.ts @@ -8,6 +8,7 @@ export enum METRIC_TYPES { AVG = 'avg', + FILTERED_METRIC = 'filtered_metric', CARDINALITY = 'cardinality', AVG_BUCKET = 'avg_bucket', MAX_BUCKET = 'max_bucket', diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts index 48ded7fa7a7fcf..e57410962fc08a 100644 --- a/src/plugins/data/common/search/aggs/types.ts +++ b/src/plugins/data/common/search/aggs/types.ts @@ -41,6 +41,7 @@ import { AggParamsBucketMax, AggParamsBucketMin, AggParamsBucketSum, + AggParamsFilteredMetric, AggParamsCardinality, AggParamsCumulativeSum, AggParamsDateHistogram, @@ -84,6 +85,7 @@ import { getCalculateAutoTimeExpression, METRIC_TYPES, AggConfig, + aggFilteredMetric, } from './'; export { IAggConfig, AggConfigSerialized } from './agg_config'; @@ -188,6 +190,7 @@ export interface AggParamsMapping { [METRIC_TYPES.MAX_BUCKET]: AggParamsBucketMax; [METRIC_TYPES.MIN_BUCKET]: AggParamsBucketMin; [METRIC_TYPES.SUM_BUCKET]: AggParamsBucketSum; + [METRIC_TYPES.FILTERED_METRIC]: AggParamsFilteredMetric; [METRIC_TYPES.CUMULATIVE_SUM]: AggParamsCumulativeSum; [METRIC_TYPES.DERIVATIVE]: AggParamsDerivative; [METRIC_TYPES.MOVING_FN]: AggParamsMovingAvg; @@ -217,6 +220,7 @@ export interface AggFunctionsMapping { aggBucketMax: ReturnType; aggBucketMin: ReturnType; aggBucketSum: ReturnType; + aggFilteredMetric: ReturnType; aggCardinality: ReturnType; aggCount: ReturnType; aggCumulativeSum: ReturnType; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index a61b8f400d2859..5dc5a8ab2ce93c 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -329,6 +329,10 @@ export interface AggFunctionsMapping { // // (undocumented) aggFilter: ReturnType; + // Warning: (ae-forgotten-export) The symbol "aggFilteredMetric" needs to be exported by the entry point index.d.ts + // + // (undocumented) + aggFilteredMetric: ReturnType; // Warning: (ae-forgotten-export) The symbol "aggFilters" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -711,7 +715,7 @@ export const ES_SEARCH_STRATEGY = "es"; // Warning: (ae-missing-release-tag) "EsaggsExpressionFunctionDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'esaggs', Input_34, Arguments_20, Output_34>; +export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'esaggs', Input_35, Arguments_21, Output_35>; // Warning: (ae-forgotten-export) The symbol "name" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Input" needs to be exported by the entry point index.d.ts @@ -720,7 +724,7 @@ export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'e // Warning: (ae-missing-release-tag) "EsdslExpressionFunctionDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition_2; +export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition_2; // Warning: (ae-missing-release-tag) "esFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -861,7 +865,7 @@ export type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', Ex // Warning: (ae-missing-release-tag) "ExpressionFunctionKibanaContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments_21, Promise, ExecutionContext>; +export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments_22, Promise, ExecutionContext>; // Warning: (ae-missing-release-tag) "ExpressionValueSearchContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1830,6 +1834,8 @@ export enum METRIC_TYPES { // (undocumented) DERIVATIVE = "derivative", // (undocumented) + FILTERED_METRIC = "filtered_metric", + // (undocumented) GEO_BOUNDS = "geo_bounds", // (undocumented) GEO_CENTROID = "geo_centroid", @@ -2649,7 +2655,7 @@ export const UI_SETTINGS: { // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:65:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:138:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:169:7 - (ae-forgotten-export) The symbol "RuntimeField" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/search/aggs/types.ts:139:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/search/aggs/types.ts:141:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:56:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:56:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:56:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/search/aggs/aggs_service.test.ts b/src/plugins/data/public/search/aggs/aggs_service.test.ts index 63c27eeaf0b11b..7e9170b98f1321 100644 --- a/src/plugins/data/public/search/aggs/aggs_service.test.ts +++ b/src/plugins/data/public/search/aggs/aggs_service.test.ts @@ -54,7 +54,7 @@ describe('AggsService - public', () => { service.setup(setupDeps); const start = service.start(startDeps); expect(start.types.getAll().buckets.length).toBe(11); - expect(start.types.getAll().metrics.length).toBe(21); + expect(start.types.getAll().metrics.length).toBe(22); }); test('registers custom agg types', () => { @@ -71,7 +71,7 @@ describe('AggsService - public', () => { const start = service.start(startDeps); expect(start.types.getAll().buckets.length).toBe(12); expect(start.types.getAll().buckets.some(({ name }) => name === 'foo')).toBe(true); - expect(start.types.getAll().metrics.length).toBe(22); + expect(start.types.getAll().metrics.length).toBe(23); expect(start.types.getAll().metrics.some(({ name }) => name === 'bar')).toBe(true); }); }); diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 83f7c67eba0571..2b7da7fe194ca2 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -134,6 +134,10 @@ export interface AggFunctionsMapping { // // (undocumented) aggFilter: ReturnType; + // Warning: (ae-forgotten-export) The symbol "aggFilteredMetric" needs to be exported by the entry point index.d.ts + // + // (undocumented) + aggFilteredMetric: ReturnType; // Warning: (ae-forgotten-export) The symbol "aggFilters" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -405,7 +409,7 @@ export const ES_SEARCH_STRATEGY = "es"; // Warning: (ae-missing-release-tag) "EsaggsExpressionFunctionDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'esaggs', Input_34, Arguments_20, Output_34>; +export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'esaggs', Input_35, Arguments_21, Output_35>; // Warning: (ae-missing-release-tag) "esFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -485,7 +489,7 @@ export type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', Ex // Warning: (ae-missing-release-tag) "ExpressionFunctionKibanaContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments_21, Promise, ExecutionContext>; +export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments_22, Promise, ExecutionContext>; // Warning: (ae-missing-release-tag) "ExpressionValueSearchContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1141,6 +1145,8 @@ export enum METRIC_TYPES { // (undocumented) DERIVATIVE = "derivative", // (undocumented) + FILTERED_METRIC = "filtered_metric", + // (undocumented) GEO_BOUNDS = "geo_bounds", // (undocumented) GEO_CENTROID = "geo_centroid", diff --git a/src/plugins/vis_type_metric/public/metric_vis_type.ts b/src/plugins/vis_type_metric/public/metric_vis_type.ts index 732205fb31eabb..9e2e248c6ccd50 100644 --- a/src/plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/plugins/vis_type_metric/public/metric_vis_type.ts @@ -63,6 +63,7 @@ export const createMetricVisTypeDefinition = (): VisTypeDefinition => '!moving_avg', '!cumulative_sum', '!geo_bounds', + '!filtered_metric', ], aggSettings: { top_hits: { diff --git a/src/plugins/vis_type_table/public/legacy/table_vis_legacy_type.ts b/src/plugins/vis_type_table/public/legacy/table_vis_legacy_type.ts index 020a317e0471b1..2f30faa8e9a899 100644 --- a/src/plugins/vis_type_table/public/legacy/table_vis_legacy_type.ts +++ b/src/plugins/vis_type_table/public/legacy/table_vis_legacy_type.ts @@ -50,7 +50,7 @@ export const tableVisLegacyTypeDefinition: VisTypeDefinition = { title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', { defaultMessage: 'Metric', }), - aggFilter: ['!geo_centroid', '!geo_bounds'], + aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'], aggSettings: { top_hits: { allowStrings: true, diff --git a/src/plugins/vis_type_table/public/table_vis_type.ts b/src/plugins/vis_type_table/public/table_vis_type.ts index 774742f02dde5b..d645af3180b080 100644 --- a/src/plugins/vis_type_table/public/table_vis_type.ts +++ b/src/plugins/vis_type_table/public/table_vis_type.ts @@ -46,7 +46,7 @@ export const tableVisTypeDefinition: VisTypeDefinition = { title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', { defaultMessage: 'Metric', }), - aggFilter: ['!geo_centroid', '!geo_bounds'], + aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'], aggSettings: { top_hits: { allowStrings: true, diff --git a/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts b/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts index 6f2c6112064a90..4052ecbe21997f 100644 --- a/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts +++ b/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts @@ -51,6 +51,7 @@ export const tagCloudVisTypeDefinition = { '!derivative', '!geo_bounds', '!geo_centroid', + '!filtered_metric', ], defaults: [{ schema: 'metric', type: 'count' }], }, diff --git a/src/plugins/vis_type_vislib/public/gauge.ts b/src/plugins/vis_type_vislib/public/gauge.ts index 172ce83b4f7c2c..7e3ff8226fbb65 100644 --- a/src/plugins/vis_type_vislib/public/gauge.ts +++ b/src/plugins/vis_type_vislib/public/gauge.ts @@ -119,6 +119,7 @@ export const gaugeVisTypeDefinition: VisTypeDefinition = { '!moving_avg', '!cumulative_sum', '!geo_bounds', + '!filtered_metric', ], defaults: [{ schema: 'metric', type: 'count' }], }, diff --git a/src/plugins/vis_type_vislib/public/goal.ts b/src/plugins/vis_type_vislib/public/goal.ts index aaeae4f675f3fd..468651bb4cf4c4 100644 --- a/src/plugins/vis_type_vislib/public/goal.ts +++ b/src/plugins/vis_type_vislib/public/goal.ts @@ -83,6 +83,7 @@ export const goalVisTypeDefinition: VisTypeDefinition = { '!moving_avg', '!cumulative_sum', '!geo_bounds', + '!filtered_metric', ], defaults: [{ schema: 'metric', type: 'count' }], }, diff --git a/src/plugins/vis_type_vislib/public/heatmap.ts b/src/plugins/vis_type_vislib/public/heatmap.ts index 6f6160f3756fdb..8d538399f68b2b 100644 --- a/src/plugins/vis_type_vislib/public/heatmap.ts +++ b/src/plugins/vis_type_vislib/public/heatmap.ts @@ -94,6 +94,7 @@ export const heatmapVisTypeDefinition: VisTypeDefinition = { 'cardinality', 'std_dev', 'top_hits', + '!filtered_metric', ], defaults: [{ schema: 'metric', type: 'count' }], }, diff --git a/src/plugins/vis_type_xy/public/vis_types/area.ts b/src/plugins/vis_type_xy/public/vis_types/area.ts index a61c25bbc075a4..dfe9bc2f42b84b 100644 --- a/src/plugins/vis_type_xy/public/vis_types/area.ts +++ b/src/plugins/vis_type_xy/public/vis_types/area.ts @@ -133,7 +133,7 @@ export const getAreaVisTypeDefinition = ( title: i18n.translate('visTypeXy.area.metricsTitle', { defaultMessage: 'Y-axis', }), - aggFilter: ['!geo_centroid', '!geo_bounds'], + aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'], min: 1, defaults: [{ schema: 'metric', type: 'count' }], }, diff --git a/src/plugins/vis_type_xy/public/vis_types/histogram.ts b/src/plugins/vis_type_xy/public/vis_types/histogram.ts index 2c2a83b48802d3..ba20502a3b9af9 100644 --- a/src/plugins/vis_type_xy/public/vis_types/histogram.ts +++ b/src/plugins/vis_type_xy/public/vis_types/histogram.ts @@ -137,7 +137,7 @@ export const getHistogramVisTypeDefinition = ( defaultMessage: 'Y-axis', }), min: 1, - aggFilter: ['!geo_centroid', '!geo_bounds'], + aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'], defaults: [{ schema: 'metric', type: 'count' }], }, { diff --git a/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.ts b/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.ts index 75c4ddd75d0b3b..62da0448e56bdd 100644 --- a/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.ts +++ b/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.ts @@ -136,7 +136,7 @@ export const getHorizontalBarVisTypeDefinition = ( defaultMessage: 'Y-axis', }), min: 1, - aggFilter: ['!geo_centroid', '!geo_bounds'], + aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'], defaults: [{ schema: 'metric', type: 'count' }], }, { diff --git a/src/plugins/vis_type_xy/public/vis_types/line.ts b/src/plugins/vis_type_xy/public/vis_types/line.ts index 87165a20592e59..5a9eb5198df353 100644 --- a/src/plugins/vis_type_xy/public/vis_types/line.ts +++ b/src/plugins/vis_type_xy/public/vis_types/line.ts @@ -132,7 +132,7 @@ export const getLineVisTypeDefinition = ( name: 'metric', title: i18n.translate('visTypeXy.line.metricTitle', { defaultMessage: 'Y-axis' }), min: 1, - aggFilter: ['!geo_centroid', '!geo_bounds'], + aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'], defaults: [{ schema: 'metric', type: 'count' }], }, { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/advanced_options.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/advanced_options.tsx new file mode 100644 index 00000000000000..ea5eb14d9c20eb --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/advanced_options.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiLink, EuiText, EuiPopover, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; + +export function AdvancedOptions(props: { + options: Array<{ + title: string; + dataTestSubj: string; + onClick: () => void; + showInPopover: boolean; + inlineElement: React.ReactElement | null; + }>; +}) { + const [popoverOpen, setPopoverOpen] = useState(false); + const popoverOptions = props.options.filter((option) => option.showInPopover); + const inlineOptions = props.options + .filter((option) => option.inlineElement) + .map((option) => React.cloneElement(option.inlineElement!, { key: option.dataTestSubj })); + + return ( + <> + {popoverOptions.length > 0 && ( + + + { + setPopoverOpen(!popoverOpen); + }} + > + {i18n.translate('xpack.lens.indexPattern.advancedSettings', { + defaultMessage: 'Add advanced options', + })} + + } + isOpen={popoverOpen} + closePopover={() => { + setPopoverOpen(false); + }} + > + {popoverOptions.map(({ dataTestSubj, onClick, title }, index) => ( + + + { + setPopoverOpen(false); + onClick(); + }} + > + {title} + + + {popoverOptions.length - 1 !== index && } + + ))} + + + )} + {inlineOptions.length > 0 && ( + <> + + {inlineOptions} + + )} + + ); +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 08842fb7558888..1fc755ec489c7d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -32,6 +32,7 @@ import { resetIncomplete, FieldBasedIndexPatternColumn, canTransition, + DEFAULT_TIME_SCALE, } from '../operations'; import { mergeLayer } from '../state_helpers'; import { FieldSelect } from './field_select'; @@ -41,7 +42,9 @@ import { IndexPattern, IndexPatternLayer } from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { FormatSelector } from './format_selector'; import { ReferenceEditor } from './reference_editor'; -import { TimeScaling } from './time_scaling'; +import { setTimeScaling, TimeScaling } from './time_scaling'; +import { defaultFilter, Filtering, setFilter } from './filtering'; +import { AdvancedOptions } from './advanced_options'; const operationPanels = getOperationDisplay(); @@ -156,6 +159,8 @@ export function DimensionEditor(props: DimensionEditorProps) { .filter((type) => fieldByOperation[type]?.size || operationWithoutField.has(type)); }, [fieldByOperation, operationWithoutField]); + const [filterByOpenInitially, setFilterByOpenInitally] = useState(false); + // Operations are compatible if they match inputs. They are always compatible in // the empty state. Field-based operations are not compatible with field-less operations. const operationsWithCompatibility = [...possibleOperations].map((operationType) => { @@ -458,11 +463,63 @@ export function DimensionEditor(props: DimensionEditorProps) { )} {!currentFieldIsInvalid && !incompleteInfo && selectedColumn && ( - { + setStateWrapper( + setTimeScaling(columnId, state.layers[layerId], DEFAULT_TIME_SCALE) + ); + }, + showInPopover: Boolean( + operationDefinitionMap[selectedColumn.operationType].timeScalingMode && + operationDefinitionMap[selectedColumn.operationType].timeScalingMode !== + 'disabled' && + Object.values(state.layers[layerId].columns).some( + (col) => col.operationType === 'date_histogram' + ) && + !selectedColumn.timeScale + ), + inlineElement: ( + + ), + }, + { + title: i18n.translate('xpack.lens.indexPattern.filterBy.label', { + defaultMessage: 'Filter by', + }), + dataTestSubj: 'indexPattern-filter-by-enable', + onClick: () => { + setFilterByOpenInitally(true); + setStateWrapper(setFilter(columnId, state.layers[layerId], defaultFilter)); + }, + showInPopover: Boolean( + operationDefinitionMap[selectedColumn.operationType].filterable && + !selectedColumn.filter + ), + inlineElement: + operationDefinitionMap[selectedColumn.operationType].filterable && + selectedColumn.filter ? ( + + ) : null, + }, + ]} /> )}

diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index a6d2361be21d4c..d586818cb3c116 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -6,7 +6,7 @@ */ import { ReactWrapper, ShallowWrapper } from 'enzyme'; -import React, { ChangeEvent, MouseEvent } from 'react'; +import React, { ChangeEvent, MouseEvent, ReactElement } from 'react'; import { act } from 'react-dom/test-utils'; import { EuiComboBox, @@ -15,6 +15,7 @@ import { EuiRange, EuiSelect, EuiButtonIcon, + EuiPopover, } from '@elastic/eui'; import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { @@ -30,10 +31,14 @@ import { documentField } from '../document_field'; import { OperationMetadata } from '../../types'; import { DateHistogramIndexPatternColumn } from '../operations/definitions/date_histogram'; import { getFieldByNameFactory } from '../pure_helpers'; -import { TimeScaling } from './time_scaling'; import { DimensionEditor } from './dimension_editor'; +import { AdvancedOptions } from './advanced_options'; +import { Filtering } from './filtering'; jest.mock('../loader'); +jest.mock('../query_input', () => ({ + QueryInput: () => null, +})); jest.mock('../operations'); jest.mock('lodash', () => { const original = jest.requireActual('lodash'); @@ -1029,7 +1034,7 @@ describe('IndexPatternDimensionEditorPanel', () => { } it('should not show custom options if time scaling is not available', () => { - wrapper = mount( + wrapper = shallow( { })} /> ); - expect(wrapper.find('[data-test-subj="indexPattern-time-scaling"]')).toHaveLength(0); + expect( + wrapper + .find(DimensionEditor) + .dive() + .find(AdvancedOptions) + .dive() + .find('[data-test-subj="indexPattern-time-scaling-enable"]') + ).toHaveLength(0); }); it('should show custom options if time scaling is available', () => { - wrapper = mount(); + wrapper = shallow(); expect( wrapper - .find(TimeScaling) - .find('[data-test-subj="indexPattern-time-scaling-popover"]') - .exists() - ).toBe(true); + .find(DimensionEditor) + .dive() + .find(AdvancedOptions) + .dive() + .find('[data-test-subj="indexPattern-time-scaling-enable"]') + ).toHaveLength(1); }); it('should show current time scaling if set', () => { @@ -1066,7 +1080,7 @@ describe('IndexPatternDimensionEditorPanel', () => { wrapper .find(DimensionEditor) .dive() - .find(TimeScaling) + .find(AdvancedOptions) .dive() .find('[data-test-subj="indexPattern-time-scaling-enable"]') .prop('onClick')!({} as MouseEvent); @@ -1239,6 +1253,199 @@ describe('IndexPatternDimensionEditorPanel', () => { }); }); + describe('filtering', () => { + function getProps(colOverrides: Partial) { + return { + ...defaultProps, + state: getStateWithColumns({ + datecolumn: { + dataType: 'date', + isBucketed: true, + label: '', + customLabel: true, + operationType: 'date_histogram', + sourceField: 'ts', + params: { + interval: '1d', + }, + }, + col2: { + dataType: 'number', + isBucketed: false, + label: 'Count of records', + operationType: 'count', + sourceField: 'Records', + ...colOverrides, + } as IndexPatternColumn, + }), + columnId: 'col2', + }; + } + + it('should not show custom options if time scaling is not available', () => { + wrapper = shallow( + + ); + expect( + wrapper + .find(DimensionEditor) + .dive() + .find(AdvancedOptions) + .dive() + .find('[data-test-subj="indexPattern-filter-by-enable"]') + ).toHaveLength(0); + }); + + it('should show custom options if filtering is available', () => { + wrapper = shallow(); + expect( + wrapper + .find(DimensionEditor) + .dive() + .find(AdvancedOptions) + .dive() + .find('[data-test-subj="indexPattern-filter-by-enable"]') + ).toHaveLength(1); + }); + + it('should show current filter if set', () => { + wrapper = mount( + + ); + expect( + (wrapper.find(Filtering).find(EuiPopover).prop('children') as ReactElement).props.value + ).toEqual({ language: 'kuery', query: 'a: b' }); + }); + + it('should allow to set filter initially', () => { + const props = getProps({}); + wrapper = shallow(); + wrapper + .find(DimensionEditor) + .dive() + .find(AdvancedOptions) + .dive() + .find('[data-test-subj="indexPattern-filter-by-enable"]') + .prop('onClick')!({} as MouseEvent); + expect(props.setState).toHaveBeenCalledWith( + { + ...props.state, + layers: { + first: { + ...props.state.layers.first, + columns: { + ...props.state.layers.first.columns, + col2: expect.objectContaining({ + filter: { + language: 'kuery', + query: '', + }, + }), + }, + }, + }, + }, + { shouldRemoveDimension: false, shouldReplaceDimension: true } + ); + }); + + it('should carry over filter to other operation if possible', () => { + const props = getProps({ + filter: { language: 'kuery', query: 'a: b' }, + sourceField: 'bytes', + operationType: 'sum', + label: 'Sum of bytes per hour', + }); + wrapper = mount(); + wrapper + .find('button[data-test-subj="lns-indexPatternDimension-count incompatible"]') + .simulate('click'); + expect(props.setState).toHaveBeenCalledWith( + { + ...props.state, + layers: { + first: { + ...props.state.layers.first, + columns: { + ...props.state.layers.first.columns, + col2: expect.objectContaining({ + filter: { language: 'kuery', query: 'a: b' }, + }), + }, + }, + }, + }, + { shouldRemoveDimension: false, shouldReplaceDimension: true } + ); + }); + + it('should allow to change filter', () => { + const props = getProps({ + filter: { language: 'kuery', query: 'a: b' }, + }); + wrapper = mount(); + (wrapper.find(Filtering).find(EuiPopover).prop('children') as ReactElement).props.onChange({ + language: 'kuery', + query: 'c: d', + }); + expect(props.setState).toHaveBeenCalledWith( + { + ...props.state, + layers: { + first: { + ...props.state.layers.first, + columns: { + ...props.state.layers.first.columns, + col2: expect.objectContaining({ + filter: { language: 'kuery', query: 'c: d' }, + }), + }, + }, + }, + }, + { shouldRemoveDimension: false, shouldReplaceDimension: true } + ); + }); + + it('should allow to remove filter', () => { + const props = getProps({ + filter: { language: 'kuery', query: 'a: b' }, + }); + wrapper = mount(); + wrapper + .find('[data-test-subj="indexPattern-filter-by-remove"]') + .find(EuiButtonIcon) + .prop('onClick')!( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + {} as any + ); + expect(props.setState).toHaveBeenCalledWith( + { + ...props.state, + layers: { + first: { + ...props.state.layers.first, + columns: { + ...props.state.layers.first.columns, + col2: expect.objectContaining({ + filter: undefined, + }), + }, + }, + }, + }, + { shouldRemoveDimension: false, shouldReplaceDimension: true } + ); + }); + }); + it('should render invalid field if field reference is broken', () => { wrapper = mount( void; + isInitiallyOpen: boolean; +}) { + const [filterPopoverOpen, setFilterPopoverOpen] = useState(isInitiallyOpen); + const selectedOperation = operationDefinitionMap[selectedColumn.operationType]; + if (!selectedOperation.filterable || !selectedColumn.filter) { + return null; + } + + const isInvalid = !isQueryValid(selectedColumn.filter, indexPattern); + + return ( + + + + { + setFilterPopoverOpen(false); + }} + anchorClassName="eui-fullWidth" + panelClassName="lnsIndexPatternDimensionEditor__filtersEditor" + button={ + + + {/* Empty for spacing */} + + { + setFilterPopoverOpen(!filterPopoverOpen); + }} + color={isInvalid ? 'danger' : 'text'} + title={i18n.translate('xpack.lens.indexPattern.filterBy.clickToEdit', { + defaultMessage: 'Click to edit', + })} + > + {selectedColumn.filter.query || + i18n.translate('xpack.lens.indexPattern.filterBy.emptyFilterQuery', { + defaultMessage: '(empty)', + })} + + + + + } + > + { + updateLayer(setFilter(columnId, layer, newQuery)); + }} + isInvalid={false} + onSubmit={() => {}} + /> + + + + { + updateLayer(setFilter(columnId, layer, undefined)); + }} + iconType="cross" + /> + + + + ); +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx index a9362060b2dd04..bf5b64bf3d6152 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx @@ -7,23 +7,11 @@ import { EuiToolTip } from '@elastic/eui'; import { EuiIcon } from '@elastic/eui'; -import { - EuiLink, - EuiFormRow, - EuiSelect, - EuiFlexItem, - EuiFlexGroup, - EuiButtonIcon, - EuiText, - EuiPopover, - EuiButtonEmpty, - EuiSpacer, -} from '@elastic/eui'; +import { EuiFormRow, EuiSelect, EuiFlexItem, EuiFlexGroup, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useState } from 'react'; +import React from 'react'; import { adjustTimeScaleLabelSuffix, - DEFAULT_TIME_SCALE, IndexPatternColumn, operationDefinitionMap, } from '../operations'; @@ -64,7 +52,6 @@ export function TimeScaling({ layer: IndexPatternLayer; updateLayer: (newLayer: IndexPatternLayer) => void; }) { - const [popoverOpen, setPopoverOpen] = useState(false); const hasDateHistogram = layer.columnOrder.some( (colId) => layer.columns[colId].operationType === 'date_histogram' ); @@ -72,56 +59,12 @@ export function TimeScaling({ if ( !selectedOperation.timeScalingMode || selectedOperation.timeScalingMode === 'disabled' || - !hasDateHistogram + !hasDateHistogram || + !selectedColumn.timeScale ) { return null; } - if (!selectedColumn.timeScale) { - return ( - - - { - setPopoverOpen(!popoverOpen); - }} - > - {i18n.translate('xpack.lens.indexPattern.timeScale.advancedSettings', { - defaultMessage: 'Add advanced options', - })} - - } - isOpen={popoverOpen} - closePopover={() => { - setPopoverOpen(false); - }} - > - - { - setPopoverOpen(false); - updateLayer(setTimeScaling(columnId, layer, DEFAULT_TIME_SCALE)); - }} - > - {i18n.translate('xpack.lens.indexPattern.timeScale.enableTimeScale', { - defaultMessage: 'Normalize by unit', - })} - - - - - ); - } - return ( { ]); }); + it('should wrap filtered metrics in filtered metric aggregation', async () => { + const queryBaseState: IndexPatternBaseState = { + currentIndexPatternId: '1', + layers: { + first: { + indexPatternId: '1', + columnOrder: ['col1', 'col2', 'col3'], + columns: { + col1: { + label: 'Count of records', + dataType: 'number', + isBucketed: false, + sourceField: 'Records', + operationType: 'count', + timeScale: 'h', + filter: { + language: 'kuery', + query: 'bytes > 5', + }, + }, + col2: { + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + sourceField: 'bytes', + operationType: 'avg', + timeScale: 'h', + }, + col3: { + label: 'Date', + dataType: 'date', + isBucketed: true, + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'auto', + }, + }, + }, + }, + }, + }; + + const state = enrichBaseState(queryBaseState); + + const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + expect(ast.chain[0].arguments.aggs[0]).toMatchInlineSnapshot(` + Object { + "chain": Array [ + Object { + "arguments": Object { + "customBucket": Array [ + Object { + "chain": Array [ + Object { + "arguments": Object { + "enabled": Array [ + true, + ], + "filter": Array [ + "{\\"language\\":\\"kuery\\",\\"query\\":\\"bytes > 5\\"}", + ], + "id": Array [ + "col1-filter", + ], + "schema": Array [ + "bucket", + ], + }, + "function": "aggFilter", + "type": "function", + }, + ], + "type": "expression", + }, + ], + "customMetric": Array [ + Object { + "chain": Array [ + Object { + "arguments": Object { + "enabled": Array [ + true, + ], + "id": Array [ + "col1-metric", + ], + "schema": Array [ + "metric", + ], + }, + "function": "aggCount", + "type": "function", + }, + ], + "type": "expression", + }, + ], + "enabled": Array [ + true, + ], + "id": Array [ + "col1", + ], + "schema": Array [ + "metric", + ], + }, + "function": "aggFilteredMetric", + "type": "function", + }, + ], + "type": "expression", + } + `); + }); + it('should add time_scale and format function if time scale is set and supported', async () => { const queryBaseState: IndexPatternBaseState = { currentIndexPatternId: '1', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx index b02926fe03fc36..331aa528e6d555 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx @@ -82,6 +82,7 @@ export const counterRateOperation: OperationDefinition< scale: 'ratio', references: referenceIds, timeScale, + filter: previousColumn?.filter, params: getFormatFromPreviousColumn(previousColumn), }; }, @@ -106,4 +107,5 @@ export const counterRateOperation: OperationDefinition< )?.join(', '); }, timeScalingMode: 'mandatory', + filterable: true, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx index 2bf46e3acad1bb..1664f3639b598a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx @@ -77,6 +77,7 @@ export const cumulativeSumOperation: OperationDefinition< operationType: 'cumulative_sum', isBucketed: false, scale: 'ratio', + filter: previousColumn?.filter, references: referenceIds, params: getFormatFromPreviousColumn(previousColumn), }; @@ -101,4 +102,5 @@ export const cumulativeSumOperation: OperationDefinition< }) )?.join(', '); }, + filterable: true, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/derivative.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/derivative.tsx index 2d4b69a151115f..5fb0bc3a835283 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/derivative.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/derivative.tsx @@ -83,6 +83,7 @@ export const derivativeOperation: OperationDefinition< scale: 'ratio', references: referenceIds, timeScale: previousColumn?.timeScale, + filter: previousColumn?.filter, params: getFormatFromPreviousColumn(previousColumn), }; }, @@ -108,4 +109,5 @@ export const derivativeOperation: OperationDefinition< )?.join(', '); }, timeScalingMode: 'optional', + filterable: true, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx index f9dfc962ce8b7d..0af750a1fd4817 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx @@ -89,6 +89,7 @@ export const movingAverageOperation: OperationDefinition< scale: 'ratio', references: referenceIds, timeScale: previousColumn?.timeScale, + filter: previousColumn?.filter, params: { window: 5, ...getFormatFromPreviousColumn(previousColumn), @@ -119,6 +120,7 @@ export const movingAverageOperation: OperationDefinition< )?.join(', '); }, timeScalingMode: 'optional', + filterable: true, }; function MovingAverageParamEditor({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx index 80885a58e17f5d..513bac14af7a39 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx @@ -70,6 +70,7 @@ export const cardinalityOperation: OperationDefinition ofName(getSafeName(column.sourceField, indexPattern)), buildColumn({ field, previousColumn }) { return { @@ -79,6 +80,7 @@ export const cardinalityOperation: OperationDefinition ({ isQueryValid: () => true, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx index 569e394335648d..f5428bf24348f6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx @@ -8,13 +8,12 @@ import './filter_popover.scss'; import React, { MouseEventHandler, useEffect, useState } from 'react'; -import useDebounce from 'react-use/lib/useDebounce'; import { EuiPopover, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { FilterValue, defaultLabel, isQueryValid } from '.'; import { IndexPattern } from '../../../types'; -import { QueryStringInput, Query } from '../../../../../../../../src/plugins/data/public'; +import { Query } from '../../../../../../../../src/plugins/data/public'; import { LabelInput } from '../shared_components'; +import { QueryInput } from '../../../query_input'; export const FilterPopover = ({ filter, @@ -94,54 +93,3 @@ export const FilterPopover = ({ ); }; - -export const QueryInput = ({ - value, - onChange, - indexPattern, - isInvalid, - onSubmit, -}: { - value: Query; - onChange: (input: Query) => void; - indexPattern: IndexPattern; - isInvalid: boolean; - onSubmit: () => void; -}) => { - const [inputValue, setInputValue] = useState(value); - - useDebounce(() => onChange(inputValue), 256, [inputValue]); - - const handleInputChange = (input: Query) => { - setInputValue(input); - }; - - return ( - { - if (inputValue.query) { - onSubmit(); - } - }} - placeholder={ - inputValue.language === 'kuery' - ? i18n.translate('xpack.lens.indexPattern.filters.queryPlaceholderKql', { - defaultMessage: '{example}', - values: { example: 'method : "GET" or status : "404"' }, - }) - : i18n.translate('xpack.lens.indexPattern.filters.queryPlaceholderLucene', { - defaultMessage: '{example}', - values: { example: 'method:GET OR status:404' }, - }) - } - languageSwitcherPopoverAnchorPosition="rightDown" - /> - ); -}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 433c69466dc006..cdb93048c9a58f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -230,6 +230,7 @@ interface BaseOperationDefinitionProps { * If set to optional, time scaling won't be enabled by default and can be removed. */ timeScalingMode?: TimeScalingMode; + filterable?: boolean; getHelpMessage?: (props: HelpProps) => React.ReactNode; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx index 54d884c83020d2..4f5c897fb5378b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx @@ -161,12 +161,14 @@ export const lastValueOperation: OperationDefinition { return buildExpressionFunction('aggTopHit', { id: columnId, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index b24b08c0be0c35..1767b76e88202a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -99,6 +99,7 @@ function buildMetricOperation>({ isBucketed: false, scale: 'ratio', timeScale: optionalTimeScaling ? previousColumn?.timeScale : undefined, + filter: previousColumn?.filter, params: getFormatFromPreviousColumn(previousColumn), } as T), onFieldChange: (oldColumn, field) => { @@ -118,6 +119,7 @@ function buildMetricOperation>({ }, getErrorMessage: (layer, columnId, indexPattern) => getInvalidFieldMessage(layer.columns[columnId] as FieldBasedIndexPatternColumn, indexPattern), + filterable: true, } as OperationDefinition; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index 4f915160a52a85..4a05e6f372d30b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -967,6 +967,49 @@ describe('state_helpers', () => { ); }); + it('should remove filter from the wrapped column if it gets wrapped (case new1)', () => { + const expectedColumn = { + label: 'Count', + customLabel: true, + dataType: 'number' as const, + isBucketed: false, + sourceField: 'Records', + operationType: 'count' as const, + }; + + const testFilter = { language: 'kuery', query: '' }; + + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { col1: { ...expectedColumn, filter: testFilter } }, + }; + const result = replaceColumn({ + layer, + indexPattern, + columnId: 'col1', + op: 'testReference' as OperationType, + visualizationGroups: [], + }); + + expect(operationDefinitionMap.testReference.buildColumn).toHaveBeenCalledWith( + expect.objectContaining({ + referenceIds: ['id1'], + previousColumn: expect.objectContaining({ + // filter should be passed to the buildColumn function of the target operation + filter: testFilter, + }), + }) + ); + expect(result.columns).toEqual( + expect.objectContaining({ + // filter should be stripped from the original column + id1: expectedColumn, + col1: expect.any(Object), + }) + ); + }); + it('should create a new no-input operation to use as reference (case new2)', () => { // @ts-expect-error this function is not valid operationDefinitionMap.testReference.requiredReferences = [ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 3a67e8e464323f..7853b7da7956ee 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -509,7 +509,18 @@ function applyReferenceTransition({ if (!hasExactMatch && isColumnValidAsReference({ validation, column: previousColumn })) { hasExactMatch = true; - const newLayer = { ...layer, columns: { ...layer.columns, [newId]: { ...previousColumn } } }; + const newLayer = { + ...layer, + columns: { + ...layer.columns, + [newId]: { + ...previousColumn, + // drop the filter for the referenced column because the wrapping operation + // is filterable as well and will handle it one level higher. + filter: operationDefinition.filterable ? undefined : previousColumn.filter, + }, + }, + }; layer = { ...layer, columnOrder: getColumnOrder(newLayer), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/mocks.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/mocks.ts index d5ea34f003561f..429d881341e791 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/mocks.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/mocks.ts @@ -32,6 +32,7 @@ export const createMockedReferenceOperation = () => { references: args.referenceIds, }; }), + filterable: true, isTransferable: jest.fn(), toExpression: jest.fn().mockReturnValue([]), getPossibleOperation: jest.fn().mockReturnValue({ dataType: 'number', isBucketed: false }), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx new file mode 100644 index 00000000000000..50941148342c33 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; +import { i18n } from '@kbn/i18n'; +import { IndexPattern } from './types'; +import { QueryStringInput, Query } from '../../../../../src/plugins/data/public'; + +export const QueryInput = ({ + value, + onChange, + indexPattern, + isInvalid, + onSubmit, + disableAutoFocus, +}: { + value: Query; + onChange: (input: Query) => void; + indexPattern: IndexPattern; + isInvalid: boolean; + onSubmit: () => void; + disableAutoFocus?: boolean; +}) => { + const [inputValue, setInputValue] = useState(value); + + useDebounce(() => onChange(inputValue), 256, [inputValue]); + + const handleInputChange = (input: Query) => { + setInputValue(input); + }; + + return ( + { + if (inputValue.query) { + onSubmit(); + } + }} + placeholder={ + inputValue.language === 'kuery' + ? i18n.translate('xpack.lens.indexPattern.filters.queryPlaceholderKql', { + defaultMessage: '{example}', + values: { example: 'method : "GET" or status : "404"' }, + }) + : i18n.translate('xpack.lens.indexPattern.filters.queryPlaceholderLucene', { + defaultMessage: '{example}', + values: { example: 'method:GET OR status:404' }, + }) + } + languageSwitcherPopoverAnchorPosition="rightDown" + /> + ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts index 6a0fbb30068475..d786d781199b62 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts @@ -7,6 +7,7 @@ import type { IUiSettingsClient } from 'kibana/public'; import { + AggFunctionsMapping, EsaggsExpressionFunctionDefinition, IndexPatternLoadExpressionFunctionDefinition, } from '../../../../../src/plugins/data/public'; @@ -29,11 +30,32 @@ function getExpressionForLayer( indexPattern: IndexPattern, uiSettings: IUiSettingsClient ): ExpressionAstExpression | null { - const { columns, columnOrder } = layer; + const { columnOrder } = layer; if (columnOrder.length === 0 || !indexPattern) { return null; } + const columns = { ...layer.columns }; + Object.keys(columns).forEach((columnId) => { + const column = columns[columnId]; + const rootDef = operationDefinitionMap[column.operationType]; + if ( + 'references' in column && + rootDef.filterable && + rootDef.input === 'fullReference' && + column.filter + ) { + // inherit filter to all referenced operations + column.references.forEach((referenceColumnId) => { + const referencedColumn = columns[referenceColumnId]; + const referenceDef = operationDefinitionMap[column.operationType]; + if (referenceDef.filterable) { + columns[referenceColumnId] = { ...referencedColumn, filter: column.filter }; + } + }); + } + }); + const columnEntries = columnOrder.map((colId) => [colId, columns[colId]] as const); if (columnEntries.length) { @@ -44,10 +66,37 @@ function getExpressionForLayer( if (def.input === 'fullReference') { expressions.push(...def.toExpression(layer, colId, indexPattern)); } else { + const wrapInFilter = Boolean(def.filterable && col.filter); + let aggAst = def.toEsAggsFn( + col, + wrapInFilter ? `${colId}-metric` : colId, + indexPattern, + layer, + uiSettings + ); + if (wrapInFilter) { + aggAst = buildExpressionFunction( + 'aggFilteredMetric', + { + id: colId, + enabled: true, + schema: 'metric', + customBucket: buildExpression([ + buildExpressionFunction('aggFilter', { + id: `${colId}-filter`, + enabled: true, + schema: 'bucket', + filter: JSON.stringify(col.filter), + }), + ]), + customMetric: buildExpression({ type: 'expression', chain: [aggAst] }), + } + ).toAst(); + } aggs.push( buildExpression({ type: 'expression', - chain: [def.toEsAggsFn(col, colId, indexPattern, layer, uiSettings)], + chain: [aggAst], }) ); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c07210f2e3eff1..9b7a25ff62661d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12095,7 +12095,6 @@ "xpack.lens.indexPattern.terms.otherLabel": "その他", "xpack.lens.indexPattern.terms.size": "値の数", "xpack.lens.indexPattern.termsOf": "{name} のトップの値", - "xpack.lens.indexPattern.timeScale.advancedSettings": "高度なオプションを追加", "xpack.lens.indexPattern.timeScale.enableTimeScale": "単位で正規化", "xpack.lens.indexPattern.timeScale.label": "単位で正規化", "xpack.lens.indexPattern.timeScale.tooltip": "基本の日付間隔に関係なく、常に指定された時間単位のレートとして表示されるように値を正規化します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8a027942c42752..7bb27ee6626b8d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12253,7 +12253,6 @@ "xpack.lens.indexPattern.terms.otherLabel": "其他", "xpack.lens.indexPattern.terms.size": "值数目", "xpack.lens.indexPattern.termsOf": "{name} 排名最前值", - "xpack.lens.indexPattern.timeScale.advancedSettings": "添加高级选项", "xpack.lens.indexPattern.timeScale.enableTimeScale": "按单位标准化", "xpack.lens.indexPattern.timeScale.label": "按单位标准化", "xpack.lens.indexPattern.timeScale.tooltip": "将值标准化为始终显示为每指定时间单位速率,无论基础日期时间间隔是多少。", From c218ce292af1cc90ce619fbc4f1b7459e3255d4b Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 24 Mar 2021 11:47:56 +0100 Subject: [PATCH 53/93] [Lens] Add ability to remove runtime field (#94949) --- .../indexpattern_datasource/datapanel.tsx | 46 ++++++++++-- .../indexpattern_datasource/field_item.tsx | 65 +++++++++++++---- .../indexpattern_datasource/field_list.tsx | 4 ++ .../fields_accordion.tsx | 4 ++ x-pack/plugins/lens/server/usage/schema.ts | 38 ++++++++++ .../schema/xpack_plugins.json | 72 +++++++++++++++++++ .../functional/apps/lens/runtime_fields.ts | 6 ++ .../test/functional/page_objects/lens_page.ts | 15 ++++ 8 files changed, 230 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 68a3e9056b05d0..2cad77b0034545 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -496,6 +496,15 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ }; }, []); + const refreshFieldList = useCallback(async () => { + const newlyMappedIndexPattern = await loadIndexPatterns({ + indexPatternsService: data.indexPatterns, + cache: {}, + patterns: [currentIndexPattern.id], + }); + onUpdateIndexPattern(newlyMappedIndexPattern[currentIndexPattern.id]); + }, [data, currentIndexPattern, onUpdateIndexPattern]); + const editField = useMemo( () => editPermission @@ -509,17 +518,39 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ fieldName, onSave: async () => { trackUiEvent(`save_field_${uiAction}`); - const newlyMappedIndexPattern = await loadIndexPatterns({ - indexPatternsService: data.indexPatterns, - cache: {}, - patterns: [currentIndexPattern.id], - }); - onUpdateIndexPattern(newlyMappedIndexPattern[currentIndexPattern.id]); + await refreshFieldList(); + }, + }); + } + : undefined, + [data, indexPatternFieldEditor, currentIndexPattern, editPermission, refreshFieldList] + ); + + const removeField = useMemo( + () => + editPermission + ? async (fieldName: string) => { + trackUiEvent('open_field_delete_modal'); + const indexPatternInstance = await data.indexPatterns.get(currentIndexPattern.id); + closeFieldEditor.current = indexPatternFieldEditor.openDeleteModal({ + ctx: { + indexPattern: indexPatternInstance, + }, + fieldName, + onDelete: async () => { + trackUiEvent('delete_field'); + await refreshFieldList(); }, }); } : undefined, - [data, indexPatternFieldEditor, currentIndexPattern, editPermission, onUpdateIndexPattern] + [ + currentIndexPattern.id, + data.indexPatterns, + editPermission, + indexPatternFieldEditor, + refreshFieldList, + ] ); const addField = useMemo( @@ -765,6 +796,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ dropOntoWorkspace={dropOntoWorkspace} hasSuggestionForField={hasSuggestionForField} editField={editField} + removeField={removeField} />
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 3094b6463fe158..8ae62e4d843c28 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -73,6 +73,7 @@ export interface FieldItemProps { groupIndex: number; dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; editField?: (name: string) => void; + removeField?: (name: string) => void; hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; } @@ -107,6 +108,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { groupIndex, dropOntoWorkspace, editField, + removeField, } = props; const [infoIsOpen, setOpen] = useState(false); @@ -122,6 +124,17 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { [editField, setOpen] ); + const closeAndRemove = useMemo( + () => + removeField + ? (name: string) => { + removeField(name); + setOpen(false); + } + : undefined, + [removeField, setOpen] + ); + const dropOntoWorkspaceAndClose = useCallback( (droppedField: DragDropIdentifier) => { dropOntoWorkspace(droppedField); @@ -270,6 +283,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { {...state} {...props} editField={closeAndEdit} + removeField={closeAndRemove} dropOntoWorkspace={dropOntoWorkspaceAndClose} /> @@ -285,12 +299,14 @@ function FieldPanelHeader({ hasSuggestionForField, dropOntoWorkspace, editField, + removeField, }: { field: IndexPatternField; indexPatternId: string; hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; editField?: (name: string) => void; + removeField?: (name: string) => void; }) { const draggableField = { indexPatternId, @@ -302,7 +318,7 @@ function FieldPanelHeader({ }; return ( - +
{field.displayName}
@@ -315,20 +331,41 @@ function FieldPanelHeader({ field={draggableField} /> {editField && ( - - editField(field.name)} - iconType="pencil" - data-test-subj="lnsFieldListPanelEdit" - aria-label={i18n.translate('xpack.lens.indexPattern.editFieldLabel', { + + - + > + editField(field.name)} + iconType="pencil" + data-test-subj="lnsFieldListPanelEdit" + aria-label={i18n.translate('xpack.lens.indexPattern.editFieldLabel', { + defaultMessage: 'Edit index pattern field', + })} + /> + +
+ )} + {removeField && field.runtime && ( + + + removeField(field.name)} + iconType="trash" + data-test-subj="lnsFieldListPanelRemove" + color="danger" + aria-label={i18n.translate('xpack.lens.indexPattern.removeFieldLabel', { + defaultMessage: 'Remove index pattern field', + })} + /> + + )}
); @@ -347,6 +384,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { data: { fieldFormats }, dropOntoWorkspace, editField, + removeField, hasSuggestionForField, hideDetails, } = props; @@ -379,6 +417,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { dropOntoWorkspace={dropOntoWorkspace} hasSuggestionForField={hasSuggestionForField} editField={editField} + removeField={removeField} /> ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx index 01ba0726d9e4d7..ceeb1f5b1caf3a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx @@ -53,6 +53,7 @@ export const FieldList = React.memo(function FieldList({ dropOntoWorkspace, hasSuggestionForField, editField, + removeField, }: { exists: (field: IndexPatternField) => boolean; fieldGroups: FieldGroups; @@ -68,6 +69,7 @@ export const FieldList = React.memo(function FieldList({ dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; editField?: (name: string) => void; + removeField?: (name: string) => void; }) { const [pageSize, setPageSize] = useState(PAGINATION_SIZE); const [scrollContainer, setScrollContainer] = useState(undefined); @@ -144,6 +146,7 @@ export const FieldList = React.memo(function FieldList({ exists={exists(field)} field={field} editField={editField} + removeField={removeField} hideDetails={true} key={field.name} itemIndex={index} @@ -169,6 +172,7 @@ export const FieldList = React.memo(function FieldList({ helpTooltip={fieldGroup.helpText} exists={exists} editField={editField} + removeField={removeField} hideDetails={fieldGroup.hideDetails} hasLoaded={!!hasSyncedExistingFields} fieldsCount={fieldGroup.fields.length} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx index 74ea13a81539fe..a00f25b04651bf 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx @@ -55,6 +55,7 @@ export interface FieldsAccordionProps { dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; editField?: (name: string) => void; + removeField?: (name: string) => void; } export const FieldsAccordion = memo(function InnerFieldsAccordion({ @@ -76,6 +77,7 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ dropOntoWorkspace, hasSuggestionForField, editField, + removeField, }: FieldsAccordionProps) { const renderField = useCallback( (field: IndexPatternField, index) => ( @@ -90,6 +92,7 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ dropOntoWorkspace={dropOntoWorkspace} hasSuggestionForField={hasSuggestionForField} editField={editField} + removeField={removeField} /> ), [ @@ -100,6 +103,7 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ hasSuggestionForField, groupIndex, editField, + removeField, ] ); diff --git a/x-pack/plugins/lens/server/usage/schema.ts b/x-pack/plugins/lens/server/usage/schema.ts index 158e62ee8cfd80..5c15855aca48a0 100644 --- a/x-pack/plugins/lens/server/usage/schema.ts +++ b/x-pack/plugins/lens/server/usage/schema.ts @@ -34,6 +34,44 @@ const eventsSchema: MakeSchemaFrom = { xy_change_layer_display: { type: 'long' }, xy_layer_removed: { type: 'long' }, xy_layer_added: { type: 'long' }, + open_field_editor_edit: { + type: 'long', + _meta: { + description: + 'Number of times the user opened the editor flyout to edit a field from within Lens.', + }, + }, + open_field_editor_add: { + type: 'long', + _meta: { + description: + 'Number of times the user opened the editor flyout to add a field from within Lens.', + }, + }, + save_field_edit: { + type: 'long', + _meta: { + description: 'Number of times the user edited a field from within Lens.', + }, + }, + save_field_add: { + type: 'long', + _meta: { + description: 'Number of times the user added a field from within Lens.', + }, + }, + open_field_delete_modal: { + type: 'long', + _meta: { + description: 'Number of times the user opened the field delete modal from within Lens.', + }, + }, + delete_field: { + type: 'long', + _meta: { + description: 'Number of times the user deleted a field from within Lens.', + }, + }, indexpattern_dimension_operation_terms: { type: 'long', _meta: { diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index ed8e44072b9145..6bb152433f2fb9 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -2033,6 +2033,42 @@ "indexpattern_dimension_operation_moving_average": { "type": "long", "_meta": { "description": "Number of times the moving average function was selected" } + }, + "open_field_editor_edit": { + "type": "long", + "_meta": { + "description": "Number of times the user opened the editor flyout to edit a field from within Lens." + } + }, + "open_field_editor_add": { + "type": "long", + "_meta": { + "description": "Number of times the user opened the editor flyout to add a field from within Lens." + } + }, + "save_field_edit": { + "type": "long", + "_meta": { + "description": "Number of times the user edited a field from within Lens." + } + }, + "save_field_add": { + "type": "long", + "_meta": { + "description": "Number of times the user added a field from within Lens." + } + }, + "open_field_delete_modal": { + "type": "long", + "_meta": { + "description": "Number of times the user opened the field delete modal from within Lens." + } + }, + "delete_field": { + "type": "long", + "_meta": { + "description": "Number of times the user deleted a field from within Lens." + } } } }, @@ -2198,6 +2234,42 @@ "indexpattern_dimension_operation_moving_average": { "type": "long", "_meta": { "description": "Number of times the moving average function was selected" } + }, + "open_field_editor_edit": { + "type": "long", + "_meta": { + "description": "Number of times the user opened the editor flyout to edit a field from within Lens." + } + }, + "open_field_editor_add": { + "type": "long", + "_meta": { + "description": "Number of times the user opened the editor flyout to add a field from within Lens." + } + }, + "save_field_edit": { + "type": "long", + "_meta": { + "description": "Number of times the user edited a field from within Lens." + } + }, + "save_field_add": { + "type": "long", + "_meta": { + "description": "Number of times the user added a field from within Lens." + } + }, + "open_field_delete_modal": { + "type": "long", + "_meta": { + "description": "Number of times the user opened the field delete modal from within Lens." + } + }, + "delete_field": { + "type": "long", + "_meta": { + "description": "Number of times the user deleted a field from within Lens." + } } } }, diff --git a/x-pack/test/functional/apps/lens/runtime_fields.ts b/x-pack/test/functional/apps/lens/runtime_fields.ts index 9b8ef3a8b69051..53fe9eab0e654b 100644 --- a/x-pack/test/functional/apps/lens/runtime_fields.ts +++ b/x-pack/test/functional/apps/lens/runtime_fields.ts @@ -62,5 +62,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('abc'); }); + + it('should able to remove field', async () => { + await PageObjects.lens.clickField('runtimefield2'); + await PageObjects.lens.removeField(); + await PageObjects.lens.waitForFieldMissing('runtimefield2'); + }); }); } diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 3858b5834d4831..3c2f7f12683316 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -188,6 +188,15 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }); }, + async removeField() { + await retry.try(async () => { + await testSubjects.click('lnsFieldListPanelRemove'); + await testSubjects.missingOrFail('lnsFieldListPanelRemove'); + await testSubjects.click('confirmModalConfirmButton'); + await testSubjects.missingOrFail('confirmModalConfirmButton'); + }); + }, + async searchField(name: string) { await testSubjects.setValue('lnsIndexPatternFieldSearch', name); }, @@ -198,6 +207,12 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }); }, + async waitForFieldMissing(field: string) { + await retry.try(async () => { + await testSubjects.missingOrFail(`lnsFieldListPanelField-${field}`); + }); + }, + /** * Copies field to chosen destination that is defined by distance of `steps` * (right arrow presses) from it From cb61b4a8fd07069396c9697a1d0d386dcf65e75f Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 24 Mar 2021 12:09:19 +0100 Subject: [PATCH 54/93] [Discover] Move doc viewer table row actions to left (#95064) * Move doc viewer table actions to left * Improve SCSS --- .../components/doc_viewer/doc_viewer.scss | 6 +-- .../components/table/table_row.tsx | 40 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss index 95a50b54b53648..f5a4180207c331 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss @@ -1,5 +1,5 @@ .kbnDocViewerTable { - @include euiBreakpoint('xs', 's') { + @include euiBreakpoint('xs', 's','m') { table-layout: fixed; } } @@ -52,7 +52,7 @@ white-space: nowrap; } .kbnDocViewer__buttons { - width: 96px; + width: 108px; // Show all icons if one is focused, &:focus-within { @@ -64,7 +64,7 @@ .kbnDocViewer__field { width: $euiSize * 10; - @include euiBreakpoint('xs', 's') { + @include euiBreakpoint('xs', 's', 'm') { width: $euiSize * 6; } } diff --git a/src/plugins/discover/public/application/components/table/table_row.tsx b/src/plugins/discover/public/application/components/table/table_row.tsx index 731dbeed85cc86..5c6ae49770bc70 100644 --- a/src/plugins/discover/public/application/components/table/table_row.tsx +++ b/src/plugins/discover/public/application/components/table/table_row.tsx @@ -54,6 +54,26 @@ export function DocViewTableRow({ const key = field ? field : fieldMapping?.displayName; return ( + {typeof onFilter === 'function' && ( + + onFilter(fieldMapping, valueRaw, '+')} + /> + onFilter(fieldMapping, valueRaw, '-')} + /> + {typeof onToggleColumn === 'function' && ( + + )} + onFilter('_exists_', field, '+')} + scripted={fieldMapping && fieldMapping.scripted} + /> + + )} {field ? ( - {typeof onFilter === 'function' && ( - - onFilter(fieldMapping, valueRaw, '+')} - /> - onFilter(fieldMapping, valueRaw, '-')} - /> - {typeof onToggleColumn === 'function' && ( - - )} - onFilter('_exists_', field, '+')} - scripted={fieldMapping && fieldMapping.scripted} - /> - - )} ); } From e6f477021334e468770c3a3876041e448543d1a9 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 24 Mar 2021 13:24:31 +0100 Subject: [PATCH 55/93] [Lens] Add telemetry for popovers (#94903) --- .../public/indexpattern_datasource/help_popover.tsx | 8 +++++++- .../definitions/calculations/moving_average.tsx | 6 +++++- .../operations/definitions/date_histogram.tsx | 6 +++++- .../operations/definitions/ranges/range_editor.tsx | 6 +++++- x-pack/plugins/lens/server/usage/schema.ts | 4 ++++ .../schema/xpack_plugins.json | 12 ++++++++++++ 6 files changed, 38 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx index ca126ff4e40bfc..0e4c1897743b49 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { ReactNode } from 'react'; +import React, { ReactNode, useEffect } from 'react'; import { EuiIcon, EuiLink, @@ -15,6 +15,7 @@ import { EuiPopoverTitle, EuiText, } from '@elastic/eui'; +import { trackUiEvent } from '../lens_ui_telemetry'; import './help_popover.scss'; export const HelpPopoverButton = ({ @@ -50,6 +51,11 @@ export const HelpPopover = ({ isOpen: EuiPopoverProps['isOpen']; title?: string; }) => { + useEffect(() => { + if (isOpen) { + trackUiEvent('open_help_popover'); + } + }, [isOpen]); return ( { setIsPopoverOpen(!isPopoverOpen)}> + { + setIsPopoverOpen(!isPopoverOpen); + }} + > {i18n.translate('xpack.lens.indexPattern.movingAverage.helpText', { defaultMessage: 'How it works', })} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index 8d40ed9b7f0661..bd7a270fd7ad80 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -355,7 +355,11 @@ const AutoDateHistogramPopover = ({ data }: { data: DataPublicPluginStart }) => setIsPopoverOpen(!isPopoverOpen)}> + { + setIsPopoverOpen(!isPopoverOpen); + }} + > {i18n.translate('xpack.lens.indexPattern.dateHistogram.autoHelpText', { defaultMessage: 'How it works', })} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx index 269c59822fefca..4851b6ff3ec975 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx @@ -33,7 +33,11 @@ const GranularityHelpPopover = () => { setIsPopoverOpen(!isPopoverOpen)}> + { + setIsPopoverOpen(!isPopoverOpen); + }} + > {i18n.translate('xpack.lens.indexPattern.ranges.granularityHelpText', { defaultMessage: 'How it works', })} diff --git a/x-pack/plugins/lens/server/usage/schema.ts b/x-pack/plugins/lens/server/usage/schema.ts index 5c15855aca48a0..ab3945a0162a68 100644 --- a/x-pack/plugins/lens/server/usage/schema.ts +++ b/x-pack/plugins/lens/server/usage/schema.ts @@ -10,6 +10,10 @@ import { LensUsage } from './types'; const eventsSchema: MakeSchemaFrom = { app_query_change: { type: 'long' }, + open_help_popover: { + type: 'long', + _meta: { description: 'Number of times the user opened one of the in-product help popovers.' }, + }, indexpattern_field_info_click: { type: 'long' }, loaded: { type: 'long' }, app_filters_updated: { type: 'long' }, diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 6bb152433f2fb9..36488fceb2a1d4 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -1876,6 +1876,12 @@ "app_query_change": { "type": "long" }, + "open_help_popover": { + "type": "long", + "_meta": { + "description": "Number of times the user opened one of the in-product help popovers." + } + }, "indexpattern_field_info_click": { "type": "long" }, @@ -2077,6 +2083,12 @@ "app_query_change": { "type": "long" }, + "open_help_popover": { + "type": "long", + "_meta": { + "description": "Number of times the user opened one of the in-product help popovers." + } + }, "indexpattern_field_info_click": { "type": "long" }, From ad1004519afb7b2f236b1eb393bacd9f68207d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= Date: Wed, 24 Mar 2021 13:45:56 +0100 Subject: [PATCH 56/93] [Metrics UI] Fix preview charts for inventory alerts when using a filter (#94561) * Pass filterQuery instead of filterQueryText to invertory alerts preview chart * Add test to verify that filterQuery is sent to the expression chart * Improve test description Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../inventory/components/expression.test.tsx | 40 ++++++++++++++++++- .../inventory/components/expression.tsx | 3 +- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx index 891e98606264e0..88d72300c2d6db 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl, nextTick } from '@kbn/test/jest'; // We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock` import { coreMock as mockCoreMock } from 'src/core/public/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -89,6 +89,44 @@ describe('Expression', () => { }, ]); }); + + it('should pass the elasticsearch query to the expression chart', async () => { + const FILTER_QUERY = + '{"bool":{"should":[{"match_phrase":{"host.name":"testHostName"}}],"minimum_should_match":1}}'; + + const alertParams = { + criteria: [ + { + metric: 'cpu', + timeSize: 1, + timeUnit: 'm', + threshold: [10], + comparator: Comparator.GT, + }, + ], + nodeType: undefined, + filterQueryText: 'host.name: "testHostName"', + filterQuery: FILTER_QUERY, + }; + + const wrapper = shallowWithIntl( + Reflect.set(alertParams, key, value)} + setAlertProperty={() => {}} + metadata={{}} + /> + ); + + const chart = wrapper.find('[data-test-subj="preview-chart"]'); + + expect(chart.prop('filterQuery')).toBe(FILTER_QUERY); + }); + describe('using custom metrics', () => { it('should prefill the alert using the context metadata', async () => { const currentOptions = { diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index 9ce7162933f2d6..b28c76d1cb3746 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -319,9 +319,10 @@ export const Expressions: React.FC = (props) => { > ); From 326abc2a9472df5712497affa62d157b46946b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= Date: Wed, 24 Mar 2021 13:51:20 +0100 Subject: [PATCH 57/93] Add filters to query total groupings (#94792) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../lib/query_total_grouping.test.ts | 127 ++++++++++++++++++ .../lib/query_total_groupings.ts | 30 +++-- 2 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_grouping.test.ts diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_grouping.test.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_grouping.test.ts new file mode 100644 index 00000000000000..cc5631ffcec9c2 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_grouping.test.ts @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { queryTotalGroupings } from './query_total_groupings'; + +describe('queryTotalGroupings', () => { + const ESSearchClientMock = jest.fn().mockReturnValue({}); + const defaultOptions = { + timerange: { + from: 1615972672011, + interval: '>=10s', + to: 1615976272012, + field: '@timestamp', + }, + indexPattern: 'testIndexPattern', + metrics: [], + dropLastBucket: true, + groupBy: ['testField'], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return 0 when there is no groupBy', async () => { + const { groupBy, ...options } = defaultOptions; + + const response = await queryTotalGroupings(ESSearchClientMock, options); + expect(response).toBe(0); + }); + + it('should return 0 when there is groupBy is empty', async () => { + const options = { + ...defaultOptions, + groupBy: [], + }; + + const response = await queryTotalGroupings(ESSearchClientMock, options); + expect(response).toBe(0); + }); + + it('should query ES with a timerange', async () => { + await queryTotalGroupings(ESSearchClientMock, defaultOptions); + + expect(ESSearchClientMock.mock.calls[0][0].body.query.bool.filter).toContainEqual({ + range: { + '@timestamp': { + gte: 1615972672011, + lte: 1615976272012, + format: 'epoch_millis', + }, + }, + }); + }); + + it('should query ES with a exist fields', async () => { + const options = { + ...defaultOptions, + groupBy: ['testField1', 'testField2'], + }; + + await queryTotalGroupings(ESSearchClientMock, options); + + expect(ESSearchClientMock.mock.calls[0][0].body.query.bool.filter).toContainEqual({ + exists: { field: 'testField1' }, + }); + + expect(ESSearchClientMock.mock.calls[0][0].body.query.bool.filter).toContainEqual({ + exists: { field: 'testField2' }, + }); + }); + + it('should query ES with a query filter', async () => { + const options = { + ...defaultOptions, + filters: [ + { + bool: { + should: [{ match_phrase: { field1: 'value1' } }], + minimum_should_match: 1, + }, + }, + ], + }; + + await queryTotalGroupings(ESSearchClientMock, options); + + expect(ESSearchClientMock.mock.calls[0][0].body.query.bool.filter).toContainEqual({ + bool: { + should: [ + { + match_phrase: { + field1: 'value1', + }, + }, + ], + minimum_should_match: 1, + }, + }); + }); + + it('should return 0 when there are no aggregations in the response', async () => { + const clientMock = jest.fn().mockReturnValue({}); + + const response = await queryTotalGroupings(clientMock, defaultOptions); + + expect(response).toBe(0); + }); + + it('should return the value of the aggregation in the response', async () => { + const clientMock = jest.fn().mockReturnValue({ + aggregations: { + count: { + value: 10, + }, + }, + }); + + const response = await queryTotalGroupings(clientMock, defaultOptions); + + expect(response).toBe(10); + }); +}); diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_groupings.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_groupings.ts index 92aa39d3bf8206..b871fa21c111d7 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_groupings.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_groupings.ts @@ -23,6 +23,23 @@ export const queryTotalGroupings = async ( return Promise.resolve(0); } + let filters: Array> = [ + { + range: { + [options.timerange.field]: { + gte: options.timerange.from, + lte: options.timerange.to, + format: 'epoch_millis', + }, + }, + }, + ...options.groupBy.map((field) => ({ exists: { field } })), + ]; + + if (options.filters) { + filters = [...filters, ...options.filters]; + } + const params = { allowNoIndices: true, ignoreUnavailable: true, @@ -31,18 +48,7 @@ export const queryTotalGroupings = async ( size: 0, query: { bool: { - filter: [ - { - range: { - [options.timerange.field]: { - gte: options.timerange.from, - lte: options.timerange.to, - format: 'epoch_millis', - }, - }, - }, - ...options.groupBy.map((field) => ({ exists: { field } })), - ], + filter: filters, }, }, aggs: { From 6295ae71b5d3a2810767968780be7c7eaccc60d7 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 24 Mar 2021 14:04:19 +0100 Subject: [PATCH 58/93] [Lens] Cache avilable operations meta data (#95034) --- .../dimension_panel/operation_support.ts | 4 ++-- .../plugins/lens/public/indexpattern_datasource/loader.ts | 6 ++++++ .../indexpattern_datasource/operations/__mocks__/index.ts | 1 + .../public/indexpattern_datasource/operations/operations.ts | 4 +++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts index d462d73740aaa0..801b1b17a1831d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts @@ -8,7 +8,7 @@ import _ from 'lodash'; import { DatasourceDimensionDropProps } from '../../types'; import { OperationType } from '../indexpattern'; -import { getAvailableOperationsByMetadata } from '../operations'; +import { memoizedGetAvailableOperationsByMetadata } from '../operations'; import { IndexPatternPrivateState } from '../types'; export interface OperationSupportMatrix { @@ -30,7 +30,7 @@ export const getOperationSupportMatrix = (props: Props): OperationSupportMatrix const layerId = props.layerId; const currentIndexPattern = props.state.indexPatterns[props.state.layers[layerId].indexPatternId]; - const filteredOperationsByMetadata = getAvailableOperationsByMetadata( + const filteredOperationsByMetadata = memoizedGetAvailableOperationsByMetadata( currentIndexPattern ).filter((operation) => props.filterOperations(operation.operationMetaData)); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index 7a50b1e60d13fe..7052a69ee6fb73 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -29,6 +29,7 @@ import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/pub import { documentField } from './document_field'; import { readFromStorage, writeToStorage } from '../settings_storage'; import { getFieldByNameFactory } from './pure_helpers'; +import { memoizedGetAvailableOperationsByMetadata } from './operations'; type SetState = StateSetter; type IndexPatternsService = Pick; @@ -49,6 +50,11 @@ export async function loadIndexPatterns({ return cache; } + if (memoizedGetAvailableOperationsByMetadata.cache.clear) { + // clear operations meta data cache because index pattern reference may change + memoizedGetAvailableOperationsByMetadata.cache.clear(); + } + const allIndexPatterns = await Promise.allSettled( missingIds.map((id) => indexPatternsService.get(id)) ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts index dfb86ec16da30a..6ac208913af2e8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts @@ -19,6 +19,7 @@ jest.spyOn(actualHelpers, 'getErrorMessages'); export const { getAvailableOperationsByMetadata, + memoizedGetAvailableOperationsByMetadata, getOperations, getOperationDisplay, getOperationTypesForField, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts index 63671fe35e99e1..140ebc813f6c15 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts @@ -5,7 +5,7 @@ * 2.0. */ -import _ from 'lodash'; +import { memoize } from 'lodash'; import { OperationMetadata } from '../../types'; import { operationDefinitionMap, @@ -187,3 +187,5 @@ export function getAvailableOperationsByMetadata(indexPattern: IndexPattern) { return Object.values(operationByMetadata); } + +export const memoizedGetAvailableOperationsByMetadata = memoize(getAvailableOperationsByMetadata); From edfdb7895773120d6ffb2b8670f85e10f6955a8c Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 24 Mar 2021 14:05:08 +0100 Subject: [PATCH 59/93] [Console] Clean up use of `any` (#95065) * cleaned up autocomplete.ts and get_endpoint_from_postition.ts of anys * general clean up of lowering hanging fruit * cleaned up remaining anys, still need to test * fix remaining TS compilation issues * also tidy up use of "as any" and some more ": any" * addressed type issues and introduced the default editor settings object * clean up @ts-ignore * added comments to interface Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/components/console_menu.tsx | 5 +- .../application/components/settings_modal.tsx | 4 +- .../console_history/console_history.tsx | 2 +- .../console_history/history_viewer.tsx | 2 +- .../console_editor/apply_editor_settings.ts | 2 +- .../legacy/console_editor/editor.test.tsx | 4 +- .../editor/legacy/console_editor/editor.tsx | 2 +- .../editor/legacy/console_menu_actions.ts | 2 +- .../application/containers/settings.tsx | 22 ++- .../editor_context/editor_context.tsx | 8 +- .../application/contexts/request_context.tsx | 4 +- .../contexts/services_context.mock.ts | 5 +- .../application/contexts/services_context.tsx | 8 +- .../restore_request_from_history.ts | 8 +- .../use_restore_request_from_history.ts | 3 +- .../send_request_to_es.ts | 17 +- .../use_send_current_request_to_es/track.ts | 6 +- .../use_send_current_request_to_es.test.tsx | 4 +- .../application/hooks/use_set_input_editor.ts | 3 +- .../console/public/application/index.tsx | 4 +- .../legacy_core_editor/create_readonly.ts | 8 +- .../legacy_core_editor.test.mocks.ts | 2 +- .../legacy_core_editor/legacy_core_editor.ts | 47 +++--- .../models/legacy_core_editor/smart_resize.ts | 3 +- .../application/models/sense_editor/curl.ts | 4 +- .../models/sense_editor/sense_editor.ts | 27 +-- .../public/application/stores/editor.ts | 9 +- .../lib/ace_token_provider/token_provider.ts | 4 +- .../public/lib/autocomplete/autocomplete.ts | 154 +++++++++++------- .../components/full_request_component.ts | 2 +- .../get_endpoint_from_position.ts | 3 +- .../console/public/lib/autocomplete/types.ts | 61 +++++++ src/plugins/console/public/lib/es/es.ts | 8 +- src/plugins/console/public/lib/row_parser.ts | 2 +- .../lib/token_iterator/token_iterator.test.ts | 2 +- src/plugins/console/public/lib/utils/index.ts | 4 +- .../console/public/services/history.ts | 6 +- src/plugins/console/public/services/index.ts | 2 +- .../console/public/services/settings.ts | 32 ++-- .../console/public/services/storage.ts | 6 +- src/plugins/console/public/types/common.ts | 6 + .../console/public/types/core_editor.ts | 10 +- .../server/lib/elasticsearch_proxy_config.ts | 4 +- .../console/server/lib/proxy_config.ts | 19 ++- .../server/lib/proxy_config_collection.ts | 10 +- .../console/server/lib/proxy_request.test.ts | 2 +- .../console/server/lib/proxy_request.ts | 6 +- .../routes/api/console/proxy/body.test.ts | 10 +- .../api/console/proxy/create_handler.ts | 8 +- .../api/console/proxy/query_string.test.ts | 3 +- .../server/routes/api/console/proxy/stubs.ts | 8 +- .../services/spec_definitions_service.ts | 34 +++- 52 files changed, 402 insertions(+), 219 deletions(-) create mode 100644 src/plugins/console/public/lib/autocomplete/types.ts diff --git a/src/plugins/console/public/application/components/console_menu.tsx b/src/plugins/console/public/application/components/console_menu.tsx index 40e3ce9c44e26d..6c5eb8646c58d1 100644 --- a/src/plugins/console/public/application/components/console_menu.tsx +++ b/src/plugins/console/public/application/components/console_menu.tsx @@ -16,7 +16,7 @@ import { i18n } from '@kbn/i18n'; interface Props { getCurl: () => Promise; getDocumentation: () => Promise; - autoIndent: (ev?: React.MouseEvent) => void; + autoIndent: (ev: React.MouseEvent) => void; addNotification?: (opts: { title: string }) => void; } @@ -84,8 +84,7 @@ export class ConsoleMenu extends Component { window.open(documentation, '_blank'); }; - // Using `any` here per this issue: https://github.com/elastic/eui/issues/2265 - autoIndent: any = (event: React.MouseEvent) => { + autoIndent = (event: React.MouseEvent) => { this.closePopover(); this.props.autoIndent(event); }; diff --git a/src/plugins/console/public/application/components/settings_modal.tsx b/src/plugins/console/public/application/components/settings_modal.tsx index 161b67500b47c7..033bce42177bef 100644 --- a/src/plugins/console/public/application/components/settings_modal.tsx +++ b/src/plugins/console/public/application/components/settings_modal.tsx @@ -32,7 +32,7 @@ export type AutocompleteOptions = 'fields' | 'indices' | 'templates'; interface Props { onSaveSettings: (newSettings: DevToolsSettings) => void; onClose: () => void; - refreshAutocompleteSettings: (selectedSettings: any) => void; + refreshAutocompleteSettings: (selectedSettings: DevToolsSettings['autocomplete']) => void; settings: DevToolsSettings; } @@ -233,7 +233,7 @@ export function DevToolsSettingsModal(props: Props) { return rest; })} idToSelectedMap={checkboxIdToSelectedMap} - onChange={(e: any) => { + onChange={(e: unknown) => { onAutocompleteChange(e as AutocompleteOptions); }} /> diff --git a/src/plugins/console/public/application/containers/console_history/console_history.tsx b/src/plugins/console/public/application/containers/console_history/console_history.tsx index 4d5c08705e0d5f..7fbef6de80eef8 100644 --- a/src/plugins/console/public/application/containers/console_history/console_history.tsx +++ b/src/plugins/console/public/application/containers/console_history/console_history.tsx @@ -53,7 +53,7 @@ export function ConsoleHistory({ close }: Props) { const selectedReq = useRef(null); const describeReq = useMemo(() => { - const _describeReq = (req: any) => { + const _describeReq = (req: { endpoint: string; time: string }) => { const endpoint = req.endpoint; const date = moment(req.time); diff --git a/src/plugins/console/public/application/containers/console_history/history_viewer.tsx b/src/plugins/console/public/application/containers/console_history/history_viewer.tsx index 00a7cf8afa2c08..605f9a254f228d 100644 --- a/src/plugins/console/public/application/containers/console_history/history_viewer.tsx +++ b/src/plugins/console/public/application/containers/console_history/history_viewer.tsx @@ -20,7 +20,7 @@ import { applyCurrentSettings } from '../editor/legacy/console_editor/apply_edit interface Props { settings: DevToolsSettings; - req: any | null; + req: { method: string; endpoint: string; data: string; time: string } | null; } export function HistoryViewer({ settings, req }: Props) { diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/apply_editor_settings.ts b/src/plugins/console/public/application/containers/editor/legacy/console_editor/apply_editor_settings.ts index 678ea1c387096e..f84999b2947426 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/apply_editor_settings.ts +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/apply_editor_settings.ts @@ -14,7 +14,7 @@ export function applyCurrentSettings( editor: CoreEditor | CustomAceEditor, settings: DevToolsSettings ) { - if ((editor as any).setStyles) { + if ((editor as { setStyles?: Function }).setStyles) { (editor as CoreEditor).setStyles({ wrapLines: settings.wrapMode, fontSize: settings.fontSize + 'px', diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx index c4da04dfbf5a62..1732dd9572b905 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx @@ -27,7 +27,7 @@ import { // Mocked functions import { sendRequestToES } from '../../../../hooks/use_send_current_request_to_es/send_request_to_es'; import { getEndpointFromPosition } from '../../../../../lib/autocomplete/get_endpoint_from_position'; - +import type { DevToolsSettings } from '../../../../../services'; import * as consoleMenuActions from '../console_menu_actions'; import { Editor } from './editor'; @@ -40,7 +40,7 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { - + diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index 8b965480d077bc..541ad8b0563a4f 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -229,7 +229,7 @@ function EditorUI({ initialTextValue }: EditorProps) { getDocumentation={() => { return getDocumentation(editorInstanceRef.current!, docLinkVersion); }} - autoIndent={(event: any) => { + autoIndent={(event) => { autoIndent(editorInstanceRef.current!, event); }} addNotification={({ title }) => notifications.toasts.add({ title })} diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_menu_actions.ts b/src/plugins/console/public/application/containers/editor/legacy/console_menu_actions.ts index 7aa3e96464800c..f1bacd62289fb1 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_menu_actions.ts +++ b/src/plugins/console/public/application/containers/editor/legacy/console_menu_actions.ts @@ -9,7 +9,7 @@ import { getEndpointFromPosition } from '../../../../lib/autocomplete/get_endpoint_from_position'; import { SenseEditor } from '../../../models/sense_editor'; -export async function autoIndent(editor: SenseEditor, event: Event) { +export async function autoIndent(editor: SenseEditor, event: React.MouseEvent) { event.preventDefault(); await editor.autoIndent(); editor.getCoreEditor().getContainer().focus(); diff --git a/src/plugins/console/public/application/containers/settings.tsx b/src/plugins/console/public/application/containers/settings.tsx index 7028a479635f4a..1282a0b3899026 100644 --- a/src/plugins/console/public/application/containers/settings.tsx +++ b/src/plugins/console/public/application/containers/settings.tsx @@ -15,14 +15,20 @@ import { retrieveAutoCompleteInfo } from '../../lib/mappings/mappings'; import { useServicesContext, useEditorActionContext } from '../contexts'; import { DevToolsSettings, Settings as SettingsService } from '../../services'; -const getAutocompleteDiff = (newSettings: DevToolsSettings, prevSettings: DevToolsSettings) => { +const getAutocompleteDiff = ( + newSettings: DevToolsSettings, + prevSettings: DevToolsSettings +): AutocompleteOptions[] => { return Object.keys(newSettings.autocomplete).filter((key) => { // @ts-ignore return prevSettings.autocomplete[key] !== newSettings.autocomplete[key]; - }); + }) as AutocompleteOptions[]; }; -const refreshAutocompleteSettings = (settings: SettingsService, selectedSettings: any) => { +const refreshAutocompleteSettings = ( + settings: SettingsService, + selectedSettings: DevToolsSettings['autocomplete'] +) => { retrieveAutoCompleteInfo(settings, selectedSettings); }; @@ -44,12 +50,12 @@ const fetchAutocompleteSettingsIfNeeded = ( if (isSettingsChanged) { // If the user has changed one of the autocomplete settings, then we'll fetch just the // ones which have changed. - const changedSettings: any = autocompleteDiff.reduce( - (changedSettingsAccum: any, setting: string): any => { - changedSettingsAccum[setting] = newSettings.autocomplete[setting as AutocompleteOptions]; + const changedSettings: DevToolsSettings['autocomplete'] = autocompleteDiff.reduce( + (changedSettingsAccum, setting) => { + changedSettingsAccum[setting] = newSettings.autocomplete[setting]; return changedSettingsAccum; }, - {} + {} as DevToolsSettings['autocomplete'] ); retrieveAutoCompleteInfo(settings, changedSettings); } else if (isPollingChanged && newSettings.polling) { @@ -89,7 +95,7 @@ export function Settings({ onClose }: Props) { + refreshAutocompleteSettings={(selectedSettings) => refreshAutocompleteSettings(settings, selectedSettings) } settings={settings.toJSON()} diff --git a/src/plugins/console/public/application/contexts/editor_context/editor_context.tsx b/src/plugins/console/public/application/contexts/editor_context/editor_context.tsx index 32e5216a85d7ed..d4f809b5fbfb32 100644 --- a/src/plugins/console/public/application/contexts/editor_context/editor_context.tsx +++ b/src/plugins/console/public/application/contexts/editor_context/editor_context.tsx @@ -11,11 +11,11 @@ import * as editor from '../../stores/editor'; import { DevToolsSettings } from '../../../services'; import { createUseContext } from '../create_use_context'; -const EditorReadContext = createContext(null as any); -const EditorActionContext = createContext>(null as any); +const EditorReadContext = createContext(editor.initialValue); +const EditorActionContext = createContext>(() => {}); export interface EditorContextArgs { - children: any; + children: JSX.Element; settings: DevToolsSettings; } @@ -25,7 +25,7 @@ export function EditorContextProvider({ children, settings }: EditorContextArgs) settings, })); return ( - + {children} ); diff --git a/src/plugins/console/public/application/contexts/request_context.tsx b/src/plugins/console/public/application/contexts/request_context.tsx index 38ac5c7163add4..96ba1f69212b4b 100644 --- a/src/plugins/console/public/application/contexts/request_context.tsx +++ b/src/plugins/console/public/application/contexts/request_context.tsx @@ -10,8 +10,8 @@ import React, { createContext, useReducer, Dispatch } from 'react'; import { createUseContext } from './create_use_context'; import * as store from '../stores/request'; -const RequestReadContext = createContext(null as any); -const RequestActionContext = createContext>(null as any); +const RequestReadContext = createContext(store.initialValue); +const RequestActionContext = createContext>(() => {}); export function RequestContextProvider({ children }: { children: React.ReactNode }) { const [state, dispatch] = useReducer(store.reducer, store.initialValue); diff --git a/src/plugins/console/public/application/contexts/services_context.mock.ts b/src/plugins/console/public/application/contexts/services_context.mock.ts index 6c67aa37727b2f..c4ac8ca25378bf 100644 --- a/src/plugins/console/public/application/contexts/services_context.mock.ts +++ b/src/plugins/console/public/application/contexts/services_context.mock.ts @@ -9,6 +9,7 @@ import { notificationServiceMock } from '../../../../../core/public/mocks'; import { httpServiceMock } from '../../../../../core/public/mocks'; +import type { ObjectStorageClient } from '../../../common/types'; import { HistoryMock } from '../../services/history.mock'; import { SettingsMock } from '../../services/settings.mock'; import { StorageMock } from '../../services/storage.mock'; @@ -18,7 +19,7 @@ import { ContextValue } from './services_context'; export const serviceContextMock = { create: (): ContextValue => { - const storage = new StorageMock({} as any, 'test'); + const storage = new StorageMock(({} as unknown) as Storage, 'test'); const http = httpServiceMock.createSetupContract(); const api = createApi({ http }); const esHostService = createEsHostService({ api }); @@ -31,7 +32,7 @@ export const serviceContextMock = { settings: new SettingsMock(storage), history: new HistoryMock(storage), notifications: notificationServiceMock.createSetupContract(), - objectStorageClient: {} as any, + objectStorageClient: ({} as unknown) as ObjectStorageClient, }, docLinkVersion: 'NA', }; diff --git a/src/plugins/console/public/application/contexts/services_context.tsx b/src/plugins/console/public/application/contexts/services_context.tsx index 58587bf3030e2c..53c021d4d09825 100644 --- a/src/plugins/console/public/application/contexts/services_context.tsx +++ b/src/plugins/console/public/application/contexts/services_context.tsx @@ -30,10 +30,10 @@ export interface ContextValue { interface ContextProps { value: ContextValue; - children: any; + children: JSX.Element; } -const ServicesContext = createContext(null as any); +const ServicesContext = createContext(null); export function ServicesContextProvider({ children, value }: ContextProps) { useEffect(() => { @@ -46,8 +46,8 @@ export function ServicesContextProvider({ children, value }: ContextProps) { export const useServicesContext = () => { const context = useContext(ServicesContext); - if (context === undefined) { + if (context == null) { throw new Error('useServicesContext must be used inside the ServicesContextProvider.'); } - return context; + return context!; }; diff --git a/src/plugins/console/public/application/hooks/use_restore_request_from_history/restore_request_from_history.ts b/src/plugins/console/public/application/hooks/use_restore_request_from_history/restore_request_from_history.ts index f8537f9d0b3c4b..85c9cf6b9f014f 100644 --- a/src/plugins/console/public/application/hooks/use_restore_request_from_history/restore_request_from_history.ts +++ b/src/plugins/console/public/application/hooks/use_restore_request_from_history/restore_request_from_history.ts @@ -7,12 +7,10 @@ */ import RowParser from '../../../lib/row_parser'; +import { ESRequest } from '../../../types'; import { SenseEditor } from '../../models/sense_editor'; -/** - * This function is considered legacy and should not be changed or updated before we have editor - * interfaces in place (it's using a customized version of Ace directly). - */ -export function restoreRequestFromHistory(editor: SenseEditor, req: any) { + +export function restoreRequestFromHistory(editor: SenseEditor, req: ESRequest) { const coreEditor = editor.getCoreEditor(); let pos = coreEditor.getCurrentPosition(); let prefix = ''; diff --git a/src/plugins/console/public/application/hooks/use_restore_request_from_history/use_restore_request_from_history.ts b/src/plugins/console/public/application/hooks/use_restore_request_from_history/use_restore_request_from_history.ts index 310d6c67b90bc1..7c140e2b189754 100644 --- a/src/plugins/console/public/application/hooks/use_restore_request_from_history/use_restore_request_from_history.ts +++ b/src/plugins/console/public/application/hooks/use_restore_request_from_history/use_restore_request_from_history.ts @@ -8,10 +8,11 @@ import { useCallback } from 'react'; import { instance as registry } from '../../contexts/editor_context/editor_registry'; +import { ESRequest } from '../../../types'; import { restoreRequestFromHistory } from './restore_request_from_history'; export const useRestoreRequestFromHistory = () => { - return useCallback((req: any) => { + return useCallback((req: ESRequest) => { const editor = registry.getInputEditor(); restoreRequestFromHistory(editor, req); }, []); diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts b/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts index aeaa2f76816e4d..a86cfd8890a5b7 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts @@ -8,19 +8,14 @@ import { extractWarningMessages } from '../../../lib/utils'; import { XJson } from '../../../../../es_ui_shared/public'; -const { collapseLiteralStrings } = XJson; // @ts-ignore import * as es from '../../../lib/es/es'; import { BaseResponseType } from '../../../types'; -export interface EsRequestArgs { - requests: any; -} +const { collapseLiteralStrings } = XJson; -export interface ESRequestObject { - path: string; - data: any; - method: string; +export interface EsRequestArgs { + requests: Array<{ url: string; method: string; data: string[] }>; } export interface ESResponseObject { @@ -32,7 +27,7 @@ export interface ESResponseObject { } export interface ESRequestResult { - request: ESRequestObject; + request: { data: string; method: string; path: string }; response: ESResponseObject; } @@ -61,7 +56,7 @@ export function sendRequestToES(args: EsRequestArgs): Promise resolve(results); return; } - const req = requests.shift(); + const req = requests.shift()!; const esPath = req.url; const esMethod = req.method; let esData = collapseLiteralStrings(req.data.join('\n')); @@ -71,7 +66,7 @@ export function sendRequestToES(args: EsRequestArgs): Promise const startTime = Date.now(); es.send(esMethod, esPath, esData).always( - (dataOrjqXHR: any, textStatus: string, jqXhrORerrorThrown: any) => { + (dataOrjqXHR, textStatus: string, jqXhrORerrorThrown) => { if (reqId !== CURRENT_REQ_ID) { return; } diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/track.ts b/src/plugins/console/public/application/hooks/use_send_current_request_to_es/track.ts index c9b8cdec66a96c..f5deefd6271878 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/track.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request_to_es/track.ts @@ -10,7 +10,11 @@ import { SenseEditor } from '../../models/sense_editor'; import { getEndpointFromPosition } from '../../../lib/autocomplete/get_endpoint_from_position'; import { MetricsTracker } from '../../../types'; -export const track = (requests: any[], editor: SenseEditor, trackUiMetric: MetricsTracker) => { +export const track = ( + requests: Array<{ method: string }>, + editor: SenseEditor, + trackUiMetric: MetricsTracker +) => { const coreEditor = editor.getCoreEditor(); // `getEndpointFromPosition` gets values from the server-side generated JSON files which // are a combination of JS, automatically generated JSON and manual overrides. That means diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.test.tsx b/src/plugins/console/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.test.tsx index e1fc323cd2d9dc..63b57883169564 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.test.tsx +++ b/src/plugins/console/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.test.tsx @@ -26,8 +26,8 @@ import { useSendCurrentRequestToES } from './use_send_current_request_to_es'; describe('useSendCurrentRequestToES', () => { let mockContextValue: ContextValue; - let dispatch: (...args: any[]) => void; - const contexts = ({ children }: { children?: any }) => ( + let dispatch: (...args: unknown[]) => void; + const contexts = ({ children }: { children: JSX.Element }) => ( {children} ); diff --git a/src/plugins/console/public/application/hooks/use_set_input_editor.ts b/src/plugins/console/public/application/hooks/use_set_input_editor.ts index 2c6dc101bee0e6..c01dbbb0d27986 100644 --- a/src/plugins/console/public/application/hooks/use_set_input_editor.ts +++ b/src/plugins/console/public/application/hooks/use_set_input_editor.ts @@ -9,12 +9,13 @@ import { useCallback } from 'react'; import { useEditorActionContext } from '../contexts/editor_context'; import { instance as registry } from '../contexts/editor_context/editor_registry'; +import { SenseEditor } from '../models'; export const useSetInputEditor = () => { const dispatch = useEditorActionContext(); return useCallback( - (editor: any) => { + (editor: SenseEditor) => { dispatch({ type: 'setInputEditor', payload: editor }); registry.setInputEditor(editor); }, diff --git a/src/plugins/console/public/application/index.tsx b/src/plugins/console/public/application/index.tsx index 1237348af215ce..0b41095f8cc19d 100644 --- a/src/plugins/console/public/application/index.tsx +++ b/src/plugins/console/public/application/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HttpSetup, NotificationsSetup } from 'src/core/public'; +import { HttpSetup, NotificationsSetup, I18nStart } from 'src/core/public'; import { ServicesContextProvider, EditorContextProvider, RequestContextProvider } from './contexts'; import { Main } from './containers'; import { createStorage, createHistory, createSettings } from '../services'; @@ -20,7 +20,7 @@ import { createApi, createEsHostService } from './lib'; export interface BootDependencies { http: HttpSetup; docLinkVersion: string; - I18nContext: any; + I18nContext: I18nStart['Context']; notifications: NotificationsSetup; usageCollection?: UsageCollectionSetup; element: HTMLElement; diff --git a/src/plugins/console/public/application/models/legacy_core_editor/create_readonly.ts b/src/plugins/console/public/application/models/legacy_core_editor/create_readonly.ts index 43da86773d294a..dc63f0dcd480c6 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/create_readonly.ts +++ b/src/plugins/console/public/application/models/legacy_core_editor/create_readonly.ts @@ -13,7 +13,7 @@ import * as OutputMode from './mode/output'; import smartResize from './smart_resize'; export interface CustomAceEditor extends ace.Editor { - update: (text: string, mode?: any, cb?: () => void) => void; + update: (text: string, mode?: unknown, cb?: () => void) => void; append: (text: string, foldPrevious?: boolean, cb?: () => void) => void; } @@ -28,9 +28,9 @@ export function createReadOnlyAceEditor(element: HTMLElement): CustomAceEditor { output.$blockScrolling = Infinity; output.resize = smartResize(output); - output.update = (val: string, mode?: any, cb?: () => void) => { + output.update = (val: string, mode?: unknown, cb?: () => void) => { if (typeof mode === 'function') { - cb = mode; + cb = mode as () => void; mode = void 0; } @@ -65,7 +65,7 @@ export function createReadOnlyAceEditor(element: HTMLElement): CustomAceEditor { (function setupSession(session) { session.setMode('ace/mode/text'); - (session as any).setFoldStyle('markbeginend'); + ((session as unknown) as { setFoldStyle: (v: string) => void }).setFoldStyle('markbeginend'); session.setTabSize(2); session.setUseWrapMode(true); })(output.getSession()); diff --git a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts index c39d4549de0b68..0ee15f7a559ae2 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts +++ b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts @@ -13,7 +13,7 @@ jest.mock('./mode/worker', () => { // @ts-ignore window.Worker = function () { this.postMessage = () => {}; - (this as any).terminate = () => {}; + ((this as unknown) as { terminate: () => void }).terminate = () => {}; }; // @ts-ignore diff --git a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts index eab5ac16d17db0..fa118532aa52d0 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts +++ b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts @@ -31,8 +31,8 @@ const rangeToAceRange = ({ start, end }: Range) => new _AceRange(start.lineNumber - 1, start.column - 1, end.lineNumber - 1, end.column - 1); export class LegacyCoreEditor implements CoreEditor { - private _aceOnPaste: any; - $actions: any; + private _aceOnPaste: Function; + $actions: JQuery; resize: () => void; constructor(private readonly editor: IAceEditor, actions: HTMLElement) { @@ -41,7 +41,9 @@ export class LegacyCoreEditor implements CoreEditor { const session = this.editor.getSession(); session.setMode(new InputMode.Mode()); - (session as any).setFoldStyle('markbeginend'); + ((session as unknown) as { setFoldStyle: (style: string) => void }).setFoldStyle( + 'markbeginend' + ); session.setTabSize(2); session.setUseWrapMode(true); @@ -72,7 +74,7 @@ export class LegacyCoreEditor implements CoreEditor { // torn down, e.g. by closing the History tab, and we don't need to do anything further. if (session.bgTokenizer) { // Wait until the bgTokenizer is done running before executing the callback. - if ((session.bgTokenizer as any).running) { + if (((session.bgTokenizer as unknown) as { running: boolean }).running) { setTimeout(check, checkInterval); } else { resolve(); @@ -197,7 +199,7 @@ export class LegacyCoreEditor implements CoreEditor { .addMarker(rangeToAceRange(range), 'ace_snippet-marker', 'fullLine', false); } - removeMarker(ref: any) { + removeMarker(ref: number) { this.editor.getSession().removeMarker(ref); } @@ -222,8 +224,10 @@ export class LegacyCoreEditor implements CoreEditor { } isCompleterActive() { - // Secrets of the arcane here. - return Boolean((this.editor as any).completer && (this.editor as any).completer.activated); + return Boolean( + ((this.editor as unknown) as { completer: { activated: unknown } }).completer && + ((this.editor as unknown) as { completer: { activated: unknown } }).completer.activated + ); } private forceRetokenize() { @@ -250,7 +254,7 @@ export class LegacyCoreEditor implements CoreEditor { this._aceOnPaste.call(this.editor, text); } - private setActionsBar = (value?: any, topOrBottom: 'top' | 'bottom' = 'top') => { + private setActionsBar = (value: number | null, topOrBottom: 'top' | 'bottom' = 'top') => { if (value === null) { this.$actions.css('visibility', 'hidden'); } else { @@ -271,7 +275,7 @@ export class LegacyCoreEditor implements CoreEditor { }; private hideActionsBar = () => { - this.setActionsBar(); + this.setActionsBar(null); }; execCommand(cmd: string) { @@ -295,7 +299,7 @@ export class LegacyCoreEditor implements CoreEditor { }); } - legacyUpdateUI(range: any) { + legacyUpdateUI(range: Range) { if (!this.$actions) { return; } @@ -360,14 +364,19 @@ export class LegacyCoreEditor implements CoreEditor { ace.define( 'ace/autocomplete/text_completer', ['require', 'exports', 'module'], - function (require: any, exports: any) { - exports.getCompletions = function ( - innerEditor: any, - session: any, - pos: any, - prefix: any, - callback: any - ) { + function ( + require: unknown, + exports: { + getCompletions: ( + innerEditor: unknown, + session: unknown, + pos: unknown, + prefix: unknown, + callback: (e: null | Error, values: string[]) => void + ) => void; + } + ) { + exports.getCompletions = function (innerEditor, session, pos, prefix, callback) { callback(null, []); }; } @@ -387,7 +396,7 @@ export class LegacyCoreEditor implements CoreEditor { DO_NOT_USE_2: IAceEditSession, pos: { row: number; column: number }, prefix: string, - callback: (...args: any[]) => void + callback: (...args: unknown[]) => void ) => { const position: Position = { lineNumber: pos.row + 1, diff --git a/src/plugins/console/public/application/models/legacy_core_editor/smart_resize.ts b/src/plugins/console/public/application/models/legacy_core_editor/smart_resize.ts index fdbaedce091873..83d7cd15e60eb6 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/smart_resize.ts +++ b/src/plugins/console/public/application/models/legacy_core_editor/smart_resize.ts @@ -7,9 +7,10 @@ */ import { get, throttle } from 'lodash'; +import type { Editor } from 'brace'; // eslint-disable-next-line import/no-default-export -export default function (editor: any) { +export default function (editor: Editor) { const resize = editor.resize; const throttledResize = throttle(() => { diff --git a/src/plugins/console/public/application/models/sense_editor/curl.ts b/src/plugins/console/public/application/models/sense_editor/curl.ts index 299ccd0a1f6a60..74cbebf051d03c 100644 --- a/src/plugins/console/public/application/models/sense_editor/curl.ts +++ b/src/plugins/console/public/application/models/sense_editor/curl.ts @@ -25,7 +25,7 @@ export function detectCURL(text: string) { export function parseCURL(text: string) { let state = 'NONE'; const out = []; - let body: any[] = []; + let body: string[] = []; let line = ''; const lines = text.trim().split('\n'); let matches; @@ -62,7 +62,7 @@ export function parseCURL(text: string) { } function unescapeLastBodyEl() { - const str = body.pop().replace(/\\([\\"'])/g, '$1'); + const str = body.pop()!.replace(/\\([\\"'])/g, '$1'); body.push(str); } diff --git a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts index d6dd74f0fefe3e..0f65d3f1e33e27 100644 --- a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts +++ b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts @@ -7,8 +7,10 @@ */ import _ from 'lodash'; -import RowParser from '../../../lib/row_parser'; + import { XJson } from '../../../../../es_ui_shared/public'; + +import RowParser from '../../../lib/row_parser'; import * as utils from '../../../lib/utils'; // @ts-ignore @@ -16,22 +18,20 @@ import * as es from '../../../lib/es/es'; import { CoreEditor, Position, Range } from '../../../types'; import { createTokenIterator } from '../../factories'; - -import Autocomplete from '../../../lib/autocomplete/autocomplete'; +import createAutocompleter from '../../../lib/autocomplete/autocomplete'; const { collapseLiteralStrings } = XJson; export class SenseEditor { - currentReqRange: (Range & { markerRef: any }) | null; - parser: any; + currentReqRange: (Range & { markerRef: unknown }) | null; + parser: RowParser; - // @ts-ignore - private readonly autocomplete: any; + private readonly autocomplete: ReturnType; constructor(private readonly coreEditor: CoreEditor) { this.currentReqRange = null; this.parser = new RowParser(this.coreEditor); - this.autocomplete = new (Autocomplete as any)({ + this.autocomplete = createAutocompleter({ coreEditor, parser: this.parser, }); @@ -114,7 +114,10 @@ export class SenseEditor { return this.coreEditor.setValue(data, reTokenizeAll); }; - replaceRequestRange = (newRequest: any, requestRange: Range) => { + replaceRequestRange = ( + newRequest: { method: string; url: string; data: string | string[] }, + requestRange: Range + ) => { const text = utils.textFromRequest(newRequest); if (requestRange) { this.coreEditor.replaceRange(requestRange, text); @@ -207,12 +210,12 @@ export class SenseEditor { const request: { method: string; data: string[]; - url: string | null; + url: string; range: Range; } = { method: '', data: [], - url: null, + url: '', range, }; @@ -284,7 +287,7 @@ export class SenseEditor { return []; } - const requests: any = []; + const requests: unknown[] = []; let rangeStartCursor = expandedRange.start.lineNumber; const endLineNumber = expandedRange.end.lineNumber; diff --git a/src/plugins/console/public/application/stores/editor.ts b/src/plugins/console/public/application/stores/editor.ts index 1de4712d9640f3..3fdb0c3fd3422a 100644 --- a/src/plugins/console/public/application/stores/editor.ts +++ b/src/plugins/console/public/application/stores/editor.ts @@ -9,8 +9,9 @@ import { Reducer } from 'react'; import { produce } from 'immer'; import { identity } from 'fp-ts/lib/function'; -import { DevToolsSettings } from '../../services'; +import { DevToolsSettings, DEFAULT_SETTINGS } from '../../services'; import { TextObject } from '../../../common/text_object'; +import { SenseEditor } from '../models'; export interface Store { ready: boolean; @@ -21,15 +22,15 @@ export interface Store { export const initialValue: Store = produce( { ready: false, - settings: null as any, + settings: DEFAULT_SETTINGS, currentTextObject: null, }, identity ); export type Action = - | { type: 'setInputEditor'; payload: any } - | { type: 'setCurrentTextObject'; payload: any } + | { type: 'setInputEditor'; payload: SenseEditor } + | { type: 'setCurrentTextObject'; payload: TextObject } | { type: 'updateSettings'; payload: DevToolsSettings }; export const reducer: Reducer = (state, action) => diff --git a/src/plugins/console/public/lib/ace_token_provider/token_provider.ts b/src/plugins/console/public/lib/ace_token_provider/token_provider.ts index ac518d43ddf256..692528fb8bced8 100644 --- a/src/plugins/console/public/lib/ace_token_provider/token_provider.ts +++ b/src/plugins/console/public/lib/ace_token_provider/token_provider.ts @@ -63,7 +63,7 @@ export class AceTokensProvider implements TokensProvider { return null; } - const tokens: TokenInfo[] = this.session.getTokens(lineNumber - 1) as any; + const tokens = (this.session.getTokens(lineNumber - 1) as unknown) as TokenInfo[]; if (!tokens || !tokens.length) { // We are inside of the document but have no tokens for this line. Return an empty // array to represent this empty line. @@ -74,7 +74,7 @@ export class AceTokensProvider implements TokensProvider { } getTokenAt(pos: Position): Token | null { - const tokens: TokenInfo[] = this.session.getTokens(pos.lineNumber - 1) as any; + const tokens = (this.session.getTokens(pos.lineNumber - 1) as unknown) as TokenInfo[]; if (tokens) { return extractTokenFromAceTokenRow(pos.lineNumber, pos.column, tokens); } diff --git a/src/plugins/console/public/lib/autocomplete/autocomplete.ts b/src/plugins/console/public/lib/autocomplete/autocomplete.ts index e46b5cda3c3a99..d89a9f3d2e5e2b 100644 --- a/src/plugins/console/public/lib/autocomplete/autocomplete.ts +++ b/src/plugins/console/public/lib/autocomplete/autocomplete.ts @@ -18,19 +18,21 @@ import { // @ts-ignore } from '../kb/kb'; +import { createTokenIterator } from '../../application/factories'; +import { Position, Token, Range, CoreEditor } from '../../types'; +import type RowParser from '../row_parser'; + import * as utils from '../utils'; // @ts-ignore import { populateContext } from './engine'; +import { AutoCompleteContext, ResultTerm } from './types'; // @ts-ignore import { URL_PATH_END_MARKER } from './components/index'; -import { createTokenIterator } from '../../application/factories'; -import { Position, Token, Range, CoreEditor } from '../../types'; +let lastEvaluatedToken: Token | null = null; -let lastEvaluatedToken: any = null; - -function isUrlParamsToken(token: any) { +function isUrlParamsToken(token: { type: string } | null) { switch ((token || {}).type) { case 'url.param': case 'url.equal': @@ -54,7 +56,7 @@ function isUrlParamsToken(token: any) { export function getCurrentMethodAndTokenPaths( editor: CoreEditor, pos: Position, - parser: any, + parser: RowParser, forceEndOfUrl?: boolean /* Flag for indicating whether we want to avoid early escape optimization. */ ) { const tokenIter = createTokenIterator({ @@ -62,8 +64,8 @@ export function getCurrentMethodAndTokenPaths( position: pos, }); const startPos = pos; - let bodyTokenPath: any = []; - const ret: any = {}; + let bodyTokenPath: string[] | null = []; + const ret: AutoCompleteContext = {}; const STATES = { looking_for_key: 0, // looking for a key but without jumping over anything but white space and colon. @@ -210,7 +212,12 @@ export function getCurrentMethodAndTokenPaths( ret.urlParamsTokenPath = null; ret.requestStartRow = tokenIter.getCurrentPosition().lineNumber; - let curUrlPart: any; + let curUrlPart: + | null + | string + | Array> + | undefined + | Record; while (t && isUrlParamsToken(t)) { switch (t.type) { @@ -240,7 +247,7 @@ export function getCurrentMethodAndTokenPaths( if (!ret.urlParamsTokenPath) { ret.urlParamsTokenPath = []; } - ret.urlParamsTokenPath.unshift(curUrlPart || {}); + ret.urlParamsTokenPath.unshift((curUrlPart as Record) || {}); curUrlPart = null; break; } @@ -268,7 +275,7 @@ export function getCurrentMethodAndTokenPaths( break; case 'url.slash': if (curUrlPart) { - ret.urlTokenPath.unshift(curUrlPart); + ret.urlTokenPath.unshift(curUrlPart as string); curUrlPart = null; } break; @@ -277,7 +284,7 @@ export function getCurrentMethodAndTokenPaths( } if (curUrlPart) { - ret.urlTokenPath.unshift(curUrlPart); + ret.urlTokenPath.unshift(curUrlPart as string); } if (!ret.bodyTokenPath && !ret.urlParamsTokenPath) { @@ -297,9 +304,15 @@ export function getCurrentMethodAndTokenPaths( } // eslint-disable-next-line -export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEditor; parser: any }) { +export default function ({ + coreEditor: editor, + parser, +}: { + coreEditor: CoreEditor; + parser: RowParser; +}) { function isUrlPathToken(token: Token | null) { - switch ((token || ({} as any)).type) { + switch ((token || ({} as Token)).type) { case 'url.slash': case 'url.comma': case 'url.part': @@ -309,8 +322,12 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito } } - function addMetaToTermsList(list: any, meta: any, template?: string) { - return _.map(list, function (t: any) { + function addMetaToTermsList( + list: unknown[], + meta: unknown, + template?: string + ): Array<{ meta: unknown; template: unknown; name?: string }> { + return _.map(list, function (t) { if (typeof t !== 'object') { t = { name: t }; } @@ -318,8 +335,13 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito }); } - function applyTerm(term: any) { - const context = term.context; + function applyTerm(term: { + value?: string; + context?: AutoCompleteContext; + template?: { __raw: boolean; value: string }; + insertValue?: string; + }) { + const context = term.context!; // make sure we get up to date replacement info. addReplacementInfoToContext(context, editor.getCurrentPosition(), term.insertValue); @@ -346,7 +368,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito } else { indentedTemplateLines = utils.jsonToString(term.template, true).split('\n'); } - let currentIndentation = editor.getLineValue(context.rangeToReplace.start.lineNumber); + let currentIndentation = editor.getLineValue(context.rangeToReplace!.start.lineNumber); currentIndentation = currentIndentation.match(/^\s*/)![0]; for ( let i = 1; @@ -374,8 +396,8 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito // disable listening to the changes we are making. editor.off('changeSelection', editorChangeListener); - if (context.rangeToReplace.start.column !== context.rangeToReplace.end.column) { - editor.replace(context.rangeToReplace, valueToInsert); + if (context.rangeToReplace!.start.column !== context.rangeToReplace!.end.column) { + editor.replace(context.rangeToReplace!, valueToInsert); } else { editor.insert(valueToInsert); } @@ -384,12 +406,12 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito // go back to see whether we have one of ( : { & [ do not require a comma. All the rest do. let newPos = { - lineNumber: context.rangeToReplace.start.lineNumber, + lineNumber: context.rangeToReplace!.start.lineNumber, column: - context.rangeToReplace.start.column + + context.rangeToReplace!.start.column + termAsString.length + - context.prefixToAdd.length + - (templateInserted ? 0 : context.suffixToAdd.length), + context.prefixToAdd!.length + + (templateInserted ? 0 : context.suffixToAdd!.length), }; const tokenIter = createTokenIterator({ @@ -406,7 +428,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito break; case 'punctuation.colon': nonEmptyToken = parser.nextNonEmptyToken(tokenIter); - if ((nonEmptyToken || ({} as any)).type === 'paren.lparen') { + if ((nonEmptyToken || ({} as Token)).type === 'paren.lparen') { nonEmptyToken = parser.nextNonEmptyToken(tokenIter); newPos = tokenIter.getCurrentPosition(); if (nonEmptyToken && nonEmptyToken.value.indexOf('"') === 0) { @@ -429,7 +451,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito function getAutoCompleteContext(ctxEditor: CoreEditor, pos: Position) { // deduces all the parameters need to position and insert the auto complete - const context: any = { + const context: AutoCompleteContext = { autoCompleteSet: null, // instructions for what can be here endpoint: null, urlPath: null, @@ -501,7 +523,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito case 'whitespace': t = parser.prevNonEmptyToken(tokenIter); - switch ((t || ({} as any)).type) { + switch ((t || ({} as Token)).type) { case 'method': // we moved one back return 'path'; @@ -552,7 +574,11 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito return null; } - function addReplacementInfoToContext(context: any, pos: Position, replacingTerm?: any) { + function addReplacementInfoToContext( + context: AutoCompleteContext, + pos: Position, + replacingTerm?: unknown + ) { // extract the initial value, rangeToReplace & textBoxPosition // Scenarios for current token: @@ -605,7 +631,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito default: if (replacingTerm && context.updatedForToken.value === replacingTerm) { context.rangeToReplace = { - start: { lineNumber: pos.lineNumber, column: anchorToken.column }, + start: { lineNumber: pos.lineNumber, column: anchorToken.position.column }, end: { lineNumber: pos.lineNumber, column: @@ -645,7 +671,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito } } - function addBodyPrefixSuffixToContext(context: any) { + function addBodyPrefixSuffixToContext(context: AutoCompleteContext) { // Figure out what happens next to the token to see whether it needs trailing commas etc. // Templates will be used if not destroying existing structure. @@ -680,9 +706,9 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito } context.addTemplate = true; // extend range to replace to include all up to token - context.rangeToReplace.end.lineNumber = tokenIter.getCurrentTokenLineNumber(); - context.rangeToReplace.end.column = - tokenIter.getCurrentTokenColumn() + nonEmptyToken.value.length; + context.rangeToReplace!.end.lineNumber = tokenIter.getCurrentTokenLineNumber() as number; + context.rangeToReplace!.end.column = + (tokenIter.getCurrentTokenColumn() as number) + nonEmptyToken.value.length; // move one more time to check if we need a trailing comma nonEmptyToken = parser.nextNonEmptyToken(tokenIter); @@ -711,11 +737,11 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito insertingRelativeToToken = 0; } else { const pos = editor.getCurrentPosition(); - if (pos.column === context.updatedForToken.position.column) { + if (pos.column === context.updatedForToken!.position.column) { insertingRelativeToToken = -1; } else if ( pos.column < - context.updatedForToken.position.column + context.updatedForToken.value.length + context.updatedForToken!.position.column + context.updatedForToken!.value.length ) { insertingRelativeToToken = 0; } else { @@ -743,12 +769,12 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito return context; } - function addUrlParamsPrefixSuffixToContext(context: any) { + function addUrlParamsPrefixSuffixToContext(context: AutoCompleteContext) { context.prefixToAdd = ''; context.suffixToAdd = ''; } - function addMethodPrefixSuffixToContext(context: any) { + function addMethodPrefixSuffixToContext(context: AutoCompleteContext) { context.prefixToAdd = ''; context.suffixToAdd = ''; const tokenIter = createTokenIterator({ editor, position: editor.getCurrentPosition() }); @@ -761,12 +787,12 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito } } - function addPathPrefixSuffixToContext(context: any) { + function addPathPrefixSuffixToContext(context: AutoCompleteContext) { context.prefixToAdd = ''; context.suffixToAdd = ''; } - function addMethodAutoCompleteSetToContext(context: any) { + function addMethodAutoCompleteSetToContext(context: AutoCompleteContext) { context.autoCompleteSet = ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'].map((m, i) => ({ name: m, score: -i, @@ -774,7 +800,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito })); } - function addPathAutoCompleteSetToContext(context: any, pos: Position) { + function addPathAutoCompleteSetToContext(context: AutoCompleteContext, pos: Position) { const ret = getCurrentMethodAndTokenPaths(editor, pos, parser); context.method = ret.method; context.token = ret.token; @@ -783,10 +809,10 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito const components = getTopLevelUrlCompleteComponents(context.method); populateContext(ret.urlTokenPath, context, editor, true, components); - context.autoCompleteSet = addMetaToTermsList(context.autoCompleteSet, 'endpoint'); + context.autoCompleteSet = addMetaToTermsList(context.autoCompleteSet!, 'endpoint'); } - function addUrlParamsAutoCompleteSetToContext(context: any, pos: Position) { + function addUrlParamsAutoCompleteSetToContext(context: AutoCompleteContext, pos: Position) { const ret = getCurrentMethodAndTokenPaths(editor, pos, parser); context.method = ret.method; context.otherTokenValues = ret.otherTokenValues; @@ -813,7 +839,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito // zero length tokenPath is true return context; } - let tokenPath: any[] = []; + let tokenPath: string[] = []; const currentParam = ret.urlParamsTokenPath.pop(); if (currentParam) { tokenPath = Object.keys(currentParam); // single key object @@ -830,7 +856,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito return context; } - function addBodyAutoCompleteSetToContext(context: any, pos: Position) { + function addBodyAutoCompleteSetToContext(context: AutoCompleteContext, pos: Position) { const ret = getCurrentMethodAndTokenPaths(editor, pos, parser); context.method = ret.method; context.otherTokenValues = ret.otherTokenValues; @@ -859,7 +885,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito // needed for scope linking + global term resolving context.endpointComponentResolver = getEndpointBodyCompleteComponents; context.globalComponentResolver = getGlobalAutocompleteComponents; - let components; + let components: unknown; if (context.endpoint) { components = context.endpoint.bodyAutocompleteRootComponents; } else { @@ -935,15 +961,19 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito } } - function getCompletions(position: Position, prefix: string, callback: (...args: any[]) => void) { + function getCompletions( + position: Position, + prefix: string, + callback: (e: Error | null, result: ResultTerm[] | null) => void + ) { try { const context = getAutoCompleteContext(editor, position); if (!context) { callback(null, []); } else { const terms = _.map( - context.autoCompleteSet.filter((term: any) => Boolean(term) && term.name != null), - function (term: any) { + context.autoCompleteSet!.filter((term) => Boolean(term) && term.name != null), + function (term) { if (typeof term !== 'object') { term = { name: term, @@ -951,7 +981,13 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito } else { term = _.clone(term); } - const defaults: any = { + const defaults: { + value?: string; + meta: string; + score: number; + context: AutoCompleteContext; + completer?: { insertMatch: (v: unknown) => void }; + } = { value: term.name, meta: 'API', score: 0, @@ -969,7 +1005,10 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito } ); - terms.sort(function (t1: any, t2: any) { + terms.sort(function ( + t1: { score: number; name?: string }, + t2: { score: number; name?: string } + ) { /* score sorts from high to low */ if (t1.score > t2.score) { return -1; @@ -978,7 +1017,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito return 1; } /* names sort from low to high */ - if (t1.name < t2.name) { + if (t1.name! < t2.name!) { return -1; } if (t1.name === t2.name) { @@ -989,7 +1028,7 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito callback( null, - _.map(terms, function (t: any, i: any) { + _.map(terms, function (t, i) { t.insertValue = t.insertValue || t.value; t.value = '' + t.value; // normalize to strings t.score = -i; @@ -1010,8 +1049,13 @@ export default function ({ coreEditor: editor, parser }: { coreEditor: CoreEdito getCompletions, // TODO: This needs to be cleaned up _test: { - getCompletions: (_editor: any, _editSession: any, pos: any, prefix: any, callback: any) => - getCompletions(pos, prefix, callback), + getCompletions: ( + _editor: unknown, + _editSession: unknown, + pos: Position, + prefix: string, + callback: (e: Error | null, result: ResultTerm[] | null) => void + ) => getCompletions(pos, prefix, callback), addReplacementInfoToContext, addChangeListener: () => editor.on('changeSelection', editorChangeListener), removeChangeListener: () => editor.off('changeSelection', editorChangeListener), diff --git a/src/plugins/console/public/lib/autocomplete/components/full_request_component.ts b/src/plugins/console/public/lib/autocomplete/components/full_request_component.ts index 935e3622bde042..64aefa7b4a1217 100644 --- a/src/plugins/console/public/lib/autocomplete/components/full_request_component.ts +++ b/src/plugins/console/public/lib/autocomplete/components/full_request_component.ts @@ -11,7 +11,7 @@ import { ConstantComponent } from './constant_component'; export class FullRequestComponent extends ConstantComponent { private readonly name: string; - constructor(name: string, parent: any, private readonly template: string) { + constructor(name: string, parent: unknown, private readonly template: string) { super(name, parent); this.name = name; } diff --git a/src/plugins/console/public/lib/autocomplete/get_endpoint_from_position.ts b/src/plugins/console/public/lib/autocomplete/get_endpoint_from_position.ts index 849123bc68a71e..fe24492df0e7b0 100644 --- a/src/plugins/console/public/lib/autocomplete/get_endpoint_from_position.ts +++ b/src/plugins/console/public/lib/autocomplete/get_endpoint_from_position.ts @@ -8,13 +8,14 @@ import { CoreEditor, Position } from '../../types'; import { getCurrentMethodAndTokenPaths } from './autocomplete'; +import type RowParser from '../row_parser'; // @ts-ignore import { getTopLevelUrlCompleteComponents } from '../kb/kb'; // @ts-ignore import { populateContext } from './engine'; -export function getEndpointFromPosition(editor: CoreEditor, pos: Position, parser: any) { +export function getEndpointFromPosition(editor: CoreEditor, pos: Position, parser: RowParser) { const lineValue = editor.getLineValue(pos.lineNumber); const context = { ...getCurrentMethodAndTokenPaths( diff --git a/src/plugins/console/public/lib/autocomplete/types.ts b/src/plugins/console/public/lib/autocomplete/types.ts new file mode 100644 index 00000000000000..33c543f43be9ec --- /dev/null +++ b/src/plugins/console/public/lib/autocomplete/types.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreEditor, Range, Token } from '../../types'; + +export interface ResultTerm { + context?: AutoCompleteContext; + insertValue?: string; + name?: string; + value?: string; +} + +export interface AutoCompleteContext { + autoCompleteSet?: null | ResultTerm[]; + endpoint?: null | { + paramsAutocomplete: { + getTopLevelComponents: (method?: string | null) => unknown; + }; + bodyAutocompleteRootComponents: unknown; + id?: string; + documentation?: string; + }; + urlPath?: null | unknown; + urlParamsTokenPath?: Array> | null; + method?: string | null; + token?: Token; + activeScheme?: unknown; + replacingToken?: boolean; + rangeToReplace?: Range; + autoCompleteType?: null | string; + editor?: CoreEditor; + + /** + * The tokenized user input that prompted the current autocomplete at the cursor. This can be out of sync with + * the input that is currently being displayed in the editor. + */ + createdWithToken?: Token | null; + + /** + * The tokenized user input that is currently being displayed at the cursor in the editor when the user accepted + * the autocomplete suggestion. + */ + updatedForToken?: Token | null; + + addTemplate?: unknown; + prefixToAdd?: string; + suffixToAdd?: string; + textBoxPosition?: { lineNumber: number; column: number }; + urlTokenPath?: string[]; + otherTokenValues?: string; + requestStartRow?: number | null; + bodyTokenPath?: string[] | null; + endpointComponentResolver?: unknown; + globalComponentResolver?: unknown; + documentation?: string; +} diff --git a/src/plugins/console/public/lib/es/es.ts b/src/plugins/console/public/lib/es/es.ts index 03ee218fa2e1d0..dffc2c9682cf2f 100644 --- a/src/plugins/console/public/lib/es/es.ts +++ b/src/plugins/console/public/lib/es/es.ts @@ -19,7 +19,7 @@ export function getVersion() { return esVersion; } -export function getContentType(body: any) { +export function getContentType(body: unknown) { if (!body) return; return 'application/json'; } @@ -27,7 +27,7 @@ export function getContentType(body: any) { export function send( method: string, path: string, - data: any, + data: string | object, { asSystemRequest }: SendOptions = {} ) { const wrappedDfd = $.Deferred(); @@ -47,10 +47,10 @@ export function send( }; $.ajax(options).then( - (responseData: any, textStatus: string, jqXHR: any) => { + (responseData, textStatus: string, jqXHR: unknown) => { wrappedDfd.resolveWith({}, [responseData, textStatus, jqXHR]); }, - ((jqXHR: any, textStatus: string, errorThrown: Error) => { + ((jqXHR: { status: number; responseText: string }, textStatus: string, errorThrown: Error) => { if (jqXHR.status === 0) { jqXHR.responseText = "\n\nFailed to connect to Console's backend.\nPlease check the Kibana server is up and running"; diff --git a/src/plugins/console/public/lib/row_parser.ts b/src/plugins/console/public/lib/row_parser.ts index 5f8fb08ca1d6f8..e18bf6ac6446b8 100644 --- a/src/plugins/console/public/lib/row_parser.ts +++ b/src/plugins/console/public/lib/row_parser.ts @@ -75,7 +75,7 @@ export default class RowParser { return MODE.REQUEST_START; } - rowPredicate(lineNumber: number | undefined, editor: CoreEditor, value: any) { + rowPredicate(lineNumber: number | undefined, editor: CoreEditor, value: number) { const mode = this.getRowParseMode(lineNumber); // eslint-disable-next-line no-bitwise return (mode & value) > 0; diff --git a/src/plugins/console/public/lib/token_iterator/token_iterator.test.ts b/src/plugins/console/public/lib/token_iterator/token_iterator.test.ts index bc28e1d9e1a034..6b2f556f82a0e2 100644 --- a/src/plugins/console/public/lib/token_iterator/token_iterator.test.ts +++ b/src/plugins/console/public/lib/token_iterator/token_iterator.test.ts @@ -15,7 +15,7 @@ const mockTokensProviderFactory = (tokenMtx: Token[][]): TokensProvider => { return tokenMtx[lineNumber - 1] || null; }, getTokenAt(pos: Position): Token | null { - return null as any; + return null; }, }; }; diff --git a/src/plugins/console/public/lib/utils/index.ts b/src/plugins/console/public/lib/utils/index.ts index 71b305807e61d4..8b8974f4e2f0d5 100644 --- a/src/plugins/console/public/lib/utils/index.ts +++ b/src/plugins/console/public/lib/utils/index.ts @@ -11,7 +11,7 @@ import { XJson } from '../../../../es_ui_shared/public'; const { collapseLiteralStrings, expandLiteralStrings } = XJson; -export function textFromRequest(request: any) { +export function textFromRequest(request: { method: string; url: string; data: string | string[] }) { let data = request.data; if (typeof data !== 'string') { data = data.join('\n'); @@ -19,7 +19,7 @@ export function textFromRequest(request: any) { return request.method + ' ' + request.url + '\n' + data; } -export function jsonToString(data: any, indent: boolean) { +export function jsonToString(data: object, indent: boolean) { return JSON.stringify(data, null, indent ? 2 : 0); } diff --git a/src/plugins/console/public/services/history.ts b/src/plugins/console/public/services/history.ts index 1dd18f8672a4b3..ee1e97ceb386e2 100644 --- a/src/plugins/console/public/services/history.ts +++ b/src/plugins/console/public/services/history.ts @@ -35,12 +35,12 @@ export class History { // be triggered from different places in the app. The alternative would be to store // this in state so that we hook into the React model, but it would require loading history // every time the application starts even if a user is not going to view history. - change(listener: (reqs: any[]) => void) { + change(listener: (reqs: unknown[]) => void) { const subscription = this.changeEmitter.subscribe(listener); return () => subscription.unsubscribe(); } - addToHistory(endpoint: string, method: string, data: any) { + addToHistory(endpoint: string, method: string, data?: string) { const keys = this.getHistoryKeys(); keys.splice(0, MAX_NUMBER_OF_HISTORY_ITEMS); // only maintain most recent X; keys.forEach((key) => { @@ -59,7 +59,7 @@ export class History { this.changeEmitter.next(this.getHistory()); } - updateCurrentState(content: any) { + updateCurrentState(content: string) { const timestamp = new Date().getTime(); this.storage.set('editor_state', { time: timestamp, diff --git a/src/plugins/console/public/services/index.ts b/src/plugins/console/public/services/index.ts index b6bcafc974b935..2b1e64525d0f9f 100644 --- a/src/plugins/console/public/services/index.ts +++ b/src/plugins/console/public/services/index.ts @@ -8,4 +8,4 @@ export { createHistory, History } from './history'; export { createStorage, Storage, StorageKeys } from './storage'; -export { createSettings, Settings, DevToolsSettings } from './settings'; +export { createSettings, Settings, DevToolsSettings, DEFAULT_SETTINGS } from './settings'; diff --git a/src/plugins/console/public/services/settings.ts b/src/plugins/console/public/services/settings.ts index 8f142e876293ef..647ac1e0ad09fb 100644 --- a/src/plugins/console/public/services/settings.ts +++ b/src/plugins/console/public/services/settings.ts @@ -8,6 +8,14 @@ import { Storage } from './index'; +export const DEFAULT_SETTINGS = Object.freeze({ + fontSize: 14, + polling: true, + tripleQuotes: true, + wrapMode: true, + autocomplete: Object.freeze({ fields: true, indices: true, templates: true }), +}); + export interface DevToolsSettings { fontSize: number; wrapMode: boolean; @@ -24,50 +32,46 @@ export class Settings { constructor(private readonly storage: Storage) {} getFontSize() { - return this.storage.get('font_size', 14); + return this.storage.get('font_size', DEFAULT_SETTINGS.fontSize); } - setFontSize(size: any) { + setFontSize(size: number) { this.storage.set('font_size', size); return true; } getWrapMode() { - return this.storage.get('wrap_mode', true); + return this.storage.get('wrap_mode', DEFAULT_SETTINGS.wrapMode); } - setWrapMode(mode: any) { + setWrapMode(mode: boolean) { this.storage.set('wrap_mode', mode); return true; } - setTripleQuotes(tripleQuotes: any) { + setTripleQuotes(tripleQuotes: boolean) { this.storage.set('triple_quotes', tripleQuotes); return true; } getTripleQuotes() { - return this.storage.get('triple_quotes', true); + return this.storage.get('triple_quotes', DEFAULT_SETTINGS.tripleQuotes); } getAutocomplete() { - return this.storage.get('autocomplete_settings', { - fields: true, - indices: true, - templates: true, - }); + return this.storage.get('autocomplete_settings', DEFAULT_SETTINGS.autocomplete); } - setAutocomplete(settings: any) { + setAutocomplete(settings: object) { this.storage.set('autocomplete_settings', settings); return true; } getPolling() { - return this.storage.get('console_polling', true); + return this.storage.get('console_polling', DEFAULT_SETTINGS.polling); } - setPolling(polling: any) { + setPolling(polling: boolean) { this.storage.set('console_polling', polling); return true; } diff --git a/src/plugins/console/public/services/storage.ts b/src/plugins/console/public/services/storage.ts index d933024625e778..221020e496feca 100644 --- a/src/plugins/console/public/services/storage.ts +++ b/src/plugins/console/public/services/storage.ts @@ -17,11 +17,11 @@ export enum StorageKeys { export class Storage { constructor(private readonly engine: IStorageEngine, private readonly prefix: string) {} - encode(val: any) { + encode(val: unknown) { return JSON.stringify(val); } - decode(val: any) { + decode(val: string | null) { if (typeof val === 'string') { return JSON.parse(val); } @@ -37,7 +37,7 @@ export class Storage { } } - set(key: string, val: any) { + set(key: string, val: unknown) { this.engine.setItem(this.encodeKey(key), this.encode(val)); return val; } diff --git a/src/plugins/console/public/types/common.ts b/src/plugins/console/public/types/common.ts index 77b3d7c4477fc2..53d896ad01d2f6 100644 --- a/src/plugins/console/public/types/common.ts +++ b/src/plugins/console/public/types/common.ts @@ -11,6 +11,12 @@ export interface MetricsTracker { load: (eventName: string) => void; } +export interface ESRequest { + method: string; + endpoint: string; + data?: string; +} + export type BaseResponseType = | 'application/json' | 'text/csv' diff --git a/src/plugins/console/public/types/core_editor.ts b/src/plugins/console/public/types/core_editor.ts index f5e81f413d5c51..cc344d6bcc8815 100644 --- a/src/plugins/console/public/types/core_editor.ts +++ b/src/plugins/console/public/types/core_editor.ts @@ -21,7 +21,7 @@ export type EditorEvent = export type AutoCompleterFunction = ( pos: Position, prefix: string, - callback: (...args: any[]) => void + callback: (...args: unknown[]) => void ) => void; export interface Position { @@ -235,7 +235,7 @@ export interface CoreEditor { * have this backdoor to update UI in response to request range changes, for example, as the user * moves the cursor around */ - legacyUpdateUI(opts: any): void; + legacyUpdateUI(opts: unknown): void; /** * A method to for the editor to resize, useful when, for instance, window size changes. @@ -250,7 +250,11 @@ export interface CoreEditor { /** * Register a keyboard shortcut and provide a function to be called. */ - registerKeyboardShortcut(opts: { keys: any; fn: () => void; name: string }): void; + registerKeyboardShortcut(opts: { + keys: string | { win?: string; mac?: string }; + fn: () => void; + name: string; + }): void; /** * Register a completions function that will be called when the editor diff --git a/src/plugins/console/server/lib/elasticsearch_proxy_config.ts b/src/plugins/console/server/lib/elasticsearch_proxy_config.ts index 757142c87a1192..bad6942d0c9af4 100644 --- a/src/plugins/console/server/lib/elasticsearch_proxy_config.ts +++ b/src/plugins/console/server/lib/elasticsearch_proxy_config.ts @@ -14,7 +14,7 @@ import url from 'url'; import { ESConfigForProxy } from '../types'; const createAgent = (legacyConfig: ESConfigForProxy) => { - const target = url.parse(_.head(legacyConfig.hosts) as any); + const target = url.parse(_.head(legacyConfig.hosts)!); if (!/^https/.test(target.protocol || '')) return new http.Agent(); const agentOptions: https.AgentOptions = {}; @@ -28,7 +28,7 @@ const createAgent = (legacyConfig: ESConfigForProxy) => { agentOptions.rejectUnauthorized = true; // by default, NodeJS is checking the server identify - agentOptions.checkServerIdentity = _.noop as any; + agentOptions.checkServerIdentity = (_.noop as unknown) as https.AgentOptions['checkServerIdentity']; break; case 'full': agentOptions.rejectUnauthorized = true; diff --git a/src/plugins/console/server/lib/proxy_config.ts b/src/plugins/console/server/lib/proxy_config.ts index 556f6affca91df..8e2633545799bb 100644 --- a/src/plugins/console/server/lib/proxy_config.ts +++ b/src/plugins/console/server/lib/proxy_config.ts @@ -12,6 +12,17 @@ import { Agent as HttpsAgent, AgentOptions } from 'https'; import { WildcardMatcher } from './wildcard_matcher'; +interface Config { + match: { + protocol: string; + host: string; + port: string; + path: string; + }; + ssl?: { verify?: boolean; ca?: string; cert?: string; key?: string }; + timeout: number; +} + export class ProxyConfig { // @ts-ignore private id: string; @@ -26,9 +37,9 @@ export class ProxyConfig { private readonly sslAgent?: HttpsAgent; - private verifySsl: any; + private verifySsl: undefined | boolean; - constructor(config: { match: any; timeout: number }) { + constructor(config: Config) { config = { ...config, }; @@ -61,8 +72,8 @@ export class ProxyConfig { this.sslAgent = this._makeSslAgent(config); } - _makeSslAgent(config: any) { - const ssl = config.ssl || {}; + _makeSslAgent(config: Config) { + const ssl: Config['ssl'] = config.ssl || {}; this.verifySsl = ssl.verify; const sslAgentOpts: AgentOptions = { diff --git a/src/plugins/console/server/lib/proxy_config_collection.ts b/src/plugins/console/server/lib/proxy_config_collection.ts index 83900838332b5d..f6c0b7c659de08 100644 --- a/src/plugins/console/server/lib/proxy_config_collection.ts +++ b/src/plugins/console/server/lib/proxy_config_collection.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { Agent } from 'http'; import { defaultsDeep } from 'lodash'; import { parse as parseUrl } from 'url'; @@ -14,7 +15,12 @@ import { ProxyConfig } from './proxy_config'; export class ProxyConfigCollection { private configs: ProxyConfig[]; - constructor(configs: Array<{ match: any; timeout: number }> = []) { + constructor( + configs: Array<{ + match: { protocol: string; host: string; port: string; path: string }; + timeout: number; + }> = [] + ) { this.configs = configs.map((settings) => new ProxyConfig(settings)); } @@ -22,7 +28,7 @@ export class ProxyConfigCollection { return Boolean(this.configs.length); } - configForUri(uri: string): object { + configForUri(uri: string): { agent: Agent; timeout: number } { const parsedUri = parseUrl(uri); const settings = this.configs.map((config) => config.getForParsedUri(parsedUri as any)); return defaultsDeep({}, ...settings); diff --git a/src/plugins/console/server/lib/proxy_request.test.ts b/src/plugins/console/server/lib/proxy_request.test.ts index 3fb915f0540b46..25257aa4c55798 100644 --- a/src/plugins/console/server/lib/proxy_request.test.ts +++ b/src/plugins/console/server/lib/proxy_request.test.ts @@ -55,7 +55,7 @@ describe(`Console's send request`, () => { fakeRequest = { abort: sinon.stub(), on() {}, - once(event: string, fn: any) { + once(event: string, fn: (v: string) => void) { if (event === 'response') { return fn('done'); } diff --git a/src/plugins/console/server/lib/proxy_request.ts b/src/plugins/console/server/lib/proxy_request.ts index d5914ab32bab7b..46b4aa642a70eb 100644 --- a/src/plugins/console/server/lib/proxy_request.ts +++ b/src/plugins/console/server/lib/proxy_request.ts @@ -46,9 +46,9 @@ export const proxyRequest = ({ const client = uri.protocol === 'https:' ? https : http; let resolved = false; - let resolve: any; - let reject: any; - const reqPromise = new Promise((res, rej) => { + let resolve: (res: http.IncomingMessage) => void; + let reject: (res: unknown) => void; + const reqPromise = new Promise((res, rej) => { resolve = res; reject = rej; }); diff --git a/src/plugins/console/server/routes/api/console/proxy/body.test.ts b/src/plugins/console/server/routes/api/console/proxy/body.test.ts index 64e2918764d00d..80a2e075957dec 100644 --- a/src/plugins/console/server/routes/api/console/proxy/body.test.ts +++ b/src/plugins/console/server/routes/api/console/proxy/body.test.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import { IKibanaResponse } from 'src/core/server'; import { getProxyRouteHandlerDeps } from './mocks'; import { Readable } from 'stream'; @@ -16,10 +16,14 @@ import * as requestModule from '../../../../lib/proxy_request'; import { createResponseStub } from './stubs'; describe('Console Proxy Route', () => { - let request: any; + let request: ( + method: string, + path: string, + response?: string + ) => Promise | IKibanaResponse; beforeEach(() => { - request = (method: string, path: string, response: string) => { + request = (method, path, response) => { (requestModule.proxyRequest as jest.Mock).mockResolvedValue(createResponseStub(response)); const handler = createHandler(getProxyRouteHandlerDeps({})); diff --git a/src/plugins/console/server/routes/api/console/proxy/create_handler.ts b/src/plugins/console/server/routes/api/console/proxy/create_handler.ts index 290a2cdec7b764..6a514483d14f28 100644 --- a/src/plugins/console/server/routes/api/console/proxy/create_handler.ts +++ b/src/plugins/console/server/routes/api/console/proxy/create_handler.ts @@ -41,7 +41,7 @@ function toURL(base: string, path: string) { } function filterHeaders(originalHeaders: object, headersToKeep: string[]): object { - const normalizeHeader = function (header: any) { + const normalizeHeader = function (header: string) { if (!header) { return ''; } @@ -68,7 +68,7 @@ function getRequestConfig( return { ...proxyConfigCollection.configForUri(uri), headers: newHeaders, - } as any; + }; } return { @@ -81,7 +81,7 @@ function getProxyHeaders(req: KibanaRequest) { const headers = Object.create(null); // Scope this proto-unsafe functionality to where it is being used. - function extendCommaList(obj: Record, property: string, value: any) { + function extendCommaList(obj: Record, property: string, value: string) { obj[property] = (obj[property] ? obj[property] + ',' : '') + value; } @@ -142,7 +142,7 @@ export const createHandler = ({ }; esIncomingMessage = await proxyRequest({ - method: method.toLowerCase() as any, + method: method.toLowerCase() as 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head', headers: requestHeaders, uri, timeout, diff --git a/src/plugins/console/server/routes/api/console/proxy/query_string.test.ts b/src/plugins/console/server/routes/api/console/proxy/query_string.test.ts index 19b50702368722..be4f1dbab942f4 100644 --- a/src/plugins/console/server/routes/api/console/proxy/query_string.test.ts +++ b/src/plugins/console/server/routes/api/console/proxy/query_string.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { IKibanaResponse } from 'src/core/server'; import { kibanaResponseFactory } from '../../../../../../../core/server'; import { getProxyRouteHandlerDeps } from './mocks'; import { createResponseStub } from './stubs'; @@ -14,7 +15,7 @@ import * as requestModule from '../../../../lib/proxy_request'; import { createHandler } from './create_handler'; describe('Console Proxy Route', () => { - let request: any; + let request: (method: string, path: string) => Promise | IKibanaResponse; beforeEach(() => { (requestModule.proxyRequest as jest.Mock).mockResolvedValue(createResponseStub('foo')); diff --git a/src/plugins/console/server/routes/api/console/proxy/stubs.ts b/src/plugins/console/server/routes/api/console/proxy/stubs.ts index 4aa23f99ea30e1..3474d0c9b33b84 100644 --- a/src/plugins/console/server/routes/api/console/proxy/stubs.ts +++ b/src/plugins/console/server/routes/api/console/proxy/stubs.ts @@ -9,8 +9,12 @@ import { IncomingMessage } from 'http'; import { Readable } from 'stream'; -export function createResponseStub(response: any) { - const resp: any = new Readable({ +export function createResponseStub(response?: string) { + const resp: Readable & { + statusCode?: number; + statusMessage?: string; + headers?: Record; + } = new Readable({ read() { if (response) { this.push(response); diff --git a/src/plugins/console/server/services/spec_definitions_service.ts b/src/plugins/console/server/services/spec_definitions_service.ts index 8b0f4c04ae0bdb..e0af9422666afb 100644 --- a/src/plugins/console/server/services/spec_definitions_service.ts +++ b/src/plugins/console/server/services/spec_definitions_service.ts @@ -15,6 +15,15 @@ import { jsSpecLoaders } from '../lib'; const PATH_TO_OSS_JSON_SPEC = resolve(__dirname, '../lib/spec_definitions/json'); +interface EndpointDescription { + methods?: string[]; + patterns?: string | string[]; + url_params?: Record; + data_autocomplete_rules?: Record; + url_components?: Record; + priority?: number; +} + export class SpecDefinitionsService { private readonly name = 'es'; @@ -24,16 +33,23 @@ export class SpecDefinitionsService { private hasLoadedSpec = false; - public addGlobalAutocompleteRules(parentNode: string, rules: any) { + public addGlobalAutocompleteRules(parentNode: string, rules: unknown) { this.globalRules[parentNode] = rules; } - public addEndpointDescription(endpoint: string, description: any = {}) { - let copiedDescription: any = {}; + public addEndpointDescription(endpoint: string, description: EndpointDescription = {}) { + let copiedDescription: { patterns?: string; url_params?: Record } = {}; if (this.endpoints[endpoint]) { copiedDescription = { ...this.endpoints[endpoint] }; } - let urlParamsDef: any; + let urlParamsDef: + | { + ignore_unavailable?: string; + allow_no_indices?: string; + expand_wildcards?: string[]; + } + | undefined; + _.each(description.patterns || [], function (p) { if (p.indexOf('{indices}') >= 0) { urlParamsDef = urlParamsDef || {}; @@ -70,7 +86,7 @@ export class SpecDefinitionsService { this.extensionSpecFilePaths.push(path); } - public addProcessorDefinition(processor: any) { + public addProcessorDefinition(processor: unknown) { if (!this.hasLoadedSpec) { throw new Error( 'Cannot add a processor definition because spec definitions have not loaded!' @@ -104,11 +120,13 @@ export class SpecDefinitionsService { return generatedFiles.reduce((acc, file) => { const overrideFile = overrideFiles.find((f) => basename(f) === basename(file)); - const loadedSpec = JSON.parse(readFileSync(file, 'utf8')); + const loadedSpec: Record = JSON.parse( + readFileSync(file, 'utf8') + ); if (overrideFile) { merge(loadedSpec, JSON.parse(readFileSync(overrideFile, 'utf8'))); } - const spec: any = {}; + const spec: Record = {}; Object.entries(loadedSpec).forEach(([key, value]) => { if (acc[key]) { // add time to remove key collision @@ -119,7 +137,7 @@ export class SpecDefinitionsService { }); return { ...acc, ...spec }; - }, {} as any); + }, {} as Record); } private loadJsonSpec() { From 2dea06f5a43c63d6ed63b1f537e70b07c19168b8 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 24 Mar 2021 13:43:00 +0000 Subject: [PATCH 60/93] skip flaky suite (#94545) --- test/functional/apps/discover/_data_grid_context.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/discover/_data_grid_context.ts b/test/functional/apps/discover/_data_grid_context.ts index 896cd4ad595c95..326fba9e6c0870 100644 --- a/test/functional/apps/discover/_data_grid_context.ts +++ b/test/functional/apps/discover/_data_grid_context.ts @@ -34,7 +34,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardAddPanel = getService('dashboardAddPanel'); const browser = getService('browser'); - describe('discover data grid context tests', () => { + // FLAKY: https://github.com/elastic/kibana/issues/94545 + describe.skip('discover data grid context tests', () => { before(async () => { await esArchiver.load('discover'); await esArchiver.loadIfNeeded('logstash_functional'); From 017a2b8413e01c210f1f0ece1957f3e301091d38 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 24 Mar 2021 13:46:38 +0000 Subject: [PATCH 61/93] skip flaky suite (#94535) --- .../apis/security_solution/uncommon_processes.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/security_solution/uncommon_processes.ts b/x-pack/test/api_integration/apis/security_solution/uncommon_processes.ts index ecff7da6147be8..fff8b98c8fd1fc 100644 --- a/x-pack/test/api_integration/apis/security_solution/uncommon_processes.ts +++ b/x-pack/test/api_integration/apis/security_solution/uncommon_processes.ts @@ -27,7 +27,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); - describe('uncommon_processes', () => { + // FLAKY: https://github.com/elastic/kibana/issues/94535 + describe.skip('uncommon_processes', () => { before(() => esArchiver.load('auditbeat/uncommon_processes')); after(() => esArchiver.unload('auditbeat/uncommon_processes')); From d5883beb25fd868fffcde73ea1a74c9e9b21c3ab Mon Sep 17 00:00:00 2001 From: ymao1 Date: Wed, 24 Mar 2021 10:19:35 -0400 Subject: [PATCH 62/93] Adding ES query rule type to stack alerts feature privilege (#95225) --- x-pack/plugins/stack_alerts/server/feature.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/stack_alerts/server/feature.ts b/x-pack/plugins/stack_alerts/server/feature.ts index d421832a4cd970..9f91398cc7d24b 100644 --- a/x-pack/plugins/stack_alerts/server/feature.ts +++ b/x-pack/plugins/stack_alerts/server/feature.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { ID as IndexThreshold } from './alert_types/index_threshold/alert_type'; import { GEO_CONTAINMENT_ID as GeoContainment } from './alert_types/geo_containment/alert_type'; +import { ES_QUERY_ID as ElasticsearchQuery } from './alert_types/es_query/alert_type'; import { STACK_ALERTS_FEATURE_ID } from '../common'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; @@ -21,7 +22,7 @@ export const BUILT_IN_ALERTS_FEATURE = { management: { insightsAndAlerting: ['triggersActions'], }, - alerting: [IndexThreshold, GeoContainment], + alerting: [IndexThreshold, GeoContainment, ElasticsearchQuery], privileges: { all: { app: [], @@ -30,7 +31,7 @@ export const BUILT_IN_ALERTS_FEATURE = { insightsAndAlerting: ['triggersActions'], }, alerting: { - all: [IndexThreshold, GeoContainment], + all: [IndexThreshold, GeoContainment, ElasticsearchQuery], read: [], }, savedObject: { @@ -48,7 +49,7 @@ export const BUILT_IN_ALERTS_FEATURE = { }, alerting: { all: [], - read: [IndexThreshold, GeoContainment], + read: [IndexThreshold, GeoContainment, ElasticsearchQuery], }, savedObject: { all: [], From edaa64f150d804123645778ea3de01e0c4b143d7 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 24 Mar 2021 15:38:58 +0100 Subject: [PATCH 63/93] [ML] Functional tests for Anomaly swim lane (#94723) * [ML] update @elastic/charts * [ML] swim lane service, axes tests * [ML] check single cell selection and current URL * [ML] clear selection * [ML] assert anomaly explorer charts * [ML] fix unit test * [ML] assert anomalies table and top influencers list * [ML] update apiDoc version * [ML] exclude host from the URL assertion * [ML] clicks view by swim lane * [ML] fix method for cell selection * [ML] brush action tests * [ML] use debug state flag * [ML] declare window interface * [ML] pagination tests * [ML] enable test * [ML] scroll into view for swim lane actions * [ML] rename URL assertion method * [ML] fix assertion for charts count * [ML] extend assertion * [ML] refactor test subjects selection * [ML] fix assertSelection * [ML] reduce timeout for charts assertion --- .../services/visualizations/elastic_chart.ts | 6 +- .../application/explorer/anomaly_timeline.tsx | 6 +- .../explorer_charts_container.js | 2 +- .../explorer_charts_container.test.js | 2 +- .../explorer/swimlane_container.tsx | 25 ++- .../explorer/swimlane_pagination.tsx | 17 +- x-pack/plugins/ml/server/routes/apidoc.json | 7 +- .../routes/apidoc_scripts/version_filter.ts | 2 +- .../ml/anomaly_detection/anomaly_explorer.ts | 188 ++++++++++++++++ .../functional/services/ml/anomalies_table.ts | 8 + .../services/ml/anomaly_explorer.ts | 25 +++ x-pack/test/functional/services/ml/index.ts | 3 + .../test/functional/services/ml/navigation.ts | 23 +- .../test/functional/services/ml/swim_lane.ts | 212 ++++++++++++++++++ 14 files changed, 504 insertions(+), 22 deletions(-) create mode 100644 x-pack/test/functional/services/ml/swim_lane.ts diff --git a/test/functional/services/visualizations/elastic_chart.ts b/test/functional/services/visualizations/elastic_chart.ts index 010394b2752286..80483100a06dd3 100644 --- a/test/functional/services/visualizations/elastic_chart.ts +++ b/test/functional/services/visualizations/elastic_chart.ts @@ -29,7 +29,11 @@ export function ElasticChartProvider({ getService }: FtrProviderContext) { const browser = getService('browser'); class ElasticChart { - public async getCanvas() { + public async getCanvas(dataTestSubj?: string) { + if (dataTestSubj) { + const chart = await this.getChart(dataTestSubj); + return await chart.findByClassName('echCanvasRenderer'); + } return await find.byCssSelector('.echChart canvas:last-of-type'); } diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx index 4289986bb6a594..7c63d4087ce1e0 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx @@ -160,7 +160,11 @@ export const AnomalyTimeline: FC = React.memo( {selectedCells ? ( - + - + {seriesToUse.length > 0 && seriesToUse.map((series) => ( { ); expect(wrapper.html()).toBe( - '
' + '
' ); }); diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index 8deffa15cd6bd7..c108257094b6aa 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -26,6 +26,8 @@ import { HeatmapSpec, TooltipSettings, HeatmapBrushEvent, + Position, + ScaleType, } from '@elastic/charts'; import moment from 'moment'; @@ -44,6 +46,15 @@ import './_explorer.scss'; import { EMPTY_FIELD_VALUE_LABEL } from '../timeseriesexplorer/components/entity_control/entity_control'; import { useUiSettings } from '../contexts/kibana'; +declare global { + interface Window { + /** + * Flag used to enable debugState on elastic charts + */ + _echDebugStateFlag?: boolean; + } +} + /** * Ignore insignificant resize, e.g. browser scrollbar appearance. */ @@ -352,7 +363,7 @@ export const SwimlaneContainer: FC = ({ direction={'column'} style={{ width: '100%', height: '100%', overflow: 'hidden' }} ref={resizeRef} - data-test-subj="mlSwimLaneContainer" + data-test-subj={dataTestSubj} > = ({ }} grow={false} > -
+
{showSwimlane && !isLoading && ( = ({ valueAccessor="value" highlightedData={highlightedData} valueFormatter={getFormattedSeverityScore} - xScaleType="time" + xScaleType={ScaleType.Time} ySortPredicate="dataIndex" config={swimLaneConfig} /> diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_pagination.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_pagination.tsx index 8b205d2b8d5a17..18297d06dd6fe9 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_pagination.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_pagination.tsx @@ -57,6 +57,7 @@ export const SwimLanePagination: FC = ({ closePopover(); setPerPage(v); }} + data-test-subj={`${v} rows`} > = ({ iconType="arrowDown" iconSide="right" onClick={onButtonClick} + data-test-subj="mlSwimLanePageSizeControl" > - + + + } isOpen={isPopoverOpen} closePopover={closePopover} panelPaddingSize="none" > - + @@ -102,6 +106,7 @@ export const SwimLanePagination: FC = ({ pageCount={pageCount} activePage={componentFromPage} onPageClick={goToPage} + data-test-subj="mlSwimLanePagination" /> diff --git a/x-pack/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json index 1a10046380658a..ba61a987d69ef1 100644 --- a/x-pack/plugins/ml/server/routes/apidoc.json +++ b/x-pack/plugins/ml/server/routes/apidoc.json @@ -1,6 +1,6 @@ { "name": "ml_kibana_api", - "version": "7.11.0", + "version": "7.13.0", "description": "This is the documentation of the REST API provided by the Machine Learning Kibana plugin. Each API is experimental and can include breaking changes in any version.", "title": "ML Kibana API", "order": [ @@ -159,6 +159,9 @@ "GetTrainedModel", "GetTrainedModelStats", "GetTrainedModelPipelines", - "DeleteTrainedModel" + "DeleteTrainedModel", + + "Alerting", + "PreviewAlert" ] } diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts index ad00915f28d6d1..430f105fb27d41 100644 --- a/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts @@ -7,7 +7,7 @@ import { Block } from './types'; -const API_VERSION = '7.8.0'; +const API_VERSION = '7.13.0'; /** * Post Filter parsed results. diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts b/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts index 086b6c7e7f9d7b..ff38544fa8c030 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts @@ -5,6 +5,7 @@ * 2.0. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { Job, Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; @@ -51,9 +52,15 @@ const testDataList = [ }, ]; +const cellSize = 15; + +const overallSwimLaneTestSubj = 'mlAnomalyExplorerSwimlaneOverall'; +const viewBySwimLaneTestSubj = 'mlAnomalyExplorerSwimlaneViewBy'; + export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); + const elasticChart = getService('elasticChart'); describe('anomaly explorer', function () { this.tags(['mlqa']); @@ -76,12 +83,16 @@ export default function ({ getService }: FtrProviderContext) { }); after(async () => { + await elasticChart.setNewChartUiDebugFlag(false); await ml.api.cleanMlIndices(); }); it('opens a job from job list link', async () => { await ml.testExecution.logTestStep('navigate to job list'); await ml.navigation.navigateToMl(); + // Set debug state has to happen at this point + // because page refresh happens after navigation to the ML app. + await elasticChart.setNewChartUiDebugFlag(true); await ml.navigation.navigateToJobManagement(); await ml.testExecution.logTestStep('open job in anomaly explorer'); @@ -126,6 +137,183 @@ export default function ({ getService }: FtrProviderContext) { await ml.anomaliesTable.assertTableNotEmpty(); }); + it('renders Overall swim lane', async () => { + await ml.testExecution.logTestStep('has correct axes labels'); + await ml.swimLane.assertAxisLabels(overallSwimLaneTestSubj, 'x', [ + '2016-02-07 00:00', + '2016-02-08 00:00', + '2016-02-09 00:00', + '2016-02-10 00:00', + '2016-02-11 00:00', + '2016-02-12 00:00', + ]); + await ml.swimLane.assertAxisLabels(overallSwimLaneTestSubj, 'y', ['Overall']); + }); + + it('renders View By swim lane', async () => { + await ml.testExecution.logTestStep('has correct axes labels'); + await ml.swimLane.assertAxisLabels(viewBySwimLaneTestSubj, 'x', [ + '2016-02-07 00:00', + '2016-02-08 00:00', + '2016-02-09 00:00', + '2016-02-10 00:00', + '2016-02-11 00:00', + '2016-02-12 00:00', + ]); + await ml.swimLane.assertAxisLabels(viewBySwimLaneTestSubj, 'y', [ + 'AAL', + 'VRD', + 'EGF', + 'SWR', + 'AMX', + 'JZA', + 'TRS', + 'ACA', + 'BAW', + 'ASA', + ]); + }); + + it('supports cell selection by click on Overall swim lane', async () => { + await ml.testExecution.logTestStep('checking page state before the cell selection'); + await ml.anomalyExplorer.assertClearSelectionButtonVisible(false); + await ml.anomaliesTable.assertTableRowsCount(25); + await ml.anomalyExplorer.assertInfluencerFieldListLength('airline', 10); + await ml.anomalyExplorer.assertAnomalyExplorerChartsCount(0); + + await ml.testExecution.logTestStep('clicks on the Overall swim lane cell'); + const sampleCell = (await ml.swimLane.getCells(overallSwimLaneTestSubj))[0]; + await ml.swimLane.selectSingleCell(overallSwimLaneTestSubj, { + x: sampleCell.x + cellSize, + y: sampleCell.y + cellSize, + }); + // TODO extend cell data with X and Y values, and cell width + await ml.swimLane.assertSelection(overallSwimLaneTestSubj, { + x: [1454846400000, 1454860800000], + y: ['Overall'], + }); + await ml.anomalyExplorer.assertClearSelectionButtonVisible(true); + + await ml.testExecution.logTestStep('updates the View By swim lane'); + await ml.swimLane.assertAxisLabels(viewBySwimLaneTestSubj, 'y', ['EGF', 'DAL']); + + await ml.testExecution.logTestStep('renders anomaly explorer charts'); + await ml.anomalyExplorer.assertAnomalyExplorerChartsCount(4); + + await ml.testExecution.logTestStep('updates top influencers list'); + await ml.anomalyExplorer.assertInfluencerFieldListLength('airline', 2); + + await ml.testExecution.logTestStep('updates anomalies table'); + await ml.anomaliesTable.assertTableRowsCount(4); + + await ml.testExecution.logTestStep('updates the URL state'); + await ml.navigation.assertCurrentURLContains( + 'selectedLanes%3A!(Overall)%2CselectedTimes%3A!(1454846400%2C1454860800)%2CselectedType%3Aoverall%2CshowTopFieldValues%3A!t%2CviewByFieldName%3Aairline%2CviewByFromPage%3A1%2CviewByPerPage%3A10' + ); + + await ml.testExecution.logTestStep('clears the selection'); + await ml.anomalyExplorer.clearSwimLaneSelection(); + await ml.navigation.assertCurrentURLNotContain( + 'selectedLanes%3A!(Overall)%2CselectedTimes%3A!(1454846400%2C1454860800)%2CselectedType%3Aoverall%2CshowTopFieldValues%3A!t%2CviewByFieldName%3Aairline%2CviewByFromPage%3A1%2CviewByPerPage%3A10' + ); + await ml.anomaliesTable.assertTableRowsCount(25); + await ml.anomalyExplorer.assertInfluencerFieldListLength('airline', 10); + await ml.anomalyExplorer.assertAnomalyExplorerChartsCount(0); + }); + + it('allows to change the swim lane pagination', async () => { + await ml.testExecution.logTestStep('checks default pagination'); + await ml.swimLane.assertPageSize(viewBySwimLaneTestSubj, 10); + await ml.swimLane.assertActivePage(viewBySwimLaneTestSubj, 1); + + await ml.testExecution.logTestStep('updates pagination'); + await ml.swimLane.setPageSize(viewBySwimLaneTestSubj, 5); + + const axisLabels = await ml.swimLane.getAxisLabels(viewBySwimLaneTestSubj, 'y'); + expect(axisLabels.length).to.eql(5); + + await ml.swimLane.selectPage(viewBySwimLaneTestSubj, 3); + + await ml.testExecution.logTestStep('resets pagination'); + await ml.swimLane.setPageSize(viewBySwimLaneTestSubj, 10); + await ml.swimLane.assertActivePage(viewBySwimLaneTestSubj, 1); + }); + + it('supports cell selection by click on View By swim lane', async () => { + await ml.testExecution.logTestStep('checking page state before the cell selection'); + await ml.anomalyExplorer.assertClearSelectionButtonVisible(false); + await ml.anomaliesTable.assertTableRowsCount(25); + await ml.anomalyExplorer.assertInfluencerFieldListLength('airline', 10); + await ml.anomalyExplorer.assertAnomalyExplorerChartsCount(0); + + await ml.testExecution.logTestStep('clicks on the View By swim lane cell'); + await ml.anomalyExplorer.assertSwimlaneViewByExists(); + const sampleCell = (await ml.swimLane.getCells(viewBySwimLaneTestSubj))[0]; + await ml.swimLane.selectSingleCell(viewBySwimLaneTestSubj, { + x: sampleCell.x + cellSize, + y: sampleCell.y + cellSize, + }); + + await ml.testExecution.logTestStep('check page content'); + await ml.swimLane.assertSelection(viewBySwimLaneTestSubj, { + x: [1454817600000, 1454832000000], + y: ['AAL'], + }); + + await ml.anomaliesTable.assertTableRowsCount(1); + await ml.anomalyExplorer.assertInfluencerFieldListLength('airline', 1); + await ml.anomalyExplorer.assertAnomalyExplorerChartsCount(1); + + await ml.testExecution.logTestStep('highlights the Overall swim lane'); + await ml.swimLane.assertSelection(overallSwimLaneTestSubj, { + x: [1454817600000, 1454832000000], + y: ['Overall'], + }); + + await ml.testExecution.logTestStep('clears the selection'); + await ml.anomalyExplorer.clearSwimLaneSelection(); + await ml.anomaliesTable.assertTableRowsCount(25); + await ml.anomalyExplorer.assertInfluencerFieldListLength('airline', 10); + await ml.anomalyExplorer.assertAnomalyExplorerChartsCount(0); + }); + + it('supports cell selection by brush action', async () => { + await ml.testExecution.logTestStep('checking page state before the cell selection'); + await ml.anomalyExplorer.assertClearSelectionButtonVisible(false); + await ml.anomaliesTable.assertTableRowsCount(25); + await ml.anomalyExplorer.assertInfluencerFieldListLength('airline', 10); + await ml.anomalyExplorer.assertAnomalyExplorerChartsCount(0); + + await ml.anomalyExplorer.assertSwimlaneViewByExists(); + const cells = await ml.swimLane.getCells(viewBySwimLaneTestSubj); + + const sampleCell1 = cells[0]; + // Get cell from another row + const sampleCell2 = cells.find((c) => c.y !== sampleCell1.y); + + await ml.swimLane.selectCells(viewBySwimLaneTestSubj, { + x1: sampleCell1.x + cellSize, + y1: sampleCell1.y + cellSize, + x2: sampleCell2!.x + cellSize, + y2: sampleCell2!.y + cellSize, + }); + + await ml.swimLane.assertSelection(viewBySwimLaneTestSubj, { + x: [1454817600000, 1454846400000], + y: ['AAL', 'VRD'], + }); + + await ml.anomaliesTable.assertTableRowsCount(2); + await ml.anomalyExplorer.assertInfluencerFieldListLength('airline', 2); + await ml.anomalyExplorer.assertAnomalyExplorerChartsCount(2); + + await ml.testExecution.logTestStep('clears the selection'); + await ml.anomalyExplorer.clearSwimLaneSelection(); + await ml.anomaliesTable.assertTableRowsCount(25); + await ml.anomalyExplorer.assertInfluencerFieldListLength('airline', 10); + await ml.anomalyExplorer.assertAnomalyExplorerChartsCount(0); + }); + it('adds swim lane embeddable to a dashboard', async () => { // should be the last step because it navigates away from the Anomaly Explorer page await ml.testExecution.logTestStep( diff --git a/x-pack/test/functional/services/ml/anomalies_table.ts b/x-pack/test/functional/services/ml/anomalies_table.ts index 54109e40a75262..30bb3e67bc862d 100644 --- a/x-pack/test/functional/services/ml/anomalies_table.ts +++ b/x-pack/test/functional/services/ml/anomalies_table.ts @@ -22,6 +22,14 @@ export function MachineLearningAnomaliesTableProvider({ getService }: FtrProvide return await testSubjects.findAll('mlAnomaliesTable > ~mlAnomaliesListRow'); }, + async assertTableRowsCount(expectedCount: number) { + const actualCount = (await this.getTableRows()).length; + expect(actualCount).to.eql( + expectedCount, + `Expect anomaly table rows count to be ${expectedCount}, got ${actualCount}` + ); + }, + async getRowSubjByRowIndex(rowIndex: number) { const tableRows = await this.getTableRows(); expect(tableRows.length).to.be.greaterThan( diff --git a/x-pack/test/functional/services/ml/anomaly_explorer.ts b/x-pack/test/functional/services/ml/anomaly_explorer.ts index 7fe53b1e3773ab..4b1992777b8a76 100644 --- a/x-pack/test/functional/services/ml/anomaly_explorer.ts +++ b/x-pack/test/functional/services/ml/anomaly_explorer.ts @@ -111,5 +111,30 @@ export function MachineLearningAnomalyExplorerProvider({ getService }: FtrProvid await searchBarInput.clearValueWithKeyboard(); await searchBarInput.type(filter); }, + + async assertClearSelectionButtonVisible(expectVisible: boolean) { + if (expectVisible) { + await testSubjects.existOrFail('mlAnomalyTimelineClearSelection'); + } else { + await testSubjects.missingOrFail('mlAnomalyTimelineClearSelection'); + } + }, + + async clearSwimLaneSelection() { + await this.assertClearSelectionButtonVisible(true); + await testSubjects.click('mlAnomalyTimelineClearSelection'); + await this.assertClearSelectionButtonVisible(false); + }, + + async assertAnomalyExplorerChartsCount(expectedChartsCount: number) { + const chartsContainer = await testSubjects.find('mlExplorerChartsContainer'); + const actualChartsCount = ( + await chartsContainer.findAllByClassName('ml-explorer-chart-container', 3000) + ).length; + expect(actualChartsCount).to.eql( + expectedChartsCount, + `Expect ${expectedChartsCount} charts to appear, got ${actualChartsCount}` + ); + }, }; } diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index 894ba3d6ef07d0..83c0c5e4164344 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -45,6 +45,7 @@ import { MachineLearningTestExecutionProvider } from './test_execution'; import { MachineLearningTestResourcesProvider } from './test_resources'; import { MachineLearningDataVisualizerTableProvider } from './data_visualizer_table'; import { MachineLearningAlertingProvider } from './alerting'; +import { SwimLaneProvider } from './swim_lane'; export function MachineLearningProvider(context: FtrProviderContext) { const commonAPI = MachineLearningCommonAPIProvider(context); @@ -96,6 +97,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { const testExecution = MachineLearningTestExecutionProvider(context); const testResources = MachineLearningTestResourcesProvider(context); const alerting = MachineLearningAlertingProvider(context, commonUI); + const swimLane = SwimLaneProvider(context); return { anomaliesTable, @@ -134,6 +136,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { settingsCalendar, settingsFilterList, singleMetricViewer, + swimLane, testExecution, testResources, }; diff --git a/x-pack/test/functional/services/ml/navigation.ts b/x-pack/test/functional/services/ml/navigation.ts index 93b8a5efecc079..075c788a863363 100644 --- a/x-pack/test/functional/services/ml/navigation.ts +++ b/x-pack/test/functional/services/ml/navigation.ts @@ -14,6 +14,7 @@ export function MachineLearningNavigationProvider({ getPageObjects, }: FtrProviderContext) { const retry = getService('retry'); + const browser = getService('browser'); const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common']); @@ -156,7 +157,7 @@ export function MachineLearningNavigationProvider({ }, async navigateToSingleMetricViewerViaAnomalyExplorer() { - // clicks the `Single Metric Viewere` icon on the button group to switch result views + // clicks the `Single Metric Viewer` icon on the button group to switch result views await testSubjects.click('mlAnomalyResultsViewSelectorSingleMetricViewer'); await retry.tryForTime(60 * 1000, async () => { // verify that the single metric viewer page is visible @@ -193,5 +194,25 @@ export function MachineLearningNavigationProvider({ await testSubjects.existOrFail('homeApp', { timeout: 2000 }); }); }, + + /** + * Assert the active URL. + * @param expectedUrlPart - URL component excluding host + */ + async assertCurrentURLContains(expectedUrlPart: string) { + const currentUrl = await browser.getCurrentUrl(); + expect(currentUrl).to.include.string( + expectedUrlPart, + `Expected the current URL "${currentUrl}" to include ${expectedUrlPart}` + ); + }, + + async assertCurrentURLNotContain(expectedUrlPart: string) { + const currentUrl = await browser.getCurrentUrl(); + expect(currentUrl).to.not.include.string( + expectedUrlPart, + `Expected the current URL "${currentUrl}" to not include ${expectedUrlPart}` + ); + }, }; } diff --git a/x-pack/test/functional/services/ml/swim_lane.ts b/x-pack/test/functional/services/ml/swim_lane.ts new file mode 100644 index 00000000000000..d659b24559a430 --- /dev/null +++ b/x-pack/test/functional/services/ml/swim_lane.ts @@ -0,0 +1,212 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { DebugState } from '@elastic/charts'; +import { DebugStateAxis } from '@elastic/charts/dist/state/types'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper'; + +type HeatmapDebugState = Required>; + +export function SwimLaneProvider({ getService }: FtrProviderContext) { + const elasticChart = getService('elasticChart'); + const browser = getService('browser'); + const testSubjects = getService('testSubjects'); + + /** + * Y axis labels width + padding + */ + const xOffset = 185; + + /** + * Get coordinates relative to the left top corner of the canvas + * and transpose them from the center point. + */ + async function getCoordinatesFromCenter( + el: WebElementWrapper, + coordinates: { x: number; y: number } + ) { + const { width, height } = await el.getSize(); + + const elCenter = { + x: Math.round(width / 2), + y: Math.round(height / 2), + }; + + /** + * Origin of the element uses the center point, hence we need ot adjust + * the click coordinated accordingly. + */ + const resultX = xOffset + Math.round(coordinates.x) - elCenter.x; + const resultY = Math.round(coordinates.y) - elCenter.y; + + return { + x: resultX, + y: resultY, + }; + } + + const getRenderTracker = async (testSubj: string) => { + const renderCount = await elasticChart.getVisualizationRenderingCount(testSubj); + + return { + async verify() { + if (testSubj === 'mlAnomalyExplorerSwimlaneViewBy') { + // We have a glitchy behaviour when clicking on the View By swim lane. + // The entire charts is re-rendered, hence it requires a different check + await testSubjects.existOrFail(testSubj); + await elasticChart.waitForRenderComplete(testSubj); + } else { + await elasticChart.waitForRenderingCount(renderCount + 1, testSubj); + } + }, + }; + }; + + return { + async getDebugState(testSubj: string): Promise { + const state = await elasticChart.getChartDebugData(testSubj); + if (!state) { + throw new Error('Swim lane debug state is not available'); + } + return state as HeatmapDebugState; + }, + + async getAxisLabels(testSubj: string, axis: 'x' | 'y'): Promise { + const state = await this.getDebugState(testSubj); + return state.axes[axis][0].labels; + }, + + async assertAxisLabels(testSubj: string, axis: 'x' | 'y', expectedValues: string[]) { + const actualValues = await this.getAxisLabels(testSubj, axis); + expect(actualValues).to.eql( + expectedValues, + `Expected swim lane ${axis} labels to be ${expectedValues}, got ${actualValues}` + ); + }, + + async getCells(testSubj: string): Promise { + const state = await this.getDebugState(testSubj); + return state.heatmap.cells; + }, + + async getHighlighted(testSubj: string): Promise { + const state = await this.getDebugState(testSubj); + return state.heatmap.selection; + }, + + async assertSelection( + testSubj: string, + expectedData: HeatmapDebugState['heatmap']['selection']['data'], + expectedArea?: HeatmapDebugState['heatmap']['selection']['area'] + ) { + const actualSelection = await this.getHighlighted(testSubj); + expect(actualSelection.data).to.eql( + expectedData, + `Expected swim lane to have ${ + expectedData + ? `selected X-axis values ${expectedData.x.join( + ',' + )} and Y-axis values ${expectedData.y.join(',')}` + : 'no data selected' + }, got ${ + actualSelection.data + ? `${actualSelection.data.x.join(',')} and ${actualSelection.data.y.join(',')}` + : 'null' + }` + ); + if (expectedArea) { + expect(actualSelection.area).to.eql(expectedArea); + } + }, + + /** + * Selects a single cell + * @param testSubj + * @param x - number of pixels from the Y-axis + * @param y - number of pixels from the top of the canvas element + */ + async selectSingleCell(testSubj: string, { x, y }: { x: number; y: number }) { + await testSubjects.existOrFail(testSubj); + await testSubjects.scrollIntoView(testSubj); + const renderTracker = await getRenderTracker(testSubj); + const el = await elasticChart.getCanvas(testSubj); + + const { x: resultX, y: resultY } = await getCoordinatesFromCenter(el, { x, y }); + + await browser + .getActions() + .move({ x: resultX, y: resultY, origin: el._webElement }) + .click() + .perform(); + + await renderTracker.verify(); + }, + + async selectCells( + testSubj: string, + coordinates: { x1: number; x2: number; y1: number; y2: number } + ) { + await testSubjects.existOrFail(testSubj); + await testSubjects.scrollIntoView(testSubj); + const renderTracker = await getRenderTracker(testSubj); + + const el = await elasticChart.getCanvas(testSubj); + + const { x: resultX1, y: resultY1 } = await getCoordinatesFromCenter(el, { + x: coordinates.x1, + y: coordinates.y1, + }); + const { x: resultX2, y: resultY2 } = await getCoordinatesFromCenter(el, { + x: coordinates.x2, + y: coordinates.y2, + }); + + await browser.dragAndDrop( + { + location: el, + offset: { x: resultX1, y: resultY1 }, + }, + { + location: el, + offset: { x: resultX2, y: resultY2 }, + } + ); + + await renderTracker.verify(); + }, + + async assertActivePage(testSubj: string, expectedPage: number) { + const pagination = await testSubjects.find(`${testSubj} > mlSwimLanePagination`); + const activePage = await pagination.findByCssSelector( + '.euiPaginationButton-isActive .euiButtonEmpty__text' + ); + const text = await activePage.getVisibleText(); + expect(text).to.eql(expectedPage); + }, + + async assertPageSize(testSubj: string, expectedPageSize: number) { + const actualPageSize = await testSubjects.find( + `${testSubj} > ${expectedPageSize.toString()}` + ); + expect(await actualPageSize.isDisplayed()).to.be(true); + }, + + async selectPage(testSubj: string, page: number) { + await testSubjects.click(`${testSubj} > pagination-button-${page - 1}`); + await this.assertActivePage(testSubj, page); + }, + + async setPageSize(testSubj: string, rowsCount: 5 | 10 | 20 | 50 | 100) { + await testSubjects.click(`${testSubj} > mlSwimLanePageSizeControl`); + await testSubjects.existOrFail('mlSwimLanePageSizePanel'); + await testSubjects.click(`mlSwimLanePageSizePanel > ${rowsCount} rows`); + await this.assertPageSize(testSubj, rowsCount); + }, + }; +} From cf33d72442d2bf330e937d17dca9750d8a4bb756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Wed, 24 Mar 2021 15:52:41 +0100 Subject: [PATCH 64/93] [Logs UI] Tolerate log entries for which fields retrieval fails (#94972) --- .../log_entries/log_entries_search_strategy.ts | 10 +++++----- .../services/log_entries/log_entry_search_strategy.ts | 2 +- .../server/services/log_entries/queries/log_entries.ts | 2 +- .../server/services/log_entries/queries/log_entry.ts | 4 +++- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts index 190464ab6d5c1f..161685aac29ad4 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts @@ -203,13 +203,13 @@ const getLogEntryFromHit = ( } else if ('messageColumn' in column) { return { columnId: column.messageColumn.id, - message: messageFormattingRules.format(hit.fields, hit.highlight || {}), + message: messageFormattingRules.format(hit.fields ?? {}, hit.highlight || {}), }; } else { return { columnId: column.fieldColumn.id, field: column.fieldColumn.field, - value: hit.fields[column.fieldColumn.field] ?? [], + value: hit.fields?.[column.fieldColumn.field] ?? [], highlights: hit.highlight?.[column.fieldColumn.field] ?? [], }; } @@ -233,9 +233,9 @@ const pickRequestCursor = ( const getContextFromHit = (hit: LogEntryHit): LogEntryContext => { // Get all context fields, then test for the presence and type of the ones that go together - const containerId = hit.fields['container.id']?.[0]; - const hostName = hit.fields['host.name']?.[0]; - const logFilePath = hit.fields['log.file.path']?.[0]; + const containerId = hit.fields?.['container.id']?.[0]; + const hostName = hit.fields?.['host.name']?.[0]; + const logFilePath = hit.fields?.['log.file.path']?.[0]; if (typeof containerId === 'string') { return { 'container.id': containerId }; diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts index 2088761800cfe4..85eacba823b2bd 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts @@ -121,5 +121,5 @@ const createLogEntryFromHit = (hit: LogEntryHit) => ({ id: hit._id, index: hit._index, cursor: getLogEntryCursorFromHit(hit), - fields: Object.entries(hit.fields).map(([field, value]) => ({ field, value })), + fields: Object.entries(hit.fields ?? {}).map(([field, value]) => ({ field, value })), }); diff --git a/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts b/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts index 460703b22766f1..aa640f106d1ee9 100644 --- a/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts +++ b/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts @@ -120,10 +120,10 @@ const createHighlightQuery = ( export const logEntryHitRT = rt.intersection([ commonHitFieldsRT, rt.type({ - fields: rt.record(rt.string, jsonArrayRT), sort: rt.tuple([rt.number, rt.number]), }), rt.partial({ + fields: rt.record(rt.string, jsonArrayRT), highlight: rt.record(rt.string, rt.array(rt.string)), }), ]); diff --git a/x-pack/plugins/infra/server/services/log_entries/queries/log_entry.ts b/x-pack/plugins/infra/server/services/log_entries/queries/log_entry.ts index 74a12f14adcaa7..51714be775e977 100644 --- a/x-pack/plugins/infra/server/services/log_entries/queries/log_entry.ts +++ b/x-pack/plugins/infra/server/services/log_entries/queries/log_entry.ts @@ -39,9 +39,11 @@ export const createGetLogEntryQuery = ( export const logEntryHitRT = rt.intersection([ commonHitFieldsRT, rt.type({ - fields: rt.record(rt.string, jsonArrayRT), sort: rt.tuple([rt.number, rt.number]), }), + rt.partial({ + fields: rt.record(rt.string, jsonArrayRT), + }), ]); export type LogEntryHit = rt.TypeOf; From 1527ab510b9d580b9f6f2b5dbbfa6f789118554c Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 24 Mar 2021 16:01:52 +0100 Subject: [PATCH 65/93] [Search Sessions] Improve search session name edit test (#95152) --- x-pack/test/functional/services/search_sessions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/services/search_sessions.ts b/x-pack/test/functional/services/search_sessions.ts index 3ecb056c06074a..34bea998925e50 100644 --- a/x-pack/test/functional/services/search_sessions.ts +++ b/x-pack/test/functional/services/search_sessions.ts @@ -72,7 +72,9 @@ export function SearchSessionsProvider({ getService }: FtrProviderContext) { if (searchSessionName) { await testSubjects.click('searchSessionNameEdit'); - await testSubjects.setValue('searchSessionNameInput', searchSessionName); + await testSubjects.setValue('searchSessionNameInput', searchSessionName, { + clearWithKeyboard: true, + }); await testSubjects.click('searchSessionNameSave'); } From 759a52c74db62ac9d86416896f356b21eb8ec064 Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 24 Mar 2021 08:36:23 -0700 Subject: [PATCH 66/93] [App Search] Add describe('listeners') blocks to older logic tests (#95215) * Add describe('listener') blocks to older logic tests * [Misc] LogRetentionLogic - move 2 it() blocks not within a describe() to its parent listener --- .../credentials/credentials_logic.test.ts | 2 + .../document_creation_logic.test.ts | 2 + .../documents/document_detail_logic.test.ts | 2 + .../components/engine/engine_logic.test.ts | 2 + .../engine_overview_logic.test.ts | 2 + .../log_retention/log_retention_logic.test.ts | 64 ++++++++++--------- 6 files changed, 43 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts index a178228f4996bf..bf84b03e7603ee 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts @@ -1025,7 +1025,9 @@ describe('CredentialsLogic', () => { }); }); }); + }); + describe('listeners', () => { describe('fetchCredentials', () => { const meta = { page: { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts index 37d3d1577767f0..2c6cadf9a8ece5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts @@ -294,7 +294,9 @@ describe('DocumentCreationLogic', () => { }); }); }); + }); + describe('listeners', () => { describe('onSubmitFile', () => { describe('with a valid file', () => { beforeAll(() => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts index d2683fac649a0d..add5e9414be133 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts @@ -54,7 +54,9 @@ describe('DocumentDetailLogic', () => { }); }); }); + }); + describe('listeners', () => { describe('getDocumentDetails', () => { it('will call an API endpoint and then store the result', async () => { const fields = [{ name: 'name', value: 'python', type: 'string' }]; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts index bf2fba6344e7a4..b9ec83db99f709 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts @@ -172,7 +172,9 @@ describe('EngineLogic', () => { }); }); }); + }); + describe('listeners', () => { describe('initializeEngine', () => { it('fetches and sets engine data', async () => { mount({ engineName: 'some-engine' }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts index df8ed920e88dfa..decadba1092d39 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts @@ -78,7 +78,9 @@ describe('EngineOverviewLogic', () => { }); }); }); + }); + describe('listeners', () => { describe('pollForOverviewMetrics', () => { it('fetches data and calls onPollingSuccess', async () => { mount(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts index 19bd2af50aad97..7b63397ac6380e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts @@ -177,7 +177,9 @@ describe('LogRetentionLogic', () => { }); }); }); + }); + describe('listeners', () => { describe('saveLogRetention', () => { beforeEach(() => { mount(); @@ -264,6 +266,37 @@ describe('LogRetentionLogic', () => { LogRetentionOptions.Analytics ); }); + + it('will call saveLogRetention if NOT already enabled', () => { + mount({ + logRetention: { + [LogRetentionOptions.Analytics]: { + enabled: false, + }, + }, + }); + jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); + + LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.Analytics); + + expect(LogRetentionLogic.actions.saveLogRetention).toHaveBeenCalledWith( + LogRetentionOptions.Analytics, + true + ); + }); + + it('will do nothing if logRetention option is not yet set', () => { + mount({ + logRetention: {}, + }); + jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); + jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); + + LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.API); + + expect(LogRetentionLogic.actions.saveLogRetention).not.toHaveBeenCalled(); + expect(LogRetentionLogic.actions.setOpenedModal).not.toHaveBeenCalled(); + }); }); describe('fetchLogRetention', () => { @@ -306,36 +339,5 @@ describe('LogRetentionLogic', () => { expect(http.get).not.toHaveBeenCalled(); }); }); - - it('will call saveLogRetention if NOT already enabled', () => { - mount({ - logRetention: { - [LogRetentionOptions.Analytics]: { - enabled: false, - }, - }, - }); - jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); - - LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.Analytics); - - expect(LogRetentionLogic.actions.saveLogRetention).toHaveBeenCalledWith( - LogRetentionOptions.Analytics, - true - ); - }); - - it('will do nothing if logRetention option is not yet set', () => { - mount({ - logRetention: {}, - }); - jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); - jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); - - LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.API); - - expect(LogRetentionLogic.actions.saveLogRetention).not.toHaveBeenCalled(); - expect(LogRetentionLogic.actions.setOpenedModal).not.toHaveBeenCalled(); - }); }); }); From 3639aa442283bdbdd9132860a77f267bc841e061 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Wed, 24 Mar 2021 11:44:22 -0400 Subject: [PATCH 67/93] [Fleet] Bulk reassign response should include all given ids (#95024) ## Summary `/agents/bulk_reassign` should return a response with a result for each agent given; including invalid or missing ids. It currently filters out missing or invalid before updating. This PR leaves them in and includes their error results in the response. [Added/updated tests](https://github.com/elastic/kibana/pull/95024/files#diff-7ec94bee3e2bae79e5d98b8c17c17b26fad14736143ffa144f3e035773d4cad1R113-R128) to confirm ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../fleet/common/types/rest_spec/agent.ts | 11 ++- .../fleet/server/routes/agent/handlers.ts | 20 ++-- .../fleet/server/services/agents/crud.ts | 44 +++++---- .../fleet/server/services/agents/reassign.ts | 95 ++++++++++++++----- x-pack/plugins/fleet/server/types/index.tsx | 6 ++ .../apis/agents/reassign.ts | 64 ++++++++++++- 6 files changed, 180 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 93cbb8369a3b13..b654c513e0afbc 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -164,12 +164,13 @@ export interface PostBulkAgentReassignRequest { }; } -export interface PostBulkAgentReassignResponse { - [key: string]: { +export type PostBulkAgentReassignResponse = Record< + Agent['id'], + { success: boolean; - error?: Error; - }; -} + error?: string; + } +>; export interface GetOneAgentEventsRequest { params: { diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index e6188a83c49e9b..5ac264e29f079f 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -308,26 +308,26 @@ export const postBulkAgentsReassignHandler: RequestHandler< const soClient = context.core.savedObjects.client; const esClient = context.core.elasticsearch.client.asInternalUser; + const agentOptions = Array.isArray(request.body.agents) + ? { agentIds: request.body.agents } + : { kuery: request.body.agents }; try { const results = await AgentService.reassignAgents( soClient, esClient, - Array.isArray(request.body.agents) - ? { agentIds: request.body.agents } - : { kuery: request.body.agents }, + agentOptions, request.body.policy_id ); - const body: PostBulkAgentReassignResponse = results.items.reduce((acc, so) => { - return { - ...acc, - [so.id]: { - success: !so.error, - error: so.error || undefined, - }, + const body = results.items.reduce((acc, so) => { + acc[so.id] = { + success: !so.error, + error: so.error?.message, }; + return acc; }, {}); + return response.ok({ body }); } catch (error) { return defaultIngestErrorHandler({ error, response }); diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 22e9f559c56b8a..9aa7bbc9f2b18b 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -9,8 +9,8 @@ import Boom from '@hapi/boom'; import type { SearchResponse, MGetResponse, GetResponse } from 'elasticsearch'; import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; +import type { AgentSOAttributes, Agent, BulkActionResult, ListWithKuery } from '../../types'; import type { ESSearchResponse } from '../../../../../../typings/elasticsearch'; -import type { AgentSOAttributes, Agent, ListWithKuery } from '../../types'; import { appContextService, agentPolicyService } from '../../services'; import type { FleetServerAgent } from '../../../common'; import { isAgentUpgradeable, SO_SEARCH_LIMIT } from '../../../common'; @@ -69,22 +69,23 @@ export type GetAgentsOptions = }; export async function getAgents(esClient: ElasticsearchClient, options: GetAgentsOptions) { - let initialResults = []; - + let agents: Agent[] = []; if ('agentIds' in options) { - initialResults = await getAgentsById(esClient, options.agentIds); + agents = await getAgentsById(esClient, options.agentIds); } else if ('kuery' in options) { - initialResults = ( + agents = ( await getAllAgentsByKuery(esClient, { kuery: options.kuery, showInactive: options.showInactive ?? false, }) ).agents; } else { - throw new IngestManagerError('Cannot get agents'); + throw new IngestManagerError( + 'Either options.agentIds or options.kuery are required to get agents' + ); } - return initialResults; + return agents; } export async function getAgentsByKuery( @@ -188,7 +189,7 @@ export async function countInactiveAgents( export async function getAgentById(esClient: ElasticsearchClient, agentId: string) { const agentNotFoundError = new AgentNotFoundError(`Agent ${agentId} not found`); try { - const agentHit = await esClient.get>({ + const agentHit = await esClient.get({ index: AGENTS_INDEX, id: agentId, }); @@ -207,10 +208,17 @@ export async function getAgentById(esClient: ElasticsearchClient, agentId: strin } } -async function getAgentDocuments( +export function isAgentDocument( + maybeDocument: any +): maybeDocument is GetResponse { + return '_id' in maybeDocument && '_source' in maybeDocument; +} + +export type ESAgentDocumentResult = GetResponse; +export async function getAgentDocuments( esClient: ElasticsearchClient, agentIds: string[] -): Promise>> { +): Promise { const res = await esClient.mget>({ index: AGENTS_INDEX, body: { docs: agentIds.map((_id) => ({ _id })) }, @@ -221,14 +229,16 @@ async function getAgentDocuments( export async function getAgentsById( esClient: ElasticsearchClient, - agentIds: string[], - options: { includeMissing?: boolean } = { includeMissing: false } + agentIds: string[] ): Promise { const allDocs = await getAgentDocuments(esClient, agentIds); - const agentDocs = options.includeMissing - ? allDocs - : allDocs.filter((res) => res._id && res._source); - const agents = agentDocs.map((doc) => searchHitToAgent(doc)); + const agents = allDocs.reduce((results, doc) => { + if (isAgentDocument(doc)) { + results.push(searchHitToAgent(doc)); + } + + return results; + }, []); return agents; } @@ -276,7 +286,7 @@ export async function bulkUpdateAgents( agentId: string; data: Partial; }> -) { +): Promise<{ items: BulkActionResult[] }> { if (updateData.length === 0) { return { items: [] }; } diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts index 74e60c42b99734..5574c42ced0530 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.ts @@ -8,13 +8,20 @@ import type { SavedObjectsClientContract, ElasticsearchClient } from 'kibana/server'; import Boom from '@hapi/boom'; -import type { Agent } from '../../types'; +import type { Agent, BulkActionResult } from '../../types'; import { agentPolicyService } from '../agent_policy'; import { AgentReassignmentError } from '../../errors'; -import { getAgents, getAgentPolicyForAgent, updateAgent, bulkUpdateAgents } from './crud'; +import { + getAgentDocuments, + getAgents, + getAgentPolicyForAgent, + updateAgent, + bulkUpdateAgents, +} from './crud'; import type { GetAgentsOptions } from './index'; import { createAgentAction, bulkCreateAgentActions } from './actions'; +import { searchHitToAgent } from './helpers'; export async function reassignAgent( soClient: SavedObjectsClientContract, @@ -67,39 +74,67 @@ export async function reassignAgentIsAllowed( export async function reassignAgents( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, - options: { agents: Agent[] } | GetAgentsOptions, + options: ({ agents: Agent[] } | GetAgentsOptions) & { force?: boolean }, newAgentPolicyId: string -): Promise<{ items: Array<{ id: string; success: boolean; error?: Error }> }> { +): Promise<{ items: BulkActionResult[] }> { const agentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId); if (!agentPolicy) { throw Boom.notFound(`Agent policy not found: ${newAgentPolicyId}`); } - const allResults = 'agents' in options ? options.agents : await getAgents(esClient, options); + const outgoingErrors: Record = {}; + let givenAgents: Agent[] = []; + if ('agents' in options) { + givenAgents = options.agents; + } else if ('agentIds' in options) { + const givenAgentsResults = await getAgentDocuments(esClient, options.agentIds); + for (const agentResult of givenAgentsResults) { + if (agentResult.found === false) { + outgoingErrors[agentResult._id] = new AgentReassignmentError( + `Cannot find agent ${agentResult._id}` + ); + } else { + givenAgents.push(searchHitToAgent(agentResult)); + } + } + } else if ('kuery' in options) { + givenAgents = await getAgents(esClient, options); + } + const givenOrder = + 'agentIds' in options ? options.agentIds : givenAgents.map((agent) => agent.id); + // which are allowed to unenroll - const settled = await Promise.allSettled( - allResults.map((agent) => - reassignAgentIsAllowed(soClient, esClient, agent.id, newAgentPolicyId).then((_) => agent) - ) + const agentResults = await Promise.allSettled( + givenAgents.map(async (agent, index) => { + if (agent.policy_id === newAgentPolicyId) { + throw new AgentReassignmentError(`${agent.id} is already assigned to ${newAgentPolicyId}`); + } + + const isAllowed = await reassignAgentIsAllowed( + soClient, + esClient, + agent.id, + newAgentPolicyId + ); + if (isAllowed) { + return agent; + } + throw new AgentReassignmentError(`${agent.id} may not be reassigned to ${newAgentPolicyId}`); + }) ); // Filter to agents that do not already use the new agent policy ID - const agentsToUpdate = allResults.filter((agent, index) => { - if (settled[index].status === 'fulfilled') { - if (agent.policy_id === newAgentPolicyId) { - settled[index] = { - status: 'rejected', - reason: new AgentReassignmentError( - `${agent.id} is already assigned to ${newAgentPolicyId}` - ), - }; - } else { - return true; - } + const agentsToUpdate = agentResults.reduce((agents, result, index) => { + if (result.status === 'fulfilled') { + agents.push(result.value); + } else { + const id = givenAgents[index].id; + outgoingErrors[id] = result.reason; } - }); + return agents; + }, []); - const res = await bulkUpdateAgents( + await bulkUpdateAgents( esClient, agentsToUpdate.map((agent) => ({ agentId: agent.id, @@ -110,6 +145,18 @@ export async function reassignAgents( })) ); + const orderedOut = givenOrder.map((agentId) => { + const hasError = agentId in outgoingErrors; + const result: BulkActionResult = { + id: agentId, + success: !hasError, + }; + if (hasError) { + result.error = outgoingErrors[agentId]; + } + return result; + }); + const now = new Date().toISOString(); await bulkCreateAgentActions( soClient, @@ -121,5 +168,5 @@ export async function reassignAgents( })) ); - return res; + return { items: orderedOut }; } diff --git a/x-pack/plugins/fleet/server/types/index.tsx b/x-pack/plugins/fleet/server/types/index.tsx index c25b047c0e1ad6..2b46f7e76a7198 100644 --- a/x-pack/plugins/fleet/server/types/index.tsx +++ b/x-pack/plugins/fleet/server/types/index.tsx @@ -90,5 +90,11 @@ export type AgentPolicyUpdateHandler = ( agentPolicyId: string ) => Promise; +export interface BulkActionResult { + id: string; + success: boolean; + error?: Error; +} + export * from './models'; export * from './rest_spec'; diff --git a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts index 77da9ecce3294c..627cb299f0909d 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts @@ -101,15 +101,32 @@ export default function (providerContext: FtrProviderContext) { expect(agent3data.body.item.policy_id).to.eql('policy2'); }); - it('should allow to reassign multiple agents by id -- some invalid', async () => { - await supertest + it('should allow to reassign multiple agents by id -- mix valid & invalid', async () => { + const { body } = await supertest .post(`/api/fleet/agents/bulk_reassign`) .set('kbn-xsrf', 'xxx') .send({ agents: ['agent2', 'INVALID_ID', 'agent3', 'MISSING_ID', 'etc'], policy_id: 'policy2', - }) - .expect(200); + }); + + expect(body).to.eql({ + agent2: { success: true }, + INVALID_ID: { + success: false, + error: 'Cannot find agent INVALID_ID', + }, + agent3: { success: true }, + MISSING_ID: { + success: false, + error: 'Cannot find agent MISSING_ID', + }, + etc: { + success: false, + error: 'Cannot find agent etc', + }, + }); + const [agent2data, agent3data] = await Promise.all([ supertest.get(`/api/fleet/agents/agent2`), supertest.get(`/api/fleet/agents/agent3`), @@ -118,6 +135,45 @@ export default function (providerContext: FtrProviderContext) { expect(agent3data.body.item.policy_id).to.eql('policy2'); }); + it('should allow to reassign multiple agents by id -- mixed invalid, managed, etc', async () => { + // agent1 is enrolled in policy1. set policy1 to managed + await supertest + .put(`/api/fleet/agent_policies/policy1`) + .set('kbn-xsrf', 'xxx') + .send({ name: 'Test policy', namespace: 'default', is_managed: true }) + .expect(200); + + const { body } = await supertest + .post(`/api/fleet/agents/bulk_reassign`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: ['agent2', 'INVALID_ID', 'agent3'], + policy_id: 'policy2', + }); + + expect(body).to.eql({ + agent2: { + success: false, + error: 'Cannot reassign an agent from managed agent policy policy1', + }, + INVALID_ID: { + success: false, + error: 'Cannot find agent INVALID_ID', + }, + agent3: { + success: false, + error: 'Cannot reassign an agent from managed agent policy policy1', + }, + }); + + const [agent2data, agent3data] = await Promise.all([ + supertest.get(`/api/fleet/agents/agent2`), + supertest.get(`/api/fleet/agents/agent3`), + ]); + expect(agent2data.body.item.policy_id).to.eql('policy1'); + expect(agent3data.body.item.policy_id).to.eql('policy1'); + }); + it('should allow to reassign multiple agents by kuery', async () => { await supertest .post(`/api/fleet/agents/bulk_reassign`) From de3a7d6f0d1091cd26ab29a49b138bb468a38ae1 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Wed, 24 Mar 2021 10:45:51 -0500 Subject: [PATCH 68/93] Use `es` instead of `legacyEs` in APM API integration test (#95303) References #83910. --- x-pack/test/apm_api_integration/tests/feature_controls.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/apm_api_integration/tests/feature_controls.ts b/x-pack/test/apm_api_integration/tests/feature_controls.ts index e82b14d6cb7e62..edeffe1e5c296a 100644 --- a/x-pack/test/apm_api_integration/tests/feature_controls.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.ts @@ -14,7 +14,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) const supertestWithoutAuth = getService('supertestWithoutAuth'); const security = getService('security'); const spaces = getService('spaces'); - const es = getService('legacyEs'); + const es = getService('es'); const log = getService('log'); const start = encodeURIComponent(new Date(Date.now() - 10000).toISOString()); From 0551472cd90c2b29919c44ccf1c492b85e0fdb62 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 24 Mar 2021 08:47:10 -0700 Subject: [PATCH 69/93] [jest] switch to jest-environment-jsdom (#95125) Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 1 + packages/kbn-test/jest-preset.js | 2 +- x-pack/plugins/global_search_bar/jest.config.js | 3 +++ x-pack/plugins/lens/jest.config.js | 3 +++ x-pack/plugins/security_solution/jest.config.js | 3 +++ 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 32cf8dc1aee0f3..7cb6a505eeafe4 100644 --- a/package.json +++ b/package.json @@ -697,6 +697,7 @@ "jest-cli": "^26.6.3", "jest-diff": "^26.6.2", "jest-environment-jsdom-thirteen": "^1.0.1", + "jest-environment-jsdom": "^26.6.2", "jest-raw-loader": "^1.0.1", "jest-silent-reporter": "^0.2.1", "jest-snapshot": "^26.6.2", diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index a1475985af8df0..4949d6d1f9fad4 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -68,7 +68,7 @@ module.exports = { ], // The test environment that will be used for testing - testEnvironment: 'jest-environment-jsdom-thirteen', + testEnvironment: 'jest-environment-jsdom', // The glob patterns Jest uses to detect test files testMatch: ['**/*.test.{js,mjs,ts,tsx}'], diff --git a/x-pack/plugins/global_search_bar/jest.config.js b/x-pack/plugins/global_search_bar/jest.config.js index 73cf5402a83a93..26a6934226ec43 100644 --- a/x-pack/plugins/global_search_bar/jest.config.js +++ b/x-pack/plugins/global_search_bar/jest.config.js @@ -9,4 +9,7 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', roots: ['/x-pack/plugins/global_search_bar'], + + // TODO: migrate to "jest-environment-jsdom" https://github.com/elastic/kibana/issues/95200 + testEnvironment: 'jest-environment-jsdom-thirteen', }; diff --git a/x-pack/plugins/lens/jest.config.js b/x-pack/plugins/lens/jest.config.js index 615e540eaedce2..9a3f12e1ead32b 100644 --- a/x-pack/plugins/lens/jest.config.js +++ b/x-pack/plugins/lens/jest.config.js @@ -9,4 +9,7 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', roots: ['/x-pack/plugins/lens'], + + // TODO: migrate to "jest-environment-jsdom" https://github.com/elastic/kibana/issues/95202 + testEnvironment: 'jest-environment-jsdom-thirteen', }; diff --git a/x-pack/plugins/security_solution/jest.config.js b/x-pack/plugins/security_solution/jest.config.js index 700eaebf6c2023..b4dcedfcceeeeb 100644 --- a/x-pack/plugins/security_solution/jest.config.js +++ b/x-pack/plugins/security_solution/jest.config.js @@ -9,4 +9,7 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', roots: ['/x-pack/plugins/security_solution'], + + // TODO: migrate to "jest-environment-jsdom" https://github.com/elastic/kibana/issues/95201 + testEnvironment: 'jest-environment-jsdom-thirteen', }; From 2e5b5debb5e198a4f6dfd45dafdb5b4cdfecb561 Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Wed, 24 Mar 2021 17:04:49 +0100 Subject: [PATCH 70/93] [Fleet] Add force option to DELETE package endpoint. (#95051) * Add force option to DELETE package endpoint. * Add integration test. * Adjust openapi spec. * Run EPM tests before fleet setup tests. * Run package delete tests first in EPM tests --- .../plugins/fleet/common/openapi/bundled.json | 16 +++++- .../plugins/fleet/common/openapi/bundled.yaml | 8 +++ .../openapi/paths/epm@packages@{pkgkey}.yaml | 8 +++ .../fleet/server/routes/epm/handlers.ts | 11 +++- .../server/services/epm/packages/remove.ts | 5 +- .../fleet/server/types/rest_spec/epm.ts | 5 ++ .../fleet_api_integration/apis/epm/delete.ts | 54 +++++++++++++++++++ .../fleet_api_integration/apis/epm/index.js | 1 + .../test/fleet_api_integration/apis/index.js | 6 +-- 9 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 x-pack/test/fleet_api_integration/apis/epm/delete.ts diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 55c32802c3334d..388aebed9a85b4 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -1633,7 +1633,21 @@ { "$ref": "#/paths/~1setup/post/parameters/0" } - ] + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "force": { + "type": "boolean" + } + } + } + } + } + } } }, "/agents/{agentId}": { diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 9461927bb09b86..227faffdac489f 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -1038,6 +1038,14 @@ paths: operationId: post-epm-delete-pkgkey parameters: - $ref: '#/paths/~1setup/post/parameters/0' + requestBody: + content: + application/json: + schema: + type: object + properties: + force: + type: boolean '/agents/{agentId}': parameters: - schema: diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}.yaml index 43937aa153f504..85d8615a9eb4b9 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}.yaml @@ -89,3 +89,11 @@ delete: operationId: post-epm-delete-pkgkey parameters: - $ref: ../components/headers/kbn_xsrf.yaml + requestBody: + content: + application/json: + schema: + type: object + properties: + force: + type: boolean diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 3ac951f7987f83..f0d6e684273614 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -310,13 +310,20 @@ export const installPackageByUploadHandler: RequestHandler< }; export const deletePackageHandler: RequestHandler< - TypeOf + TypeOf, + undefined, + TypeOf > = async (context, request, response) => { try { const { pkgkey } = request.params; const savedObjectsClient = context.core.savedObjects.client; const esClient = context.core.elasticsearch.client.asCurrentUser; - const res = await removeInstallation({ savedObjectsClient, pkgkey, esClient }); + const res = await removeInstallation({ + savedObjectsClient, + pkgkey, + esClient, + force: request.body?.force, + }); const body: DeletePackageResponse = { response: res, }; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts index 21e4e31be2bd0d..de798e822b0298 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts @@ -32,13 +32,14 @@ export async function removeInstallation(options: { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; esClient: ElasticsearchClient; + force?: boolean; }): Promise { - const { savedObjectsClient, pkgkey, esClient } = options; + const { savedObjectsClient, pkgkey, esClient, force } = options; // TODO: the epm api should change to /name/version so we don't need to do this const { pkgName, pkgVersion } = splitPkgKey(pkgkey); const installation = await getInstallation({ savedObjectsClient, pkgName }); if (!installation) throw Boom.badRequest(`${pkgName} is not installed`); - if (installation.removable === false) + if (installation.removable === false && !force) throw Boom.badRequest(`${pkgName} is installed by default and cannot be removed`); const { total } = await packagePolicyService.list(savedObjectsClient, { diff --git a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts index 2243a7d3930cd3..f7e3ed906e24b0 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts @@ -65,4 +65,9 @@ export const DeletePackageRequestSchema = { params: schema.object({ pkgkey: schema.string(), }), + body: schema.nullable( + schema.object({ + force: schema.boolean(), + }) + ), }; diff --git a/x-pack/test/fleet_api_integration/apis/epm/delete.ts b/x-pack/test/fleet_api_integration/apis/epm/delete.ts new file mode 100644 index 00000000000000..ecf9eb625bd7e0 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/epm/delete.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const requiredPackage = 'system-0.11.0'; + + const installPackage = async (pkgkey: string) => { + await supertest + .post(`/api/fleet/epm/packages/${pkgkey}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + }; + + const deletePackage = async (pkgkey: string) => { + await supertest + .delete(`/api/fleet/epm/packages/${pkgkey}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + }; + + describe('delete and force delete scenarios', async () => { + skipIfNoDockerRegistry(providerContext); + before(async () => { + await installPackage(requiredPackage); + }); + after(async () => { + await deletePackage(requiredPackage); + }); + + it('should return 400 if trying to uninstall a required package', async function () { + await supertest + .delete(`/api/fleet/epm/packages/${requiredPackage}`) + .set('kbn-xsrf', 'xxxx') + .expect(400); + }); + + it('should return 200 if trying to force uninstall a required package', async function () { + await supertest + .delete(`/api/fleet/epm/packages/${requiredPackage}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }) + .expect(200); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/epm/index.js b/x-pack/test/fleet_api_integration/apis/epm/index.js index 0020e6bdf1bb01..009e1a2dad5f15 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/index.js +++ b/x-pack/test/fleet_api_integration/apis/epm/index.js @@ -7,6 +7,7 @@ export default function loadTests({ loadTestFile }) { describe('EPM Endpoints', () => { + loadTestFile(require.resolve('./delete')); loadTestFile(require.resolve('./list')); loadTestFile(require.resolve('./setup')); loadTestFile(require.resolve('./get')); diff --git a/x-pack/test/fleet_api_integration/apis/index.js b/x-pack/test/fleet_api_integration/apis/index.js index 3d7ee3686b575c..27987e469cfe7b 100644 --- a/x-pack/test/fleet_api_integration/apis/index.js +++ b/x-pack/test/fleet_api_integration/apis/index.js @@ -8,6 +8,9 @@ export default function ({ loadTestFile }) { describe('Fleet Endpoints', function () { this.tags('ciGroup10'); + // EPM + loadTestFile(require.resolve('./epm/index')); + // Fleet setup loadTestFile(require.resolve('./fleet_setup')); @@ -30,9 +33,6 @@ export default function ({ loadTestFile }) { // Enrollment API keys loadTestFile(require.resolve('./enrollment_api_keys/crud')); - // EPM - loadTestFile(require.resolve('./epm/index')); - // Package policies loadTestFile(require.resolve('./package_policy/create')); loadTestFile(require.resolve('./package_policy/update')); From d0c09463d00d5e4433ab016f8d04558d15b61e86 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Wed, 24 Mar 2021 12:39:39 -0400 Subject: [PATCH 71/93] [Upgrade Assistant] Reorganize folder structure (#94843) --- .../public/application/components/_index.scss | 4 ++-- .../__fixtures__/checkup_api_response.json | 0 .../__snapshots__/filter_bar.test.tsx.snap | 0 .../__snapshots__/group_by_bar.test.tsx.snap | 0 .../{tabs/checkup => es_deprecations}/_index.scss | 0 .../{tabs/checkup => es_deprecations}/constants.tsx | 10 ++++++---- .../{tabs/checkup => es_deprecations}/controls.tsx | 7 +++---- .../deprecation_tab.tsx} | 8 ++++---- .../deprecations/_cell.scss | 0 .../deprecations/_deprecations.scss | 0 .../deprecations/_index.scss | 0 .../checkup => es_deprecations}/deprecations/cell.tsx | 4 ++-- .../deprecations/count_summary.tsx | 2 +- .../deprecations/grouped.test.tsx | 4 ++-- .../deprecations/grouped.tsx | 4 ++-- .../deprecations/health.tsx | 2 +- .../checkup => es_deprecations}/deprecations/index.tsx | 0 .../deprecations/index_settings/button.tsx | 0 .../deprecations/index_settings/index.ts | 0 .../index_settings/remove_settings_provider.tsx | 2 +- .../deprecations/index_table.test.tsx | 0 .../deprecations/index_table.tsx | 4 ++-- .../deprecations/list.test.tsx | 4 ++-- .../checkup => es_deprecations}/deprecations/list.tsx | 4 ++-- .../deprecations/reindex/_button.scss | 0 .../deprecations/reindex/_index.scss | 0 .../deprecations/reindex/button.tsx | 6 +++--- .../flyout/__snapshots__/checklist_step.test.tsx.snap | 0 .../flyout/__snapshots__/warning_step.test.tsx.snap | 0 .../deprecations/reindex/flyout/_index.scss | 0 .../deprecations/reindex/flyout/_step_progress.scss | 0 .../reindex/flyout/checklist_step.test.tsx | 4 ++-- .../deprecations/reindex/flyout/checklist_step.tsx | 4 ++-- .../deprecations/reindex/flyout/container.tsx | 2 +- .../deprecations/reindex/flyout/index.tsx | 0 .../deprecations/reindex/flyout/progress.test.tsx | 2 +- .../deprecations/reindex/flyout/progress.tsx | 4 ++-- .../deprecations/reindex/flyout/step_progress.tsx | 0 .../deprecations/reindex/flyout/warning_step.test.tsx | 8 ++++---- .../reindex/flyout/warning_step_checkbox.tsx | 2 +- .../deprecations/reindex/flyout/warnings_step.tsx | 4 ++-- .../deprecations/reindex/index.tsx | 0 .../deprecations/reindex/polling_service.test.ts | 2 +- .../deprecations/reindex/polling_service.ts | 6 +++--- .../checkup => es_deprecations}/filter_bar.test.tsx | 4 ++-- .../{tabs/checkup => es_deprecations}/filter_bar.tsx | 4 ++-- .../checkup => es_deprecations}/group_by_bar.test.tsx | 2 +- .../{tabs/checkup => es_deprecations}/group_by_bar.tsx | 2 +- .../application/components/es_deprecations/index.ts | 8 ++++++++ .../components/{tabs => }/overview/_index.scss | 0 .../components/{tabs => }/overview/_steps.scss | 0 .../{tabs => }/overview/deprecation_logging_toggle.tsx | 4 ++-- .../{tabs/checkup/index.tsx => overview/index.ts} | 2 +- .../{tabs/overview/index.tsx => overview/overview.tsx} | 6 +++--- .../components/{tabs => }/overview/steps.tsx | 4 ++-- .../public/application/components/tabs.tsx | 8 ++++---- .../public/application/{ => lib}/utils.test.ts | 0 .../public/application/{ => lib}/utils.ts | 0 58 files changed, 78 insertions(+), 69 deletions(-) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs => es_deprecations}/__fixtures__/checkup_api_response.json (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/__snapshots__/filter_bar.test.tsx.snap (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/__snapshots__/group_by_bar.test.tsx.snap (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/_index.scss (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/constants.tsx (70%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/controls.tsx (95%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup/checkup_tab.tsx => es_deprecations/deprecation_tab.tsx} (97%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/_cell.scss (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/_deprecations.scss (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/_index.scss (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/cell.tsx (95%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/count_summary.tsx (95%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/grouped.test.tsx (98%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/grouped.tsx (98%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/health.tsx (97%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/index.tsx (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/index_settings/button.tsx (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/index_settings/index.ts (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/index_settings/remove_settings_provider.tsx (98%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/index_table.test.tsx (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/index_table.tsx (97%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/list.test.tsx (96%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/list.tsx (98%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/_button.scss (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/_index.scss (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/button.tsx (97%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/__snapshots__/checklist_step.test.tsx.snap (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/__snapshots__/warning_step.test.tsx.snap (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/_index.scss (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/_step_progress.scss (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/checklist_step.test.tsx (94%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/checklist_step.tsx (97%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/container.tsx (99%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/index.tsx (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/progress.test.tsx (99%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/progress.tsx (99%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/step_progress.tsx (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/warning_step.test.tsx (88%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/warning_step_checkbox.tsx (99%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/flyout/warnings_step.tsx (97%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/index.tsx (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/polling_service.test.ts (97%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/deprecations/reindex/polling_service.ts (96%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/filter_bar.test.tsx (90%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/filter_bar.tsx (94%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/group_by_bar.test.tsx (95%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup => es_deprecations}/group_by_bar.tsx (97%) create mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/index.ts rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs => }/overview/_index.scss (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs => }/overview/_steps.scss (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs => }/overview/deprecation_logging_toggle.tsx (96%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/checkup/index.tsx => overview/index.ts} (85%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs/overview/index.tsx => overview/overview.tsx} (91%) rename x-pack/plugins/upgrade_assistant/public/application/components/{tabs => }/overview/steps.tsx (99%) rename x-pack/plugins/upgrade_assistant/public/application/{ => lib}/utils.test.ts (100%) rename x-pack/plugins/upgrade_assistant/public/application/{ => lib}/utils.ts (100%) diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/_index.scss index bb01107f334f65..8f900ca8dc055b 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/_index.scss +++ b/x-pack/plugins/upgrade_assistant/public/application/components/_index.scss @@ -1,2 +1,2 @@ -@import 'tabs/checkup/index'; -@import 'tabs/overview/index'; +@import 'es_deprecations/index'; +@import 'overview/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/__fixtures__/checkup_api_response.json b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__fixtures__/checkup_api_response.json similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/__fixtures__/checkup_api_response.json rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__fixtures__/checkup_api_response.json diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/filter_bar.test.tsx.snap similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/filter_bar.test.tsx.snap diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/group_by_bar.test.tsx.snap similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/group_by_bar.test.tsx.snap diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/_index.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/_index.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/_index.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/constants.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/constants.tsx similarity index 70% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/constants.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/constants.tsx index a3e9ae783e8123..feff6010efe38b 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/constants.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/constants.tsx @@ -7,16 +7,18 @@ import { IconColor } from '@elastic/eui'; import { invert } from 'lodash'; -import { DeprecationInfo } from '../../../../../common/types'; +import { DeprecationInfo } from '../../../../common/types'; export const LEVEL_MAP: { [level: string]: number } = { warning: 0, critical: 1, }; -export const REVERSE_LEVEL_MAP: { [idx: number]: DeprecationInfo['level'] } = invert( - LEVEL_MAP -) as any; +interface ReverseLevelMap { + [idx: number]: DeprecationInfo['level']; +} + +export const REVERSE_LEVEL_MAP: ReverseLevelMap = invert(LEVEL_MAP) as ReverseLevelMap; export const COLOR_MAP: { [level: string]: IconColor } = { warning: 'default', diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/controls.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/controls.tsx similarity index 95% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/controls.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/controls.tsx index 578e9344659c27..7212c2db4c6b49 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/controls.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/controls.tsx @@ -9,13 +9,12 @@ import React, { FunctionComponent, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButton, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { DeprecationInfo } from '../../../../../common/types'; -import { GroupByOption, LevelFilterOption } from '../../types'; +import { DeprecationInfo } from '../../../../common/types'; +import { validateRegExpString } from '../../lib/utils'; +import { GroupByOption, LevelFilterOption } from '../types'; import { FilterBar } from './filter_bar'; import { GroupByBar } from './group_by_bar'; -import { validateRegExpString } from '../../../utils'; - interface CheckupControlsProps { allDeprecations?: DeprecationInfo[]; isLoading: boolean; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab.tsx similarity index 97% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab.tsx index 3c4c747529915d..a5ae341f1e4248 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab.tsx @@ -19,9 +19,9 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { LoadingErrorBanner } from '../../error_banner'; -import { useAppContext } from '../../../app_context'; -import { GroupByOption, LevelFilterOption, UpgradeAssistantTabProps } from '../../types'; +import { LoadingErrorBanner } from '../error_banner'; +import { useAppContext } from '../../app_context'; +import { GroupByOption, LevelFilterOption, UpgradeAssistantTabProps } from '../types'; import { CheckupControls } from './controls'; import { GroupedDeprecations } from './deprecations/grouped'; @@ -34,7 +34,7 @@ export interface CheckupTabProps extends UpgradeAssistantTabProps { * Displays a list of deprecations that filterable and groupable. Can be used for cluster, * nodes, or indices checkups. */ -export const CheckupTab: FunctionComponent = ({ +export const DeprecationTab: FunctionComponent = ({ alertBanner, checkupLabel, deprecations, diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_cell.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_cell.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_cell.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_cell.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_deprecations.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_deprecations.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_deprecations.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_deprecations.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_index.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_index.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_index.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/cell.tsx similarity index 95% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/cell.tsx index 890f7e63c9904a..5f960bd09d2865 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/cell.tsx @@ -17,9 +17,9 @@ import { EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { EnrichedDeprecationInfo } from '../../../../../common/types'; +import { AppContext } from '../../../app_context'; import { ReindexButton } from './reindex'; -import { AppContext } from '../../../../app_context'; -import { EnrichedDeprecationInfo } from '../../../../../../common/types'; import { FixIndexSettingsButton } from './index_settings'; interface DeprecationCellProps { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/count_summary.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/count_summary.tsx similarity index 95% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/count_summary.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/count_summary.tsx index 872e508494ed7a..db176ba43d8edb 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/count_summary.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/count_summary.tsx @@ -10,7 +10,7 @@ import React, { Fragment, FunctionComponent } from 'react'; import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EnrichedDeprecationInfo } from '../../../../../../common/types'; +import { EnrichedDeprecationInfo } from '../../../../../common/types'; export const DeprecationCountSummary: FunctionComponent<{ deprecations: EnrichedDeprecationInfo[]; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.test.tsx similarity index 98% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.test.tsx index 0885286961b115..00059fe0456ce0 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.test.tsx @@ -10,8 +10,8 @@ import React from 'react'; import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; import { EuiBadge, EuiPagination } from '@elastic/eui'; -import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../../common/types'; -import { GroupByOption, LevelFilterOption } from '../../../types'; +import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../common/types'; +import { GroupByOption, LevelFilterOption } from '../../types'; import { DeprecationAccordion, filterDeps, GroupedDeprecations } from './grouped'; describe('filterDeps', () => { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.tsx similarity index 98% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.tsx index 3c18d6fe8a6090..9879b977f1cfde 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.tsx @@ -19,8 +19,8 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../../common/types'; -import { GroupByOption, LevelFilterOption } from '../../../types'; +import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../common/types'; +import { GroupByOption, LevelFilterOption } from '../../types'; import { DeprecationCountSummary } from './count_summary'; import { DeprecationHealth } from './health'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/health.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/health.tsx similarity index 97% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/health.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/health.tsx index d7e62684c45705..c489824b1059dd 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/health.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/health.tsx @@ -11,7 +11,7 @@ import React, { FunctionComponent } from 'react'; import { EuiBadge, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DeprecationInfo } from '../../../../../../common/types'; +import { DeprecationInfo } from '../../../../../common/types'; import { COLOR_MAP, LEVEL_MAP, REVERSE_LEVEL_MAP } from '../constants'; const LocalizedLevels: { [level: string]: string } = { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index.tsx similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index.tsx diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_settings/button.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_settings/button.tsx similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_settings/button.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_settings/button.tsx diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_settings/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_settings/index.ts similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_settings/index.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_settings/index.ts diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_settings/remove_settings_provider.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_settings/remove_settings_provider.tsx similarity index 98% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_settings/remove_settings_provider.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_settings/remove_settings_provider.tsx index e344d300be06c4..1fd0c79dbbef37 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_settings/remove_settings_provider.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_settings/remove_settings_provider.tsx @@ -8,7 +8,7 @@ import React, { useState, useRef } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiCode, EuiConfirmModal } from '@elastic/eui'; -import { useAppContext } from '../../../../../app_context'; +import { useAppContext } from '../../../../app_context'; interface Props { children: ( diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.test.tsx similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.test.tsx diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.tsx similarity index 97% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.tsx index da2f5f04effc4d..216884d547eebb 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.tsx @@ -10,9 +10,9 @@ import React from 'react'; import { EuiBasicTable } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { EnrichedDeprecationInfo } from '../../../../../common/types'; +import { AppContext } from '../../../app_context'; import { ReindexButton } from './reindex'; -import { AppContext } from '../../../../app_context'; -import { EnrichedDeprecationInfo } from '../../../../../../common/types'; import { FixIndexSettingsButton } from './index_settings'; const PAGE_SIZES = [10, 25, 50, 100, 250, 500, 1000]; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.test.tsx similarity index 96% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.test.tsx index 1d646442ebee04..c1b6357d504eb7 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.test.tsx @@ -8,8 +8,8 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { EnrichedDeprecationInfo } from '../../../../../../common/types'; -import { GroupByOption } from '../../../types'; +import { EnrichedDeprecationInfo } from '../../../../../common/types'; +import { GroupByOption } from '../../types'; import { DeprecationList } from './list'; describe('DeprecationList', () => { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.tsx similarity index 98% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.tsx index eb9012b3bddd13..65b878fe36a869 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.tsx @@ -7,8 +7,8 @@ import React, { FunctionComponent } from 'react'; -import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../../common/types'; -import { GroupByOption } from '../../../types'; +import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../common/types'; +import { GroupByOption } from '../../types'; import { COLOR_MAP, LEVEL_MAP } from '../constants'; import { DeprecationCell } from './cell'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/_button.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/_button.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/_button.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/_button.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/_index.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/_index.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/_index.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/button.tsx similarity index 97% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/button.tsx index 98c22308d9373c..34c1328459cdb7 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/button.tsx @@ -13,13 +13,13 @@ import { Subscription } from 'rxjs'; import { EuiButton, EuiLoadingSpinner, EuiText, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { DocLinksStart, HttpSetup } from 'src/core/public'; -import { API_BASE_PATH } from '../../../../../../../common/constants'; +import { API_BASE_PATH } from '../../../../../../common/constants'; import { EnrichedDeprecationInfo, ReindexStatus, UIReindexOption, -} from '../../../../../../../common/types'; -import { LoadingState } from '../../../../types'; +} from '../../../../../../common/types'; +import { LoadingState } from '../../../types'; import { ReindexFlyout } from './flyout'; import { ReindexPollingService, ReindexState } from './polling_service'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/__snapshots__/checklist_step.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/__snapshots__/checklist_step.test.tsx.snap similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/__snapshots__/checklist_step.test.tsx.snap rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/__snapshots__/checklist_step.test.tsx.snap diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/__snapshots__/warning_step.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/__snapshots__/warning_step.test.tsx.snap similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/__snapshots__/warning_step.test.tsx.snap rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/__snapshots__/warning_step.test.tsx.snap diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/_index.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/_index.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/_index.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/_step_progress.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/_step_progress.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/_step_progress.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/_step_progress.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/checklist_step.test.tsx similarity index 94% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/checklist_step.test.tsx index 5bea0d855e45e4..f8d72addc2d188 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/checklist_step.test.tsx @@ -9,8 +9,8 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash'; import React from 'react'; -import { ReindexStatus } from '../../../../../../../../common/types'; -import { LoadingState } from '../../../../../types'; +import { ReindexStatus } from '../../../../../../../common/types'; +import { LoadingState } from '../../../../types'; import { ReindexState } from '../polling_service'; import { ChecklistFlyoutStep } from './checklist_step'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/checklist_step.tsx similarity index 97% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/checklist_step.tsx index fc4bdcc130e25d..e852171a696b4b 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/checklist_step.tsx @@ -20,8 +20,8 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ReindexStatus } from '../../../../../../../../common/types'; -import { LoadingState } from '../../../../../types'; +import { ReindexStatus } from '../../../../../../../common/types'; +import { LoadingState } from '../../../../types'; import { ReindexState } from '../polling_service'; import { ReindexProgress } from './progress'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/container.tsx similarity index 99% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/container.tsx index 2f776f3937b502..3e7b931452566e 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/container.tsx @@ -19,7 +19,7 @@ import { EuiTitle, } from '@elastic/eui'; -import { EnrichedDeprecationInfo, ReindexStatus } from '../../../../../../../../common/types'; +import { EnrichedDeprecationInfo, ReindexStatus } from '../../../../../../../common/types'; import { ReindexState } from '../polling_service'; import { ChecklistFlyoutStep } from './checklist_step'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/index.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/index.tsx similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/index.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/index.tsx diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/progress.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/progress.test.tsx similarity index 99% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/progress.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/progress.test.tsx index 690775efb9d12e..24a00af7a9fee3 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/progress.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/progress.test.tsx @@ -8,7 +8,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { IndexGroup, ReindexStatus, ReindexStep } from '../../../../../../../../common/types'; +import { IndexGroup, ReindexStatus, ReindexStep } from '../../../../../../../common/types'; import { ReindexState } from '../polling_service'; import { ReindexProgress } from './progress'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/progress.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/progress.tsx similarity index 99% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/progress.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/progress.tsx index 5d10111f3ae5e6..088266f3a48406 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/progress.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/progress.tsx @@ -17,8 +17,8 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { IndexGroup, ReindexStatus, ReindexStep } from '../../../../../../../../common/types'; -import { LoadingState } from '../../../../../types'; +import { IndexGroup, ReindexStatus, ReindexStep } from '../../../../../../../common/types'; +import { LoadingState } from '../../../../types'; import { ReindexState } from '../polling_service'; import { StepProgress, StepProgressStep } from './step_progress'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/step_progress.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/step_progress.tsx similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/step_progress.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/step_progress.tsx diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/warning_step.test.tsx similarity index 88% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/warning_step.test.tsx index ca9c53354bf755..ff11b9f1a8450c 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/warning_step.test.tsx @@ -9,14 +9,14 @@ import { I18nProvider } from '@kbn/i18n/react'; import { mount, shallow } from 'enzyme'; import React from 'react'; -import { ReindexWarning } from '../../../../../../../../common/types'; -import { mockKibanaSemverVersion } from '../../../../../../../../common/constants'; +import { ReindexWarning } from '../../../../../../../common/types'; +import { mockKibanaSemverVersion } from '../../../../../../../common/constants'; import { idForWarning, WarningsFlyoutStep } from './warnings_step'; -jest.mock('../../../../../../app_context', () => { +jest.mock('../../../../../app_context', () => { const { docLinksServiceMock } = jest.requireActual( - '../../../../../../../../../../../src/core/public/doc_links/doc_links_service.mock' + '../../../../../../../../../../src/core/public/doc_links/doc_links_service.mock' ); return { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step_checkbox.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/warning_step_checkbox.tsx similarity index 99% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step_checkbox.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/warning_step_checkbox.tsx index de41108886c002..a5e3260167218a 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step_checkbox.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/warning_step_checkbox.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { DocLinksStart } from 'kibana/public'; -import { ReindexWarning, ReindexWarningTypes } from '../../../../../../../../common/types'; +import { ReindexWarning, ReindexWarningTypes } from '../../../../../../../common/types'; export const hasReindexWarning = ( warnings: ReindexWarning[], diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/warnings_step.tsx similarity index 97% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/warnings_step.tsx index 241884e95c1eba..4415811f6bf38c 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/warnings_step.tsx @@ -19,8 +19,8 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ReindexWarning, ReindexWarningTypes } from '../../../../../../../../common/types'; -import { useAppContext } from '../../../../../../app_context'; +import { ReindexWarning, ReindexWarningTypes } from '../../../../../../../common/types'; +import { useAppContext } from '../../../../../app_context'; import { CustomTypeNameWarningCheckbox, DeprecatedSettingWarningCheckbox, diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/index.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/index.tsx similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/index.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/index.tsx diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/polling_service.test.ts similarity index 97% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/polling_service.test.ts index e7f56fc1bdf791..13818e864783e6 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/polling_service.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ReindexStatus, ReindexStep } from '../../../../../../../common/types'; +import { ReindexStatus, ReindexStep } from '../../../../../../common/types'; import { ReindexPollingService } from './polling_service'; import { httpServiceMock } from 'src/core/public/mocks'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/polling_service.ts similarity index 96% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/polling_service.ts index d6a4a8e7b5a561..239bd56bd2fa53 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/polling_service.ts @@ -8,15 +8,15 @@ import { BehaviorSubject } from 'rxjs'; import { HttpSetup } from 'src/core/public'; -import { API_BASE_PATH } from '../../../../../../../common/constants'; +import { API_BASE_PATH } from '../../../../../../common/constants'; import { IndexGroup, ReindexOperation, ReindexStatus, ReindexStep, ReindexWarning, -} from '../../../../../../../common/types'; -import { LoadingState } from '../../../../types'; +} from '../../../../../../common/types'; +import { LoadingState } from '../../../types'; const POLL_INTERVAL = 1000; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.test.tsx similarity index 90% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.test.tsx index 81e04ac7804220..feac88cf4a5251 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.test.tsx @@ -7,9 +7,9 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { DeprecationInfo } from '../../../../../common/types'; +import { DeprecationInfo } from '../../../../common/types'; -import { LevelFilterOption } from '../../types'; +import { LevelFilterOption } from '../types'; import { FilterBar } from './filter_bar'; const defaultProps = { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.tsx similarity index 94% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.tsx index ffb81832b505dc..7ef3ae2fc93325 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.tsx @@ -11,8 +11,8 @@ import React from 'react'; import { EuiFilterButton, EuiFilterGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DeprecationInfo } from '../../../../../common/types'; -import { LevelFilterOption } from '../../types'; +import { DeprecationInfo } from '../../../../common/types'; +import { LevelFilterOption } from '../types'; const LocalizedOptions: { [option: string]: string } = { all: i18n.translate('xpack.upgradeAssistant.checkupTab.controls.filterBar.allButtonLabel', { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/group_by_bar.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.test.tsx similarity index 95% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/group_by_bar.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.test.tsx index 825dd9b337e49d..53f76d6d0f9812 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/group_by_bar.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.test.tsx @@ -8,7 +8,7 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { GroupByOption } from '../../types'; +import { GroupByOption } from '../types'; import { GroupByBar } from './group_by_bar'; const defaultProps = { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/group_by_bar.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.tsx similarity index 97% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/group_by_bar.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.tsx index fc3971d4082b91..a80fe664ced2e0 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/group_by_bar.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiFilterButton, EuiFilterGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { GroupByOption } from '../../types'; +import { GroupByOption } from '../types'; const LocalizedOptions: { [option: string]: string } = { message: i18n.translate('xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIssueLabel', { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/index.ts new file mode 100644 index 00000000000000..8b7435b94b2c1a --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { DeprecationTab } from './deprecation_tab'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/overview/_index.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/_index.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/overview/_index.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/_steps.scss b/x-pack/plugins/upgrade_assistant/public/application/components/overview/_steps.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/_steps.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/overview/_steps.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/deprecation_logging_toggle.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/deprecation_logging_toggle.tsx similarity index 96% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/deprecation_logging_toggle.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/overview/deprecation_logging_toggle.tsx index b026b17c65f18e..5ed46c25ecf17c 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/deprecation_logging_toggle.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/deprecation_logging_toggle.tsx @@ -10,8 +10,8 @@ import React, { useEffect, useState } from 'react'; import { EuiLoadingSpinner, EuiSwitch } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useAppContext } from '../../../app_context'; -import { ResponseError } from '../../../lib/api'; +import { useAppContext } from '../../app_context'; +import { ResponseError } from '../../lib/api'; export const DeprecationLoggingToggle: React.FunctionComponent = () => { const { api } = useAppContext(); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/index.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/index.ts similarity index 85% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/index.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/overview/index.ts index 8c04da8b7843a2..c43c1415f6f7c2 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/index.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { CheckupTab } from './checkup_tab'; +export { OverviewTab } from './overview'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/index.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx similarity index 91% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/index.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx index 8cd3bbebd78613..01677e7394a872 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/index.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx @@ -18,9 +18,9 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useAppContext } from '../../../app_context'; -import { LoadingErrorBanner } from '../../error_banner'; -import { UpgradeAssistantTabProps } from '../../types'; +import { useAppContext } from '../../app_context'; +import { LoadingErrorBanner } from '../error_banner'; +import { UpgradeAssistantTabProps } from '../types'; import { Steps } from './steps'; export const OverviewTab: FunctionComponent = (props) => { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/steps.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/steps.tsx similarity index 99% rename from x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/steps.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/overview/steps.tsx index afd04fb02e6dda..095960ae93562d 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/steps.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/steps.tsx @@ -20,9 +20,9 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { UpgradeAssistantTabProps } from '../../types'; +import { useAppContext } from '../../app_context'; +import { UpgradeAssistantTabProps } from '../types'; import { DeprecationLoggingToggle } from './deprecation_logging_toggle'; -import { useAppContext } from '../../../app_context'; // Leaving these here even if unused so they are picked up for i18n static analysis // Keep this until last minor release (when next major is also released). diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx index 9875b4c5a1a330..231d9705bd0d91 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx @@ -19,8 +19,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { LatestMinorBanner } from './latest_minor_banner'; -import { CheckupTab } from './tabs/checkup'; -import { OverviewTab } from './tabs/overview'; +import { DeprecationTab } from './es_deprecations'; +import { OverviewTab } from './overview'; import { TelemetryState, UpgradeAssistantTabProps } from './types'; import { useAppContext } from '../app_context'; @@ -58,7 +58,7 @@ export const UpgradeAssistantTabs: React.FunctionComponent = () => { defaultMessage: 'Cluster', }), content: ( - { defaultMessage: 'Indices', }), content: ( - Date: Wed, 24 Mar 2021 10:23:07 -0700 Subject: [PATCH 72/93] [App Search] Various engines fixes (#95127) * [Misc] Update trash icon color - should be danger/red to match other tables in Kibana * Engine table - fix incorrect conditional around when users can delete engines - The check for that should be around Dev/Editor/Analyst roles, not around whether the account has a platinum license - Tests - DRY out reset mock - ideally would be in a beforeEach, but mount/beforeAll perf makes that difficult * Create engine button - wrap with canManageEngines check - prevents Dev/Editor/Analyst roles from hitting a 404 page - test cleanup - use describe blocks to convey conditional branching, combine 2 tests into 1 * Empty engines prompt - add canManageEngines check + switch from FormattedMessage to i18n (this view was created a long time ago before we settled on generally preferring i18n) + provide a more helpful body text when the user cannot create engines * Update x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx Co-authored-by: Jason Stoltzfus Co-authored-by: Jason Stoltzfus --- .../engines/components/empty_state.test.tsx | 53 ++++++--- .../engines/components/empty_state.tsx | 112 +++++++++++------- .../engines/engines_overview.test.tsx | 88 ++++++++------ .../components/engines/engines_overview.tsx | 56 +++++---- .../components/engines/engines_table.test.tsx | 46 ++++--- .../components/engines/engines_table.tsx | 9 +- 6 files changed, 216 insertions(+), 148 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.test.tsx index 14772375c9bd49..a7371744771777 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.test.tsx @@ -6,7 +6,7 @@ */ import '../../../../__mocks__/kea.mock'; -import { mockTelemetryActions } from '../../../../__mocks__'; +import { setMockValues, mockTelemetryActions } from '../../../../__mocks__'; import React from 'react'; @@ -17,30 +17,47 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import { EmptyState } from './'; describe('EmptyState', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); - }); - - describe('CTA Button', () => { + describe('when the user can manage/create engines', () => { let wrapper: ShallowWrapper; - let prompt: ShallowWrapper; - let button: ShallowWrapper; - beforeEach(() => { + beforeAll(() => { + setMockValues({ myRole: { canManageEngines: true } }); wrapper = shallow(); - prompt = wrapper.find(EuiEmptyPrompt).dive(); - button = prompt.find('[data-test-subj="EmptyStateCreateFirstEngineCta"]'); }); - it('sends telemetry on create first engine click', () => { - button.simulate('click'); - expect(mockTelemetryActions.sendAppSearchTelemetry).toHaveBeenCalled(); + it('renders a prompt to create an engine', () => { + expect(wrapper.find('[data-test-subj="AdminEmptyEnginesPrompt"]')).toHaveLength(1); + }); + + describe('create engine button', () => { + let prompt: ShallowWrapper; + let button: ShallowWrapper; + + beforeAll(() => { + prompt = wrapper.find(EuiEmptyPrompt).dive(); + button = prompt.find('[data-test-subj="EmptyStateCreateFirstEngineCta"]'); + }); + + it('sends telemetry on create first engine click', () => { + button.simulate('click'); + expect(mockTelemetryActions.sendAppSearchTelemetry).toHaveBeenCalled(); + }); + + it('sends a user to engine creation', () => { + expect(button.prop('to')).toEqual('/engine_creation'); + }); + }); + }); + + describe('when the user cannot manage/create engines', () => { + beforeAll(() => { + setMockValues({ myRole: { canManageEngines: false } }); }); - it('sends a user to engine creation', () => { - expect(button.prop('to')).toEqual('/engine_creation'); + it('renders a prompt to contact the App Search admin', () => { + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="NonAdminEmptyEnginesPrompt"]')).toHaveLength(1); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx index fc77e2f2511e01..df5a057e5d9c6e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx @@ -7,14 +7,15 @@ import React from 'react'; -import { useActions } from 'kea'; +import { useValues, useActions } from 'kea'; import { EuiPageContent, EuiEmptyPrompt } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; import { EuiButtonTo } from '../../../../shared/react_router_helpers'; import { TelemetryLogic } from '../../../../shared/telemetry'; +import { AppLogic } from '../../../app_logic'; import { ENGINE_CREATION_PATH } from '../../../routes'; import { EnginesOverviewHeader } from './header'; @@ -22,6 +23,9 @@ import { EnginesOverviewHeader } from './header'; import './empty_state.scss'; export const EmptyState: React.FC = () => { + const { + myRole: { canManageEngines }, + } = useValues(AppLogic); const { sendAppSearchTelemetry } = useActions(TelemetryLogic); return ( @@ -29,45 +33,71 @@ export const EmptyState: React.FC = () => { - - - - } - titleSize="l" - body={ -

- -

- } - actions={ - - sendAppSearchTelemetry({ - action: 'clicked', - metric: 'create_first_engine_button', - }) - } - > - - - } - /> + {canManageEngines ? ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.title', { + defaultMessage: 'Create your first engine', + })} + + } + titleSize="l" + body={ +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.description1', { + defaultMessage: + 'An App Search engine stores the documents for your search experience.', + })} +

+ } + actions={ + + sendAppSearchTelemetry({ + action: 'clicked', + metric: 'create_first_engine_button', + }) + } + > + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta', + { defaultMessage: 'Create an engine' } + )} + + } + /> + ) : ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.title', { + defaultMessage: 'No engines available', + })} + + } + body={ +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.description', + { + defaultMessage: + 'Contact your App Search administrator to either create or grant you access to an engine.', + } + )} +

+ } + /> + )}
); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx index 5a3f7309407600..3ca039907932ee 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx @@ -41,6 +41,7 @@ describe('EnginesOverview', () => { }, metaEnginesLoading: false, hasPlatinumLicense: false, + myRole: { canManageEngines: false }, }; const actions = { loadEngines: jest.fn(), @@ -90,61 +91,72 @@ describe('EnginesOverview', () => { setMockValues(valuesWithEngines); }); - it('renders and calls the engines API', async () => { + it('renders and calls the engines API', () => { const wrapper = shallow(); expect(wrapper.find(EnginesTable)).toHaveLength(1); expect(actions.loadEngines).toHaveBeenCalled(); }); - it('renders a create engine button which takes users to the create engine page', () => { - const wrapper = shallow(); + describe('when the user can manage/create engines', () => { + it('renders a create engine button which takes users to the create engine page', () => { + setMockValues({ + ...valuesWithEngines, + myRole: { canManageEngines: true }, + }); + const wrapper = shallow(); - expect( - wrapper.find('[data-test-subj="appSearchEnginesEngineCreationButton"]').prop('to') - ).toEqual('/engine_creation'); + expect( + wrapper.find('[data-test-subj="appSearchEnginesEngineCreationButton"]').prop('to') + ).toEqual('/engine_creation'); + }); }); - describe('when user has a platinum license', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { + describe('when the account has a platinum license', () => { + it('renders a 2nd meta engines table & makes a 2nd meta engines call', () => { setMockValues({ ...valuesWithEngines, hasPlatinumLicense: true, }); - wrapper = shallow(); - }); + const wrapper = shallow(); - it('renders a 2nd meta engines table ', async () => { expect(wrapper.find(EnginesTable)).toHaveLength(2); - }); - - it('makes a 2nd meta engines call', () => { expect(actions.loadMetaEngines).toHaveBeenCalled(); }); - it('renders a create engine button which takes users to the create meta engine page', () => { - expect( - wrapper.find('[data-test-subj="appSearchEnginesMetaEngineCreationButton"]').prop('to') - ).toEqual('/meta_engine_creation'); - }); - - it('contains an EuiEmptyPrompt that takes users to the create meta when metaEngines is empty', () => { - setMockValues({ - ...valuesWithEngines, - hasPlatinumLicense: true, - metaEngines: [], + describe('when the user can manage/create engines', () => { + it('renders a create engine button which takes users to the create meta engine page', () => { + setMockValues({ + ...valuesWithEngines, + hasPlatinumLicense: true, + myRole: { canManageEngines: true }, + }); + const wrapper = shallow(); + + expect( + wrapper.find('[data-test-subj="appSearchEnginesMetaEngineCreationButton"]').prop('to') + ).toEqual('/meta_engine_creation'); }); - wrapper = shallow(); - const metaEnginesTable = wrapper.find(EnginesTable).last().dive(); - const emptyPrompt = metaEnginesTable.dive().find(EuiEmptyPrompt).dive(); - expect( - emptyPrompt - .find('[data-test-subj="appSearchMetaEnginesEmptyStateCreationButton"]') - .prop('to') - ).toEqual('/meta_engine_creation'); + describe('when metaEngines is empty', () => { + it('contains an EuiEmptyPrompt that takes users to the create meta engine page', () => { + setMockValues({ + ...valuesWithEngines, + hasPlatinumLicense: true, + myRole: { canManageEngines: true }, + metaEngines: [], + }); + const wrapper = shallow(); + const metaEnginesTable = wrapper.find(EnginesTable).last().dive(); + const emptyPrompt = metaEnginesTable.dive().find(EuiEmptyPrompt).dive(); + + expect( + emptyPrompt + .find('[data-test-subj="appSearchMetaEnginesEmptyStateCreationButton"]') + .prop('to') + ).toEqual('/meta_engine_creation'); + }); + }); }); }); @@ -152,7 +164,7 @@ describe('EnginesOverview', () => { const getTablePagination = (wrapper: ShallowWrapper) => wrapper.find(EnginesTable).prop('pagination'); - it('passes down page data from the API', async () => { + it('passes down page data from the API', () => { const wrapper = shallow(); const pagination = getTablePagination(wrapper); @@ -160,7 +172,7 @@ describe('EnginesOverview', () => { expect(pagination.pageIndex).toEqual(0); }); - it('re-polls the API on page change', async () => { + it('re-polls the API on page change', () => { const wrapper = shallow(); setMockValues({ @@ -178,7 +190,7 @@ describe('EnginesOverview', () => { expect(getTablePagination(wrapper).pageIndex).toEqual(50); }); - it('calls onPagination handlers', async () => { + it('calls onPagination handlers', () => { setMockValues({ ...valuesWithEngines, hasPlatinumLicense: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx index 8297fb490ee04a..baf275fbe6c2ce 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx @@ -25,6 +25,7 @@ import { LicensingLogic } from '../../../shared/licensing'; import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { convertMetaToPagination, handlePageChange } from '../../../shared/table_pagination'; import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; +import { AppLogic } from '../../app_logic'; import { EngineIcon, MetaEngineIcon } from '../../icons'; import { ENGINE_CREATION_PATH, META_ENGINE_CREATION_PATH } from '../../routes'; @@ -44,6 +45,9 @@ import './engines_overview.scss'; export const EnginesOverview: React.FC = () => { const { hasPlatinumLicense } = useValues(LicensingLogic); + const { + myRole: { canManageEngines }, + } = useValues(AppLogic); const { dataLoading, @@ -91,14 +95,16 @@ export const EnginesOverview: React.FC = () => { - - {CREATE_AN_ENGINE_BUTTON_LABEL} - + {canManageEngines && ( + + {CREATE_AN_ENGINE_BUTTON_LABEL} + + )} @@ -126,14 +132,16 @@ export const EnginesOverview: React.FC = () => { - - {CREATE_A_META_ENGINE_BUTTON_LABEL} - + {canManageEngines && ( + + {CREATE_A_META_ENGINE_BUTTON_LABEL} + + )} @@ -149,13 +157,15 @@ export const EnginesOverview: React.FC = () => { title={

{META_ENGINE_EMPTY_PROMPT_TITLE}

} body={

{META_ENGINE_EMPTY_PROMPT_DESCRIPTION}

} actions={ - - {CREATE_A_META_ENGINE_BUTTON_LABEL} - + canManageEngines && ( + + {CREATE_A_META_ENGINE_BUTTON_LABEL} + + ) } /> } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx index 66f9c3c3c473d8..fc37c3543af569 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx @@ -51,16 +51,21 @@ describe('EnginesTable', () => { onDeleteEngine, }; + const resetMocks = () => { + jest.clearAllMocks(); + setMockValues({ + myRole: { + canManageEngines: false, + }, + }); + }; + describe('basic table', () => { let wrapper: ReactWrapper; let table: ReactWrapper; beforeAll(() => { - jest.clearAllMocks(); - setMockValues({ - // LicensingLogic - hasPlatinumLicense: false, - }); + resetMocks(); wrapper = mountWithIntl(); table = wrapper.find(EuiBasicTable); }); @@ -101,11 +106,7 @@ describe('EnginesTable', () => { describe('loading', () => { it('passes the loading prop', () => { - jest.clearAllMocks(); - setMockValues({ - // LicensingLogic - hasPlatinumLicense: false, - }); + resetMocks(); const wrapper = mountWithIntl(); expect(wrapper.find(EuiBasicTable).prop('loading')).toEqual(true); @@ -114,6 +115,7 @@ describe('EnginesTable', () => { describe('noItemsMessage', () => { it('passes the noItemsMessage prop', () => { + resetMocks(); const wrapper = mountWithIntl(); expect(wrapper.find(EuiBasicTable).prop('noItemsMessage')).toEqual('No items.'); }); @@ -121,11 +123,7 @@ describe('EnginesTable', () => { describe('language field', () => { beforeAll(() => { - jest.clearAllMocks(); - setMockValues({ - // LicensingLogic - hasPlatinumLicense: false, - }); + resetMocks(); }); it('renders language when available', () => { @@ -181,29 +179,27 @@ describe('EnginesTable', () => { }); describe('actions', () => { - it('will hide the action buttons if the user does not have a platinum license', () => { - jest.clearAllMocks(); - setMockValues({ - // LicensingLogic - hasPlatinumLicense: false, - }); + it('will hide the action buttons if the user cannot manage/delete engines', () => { + resetMocks(); const wrapper = shallow(); const tableRow = wrapper.find(EuiTableRow).first(); expect(tableRow.find(EuiIcon)).toHaveLength(0); }); - describe('when user has a platinum license', () => { + describe('when the user can manage/delete engines', () => { let wrapper: ReactWrapper; let tableRow: ReactWrapper; let actions: ReactWrapper; beforeEach(() => { - jest.clearAllMocks(); + resetMocks(); setMockValues({ - // LicensingLogic - hasPlatinumLicense: true, + myRole: { + canManageEngines: true, + }, }); + wrapper = mountWithIntl(); tableRow = wrapper.find(EuiTableRow).first(); actions = tableRow.find(EuiIcon); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx index 3b9b6e6c6a7780..624e212c72702a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx @@ -19,9 +19,9 @@ import { i18n } from '@kbn/i18n'; import { FormattedNumber } from '@kbn/i18n/react'; import { KibanaLogic } from '../../../shared/kibana'; -import { LicensingLogic } from '../../../shared/licensing'; import { EuiLinkTo } from '../../../shared/react_router_helpers'; import { TelemetryLogic } from '../../../shared/telemetry'; +import { AppLogic } from '../../app_logic'; import { UNIVERSAL_LANGUAGE } from '../../constants'; import { ENGINE_PATH } from '../../routes'; import { generateEncodedPath } from '../../utils/encode_path_params'; @@ -52,7 +52,9 @@ export const EnginesTable: React.FC = ({ }) => { const { sendAppSearchTelemetry } = useActions(TelemetryLogic); const { navigateToUrl } = useValues(KibanaLogic); - const { hasPlatinumLicense } = useValues(LicensingLogic); + const { + myRole: { canManageEngines }, + } = useValues(AppLogic); const generateEncodedEnginePath = (engineName: string) => generateEncodedPath(ENGINE_PATH, { engineName }); @@ -177,6 +179,7 @@ export const EnginesTable: React.FC = ({ ), type: 'icon', icon: 'trash', + color: 'danger', onClick: (engine) => { if ( window.confirm( @@ -199,7 +202,7 @@ export const EnginesTable: React.FC = ({ ], }; - if (hasPlatinumLicense) { + if (canManageEngines) { columns.push(actionsColumn); } From f3908f77546a7baa6f0a2d6d7bf8eed4aca50144 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Wed, 24 Mar 2021 11:05:04 -0700 Subject: [PATCH 73/93] [DOCS] Forward port of information (#95340) --- .../production.asciidoc | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/user/production-considerations/production.asciidoc b/docs/user/production-considerations/production.asciidoc index 8802719f95a957..726747d5d69d0e 100644 --- a/docs/user/production-considerations/production.asciidoc +++ b/docs/user/production-considerations/production.asciidoc @@ -10,6 +10,7 @@ * <> * <> * <> +* <> * <> * <> @@ -56,6 +57,7 @@ protections. To do this, set `csp.strict` to `true` in your `kibana.yml`: +[source,js] -------- csp.strict: true -------- @@ -82,6 +84,7 @@ To use a local client node to load balance Kibana requests: . Install Elasticsearch on the same machine as Kibana. . Configure the node as a Coordinating only node. In `elasticsearch.yml`, set `node.data`, `node.master` and `node.ingest` to `false`: + +[source,js] -------- # 3. You want this node to be neither master nor data node nor ingest node, but # to act as a "search load balancer" (fetching data from nodes, @@ -94,11 +97,13 @@ node.ingest: false . Configure the client node to join your Elasticsearch cluster. In `elasticsearch.yml`, set the `cluster.name` to the name of your cluster. + +[source,js] -------- cluster.name: "my_cluster" -------- . Check your transport and HTTP host configs in `elasticsearch.yml` under `network.host` and `transport.host`. The `transport.host` needs to be on the network reachable to the cluster members, the `network.host` is the network for the HTTP connection for Kibana (localhost:9200 by default). + +[source,js] -------- network.host: localhost http.port: 9200 @@ -110,6 +115,7 @@ transport.tcp.port: 9300 - 9400 . Make sure Kibana is configured to point to your local client node. In `kibana.yml`, the `elasticsearch.hosts` setting should be set to `["localhost:9200"]`. + +[source,js] -------- # The Elasticsearch instance to use for all your queries. elasticsearch.hosts: ["http://localhost:9200"] @@ -121,12 +127,14 @@ elasticsearch.hosts: ["http://localhost:9200"] To serve multiple Kibana installations behind a load balancer, you must change the configuration. See {kibana-ref}/settings.html[Configuring Kibana] for details on each setting. Settings unique across each Kibana instance: +[source,js] -------- server.uuid server.name -------- Settings unique across each host (for example, running multiple installations on the same virtual machine): +[source,js] -------- logging.dest path.data @@ -135,6 +143,7 @@ server.port -------- Settings that must be the same: +[source,js] -------- xpack.security.encryptionKey //decrypting session information xpack.reporting.encryptionKey //decrypting reports @@ -143,11 +152,24 @@ xpack.encryptedSavedObjects.keyRotation.decryptionOnlyKeys // saved objects encr -------- Separate configuration files can be used from the command line by using the `-c` flag: +[source,js] -------- bin/kibana -c config/instance1.yml bin/kibana -c config/instance2.yml -------- +[float] +[[accessing-load-balanced-kibana]] +=== Accessing multiple load-balanced {kib} clusters + +To access multiple load-balanced {kib} clusters from the same browser, +set `xpack.security.cookieName` in the configuration. +This avoids conflicts between cookies from the different {kib} instances. + +In each cluster, {kib} instances should have the same `cookieName` +value. This will achieve seamless high availability and keep the session +active in case of failure from the currently used instance. + [float] [[high-availability]] === High availability across multiple {es} nodes @@ -157,6 +179,7 @@ Kibana will transparently connect to an available node and continue operating. Currently the Console application is limited to connecting to the first node listed. In kibana.yml: +[source,js] -------- elasticsearch.hosts: - http://elasticsearch1:9200 @@ -175,6 +198,7 @@ it may make sense to tweak limits to meet more specific requirements. You can modify this limit by setting `--max-old-space-size` in the `node.options` config file that can be found inside `kibana/config` folder or any other configured with the environment variable `KBN_PATH_CONF` (for example in debian based system would be `/etc/kibana`). The option accepts a limit in MB: +[source,js] -------- --max-old-space-size=2048 -------- From 07a041ab3a42b9fbdfa62c8a717789c3d66eebf3 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Wed, 24 Mar 2021 13:28:31 -0500 Subject: [PATCH 74/93] [ML] Fix Transform runtime mapping editor so mappings can be removed (#95108) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../advanced_runtime_mappings_editor.tsx | 7 +++++-- .../advanced_runtime_mappings_settings.tsx | 3 ++- .../hooks/use_advanced_runtime_mappings_editor.ts | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx index bd37410518176b..1e8397a4d9cc34 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx @@ -44,8 +44,11 @@ export const AdvancedRuntimeMappingsEditor: FC = (props) = } = props.pivotConfig; const applyChanges = () => { - const nextConfig = JSON.parse(advancedRuntimeMappingsConfig); + const nextConfig = + advancedRuntimeMappingsConfig === '' ? {} : JSON.parse(advancedRuntimeMappingsConfig); const previousConfig = runtimeMappings; applyRuntimeMappingsEditorChanges(); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts index 9bb5f91ae03c79..948c520def1952 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts @@ -52,7 +52,8 @@ export const useAdvancedRuntimeMappingsEditor = (defaults: StepDefineExposedStat } = useXJsonMode(stringifiedRuntimeMappings ?? ''); const applyRuntimeMappingsEditorChanges = () => { - const parsedRuntimeMappings = JSON.parse(advancedRuntimeMappingsConfig); + const parsedRuntimeMappings = + advancedRuntimeMappingsConfig === '' ? {} : JSON.parse(advancedRuntimeMappingsConfig); const prettySourceConfig = JSON.stringify(parsedRuntimeMappings, null, 2); setRuntimeMappingsUpdated(true); setRuntimeMappings(parsedRuntimeMappings); From 2da269fe93012e6582f7a72d5f8074597f7e680f Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 24 Mar 2021 20:09:11 +0100 Subject: [PATCH 75/93] [Discover][EuiDataGrid] Fix generating reports (#93748) --- .../create_discover_grid_directive.tsx | 2 +- .../application/components/discover.test.tsx | 1 + .../application/components/discover.tsx | 2 ++ .../discover_grid/discover_grid.tsx | 15 +++++++++++++-- .../public/application/components/types.ts | 4 ++++ .../embeddable/search_embeddable.ts | 11 ++++++++++- .../embeddable/search_template_datagrid.html | 8 ++++---- .../apps/dashboard/reporting/screenshots.ts | 19 +++++++++++++++++++ 8 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/plugins/discover/public/application/components/create_discover_grid_directive.tsx b/src/plugins/discover/public/application/components/create_discover_grid_directive.tsx index 0d17fcbba9c238..d55e46574e1a7a 100644 --- a/src/plugins/discover/public/application/components/create_discover_grid_directive.tsx +++ b/src/plugins/discover/public/application/components/create_discover_grid_directive.tsx @@ -5,7 +5,6 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - import React, { useState } from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import { DiscoverGrid, DiscoverGridProps } from './discover_grid/discover_grid'; @@ -38,6 +37,7 @@ export function createDiscoverGridDirective(reactDirective: any) { return reactDirective(DiscoverGridEmbeddable, [ ['columns', { watchDepth: 'collection' }], ['indexPattern', { watchDepth: 'reference' }], + ['isLoading', { watchDepth: 'reference' }], ['onAddColumn', { watchDepth: 'reference', wrapApply: false }], ['onFilter', { watchDepth: 'reference', wrapApply: false }], ['onRemoveColumn', { watchDepth: 'reference', wrapApply: false }], diff --git a/src/plugins/discover/public/application/components/discover.test.tsx b/src/plugins/discover/public/application/components/discover.test.tsx index 00554196e11fd2..d804d608704219 100644 --- a/src/plugins/discover/public/application/components/discover.test.tsx +++ b/src/plugins/discover/public/application/components/discover.test.tsx @@ -62,6 +62,7 @@ function getProps(indexPattern: IndexPattern): DiscoverProps { fetch: jest.fn(), fetchCounter: 0, fetchError: undefined, + fetchStatus: 'loading', fieldCounts: calcFieldCounts({}, esHits, indexPattern), hits: esHits.length, indexPattern, diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx index 1c4a5be2f2b244..056581e30b4d6f 100644 --- a/src/plugins/discover/public/application/components/discover.tsx +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -53,6 +53,7 @@ export function Discover({ fetchCounter, fetchError, fieldCounts, + fetchStatus, histogramData, hits, indexPattern, @@ -393,6 +394,7 @@ export function Discover({ columns={columns} expandedDoc={expandedDoc} indexPattern={indexPattern} + isLoading={fetchStatus === 'loading'} rows={rows} sort={(state.sort as SortPairArr[]) || []} sampleSize={opts.sampleSize} diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx index 20d7d80b498a8a..1888ae8562a37f 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -60,6 +60,10 @@ export interface DiscoverGridProps { * The used index pattern */ indexPattern: IndexPattern; + /** + * Determines if data is currently loaded + */ + isLoading: boolean; /** * Function used to add a column in the document flyout */ @@ -135,6 +139,7 @@ export const DiscoverGrid = ({ ariaLabelledBy, columns, indexPattern, + isLoading, expandedDoc, onAddColumn, onFilter, @@ -258,7 +263,13 @@ export const DiscoverGrid = ({ isDarkMode: services.uiSettings.get('theme:darkMode'), }} > - <> + )} - + ); }; diff --git a/src/plugins/discover/public/application/components/types.ts b/src/plugins/discover/public/application/components/types.ts index e488f596cece85..db1cd894224540 100644 --- a/src/plugins/discover/public/application/components/types.ts +++ b/src/plugins/discover/public/application/components/types.ts @@ -42,6 +42,10 @@ export interface DiscoverProps { * Statistics by fields calculated using the fetched documents */ fieldCounts: Record; + /** + * Current state of data fetching + */ + fetchStatus: string; /** * Histogram aggregation data */ diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index 1bf4cdc947be93..829d23fa071fb4 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -168,7 +168,7 @@ export class SearchEmbeddable throw new Error('Search scope not defined'); } this.searchInstance = this.$compile( - this.services.uiSettings.get('doc_table:legacy', true) ? searchTemplate : searchTemplateGrid + this.services.uiSettings.get('doc_table:legacy') ? searchTemplate : searchTemplateGrid )(this.searchScope); const rootNode = angular.element(domNode); rootNode.append(this.searchInstance); @@ -226,6 +226,8 @@ export class SearchEmbeddable this.updateInput({ sort }); }; + searchScope.isLoading = true; + const useNewFieldsApi = !getServices().uiSettings.get(SEARCH_FIELDS_FROM_SOURCE, false); searchScope.useNewFieldsApi = useNewFieldsApi; @@ -336,6 +338,9 @@ export class SearchEmbeddable searchSource.getSearchRequestBody().then((body: Record) => { inspectorRequest.json(body); }); + this.searchScope.$apply(() => { + this.searchScope!.isLoading = true; + }); this.updateOutput({ loading: true, error: undefined }); try { @@ -353,9 +358,13 @@ export class SearchEmbeddable this.searchScope.$apply(() => { this.searchScope!.hits = resp.hits.hits; this.searchScope!.totalHitCount = resp.hits.total; + this.searchScope!.isLoading = false; }); } catch (error) { this.updateOutput({ loading: false, error }); + this.searchScope.$apply(() => { + this.searchScope!.isLoading = false; + }); } }; diff --git a/src/plugins/discover/public/application/embeddable/search_template_datagrid.html b/src/plugins/discover/public/application/embeddable/search_template_datagrid.html index 6524783897f8f3..8ad7938350d9c5 100644 --- a/src/plugins/discover/public/application/embeddable/search_template_datagrid.html +++ b/src/plugins/discover/public/application/embeddable/search_template_datagrid.html @@ -1,16 +1,16 @@ { before('initialize tests', async () => { @@ -150,6 +151,24 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(res.status).to.equal(200); expect(res.get('content-type')).to.equal('application/pdf'); }); + + it('downloads a PDF file with saved search given EuiDataGrid enabled', async function () { + await kibanaServer.uiSettings.replace({ 'doc_table:legacy': false }); + // Generating and then comparing reports can take longer than the default 60s timeout because the comparePngs + // function is taking about 15 seconds per comparison in jenkins. + this.timeout(300000); + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard'); + await PageObjects.reporting.openPdfReportingPanel(); + await PageObjects.reporting.clickGenerateReportButton(); + + const url = await PageObjects.reporting.getReportURL(60000); + const res = await PageObjects.reporting.getResponse(url); + + expect(res.status).to.equal(200); + expect(res.get('content-type')).to.equal('application/pdf'); + await kibanaServer.uiSettings.replace({}); + }); }); }); } From 983c3a0139cd9079f41088a4bd2c68c244dedb50 Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 24 Mar 2021 12:37:02 -0700 Subject: [PATCH 76/93] [App Search] Log retention role fix (#95227) * LogRetentionCallout - add ability check to API call - on non-admin users, we're otherwise getting a forbidden error - remove now-unnecessary canManageLogSettings wrapping check around the link CTA, since the entire callout is now essentially gated behind the check * LogRententionTooltip - add ability check to API call - on non-admin users, we're otherwise getting a forbidden error - tests now require a ...values spread for myRole * [MISC] Refactor previous isLogRetentionUpdating check in favor of a Kea breakpoint - both dedupe calls for the commented use case, but breakpoint feels simpler & more Kea-y * PR feedback: Increase breakpoint speed --- .../components/log_retention_callout.test.tsx | 16 +++--- .../components/log_retention_callout.tsx | 36 +++++++------- .../components/log_retention_tooltip.test.tsx | 18 +++++-- .../components/log_retention_tooltip.tsx | 9 +++- .../log_retention/log_retention_logic.test.ts | 49 +++++++++---------- .../log_retention/log_retention_logic.ts | 10 ++-- 6 files changed, 71 insertions(+), 67 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.test.tsx index 124edb68714531..fe022391d76b45 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.test.tsx @@ -63,15 +63,6 @@ describe('LogRetentionCallout', () => { expect(wrapper.find('.euiCallOutHeader__title').text()).toEqual('API Logs have been disabled.'); }); - it('does not render a settings link if the user cannot manage settings', () => { - setMockValues({ myRole: { canManageLogSettings: false }, logRetention: { api: DISABLED } }); - const wrapper = mountWithIntl(); - - expect(wrapper.find(EuiCallOut)).toHaveLength(1); - expect(wrapper.find(EuiLink)).toHaveLength(0); - expect(wrapper.find('p')).toHaveLength(0); - }); - it('does not render if log retention is enabled', () => { setMockValues({ ...values, logRetention: { api: { enabled: true } } }); const wrapper = shallow(); @@ -100,5 +91,12 @@ describe('LogRetentionCallout', () => { expect(actions.fetchLogRetention).not.toHaveBeenCalled(); }); + + it('does not fetch log retention data if the user does not have access to log settings', () => { + setMockValues({ ...values, logRetention: null, myRole: { canManageLogSettings: false } }); + shallow(); + + expect(actions.fetchLogRetention).not.toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.tsx index 235d9777931612..1fd1a9a79b2251 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.tsx @@ -40,7 +40,7 @@ export const LogRetentionCallout: React.FC = ({ type }) => { const hasLogRetention = logRetention !== null; useEffect(() => { - if (!hasLogRetention) fetchLogRetention(); + if (!hasLogRetention && canManageLogSettings) fetchLogRetention(); }, []); const logRetentionSettings = logRetention?.[type]; @@ -72,24 +72,22 @@ export const LogRetentionCallout: React.FC = ({ type }) => { ) } > - {canManageLogSettings && ( -

- - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.logRetention.callout.description.manageSettingsLinkText', - { defaultMessage: 'visit your settings' } - )} - - ), - }} - /> -

- )} +

+ + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.logRetention.callout.description.manageSettingsLinkText', + { defaultMessage: 'visit your settings' } + )} + + ), + }} + /> +

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.test.tsx index 854a9f1d8d162f..6b5693e4c301e1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.test.tsx @@ -19,7 +19,10 @@ import { LogRetentionOptions, LogRetentionMessage } from '../'; import { LogRetentionTooltip } from './'; describe('LogRetentionTooltip', () => { - const values = { logRetention: {} }; + const values = { + logRetention: {}, + myRole: { canManageLogSettings: true }, + }; const actions = { fetchLogRetention: jest.fn() }; beforeEach(() => { @@ -53,7 +56,7 @@ describe('LogRetentionTooltip', () => { }); it('does not render if log retention is not available', () => { - setMockValues({ logRetention: null }); + setMockValues({ ...values, logRetention: null }); const wrapper = mount(); expect(wrapper.isEmptyRender()).toBe(true); @@ -61,14 +64,21 @@ describe('LogRetentionTooltip', () => { describe('on mount', () => { it('fetches log retention data when not already loaded', () => { - setMockValues({ logRetention: null }); + setMockValues({ ...values, logRetention: null }); shallow(); expect(actions.fetchLogRetention).toHaveBeenCalled(); }); it('does not fetch log retention data if it has already been loaded', () => { - setMockValues({ logRetention: {} }); + setMockValues({ ...values, logRetention: {} }); + shallow(); + + expect(actions.fetchLogRetention).not.toHaveBeenCalled(); + }); + + it('does not fetch log retention data if the user does not have access to log settings', () => { + setMockValues({ ...values, logRetention: null, myRole: { canManageLogSettings: false } }); shallow(); expect(actions.fetchLogRetention).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.tsx index bf074ba0272f2b..ac701d4cc067b7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.tsx @@ -12,7 +12,9 @@ import { useValues, useActions } from 'kea'; import { EuiIconTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { LogRetentionLogic, LogRetentionMessage, LogRetentionOptions } from '../'; +import { AppLogic } from '../../../app_logic'; + +import { LogRetentionLogic, LogRetentionMessage, LogRetentionOptions } from '../index'; interface Props { type: LogRetentionOptions; @@ -21,11 +23,14 @@ interface Props { export const LogRetentionTooltip: React.FC = ({ type, position = 'bottom' }) => { const { fetchLogRetention } = useActions(LogRetentionLogic); const { logRetention } = useValues(LogRetentionLogic); + const { + myRole: { canManageLogSettings }, + } = useValues(AppLogic); const hasLogRetention = logRetention !== null; useEffect(() => { - if (!hasLogRetention) fetchLogRetention(); + if (!hasLogRetention && canManageLogSettings) fetchLogRetention(); }, []); return hasLogRetention ? ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts index 7b63397ac6380e..57f3f46fed92e7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts @@ -107,21 +107,6 @@ describe('LogRetentionLogic', () => { }); }); - describe('setLogRetentionUpdating', () => { - describe('isLogRetentionUpdating', () => { - it('sets isLogRetentionUpdating to true', () => { - mount(); - - LogRetentionLogic.actions.setLogRetentionUpdating(); - - expect(LogRetentionLogic.values).toEqual({ - ...DEFAULT_VALUES, - isLogRetentionUpdating: true, - }); - }); - }); - }); - describe('clearLogRetentionUpdating', () => { describe('isLogRetentionUpdating', () => { it('resets isLogRetentionUpdating to false', () => { @@ -300,8 +285,27 @@ describe('LogRetentionLogic', () => { }); describe('fetchLogRetention', () => { + beforeAll(() => jest.useFakeTimers()); + afterAll(() => jest.useRealTimers()); + + describe('isLogRetentionUpdating', () => { + it('sets isLogRetentionUpdating to true', () => { + mount({ + isLogRetentionUpdating: false, + }); + + LogRetentionLogic.actions.fetchLogRetention(); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + isLogRetentionUpdating: true, + }); + }); + }); + it('will call an API endpoint and update log retention', async () => { mount(); + jest.spyOn(LogRetentionLogic.actions, 'clearLogRetentionUpdating'); jest .spyOn(LogRetentionLogic.actions, 'updateLogRetention') .mockImplementationOnce(() => {}); @@ -309,14 +313,14 @@ describe('LogRetentionLogic', () => { http.get.mockReturnValue(Promise.resolve(TYPICAL_SERVER_LOG_RETENTION)); LogRetentionLogic.actions.fetchLogRetention(); - expect(LogRetentionLogic.values.isLogRetentionUpdating).toBe(true); + jest.runAllTimers(); + await nextTick(); expect(http.get).toHaveBeenCalledWith('/api/app_search/log_settings'); - await nextTick(); expect(LogRetentionLogic.actions.updateLogRetention).toHaveBeenCalledWith( TYPICAL_CLIENT_LOG_RETENTION ); - expect(LogRetentionLogic.values.isLogRetentionUpdating).toBe(false); + expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); }); it('handles errors', async () => { @@ -325,19 +329,12 @@ describe('LogRetentionLogic', () => { http.get.mockReturnValue(Promise.reject('An error occured')); LogRetentionLogic.actions.fetchLogRetention(); + jest.runAllTimers(); await nextTick(); expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); }); - - it('does not run if isLogRetentionUpdating is true, preventing duplicate fetches', async () => { - mount({ isLogRetentionUpdating: true }); - - LogRetentionLogic.actions.fetchLogRetention(); - - expect(http.get).not.toHaveBeenCalled(); - }); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.ts index ec078842dab557..aa1be3f8cdc641 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.ts @@ -14,7 +14,6 @@ import { LogRetentionOptions, LogRetention, LogRetentionServer } from './types'; import { convertLogRetentionFromServerToClient } from './utils/convert_log_retention'; interface LogRetentionActions { - setLogRetentionUpdating(): void; clearLogRetentionUpdating(): void; closeModals(): void; fetchLogRetention(): void; @@ -36,7 +35,6 @@ interface LogRetentionValues { export const LogRetentionLogic = kea>({ path: ['enterprise_search', 'app_search', 'log_retention_logic'], actions: () => ({ - setLogRetentionUpdating: true, clearLogRetentionUpdating: true, closeModals: true, fetchLogRetention: true, @@ -57,7 +55,7 @@ export const LogRetentionLogic = kea false, closeModals: () => false, - setLogRetentionUpdating: () => true, + fetchLogRetention: () => true, toggleLogRetention: () => true, }, ], @@ -71,12 +69,10 @@ export const LogRetentionLogic = kea ({ - fetchLogRetention: async () => { - if (values.isLogRetentionUpdating) return; // Prevent duplicate calls to the API + fetchLogRetention: async (_, breakpoint) => { + await breakpoint(100); // Prevents duplicate calls to the API (e.g., when a tooltip & callout are on the same page) try { - actions.setLogRetentionUpdating(); - const { http } = HttpLogic.values; const response = await http.get('/api/app_search/log_settings'); From c7aba55f7b0e20ae2f5e21e364678d7e994a80dc Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 24 Mar 2021 14:02:31 -0600 Subject: [PATCH 77/93] [Maps] do not call MapEmbeddable updateInput after embeddable is destroyed (#95337) --- x-pack/plugins/maps/public/embeddable/map_embeddable.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index b7e50815fd1f71..d11e1d59b28f77 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -93,6 +93,7 @@ export class MapEmbeddable implements ReferenceOrValueEmbeddable { type = MAP_SAVED_OBJECT_TYPE; + private _isActive: boolean; private _savedMap: SavedMap; private _renderTooltipContent?: RenderToolTipContent; private _subscription: Subscription; @@ -118,6 +119,7 @@ export class MapEmbeddable parent ); + this._isActive = true; this._savedMap = new SavedMap({ mapEmbeddableInput: initialInput }); this._initializeSaveMap(); this._subscription = this.getUpdated$().subscribe(() => this.onUpdate()); @@ -404,6 +406,7 @@ export class MapEmbeddable destroy() { super.destroy(); + this._isActive = false; if (this._unsubscribeFromStore) { this._unsubscribeFromStore(); } @@ -424,6 +427,9 @@ export class MapEmbeddable } _handleStoreChanges() { + if (!this._isActive) { + return; + } const center = getMapCenter(this._savedMap.getStore().getState()); const zoom = getMapZoom(this._savedMap.getStore().getState()); From 7f740831cb9924a32460cc5008ac0fb1619be8c2 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Wed, 24 Mar 2021 22:16:38 +0200 Subject: [PATCH 78/93] Revert "use index patterns and search services for autocomplete (#92861)" (#95335) This reverts commit 2ef7f3bd0cee5f44d71e0528409bc3f8576be333. --- ...ata-server.indexpatternsserviceprovider.md | 2 +- ...rver.indexpatternsserviceprovider.setup.md | 4 +-- .../kibana-plugin-plugins-data-server.md | 1 + ...data-server.searchrequesthandlercontext.md | 11 ++++++ .../data/server/autocomplete/routes.ts | 3 +- .../autocomplete/value_suggestions_route.ts | 36 ++++++------------- src/plugins/data/server/index.ts | 4 +-- .../data/server/index_patterns/index.ts | 4 --- .../index_patterns/index_patterns_service.ts | 20 +---------- src/plugins/data/server/mocks.ts | 2 +- src/plugins/data/server/plugin.ts | 5 +-- .../data/server/search/routes/msearch.ts | 2 +- .../data/server/search/routes/search.ts | 2 +- .../data/server/search/search_service.ts | 2 +- src/plugins/data/server/search/types.ts | 11 ++++++ src/plugins/data/server/server.api.md | 15 ++++---- src/plugins/data/server/types.ts | 22 ------------ x-pack/plugins/infra/server/types.ts | 6 ++-- 18 files changed, 56 insertions(+), 96 deletions(-) create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md delete mode 100644 src/plugins/data/server/types.ts diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md index 698b4bc7f20434..d408f00e33c9e8 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md @@ -14,6 +14,6 @@ export declare class IndexPatternsServiceProvider implements PluginSignature:
```typescript -setup(core: CoreSetup, { logger, expressions }: IndexPatternsServiceSetupDeps): void; +setup(core: CoreSetup, { expressions }: IndexPatternsServiceSetupDeps): void; ``` ## Parameters @@ -15,7 +15,7 @@ setup(core: CoreSetup, { logger, e | Parameter | Type | Description | | --- | --- | --- | | core | CoreSetup<DataPluginStartDependencies, DataPluginStart> | | -| { logger, expressions } | IndexPatternsServiceSetupDeps | | +| { expressions } | IndexPatternsServiceSetupDeps | | Returns: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 16d9ce457603eb..e0734bc017f4f5 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -109,5 +109,6 @@ | [KibanaContext](./kibana-plugin-plugins-data-server.kibanacontext.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | | | [Query](./kibana-plugin-plugins-data-server.query.md) | | +| [SearchRequestHandlerContext](./kibana-plugin-plugins-data-server.searchrequesthandlercontext.md) | | | [TimeRange](./kibana-plugin-plugins-data-server.timerange.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md new file mode 100644 index 00000000000000..f031ddfbd09af7 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SearchRequestHandlerContext](./kibana-plugin-plugins-data-server.searchrequesthandlercontext.md) + +## SearchRequestHandlerContext type + +Signature: + +```typescript +export declare type SearchRequestHandlerContext = IScopedSearchClient; +``` diff --git a/src/plugins/data/server/autocomplete/routes.ts b/src/plugins/data/server/autocomplete/routes.ts index fc6bb0b69c1021..c453094ff68749 100644 --- a/src/plugins/data/server/autocomplete/routes.ts +++ b/src/plugins/data/server/autocomplete/routes.ts @@ -9,10 +9,9 @@ import { Observable } from 'rxjs'; import { CoreSetup, SharedGlobalConfig } from 'kibana/server'; import { registerValueSuggestionsRoute } from './value_suggestions_route'; -import { DataRequestHandlerContext } from '../types'; export function registerRoutes({ http }: CoreSetup, config$: Observable): void { - const router = http.createRouter(); + const router = http.createRouter(); registerValueSuggestionsRoute(router, config$); } diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index 8e6d3afa18ed5f..489a23eb83897c 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -12,12 +12,12 @@ import { IRouter, SharedGlobalConfig } from 'kibana/server'; import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; -import { IFieldType, Filter, ES_SEARCH_STRATEGY, IEsSearchRequest } from '../index'; +import { IFieldType, Filter } from '../index'; +import { findIndexPatternById, getFieldByName } from '../index_patterns'; import { getRequestAbortedSignal } from '../lib'; -import { DataRequestHandlerContext } from '../types'; export function registerValueSuggestionsRoute( - router: IRouter, + router: IRouter, config$: Observable ) { router.post( @@ -44,40 +44,24 @@ export function registerValueSuggestionsRoute( const config = await config$.pipe(first()).toPromise(); const { field: fieldName, query, filters } = request.body; const { index } = request.params; + const { client } = context.core.elasticsearch.legacy; const signal = getRequestAbortedSignal(request.events.aborted$); - if (!context.indexPatterns) { - return response.badRequest(); - } - const autocompleteSearchOptions = { timeout: `${config.kibana.autocompleteTimeout.asMilliseconds()}ms`, terminate_after: config.kibana.autocompleteTerminateAfter.asMilliseconds(), }; - const indexPatterns = await context.indexPatterns.find(index, 1); - if (!indexPatterns || indexPatterns.length === 0) { - return response.notFound(); - } - const field = indexPatterns[0].getFieldByName(fieldName); + const indexPattern = await findIndexPatternById(context.core.savedObjects.client, index); + + const field = indexPattern && getFieldByName(fieldName, indexPattern); const body = await getBody(autocompleteSearchOptions, field || fieldName, query, filters); - const searchRequest: IEsSearchRequest = { - params: { - index, - body, - }, - }; - const { rawResponse } = await context.search - .search(searchRequest, { - strategy: ES_SEARCH_STRATEGY, - abortSignal: signal, - }) - .toPromise(); + const result = await client.callAsCurrentUser('search', { index, body }, { signal }); const buckets: any[] = - get(rawResponse, 'aggregations.suggestions.buckets') || - get(rawResponse, 'aggregations.nestedSuggestions.suggestions.buckets'); + get(result, 'aggregations.suggestions.buckets') || + get(result, 'aggregations.nestedSuggestions.suggestions.buckets'); return response.ok({ body: map(buckets || [], 'key') }); } diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index c153c0efa88921..cbf09ef57d96ae 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -236,10 +236,10 @@ export { SearchUsage, SearchSessionService, ISearchSessionService, + SearchRequestHandlerContext, + DataRequestHandlerContext, } from './search'; -export { DataRequestHandlerContext } from './types'; - // Search namespace export const search = { aggs: { diff --git a/src/plugins/data/server/index_patterns/index.ts b/src/plugins/data/server/index_patterns/index.ts index 85610cd85a3cef..7226d6f015cf88 100644 --- a/src/plugins/data/server/index_patterns/index.ts +++ b/src/plugins/data/server/index_patterns/index.ts @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -import { IndexPatternsService } from '../../common/index_patterns'; - export * from './utils'; export { IndexPatternsFetcher, @@ -17,5 +15,3 @@ export { getCapabilitiesForRollupIndices, } from './fetcher'; export { IndexPatternsServiceProvider, IndexPatternsServiceStart } from './index_patterns_service'; - -export type IndexPatternsHandlerContext = IndexPatternsService; diff --git a/src/plugins/data/server/index_patterns/index_patterns_service.ts b/src/plugins/data/server/index_patterns/index_patterns_service.ts index b489c29bc3b707..5d703021b94da9 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_service.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_service.ts @@ -25,7 +25,6 @@ import { getIndexPatternLoad } from './expressions'; import { UiSettingsServerToCommon } from './ui_settings_wrapper'; import { IndexPatternsApiServer } from './index_patterns_api_client'; import { SavedObjectsClientServerToCommon } from './saved_objects_client_wrapper'; -import { DataRequestHandlerContext } from '../types'; export interface IndexPatternsServiceStart { indexPatternsServiceFactory: ( @@ -36,7 +35,6 @@ export interface IndexPatternsServiceStart { export interface IndexPatternsServiceSetupDeps { expressions: ExpressionsServerSetup; - logger: Logger; } export interface IndexPatternsServiceStartDeps { @@ -47,27 +45,11 @@ export interface IndexPatternsServiceStartDeps { export class IndexPatternsServiceProvider implements Plugin { public setup( core: CoreSetup, - { logger, expressions }: IndexPatternsServiceSetupDeps + { expressions }: IndexPatternsServiceSetupDeps ) { core.savedObjects.registerType(indexPatternSavedObjectType); core.capabilities.registerProvider(capabilitiesProvider); - core.http.registerRouteHandlerContext( - 'indexPatterns', - async (context, request) => { - const [coreStart, , dataStart] = await core.getStartServices(); - try { - return await dataStart.indexPatterns.indexPatternsServiceFactory( - coreStart.savedObjects.getScopedClient(request), - coreStart.elasticsearch.client.asScoped(request).asCurrentUser - ); - } catch (e) { - logger.error(e); - return undefined; - } - } - ); - registerRoutes(core.http, core.getStartServices); expressions.registerFunction(getIndexPatternLoad({ getStartServices: core.getStartServices })); diff --git a/src/plugins/data/server/mocks.ts b/src/plugins/data/server/mocks.ts index c82db7a141403f..786dd30dbabd0b 100644 --- a/src/plugins/data/server/mocks.ts +++ b/src/plugins/data/server/mocks.ts @@ -13,7 +13,7 @@ import { } from './search/mocks'; import { createFieldFormatsSetupMock, createFieldFormatsStartMock } from './field_formats/mocks'; import { createIndexPatternsStartMock } from './index_patterns/mocks'; -import { DataRequestHandlerContext } from './types'; +import { DataRequestHandlerContext } from './search'; function createSetupContract() { return { diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 3408c39cbb8e2d..a7a7663d6981c4 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -82,10 +82,7 @@ export class DataServerPlugin this.queryService.setup(core); this.autocompleteService.setup(core); this.kqlTelemetryService.setup(core, { usageCollection }); - this.indexPatterns.setup(core, { - expressions, - logger: this.logger.get('indexPatterns'), - }); + this.indexPatterns.setup(core, { expressions }); core.uiSettings.register(getUiSettings()); diff --git a/src/plugins/data/server/search/routes/msearch.ts b/src/plugins/data/server/search/routes/msearch.ts index b5f06c4b343e78..b578805d8c2dfd 100644 --- a/src/plugins/data/server/search/routes/msearch.ts +++ b/src/plugins/data/server/search/routes/msearch.ts @@ -12,7 +12,7 @@ import { SearchRouteDependencies } from '../search_service'; import { getCallMsearch } from './call_msearch'; import { reportServerError } from '../../../../kibana_utils/server'; -import type { DataPluginRouter } from '../../types'; +import type { DataPluginRouter } from '../types'; /** * The msearch route takes in an array of searches, each consisting of header * and body json, and reformts them into a single request for the _msearch API. diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts index 6690e2b81f3e4a..1680a9c4a72371 100644 --- a/src/plugins/data/server/search/routes/search.ts +++ b/src/plugins/data/server/search/routes/search.ts @@ -10,7 +10,7 @@ import { first } from 'rxjs/operators'; import { schema } from '@kbn/config-schema'; import { getRequestAbortedSignal } from '../../lib'; import { reportServerError } from '../../../../kibana_utils/server'; -import type { DataPluginRouter } from '../../types'; +import type { DataPluginRouter } from '../types'; export function registerSearchRoute(router: DataPluginRouter): void { router.post( diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 69710e82b73b42..4dcab4eda34d1d 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -29,6 +29,7 @@ import type { ISearchStrategy, SearchEnhancements, SearchStrategyDependencies, + DataRequestHandlerContext, } from './types'; import { AggsService } from './aggs'; @@ -74,7 +75,6 @@ import { ConfigSchema } from '../../config'; import { ISearchSessionService, SearchSessionService } from './session'; import { KbnServerError } from '../../../kibana_utils/server'; import { registerBsearchRoute } from './routes/bsearch'; -import { DataRequestHandlerContext } from '../types'; type StrategyMap = Record>; diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index d7aadcc348c87d..e8548257c01679 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -8,10 +8,12 @@ import { Observable } from 'rxjs'; import type { + IRouter, IScopedClusterClient, IUiSettingsClient, SavedObjectsClientContract, KibanaRequest, + RequestHandlerContext, } from 'src/core/server'; import { ISearchOptions, @@ -114,3 +116,12 @@ export interface ISearchStart< } export type SearchRequestHandlerContext = IScopedSearchClient; + +/** + * @internal + */ +export interface DataRequestHandlerContext extends RequestHandlerContext { + search: SearchRequestHandlerContext; +} + +export type DataPluginRouter = IRouter; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 2b7da7fe194ca2..c1314ddbe6fa26 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -316,12 +316,6 @@ export const config: PluginConfigDescriptor; // @internal (undocumented) export interface DataRequestHandlerContext extends RequestHandlerContext { - // Warning: (ae-forgotten-export) The symbol "IndexPatternsHandlerContext" needs to be exported by the entry point index.d.ts - // - // (undocumented) - indexPatterns?: IndexPatternsHandlerContext; - // Warning: (ae-forgotten-export) The symbol "SearchRequestHandlerContext" needs to be exported by the entry point index.d.ts - // // (undocumented) search: SearchRequestHandlerContext; } @@ -964,7 +958,7 @@ export class IndexPatternsServiceProvider implements Plugin_3, { logger, expressions }: IndexPatternsServiceSetupDeps): void; + setup(core: CoreSetup_2, { expressions }: IndexPatternsServiceSetupDeps): void; // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceStartDeps" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1331,6 +1325,11 @@ export const search: { tabifyGetColumns: typeof tabifyGetColumns; }; +// Warning: (ae-missing-release-tag) "SearchRequestHandlerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type SearchRequestHandlerContext = IScopedSearchClient; + // @internal export class SearchSessionService implements ISearchSessionService { constructor(); @@ -1522,7 +1521,7 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:270:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:271:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:79:74 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/search/types.ts:112:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/search/types.ts:114:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/server/types.ts b/src/plugins/data/server/types.ts deleted file mode 100644 index ea0fa49058d374..00000000000000 --- a/src/plugins/data/server/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { IRouter, RequestHandlerContext } from 'src/core/server'; - -import { SearchRequestHandlerContext } from './search'; -import { IndexPatternsHandlerContext } from './index_patterns'; - -/** - * @internal - */ -export interface DataRequestHandlerContext extends RequestHandlerContext { - search: SearchRequestHandlerContext; - indexPatterns?: IndexPatternsHandlerContext; -} - -export type DataPluginRouter = IRouter; diff --git a/x-pack/plugins/infra/server/types.ts b/x-pack/plugins/infra/server/types.ts index 1c51a5549cb41f..5cae0158619460 100644 --- a/x-pack/plugins/infra/server/types.ts +++ b/x-pack/plugins/infra/server/types.ts @@ -5,7 +5,8 @@ * 2.0. */ -import type { DataRequestHandlerContext } from '../../../../src/plugins/data/server'; +import type { RequestHandlerContext } from 'src/core/server'; +import type { SearchRequestHandlerContext } from '../../../../src/plugins/data/server'; import { MlPluginSetup } from '../../ml/server'; export type MlSystem = ReturnType; @@ -26,6 +27,7 @@ export type InfraRequestHandlerContext = InfraMlRequestHandlerContext & /** * @internal */ -export interface InfraPluginRequestHandlerContext extends DataRequestHandlerContext { +export interface InfraPluginRequestHandlerContext extends RequestHandlerContext { infra: InfraRequestHandlerContext; + search: SearchRequestHandlerContext; } From 7b35b28edc302a80cd69786824d2c982ff4bd06f Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 24 Mar 2021 13:32:52 -0700 Subject: [PATCH 79/93] [ci] try reducing Jest concurrency to mitigate random timeouts (#95348) Co-authored-by: spalger --- test/scripts/test/jest_unit.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scripts/test/jest_unit.sh b/test/scripts/test/jest_unit.sh index fd1166b07f3228..74d3c5b0cad180 100755 --- a/test/scripts/test/jest_unit.sh +++ b/test/scripts/test/jest_unit.sh @@ -3,4 +3,4 @@ source src/dev/ci_setup/setup_env.sh checks-reporter-with-killswitch "Jest Unit Tests" \ - node scripts/jest --ci --verbose --maxWorkers=8 + node scripts/jest --ci --verbose --maxWorkers=6 From 9d472bceafd2208da3301eaf79c8cc1c8636b4fe Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 24 Mar 2021 15:45:24 -0700 Subject: [PATCH 80/93] [dev/cli/timings] report on time to dev server listening (#95120) Co-authored-by: spalger --- docs/developer/telemetry.asciidoc | 1 + src/dev/cli_dev_mode/cli_dev_mode.test.ts | 8 ++ src/dev/cli_dev_mode/cli_dev_mode.ts | 96 +++++++++++++----- src/dev/cli_dev_mode/dev_server.test.ts | 117 +++++++++++++++++++++- src/dev/cli_dev_mode/dev_server.ts | 27 +++++ 5 files changed, 224 insertions(+), 25 deletions(-) diff --git a/docs/developer/telemetry.asciidoc b/docs/developer/telemetry.asciidoc index fe2bf5f9573791..c478c091c1c101 100644 --- a/docs/developer/telemetry.asciidoc +++ b/docs/developer/telemetry.asciidoc @@ -8,6 +8,7 @@ The operations we current report timing data for: * Total execution time of `yarn kbn bootstrap`. * Total execution time of `@kbn/optimizer` runs as well as the following metadata about the runs: The number of bundles created, the number of bundles which were cached, usage of `--watch`, `--dist`, `--workers` and `--no-cache` flags, and the count of themes being built. * The time from when you run `yarn start` until both the Kibana server and `@kbn/optimizer` are ready for use. +* The time it takes for the Kibana server to start listening after it is spawned by `yarn start`. Along with the execution time of each execution, we ship the following information about your machine to the service: diff --git a/src/dev/cli_dev_mode/cli_dev_mode.test.ts b/src/dev/cli_dev_mode/cli_dev_mode.test.ts index 54c49ce21505ff..9ace543a8929bf 100644 --- a/src/dev/cli_dev_mode/cli_dev_mode.test.ts +++ b/src/dev/cli_dev_mode/cli_dev_mode.test.ts @@ -31,6 +31,9 @@ const { Optimizer } = jest.requireMock('./optimizer'); jest.mock('./dev_server'); const { DevServer } = jest.requireMock('./dev_server'); +jest.mock('@kbn/dev-utils/target/ci_stats_reporter'); +const { CiStatsReporter } = jest.requireMock('@kbn/dev-utils/target/ci_stats_reporter'); + jest.mock('./get_server_watch_paths', () => ({ getServerWatchPaths: jest.fn(() => ({ watchPaths: [''], @@ -208,6 +211,11 @@ describe('#start()/#stop()', () => { run$: devServerRun$, }; }); + CiStatsReporter.fromEnv.mockImplementation(() => { + return { + isEnabled: jest.fn().mockReturnValue(false), + }; + }); }); afterEach(() => { diff --git a/src/dev/cli_dev_mode/cli_dev_mode.ts b/src/dev/cli_dev_mode/cli_dev_mode.ts index 1eed8b14aed4ab..f4f95f20daeef3 100644 --- a/src/dev/cli_dev_mode/cli_dev_mode.ts +++ b/src/dev/cli_dev_mode/cli_dev_mode.ts @@ -10,7 +10,16 @@ import Path from 'path'; import { REPO_ROOT, CiStatsReporter } from '@kbn/dev-utils'; import * as Rx from 'rxjs'; -import { map, mapTo, filter, take, tap, distinctUntilChanged, switchMap } from 'rxjs/operators'; +import { + map, + mapTo, + filter, + take, + tap, + distinctUntilChanged, + switchMap, + concatMap, +} from 'rxjs/operators'; import { CliArgs } from '../../core/server/config'; import { LegacyConfig } from '../../core/server/legacy'; @@ -167,29 +176,10 @@ export class CliDevMode { this.subscription = new Rx.Subscription(); this.startTime = Date.now(); - this.subscription.add( - this.getStarted$() - .pipe( - switchMap(async (success) => { - const reporter = CiStatsReporter.fromEnv(this.log.toolingLog); - await reporter.timings({ - timings: [ - { - group: 'yarn start', - id: 'started', - ms: Date.now() - this.startTime!, - meta: { success }, - }, - ], - }); - }) - ) - .subscribe({ - error: (error) => { - this.log.bad(`[ci-stats/timings] unable to record startup time:`, error.stack); - }, - }) - ); + const reporter = CiStatsReporter.fromEnv(this.log.toolingLog); + if (reporter.isEnabled()) { + this.subscription.add(this.reportTimings(reporter)); + } if (basePathProxy) { const serverReady$ = new Rx.BehaviorSubject(false); @@ -245,6 +235,64 @@ export class CliDevMode { this.subscription.add(this.devServer.run$.subscribe(this.observer('dev server'))); } + private reportTimings(reporter: CiStatsReporter) { + const sub = new Rx.Subscription(); + + sub.add( + this.getStarted$() + .pipe( + concatMap(async (success) => { + await reporter.timings({ + timings: [ + { + group: 'yarn start', + id: 'started', + ms: Date.now() - this.startTime!, + meta: { success }, + }, + ], + }); + }) + ) + .subscribe({ + error: (error) => { + this.log.bad(`[ci-stats/timings] unable to record startup time:`, error.stack); + }, + }) + ); + + sub.add( + this.devServer + .getRestartTime$() + .pipe( + concatMap(async ({ ms }, i) => { + await reporter.timings({ + timings: [ + { + group: 'yarn start', + id: 'dev server restart', + ms, + meta: { + sequence: i + 1, + }, + }, + ], + }); + }) + ) + .subscribe({ + error: (error) => { + this.log.bad( + `[ci-stats/timings] unable to record dev server restart time:`, + error.stack + ); + }, + }) + ); + + return sub; + } + /** * returns an observable that emits once the dev server and optimizer are started, emits * true if they both started successfully, otherwise false diff --git a/src/dev/cli_dev_mode/dev_server.test.ts b/src/dev/cli_dev_mode/dev_server.test.ts index c296c7caca63a1..9962a9a285a42d 100644 --- a/src/dev/cli_dev_mode/dev_server.test.ts +++ b/src/dev/cli_dev_mode/dev_server.test.ts @@ -15,6 +15,8 @@ import { extendedEnvSerializer } from './test_helpers'; import { DevServer, Options } from './dev_server'; import { TestLog } from './log'; +jest.useFakeTimers('modern'); + class MockProc extends EventEmitter { public readonly signalsSent: string[] = []; @@ -91,6 +93,17 @@ const run = (server: DevServer) => { return subscription; }; +const collect = (stream: Rx.Observable) => { + const events: T[] = []; + const subscription = stream.subscribe({ + next(item) { + events.push(item); + }, + }); + subscriptions.push(subscription); + return events; +}; + afterEach(() => { if (currentProc) { currentProc.removeAllListeners(); @@ -107,6 +120,9 @@ describe('#run$', () => { it('starts the dev server with the right options', () => { run(new DevServer(defaultOptions)).unsubscribe(); + // ensure that FORCE_COLOR is in the env for consistency in snapshot + process.env.FORCE_COLOR = process.env.FORCE_COLOR || 'true'; + expect(execa.node.mock.calls).toMatchInlineSnapshot(` Array [ Array [ @@ -305,7 +321,106 @@ describe('#run$', () => { expect(currentProc.signalsSent).toEqual([]); sigint$.next(); expect(currentProc.signalsSent).toEqual(['SIGINT']); - await new Promise((resolve) => setTimeout(resolve, 1000)); + jest.advanceTimersByTime(100); expect(currentProc.signalsSent).toEqual(['SIGINT', 'SIGKILL']); }); }); + +describe('#getPhase$', () => { + it('emits "starting" when run$ is subscribed then emits "fatal exit" when server exits with code > 0, then starting once watcher fires and "listening" when the server is ready', () => { + const server = new DevServer(defaultOptions); + const events = collect(server.getPhase$()); + + expect(events).toEqual([]); + run(server); + expect(events).toEqual(['starting']); + events.length = 0; + + isProc(currentProc); + currentProc.mockExit(2); + expect(events).toEqual(['fatal exit']); + events.length = 0; + + restart$.next(); + expect(events).toEqual(['starting']); + events.length = 0; + + currentProc.mockListening(); + expect(events).toEqual(['listening']); + }); +}); + +describe('#getRestartTime$()', () => { + it('does not send event if server does not start listening before starting again', () => { + const server = new DevServer(defaultOptions); + const phases = collect(server.getPhase$()); + const events = collect(server.getRestartTime$()); + run(server); + + isProc(currentProc); + restart$.next(); + jest.advanceTimersByTime(1000); + restart$.next(); + jest.advanceTimersByTime(1000); + restart$.next(); + expect(phases).toMatchInlineSnapshot(` + Array [ + "starting", + "starting", + "starting", + "starting", + ] + `); + expect(events).toEqual([]); + }); + + it('reports restart times', () => { + const server = new DevServer(defaultOptions); + const phases = collect(server.getPhase$()); + const events = collect(server.getRestartTime$()); + + run(server); + isProc(currentProc); + + restart$.next(); + currentProc.mockExit(1); + restart$.next(); + restart$.next(); + restart$.next(); + currentProc.mockExit(1); + restart$.next(); + jest.advanceTimersByTime(1234); + currentProc.mockListening(); + restart$.next(); + restart$.next(); + jest.advanceTimersByTime(5678); + currentProc.mockListening(); + + expect(phases).toMatchInlineSnapshot(` + Array [ + "starting", + "starting", + "fatal exit", + "starting", + "starting", + "starting", + "fatal exit", + "starting", + "listening", + "starting", + "starting", + "listening", + ] + `); + expect(events).toMatchInlineSnapshot(` + Array [ + Object { + "ms": 1234, + }, + Object { + "ms": 5678, + }, + ] + `); + }); +}); diff --git a/src/dev/cli_dev_mode/dev_server.ts b/src/dev/cli_dev_mode/dev_server.ts index a4e32a40665e3c..3daf298c823249 100644 --- a/src/dev/cli_dev_mode/dev_server.ts +++ b/src/dev/cli_dev_mode/dev_server.ts @@ -16,6 +16,7 @@ import { share, mergeMap, switchMap, + scan, takeUntil, ignoreElements, } from 'rxjs/operators'; @@ -73,6 +74,32 @@ export class DevServer { return this.phase$.asObservable(); } + /** + * returns an observable of objects describing server start time. + */ + getRestartTime$() { + return this.phase$.pipe( + scan((acc: undefined | { phase: string; time: number }, phase) => { + if (phase === 'starting') { + return { phase, time: Date.now() }; + } + + if (phase === 'listening' && acc?.phase === 'starting') { + return { phase, time: Date.now() - acc.time }; + } + + return undefined; + }, undefined), + mergeMap((desc) => { + if (desc?.phase !== 'listening') { + return []; + } + + return [{ ms: desc.time }]; + }) + ); + } + /** * Run the Kibana server * From 49078c82bc10379f0f8a9691b33f366751e4831a Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Wed, 24 Mar 2021 17:59:13 -0600 Subject: [PATCH 81/93] [core.savedObjects] Add helper for using find with pit and search_after. (#92981) --- ...er.isavedobjectspointintimefinder.close.md | 15 ++ ...ver.isavedobjectspointintimefinder.find.md | 13 ++ ...e-server.isavedobjectspointintimefinder.md | 20 ++ .../core/server/kibana-plugin-core-server.md | 3 + ...ver.savedobjectsclient.closepointintime.md | 2 + ...edobjectsclient.createpointintimefinder.md | 53 ++++++ ...a-plugin-core-server.savedobjectsclient.md | 5 +- ...vedobjectsclient.openpointintimefortype.md | 2 + ...atepointintimefinderdependencies.client.md | 11 ++ ...ectscreatepointintimefinderdependencies.md | 19 ++ ...edobjectscreatepointintimefinderoptions.md | 12 ++ ...savedobjectsrepository.closepointintime.md | 2 + ...jectsrepository.createpointintimefinder.md | 53 ++++++ ...ugin-core-server.savedobjectsrepository.md | 5 +- ...bjectsrepository.openpointintimefortype.md | 2 + ...plugin-plugins-data-server.plugin.start.md | 4 +- src/core/server/index.ts | 3 + .../export/saved_objects_exporter.ts | 11 +- .../saved_objects_service.test.ts | 8 +- .../saved_objects/saved_objects_service.ts | 1 + .../server/saved_objects/service/index.ts | 3 + .../server/saved_objects/service/lib/index.ts | 7 + .../service/lib/point_in_time_finder.mock.ts | 43 +++++ .../lib}/point_in_time_finder.test.ts | 173 ++++++++++++------ .../lib}/point_in_time_finder.ts | 128 +++++++------ .../service/lib/repository.mock.ts | 48 +++-- .../service/lib/repository.test.js | 35 ++++ .../service/lib/repository.test.mock.ts | 12 ++ .../saved_objects/service/lib/repository.ts | 75 ++++++++ .../lib/repository_create_repository.test.ts | 6 + .../service/saved_objects_client.mock.ts | 15 +- .../service/saved_objects_client.test.js | 39 ++++ .../service/saved_objects_client.ts | 69 ++++++- src/core/server/server.api.md | 19 +- src/plugins/data/server/server.api.md | 2 +- ...ypted_saved_objects_client_wrapper.test.ts | 27 +++ .../encrypted_saved_objects_client_wrapper.ts | 13 ++ ...ecure_saved_objects_client_wrapper.test.ts | 30 +++ .../secure_saved_objects_client_wrapper.ts | 16 ++ .../spaces_saved_objects_client.test.ts | 38 ++++ .../spaces_saved_objects_client.ts | 29 +++ 41 files changed, 904 insertions(+), 167 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.close.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.find.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.client.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderoptions.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md create mode 100644 src/core/server/saved_objects/service/lib/point_in_time_finder.mock.ts rename src/core/server/saved_objects/{export => service/lib}/point_in_time_finder.test.ts (57%) rename src/core/server/saved_objects/{export => service/lib}/point_in_time_finder.ts (58%) create mode 100644 src/core/server/saved_objects/service/lib/repository.test.mock.ts diff --git a/docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.close.md b/docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.close.md new file mode 100644 index 00000000000000..f7cfab446eecaa --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.close.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) > [close](./kibana-plugin-core-server.isavedobjectspointintimefinder.close.md) + +## ISavedObjectsPointInTimeFinder.close property + +Closes the Point-In-Time associated with this finder instance. + +Once you have retrieved all of the results you need, it is recommended to call `close()` to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to `find` fails for any reason. + +Signature: + +```typescript +close: () => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.find.md b/docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.find.md new file mode 100644 index 00000000000000..1755ff40c2bc06 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.find.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) > [find](./kibana-plugin-core-server.isavedobjectspointintimefinder.find.md) + +## ISavedObjectsPointInTimeFinder.find property + +An async generator which wraps calls to `savedObjectsClient.find` and iterates over multiple pages of results using `_pit` and `search_after`. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated `perPage` size. + +Signature: + +```typescript +find: () => AsyncGenerator; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.md b/docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.md new file mode 100644 index 00000000000000..4686df18e01343 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.isavedobjectspointintimefinder.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) + +## ISavedObjectsPointInTimeFinder interface + + +Signature: + +```typescript +export interface ISavedObjectsPointInTimeFinder +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [close](./kibana-plugin-core-server.isavedobjectspointintimefinder.close.md) | () => Promise<void> | Closes the Point-In-Time associated with this finder instance.Once you have retrieved all of the results you need, it is recommended to call close() to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to find fails for any reason. | +| [find](./kibana-plugin-core-server.isavedobjectspointintimefinder.find.md) | () => AsyncGenerator<SavedObjectsFindResponse> | An async generator which wraps calls to savedObjectsClient.find and iterates over multiple pages of results using _pit and search_after. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated perPage size. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 8dd4667002ead1..4bf00d2da6e23a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -98,6 +98,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IndexSettingsDeprecationInfo](./kibana-plugin-core-server.indexsettingsdeprecationinfo.md) | | | [IRenderOptions](./kibana-plugin-core-server.irenderoptions.md) | | | [IRouter](./kibana-plugin-core-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-core-server.routeconfig.md) and [RequestHandler](./kibana-plugin-core-server.requesthandler.md) for more information about arguments to route registrations. | +| [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) | | | [IScopedClusterClient](./kibana-plugin-core-server.iscopedclusterclient.md) | Serves the same purpose as the normal [cluster client](./kibana-plugin-core-server.iclusterclient.md) but exposes an additional asCurrentUser method that doesn't use credentials of the Kibana internal user (as asInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API instead. | | [IUiSettingsClient](./kibana-plugin-core-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. | | [KibanaRequestEvents](./kibana-plugin-core-server.kibanarequestevents.md) | Request events. | @@ -158,6 +159,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsComplexFieldMapping](./kibana-plugin-core-server.savedobjectscomplexfieldmapping.md) | See [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) for documentation. | | [SavedObjectsCoreFieldMapping](./kibana-plugin-core-server.savedobjectscorefieldmapping.md) | See [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) for documentation. | | [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md) | | +| [SavedObjectsCreatePointInTimeFinderDependencies](./kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.md) | | | [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.md) | | | [SavedObjectsDeleteFromNamespacesOptions](./kibana-plugin-core-server.savedobjectsdeletefromnamespacesoptions.md) | | | [SavedObjectsDeleteFromNamespacesResponse](./kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.md) | | @@ -305,6 +307,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientFactoryProvider](./kibana-plugin-core-server.savedobjectsclientfactoryprovider.md) | Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md). | | [SavedObjectsClientWrapperFactory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | | [SavedObjectsClosePointInTimeOptions](./kibana-plugin-core-server.savedobjectsclosepointintimeoptions.md) | | +| [SavedObjectsCreatePointInTimeFinderOptions](./kibana-plugin-core-server.savedobjectscreatepointintimefinderoptions.md) | | | [SavedObjectsExportTransform](./kibana-plugin-core-server.savedobjectsexporttransform.md) | Transformation function used to mutate the exported objects of the associated type.A type's export transform function will be executed once per user-initiated export, for all objects of that type. | | [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) field.Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html) For the mapping documentation | | [SavedObjectsImportHook](./kibana-plugin-core-server.savedobjectsimporthook.md) | A hook associated with a specific saved object type, that will be invoked during the import process. The hook will have access to the objects of the registered type.Currently, the only supported feature for import hooks is to return warnings to be displayed in the UI when the import succeeds. The only interactions the hook can have with the import process is via the hook's response. Mutating the objects inside the hook's code will have no effect. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.closepointintime.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.closepointintime.md index dc765260a08ca7..79c7d18adf3066 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.closepointintime.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.closepointintime.md @@ -6,6 +6,8 @@ Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using [SavedObjectsClient.openPointInTimeForType()](./kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md). +Only use this API if you have an advanced use case that's not solved by the [SavedObjectsClient.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md) method. + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md new file mode 100644 index 00000000000000..8afd9634645741 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md @@ -0,0 +1,53 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsClient](./kibana-plugin-core-server.savedobjectsclient.md) > [createPointInTimeFinder](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md) + +## SavedObjectsClient.createPointInTimeFinder() method + +Returns a [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) to help page through large sets of saved objects. We strongly recommend using this API for any `find` queries that might return more than 1000 saved objects, however this API is only intended for use in server-side "batch" processing of objects where you are collecting all objects in memory or streaming them back to the client. + +Do NOT use this API in a route handler to facilitate paging through saved objects on the client-side unless you are streaming all of the results back to the client at once. Because the returned generator is stateful, you cannot rely on subsequent http requests retrieving new pages from the same Kibana server in multi-instance deployments. + +The generator wraps calls to [SavedObjectsClient.find()](./kibana-plugin-core-server.savedobjectsclient.find.md) and iterates over multiple pages of results using `_pit` and `search_after`. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated `perPage`. + +Once you have retrieved all of the results you need, it is recommended to call `close()` to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to `find` fails for any reason. + +Signature: + +```typescript +createPointInTimeFinder(findOptions: SavedObjectsCreatePointInTimeFinderOptions, dependencies?: SavedObjectsCreatePointInTimeFinderDependencies): ISavedObjectsPointInTimeFinder; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| findOptions | SavedObjectsCreatePointInTimeFinderOptions | | +| dependencies | SavedObjectsCreatePointInTimeFinderDependencies | | + +Returns: + +`ISavedObjectsPointInTimeFinder` + +## Example + + +```ts +const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { + type: 'visualization', + search: 'foo*', + perPage: 100, +}; + +const finder = savedObjectsClient.createPointInTimeFinder(findOptions); + +const responses: SavedObjectFindResponse[] = []; +for await (const response of finder.find()) { + responses.push(...response); + if (doneSearching) { + await finder.close(); + } +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md index 887f7f7d93a873..95c2251f72c900 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md @@ -30,13 +30,14 @@ The constructor for this class is marked as internal. Third-party code should no | [bulkGet(objects, options)](./kibana-plugin-core-server.savedobjectsclient.bulkget.md) | | Returns an array of objects by id | | [bulkUpdate(objects, options)](./kibana-plugin-core-server.savedobjectsclient.bulkupdate.md) | | Bulk Updates multiple SavedObject at once | | [checkConflicts(objects, options)](./kibana-plugin-core-server.savedobjectsclient.checkconflicts.md) | | Check what conflicts will result when creating a given array of saved objects. This includes "unresolvable conflicts", which are multi-namespace objects that exist in a different namespace; such conflicts cannot be resolved/overwritten. | -| [closePointInTime(id, options)](./kibana-plugin-core-server.savedobjectsclient.closepointintime.md) | | Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using [SavedObjectsClient.openPointInTimeForType()](./kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md). | +| [closePointInTime(id, options)](./kibana-plugin-core-server.savedobjectsclient.closepointintime.md) | | Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using [SavedObjectsClient.openPointInTimeForType()](./kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md).Only use this API if you have an advanced use case that's not solved by the [SavedObjectsClient.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md) method. | | [create(type, attributes, options)](./kibana-plugin-core-server.savedobjectsclient.create.md) | | Persists a SavedObject | +| [createPointInTimeFinder(findOptions, dependencies)](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md) | | Returns a [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) to help page through large sets of saved objects. We strongly recommend using this API for any find queries that might return more than 1000 saved objects, however this API is only intended for use in server-side "batch" processing of objects where you are collecting all objects in memory or streaming them back to the client.Do NOT use this API in a route handler to facilitate paging through saved objects on the client-side unless you are streaming all of the results back to the client at once. Because the returned generator is stateful, you cannot rely on subsequent http requests retrieving new pages from the same Kibana server in multi-instance deployments.The generator wraps calls to [SavedObjectsClient.find()](./kibana-plugin-core-server.savedobjectsclient.find.md) and iterates over multiple pages of results using _pit and search_after. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated perPage.Once you have retrieved all of the results you need, it is recommended to call close() to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to find fails for any reason. | | [delete(type, id, options)](./kibana-plugin-core-server.savedobjectsclient.delete.md) | | Deletes a SavedObject | | [deleteFromNamespaces(type, id, namespaces, options)](./kibana-plugin-core-server.savedobjectsclient.deletefromnamespaces.md) | | Removes namespaces from a SavedObject | | [find(options)](./kibana-plugin-core-server.savedobjectsclient.find.md) | | Find all SavedObjects matching the search query | | [get(type, id, options)](./kibana-plugin-core-server.savedobjectsclient.get.md) | | Retrieves a single object | -| [openPointInTimeForType(type, options)](./kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md) | | Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned id can then be passed to [SavedObjectsClient.find()](./kibana-plugin-core-server.savedobjectsclient.find.md) to search against that PIT. | +| [openPointInTimeForType(type, options)](./kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md) | | Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned id can then be passed to [SavedObjectsClient.find()](./kibana-plugin-core-server.savedobjectsclient.find.md) to search against that PIT.Only use this API if you have an advanced use case that's not solved by the [SavedObjectsClient.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md) method. | | [removeReferencesTo(type, id, options)](./kibana-plugin-core-server.savedobjectsclient.removereferencesto.md) | | Updates all objects containing a reference to the given {type, id} tuple to remove the said reference. | | [resolve(type, id, options)](./kibana-plugin-core-server.savedobjectsclient.resolve.md) | | Resolves a single object, using any legacy URL alias if it exists | | [update(type, id, attributes, options)](./kibana-plugin-core-server.savedobjectsclient.update.md) | | Updates an SavedObject | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md index 56c1d6d1ddc331..c76159ffa50323 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md @@ -6,6 +6,8 @@ Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned `id` can then be passed to [SavedObjectsClient.find()](./kibana-plugin-core-server.savedobjectsclient.find.md) to search against that PIT. +Only use this API if you have an advanced use case that's not solved by the [SavedObjectsClient.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md) method. + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.client.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.client.md new file mode 100644 index 00000000000000..95ab9e225c0497 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.client.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsCreatePointInTimeFinderDependencies](./kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.md) > [client](./kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.client.md) + +## SavedObjectsCreatePointInTimeFinderDependencies.client property + +Signature: + +```typescript +client: Pick; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.md new file mode 100644 index 00000000000000..47c640bfabcb07 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsCreatePointInTimeFinderDependencies](./kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.md) + +## SavedObjectsCreatePointInTimeFinderDependencies interface + + +Signature: + +```typescript +export interface SavedObjectsCreatePointInTimeFinderDependencies +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [client](./kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.client.md) | Pick<SavedObjectsClientContract, 'find' | 'openPointInTimeForType' | 'closePointInTime'> | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderoptions.md new file mode 100644 index 00000000000000..928c6f72bcbf5d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreatepointintimefinderoptions.md @@ -0,0 +1,12 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsCreatePointInTimeFinderOptions](./kibana-plugin-core-server.savedobjectscreatepointintimefinderoptions.md) + +## SavedObjectsCreatePointInTimeFinderOptions type + + +Signature: + +```typescript +export declare type SavedObjectsCreatePointInTimeFinderOptions = Omit; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.closepointintime.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.closepointintime.md index 8f9dca35fa3629..b9d81c89bffd74 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.closepointintime.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.closepointintime.md @@ -6,6 +6,8 @@ Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using `openPointInTimeForType`. +Only use this API if you have an advanced use case that's not solved by the [SavedObjectsRepository.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md) method. + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md new file mode 100644 index 00000000000000..5d9d2857f6e0b1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md @@ -0,0 +1,53 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsRepository](./kibana-plugin-core-server.savedobjectsrepository.md) > [createPointInTimeFinder](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md) + +## SavedObjectsRepository.createPointInTimeFinder() method + +Returns a [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) to help page through large sets of saved objects. We strongly recommend using this API for any `find` queries that might return more than 1000 saved objects, however this API is only intended for use in server-side "batch" processing of objects where you are collecting all objects in memory or streaming them back to the client. + +Do NOT use this API in a route handler to facilitate paging through saved objects on the client-side unless you are streaming all of the results back to the client at once. Because the returned generator is stateful, you cannot rely on subsequent http requests retrieving new pages from the same Kibana server in multi-instance deployments. + +This generator wraps calls to [SavedObjectsRepository.find()](./kibana-plugin-core-server.savedobjectsrepository.find.md) and iterates over multiple pages of results using `_pit` and `search_after`. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated `perPage`. + +Once you have retrieved all of the results you need, it is recommended to call `close()` to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to `find` fails for any reason. + +Signature: + +```typescript +createPointInTimeFinder(findOptions: SavedObjectsCreatePointInTimeFinderOptions, dependencies?: SavedObjectsCreatePointInTimeFinderDependencies): ISavedObjectsPointInTimeFinder; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| findOptions | SavedObjectsCreatePointInTimeFinderOptions | | +| dependencies | SavedObjectsCreatePointInTimeFinderDependencies | | + +Returns: + +`ISavedObjectsPointInTimeFinder` + +## Example + + +```ts +const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { + type: 'visualization', + search: 'foo*', + perPage: 100, +}; + +const finder = savedObjectsClient.createPointInTimeFinder(findOptions); + +const responses: SavedObjectFindResponse[] = []; +for await (const response of finder.find()) { + responses.push(...response); + if (doneSearching) { + await finder.close(); + } +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md index 632d9c279cb882..00e6ed3aeddfcb 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md @@ -20,15 +20,16 @@ export declare class SavedObjectsRepository | [bulkGet(objects, options)](./kibana-plugin-core-server.savedobjectsrepository.bulkget.md) | | Returns an array of objects by id | | [bulkUpdate(objects, options)](./kibana-plugin-core-server.savedobjectsrepository.bulkupdate.md) | | Updates multiple objects in bulk | | [checkConflicts(objects, options)](./kibana-plugin-core-server.savedobjectsrepository.checkconflicts.md) | | Check what conflicts will result when creating a given array of saved objects. This includes "unresolvable conflicts", which are multi-namespace objects that exist in a different namespace; such conflicts cannot be resolved/overwritten. | -| [closePointInTime(id, options)](./kibana-plugin-core-server.savedobjectsrepository.closepointintime.md) | | Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using openPointInTimeForType. | +| [closePointInTime(id, options)](./kibana-plugin-core-server.savedobjectsrepository.closepointintime.md) | | Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using openPointInTimeForType.Only use this API if you have an advanced use case that's not solved by the [SavedObjectsRepository.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md) method. | | [create(type, attributes, options)](./kibana-plugin-core-server.savedobjectsrepository.create.md) | | Persists an object | +| [createPointInTimeFinder(findOptions, dependencies)](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md) | | Returns a [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) to help page through large sets of saved objects. We strongly recommend using this API for any find queries that might return more than 1000 saved objects, however this API is only intended for use in server-side "batch" processing of objects where you are collecting all objects in memory or streaming them back to the client.Do NOT use this API in a route handler to facilitate paging through saved objects on the client-side unless you are streaming all of the results back to the client at once. Because the returned generator is stateful, you cannot rely on subsequent http requests retrieving new pages from the same Kibana server in multi-instance deployments.This generator wraps calls to [SavedObjectsRepository.find()](./kibana-plugin-core-server.savedobjectsrepository.find.md) and iterates over multiple pages of results using _pit and search_after. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated perPage.Once you have retrieved all of the results you need, it is recommended to call close() to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to find fails for any reason. | | [delete(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.delete.md) | | Deletes an object | | [deleteByNamespace(namespace, options)](./kibana-plugin-core-server.savedobjectsrepository.deletebynamespace.md) | | Deletes all objects from the provided namespace. | | [deleteFromNamespaces(type, id, namespaces, options)](./kibana-plugin-core-server.savedobjectsrepository.deletefromnamespaces.md) | | Removes one or more namespaces from a given multi-namespace saved object. If no namespaces remain, the saved object is deleted entirely. This method and \[addToNamespaces\][SavedObjectsRepository.addToNamespaces()](./kibana-plugin-core-server.savedobjectsrepository.addtonamespaces.md) are the only ways to change which Spaces a multi-namespace saved object is shared to. | | [find(options)](./kibana-plugin-core-server.savedobjectsrepository.find.md) | | | | [get(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.get.md) | | Gets a single object | | [incrementCounter(type, id, counterFields, options)](./kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md) | | Increments all the specified counter fields (by one by default). Creates the document if one doesn't exist for the given id. | -| [openPointInTimeForType(type, { keepAlive, preference })](./kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md) | | Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned id can then be passed to SavedObjects.find to search against that PIT. | +| [openPointInTimeForType(type, { keepAlive, preference })](./kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md) | | Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned id can then be passed to SavedObjects.find to search against that PIT.Only use this API if you have an advanced use case that's not solved by the [SavedObjectsRepository.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md) method. | | [removeReferencesTo(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.removereferencesto.md) | | Updates all objects containing a reference to the given {type, id} tuple to remove the said reference. | | [resolve(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.resolve.md) | | Resolves a single object, using any legacy URL alias if it exists | | [update(type, id, attributes, options)](./kibana-plugin-core-server.savedobjectsrepository.update.md) | | Updates an object | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md index 6b668824845202..b33765bb79dd8e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md @@ -6,6 +6,8 @@ Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned `id` can then be passed to `SavedObjects.find` to search against that PIT. +Only use this API if you have an advanced use case that's not solved by the [SavedObjectsRepository.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md) method. + Signature: ```typescript diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index f479ffd52e9b89..025cab9f48c1a5 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -12,7 +12,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -31,7 +31,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }` diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 3e336dceb83d7a..788c179501a80e 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -282,6 +282,9 @@ export type { SavedObjectsClientFactoryProvider, SavedObjectsClosePointInTimeOptions, SavedObjectsClosePointInTimeResponse, + ISavedObjectsPointInTimeFinder, + SavedObjectsCreatePointInTimeFinderDependencies, + SavedObjectsCreatePointInTimeFinderOptions, SavedObjectsCreateOptions, SavedObjectsExportResultDetails, SavedObjectsFindResult, diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.ts b/src/core/server/saved_objects/export/saved_objects_exporter.ts index c1c0ea73f0bd3d..868efa872d643d 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.ts @@ -9,7 +9,7 @@ import { createListStream } from '@kbn/utils'; import { PublicMethodsOf } from '@kbn/utility-types'; import { Logger } from '../../logging'; -import { SavedObject, SavedObjectsClientContract, SavedObjectsFindOptions } from '../types'; +import { SavedObject, SavedObjectsClientContract } from '../types'; import { SavedObjectsFindResult } from '../service'; import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry'; import { fetchNestedDependencies } from './fetch_nested_dependencies'; @@ -23,7 +23,6 @@ import { } from './types'; import { SavedObjectsExportError } from './errors'; import { applyExportTransforms } from './apply_export_transforms'; -import { createPointInTimeFinder } from './point_in_time_finder'; import { byIdAscComparator, getPreservedOrderComparator, SavedObjectComparator } from './utils'; /** @@ -168,18 +167,12 @@ export class SavedObjectsExporter { hasReference, search, }: SavedObjectsExportByTypeOptions) { - const findOptions: SavedObjectsFindOptions = { + const finder = this.#savedObjectsClient.createPointInTimeFinder({ type: types, hasReference, hasReferenceOperator: hasReference ? 'OR' : undefined, search, namespaces: namespace ? [namespace] : undefined, - }; - - const finder = createPointInTimeFinder({ - findOptions, - logger: this.#log, - savedObjectsClient: this.#savedObjectsClient, }); const hits: SavedObjectsFindResult[] = []; diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index d589809e38f01d..52f8dcd3105090 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -274,7 +274,7 @@ describe('SavedObjectsService', () => { expect(coreStart.elasticsearch.client.asScoped).toHaveBeenCalledWith(req); const [ - [, , , , includedHiddenTypes], + [, , , , , includedHiddenTypes], ] = (SavedObjectsRepository.createRepository as jest.Mocked).mock.calls; expect(includedHiddenTypes).toEqual([]); @@ -292,7 +292,7 @@ describe('SavedObjectsService', () => { createScopedRepository(req, ['someHiddenType']); const [ - [, , , , includedHiddenTypes], + [, , , , , includedHiddenTypes], ] = (SavedObjectsRepository.createRepository as jest.Mocked).mock.calls; expect(includedHiddenTypes).toEqual(['someHiddenType']); @@ -311,7 +311,7 @@ describe('SavedObjectsService', () => { createInternalRepository(); const [ - [, , , client, includedHiddenTypes], + [, , , client, , includedHiddenTypes], ] = (SavedObjectsRepository.createRepository as jest.Mocked).mock.calls; expect(coreStart.elasticsearch.client.asInternalUser).toBe(client); @@ -328,7 +328,7 @@ describe('SavedObjectsService', () => { createInternalRepository(['someHiddenType']); const [ - [, , , , includedHiddenTypes], + [, , , , , includedHiddenTypes], ] = (SavedObjectsRepository.createRepository as jest.Mocked).mock.calls; expect(includedHiddenTypes).toEqual(['someHiddenType']); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index fce7f123844564..8e4320eb841f81 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -421,6 +421,7 @@ export class SavedObjectsService this.typeRegistry, kibanaConfig.index, esClient, + this.logger.get('repository'), includedHiddenTypes ); }; diff --git a/src/core/server/saved_objects/service/index.ts b/src/core/server/saved_objects/service/index.ts index 1186e15cbef4a5..8a66e6176d1f5a 100644 --- a/src/core/server/saved_objects/service/index.ts +++ b/src/core/server/saved_objects/service/index.ts @@ -8,6 +8,9 @@ export { SavedObjectsErrorHelpers, SavedObjectsClientProvider, SavedObjectsUtils } from './lib'; export type { SavedObjectsRepository, + ISavedObjectsPointInTimeFinder, + SavedObjectsCreatePointInTimeFinderOptions, + SavedObjectsCreatePointInTimeFinderDependencies, ISavedObjectsClientProvider, SavedObjectsClientProviderOptions, SavedObjectsClientWrapperFactory, diff --git a/src/core/server/saved_objects/service/lib/index.ts b/src/core/server/saved_objects/service/lib/index.ts index d05552bc6e55e4..09bce81b14c39f 100644 --- a/src/core/server/saved_objects/service/lib/index.ts +++ b/src/core/server/saved_objects/service/lib/index.ts @@ -8,6 +8,13 @@ export type { ISavedObjectsRepository, SavedObjectsRepository } from './repository'; export { SavedObjectsClientProvider } from './scoped_client_provider'; + +export type { + ISavedObjectsPointInTimeFinder, + SavedObjectsCreatePointInTimeFinderOptions, + SavedObjectsCreatePointInTimeFinderDependencies, +} from './point_in_time_finder'; + export type { SavedObjectsClientWrapperFactory, SavedObjectsClientWrapperOptions, diff --git a/src/core/server/saved_objects/service/lib/point_in_time_finder.mock.ts b/src/core/server/saved_objects/service/lib/point_in_time_finder.mock.ts new file mode 100644 index 00000000000000..c689eb319898b1 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/point_in_time_finder.mock.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { loggerMock, MockedLogger } from '../../../logging/logger.mock'; +import type { SavedObjectsClientContract } from '../../types'; +import type { ISavedObjectsRepository } from './repository'; +import { PointInTimeFinder } from './point_in_time_finder'; + +const createPointInTimeFinderMock = ({ + logger = loggerMock.create(), + savedObjectsMock, +}: { + logger?: MockedLogger; + savedObjectsMock: jest.Mocked; +}): jest.Mock => { + const mock = jest.fn(); + + // To simplify testing, we use the actual implementation here, but pass through the + // mocked dependencies. This allows users to set their own `mockResolvedValue` on + // the SO client mock and have it reflected when using `createPointInTimeFinder`. + mock.mockImplementation((findOptions) => { + const finder = new PointInTimeFinder(findOptions, { + logger, + client: savedObjectsMock, + }); + + jest.spyOn(finder, 'find'); + jest.spyOn(finder, 'close'); + + return finder; + }); + + return mock; +}; + +export const savedObjectsPointInTimeFinderMock = { + create: createPointInTimeFinderMock, +}; diff --git a/src/core/server/saved_objects/export/point_in_time_finder.test.ts b/src/core/server/saved_objects/service/lib/point_in_time_finder.test.ts similarity index 57% rename from src/core/server/saved_objects/export/point_in_time_finder.test.ts rename to src/core/server/saved_objects/service/lib/point_in_time_finder.test.ts index cd79c7a4b81e5a..044bb452695385 100644 --- a/src/core/server/saved_objects/export/point_in_time_finder.test.ts +++ b/src/core/server/saved_objects/service/lib/point_in_time_finder.test.ts @@ -6,12 +6,15 @@ * Side Public License, v 1. */ -import { savedObjectsClientMock } from '../service/saved_objects_client.mock'; -import { loggerMock, MockedLogger } from '../../logging/logger.mock'; -import { SavedObjectsFindOptions } from '../types'; -import { SavedObjectsFindResult } from '../service'; +import { loggerMock, MockedLogger } from '../../../logging/logger.mock'; +import type { SavedObjectsClientContract } from '../../types'; +import type { SavedObjectsFindResult } from '../'; +import { savedObjectsRepositoryMock } from './repository.mock'; -import { createPointInTimeFinder } from './point_in_time_finder'; +import { + PointInTimeFinder, + SavedObjectsCreatePointInTimeFinderOptions, +} from './point_in_time_finder'; const mockHits = [ { @@ -40,26 +43,31 @@ const mockHits = [ describe('createPointInTimeFinder()', () => { let logger: MockedLogger; - let savedObjectsClient: ReturnType; + let find: jest.Mocked['find']; + let openPointInTimeForType: jest.Mocked['openPointInTimeForType']; + let closePointInTime: jest.Mocked['closePointInTime']; beforeEach(() => { logger = loggerMock.create(); - savedObjectsClient = savedObjectsClientMock.create(); + const mockRepository = savedObjectsRepositoryMock.create(); + find = mockRepository.find; + openPointInTimeForType = mockRepository.openPointInTimeForType; + closePointInTime = mockRepository.closePointInTime; }); describe('#find', () => { test('throws if a PIT is already open', async () => { - savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({ + openPointInTimeForType.mockResolvedValueOnce({ id: 'abc123', }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 2, saved_objects: mockHits, pit_id: 'abc123', per_page: 1, page: 0, }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 2, saved_objects: mockHits, pit_id: 'abc123', @@ -67,31 +75,38 @@ describe('createPointInTimeFinder()', () => { page: 1, }); - const findOptions: SavedObjectsFindOptions = { + const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { type: ['visualization'], search: 'foo*', perPage: 1, }; - const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient }); + const finder = new PointInTimeFinder(findOptions, { + logger, + client: { + find, + openPointInTimeForType, + closePointInTime, + }, + }); await finder.find().next(); - expect(savedObjectsClient.find).toHaveBeenCalledTimes(1); - savedObjectsClient.find.mockClear(); + expect(find).toHaveBeenCalledTimes(1); + find.mockClear(); expect(async () => { await finder.find().next(); }).rejects.toThrowErrorMatchingInlineSnapshot( `"Point In Time has already been opened for this finder instance. Please call \`close()\` before calling \`find()\` again."` ); - expect(savedObjectsClient.find).toHaveBeenCalledTimes(0); + expect(find).toHaveBeenCalledTimes(0); }); test('works with a single page of results', async () => { - savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({ + openPointInTimeForType.mockResolvedValueOnce({ id: 'abc123', }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 2, saved_objects: mockHits, pit_id: 'abc123', @@ -99,22 +114,29 @@ describe('createPointInTimeFinder()', () => { page: 0, }); - const findOptions: SavedObjectsFindOptions = { + const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { type: ['visualization'], search: 'foo*', }; - const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient }); + const finder = new PointInTimeFinder(findOptions, { + logger, + client: { + find, + openPointInTimeForType, + closePointInTime, + }, + }); const hits: SavedObjectsFindResult[] = []; for await (const result of finder.find()) { hits.push(...result.saved_objects); } expect(hits.length).toBe(2); - expect(savedObjectsClient.openPointInTimeForType).toHaveBeenCalledTimes(1); - expect(savedObjectsClient.closePointInTime).toHaveBeenCalledTimes(1); - expect(savedObjectsClient.find).toHaveBeenCalledTimes(1); - expect(savedObjectsClient.find).toHaveBeenCalledWith( + expect(openPointInTimeForType).toHaveBeenCalledTimes(1); + expect(closePointInTime).toHaveBeenCalledTimes(1); + expect(find).toHaveBeenCalledTimes(1); + expect(find).toHaveBeenCalledWith( expect.objectContaining({ pit: expect.objectContaining({ id: 'abc123', keepAlive: '2m' }), sortField: 'updated_at', @@ -125,24 +147,24 @@ describe('createPointInTimeFinder()', () => { }); test('works with multiple pages of results', async () => { - savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({ + openPointInTimeForType.mockResolvedValueOnce({ id: 'abc123', }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 2, saved_objects: [mockHits[0]], pit_id: 'abc123', per_page: 1, page: 0, }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 2, saved_objects: [mockHits[1]], pit_id: 'abc123', per_page: 1, page: 0, }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 2, saved_objects: [], per_page: 1, @@ -150,25 +172,32 @@ describe('createPointInTimeFinder()', () => { page: 0, }); - const findOptions: SavedObjectsFindOptions = { + const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { type: ['visualization'], search: 'foo*', perPage: 1, }; - const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient }); + const finder = new PointInTimeFinder(findOptions, { + logger, + client: { + find, + openPointInTimeForType, + closePointInTime, + }, + }); const hits: SavedObjectsFindResult[] = []; for await (const result of finder.find()) { hits.push(...result.saved_objects); } expect(hits.length).toBe(2); - expect(savedObjectsClient.openPointInTimeForType).toHaveBeenCalledTimes(1); - expect(savedObjectsClient.closePointInTime).toHaveBeenCalledTimes(1); + expect(openPointInTimeForType).toHaveBeenCalledTimes(1); + expect(closePointInTime).toHaveBeenCalledTimes(1); // called 3 times since we need a 3rd request to check if we // are done paginating through results. - expect(savedObjectsClient.find).toHaveBeenCalledTimes(3); - expect(savedObjectsClient.find).toHaveBeenCalledWith( + expect(find).toHaveBeenCalledTimes(3); + expect(find).toHaveBeenCalledWith( expect.objectContaining({ pit: expect.objectContaining({ id: 'abc123', keepAlive: '2m' }), sortField: 'updated_at', @@ -181,10 +210,10 @@ describe('createPointInTimeFinder()', () => { describe('#close', () => { test('calls closePointInTime with correct ID', async () => { - savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({ + openPointInTimeForType.mockResolvedValueOnce({ id: 'test', }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 1, saved_objects: [mockHits[0]], pit_id: 'test', @@ -192,41 +221,48 @@ describe('createPointInTimeFinder()', () => { page: 0, }); - const findOptions: SavedObjectsFindOptions = { + const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { type: ['visualization'], search: 'foo*', perPage: 2, }; - const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient }); + const finder = new PointInTimeFinder(findOptions, { + logger, + client: { + find, + openPointInTimeForType, + closePointInTime, + }, + }); const hits: SavedObjectsFindResult[] = []; for await (const result of finder.find()) { hits.push(...result.saved_objects); await finder.close(); } - expect(savedObjectsClient.closePointInTime).toHaveBeenCalledWith('test'); + expect(closePointInTime).toHaveBeenCalledWith('test'); }); test('causes generator to stop', async () => { - savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({ + openPointInTimeForType.mockResolvedValueOnce({ id: 'test', }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 2, saved_objects: [mockHits[0]], pit_id: 'test', per_page: 1, page: 0, }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 2, saved_objects: [mockHits[1]], pit_id: 'test', per_page: 1, page: 0, }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 2, saved_objects: [], per_page: 1, @@ -234,36 +270,50 @@ describe('createPointInTimeFinder()', () => { page: 0, }); - const findOptions: SavedObjectsFindOptions = { + const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { type: ['visualization'], search: 'foo*', perPage: 1, }; - const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient }); + const finder = new PointInTimeFinder(findOptions, { + logger, + client: { + find, + openPointInTimeForType, + closePointInTime, + }, + }); const hits: SavedObjectsFindResult[] = []; for await (const result of finder.find()) { hits.push(...result.saved_objects); await finder.close(); } - expect(savedObjectsClient.closePointInTime).toHaveBeenCalledTimes(1); + expect(closePointInTime).toHaveBeenCalledTimes(1); expect(hits.length).toBe(1); }); test('is called if `find` throws an error', async () => { - savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({ + openPointInTimeForType.mockResolvedValueOnce({ id: 'test', }); - savedObjectsClient.find.mockRejectedValueOnce(new Error('oops')); + find.mockRejectedValueOnce(new Error('oops')); - const findOptions: SavedObjectsFindOptions = { + const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { type: ['visualization'], search: 'foo*', perPage: 2, }; - const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient }); + const finder = new PointInTimeFinder(findOptions, { + logger, + client: { + find, + openPointInTimeForType, + closePointInTime, + }, + }); const hits: SavedObjectsFindResult[] = []; try { for await (const result of finder.find()) { @@ -273,21 +323,21 @@ describe('createPointInTimeFinder()', () => { // intentionally empty } - expect(savedObjectsClient.closePointInTime).toHaveBeenCalledWith('test'); + expect(closePointInTime).toHaveBeenCalledWith('test'); }); test('finder can be reused after closing', async () => { - savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({ + openPointInTimeForType.mockResolvedValueOnce({ id: 'abc123', }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 2, saved_objects: mockHits, pit_id: 'abc123', per_page: 1, page: 0, }); - savedObjectsClient.find.mockResolvedValueOnce({ + find.mockResolvedValueOnce({ total: 2, saved_objects: mockHits, pit_id: 'abc123', @@ -295,13 +345,20 @@ describe('createPointInTimeFinder()', () => { page: 1, }); - const findOptions: SavedObjectsFindOptions = { + const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { type: ['visualization'], search: 'foo*', perPage: 1, }; - const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient }); + const finder = new PointInTimeFinder(findOptions, { + logger, + client: { + find, + openPointInTimeForType, + closePointInTime, + }, + }); const findA = finder.find(); await findA.next(); @@ -313,9 +370,9 @@ describe('createPointInTimeFinder()', () => { expect((await findA.next()).done).toBe(true); expect((await findB.next()).done).toBe(true); - expect(savedObjectsClient.openPointInTimeForType).toHaveBeenCalledTimes(2); - expect(savedObjectsClient.find).toHaveBeenCalledTimes(2); - expect(savedObjectsClient.closePointInTime).toHaveBeenCalledTimes(2); + expect(openPointInTimeForType).toHaveBeenCalledTimes(2); + expect(find).toHaveBeenCalledTimes(2); + expect(closePointInTime).toHaveBeenCalledTimes(2); }); }); }); diff --git a/src/core/server/saved_objects/export/point_in_time_finder.ts b/src/core/server/saved_objects/service/lib/point_in_time_finder.ts similarity index 58% rename from src/core/server/saved_objects/export/point_in_time_finder.ts rename to src/core/server/saved_objects/service/lib/point_in_time_finder.ts index dc0bac6b6bfd99..b8f459151e7b35 100644 --- a/src/core/server/saved_objects/export/point_in_time_finder.ts +++ b/src/core/server/saved_objects/service/lib/point_in_time_finder.ts @@ -6,79 +6,76 @@ * Side Public License, v 1. */ -import { Logger } from '../../logging'; -import { SavedObjectsClientContract, SavedObjectsFindOptions } from '../types'; -import { SavedObjectsFindResponse } from '../service'; +import type { Logger } from '../../../logging'; +import type { SavedObjectsFindOptions, SavedObjectsClientContract } from '../../types'; +import type { SavedObjectsFindResponse } from '../'; + +type PointInTimeFinderClient = Pick< + SavedObjectsClientContract, + 'find' | 'openPointInTimeForType' | 'closePointInTime' +>; + +/** + * @public + */ +export type SavedObjectsCreatePointInTimeFinderOptions = Omit< + SavedObjectsFindOptions, + 'page' | 'pit' | 'searchAfter' +>; /** - * Returns a generator to help page through large sets of saved objects. - * - * The generator wraps calls to `SavedObjects.find` and iterates over - * multiple pages of results using `_pit` and `search_after`. This will - * open a new Point In Time (PIT), and continue paging until a set of - * results is received that's smaller than the designated `perPage`. - * - * Once you have retrieved all of the results you need, it is recommended - * to call `close()` to clean up the PIT and prevent Elasticsearch from - * consuming resources unnecessarily. This will automatically be done for - * you if you reach the last page of results. - * - * @example - * ```ts - * const findOptions: SavedObjectsFindOptions = { - * type: 'visualization', - * search: 'foo*', - * perPage: 100, - * }; - * - * const finder = createPointInTimeFinder({ - * logger, - * savedObjectsClient, - * findOptions, - * }); - * - * const responses: SavedObjectFindResponse[] = []; - * for await (const response of finder.find()) { - * responses.push(...response); - * if (doneSearching) { - * await finder.close(); - * } - * } - * ``` + * @public */ -export function createPointInTimeFinder({ - findOptions, - logger, - savedObjectsClient, -}: { - findOptions: SavedObjectsFindOptions; +export interface SavedObjectsCreatePointInTimeFinderDependencies { + client: Pick; +} + +/** + * @internal + */ +export interface PointInTimeFinderDependencies + extends SavedObjectsCreatePointInTimeFinderDependencies { logger: Logger; - savedObjectsClient: SavedObjectsClientContract; -}) { - return new PointInTimeFinder({ findOptions, logger, savedObjectsClient }); +} + +/** @public */ +export interface ISavedObjectsPointInTimeFinder { + /** + * An async generator which wraps calls to `savedObjectsClient.find` and + * iterates over multiple pages of results using `_pit` and `search_after`. + * This will open a new Point-In-Time (PIT), and continue paging until a set + * of results is received that's smaller than the designated `perPage` size. + */ + find: () => AsyncGenerator; + /** + * Closes the Point-In-Time associated with this finder instance. + * + * Once you have retrieved all of the results you need, it is recommended + * to call `close()` to clean up the PIT and prevent Elasticsearch from + * consuming resources unnecessarily. This is only required if you are + * done iterating and have not yet paged through all of the results: the + * PIT will automatically be closed for you once you reach the last page + * of results, or if the underlying call to `find` fails for any reason. + */ + close: () => Promise; } /** * @internal */ -export class PointInTimeFinder { +export class PointInTimeFinder implements ISavedObjectsPointInTimeFinder { readonly #log: Logger; - readonly #savedObjectsClient: SavedObjectsClientContract; + readonly #client: PointInTimeFinderClient; readonly #findOptions: SavedObjectsFindOptions; #open: boolean = false; #pitId?: string; - constructor({ - findOptions, - logger, - savedObjectsClient, - }: { - findOptions: SavedObjectsFindOptions; - logger: Logger; - savedObjectsClient: SavedObjectsClientContract; - }) { - this.#log = logger; - this.#savedObjectsClient = savedObjectsClient; + constructor( + findOptions: SavedObjectsCreatePointInTimeFinderOptions, + { logger, client }: PointInTimeFinderDependencies + ) { + this.#log = logger.get('point-in-time-finder'); + this.#client = client; this.#findOptions = { // Default to 1000 items per page as a tradeoff between // speed and memory consumption. @@ -110,7 +107,7 @@ export class PointInTimeFinder { lastResultsCount = results.saved_objects.length; lastHitSortValue = this.getLastHitSortValue(results); - this.#log.debug(`Collected [${lastResultsCount}] saved objects for export.`); + this.#log.debug(`Collected [${lastResultsCount}] saved objects`); // Close PIT if this was our last page if (this.#pitId && lastResultsCount < this.#findOptions.perPage!) { @@ -129,7 +126,7 @@ export class PointInTimeFinder { try { if (this.#pitId) { this.#log.debug(`Closing PIT for types [${this.#findOptions.type}]`); - await this.#savedObjectsClient.closePointInTime(this.#pitId); + await this.#client.closePointInTime(this.#pitId); this.#pitId = undefined; } this.#open = false; @@ -141,13 +138,14 @@ export class PointInTimeFinder { private async open() { try { - const { id } = await this.#savedObjectsClient.openPointInTimeForType(this.#findOptions.type); + const { id } = await this.#client.openPointInTimeForType(this.#findOptions.type); this.#pitId = id; this.#open = true; } catch (e) { - // Since `find` swallows 404s, it is expected that exporter will do the same, + // Since `find` swallows 404s, it is expected that finder will do the same, // so we only rethrow non-404 errors here. - if (e.output.statusCode !== 404) { + if (e.output?.statusCode !== 404) { + this.#log.error(`Failed to open PIT for types [${this.#findOptions.type}]`); throw e; } this.#log.debug(`Unable to open PIT for types [${this.#findOptions.type}]: 404 ${e}`); @@ -164,7 +162,7 @@ export class PointInTimeFinder { searchAfter?: unknown[]; }) { try { - return await this.#savedObjectsClient.find({ + return await this.#client.find({ // Sort fields are required to use searchAfter, so we set some defaults here sortField: 'updated_at', sortOrder: 'desc', diff --git a/src/core/server/saved_objects/service/lib/repository.mock.ts b/src/core/server/saved_objects/service/lib/repository.mock.ts index a3610b1e437e24..a2092e05718085 100644 --- a/src/core/server/saved_objects/service/lib/repository.mock.ts +++ b/src/core/server/saved_objects/service/lib/repository.mock.ts @@ -6,26 +6,36 @@ * Side Public License, v 1. */ +import { savedObjectsPointInTimeFinderMock } from './point_in_time_finder.mock'; import { ISavedObjectsRepository } from './repository'; -const create = (): jest.Mocked => ({ - checkConflicts: jest.fn(), - create: jest.fn(), - bulkCreate: jest.fn(), - bulkUpdate: jest.fn(), - delete: jest.fn(), - bulkGet: jest.fn(), - find: jest.fn(), - get: jest.fn(), - closePointInTime: jest.fn(), - openPointInTimeForType: jest.fn().mockResolvedValue({ id: 'some_pit_id' }), - resolve: jest.fn(), - update: jest.fn(), - addToNamespaces: jest.fn(), - deleteFromNamespaces: jest.fn(), - deleteByNamespace: jest.fn(), - incrementCounter: jest.fn(), - removeReferencesTo: jest.fn(), -}); +const create = () => { + const mock: jest.Mocked = { + checkConflicts: jest.fn(), + create: jest.fn(), + bulkCreate: jest.fn(), + bulkUpdate: jest.fn(), + delete: jest.fn(), + bulkGet: jest.fn(), + find: jest.fn(), + get: jest.fn(), + closePointInTime: jest.fn(), + createPointInTimeFinder: jest.fn(), + openPointInTimeForType: jest.fn().mockResolvedValue({ id: 'some_pit_id' }), + resolve: jest.fn(), + update: jest.fn(), + addToNamespaces: jest.fn(), + deleteFromNamespaces: jest.fn(), + deleteByNamespace: jest.fn(), + incrementCounter: jest.fn(), + removeReferencesTo: jest.fn(), + }; + + mock.createPointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ + savedObjectsMock: mock, + }); + + return mock; +}; export const savedObjectsRepositoryMock = { create }; diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index d26d92e84925a7..bff23895fe4592 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -6,10 +6,14 @@ * Side Public License, v 1. */ +import { pointInTimeFinderMock } from './repository.test.mock'; + import { SavedObjectsRepository } from './repository'; import * as getSearchDslNS from './search_dsl/search_dsl'; import { SavedObjectsErrorHelpers } from './errors'; +import { PointInTimeFinder } from './point_in_time_finder'; import { ALL_NAMESPACES_STRING } from './utils'; +import { loggerMock } from '../../../logging/logger.mock'; import { SavedObjectsSerializer } from '../../serialization'; import { encodeHitVersion } from '../../version'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; @@ -39,6 +43,7 @@ describe('SavedObjectsRepository', () => { let client; let savedObjectsRepository; let migrator; + let logger; let serializer; const mockTimestamp = '2017-08-14T15:49:14.886Z'; @@ -238,11 +243,13 @@ describe('SavedObjectsRepository', () => { }; beforeEach(() => { + pointInTimeFinderMock.mockClear(); client = elasticsearchClientMock.createElasticsearchClient(); migrator = mockKibanaMigrator.create(); documentMigrator.prepareMigrations(); migrator.migrateDocument = jest.fn().mockImplementation(documentMigrator.migrate); migrator.runMigrations = async () => ({ status: 'skipped' }); + logger = loggerMock.create(); // create a mock serializer "shim" so we can track function calls, but use the real serializer's implementation serializer = { @@ -269,6 +276,7 @@ describe('SavedObjectsRepository', () => { typeRegistry: registry, serializer, allowedTypes, + logger, }); savedObjectsRepository._getCurrentTime = jest.fn(() => mockTimestamp); @@ -4632,4 +4640,31 @@ describe('SavedObjectsRepository', () => { }); }); }); + + describe('#createPointInTimeFinder', () => { + it('returns a new PointInTimeFinder instance', async () => { + const result = await savedObjectsRepository.createPointInTimeFinder({}, {}); + expect(result).toBeInstanceOf(PointInTimeFinder); + }); + + it('calls PointInTimeFinder with the provided options and dependencies', async () => { + const options = Symbol(); + const dependencies = { + client: { + find: Symbol(), + openPointInTimeForType: Symbol(), + closePointInTime: Symbol(), + }, + }; + + await savedObjectsRepository.createPointInTimeFinder(options, dependencies); + expect(pointInTimeFinderMock).toHaveBeenCalledWith( + options, + expect.objectContaining({ + ...dependencies, + logger, + }) + ); + }); + }); }); diff --git a/src/core/server/saved_objects/service/lib/repository.test.mock.ts b/src/core/server/saved_objects/service/lib/repository.test.mock.ts new file mode 100644 index 00000000000000..3eba77b4658198 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/repository.test.mock.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const pointInTimeFinderMock = jest.fn(); +jest.doMock('./point_in_time_finder', () => ({ + PointInTimeFinder: pointInTimeFinderMock, +})); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 7a54cdb8488d8c..a302cfe5a1e6f0 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -13,7 +13,14 @@ import { GetResponse, SearchResponse, } from '../../../elasticsearch/'; +import { Logger } from '../../../logging'; import { getRootPropertiesObjects, IndexMapping } from '../../mappings'; +import { + ISavedObjectsPointInTimeFinder, + PointInTimeFinder, + SavedObjectsCreatePointInTimeFinderOptions, + SavedObjectsCreatePointInTimeFinderDependencies, +} from './point_in_time_finder'; import { createRepositoryEsClient, RepositoryEsClient } from './repository_es_client'; import { getSearchDsl } from './search_dsl'; import { includedFields } from './included_fields'; @@ -89,6 +96,7 @@ export interface SavedObjectsRepositoryOptions { serializer: SavedObjectsSerializer; migrator: IKibanaMigrator; allowedTypes: string[]; + logger: Logger; } /** @@ -148,6 +156,7 @@ export class SavedObjectsRepository { private _allowedTypes: string[]; private readonly client: RepositoryEsClient; private _serializer: SavedObjectsSerializer; + private _logger: Logger; /** * A factory function for creating SavedObjectRepository instances. @@ -162,6 +171,7 @@ export class SavedObjectsRepository { typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, + logger: Logger, includedHiddenTypes: string[] = [], injectedConstructor: any = SavedObjectsRepository ): ISavedObjectsRepository { @@ -187,6 +197,7 @@ export class SavedObjectsRepository { serializer, allowedTypes, client, + logger, }); } @@ -199,6 +210,7 @@ export class SavedObjectsRepository { serializer, migrator, allowedTypes = [], + logger, } = options; // It's important that we migrate documents / mark them as up-to-date @@ -218,6 +230,7 @@ export class SavedObjectsRepository { } this._allowedTypes = allowedTypes; this._serializer = serializer; + this._logger = logger; } /** @@ -1788,6 +1801,9 @@ export class SavedObjectsRepository { * Opens a Point In Time (PIT) against the indices for the specified Saved Object types. * The returned `id` can then be passed to `SavedObjects.find` to search against that PIT. * + * Only use this API if you have an advanced use case that's not solved by the + * {@link SavedObjectsRepository.createPointInTimeFinder} method. + * * @example * ```ts * const { id } = await savedObjectsClient.openPointInTimeForType( @@ -1853,6 +1869,9 @@ export class SavedObjectsRepository { * via the Elasticsearch client, and is included in the Saved Objects Client * as a convenience for consumers who are using `openPointInTimeForType`. * + * Only use this API if you have an advanced use case that's not solved by the + * {@link SavedObjectsRepository.createPointInTimeFinder} method. + * * @remarks * While the `keepAlive` that is provided will cause a PIT to automatically close, * it is highly recommended to explicitly close a PIT when you are done with it @@ -1896,6 +1915,62 @@ export class SavedObjectsRepository { return body; } + /** + * Returns a {@link ISavedObjectsPointInTimeFinder} to help page through + * large sets of saved objects. We strongly recommend using this API for + * any `find` queries that might return more than 1000 saved objects, + * however this API is only intended for use in server-side "batch" + * processing of objects where you are collecting all objects in memory + * or streaming them back to the client. + * + * Do NOT use this API in a route handler to facilitate paging through + * saved objects on the client-side unless you are streaming all of the + * results back to the client at once. Because the returned generator is + * stateful, you cannot rely on subsequent http requests retrieving new + * pages from the same Kibana server in multi-instance deployments. + * + * This generator wraps calls to {@link SavedObjectsRepository.find} and + * iterates over multiple pages of results using `_pit` and `search_after`. + * This will open a new Point-In-Time (PIT), and continue paging until a + * set of results is received that's smaller than the designated `perPage`. + * + * Once you have retrieved all of the results you need, it is recommended + * to call `close()` to clean up the PIT and prevent Elasticsearch from + * consuming resources unnecessarily. This is only required if you are + * done iterating and have not yet paged through all of the results: the + * PIT will automatically be closed for you once you reach the last page + * of results, or if the underlying call to `find` fails for any reason. + * + * @example + * ```ts + * const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { + * type: 'visualization', + * search: 'foo*', + * perPage: 100, + * }; + * + * const finder = savedObjectsClient.createPointInTimeFinder(findOptions); + * + * const responses: SavedObjectFindResponse[] = []; + * for await (const response of finder.find()) { + * responses.push(...response); + * if (doneSearching) { + * await finder.close(); + * } + * } + * ``` + */ + createPointInTimeFinder( + findOptions: SavedObjectsCreatePointInTimeFinderOptions, + dependencies?: SavedObjectsCreatePointInTimeFinderDependencies + ): ISavedObjectsPointInTimeFinder { + return new PointInTimeFinder(findOptions, { + logger: this._logger, + client: this, + ...dependencies, + }); + } + /** * Returns index specified by the given type or the default index * diff --git a/src/core/server/saved_objects/service/lib/repository_create_repository.test.ts b/src/core/server/saved_objects/service/lib/repository_create_repository.test.ts index 26aa152c630adc..9d9a2eb14b495f 100644 --- a/src/core/server/saved_objects/service/lib/repository_create_repository.test.ts +++ b/src/core/server/saved_objects/service/lib/repository_create_repository.test.ts @@ -10,12 +10,14 @@ import { SavedObjectsRepository } from './repository'; import { mockKibanaMigrator } from '../../migrations/kibana/kibana_migrator.mock'; import { KibanaMigrator } from '../../migrations'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; +import { loggerMock, MockedLogger } from '../../../logging/logger.mock'; jest.mock('./repository'); const { SavedObjectsRepository: originalRepository } = jest.requireActual('./repository'); describe('SavedObjectsRepository#createRepository', () => { + let logger: MockedLogger; const callAdminCluster = jest.fn(); const typeRegistry = new SavedObjectTypeRegistry(); @@ -59,6 +61,7 @@ describe('SavedObjectsRepository#createRepository', () => { const RepositoryConstructor = (SavedObjectsRepository as unknown) as jest.Mock; beforeEach(() => { + logger = loggerMock.create(); RepositoryConstructor.mockClear(); }); @@ -69,6 +72,7 @@ describe('SavedObjectsRepository#createRepository', () => { typeRegistry, '.kibana-test', callAdminCluster, + logger, ['unMappedType1', 'unmappedType2'] ); } catch (e) { @@ -84,6 +88,7 @@ describe('SavedObjectsRepository#createRepository', () => { typeRegistry, '.kibana-test', callAdminCluster, + logger, [], SavedObjectsRepository ); @@ -102,6 +107,7 @@ describe('SavedObjectsRepository#createRepository', () => { typeRegistry, '.kibana-test', callAdminCluster, + logger, ['hiddenType', 'hiddenType', 'hiddenType'], SavedObjectsRepository ); diff --git a/src/core/server/saved_objects/service/saved_objects_client.mock.ts b/src/core/server/saved_objects/service/saved_objects_client.mock.ts index ecca652cace37f..544e92e32f1a17 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.mock.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.mock.ts @@ -8,9 +8,10 @@ import { SavedObjectsClientContract } from '../types'; import { SavedObjectsErrorHelpers } from './lib/errors'; +import { savedObjectsPointInTimeFinderMock } from './lib/point_in_time_finder.mock'; -const create = () => - (({ +const create = () => { + const mock = ({ errors: SavedObjectsErrorHelpers, create: jest.fn(), bulkCreate: jest.fn(), @@ -21,12 +22,20 @@ const create = () => find: jest.fn(), get: jest.fn(), closePointInTime: jest.fn(), + createPointInTimeFinder: jest.fn(), openPointInTimeForType: jest.fn().mockResolvedValue({ id: 'some_pit_id' }), resolve: jest.fn(), update: jest.fn(), addToNamespaces: jest.fn(), deleteFromNamespaces: jest.fn(), removeReferencesTo: jest.fn(), - } as unknown) as jest.Mocked); + } as unknown) as jest.Mocked; + + mock.createPointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ + savedObjectsMock: mock, + }); + + return mock; +}; export const savedObjectsClientMock = { create }; diff --git a/src/core/server/saved_objects/service/saved_objects_client.test.js b/src/core/server/saved_objects/service/saved_objects_client.test.js index 7cbddaf195dc93..29381c7e418b5f 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.test.js +++ b/src/core/server/saved_objects/service/saved_objects_client.test.js @@ -54,6 +54,45 @@ test(`#bulkCreate`, async () => { expect(result).toBe(returnValue); }); +describe(`#createPointInTimeFinder`, () => { + test(`calls repository with options and default dependencies`, () => { + const returnValue = Symbol(); + const mockRepository = { + createPointInTimeFinder: jest.fn().mockReturnValue(returnValue), + }; + const client = new SavedObjectsClient(mockRepository); + + const options = Symbol(); + const result = client.createPointInTimeFinder(options); + + expect(mockRepository.createPointInTimeFinder).toHaveBeenCalledWith(options, { + client, + }); + expect(result).toBe(returnValue); + }); + + test(`calls repository with options and custom dependencies`, () => { + const returnValue = Symbol(); + const mockRepository = { + createPointInTimeFinder: jest.fn().mockReturnValue(returnValue), + }; + const client = new SavedObjectsClient(mockRepository); + + const options = Symbol(); + const dependencies = { + client: { + find: Symbol(), + openPointInTimeForType: Symbol(), + closePointInTime: Symbol(), + }, + }; + const result = client.createPointInTimeFinder(options, dependencies); + + expect(mockRepository.createPointInTimeFinder).toHaveBeenCalledWith(options, dependencies); + expect(result).toBe(returnValue); + }); +}); + test(`#delete`, async () => { const returnValue = Symbol(); const mockRepository = { diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index b078f3eef018cd..9fa2896b7bbfe9 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -6,7 +6,12 @@ * Side Public License, v 1. */ -import { ISavedObjectsRepository } from './lib'; +import type { + ISavedObjectsRepository, + ISavedObjectsPointInTimeFinder, + SavedObjectsCreatePointInTimeFinderOptions, + SavedObjectsCreatePointInTimeFinderDependencies, +} from './lib'; import { SavedObject, SavedObjectError, @@ -587,6 +592,9 @@ export class SavedObjectsClient { * Opens a Point In Time (PIT) against the indices for the specified Saved Object types. * The returned `id` can then be passed to {@link SavedObjectsClient.find} to search * against that PIT. + * + * Only use this API if you have an advanced use case that's not solved by the + * {@link SavedObjectsClient.createPointInTimeFinder} method. */ async openPointInTimeForType( type: string | string[], @@ -599,8 +607,67 @@ export class SavedObjectsClient { * Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the * Elasticsearch client, and is included in the Saved Objects Client as a convenience * for consumers who are using {@link SavedObjectsClient.openPointInTimeForType}. + * + * Only use this API if you have an advanced use case that's not solved by the + * {@link SavedObjectsClient.createPointInTimeFinder} method. */ async closePointInTime(id: string, options?: SavedObjectsClosePointInTimeOptions) { return await this._repository.closePointInTime(id, options); } + + /** + * Returns a {@link ISavedObjectsPointInTimeFinder} to help page through + * large sets of saved objects. We strongly recommend using this API for + * any `find` queries that might return more than 1000 saved objects, + * however this API is only intended for use in server-side "batch" + * processing of objects where you are collecting all objects in memory + * or streaming them back to the client. + * + * Do NOT use this API in a route handler to facilitate paging through + * saved objects on the client-side unless you are streaming all of the + * results back to the client at once. Because the returned generator is + * stateful, you cannot rely on subsequent http requests retrieving new + * pages from the same Kibana server in multi-instance deployments. + * + * The generator wraps calls to {@link SavedObjectsClient.find} and iterates + * over multiple pages of results using `_pit` and `search_after`. This will + * open a new Point-In-Time (PIT), and continue paging until a set of + * results is received that's smaller than the designated `perPage`. + * + * Once you have retrieved all of the results you need, it is recommended + * to call `close()` to clean up the PIT and prevent Elasticsearch from + * consuming resources unnecessarily. This is only required if you are + * done iterating and have not yet paged through all of the results: the + * PIT will automatically be closed for you once you reach the last page + * of results, or if the underlying call to `find` fails for any reason. + * + * @example + * ```ts + * const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { + * type: 'visualization', + * search: 'foo*', + * perPage: 100, + * }; + * + * const finder = savedObjectsClient.createPointInTimeFinder(findOptions); + * + * const responses: SavedObjectFindResponse[] = []; + * for await (const response of finder.find()) { + * responses.push(...response); + * if (doneSearching) { + * await finder.close(); + * } + * } + * ``` + */ + createPointInTimeFinder( + findOptions: SavedObjectsCreatePointInTimeFinderOptions, + dependencies?: SavedObjectsCreatePointInTimeFinderDependencies + ): ISavedObjectsPointInTimeFinder { + return this._repository.createPointInTimeFinder(findOptions, { + client: this, + // Include dependencies last so that SO client wrappers have their settings applied. + ...dependencies, + }); + } } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 580315973ce8fb..3d2023108c46a0 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1177,6 +1177,12 @@ export type ISavedObjectsExporter = PublicMethodsOf; // @public (undocumented) export type ISavedObjectsImporter = PublicMethodsOf; +// @public (undocumented) +export interface ISavedObjectsPointInTimeFinder { + close: () => Promise; + find: () => AsyncGenerator; +} + // @public export type ISavedObjectsRepository = Pick; @@ -2219,6 +2225,7 @@ export class SavedObjectsClient { checkConflicts(objects?: SavedObjectsCheckConflictsObject[], options?: SavedObjectsBaseOptions): Promise; closePointInTime(id: string, options?: SavedObjectsClosePointInTimeOptions): Promise; create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; + createPointInTimeFinder(findOptions: SavedObjectsCreatePointInTimeFinderOptions, dependencies?: SavedObjectsCreatePointInTimeFinderDependencies): ISavedObjectsPointInTimeFinder; delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise; // (undocumented) @@ -2321,6 +2328,15 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { version?: string; } +// @public (undocumented) +export interface SavedObjectsCreatePointInTimeFinderDependencies { + // (undocumented) + client: Pick; +} + +// @public (undocumented) +export type SavedObjectsCreatePointInTimeFinderOptions = Omit; + // @public (undocumented) export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { refresh?: boolean; @@ -2811,10 +2827,11 @@ export class SavedObjectsRepository { checkConflicts(objects?: SavedObjectsCheckConflictsObject[], options?: SavedObjectsBaseOptions): Promise; closePointInTime(id: string, options?: SavedObjectsClosePointInTimeOptions): Promise; create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; + createPointInTimeFinder(findOptions: SavedObjectsCreatePointInTimeFinderOptions, dependencies?: SavedObjectsCreatePointInTimeFinderDependencies): ISavedObjectsPointInTimeFinder; // Warning: (ae-forgotten-export) The symbol "IKibanaMigrator" needs to be exported by the entry point index.d.ts // // @internal - static createRepository(migrator: IKibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository; + static createRepository(migrator: IKibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, logger: Logger, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository; delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index c1314ddbe6fa26..e04fcdfa08f363 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -1226,7 +1226,7 @@ export class Plugin implements Plugin_2 Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts index 76f5cb49c7f078..d18e7e427eeca7 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts @@ -1820,3 +1820,30 @@ describe('#closePointInTime', () => { expect(mockBaseClient.closePointInTime).toHaveBeenCalledTimes(1); }); }); + +describe('#createPointInTimeFinder', () => { + it('redirects request to underlying base client with default dependencies', () => { + const options = { type: ['a', 'b'], search: 'query' }; + wrapper.createPointInTimeFinder(options); + + expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); + expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, { + client: wrapper, + }); + }); + + it('redirects request to underlying base client with custom dependencies', () => { + const options = { type: ['a', 'b'], search: 'query' }; + const dependencies = { + client: { + find: jest.fn(), + openPointInTimeForType: jest.fn(), + closePointInTime: jest.fn(), + }, + }; + wrapper.createPointInTimeFinder(options, dependencies); + + expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); + expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, dependencies); + }); +}); diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts index 6b06f7e4e68e90..88a89af6be3d0e 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts @@ -19,6 +19,8 @@ import type { SavedObjectsClientContract, SavedObjectsClosePointInTimeOptions, SavedObjectsCreateOptions, + SavedObjectsCreatePointInTimeFinderDependencies, + SavedObjectsCreatePointInTimeFinderOptions, SavedObjectsDeleteFromNamespacesOptions, SavedObjectsFindOptions, SavedObjectsFindResponse, @@ -263,6 +265,17 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon return await this.options.baseClient.closePointInTime(id, options); } + public createPointInTimeFinder( + findOptions: SavedObjectsCreatePointInTimeFinderOptions, + dependencies?: SavedObjectsCreatePointInTimeFinderDependencies + ) { + return this.options.baseClient.createPointInTimeFinder(findOptions, { + client: this, + // Include dependencies last so that subsequent SO client wrappers have their settings applied. + ...dependencies, + }); + } + /** * Strips encrypted attributes from any non-bulk Saved Objects API response. If type isn't * registered, response is returned as is. diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index 803b36e520a2f6..554244dc98be9f 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -1058,6 +1058,36 @@ describe('#closePointInTime', () => { }); }); +describe('#createPointInTimeFinder', () => { + it('redirects request to underlying base client with default dependencies', () => { + const options = { type: ['a', 'b'], search: 'query' }; + client.createPointInTimeFinder(options); + + expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); + expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, { + client, + }); + }); + + it('redirects request to underlying base client with custom dependencies', () => { + const options = { type: ['a', 'b'], search: 'query' }; + const dependencies = { + client: { + find: jest.fn(), + openPointInTimeForType: jest.fn(), + closePointInTime: jest.fn(), + }, + }; + client.createPointInTimeFinder(options, dependencies); + + expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); + expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledWith( + options, + dependencies + ); + }); +}); + describe('#resolve', () => { const type = 'foo'; const id = `${type}-id`; diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index 1858bc7108dc98..8378cc4d848cf2 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -16,6 +16,8 @@ import type { SavedObjectsClientContract, SavedObjectsClosePointInTimeOptions, SavedObjectsCreateOptions, + SavedObjectsCreatePointInTimeFinderDependencies, + SavedObjectsCreatePointInTimeFinderOptions, SavedObjectsDeleteFromNamespacesOptions, SavedObjectsFindOptions, SavedObjectsOpenPointInTimeOptions, @@ -616,6 +618,20 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra return await this.baseClient.closePointInTime(id, options); } + public createPointInTimeFinder( + findOptions: SavedObjectsCreatePointInTimeFinderOptions, + dependencies?: SavedObjectsCreatePointInTimeFinderDependencies + ) { + // We don't need to perform an authorization check here or add an audit log, because + // `createPointInTimeFinder` is simply a helper that calls `find`, `openPointInTimeForType`, + // and `closePointInTime` internally, so authz checks and audit logs will already be applied. + return this.baseClient.createPointInTimeFinder(findOptions, { + client: this, + // Include dependencies last so that subsequent SO client wrappers have their settings applied. + ...dependencies, + }); + } + private async checkPrivileges( actions: string | string[], namespaceOrNamespaces?: string | Array diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts index fa53f110e30c3b..cbb71d4bbcf81f 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts @@ -643,5 +643,43 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; }); }); }); + + describe('#createPointInTimeFinder', () => { + test(`throws error if options.namespace is specified`, async () => { + const { client } = createSpacesSavedObjectsClient(); + + const options = { type: ['a', 'b'], search: 'query', namespace: 'oops' }; + expect(() => client.createPointInTimeFinder(options)).toThrow(ERROR_NAMESPACE_SPECIFIED); + }); + + it('redirects request to underlying base client with default dependencies', () => { + const { client, baseClient } = createSpacesSavedObjectsClient(); + + const options = { type: ['a', 'b'], search: 'query' }; + client.createPointInTimeFinder(options); + + expect(baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); + expect(baseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, { + client, + }); + }); + + it('redirects request to underlying base client with custom dependencies', () => { + const { client, baseClient } = createSpacesSavedObjectsClient(); + + const options = { type: ['a', 'b'], search: 'query' }; + const dependencies = { + client: { + find: jest.fn(), + openPointInTimeForType: jest.fn(), + closePointInTime: jest.fn(), + }, + }; + client.createPointInTimeFinder(options, dependencies); + + expect(baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); + expect(baseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, dependencies); + }); + }); }); }); diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts index f70714b8ad1028..c544e2f46f058b 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts @@ -18,6 +18,8 @@ import type { SavedObjectsClientContract, SavedObjectsClosePointInTimeOptions, SavedObjectsCreateOptions, + SavedObjectsCreatePointInTimeFinderDependencies, + SavedObjectsCreatePointInTimeFinderOptions, SavedObjectsDeleteFromNamespacesOptions, SavedObjectsFindOptions, SavedObjectsOpenPointInTimeOptions, @@ -420,4 +422,31 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { namespace: spaceIdToNamespace(this.spaceId), }); } + + /** + * Returns a generator to help page through large sets of saved objects. + * + * The generator wraps calls to `SavedObjects.find` and iterates over + * multiple pages of results using `_pit` and `search_after`. This will + * open a new Point In Time (PIT), and continue paging until a set of + * results is received that's smaller than the designated `perPage`. + * + * @param {object} findOptions - {@link SavedObjectsCreatePointInTimeFinderOptions} + * @param {object} [dependencies] - {@link SavedObjectsCreatePointInTimeFinderDependencies} + */ + createPointInTimeFinder( + findOptions: SavedObjectsCreatePointInTimeFinderOptions, + dependencies?: SavedObjectsCreatePointInTimeFinderDependencies + ) { + throwErrorIfNamespaceSpecified(findOptions); + // We don't need to handle namespaces here, because `createPointInTimeFinder` + // is simply a helper that calls `find`, `openPointInTimeForType`, and + // `closePointInTime` internally, so namespaces will already be handled + // in those methods. + return this.client.createPointInTimeFinder(findOptions, { + client: this, + // Include dependencies last so that subsequent SO client wrappers have their settings applied. + ...dependencies, + }); + } } From a7ac120b697d1f00d0a953dc3b3c65fdc5c27bba Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 25 Mar 2021 03:54:39 +0000 Subject: [PATCH 82/93] fix(NA): support inspect flags on ensure_node_preserve_symlinks script (#95344) * fix(NA): support inspect flags on ensure_node_preserve_symlinks script * chore(NA): fix wording on function test runner schema file * chore(NA): update execargv array in case of --inspect port --- .../lib/config/schema.ts | 4 +- .../functional_tests/lib/run_kibana_server.js | 6 +-- .../ensure_node_preserve_symlinks.js | 43 ++++++++++++++++++- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index 0694bc4ffdb0fd..d82b7b83e8f151 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -13,8 +13,8 @@ import Joi from 'joi'; // valid pattern for ID // enforced camel-case identifiers for consistency const ID_PATTERN = /^[a-zA-Z0-9_]+$/; -const INSPECTING = - process.execArgv.includes('--inspect') || process.execArgv.includes('--inspect-brk'); +// it will search both --inspect and --inspect-brk +const INSPECTING = !!process.execArgv.find((arg) => arg.includes('--inspect')); const urlPartsSchema = () => Joi.object() diff --git a/packages/kbn-test/src/functional_tests/lib/run_kibana_server.js b/packages/kbn-test/src/functional_tests/lib/run_kibana_server.js index 4abbc3d29fe7c4..a43d3a09c7d70b 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_kibana_server.js +++ b/packages/kbn-test/src/functional_tests/lib/run_kibana_server.js @@ -62,15 +62,11 @@ function collectCliArgs(config, { installDir, extraKbnOpts }) { const buildArgs = config.get('kbnTestServer.buildArgs') || []; const sourceArgs = config.get('kbnTestServer.sourceArgs') || []; const serverArgs = config.get('kbnTestServer.serverArgs') || []; - const execArgv = process.execArgv || []; return pipe( serverArgs, (args) => (installDir ? args.filter((a) => a !== '--oss') : args), - (args) => - installDir - ? [...buildArgs, ...args] - : [...execArgv, KIBANA_EXEC_PATH, ...sourceArgs, ...args], + (args) => (installDir ? [...buildArgs, ...args] : [KIBANA_EXEC_PATH, ...sourceArgs, ...args]), (args) => args.concat(extraKbnOpts || []) ); } diff --git a/src/setup_node_env/ensure_node_preserve_symlinks.js b/src/setup_node_env/ensure_node_preserve_symlinks.js index 0d72ec85e6c872..826244c4829fc9 100644 --- a/src/setup_node_env/ensure_node_preserve_symlinks.js +++ b/src/setup_node_env/ensure_node_preserve_symlinks.js @@ -9,10 +9,51 @@ (function () { var cp = require('child_process'); + var calculateInspectPortOnExecArgv = function (processExecArgv) { + var execArgv = [].concat(processExecArgv); + + if (execArgv.length === 0) { + return execArgv; + } + + var inspectFlagIndex = execArgv.reverse().findIndex(function (flag) { + return flag.startsWith('--inspect'); + }); + + if (inspectFlagIndex !== -1) { + var inspectFlag; + var inspectPortCounter = 9230; + var argv = execArgv[inspectFlagIndex]; + + if (argv.includes('=')) { + // --inspect=port + var argvSplit = argv.split('='); + var flag = argvSplit[0]; + var port = argvSplit[1]; + inspectFlag = flag; + inspectPortCounter = Number.parseInt(port, 10) + 1; + } else { + // --inspect + inspectFlag = argv; + + // is number? + if (String(execArgv[inspectFlagIndex + 1]).match(/^[0-9]+$/)) { + // --inspect port + inspectPortCounter = Number.parseInt(execArgv[inspectFlagIndex + 1], 10) + 1; + execArgv.slice(inspectFlagIndex + 1, 1); + } + } + + execArgv[inspectFlagIndex] = inspectFlag + '=' + inspectPortCounter; + } + + return execArgv; + }; + var preserveSymlinksOption = '--preserve-symlinks'; var preserveSymlinksMainOption = '--preserve-symlinks-main'; var nodeOptions = (process && process.env && process.env.NODE_OPTIONS) || []; - var nodeExecArgv = (process && process.execArgv) || []; + var nodeExecArgv = calculateInspectPortOnExecArgv((process && process.execArgv) || []); var isPreserveSymlinksPresent = nodeOptions.includes(preserveSymlinksOption) || nodeExecArgv.includes(preserveSymlinksOption); From 070ca4bb103171db8aefc0cd36631a67eef1db97 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 25 Mar 2021 04:06:44 +0000 Subject: [PATCH 83/93] skip flaky suite (#89318 , #89319) --- src/core/server/http/cookie_session_storage.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index f00cbb928d6317..c8021638664234 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -124,7 +124,9 @@ const cookieOptions = { path, }; -describe('Cookie based SessionStorage', () => { +// FLAKY: https://github.com/elastic/kibana/issues/89318 +// https://github.com/elastic/kibana/issues/89319 +describe.skip('Cookie based SessionStorage', () => { describe('#set()', () => { it('Should write to session storage & set cookies', async () => { const { server: innerServer, createRouter } = await server.setup(setupDeps); From 6b5023a681673a0930b2dd34c203bbca3cb0beac Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 25 Mar 2021 07:34:01 +0100 Subject: [PATCH 84/93] [Discover] Deangularize controller part 1 (#93896) * Refactor minimumVisibleRows * Extract setupVisualization function * Extract getDimensions function * Inline breadcrumb and help menu function exec to discover.tsx * Extract getStateDefault * Remove unnecessary code * Improve performance by React.memo --- .../__mocks__/index_pattern_with_timefield.ts | 8 +- .../discover/public/__mocks__/ui_settings.ts | 4 +- .../public/application/angular/discover.js | 395 ++++-------------- .../application/angular/discover_legacy.html | 3 - .../doc_table/create_doc_table_react.tsx | 36 +- .../angular/doc_table/lib/get_sort.ts | 4 +- .../application/components/constants.ts | 14 + .../components/create_discover_directive.ts | 3 - .../application/components/discover.tsx | 35 +- .../discover_grid_columns.test.tsx | 27 +- .../{help_menu_util.js => help_menu_util.ts} | 5 +- .../apply_aggs_to_search_source.test.ts | 88 ++++ .../histogram/apply_aggs_to_search_source.ts | 50 +++ .../histogram/get_dimensions.test.ts | 63 +++ .../components/histogram/get_dimensions.ts | 52 +++ .../application/components/histogram/index.ts | 10 + .../timechart_header.test.tsx | 17 +- .../timechart_header/timechart_header.tsx | 26 +- .../public/application/components/types.ts | 4 - .../helpers/get_result_state.test.ts | 45 ++ .../application/helpers/get_result_state.ts | 31 ++ .../helpers/get_state_defaults.test.ts | 65 +++ .../application/helpers/get_state_defaults.ts | 62 +++ test/functional/apps/discover/_discover.ts | 20 +- test/functional/apps/discover/_doc_table.ts | 2 + test/functional/page_objects/discover_page.ts | 9 + 26 files changed, 704 insertions(+), 374 deletions(-) create mode 100644 src/plugins/discover/public/application/components/constants.ts rename src/plugins/discover/public/application/components/help_menu/{help_menu_util.js => help_menu_util.ts} (81%) create mode 100644 src/plugins/discover/public/application/components/histogram/apply_aggs_to_search_source.test.ts create mode 100644 src/plugins/discover/public/application/components/histogram/apply_aggs_to_search_source.ts create mode 100644 src/plugins/discover/public/application/components/histogram/get_dimensions.test.ts create mode 100644 src/plugins/discover/public/application/components/histogram/get_dimensions.ts create mode 100644 src/plugins/discover/public/application/components/histogram/index.ts create mode 100644 src/plugins/discover/public/application/helpers/get_result_state.test.ts create mode 100644 src/plugins/discover/public/application/helpers/get_result_state.ts create mode 100644 src/plugins/discover/public/application/helpers/get_state_defaults.test.ts create mode 100644 src/plugins/discover/public/application/helpers/get_state_defaults.ts diff --git a/src/plugins/discover/public/__mocks__/index_pattern_with_timefield.ts b/src/plugins/discover/public/__mocks__/index_pattern_with_timefield.ts index b53e5328f21ba7..ad84518af9de3a 100644 --- a/src/plugins/discover/public/__mocks__/index_pattern_with_timefield.ts +++ b/src/plugins/discover/public/__mocks__/index_pattern_with_timefield.ts @@ -22,6 +22,8 @@ const fields = [ type: 'date', scripted: false, filterable: true, + aggregatable: true, + sortable: true, }, { name: 'message', @@ -34,12 +36,14 @@ const fields = [ type: 'string', scripted: false, filterable: true, + aggregatable: true, }, { name: 'bytes', type: 'number', scripted: false, filterable: true, + aggregatable: true, }, { name: 'scripted', @@ -55,14 +59,14 @@ fields.getByName = (name: string) => { const indexPattern = ({ id: 'index-pattern-with-timefield-id', - title: 'index-pattern-without-timefield', + title: 'index-pattern-with-timefield', metaFields: ['_index', '_score'], flattenHit: undefined, formatHit: jest.fn((hit) => hit._source), fields, getComputedFields: () => ({}), getSourceFiltering: () => ({}), - getFieldByName: () => ({}), + getFieldByName: (name: string) => fields.getByName(name), timeFieldName: 'timestamp', } as unknown) as IndexPattern; diff --git a/src/plugins/discover/public/__mocks__/ui_settings.ts b/src/plugins/discover/public/__mocks__/ui_settings.ts index 8bc6de1b9ca41a..e021a39a568e99 100644 --- a/src/plugins/discover/public/__mocks__/ui_settings.ts +++ b/src/plugins/discover/public/__mocks__/ui_settings.ts @@ -7,12 +7,14 @@ */ import { IUiSettingsClient } from 'kibana/public'; -import { SAMPLE_SIZE_SETTING } from '../../common'; +import { DEFAULT_COLUMNS_SETTING, SAMPLE_SIZE_SETTING } from '../../common'; export const uiSettingsMock = ({ get: (key: string) => { if (key === SAMPLE_SIZE_SETTING) { return 10; + } else if (key === DEFAULT_COLUMNS_SETTING) { + return ['default_column']; } }, } as unknown) as IUiSettingsClient; diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 4a761f2fefa653..2c80fc111c740f 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -9,8 +9,6 @@ import _ from 'lodash'; import { merge, Subject, Subscription } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; -import moment from 'moment'; -import dateMath from '@elastic/datemath'; import { i18n } from '@kbn/i18n'; import { createSearchSessionRestorationDataProvider, getState, splitState } from './discover_state'; import { RequestAdapter } from '../../../../inspector/public'; @@ -23,7 +21,6 @@ import { } from '../../../../data/public'; import { getSortArray } from './doc_table'; import indexTemplateLegacy from './discover_legacy.html'; -import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util'; import { discoverResponseHandler } from './response_handler'; import { getAngularModule, @@ -36,25 +33,22 @@ import { subscribeWithScope, tabifyAggResponse, } from '../../kibana_services'; -import { - getRootBreadcrumbs, - getSavedSearchBreadcrumbs, - setBreadcrumbsTitle, -} from '../helpers/breadcrumbs'; +import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../helpers/breadcrumbs'; +import { getStateDefaults } from '../helpers/get_state_defaults'; +import { getResultState } from '../helpers/get_result_state'; import { validateTimeRange } from '../helpers/validate_time_range'; import { addFatalError } from '../../../../kibana_legacy/public'; import { - DEFAULT_COLUMNS_SETTING, SAMPLE_SIZE_SETTING, SEARCH_FIELDS_FROM_SOURCE, SEARCH_ON_PAGE_LOAD_SETTING, - SORT_DEFAULT_ORDER_SETTING, } from '../../../common'; import { loadIndexPattern, resolveIndexPattern } from '../helpers/resolve_index_pattern'; import { updateSearchSource } from '../helpers/update_search_source'; import { calcFieldCounts } from '../helpers/calc_field_counts'; -import { getDefaultSort } from './doc_table/lib/get_default_sort'; import { DiscoverSearchSessionManager } from './discover_search_session'; +import { applyAggsToSearchSource, getDimensions } from '../components/histogram'; +import { fetchStatuses } from '../components/constants'; const services = getServices(); @@ -70,13 +64,6 @@ const { uiSettings: config, } = getServices(); -const fetchStatuses = { - UNINITIALIZED: 'uninitialized', - LOADING: 'loading', - COMPLETE: 'complete', - ERROR: 'error', -}; - const app = getAngularModule(); app.config(($routeProvider) => { @@ -161,7 +148,7 @@ app.directive('discoverApp', function () { }; }); -function discoverController($route, $scope, Promise) { +function discoverController($route, $scope) { const { isDefault: isDefaultType } = indexPatternsUtils; const subscriptions = new Subscription(); const refetch$ = new Subject(); @@ -191,7 +178,14 @@ function discoverController($route, $scope, Promise) { }); const stateContainer = getState({ - getStateDefaults, + getStateDefaults: () => + getStateDefaults({ + config, + data, + indexPattern: $scope.indexPattern, + savedSearch, + searchSource: persistentSearchSource, + }), storeInSessionStorage: config.get('state:storeInSessionStorage'), history, toasts: core.notifications.toasts, @@ -232,6 +226,21 @@ function discoverController($route, $scope, Promise) { query: true, } ); + const showUnmappedFields = $scope.useNewFieldsApi; + const updateSearchSourceHelper = () => { + const { indexPattern, useNewFieldsApi } = $scope; + const { columns, sort } = $scope.state; + updateSearchSource({ + persistentSearchSource, + volatileSearchSource: $scope.volatileSearchSource, + indexPattern, + services, + sort, + columns, + useNewFieldsApi, + showUnmappedFields, + }); + }; const appStateUnsubscribe = appStateContainer.subscribe(async (newState) => { const { state: newStatePartial } = splitState(newState); @@ -293,21 +302,6 @@ function discoverController($route, $scope, Promise) { } ); - // update data source when filters update - subscriptions.add( - subscribeWithScope( - $scope, - filterManager.getUpdates$(), - { - next: () => { - $scope.state.filters = filterManager.getAppFilters(); - $scope.updateDataSource(); - }, - }, - (error) => addFatalError(core.fatalErrors, error) - ) - ); - $scope.opts = { // number of records to fetch, then paginate through sampleSize: config.get(SAMPLE_SIZE_SETTING), @@ -329,8 +323,19 @@ function discoverController($route, $scope, Promise) { requests: new RequestAdapter(), }); - $scope.minimumVisibleRows = 50; + const shouldSearchOnPageLoad = () => { + // A saved search is created on every page load, so we check the ID to see if we're loading a + // previously saved search or if it is just transient + return ( + config.get(SEARCH_ON_PAGE_LOAD_SETTING) || + savedSearch.id !== undefined || + timefilter.getRefreshInterval().pause === false || + searchSessionManager.hasSearchSessionIdInURL() + ); + }; + $scope.fetchStatus = fetchStatuses.UNINITIALIZED; + $scope.resultState = shouldSearchOnPageLoad() ? 'loading' : 'uninitialized'; let abortController; $scope.$on('$destroy', () => { @@ -385,157 +390,12 @@ function discoverController($route, $scope, Promise) { volatileSearchSource.setParent(persistentSearchSource); $scope.volatileSearchSource = volatileSearchSource; - - const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; - chrome.docTitle.change(`Discover${pageTitleSuffix}`); - - setBreadcrumbsTitle(savedSearch, chrome); - - function getDefaultColumns() { - if (savedSearch.columns.length > 0) { - return [...savedSearch.columns]; - } - return [...config.get(DEFAULT_COLUMNS_SETTING)]; - } - - function getStateDefaults() { - const query = - persistentSearchSource.getField('query') || data.query.queryString.getDefaultQuery(); - const sort = getSortArray(savedSearch.sort, $scope.indexPattern); - const columns = getDefaultColumns(); - - const defaultState = { - query, - sort: !sort.length - ? getDefaultSort($scope.indexPattern, config.get(SORT_DEFAULT_ORDER_SETTING, 'desc')) - : sort, - columns, - index: $scope.indexPattern.id, - interval: 'auto', - filters: _.cloneDeep(persistentSearchSource.getOwnField('filter')), - }; - if (savedSearch.grid) { - defaultState.grid = savedSearch.grid; - } - if (savedSearch.hideChart) { - defaultState.hideChart = savedSearch.hideChart; - } - - return defaultState; - } - $scope.state.index = $scope.indexPattern.id; $scope.state.sort = getSortArray($scope.state.sort, $scope.indexPattern); - const shouldSearchOnPageLoad = () => { - // A saved search is created on every page load, so we check the ID to see if we're loading a - // previously saved search or if it is just transient - return ( - config.get(SEARCH_ON_PAGE_LOAD_SETTING) || - savedSearch.id !== undefined || - timefilter.getRefreshInterval().pause === false || - searchSessionManager.hasSearchSessionIdInURL() - ); - }; - - const init = _.once(() => { - $scope.updateDataSource().then(async () => { - const fetch$ = merge( - refetch$, - filterManager.getFetches$(), - timefilter.getFetch$(), - timefilter.getAutoRefreshFetch$(), - data.query.queryString.getUpdates$(), - searchSessionManager.newSearchSessionIdFromURL$ - ).pipe(debounceTime(100)); - - subscriptions.add( - subscribeWithScope( - $scope, - fetch$, - { - next: $scope.fetch, - }, - (error) => addFatalError(core.fatalErrors, error) - ) - ); - subscriptions.add( - subscribeWithScope( - $scope, - timefilter.getTimeUpdate$(), - { - next: () => { - $scope.updateTime(); - }, - }, - (error) => addFatalError(core.fatalErrors, error) - ) - ); - - $scope.$watchMulti( - ['rows', 'fetchStatus'], - (function updateResultState() { - let prev = {}; - const status = { - UNINITIALIZED: 'uninitialized', - LOADING: 'loading', // initial data load - READY: 'ready', // results came back - NO_RESULTS: 'none', // no results came back - }; - - function pick(rows, oldRows, fetchStatus) { - // initial state, pretend we're already loading if we're about to execute a search so - // that the uninitilized message doesn't flash on screen - if (!$scope.fetchError && rows == null && oldRows == null && shouldSearchOnPageLoad()) { - return status.LOADING; - } - - if (fetchStatus === fetchStatuses.UNINITIALIZED) { - return status.UNINITIALIZED; - } - - const rowsEmpty = _.isEmpty(rows); - if (rowsEmpty && fetchStatus === fetchStatuses.LOADING) return status.LOADING; - else if (!rowsEmpty) return status.READY; - else return status.NO_RESULTS; - } - - return function () { - const current = { - rows: $scope.rows, - fetchStatus: $scope.fetchStatus, - }; - - $scope.resultState = pick( - current.rows, - prev.rows, - current.fetchStatus, - prev.fetchStatus - ); - - prev = current; - }; - })() - ); - - if (getTimeField()) { - setupVisualization(); - $scope.updateTime(); - } - - init.complete = true; - if (shouldSearchOnPageLoad()) { - refetch$.next(); - } - }); - }); - $scope.opts.fetch = $scope.fetch = function () { - // ignore requests to fetch before the app inits - if (!init.complete) return; $scope.fetchCounter++; $scope.fetchError = undefined; - $scope.minimumVisibleRows = 50; if (!validateTimeRange(timefilter.getTime(), toastNotifications)) { $scope.resultState = 'none'; return; @@ -546,17 +406,23 @@ function discoverController($route, $scope, Promise) { abortController = new AbortController(); const searchSessionId = searchSessionManager.getNextSearchSessionId(); + updateSearchSourceHelper(); - $scope - .updateDataSource() - .then(setupVisualization) - .then(function () { - $scope.fetchStatus = fetchStatuses.LOADING; - logInspectorRequest({ searchSessionId }); - return $scope.volatileSearchSource.fetch({ - abortSignal: abortController.signal, - sessionId: searchSessionId, - }); + $scope.opts.chartAggConfigs = applyAggsToSearchSource( + getTimeField() && !$scope.state.hideChart, + volatileSearchSource, + $scope.state.interval, + $scope.indexPattern, + data + ); + + $scope.fetchStatus = fetchStatuses.LOADING; + $scope.resultState = getResultState($scope.fetchStatus, $scope.rows); + logInspectorRequest({ searchSessionId }); + return $scope.volatileSearchSource + .fetch({ + abortSignal: abortController.signal, + sessionId: searchSessionId, }) .then(onResults) .catch((error) => { @@ -565,40 +431,14 @@ function discoverController($route, $scope, Promise) { $scope.fetchStatus = fetchStatuses.NO_RESULTS; $scope.fetchError = error; - data.search.showError(error); + }) + .finally(() => { + $scope.resultState = getResultState($scope.fetchStatus, $scope.rows); + $scope.$apply(); }); }; - function getDimensions(aggs, timeRange) { - const [metric, agg] = aggs; - agg.params.timeRange = timeRange; - const bounds = agg.params.timeRange ? timefilter.calculateBounds(agg.params.timeRange) : null; - agg.buckets.setBounds(bounds); - - const { esUnit, esValue } = agg.buckets.getInterval(); - return { - x: { - accessor: 0, - label: agg.makeLabel(), - format: agg.toSerializedFieldFormat(), - params: { - date: true, - interval: moment.duration(esValue, esUnit), - intervalESValue: esValue, - intervalESUnit: esUnit, - format: agg.buckets.getScaledDateFormat(), - bounds: agg.buckets.getBounds(), - }, - }, - y: { - accessor: 1, - format: metric.toSerializedFieldFormat(), - label: metric.makeLabel(), - }, - }; - } - function onResults(resp) { inspectorRequest .stats(getResponseInspectorStats(resp, $scope.volatileSearchSource)) @@ -607,11 +447,10 @@ function discoverController($route, $scope, Promise) { if (getTimeField() && !$scope.state.hideChart) { const tabifiedData = tabifyAggResponse($scope.opts.chartAggConfigs, resp); $scope.volatileSearchSource.rawResponse = resp; - $scope.histogramData = discoverResponseHandler( - tabifiedData, - getDimensions($scope.opts.chartAggConfigs.aggs, $scope.timeRange) - ); - $scope.updateTime(); + const dimensions = getDimensions($scope.opts.chartAggConfigs, data); + if (dimensions) { + $scope.histogramData = discoverResponseHandler(tabifiedData, dimensions); + } } $scope.hits = resp.hits.total; @@ -640,15 +479,6 @@ function discoverController($route, $scope, Promise) { }); } - $scope.updateTime = function () { - const { from, to } = timefilter.getTime(); - // this is the timerange for the histogram, should be refactored - $scope.timeRange = { - from: dateMath.parse(from), - to: dateMath.parse(to, { roundUp: true }), - }; - }; - $scope.resetQuery = function () { history.push( $route.current.params.id ? `/view/${encodeURIComponent($route.current.params.id)}` : '/' @@ -656,88 +486,39 @@ function discoverController($route, $scope, Promise) { $route.reload(); }; - $scope.onSkipBottomButtonClick = async () => { - // show all the Rows - $scope.minimumVisibleRows = $scope.hits; - - // delay scrolling to after the rows have been rendered - const bottomMarker = document.getElementById('discoverBottomMarker'); - const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - - while ($scope.rows.length !== document.getElementsByClassName('kbnDocTable__row').length) { - await wait(50); - } - bottomMarker.focus(); - await wait(50); - bottomMarker.blur(); - }; - $scope.newQuery = function () { history.push('/'); }; - const showUnmappedFields = $scope.useNewFieldsApi; - $scope.unmappedFieldsConfig = { showUnmappedFields, }; - $scope.updateDataSource = () => { - const { indexPattern, useNewFieldsApi } = $scope; - const { columns, sort } = $scope.state; - updateSearchSource({ - persistentSearchSource, - volatileSearchSource: $scope.volatileSearchSource, - indexPattern, - services, - sort, - columns, - useNewFieldsApi, - showUnmappedFields, - }); - return Promise.resolve(); - }; - - async function setupVisualization() { - // If no timefield has been specified we don't create a histogram of messages - if (!getTimeField() || $scope.state.hideChart) { - if ($scope.volatileSearchSource.getField('aggs')) { - // cleanup aggs field in case it was set before - $scope.volatileSearchSource.removeField('aggs'); - } - return; - } - const { interval: histogramInterval } = $scope.state; + const fetch$ = merge( + refetch$, + filterManager.getFetches$(), + timefilter.getFetch$(), + timefilter.getAutoRefreshFetch$(), + data.query.queryString.getUpdates$(), + searchSessionManager.newSearchSessionIdFromURL$ + ).pipe(debounceTime(100)); - const visStateAggs = [ - { - type: 'count', - schema: 'metric', - }, + subscriptions.add( + subscribeWithScope( + $scope, + fetch$, { - type: 'date_histogram', - schema: 'segment', - params: { - field: getTimeField(), - interval: histogramInterval, - timeRange: timefilter.getTime(), - }, + next: $scope.fetch, }, - ]; - $scope.opts.chartAggConfigs = data.search.aggs.createAggConfigs( - $scope.indexPattern, - visStateAggs - ); - - $scope.volatileSearchSource.setField('aggs', function () { - if (!$scope.opts.chartAggConfigs) return; - return $scope.opts.chartAggConfigs.toDsl(); - }); - } - - addHelpMenuToAppChrome(chrome); + (error) => addFatalError(core.fatalErrors, error) + ) + ); - init(); - // Propagate current app state to url, then start syncing - replaceUrlAppState().then(() => startStateSync()); + // Propagate current app state to url, then start syncing and fetching + replaceUrlAppState().then(() => { + startStateSync(); + if (shouldSearchOnPageLoad()) { + refetch$.next(); + } + }); } diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index a01f285b1a1506..f14800f81d08ea 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -7,15 +7,12 @@ histogram-data="histogramData" hits="hits" index-pattern="indexPattern" - minimum-visible-rows="minimumVisibleRows" - on-skip-bottom-button-click="onSkipBottomButtonClick" opts="opts" reset-query="resetQuery" result-state="resultState" rows="rows" search-source="volatileSearchSource" state="state" - time-range="timeRange" top-nav-menu="topNavMenu" use-new-fields-api="useNewFieldsApi" unmapped-fields-config="unmappedFieldsConfig" diff --git a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx b/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx index e768b750aa134c..ba4d56b9355123 100644 --- a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx @@ -8,11 +8,12 @@ import angular, { auto, ICompileService, IScope } from 'angular'; import { render } from 'react-dom'; -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useState, useCallback } from 'react'; import { EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { getServices, IIndexPattern } from '../../../kibana_services'; import { IndexPatternField } from '../../../../../data/common/index_patterns'; +import { SkipBottomButton } from '../../components/skip_bottom_button'; export interface DocTableLegacyProps { columns: string[]; @@ -97,18 +98,42 @@ function getRenderFn(domNode: Element, props: any) { export function DocTableLegacy(renderProps: DocTableLegacyProps) { const ref = useRef(null); const scope = useRef(); + const [rows, setRows] = useState(renderProps.rows); + const [minimumVisibleRows, setMinimumVisibleRows] = useState(50); + const onSkipBottomButtonClick = useCallback(async () => { + // delay scrolling to after the rows have been rendered + const bottomMarker = document.getElementById('discoverBottomMarker'); + const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + // show all the rows + setMinimumVisibleRows(renderProps.rows.length); + + while (renderProps.rows.length !== document.getElementsByClassName('kbnDocTable__row').length) { + await wait(50); + } + bottomMarker!.focus(); + await wait(50); + bottomMarker!.blur(); + }, [setMinimumVisibleRows, renderProps.rows]); + + useEffect(() => { + if (minimumVisibleRows > 50) { + setMinimumVisibleRows(50); + } + setRows(renderProps.rows); + }, [renderProps.rows, minimumVisibleRows, setMinimumVisibleRows]); useEffect(() => { if (ref && ref.current && !scope.current) { - const fn = getRenderFn(ref.current, renderProps); + const fn = getRenderFn(ref.current, { ...renderProps, rows, minimumVisibleRows }); fn().then((newScope) => { scope.current = newScope; }); } else if (scope && scope.current) { - scope.current.renderProps = renderProps; + scope.current.renderProps = { ...renderProps, rows, minimumVisibleRows }; scope.current.$apply(); } - }, [renderProps]); + }, [renderProps, minimumVisibleRows, rows]); + useEffect(() => { return () => { if (scope.current) { @@ -118,6 +143,7 @@ export function DocTableLegacy(renderProps: DocTableLegacyProps) { }, []); return (
+
{renderProps.rows.length === renderProps.sampleSize ? (
- +
diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.ts b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.ts index 4838a4019357c0..4b16c1aa3dcc64 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.ts +++ b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.ts @@ -14,9 +14,9 @@ export type SortPairArr = [string, string]; export type SortPair = SortPairArr | SortPairObj; export type SortInput = SortPair | SortPair[]; -export function isSortable(fieldName: string, indexPattern: IndexPattern) { +export function isSortable(fieldName: string, indexPattern: IndexPattern): boolean { const field = indexPattern.getFieldByName(fieldName); - return field && field.sortable; + return !!(field && field.sortable); } function createSortObject( diff --git a/src/plugins/discover/public/application/components/constants.ts b/src/plugins/discover/public/application/components/constants.ts new file mode 100644 index 00000000000000..42845e83b7435b --- /dev/null +++ b/src/plugins/discover/public/application/components/constants.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const fetchStatuses = { + UNINITIALIZED: 'uninitialized', + LOADING: 'loading', + COMPLETE: 'complete', + ERROR: 'error', +}; diff --git a/src/plugins/discover/public/application/components/create_discover_directive.ts b/src/plugins/discover/public/application/components/create_discover_directive.ts index 8d1360aeaddada..5abf87fdfbc084 100644 --- a/src/plugins/discover/public/application/components/create_discover_directive.ts +++ b/src/plugins/discover/public/application/components/create_discover_directive.ts @@ -16,8 +16,6 @@ export function createDiscoverDirective(reactDirective: any) { ['histogramData', { watchDepth: 'reference' }], ['hits', { watchDepth: 'reference' }], ['indexPattern', { watchDepth: 'reference' }], - ['minimumVisibleRows', { watchDepth: 'reference' }], - ['onSkipBottomButtonClick', { watchDepth: 'reference' }], ['opts', { watchDepth: 'reference' }], ['resetQuery', { watchDepth: 'reference' }], ['resultState', { watchDepth: 'reference' }], @@ -26,7 +24,6 @@ export function createDiscoverDirective(reactDirective: any) { ['searchSource', { watchDepth: 'reference' }], ['showSaveQuery', { watchDepth: 'reference' }], ['state', { watchDepth: 'reference' }], - ['timeRange', { watchDepth: 'reference' }], ['topNavMenu', { watchDepth: 'reference' }], ['updateQuery', { watchDepth: 'reference' }], ['updateSavedQueryId', { watchDepth: 'reference' }], diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx index 056581e30b4d6f..9615a1c10ea8e9 100644 --- a/src/plugins/discover/public/application/components/discover.tsx +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import './discover.scss'; -import React, { useState, useRef, useMemo, useCallback } from 'react'; +import React, { useState, useRef, useMemo, useCallback, useEffect } from 'react'; import { EuiButtonEmpty, EuiButtonIcon, @@ -30,7 +30,6 @@ import { DiscoverHistogram, DiscoverUninitialized } from '../angular/directives' import { DiscoverNoResults } from './no_results'; import { LoadingSpinner } from './loading_spinner/loading_spinner'; import { DocTableLegacy } from '../angular/doc_table/create_doc_table_react'; -import { SkipBottomButton } from './skip_bottom_button'; import { esFilters, IndexPatternField, search } from '../../../../data/public'; import { DiscoverSidebarResponsive } from './sidebar'; import { DiscoverProps } from './types'; @@ -42,11 +41,15 @@ import { DocViewFilterFn } from '../doc_views/doc_views_types'; import { DiscoverGrid } from './discover_grid/discover_grid'; import { DiscoverTopNav } from './discover_topnav'; import { ElasticSearchHit } from '../doc_views/doc_views_types'; +import { setBreadcrumbsTitle } from '../helpers/breadcrumbs'; +import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; const DocTableLegacyMemoized = React.memo(DocTableLegacy); const SidebarMemoized = React.memo(DiscoverSidebarResponsive); const DataGridMemoized = React.memo(DiscoverGrid); const TopNavMemoized = React.memo(DiscoverTopNav); +const TimechartHeaderMemoized = React.memo(TimechartHeader); +const DiscoverHistogramMemoized = React.memo(DiscoverHistogram); export function Discover({ fetch, @@ -58,14 +61,12 @@ export function Discover({ hits, indexPattern, minimumVisibleRows, - onSkipBottomButtonClick, opts, resetQuery, resultState, rows, searchSource, state, - timeRange, unmappedFieldsConfig, }: DiscoverProps) { const [expandedDoc, setExpandedDoc] = useState(undefined); @@ -81,13 +82,16 @@ export function Discover({ }, [state, opts]); const hideChart = useMemo(() => state.hideChart, [state]); const { savedSearch, indexPatternList, config, services, data, setAppState } = opts; - const { trackUiMetric, capabilities, indexPatterns } = services; + const { trackUiMetric, capabilities, indexPatterns, chrome, docLinks } = services; + const [isSidebarClosed, setIsSidebarClosed] = useState(false); - const bucketAggConfig = opts.chartAggConfigs?.aggs[1]; - const bucketInterval = - bucketAggConfig && search.aggs.isDateHistogramBucketAggConfig(bucketAggConfig) + const bucketInterval = useMemo(() => { + const bucketAggConfig = opts.chartAggConfigs?.aggs[1]; + return bucketAggConfig && search.aggs.isDateHistogramBucketAggConfig(bucketAggConfig) ? bucketAggConfig.buckets?.getInterval() : undefined; + }, [opts.chartAggConfigs]); + const contentCentered = resultState === 'uninitialized'; const isLegacy = services.uiSettings.get('doc_table:legacy'); const useNewFieldsApi = !services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); @@ -101,6 +105,14 @@ export function Discover({ [opts] ); + useEffect(() => { + const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; + chrome.docTitle.change(`Discover${pageTitleSuffix}`); + + setBreadcrumbsTitle(savedSearch, chrome); + addHelpMenuToAppChrome(chrome, docLinks); + }, [savedSearch, chrome, docLinks]); + const { onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useMemo( () => getStateColumnActions({ @@ -293,9 +305,9 @@ export function Discover({ {!hideChart && ( - )} - {isLegacy && } {!hideChart && opts.timefield && ( @@ -342,7 +353,7 @@ export function Discover({ className={isLegacy ? 'dscHistogram' : 'dscHistogramGrid'} data-test-subj="discoverChart" > - diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx index 1a721a400803e6..93b5bf8fde0c18 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx @@ -64,11 +64,14 @@ describe('Discover grid columns ', function () { "showMoveLeft": false, "showMoveRight": false, }, - "cellActions": undefined, + "cellActions": Array [ + [Function], + [Function], + ], "display": undefined, "id": "extension", "isSortable": false, - "schema": "kibana-json", + "schema": "string", }, Object { "actions": Object { @@ -80,7 +83,7 @@ describe('Discover grid columns ', function () { "display": undefined, "id": "message", "isSortable": false, - "schema": "kibana-json", + "schema": "string", }, ] `); @@ -101,12 +104,15 @@ describe('Discover grid columns ', function () { "showMoveLeft": true, "showMoveRight": true, }, - "cellActions": undefined, + "cellActions": Array [ + [Function], + [Function], + ], "display": "Time (timestamp)", "id": "timestamp", "initialWidth": 180, - "isSortable": false, - "schema": "kibana-json", + "isSortable": true, + "schema": "datetime", }, Object { "actions": Object { @@ -117,11 +123,14 @@ describe('Discover grid columns ', function () { "showMoveLeft": true, "showMoveRight": true, }, - "cellActions": undefined, + "cellActions": Array [ + [Function], + [Function], + ], "display": undefined, "id": "extension", "isSortable": false, - "schema": "kibana-json", + "schema": "string", }, Object { "actions": Object { @@ -136,7 +145,7 @@ describe('Discover grid columns ', function () { "display": undefined, "id": "message", "isSortable": false, - "schema": "kibana-json", + "schema": "string", }, ] `); diff --git a/src/plugins/discover/public/application/components/help_menu/help_menu_util.js b/src/plugins/discover/public/application/components/help_menu/help_menu_util.ts similarity index 81% rename from src/plugins/discover/public/application/components/help_menu/help_menu_util.js rename to src/plugins/discover/public/application/components/help_menu/help_menu_util.ts index 1a6815b40b581f..d0d5cfde1fe06d 100644 --- a/src/plugins/discover/public/application/components/help_menu/help_menu_util.js +++ b/src/plugins/discover/public/application/components/help_menu/help_menu_util.ts @@ -7,10 +7,9 @@ */ import { i18n } from '@kbn/i18n'; -import { getServices } from '../../../kibana_services'; -const { docLinks } = getServices(); +import { ChromeStart, DocLinksStart } from 'kibana/public'; -export function addHelpMenuToAppChrome(chrome) { +export function addHelpMenuToAppChrome(chrome: ChromeStart, docLinks: DocLinksStart) { chrome.setHelpExtension({ appName: i18n.translate('discover.helpMenu.appName', { defaultMessage: 'Discover', diff --git a/src/plugins/discover/public/application/components/histogram/apply_aggs_to_search_source.test.ts b/src/plugins/discover/public/application/components/histogram/apply_aggs_to_search_source.test.ts new file mode 100644 index 00000000000000..29c93886ebba37 --- /dev/null +++ b/src/plugins/discover/public/application/components/histogram/apply_aggs_to_search_source.test.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { indexPatternWithTimefieldMock } from '../../../__mocks__/index_pattern_with_timefield'; +import { SearchSource } from '../../../../../data/public'; +import { dataPluginMock } from '../../../../../data/public/mocks'; +import { applyAggsToSearchSource } from './apply_aggs_to_search_source'; + +describe('applyAggsToSearchSource', () => { + test('enabled = true', () => { + const indexPattern = indexPatternWithTimefieldMock; + const setField = jest.fn(); + const searchSource = ({ + setField, + removeField: jest.fn(), + } as unknown) as SearchSource; + + const dataMock = dataPluginMock.createStartContract(); + + const aggsConfig = applyAggsToSearchSource(true, searchSource, 'auto', indexPattern, dataMock); + + expect(aggsConfig!.aggs).toMatchInlineSnapshot(` + Array [ + Object { + "enabled": true, + "id": "1", + "params": Object {}, + "schema": "metric", + "type": "count", + }, + Object { + "enabled": true, + "id": "2", + "params": Object { + "drop_partials": false, + "extended_bounds": Object {}, + "field": "timestamp", + "interval": "auto", + "min_doc_count": 1, + "scaleMetricValues": false, + "useNormalizedEsInterval": true, + }, + "schema": "segment", + "type": "date_histogram", + }, + ] + `); + + expect(setField).toHaveBeenCalledWith('aggs', expect.any(Function)); + const dslFn = setField.mock.calls[0][1]; + expect(dslFn()).toMatchInlineSnapshot(` + Object { + "2": Object { + "date_histogram": Object { + "field": "timestamp", + "min_doc_count": 1, + "time_zone": "America/New_York", + }, + }, + } + `); + }); + + test('enabled = false', () => { + const indexPattern = indexPatternWithTimefieldMock; + const setField = jest.fn(); + const getField = jest.fn(() => { + return true; + }); + const removeField = jest.fn(); + const searchSource = ({ + getField, + setField, + removeField, + } as unknown) as SearchSource; + + const dataMock = dataPluginMock.createStartContract(); + + const aggsConfig = applyAggsToSearchSource(false, searchSource, 'auto', indexPattern, dataMock); + expect(aggsConfig).toBeFalsy(); + expect(getField).toHaveBeenCalledWith('aggs'); + expect(removeField).toHaveBeenCalledWith('aggs'); + }); +}); diff --git a/src/plugins/discover/public/application/components/histogram/apply_aggs_to_search_source.ts b/src/plugins/discover/public/application/components/histogram/apply_aggs_to_search_source.ts new file mode 100644 index 00000000000000..c5fb366f81c8cd --- /dev/null +++ b/src/plugins/discover/public/application/components/histogram/apply_aggs_to_search_source.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { IndexPattern, SearchSource } from '../../../../../data/common'; +import { DataPublicPluginStart } from '../../../../../data/public'; + +/** + * Helper function to apply or remove aggregations to a given search source used for gaining data + * for Discover's histogram vis + */ +export function applyAggsToSearchSource( + enabled: boolean, + searchSource: SearchSource, + histogramInterval: string, + indexPattern: IndexPattern, + data: DataPublicPluginStart +) { + if (!enabled) { + if (searchSource.getField('aggs')) { + // clean up fields in case it was set before + searchSource.removeField('aggs'); + } + return; + } + const visStateAggs = [ + { + type: 'count', + schema: 'metric', + }, + { + type: 'date_histogram', + schema: 'segment', + params: { + field: indexPattern.timeFieldName!, + interval: histogramInterval, + timeRange: data.query.timefilter.timefilter.getTime(), + }, + }, + ]; + const chartAggConfigs = data.search.aggs.createAggConfigs(indexPattern, visStateAggs); + + searchSource.setField('aggs', function () { + return chartAggConfigs.toDsl(); + }); + return chartAggConfigs; +} diff --git a/src/plugins/discover/public/application/components/histogram/get_dimensions.test.ts b/src/plugins/discover/public/application/components/histogram/get_dimensions.test.ts new file mode 100644 index 00000000000000..ad7031f3319924 --- /dev/null +++ b/src/plugins/discover/public/application/components/histogram/get_dimensions.test.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { dataPluginMock } from '../../../../../data/public/mocks'; + +import { getDimensions } from './get_dimensions'; +import { indexPatternWithTimefieldMock } from '../../../__mocks__/index_pattern_with_timefield'; +import { SearchSource } from '../../../../../data/common/search/search_source'; +import { applyAggsToSearchSource } from './apply_aggs_to_search_source'; +import { calculateBounds } from '../../../../../data/common/query/timefilter'; + +test('getDimensions', () => { + const indexPattern = indexPatternWithTimefieldMock; + const setField = jest.fn(); + const searchSource = ({ + setField, + removeField: jest.fn(), + } as unknown) as SearchSource; + + const dataMock = dataPluginMock.createStartContract(); + dataMock.query.timefilter.timefilter.getTime = () => { + return { from: 'now-30y', to: 'now' }; + }; + dataMock.query.timefilter.timefilter.calculateBounds = (timeRange) => { + return calculateBounds(timeRange); + }; + + const aggsConfig = applyAggsToSearchSource(true, searchSource, 'auto', indexPattern, dataMock); + const actual = getDimensions(aggsConfig!, dataMock); + expect(actual).toMatchInlineSnapshot(` + Object { + "x": Object { + "accessor": 0, + "format": Object { + "id": "date", + "params": Object { + "pattern": "HH:mm:ss.SSS", + }, + }, + "label": "timestamp per 0 milliseconds", + "params": Object { + "bounds": undefined, + "date": true, + "format": "HH:mm:ss.SSS", + "interval": "P365D", + "intervalESUnit": "d", + "intervalESValue": 365, + }, + }, + "y": Object { + "accessor": 1, + "format": Object { + "id": "number", + }, + "label": "Count", + }, + } + `); +}); diff --git a/src/plugins/discover/public/application/components/histogram/get_dimensions.ts b/src/plugins/discover/public/application/components/histogram/get_dimensions.ts new file mode 100644 index 00000000000000..6743c1c8431b9b --- /dev/null +++ b/src/plugins/discover/public/application/components/histogram/get_dimensions.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import moment from 'moment'; +import dateMath from '@elastic/datemath'; +import { IAggConfigs, TimeRangeBounds } from '../../../../../data/common'; +import { DataPublicPluginStart, search } from '../../../../../data/public'; + +export function getDimensions(aggs: IAggConfigs, data: DataPublicPluginStart) { + const [metric, agg] = aggs.aggs; + const { from, to } = data.query.timefilter.timefilter.getTime(); + agg.params.timeRange = { + from: dateMath.parse(from), + to: dateMath.parse(to, { roundUp: true }), + }; + const bounds = agg.params.timeRange + ? data.query.timefilter.timefilter.calculateBounds(agg.params.timeRange) + : null; + const buckets = search.aggs.isDateHistogramBucketAggConfig(agg) ? agg.buckets : undefined; + + if (!buckets) { + return; + } + + buckets.setBounds(bounds as TimeRangeBounds); + + const { esUnit, esValue } = buckets.getInterval(); + return { + x: { + accessor: 0, + label: agg.makeLabel(), + format: agg.toSerializedFieldFormat(), + params: { + date: true, + interval: moment.duration(esValue, esUnit), + intervalESValue: esValue, + intervalESUnit: esUnit, + format: buckets.getScaledDateFormat(), + bounds: buckets.getBounds(), + }, + }, + y: { + accessor: 1, + format: metric.toSerializedFieldFormat(), + label: metric.makeLabel(), + }, + }; +} diff --git a/src/plugins/discover/public/application/components/histogram/index.ts b/src/plugins/discover/public/application/components/histogram/index.ts new file mode 100644 index 00000000000000..4af75de0a029d8 --- /dev/null +++ b/src/plugins/discover/public/application/components/histogram/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { applyAggsToSearchSource } from './apply_aggs_to_search_source'; +export { getDimensions } from './get_dimensions'; diff --git a/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx b/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx index ff8f14115e492b..74836711373b28 100644 --- a/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx +++ b/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx @@ -12,6 +12,7 @@ import { ReactWrapper } from 'enzyme'; import { TimechartHeader, TimechartHeaderProps } from './timechart_header'; import { EuiIconTip } from '@elastic/eui'; import { findTestSubject } from '@elastic/eui/lib/test'; +import { DataPublicPluginStart } from '../../../../../data/public'; describe('timechart header', function () { let props: TimechartHeaderProps; @@ -19,10 +20,18 @@ describe('timechart header', function () { beforeAll(() => { props = { - timeRange: { - from: 'May 14, 2020 @ 11:05:13.590', - to: 'May 14, 2020 @ 11:20:13.590', - }, + data: { + query: { + timefilter: { + timefilter: { + getTime: () => { + return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' }; + }, + }, + }, + }, + } as DataPublicPluginStart, + dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', stateInterval: 's', options: [ { diff --git a/src/plugins/discover/public/application/components/timechart_header/timechart_header.tsx b/src/plugins/discover/public/application/components/timechart_header/timechart_header.tsx index 0379059b80e589..a2fc17e05a203e 100644 --- a/src/plugins/discover/public/application/components/timechart_header/timechart_header.tsx +++ b/src/plugins/discover/public/application/components/timechart_header/timechart_header.tsx @@ -15,9 +15,11 @@ import { EuiSelect, EuiIconTip, } from '@elastic/eui'; +import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import dateMath from '@elastic/datemath'; +import { DataPublicPluginStart } from '../../../../../data/public'; import './timechart_header.scss'; -import moment from 'moment'; export interface TimechartHeaderProps { /** @@ -32,13 +34,7 @@ export interface TimechartHeaderProps { description?: string; scale?: number; }; - /** - * Range of dates to be displayed - */ - timeRange?: { - from: string; - to: string; - }; + data: DataPublicPluginStart; /** * Interval Options */ @@ -56,21 +52,27 @@ export interface TimechartHeaderProps { export function TimechartHeader({ bucketInterval, dateFormat, - timeRange, + data, options, onChangeInterval, stateInterval, }: TimechartHeaderProps) { + const { timefilter } = data.query.timefilter; + const { from, to } = timefilter.getTime(); + const timeRange = { + from: dateMath.parse(from), + to: dateMath.parse(to, { roundUp: true }), + }; const [interval, setInterval] = useState(stateInterval); const toMoment = useCallback( - (datetime: string) => { + (datetime: moment.Moment | undefined) => { if (!datetime) { return ''; } if (!dateFormat) { - return datetime; + return String(datetime); } - return moment(datetime).format(dateFormat); + return datetime.format(dateFormat); }, [dateFormat] ); diff --git a/src/plugins/discover/public/application/components/types.ts b/src/plugins/discover/public/application/components/types.ts index db1cd894224540..23a3cc9a9bc748 100644 --- a/src/plugins/discover/public/application/components/types.ts +++ b/src/plugins/discover/public/application/components/types.ts @@ -158,10 +158,6 @@ export interface DiscoverProps { * Current app state of URL */ state: AppState; - /** - * Currently selected time range - */ - timeRange?: { from: string; to: string }; /** * An object containing properties for unmapped fields behavior */ diff --git a/src/plugins/discover/public/application/helpers/get_result_state.test.ts b/src/plugins/discover/public/application/helpers/get_result_state.test.ts new file mode 100644 index 00000000000000..98e2b854ca5abe --- /dev/null +++ b/src/plugins/discover/public/application/helpers/get_result_state.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { getResultState, resultStatuses } from './get_result_state'; +import { fetchStatuses } from '../components/constants'; +import { ElasticSearchHit } from '../doc_views/doc_views_types'; + +describe('getResultState', () => { + test('fetching uninitialized', () => { + const actual = getResultState(fetchStatuses.UNINITIALIZED, []); + expect(actual).toBe(resultStatuses.UNINITIALIZED); + }); + + test('fetching complete with no records', () => { + const actual = getResultState(fetchStatuses.COMPLETE, []); + expect(actual).toBe(resultStatuses.NO_RESULTS); + }); + + test('fetching ongoing aka loading', () => { + const actual = getResultState(fetchStatuses.LOADING, []); + expect(actual).toBe(resultStatuses.LOADING); + }); + + test('fetching ready', () => { + const record = ({ _id: 123 } as unknown) as ElasticSearchHit; + const actual = getResultState(fetchStatuses.COMPLETE, [record]); + expect(actual).toBe(resultStatuses.READY); + }); + + test('re-fetching after already data is available', () => { + const record = ({ _id: 123 } as unknown) as ElasticSearchHit; + const actual = getResultState(fetchStatuses.LOADING, [record]); + expect(actual).toBe(resultStatuses.READY); + }); + + test('after a fetch error when data was successfully fetched before ', () => { + const record = ({ _id: 123 } as unknown) as ElasticSearchHit; + const actual = getResultState(fetchStatuses.ERROR, [record]); + expect(actual).toBe(resultStatuses.READY); + }); +}); diff --git a/src/plugins/discover/public/application/helpers/get_result_state.ts b/src/plugins/discover/public/application/helpers/get_result_state.ts new file mode 100644 index 00000000000000..6f69832f369fd1 --- /dev/null +++ b/src/plugins/discover/public/application/helpers/get_result_state.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { ElasticSearchHit } from '../doc_views/doc_views_types'; +import { fetchStatuses } from '../components/constants'; + +export const resultStatuses = { + UNINITIALIZED: 'uninitialized', + LOADING: 'loading', // initial data load + READY: 'ready', // results came back + NO_RESULTS: 'none', // no results came back +}; + +/** + * Returns the current state of the result, depends on fetchStatus and the given fetched rows + * Determines what is displayed in Discover main view (loading view, data view, empty data view, ...) + */ +export function getResultState(fetchStatus: string, rows: ElasticSearchHit[]) { + if (fetchStatus === fetchStatuses.UNINITIALIZED) { + return resultStatuses.UNINITIALIZED; + } + + const rowsEmpty = !Array.isArray(rows) || rows.length === 0; + if (rowsEmpty && fetchStatus === fetchStatuses.LOADING) return resultStatuses.LOADING; + else if (!rowsEmpty) return resultStatuses.READY; + else return resultStatuses.NO_RESULTS; +} diff --git a/src/plugins/discover/public/application/helpers/get_state_defaults.test.ts b/src/plugins/discover/public/application/helpers/get_state_defaults.test.ts new file mode 100644 index 00000000000000..7ce5b9286c775c --- /dev/null +++ b/src/plugins/discover/public/application/helpers/get_state_defaults.test.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getStateDefaults } from './get_state_defaults'; +import { createSearchSourceMock, dataPluginMock } from '../../../../data/public/mocks'; +import { uiSettingsMock } from '../../__mocks__/ui_settings'; +import { indexPatternWithTimefieldMock } from '../../__mocks__/index_pattern_with_timefield'; +import { savedSearchMock } from '../../__mocks__/saved_search'; +import { indexPatternMock } from '../../__mocks__/index_pattern'; + +describe('getStateDefaults', () => { + test('index pattern with timefield', () => { + const actual = getStateDefaults({ + config: uiSettingsMock, + data: dataPluginMock.createStartContract(), + indexPattern: indexPatternWithTimefieldMock, + savedSearch: savedSearchMock, + searchSource: createSearchSourceMock({ index: indexPatternWithTimefieldMock }), + }); + expect(actual).toMatchInlineSnapshot(` + Object { + "columns": Array [ + "default_column", + ], + "filters": undefined, + "index": "index-pattern-with-timefield-id", + "interval": "auto", + "query": undefined, + "sort": Array [ + Array [ + "timestamp", + "desc", + ], + ], + } + `); + }); + + test('index pattern without timefield', () => { + const actual = getStateDefaults({ + config: uiSettingsMock, + data: dataPluginMock.createStartContract(), + indexPattern: indexPatternMock, + savedSearch: savedSearchMock, + searchSource: createSearchSourceMock({ index: indexPatternMock }), + }); + expect(actual).toMatchInlineSnapshot(` + Object { + "columns": Array [ + "default_column", + ], + "filters": undefined, + "index": "the-index-pattern-id", + "interval": "auto", + "query": undefined, + "sort": Array [], + } + `); + }); +}); diff --git a/src/plugins/discover/public/application/helpers/get_state_defaults.ts b/src/plugins/discover/public/application/helpers/get_state_defaults.ts new file mode 100644 index 00000000000000..3e012a1f85fd6b --- /dev/null +++ b/src/plugins/discover/public/application/helpers/get_state_defaults.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { cloneDeep } from 'lodash'; +import { IUiSettingsClient } from 'kibana/public'; +import { DEFAULT_COLUMNS_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common'; +import { getSortArray } from '../angular/doc_table'; +import { getDefaultSort } from '../angular/doc_table/lib/get_default_sort'; +import { SavedSearch } from '../../saved_searches'; +import { SearchSource } from '../../../../data/common/search/search_source'; +import { DataPublicPluginStart, IndexPattern } from '../../../../data/public'; + +import { AppState } from '../angular/discover_state'; + +function getDefaultColumns(savedSearch: SavedSearch, config: IUiSettingsClient) { + if (savedSearch.columns && savedSearch.columns.length > 0) { + return [...savedSearch.columns]; + } + return [...config.get(DEFAULT_COLUMNS_SETTING)]; +} + +export function getStateDefaults({ + config, + data, + indexPattern, + savedSearch, + searchSource, +}: { + config: IUiSettingsClient; + data: DataPublicPluginStart; + indexPattern: IndexPattern; + savedSearch: SavedSearch; + searchSource: SearchSource; +}) { + const query = searchSource.getField('query') || data.query.queryString.getDefaultQuery(); + const sort = getSortArray(savedSearch.sort, indexPattern); + const columns = getDefaultColumns(savedSearch, config); + + const defaultState = { + query, + sort: !sort.length + ? getDefaultSort(indexPattern, config.get(SORT_DEFAULT_ORDER_SETTING, 'desc')) + : sort, + columns, + index: indexPattern.id, + interval: 'auto', + filters: cloneDeep(searchSource.getOwnField('filter')), + } as AppState; + if (savedSearch.grid) { + defaultState.grid = savedSearch.grid; + } + if (savedSearch.hideChart) { + defaultState.hideChart = savedSearch.hideChart; + } + + return defaultState; +} diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts index aeb02e5c30eb88..def175474d40ee 100644 --- a/test/functional/apps/discover/_discover.ts +++ b/test/functional/apps/discover/_discover.ts @@ -105,21 +105,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should modify the time range when the histogram is brushed', async function () { // this is the number of renderings of the histogram needed when new data is fetched // this needs to be improved - const renderingCountInc = 3; + const renderingCountInc = 1; const prevRenderingCount = await elasticChart.getVisualizationRenderingCount(); await PageObjects.timePicker.setDefaultAbsoluteRange(); await PageObjects.discover.waitUntilSearchingHasFinished(); await retry.waitFor('chart rendering complete', async () => { - const actualRenderingCount = await elasticChart.getVisualizationRenderingCount(); - log.debug(`Number of renderings before brushing: ${actualRenderingCount}`); - return actualRenderingCount === prevRenderingCount + renderingCountInc; + const actualCount = await elasticChart.getVisualizationRenderingCount(); + const expectedCount = prevRenderingCount + renderingCountInc; + log.debug( + `renderings before brushing - actual: ${actualCount} expected: ${expectedCount}` + ); + return actualCount === expectedCount; }); await PageObjects.discover.brushHistogram(); await PageObjects.discover.waitUntilSearchingHasFinished(); await retry.waitFor('chart rendering complete after being brushed', async () => { - const actualRenderingCount = await elasticChart.getVisualizationRenderingCount(); - log.debug(`Number of renderings after brushing: ${actualRenderingCount}`); - return actualRenderingCount === prevRenderingCount + 6; + const actualCount = await elasticChart.getVisualizationRenderingCount(); + const expectedCount = prevRenderingCount + renderingCountInc * 2; + log.debug( + `renderings after brushing - actual: ${actualCount} expected: ${expectedCount}` + ); + return actualCount === expectedCount; }); const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); expect(Math.round(newDurationHours)).to.be(26); diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index 3febeb06fd600b..edcb0020001834 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -65,6 +65,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const finalRows = await PageObjects.discover.getDocTableRows(); expect(finalRows.length).to.be.above(initialRows.length); expect(finalRows.length).to.be(rowsHardLimit); + await PageObjects.discover.backToTop(); }); it('should go the end of the table when using the accessible Skip button', async function () { @@ -74,6 +75,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const footer = await PageObjects.discover.getDocTableFooter(); log.debug(await footer.getVisibleText()); expect(await footer.getVisibleText()).to.have.string(rowsHardLimit); + await PageObjects.discover.backToTop(); }); describe('expand a document row', function () { diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 733f5cb59fbbb3..32288239f98485 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -210,6 +210,15 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider return skipButton.click(); } + /** + * When scrolling down the legacy table there's a link to scroll up + * So this is done by this function + */ + public async backToTop() { + const skipButton = await testSubjects.find('discoverBackToTop'); + return skipButton.click(); + } + public async getDocTableFooter() { return await testSubjects.find('discoverDocTableFooter'); } From 69bb5979ce23eedf8ff926e12728bbc6819e5ece Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 25 Mar 2021 09:04:40 +0100 Subject: [PATCH 85/93] [Uptime] Support agent data streams (#91469) Co-authored-by: Dominique Clarke Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/constants/settings_defaults.ts | 2 +- .../settings/add_connector_flyout.tsx | 1 + .../uptime/public/lib/helper/rtl_helpers.tsx | 31 ++++++++++--- .../uptime/public/pages/settings.test.tsx | 45 ++++++++++++++++++- .../plugins/uptime/public/pages/settings.tsx | 5 ++- .../uptime/public/pages/translations.ts | 4 ++ .../get_monitor_charts.test.ts.snap | 2 +- .../server/lib/requests/get_certs.test.ts | 2 +- .../requests/get_monitor_availability.test.ts | 10 ++--- .../lib/requests/get_monitor_status.test.ts | 10 ++--- .../lib/requests/get_network_events.test.ts | 2 +- .../server/lib/requests/get_pings.test.ts | 10 ++--- 12 files changed, 98 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/uptime/common/constants/settings_defaults.ts b/x-pack/plugins/uptime/common/constants/settings_defaults.ts index b57e3dfc86beab..b57bc8a85d7cdb 100644 --- a/x-pack/plugins/uptime/common/constants/settings_defaults.ts +++ b/x-pack/plugins/uptime/common/constants/settings_defaults.ts @@ -8,7 +8,7 @@ import { DynamicSettings } from '../runtime_types'; export const DYNAMIC_SETTINGS_DEFAULTS: DynamicSettings = { - heartbeatIndices: 'heartbeat-8*', + heartbeatIndices: 'heartbeat-8*,synthetics-*', certAgeThreshold: 730, certExpirationThreshold: 30, defaultConnectors: [], diff --git a/x-pack/plugins/uptime/public/components/settings/add_connector_flyout.tsx b/x-pack/plugins/uptime/public/components/settings/add_connector_flyout.tsx index adddaf6d028290..b2363a1b21f05e 100644 --- a/x-pack/plugins/uptime/public/components/settings/add_connector_flyout.tsx +++ b/x-pack/plugins/uptime/public/components/settings/add_connector_flyout.tsx @@ -68,6 +68,7 @@ export const AddConnectorFlyout = ({ focusInput }: Props) => { return ( <> setAddFlyoutVisibility(true)} diff --git a/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx b/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx index a2a67fe7347f52..54839e9009896e 100644 --- a/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx +++ b/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx @@ -23,6 +23,8 @@ import { import { MountWithReduxProvider } from './helper_with_redux'; import { AppState } from '../../state'; import { stringifyUrlParams } from './stringify_url_params'; +import { ClientPluginsStart } from '../../apps/plugin'; +import { triggersActionsUiMock } from '../../../../triggers_actions_ui/public/mocks'; interface KibanaProps { services?: KibanaServices; @@ -55,20 +57,39 @@ interface RenderRouterOptions extends KibanaProviderOptions(key: string): T { + return ('MMM D, YYYY @ HH:mm:ss.SSS' as unknown) as T; +} + +function setSetting$(key: string): T { + return (of('MMM D, YYYY @ HH:mm:ss.SSS') as unknown) as T; +} + /* default mock core */ const defaultCore = coreMock.createStart(); -const mockCore: () => any = () => { - const core = { +const mockCore: () => Partial = () => { + const core: Partial = { ...defaultCore, application: { + ...defaultCore.application, getUrlForApp: () => '/app/uptime', navigateToUrl: jest.fn(), + capabilities: { + ...defaultCore.application.capabilities, + uptime: { + 'alerting:save': true, + configureSettings: true, + save: true, + show: true, + }, + }, }, uiSettings: { - get: (key: string) => 'MMM D, YYYY @ HH:mm:ss.SSS', - get$: (key: string) => of('MMM D, YYYY @ HH:mm:ss.SSS'), + ...defaultCore.uiSettings, + get: getSetting, + get$: setSetting$, }, - usageCollection: { reportUiCounter: () => {} }, + triggersActionsUi: triggersActionsUiMock.createStart(), }; return core; diff --git a/x-pack/plugins/uptime/public/pages/settings.test.tsx b/x-pack/plugins/uptime/public/pages/settings.test.tsx index b02f304f1a7e74..95fed208f6b0a8 100644 --- a/x-pack/plugins/uptime/public/pages/settings.test.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.test.tsx @@ -4,10 +4,53 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import React from 'react'; -import { isValidCertVal } from './settings'; +import { isValidCertVal, SettingsPage } from './settings'; +import { render } from '../lib/helper/rtl_helpers'; +import { fireEvent, waitFor } from '@testing-library/dom'; +import { act } from 'react-dom/test-utils'; +import * as alertApi from '../state/api/alerts'; describe('settings', () => { + describe('form', () => { + beforeAll(() => { + jest.spyOn(alertApi, 'fetchActionTypes').mockImplementation(async () => [ + { + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + id: '.slack', + minimumLicenseRequired: 'gold', + name: 'Slack', + }, + ]); + }); + + it('handles no spaces error', async () => { + const { getByText, getByTestId } = render(); + + expect(getByText('heartbeat-8*,synthetics-*')); + + fireEvent.input(getByTestId('heartbeat-indices-input-loaded'), { + target: { value: 'heartbeat-8*, synthetics-*' }, + }); + + await waitFor(() => expect(getByText('Index names must not contain space'))); + }); + + it('it show select a connector flyout', async () => { + const { getByText, getByTestId } = render(); + + expect(getByText('heartbeat-8*,synthetics-*')); + + act(() => { + fireEvent.click(getByTestId('createConnectorButton')); + }); + await waitFor(() => expect(getByText('Select a connector'))); + }); + }); + describe('isValidCertVal', () => { it('handles NaN values', () => { expect(isValidCertVal(NaN)).toMatchInlineSnapshot(`"Must be a number."`); diff --git a/x-pack/plugins/uptime/public/pages/settings.tsx b/x-pack/plugins/uptime/public/pages/settings.tsx index 4b476dd93b3bc4..f806ebbd09cc35 100644 --- a/x-pack/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.tsx @@ -34,6 +34,7 @@ import { VALUE_MUST_BE_AN_INTEGER, } from '../../common/translations'; import { AlertDefaultsForm } from '../components/settings/alert_defaults_form'; +import { BLANK_STR, SPACE_STR } from './translations'; interface SettingsPageFieldErrors { heartbeatIndices: string | ''; @@ -65,7 +66,9 @@ const getFieldErrors = (formFields: DynamicSettings | null): SettingsPageFieldEr if (formFields) { const { certAgeThreshold, certExpirationThreshold, heartbeatIndices } = formFields; - const indError = heartbeatIndices.match(/^\S+$/) ? '' : Translations.BLANK_STR; + const indErrorSpace = heartbeatIndices.includes(' ') ? SPACE_STR : ''; + + const indError = indErrorSpace || (heartbeatIndices.match(/^\S+$/) ? '' : BLANK_STR); const expError = isValidCertVal(certExpirationThreshold); const ageError = isValidCertVal(certAgeThreshold); diff --git a/x-pack/plugins/uptime/public/pages/translations.ts b/x-pack/plugins/uptime/public/pages/translations.ts index e6ea04013a22a0..95a8dd8ee36969 100644 --- a/x-pack/plugins/uptime/public/pages/translations.ts +++ b/x-pack/plugins/uptime/public/pages/translations.ts @@ -30,3 +30,7 @@ export const settings = { export const BLANK_STR = i18n.translate('xpack.uptime.settings.blank.error', { defaultMessage: 'May not be blank.', }); + +export const SPACE_STR = i18n.translate('xpack.uptime.settings.noSpace.error', { + defaultMessage: 'Index names must not contain space', +}); diff --git a/x-pack/plugins/uptime/server/lib/requests/__snapshots__/get_monitor_charts.test.ts.snap b/x-pack/plugins/uptime/server/lib/requests/__snapshots__/get_monitor_charts.test.ts.snap index dd47945d9eaba2..2d1bdf791e39a6 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__snapshots__/get_monitor_charts.test.ts.snap +++ b/x-pack/plugins/uptime/server/lib/requests/__snapshots__/get_monitor_charts.test.ts.snap @@ -55,7 +55,7 @@ Array [ }, "size": 0, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", }, ] `; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts index 399fca82d8d15d..333824df174a65 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts @@ -212,7 +212,7 @@ describe('getCerts', () => { }, ], }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", }, ], ] diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.test.ts index 29011ecf548718..0f1bd606307e5d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.test.ts @@ -241,7 +241,7 @@ describe('monitor availability', () => { }, "size": 0, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", } `); }); @@ -387,7 +387,7 @@ describe('monitor availability', () => { }, "size": 0, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", } `); @@ -701,7 +701,7 @@ describe('monitor availability', () => { }, "size": 0, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", } `); @@ -799,7 +799,7 @@ describe('monitor availability', () => { }, "size": 0, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", }, ] `); @@ -929,7 +929,7 @@ describe('monitor availability', () => { }, "size": 0, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", } `); }); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.test.ts index d0aaeafbe650d9..6d88ccb9a9efff 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.test.ts @@ -185,7 +185,7 @@ describe('getMonitorStatus', () => { }, "size": 0, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", } `); }); @@ -287,7 +287,7 @@ describe('getMonitorStatus', () => { }, "size": 0, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", } `); }); @@ -474,7 +474,7 @@ describe('getMonitorStatus', () => { }, "size": 0, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", } `); }); @@ -581,7 +581,7 @@ describe('getMonitorStatus', () => { }, "size": 0, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", } `); }); @@ -694,7 +694,7 @@ describe('getMonitorStatus', () => { }, "size": 0, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", } `); expect(result.length).toBe(3); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts index 9d4e42337fd756..a9c29012141da9 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts @@ -210,7 +210,7 @@ describe('getNetworkEvents', () => { "size": 1000, "track_total_hits": true, }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", }, ], ] diff --git a/x-pack/plugins/uptime/server/lib/requests/get_pings.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_pings.test.ts index b0e7a703d6b5ae..03eb10ab3daccb 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_pings.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_pings.test.ts @@ -175,7 +175,7 @@ describe('getAll', () => { }, ], }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", }, ] `); @@ -244,7 +244,7 @@ describe('getAll', () => { }, ], }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", }, ] `); @@ -313,7 +313,7 @@ describe('getAll', () => { }, ], }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", }, ] `); @@ -387,7 +387,7 @@ describe('getAll', () => { }, ], }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", }, ] `); @@ -461,7 +461,7 @@ describe('getAll', () => { }, ], }, - "index": "heartbeat-8*", + "index": "heartbeat-8*,synthetics-*", }, ] `); From b88f02ffb423579b982ade89b0ba1367c7d3c333 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 25 Mar 2021 09:05:26 +0100 Subject: [PATCH 86/93] [Uptime] unskip overview test (#95290) --- x-pack/test/functional/apps/uptime/overview.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/apps/uptime/overview.ts b/x-pack/test/functional/apps/uptime/overview.ts index b9c1767e4a8cfa..894604978598e0 100644 --- a/x-pack/test/functional/apps/uptime/overview.ts +++ b/x-pack/test/functional/apps/uptime/overview.ts @@ -15,8 +15,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const testSubjects = getService('testSubjects'); - // FLAKY: https://github.com/elastic/kibana/issues/89072 - describe.skip('overview page', function () { + describe('overview page', function () { const DEFAULT_DATE_START = 'Sep 10, 2019 @ 12:40:08.078'; const DEFAULT_DATE_END = 'Sep 11, 2019 @ 19:40:08.078'; @@ -96,7 +95,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await uptime.pageUrlContains('pagination'); await uptime.setMonitorListPageSize(50); // the pagination parameter should be cleared after a size change - await uptime.pageUrlContains('pagination', false); + await new Promise((resolve) => setTimeout(resolve, 1000)); + await retry.try(async () => { + await uptime.pageUrlContains('pagination', false); + }); }); it('pagination size updates to reflect current selection', async () => { From 9724051f928357c7db4220a577845c973eb70ed2 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 25 Mar 2021 09:06:58 +0100 Subject: [PATCH 87/93] [Observability] add loading state in use fetcher (#95292) --- x-pack/plugins/observability/public/hooks/use_fetcher.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugins/observability/public/hooks/use_fetcher.tsx b/x-pack/plugins/observability/public/hooks/use_fetcher.tsx index 2a529f634e7a86..8e30f270bc58c9 100644 --- a/x-pack/plugins/observability/public/hooks/use_fetcher.tsx +++ b/x-pack/plugins/observability/public/hooks/use_fetcher.tsx @@ -18,6 +18,7 @@ export interface FetcherResult { data?: Data; status: FETCH_STATUS; error?: Error; + loading?: boolean; } // fetcher functions can return undefined OR a promise. Previously we had a more simple type @@ -38,6 +39,7 @@ export function useFetcher( const [result, setResult] = useState>>({ data: undefined, status: FETCH_STATUS.PENDING, + loading: true, }); const [counter, setCounter] = useState(0); useEffect(() => { @@ -51,6 +53,7 @@ export function useFetcher( data: preservePreviousData ? prevResult.data : undefined, status: FETCH_STATUS.LOADING, error: undefined, + loading: true, })); try { @@ -65,6 +68,7 @@ export function useFetcher( data: preservePreviousData ? prevResult.data : undefined, status: FETCH_STATUS.FAILURE, error: e, + loading: false, })); } } @@ -76,6 +80,7 @@ export function useFetcher( return useMemo(() => { return { ...result, + loading: result.status === FETCH_STATUS.LOADING || result.status === FETCH_STATUS.PENDING, refetch: () => { // this will invalidate the deps to `useEffect` and will result in a new request setCounter((count) => count + 1); From 238791b9421cda571011fd416ae0ffd090b1e94d Mon Sep 17 00:00:00 2001 From: Tomas Della Vedova Date: Thu, 25 Mar 2021 09:47:16 +0100 Subject: [PATCH 88/93] ES client : use the new type definitions (#83808) * Use client from branch * Get type checking working in core * Fix types in other plugins * Update client types + remove type errors from core * migrate Task Manager Elasticsearch typing from legacy library to client library * use SortOrder instead o string in alerts * Update client types + fix core type issues * fix maps ts errors * Update Lens types * Convert Search Profiler body from a string to an object to conform to SearchRequest type. * Fix SOT types * Fix/mute Security/Spaces plugins type errors. * Fix bootstrap types * Fix painless_lab * corrected es typing in Event Log * Use new types from client for inferred search responses * Latest type defs * Integrate latest type defs for APM/UX * fix core errors * fix telemetry errors * fix canvas errors * fix data_enhanced errors * fix event_log errors * mute lens errors * fix or mute maps errors * fix reporting errors * fix security errors * mute errors in task_manager * fix errors in telemetry_collection_xpack * fix errors in data plugins * fix errors in alerts * mute errors in index_management * fix task_manager errors * mute or fix lens errors * fix upgrade_assistant errors * fix or mute errors in index_lifecycle_management * fix discover errors * fix core tests * ML changes * fix core type errors * mute error in kbn-es-archiver * fix error in data plugin * fix error in telemetry plugin * fix error in discover * fix discover errors * fix errors in task_manager * fix security errors * fix wrong conflict resolution * address errors with upstream code * update deps to the last commit * remove outdated comments * fix core errors * fix errors after update * adding more expect errors to ML * pull the lastest changes * fix core errors * fix errors in infra plugin * fix errors in uptime plugin * fix errors in ml * fix errors in xpack telemetry * fix or mute errors in transform * fix errors in upgrade assistant * fix or mute fleet errors * start fixing apm errors * fix errors in osquery * fix telemetry tests * core cleanup * fix asMutableArray imports * cleanup * data_enhanced cleanup * cleanup events_log * cleaup * fix error in kbn-es-archiver * fix errors in kbn-es-archiver * fix errors in kbn-es-archiver * fix ES typings for Hit * fix SO * fix actions plugin * fix fleet * fix maps * fix stack_alerts * fix eslint problems * fix event_log unit tests * fix failures in data_enhanced tests * fix test failure in kbn-es-archiver * fix test failures in index_pattern_management * fixing ML test * remove outdated comment in kbn-es-archiver * fix error type in ml * fix eslint errors in osquery plugin * fix runtime error in infra plugin * revert changes to event_log cluser exist check * fix eslint error in osquery * fixing ML endpoint argument types * fx types * Update api-extractor docs * attempt fix for ese test * Fix lint error * Fix types for ts refs * Fix data_enhanced unit test * fix lens types * generate docs * Fix a number of type issues in monitoring and ml * fix triggers_actions_ui * Fix ILM functional test * Put search.d.ts typings back * fix data plugin * Update typings in typings/elasticsearch * Update snapshots * mute errors in task_manager * mute fleet errors * lens. remove unnecessary ts-expect-errors * fix errors in stack_alerts * mute errors in osquery * fix errors in security_solution * fix errors in lists * fix errors in cases * mute errors in search_examples * use KibanaClient to enforce promise-based API * fix errors in test/ folder * update comment * fix errors in x-pack/test folder * fix errors in ml plugin * fix optional fields in ml api_integartoon tests * fix another casting problem in ml tests * fix another ml test failure * fix fleet problem after conflict resolution * rollback changes in security_solution. trying to fix test * Update type for discover rows * uncomment runtime_mappings as its outdated * address comments from Wylie * remove eslint error due to any * mute error due to incompatibility * Apply suggestions from code review Co-authored-by: John Schulz * fix type error in lens tests * Update x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts Co-authored-by: Alison Goryachev * Update x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts Co-authored-by: Alison Goryachev * update deps * fix errors in core types * fix errors for the new elastic/elasticsearch version * remove unused type * remove unnecessary manual type cast and put optional chaining back * ML: mute Datafeed is missing indices_options * Apply suggestions from code review Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com> * use canary pacakge instead of git commit Co-authored-by: Josh Dover Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com> Co-authored-by: Gidi Meir Morris Co-authored-by: Nathan Reese Co-authored-by: Wylie Conlon Co-authored-by: CJ Cenizal Co-authored-by: Aleh Zasypkin Co-authored-by: Dario Gieselaar Co-authored-by: restrry Co-authored-by: James Gowdy Co-authored-by: John Schulz Co-authored-by: Alison Goryachev --- ...gin-core-public.savedobjectsfindoptions.md | 4 +- ...lic.savedobjectsfindoptions.searchafter.md | 2 +- ...ublic.savedobjectsfindoptions.sortorder.md | 2 +- ...gin-core-server.savedobjectsfindoptions.md | 4 +- ...ver.savedobjectsfindoptions.searchafter.md | 2 +- ...erver.savedobjectsfindoptions.sortorder.md | 2 +- ...ugin-core-server.savedobjectsfindresult.md | 2 +- ...core-server.savedobjectsfindresult.sort.md | 2 +- ...n-plugins-data-public.iessearchresponse.md | 2 +- ...-plugins-data-public.searchsource.fetch.md | 4 +- ...n-plugins-data-server.iessearchresponse.md | 2 +- .../search_examples/public/search/app.tsx | 3 +- .../public/search_sessions/app.tsx | 4 +- package.json | 2 +- .../src/actions/empty_kibana_index.ts | 4 +- packages/kbn-es-archiver/src/actions/load.ts | 4 +- packages/kbn-es-archiver/src/actions/save.ts | 4 +- .../kbn-es-archiver/src/actions/unload.ts | 4 +- .../kbn-es-archiver/src/client_headers.ts | 2 +- packages/kbn-es-archiver/src/es_archiver.ts | 6 +- .../lib/docs/generate_doc_records_stream.ts | 4 +- .../src/lib/docs/index_doc_records_stream.ts | 4 +- .../src/lib/indices/__mocks__/stubs.ts | 4 +- .../lib/indices/create_index_stream.test.ts | 1 - .../src/lib/indices/create_index_stream.ts | 11 +- .../src/lib/indices/delete_index.ts | 6 +- .../src/lib/indices/delete_index_stream.ts | 4 +- .../indices/generate_index_records_stream.ts | 4 +- .../src/lib/indices/kibana_index.ts | 31 +- src/core/public/public.api.md | 5 +- .../core_usage_data_service.test.ts | 16 +- .../core_usage_data_service.ts | 12 +- src/core/server/elasticsearch/client/mocks.ts | 6 +- .../client/retry_call_cluster.test.ts | 6 +- .../export/saved_objects_exporter.test.ts | 7 +- .../import/lib/check_origin_conflicts.ts | 2 +- .../server/saved_objects/mappings/types.ts | 2 +- .../core/build_active_mappings.test.ts | 1 + .../migrations/core/call_cluster.ts | 14 +- .../migrations/core/elastic_index.test.ts | 72 +-- .../migrations/core/elastic_index.ts | 15 +- .../migrations/core/index_migrator.test.ts | 16 +- .../migrations/core/index_migrator.ts | 12 +- .../migrations/kibana/kibana_migrator.test.ts | 26 +- .../migrationsv2/actions/index.ts | 93 ++- .../integration_tests/actions.test.ts | 6 +- .../integration_tests/migration.test.ts | 8 +- .../saved_objects/migrationsv2/model.ts | 1 - .../service/lib/included_fields.ts | 5 +- .../service/lib/point_in_time_finder.ts | 14 +- .../service/lib/repository.test.js | 27 +- .../saved_objects/service/lib/repository.ts | 169 +++-- .../service/lib/search_dsl/search_dsl.test.ts | 10 +- .../service/lib/search_dsl/search_dsl.ts | 7 +- .../service/lib/search_dsl/sorting_params.ts | 5 +- .../service/saved_objects_client.ts | 2 +- src/core/server/saved_objects/types.ts | 5 +- .../version/encode_hit_version.ts | 2 +- .../saved_objects/version/encode_version.ts | 2 +- src/core/server/server.api.md | 7 +- .../data/common/search/es_search/types.ts | 9 +- .../utils/courier_inspector_stats.ts | 4 +- .../search_source/fetch/request_error.ts | 6 +- .../search/search_source/fetch/types.ts | 7 +- .../search_source/legacy/call_client.ts | 4 +- .../search/search_source/legacy/fetch_soon.ts | 8 +- .../search/search_source/legacy/types.ts | 7 +- src/plugins/data/public/public.api.md | 9 +- .../search/expressions/es_raw_response.ts | 8 +- .../public/search/fetch/handle_response.tsx | 4 +- .../shard_failure_modal.tsx | 4 +- .../shard_failure_open_modal_button.tsx | 4 +- .../autocomplete/value_suggestions_route.ts | 7 +- .../index_patterns/fetcher/lib/es_api.ts | 13 +- .../field_capabilities/field_caps_response.ts | 18 +- .../fetcher/lib/field_capabilities/index.ts | 1 - .../fetcher/lib/resolve_time_pattern.ts | 4 +- .../data/server/search/collectors/fetch.ts | 6 +- .../search/es_search/es_search_strategy.ts | 3 +- .../server/search/es_search/response_utils.ts | 8 +- .../data/server/search/routes/call_msearch.ts | 6 +- src/plugins/data/server/server.api.md | 42 +- .../doc_table/create_doc_table_react.tsx | 3 +- .../context_app/context_app_legacy.tsx | 1 + .../discover_grid/discover_grid_flyout.tsx | 2 +- .../get_render_cell_value.test.tsx | 21 +- .../components/table/table.test.tsx | 4 +- .../application/doc_views/doc_views_types.ts | 4 +- .../embeddable/search_embeddable.ts | 2 +- src/plugins/embeddable/public/public.api.md | 1 + .../routes/preview_scripted_field.test.ts | 20 +- .../server/routes/preview_scripted_field.ts | 6 +- .../kibana/get_saved_object_counts.ts | 1 + .../server/check_cluster_data.test.ts | 4 + .../security_oss/server/check_cluster_data.ts | 8 +- .../telemetry_collection/get_cluster_info.ts | 18 +- .../get_data_telemetry/get_data_telemetry.ts | 8 +- .../get_local_stats.test.ts | 49 +- .../telemetry_collection/get_local_stats.ts | 5 +- .../telemetry_collection/get_nodes_usage.ts | 5 +- .../vis_type_vega/public/data_model/types.ts | 6 +- .../usage_collector/get_usage_collector.ts | 6 +- .../usage_collector/get_usage_collector.ts | 2 +- test/api_integration/apis/home/sample_data.ts | 8 +- .../apis/saved_objects/migrations.ts | 8 +- .../telemetry/telemetry_optin_notice_seen.ts | 8 +- .../apis/ui_metric/ui_metric.ts | 4 +- test/common/services/elasticsearch.ts | 6 +- typings/elasticsearch/aggregations.d.ts | 466 -------------- typings/elasticsearch/index.d.ts | 128 +--- typings/elasticsearch/search.d.ts | 577 ++++++++++++++++++ .../actions/server/actions_client.test.ts | 6 + .../plugins/actions/server/actions_client.ts | 4 +- .../server/builtin_action_types/es_index.ts | 4 +- .../server/usage/actions_telemetry.test.ts | 2 + .../actions/server/usage/actions_telemetry.ts | 22 +- .../server/alerts_client/alerts_client.ts | 3 +- .../server/usage/alerts_telemetry.test.ts | 1 + .../alerting/server/usage/alerts_telemetry.ts | 58 +- .../VisitorBreakdownMap/EmbeddedMap.test.tsx | 2 +- .../create-functional-tests-archive/index.ts | 3 +- .../scripts/upload-telemetry-data/index.ts | 10 +- .../server/lib/alerts/alerting_es_client.ts | 4 +- .../chart_preview/get_transaction_duration.ts | 6 +- .../register_error_count_alert_type.test.ts | 30 + ..._transaction_error_rate_alert_type.test.ts | 40 ++ .../collect_data_telemetry/index.ts | 4 +- .../collect_data_telemetry/tasks.ts | 15 +- .../apm/server/lib/apm_telemetry/index.ts | 2 +- .../index.ts | 2 +- .../get_latency_distribution.ts | 2 +- .../index.ts | 6 +- .../process_significant_term_aggs.ts | 2 +- .../lib/errors/get_error_group_sample.ts | 5 +- .../apm/server/lib/errors/get_error_groups.ts | 3 +- .../create_apm_event_client/index.ts | 4 +- .../create_internal_es_client/index.ts | 12 +- .../server/lib/helpers/setup_request.test.ts | 12 +- .../lib/helpers/transaction_error_rate.ts | 2 +- .../java/gc/fetch_and_transform_gc_metrics.ts | 8 +- .../rum_client/get_page_load_distribution.ts | 16 +- .../lib/rum_client/get_pl_dist_breakdown.ts | 6 +- .../lib/rum_client/get_web_core_vitals.ts | 2 +- .../lib/service_map/get_service_anomalies.ts | 7 +- .../lib/service_map/get_trace_sample_ids.ts | 5 +- .../__snapshots__/queries.test.ts.snap | 16 +- .../annotations/get_stored_annotations.ts | 8 +- .../get_destination_map.ts | 17 +- .../get_service_error_groups/index.ts | 4 +- .../services/get_service_metadata_details.ts | 3 +- .../get_service_transaction_stats.ts | 4 +- .../get_services_from_metric_documents.ts | 2 +- .../convert_settings_to_string.ts | 4 +- .../create_agent_config_index.ts | 4 +- .../find_exact_configuration.ts | 6 +- .../search_configurations.ts | 6 +- .../custom_link/create_custom_link_index.ts | 8 +- .../settings/custom_link/get_transaction.ts | 19 +- .../settings/custom_link/list_custom_links.ts | 5 +- .../apm/server/lib/traces/get_trace_items.ts | 3 +- .../__snapshots__/queries.test.ts.snap | 16 +- .../server/lib/transaction_groups/fetcher.ts | 5 +- .../get_transaction_group_stats.ts | 16 +- .../distribution/get_buckets/index.ts | 8 +- .../transactions/get_anomaly_data/fetcher.ts | 8 +- .../lib/transactions/get_transaction/index.ts | 5 +- .../plugins/apm/server/projections/metrics.ts | 3 +- .../projections/rum_page_load_transactions.ts | 8 +- .../apm/server/projections/transactions.ts | 3 +- .../plugins/apm/server/projections/typings.ts | 15 +- .../util/merge_projection/index.test.ts | 6 +- .../util/merge_projection/index.ts | 10 +- .../collectors/custom_element_collector.ts | 7 +- .../server/collectors/workpad_collector.ts | 6 +- .../cases/server/services/alerts/index.ts | 3 +- .../data_enhanced/server/collectors/fetch.ts | 3 +- .../data_enhanced/server/routes/session.ts | 2 +- .../server/search/eql_search_strategy.ts | 11 +- .../server/search/es_search_strategy.test.ts | 2 +- .../server/search/es_search_strategy.ts | 13 +- .../search/session/get_search_status.ts | 1 + .../data_enhanced/server/search/types.ts | 3 +- .../server/es/cluster_client_adapter.test.ts | 92 ++- .../server/es/cluster_client_adapter.ts | 129 ++-- .../file_upload/server/analyze_file.tsx | 13 +- .../server/routes/data_streams/handlers.ts | 1 + .../fleet/server/services/agents/actions.ts | 2 +- .../fleet/server/services/agents/crud.ts | 28 +- .../fleet/server/services/agents/helpers.ts | 15 +- .../server/services/agents/reassign.test.ts | 4 +- .../server/services/agents/status.test.ts | 8 +- .../server/services/agents/unenroll.test.ts | 6 +- .../services/api_keys/enrollment_api_key.ts | 9 +- .../server/services/artifacts/artifacts.ts | 10 +- .../server/services/artifacts/mappings.ts | 4 +- .../fleet/server/services/artifacts/mocks.ts | 4 +- .../epm/elasticsearch/template/template.ts | 1 + .../epm/elasticsearch/transform/install.ts | 1 + .../epm/elasticsearch/transform/remove.ts | 1 + .../fleet_server/saved_object_migrations.ts | 1 + .../api/index/register_add_policy_route.ts | 1 + .../api/policies/register_delete_route.ts | 1 + .../api/policies/register_fetch_route.ts | 1 + .../templates/register_add_policy_route.ts | 10 +- .../api/data_streams/register_get_route.ts | 4 +- .../public/utils/logs_overview_fetchers.ts | 6 +- .../framework/kibana_framework_adapter.ts | 4 +- .../evaluate_condition.ts | 1 + .../metric_threshold/lib/evaluate_alert.ts | 8 +- .../log_entries_search_strategy.ts | 1 + .../log_entries/log_entry_search_strategy.ts | 1 + .../log_entries/queries/log_entries.ts | 5 +- .../services/log_entries/queries/log_entry.ts | 5 +- .../server/routes/existing_fields.test.ts | 10 +- .../lens/server/routes/existing_fields.ts | 18 +- .../plugins/lens/server/routes/field_stats.ts | 17 +- x-pack/plugins/lens/server/usage/task.ts | 3 + .../lens/server/usage/visualization_counts.ts | 1 + .../services/items/create_list_item.test.ts | 2 + .../server/services/items/create_list_item.ts | 3 +- .../services/items/find_list_item.test.ts | 2 + .../server/services/items/find_list_item.ts | 7 +- .../server/services/items/get_list_item.ts | 4 +- .../services/items/update_list_item.test.ts | 1 + .../server/services/items/update_list_item.ts | 3 +- .../items/write_list_items_to_stream.test.ts | 2 + .../items/write_list_items_to_stream.ts | 12 +- .../server/services/lists/create_list.test.ts | 3 + .../server/services/lists/create_list.ts | 3 +- .../lists/server/services/lists/find_list.ts | 5 +- .../lists/server/services/lists/get_list.ts | 3 +- .../server/services/lists/update_list.test.ts | 2 + .../server/services/lists/update_list.ts | 3 +- .../services/utils/get_search_after_scroll.ts | 4 +- .../get_search_after_with_tie_breaker.ts | 15 +- .../utils/get_sort_with_tie_breaker.ts | 14 +- ...sform_elastic_named_search_to_list_item.ts | 4 +- .../utils/transform_elastic_to_list.ts | 5 +- .../utils/transform_elastic_to_list_item.ts | 35 +- .../classes/sources/es_source/es_source.ts | 22 +- x-pack/plugins/maps/server/mvt/get_tile.ts | 2 + .../types/anomaly_detection_jobs/datafeed.ts | 69 ++- .../types/anomaly_detection_jobs/job.ts | 141 +++-- x-pack/plugins/ml/common/types/fields.ts | 3 +- x-pack/plugins/ml/common/util/job_utils.ts | 4 +- .../plugins/ml/common/util/parse_interval.ts | 5 +- .../components/data_grid/common.ts | 1 + .../chart_loader.ts | 1 + .../job_creator/advanced_job_creator.ts | 10 +- .../new_job/common/job_creator/job_creator.ts | 16 +- .../job_creator/util/default_configs.ts | 2 + .../util/filter_runtime_mappings.test.ts | 3 + .../categorization_examples_loader.ts | 1 + .../common/results_loader/results_loader.ts | 1 + .../advanced_detector_modal.tsx | 6 +- .../estimate_bucket_span.ts | 1 + .../metric_selection_summary.tsx | 1 + .../multi_metric_view/metric_selection.tsx | 2 + .../metric_selection_summary.tsx | 2 + .../population_view/metric_selection.tsx | 2 + .../metric_selection_summary.tsx | 2 + .../single_metric_view/metric_selection.tsx | 1 + .../metric_selection_summary.tsx | 1 + .../components/time_range_step/time_range.tsx | 1 + .../public/application/util/string_utils.ts | 2 +- .../ml/server/lib/alerts/alerting_service.ts | 4 +- .../ml/server/lib/ml_client/ml_client.ts | 32 +- .../plugins/ml/server/lib/ml_client/search.ts | 9 +- x-pack/plugins/ml/server/lib/node_utils.ts | 2 +- x-pack/plugins/ml/server/lib/query_utils.ts | 6 +- .../models/annotation_service/annotation.ts | 2 + .../calculate_model_memory_limit.ts | 11 +- .../models/calendar/calendar_manager.ts | 18 +- .../server/models/calendar/event_manager.ts | 13 +- .../ml/server/models/calendar/index.ts | 1 - .../analytics_audit_messages.ts | 6 +- .../models/data_frame_analytics/validation.ts | 5 +- .../models/data_recognizer/data_recognizer.ts | 5 +- .../models/data_visualizer/data_visualizer.ts | 3 + .../models/fields_service/fields_service.ts | 8 + .../ml/server/models/filter/filter_manager.ts | 56 +- .../ml/server/models/job_service/datafeeds.ts | 26 +- .../ml/server/models/job_service/jobs.ts | 26 +- .../models/job_service/model_snapshots.ts | 11 +- .../new_job/categorization/examples.ts | 28 +- .../new_job/categorization/top_categories.ts | 2 + .../job_service/new_job_caps/field_service.ts | 11 +- .../models/job_service/new_job_caps/rollup.ts | 5 +- .../models/job_validation/job_validation.ts | 1 + .../validate_model_memory_limit.test.ts | 51 +- .../get_partition_fields_values.ts | 3 +- .../models/results_service/results_service.ts | 5 + .../ml/server/routes/anomaly_detectors.ts | 28 +- .../ml/server/routes/data_frame_analytics.ts | 9 +- x-pack/plugins/ml/server/routes/datafeeds.ts | 18 +- .../ml/server/routes/trained_models.ts | 5 +- .../plugins/ml/server/saved_objects/checks.ts | 16 +- .../shared_services/providers/system.ts | 11 +- .../lib/alerts/fetch_available_ccs.test.ts | 5 +- .../lib/alerts/fetch_ccr_read_exceptions.ts | 5 +- .../lib/alerts/fetch_cluster_health.test.ts | 3 +- .../server/lib/alerts/fetch_cluster_health.ts | 12 +- .../server/lib/alerts/fetch_clusters.test.ts | 5 +- .../alerts/fetch_cpu_usage_node_stats.test.ts | 8 +- .../lib/alerts/fetch_cpu_usage_node_stats.ts | 4 +- .../fetch_disk_usage_node_stats.test.ts | 1 + .../lib/alerts/fetch_disk_usage_node_stats.ts | 3 +- .../fetch_elasticsearch_versions.test.ts | 3 +- .../alerts/fetch_elasticsearch_versions.ts | 14 +- .../lib/alerts/fetch_index_shard_size.ts | 5 +- .../lib/alerts/fetch_kibana_versions.test.ts | 1 + .../lib/alerts/fetch_kibana_versions.ts | 2 +- .../server/lib/alerts/fetch_licenses.test.ts | 3 +- .../server/lib/alerts/fetch_licenses.ts | 12 +- .../alerts/fetch_logstash_versions.test.ts | 1 + .../lib/alerts/fetch_logstash_versions.ts | 2 +- .../alerts/fetch_memory_usage_node_stats.ts | 1 + .../fetch_missing_monitoring_data.test.ts | 2 + .../alerts/fetch_missing_monitoring_data.ts | 4 +- .../alerts/fetch_nodes_from_cluster_stats.ts | 9 +- .../fetch_thread_pool_rejections_stats.ts | 9 +- x-pack/plugins/observability/server/index.ts | 4 +- .../annotations/create_annotations_client.ts | 29 +- .../server/lib/annotations/mappings.ts | 4 +- .../server/utils/create_or_update_index.ts | 31 +- .../search_strategy/osquery/actions/index.ts | 6 +- .../search_strategy/osquery/results/index.ts | 4 +- .../action_results/action_results_table.tsx | 9 +- .../osquery/public/actions/actions_table.tsx | 1 + .../osquery/public/results/results_table.tsx | 2 + .../osquery/factory/actions/all/index.ts | 1 + .../osquery/factory/actions/results/index.ts | 1 + .../osquery/factory/agents/index.ts | 6 +- .../osquery/factory/results/index.ts | 1 + .../painless_lab/server/routes/api/execute.ts | 1 + .../generate_csv/generate_csv.ts | 2 +- .../server/usage/get_reporting_usage.ts | 9 +- .../server/usage/fetch_tag_usage_data.ts | 32 +- .../searchprofiler/server/routes/profile.ts | 13 +- .../common/model/authenticated_user.mock.ts | 6 +- .../authentication/api_keys/api_keys.test.ts | 27 +- .../authentication/api_keys/api_keys.ts | 37 +- .../server/authentication/providers/base.ts | 3 +- .../authentication/providers/kerberos.test.ts | 4 +- .../authentication/providers/kerberos.ts | 8 +- .../authentication/providers/oidc.test.ts | 3 +- .../authentication/providers/saml.test.ts | 3 +- .../authentication/providers/token.test.ts | 1 + .../server/authentication/providers/token.ts | 17 +- .../server/authentication/tokens.test.ts | 39 +- .../security/server/authentication/tokens.ts | 18 +- .../authorization/check_privileges.test.ts | 18 +- .../server/authorization/check_privileges.ts | 32 +- .../authorization/privileges/get_builtin.ts | 3 +- .../server/routes/authorization/roles/get.ts | 8 +- .../routes/authorization/roles/get_all.ts | 7 +- .../roles/model/elasticsearch_role.ts | 2 +- .../server/routes/authorization/roles/put.ts | 10 +- .../server/routes/indices/get_fields.ts | 36 +- .../server/routes/role_mapping/get.ts | 9 +- .../security/server/routes/users/get.ts | 4 +- .../session_management/session_index.test.ts | 70 ++- .../session_management/session_index.ts | 17 +- .../common/search_strategy/common/index.ts | 7 +- .../security_solution/index.ts | 12 +- .../public/common/containers/source/index.tsx | 2 +- .../server/endpoint/routes/resolver/entity.ts | 8 +- .../routes/resolver/queries/events.ts | 11 +- .../resolver/tree/queries/descendants.ts | 6 +- .../routes/resolver/tree/queries/lifecycle.ts | 5 +- .../routes/resolver/tree/queries/stats.ts | 5 +- .../index/delete_all_index.ts | 1 + .../index/get_index_exists.test.ts | 2 + .../index/get_index_exists.ts | 4 +- .../migrations/create_migration.ts | 2 +- .../get_index_versions_by_index.test.ts | 2 +- .../get_signal_versions_by_index.test.ts | 2 +- .../get_signal_versions_by_index.ts | 5 +- .../get_signals_indices_in_range.ts | 7 +- .../notifications/get_signals.ts | 4 +- .../rules_notification_alert_type.ts | 1 + .../routes/index/get_index_version.ts | 13 +- .../signals/__mocks__/es_results.ts | 4 +- .../signals/build_bulk_body.test.ts | 12 + .../signals/build_bulk_body.ts | 4 +- .../signals/build_event_type_signal.test.ts | 3 + .../signals/build_event_type_signal.ts | 7 +- .../signals/build_events_query.ts | 12 +- .../signals/build_rule.test.ts | 6 + .../detection_engine/signals/build_rule.ts | 3 + .../signals/build_signal.test.ts | 8 + .../detection_engine/signals/build_signal.ts | 16 +- .../signals/bulk_create_ml_signals.ts | 13 +- .../signals/filters/filter_events.ts | 5 +- .../filters/filter_events_against_list.ts | 11 +- .../detection_engine/signals/filters/types.ts | 11 +- .../build_risk_score_from_mapping.test.ts | 1 + .../build_rule_name_from_mapping.test.ts | 1 + .../build_severity_from_mapping.test.ts | 1 + .../signals/search_after_bulk_create.test.ts | 17 + .../signals/search_after_bulk_create.ts | 4 + .../signals/send_telemetry_events.ts | 3 +- .../signals/signal_rule_alert_type.test.ts | 6 +- .../signals/single_bulk_create.test.ts | 3 + .../signals/single_bulk_create.ts | 12 +- .../signals/single_search_after.test.ts | 5 +- .../signals/single_search_after.ts | 12 +- .../build_threat_mapping_filter.mock.ts | 5 +- .../threat_mapping/create_threat_signals.ts | 1 + .../enrich_signal_threat_matches.ts | 2 +- .../signals/threat_mapping/get_threat_list.ts | 17 +- .../signals/threat_mapping/types.ts | 6 +- .../bulk_create_threshold_signals.ts | 8 +- .../threshold/find_threshold_signals.ts | 5 +- .../threshold/get_threshold_bucket_filters.ts | 2 +- .../lib/detection_engine/signals/types.ts | 10 +- .../detection_engine/signals/utils.test.ts | 14 +- .../lib/detection_engine/signals/utils.ts | 16 +- .../server/lib/machine_learning/index.ts | 11 +- .../factory/hosts/all/query.all_hosts.dsl.ts | 2 +- .../hosts/authentications/dsl/query.dsl.ts | 16 +- .../query.hosts_kpi_authentications.dsl.ts | 4 +- .../kpi/hosts/query.hosts_kpi_hosts.dsl.ts | 2 +- .../query.hosts_kpi_unique_ips.dsl.ts | 4 +- .../factory/hosts/overview/index.ts | 1 + .../hosts/overview/query.overview_host.dsl.ts | 3 +- .../hosts/uncommon_processes/dsl/query.dsl.ts | 20 +- .../factory/matrix_histogram/index.ts | 1 + .../network/details/__mocks__/index.ts | 20 +- .../details/query.details_network.dsl.ts | 6 +- .../factory/network/dns/index.ts | 1 + .../factory/network/http/index.ts | 1 + .../factory/network/kpi/dns/index.ts | 1 + .../network/kpi/network_events/index.ts | 1 + .../network/kpi/tls_handshakes/index.ts | 1 + .../network/kpi/unique_private_ips/index.ts | 1 + .../factory/network/overview/index.ts | 1 + .../timeline/factory/events/all/index.ts | 1 + .../timeline/factory/events/details/index.ts | 1 + .../usage/detections/detections_helpers.ts | 10 +- .../spaces_usage_collector.ts | 1 + .../common/build_sorted_events_query.ts | 15 +- .../alert_types/es_query/expression.tsx | 8 +- .../alert_types/es_query/action_context.ts | 4 +- .../alert_types/es_query/alert_type.test.ts | 8 + .../server/alert_types/es_query/alert_type.ts | 10 +- .../geo_containment/es_query_builder.ts | 7 +- .../geo_containment/geo_containment.ts | 8 +- .../monitoring/workload_statistics.test.ts | 167 ++--- .../server/monitoring/workload_statistics.ts | 102 +++- .../mark_available_tasks_as_claimed.test.ts | 39 +- .../mark_available_tasks_as_claimed.ts | 37 +- .../server/queries/query_clauses.test.ts | 21 +- .../server/queries/query_clauses.ts | 214 +------ .../server/queries/task_claiming.test.ts | 8 +- .../server/queries/task_claiming.ts | 11 +- .../task_manager/server/task_store.test.ts | 23 +- .../plugins/task_manager/server/task_store.ts | 44 +- .../telemetry_collection/get_license.ts | 21 +- .../get_stats_with_xpack.test.ts | 5 +- .../server/routes/api/field_histograms.ts | 1 + .../transform/server/routes/api/privileges.ts | 5 +- .../transform/server/routes/api/transforms.ts | 10 +- .../routes/api/transforms_audit_messages.ts | 21 +- .../server/data/lib/time_series_query.ts | 9 +- .../server/data/routes/indices.ts | 13 +- .../server/lib/es_indices_state_check.ts | 4 +- .../server/lib/es_migration_apis.test.ts | 4 + .../server/lib/es_migration_apis.ts | 4 +- .../lib/reindexing/reindex_actions.test.ts | 1 + .../lib/reindexing/reindex_service.test.ts | 25 +- .../server/lib/reindexing/reindex_service.ts | 19 +- .../uptime/common/utils/as_mutable_array.ts | 41 ++ x-pack/plugins/uptime/server/lib/lib.ts | 6 +- .../uptime/server/lib/requests/get_certs.ts | 6 +- .../lib/requests/get_journey_details.ts | 9 +- .../lib/requests/get_journey_failed_steps.ts | 12 +- .../lib/requests/get_journey_screenshot.ts | 3 +- .../server/lib/requests/get_journey_steps.ts | 20 +- .../lib/requests/get_last_successful_step.ts | 3 +- .../server/lib/requests/get_latest_monitor.ts | 7 +- .../lib/requests/get_monitor_availability.ts | 10 +- .../lib/requests/get_monitor_details.ts | 5 +- .../lib/requests/get_monitor_duration.ts | 3 +- .../lib/requests/get_monitor_locations.ts | 5 +- .../server/lib/requests/get_monitor_states.ts | 2 +- .../server/lib/requests/get_monitor_status.ts | 8 +- .../server/lib/requests/get_network_events.ts | 3 +- .../server/lib/requests/get_ping_histogram.ts | 4 +- .../uptime/server/lib/requests/get_pings.ts | 5 +- .../uptime/server/lib/requests/helper.ts | 3 +- .../requests/search/find_potential_matches.ts | 9 +- .../apps/index_lifecycle_management.ts | 1 + x-pack/test/accessibility/apps/ml.ts | 5 +- .../api_integration/apis/es/has_privileges.ts | 1 + .../api_integration/apis/lens/telemetry.ts | 5 +- .../apis/ml/annotations/create_annotations.ts | 1 + .../apis/ml/annotations/delete_annotations.ts | 1 + .../apis/ml/annotations/get_annotations.ts | 1 + .../apis/ml/annotations/update_annotations.ts | 9 +- .../apis/ml/anomaly_detectors/get.ts | 1 + .../apis/ml/calendars/create_calendars.ts | 6 +- .../apis/ml/calendars/delete_calendars.ts | 1 + .../apis/ml/calendars/get_calendars.ts | 6 + .../apis/ml/calendars/helpers.ts | 9 +- .../apis/ml/calendars/update_calendars.ts | 2 + .../apis/ml/jobs/common_jobs.ts | 3 + .../ml/results/get_anomalies_table_data.ts | 2 + .../apis/ml/results/get_categorizer_stats.ts | 2 + .../apis/ml/results/get_stopped_partitions.ts | 2 + .../api_integration/apis/security/roles.ts | 1 + .../apis/telemetry/telemetry_local.ts | 2 +- .../telemetry/telemetry_optin_notice_seen.ts | 8 +- .../basic/tests/cases/patch_cases.ts | 26 +- .../tests/cases/sub_cases/patch_sub_cases.ts | 36 +- .../case_api_integration/common/lib/utils.ts | 52 +- .../tests/create_signals_migrations.ts | 1 + .../detection_engine_api_integration/utils.ts | 13 +- .../fleet_api_integration/apis/agents/acks.ts | 5 +- .../apis/agents/checkin.ts | 1 + .../apis/agents/unenroll.ts | 4 +- .../fleet_api_integration/apis/fleet_setup.ts | 1 + .../aggregated_scripted_job.ts | 4 + .../apps/ml/anomaly_detection/annotations.ts | 2 + .../ml/anomaly_detection/anomaly_explorer.ts | 2 + .../anomaly_detection/single_metric_viewer.ts | 4 + .../apps/ml/permissions/full_ml_access.ts | 5 +- .../apps/ml/permissions/read_ml_access.ts | 5 +- .../apps/ml/settings/calendar_creation.ts | 1 + .../apps/ml/settings/calendar_edit.ts | 2 + x-pack/test/functional/services/ml/api.ts | 85 ++- .../functional/services/ml/common_config.ts | 3 + .../apps/ml/alert_flyout.ts | 2 + x-pack/test/lists_api_integration/utils.ts | 5 +- .../trial/tests/annotations.ts | 11 +- .../common/lib/create_users_and_roles.ts | 6 +- .../tests/session_idle/cleanup.ts | 6 +- .../tests/session_lifespan/cleanup.ts | 6 +- .../apis/package.ts | 21 +- .../services/resolver.ts | 13 +- yarn.lock | 29 +- 541 files changed, 3666 insertions(+), 3051 deletions(-) delete mode 100644 typings/elasticsearch/aggregations.d.ts create mode 100644 typings/elasticsearch/search.d.ts create mode 100644 x-pack/plugins/uptime/common/utils/as_mutable_array.ts diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md index 69cfb818561e5d..7be45c6c173b45 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md @@ -27,10 +27,10 @@ export interface SavedObjectsFindOptions | [preference](./kibana-plugin-core-public.savedobjectsfindoptions.preference.md) | string | An optional ES preference value to be used for the query \* | | [rootSearchFields](./kibana-plugin-core-public.savedobjectsfindoptions.rootsearchfields.md) | string[] | The fields to perform the parsed query against. Unlike the searchFields argument, these are expected to be root fields and will not be modified. If used in conjunction with searchFields, both are concatenated together. | | [search](./kibana-plugin-core-public.savedobjectsfindoptions.search.md) | string | Search documents using the Elasticsearch Simple Query String syntax. See Elasticsearch Simple Query String query argument for more information | -| [searchAfter](./kibana-plugin-core-public.savedobjectsfindoptions.searchafter.md) | unknown[] | Use the sort values from the previous page to retrieve the next page of results. | +| [searchAfter](./kibana-plugin-core-public.savedobjectsfindoptions.searchafter.md) | estypes.Id[] | Use the sort values from the previous page to retrieve the next page of results. | | [searchFields](./kibana-plugin-core-public.savedobjectsfindoptions.searchfields.md) | string[] | The fields to perform the parsed query against. See Elasticsearch Simple Query String fields argument for more information | | [sortField](./kibana-plugin-core-public.savedobjectsfindoptions.sortfield.md) | string | | -| [sortOrder](./kibana-plugin-core-public.savedobjectsfindoptions.sortorder.md) | string | | +| [sortOrder](./kibana-plugin-core-public.savedobjectsfindoptions.sortorder.md) | estypes.SortOrder | | | [type](./kibana-plugin-core-public.savedobjectsfindoptions.type.md) | string | string[] | | | [typeToNamespacesMap](./kibana-plugin-core-public.savedobjectsfindoptions.typetonamespacesmap.md) | Map<string, string[] | undefined> | This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved object client wrapper. If this is defined, it supersedes the type and namespaces fields when building the Elasticsearch query. Any types that are not included in this map will be excluded entirely. If a type is included but its value is undefined, the operation will search for that type in the Default namespace. | diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.searchafter.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.searchafter.md index 99ca2c34e77be9..7016e1f1b72de5 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.searchafter.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.searchafter.md @@ -9,5 +9,5 @@ Use the sort values from the previous page to retrieve the next page of results. Signature: ```typescript -searchAfter?: unknown[]; +searchAfter?: estypes.Id[]; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.sortorder.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.sortorder.md index 3834c802fa1845..36f99e51ea8c65 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.sortorder.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.sortorder.md @@ -7,5 +7,5 @@ Signature: ```typescript -sortOrder?: string; +sortOrder?: estypes.SortOrder; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md index 6f7c05ea469bc6..a92b1f48d08ebc 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md @@ -27,10 +27,10 @@ export interface SavedObjectsFindOptions | [preference](./kibana-plugin-core-server.savedobjectsfindoptions.preference.md) | string | An optional ES preference value to be used for the query \* | | [rootSearchFields](./kibana-plugin-core-server.savedobjectsfindoptions.rootsearchfields.md) | string[] | The fields to perform the parsed query against. Unlike the searchFields argument, these are expected to be root fields and will not be modified. If used in conjunction with searchFields, both are concatenated together. | | [search](./kibana-plugin-core-server.savedobjectsfindoptions.search.md) | string | Search documents using the Elasticsearch Simple Query String syntax. See Elasticsearch Simple Query String query argument for more information | -| [searchAfter](./kibana-plugin-core-server.savedobjectsfindoptions.searchafter.md) | unknown[] | Use the sort values from the previous page to retrieve the next page of results. | +| [searchAfter](./kibana-plugin-core-server.savedobjectsfindoptions.searchafter.md) | estypes.Id[] | Use the sort values from the previous page to retrieve the next page of results. | | [searchFields](./kibana-plugin-core-server.savedobjectsfindoptions.searchfields.md) | string[] | The fields to perform the parsed query against. See Elasticsearch Simple Query String fields argument for more information | | [sortField](./kibana-plugin-core-server.savedobjectsfindoptions.sortfield.md) | string | | -| [sortOrder](./kibana-plugin-core-server.savedobjectsfindoptions.sortorder.md) | string | | +| [sortOrder](./kibana-plugin-core-server.savedobjectsfindoptions.sortorder.md) | estypes.SortOrder | | | [type](./kibana-plugin-core-server.savedobjectsfindoptions.type.md) | string | string[] | | | [typeToNamespacesMap](./kibana-plugin-core-server.savedobjectsfindoptions.typetonamespacesmap.md) | Map<string, string[] | undefined> | This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved object client wrapper. If this is defined, it supersedes the type and namespaces fields when building the Elasticsearch query. Any types that are not included in this map will be excluded entirely. If a type is included but its value is undefined, the operation will search for that type in the Default namespace. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.searchafter.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.searchafter.md index 6364370948976e..9afd602259a78e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.searchafter.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.searchafter.md @@ -9,5 +9,5 @@ Use the sort values from the previous page to retrieve the next page of results. Signature: ```typescript -searchAfter?: unknown[]; +searchAfter?: estypes.Id[]; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.sortorder.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.sortorder.md index d247b9e38e4489..e1c657e3a5171f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.sortorder.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.sortorder.md @@ -7,5 +7,5 @@ Signature: ```typescript -sortOrder?: string; +sortOrder?: estypes.SortOrder; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.md index 0f8e9c59236bbc..a729ce32e1c802 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.md @@ -16,5 +16,5 @@ export interface SavedObjectsFindResult extends SavedObject | Property | Type | Description | | --- | --- | --- | | [score](./kibana-plugin-core-server.savedobjectsfindresult.score.md) | number | The Elasticsearch _score of this result. | -| [sort](./kibana-plugin-core-server.savedobjectsfindresult.sort.md) | unknown[] | The Elasticsearch sort value of this result. | +| [sort](./kibana-plugin-core-server.savedobjectsfindresult.sort.md) | string[] | The Elasticsearch sort value of this result. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md index 17f52687243321..e73d6b4926d893 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md @@ -9,7 +9,7 @@ The Elasticsearch `sort` value of this result. Signature: ```typescript -sort?: unknown[]; +sort?: string[]; ``` ## Remarks diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md index c8a372edbdb85a..073b1d462986c6 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md @@ -7,5 +7,5 @@ Signature: ```typescript -export declare type IEsSearchResponse = IKibanaSearchResponse>; +export declare type IEsSearchResponse = IKibanaSearchResponse>; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch.md index e96fe8b8e08dc6..623d6366d4d131 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch.md @@ -14,7 +14,7 @@ Fetch this source and reject the returned Promise on error Signature: ```typescript -fetch(options?: ISearchOptions): Promise>; +fetch(options?: ISearchOptions): Promise>; ``` ## Parameters @@ -25,5 +25,5 @@ fetch(options?: ISearchOptions): PromiseReturns: -`Promise>` +`Promise>` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md index d333af1b278c2d..be208c0a51c81e 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md @@ -7,5 +7,5 @@ Signature: ```typescript -export declare type IEsSearchResponse = IKibanaSearchResponse>; +export declare type IEsSearchResponse = IKibanaSearchResponse>; ``` diff --git a/examples/search_examples/public/search/app.tsx b/examples/search_examples/public/search/app.tsx index 8822be035a3d1c..c87bf21e0e71cb 100644 --- a/examples/search_examples/public/search/app.tsx +++ b/examples/search_examples/public/search/app.tsx @@ -145,7 +145,8 @@ export const SearchExamplesApp = ({ setResponse(res.rawResponse); setTimeTook(res.rawResponse.took); const avgResult: number | undefined = res.rawResponse.aggregations - ? res.rawResponse.aggregations[1].value + ? // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response + res.rawResponse.aggregations[1].value : undefined; const message = ( diff --git a/examples/search_examples/public/search_sessions/app.tsx b/examples/search_examples/public/search_sessions/app.tsx index bf57964dc1f86e..a768600db24eea 100644 --- a/examples/search_examples/public/search_sessions/app.tsx +++ b/examples/search_examples/public/search_sessions/app.tsx @@ -702,13 +702,15 @@ function doSearch( const startTs = performance.now(); // Submit the search request using the `data.search` service. + // @ts-expect-error request.params is incompatible. Filter is not assignable to QueryContainer return data.search .search(req, { sessionId }) .pipe( tap((res) => { if (isCompleteResponse(res)) { const avgResult: number | undefined = res.rawResponse.aggregations - ? res.rawResponse.aggregations[1]?.value ?? res.rawResponse.aggregations[2]?.value + ? // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response + res.rawResponse.aggregations[1]?.value ?? res.rawResponse.aggregations[2]?.value : undefined; const message = ( diff --git a/package.json b/package.json index 7cb6a505eeafe4..66a6ef1d4558bb 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ }, "dependencies": { "@elastic/datemath": "link:packages/elastic-datemath", - "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.3", + "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.4", "@elastic/ems-client": "7.12.0", "@elastic/eui": "31.7.0", "@elastic/filesaver": "1.1.2", diff --git a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts index 2c36e24453c626..dbc455bbd2f8fc 100644 --- a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts +++ b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { ToolingLog } from '@kbn/dev-utils'; import { KbnClient } from '@kbn/test'; @@ -17,7 +17,7 @@ export async function emptyKibanaIndexAction({ log, kbnClient, }: { - client: Client; + client: KibanaClient; log: ToolingLog; kbnClient: KbnClient; }) { diff --git a/packages/kbn-es-archiver/src/actions/load.ts b/packages/kbn-es-archiver/src/actions/load.ts index 68d54373360237..248c4a65cb20a6 100644 --- a/packages/kbn-es-archiver/src/actions/load.ts +++ b/packages/kbn-es-archiver/src/actions/load.ts @@ -11,7 +11,7 @@ import { createReadStream } from 'fs'; import { Readable } from 'stream'; import { ToolingLog } from '@kbn/dev-utils'; import { KbnClient } from '@kbn/test'; -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { createPromiseFromStreams, concatStreamProviders } from '@kbn/utils'; import { ES_CLIENT_HEADERS } from '../client_headers'; @@ -48,7 +48,7 @@ export async function loadAction({ name: string; skipExisting: boolean; useCreate: boolean; - client: Client; + client: KibanaClient; dataDir: string; log: ToolingLog; kbnClient: KbnClient; diff --git a/packages/kbn-es-archiver/src/actions/save.ts b/packages/kbn-es-archiver/src/actions/save.ts index 3790e0f013ee03..c90f241a1c6399 100644 --- a/packages/kbn-es-archiver/src/actions/save.ts +++ b/packages/kbn-es-archiver/src/actions/save.ts @@ -9,7 +9,7 @@ import { resolve } from 'path'; import { createWriteStream, mkdirSync } from 'fs'; import { Readable, Writable } from 'stream'; -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { ToolingLog } from '@kbn/dev-utils'; import { createListStream, createPromiseFromStreams } from '@kbn/utils'; @@ -32,7 +32,7 @@ export async function saveAction({ }: { name: string; indices: string | string[]; - client: Client; + client: KibanaClient; dataDir: string; log: ToolingLog; raw: boolean; diff --git a/packages/kbn-es-archiver/src/actions/unload.ts b/packages/kbn-es-archiver/src/actions/unload.ts index b5f259a1496bb8..f4e37871a53371 100644 --- a/packages/kbn-es-archiver/src/actions/unload.ts +++ b/packages/kbn-es-archiver/src/actions/unload.ts @@ -9,7 +9,7 @@ import { resolve } from 'path'; import { createReadStream } from 'fs'; import { Readable, Writable } from 'stream'; -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { ToolingLog } from '@kbn/dev-utils'; import { KbnClient } from '@kbn/test'; import { createPromiseFromStreams } from '@kbn/utils'; @@ -32,7 +32,7 @@ export async function unloadAction({ kbnClient, }: { name: string; - client: Client; + client: KibanaClient; dataDir: string; log: ToolingLog; kbnClient: KbnClient; diff --git a/packages/kbn-es-archiver/src/client_headers.ts b/packages/kbn-es-archiver/src/client_headers.ts index da240c3ad8318b..5733eb9b97543a 100644 --- a/packages/kbn-es-archiver/src/client_headers.ts +++ b/packages/kbn-es-archiver/src/client_headers.ts @@ -8,4 +8,4 @@ export const ES_CLIENT_HEADERS = { 'x-elastic-product-origin': 'kibana', -}; +} as const; diff --git a/packages/kbn-es-archiver/src/es_archiver.ts b/packages/kbn-es-archiver/src/es_archiver.ts index 68eacb4f3caf22..93ce97efd4c84a 100644 --- a/packages/kbn-es-archiver/src/es_archiver.ts +++ b/packages/kbn-es-archiver/src/es_archiver.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { ToolingLog } from '@kbn/dev-utils'; import { KbnClient } from '@kbn/test'; @@ -20,14 +20,14 @@ import { } from './actions'; interface Options { - client: Client; + client: KibanaClient; dataDir: string; log: ToolingLog; kbnClient: KbnClient; } export class EsArchiver { - private readonly client: Client; + private readonly client: KibanaClient; private readonly dataDir: string; private readonly log: ToolingLog; private readonly kbnClient: KbnClient; diff --git a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts index cacd224e714215..88e167b3705cb9 100644 --- a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts @@ -7,7 +7,7 @@ */ import { Transform } from 'stream'; -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { Stats } from '../stats'; import { Progress } from '../progress'; import { ES_CLIENT_HEADERS } from '../../client_headers'; @@ -21,7 +21,7 @@ export function createGenerateDocRecordsStream({ progress, query, }: { - client: Client; + client: KibanaClient; stats: Stats; progress: Progress; query?: Record; diff --git a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts index e105a243cae763..028ff16c9afb2f 100644 --- a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import AggregateError from 'aggregate-error'; import { Writable } from 'stream'; import { Stats } from '../stats'; @@ -14,7 +14,7 @@ import { Progress } from '../progress'; import { ES_CLIENT_HEADERS } from '../../client_headers'; export function createIndexDocRecordsStream( - client: Client, + client: KibanaClient, stats: Stats, progress: Progress, useCreate: boolean = false diff --git a/packages/kbn-es-archiver/src/lib/indices/__mocks__/stubs.ts b/packages/kbn-es-archiver/src/lib/indices/__mocks__/stubs.ts index 59101f54900162..7dde4075dc3f26 100644 --- a/packages/kbn-es-archiver/src/lib/indices/__mocks__/stubs.ts +++ b/packages/kbn-es-archiver/src/lib/indices/__mocks__/stubs.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import sinon from 'sinon'; import { ToolingLog } from '@kbn/dev-utils'; import { Stats } from '../../stats'; @@ -67,7 +67,7 @@ const createEsClientError = (errorType: string) => { const indexAlias = (aliases: Record, index: string) => Object.keys(aliases).find((k) => aliases[k] === index); -type StubClient = Client; +type StubClient = KibanaClient; export const createStubClient = ( existingIndices: string[] = [], diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts index 39e00ff0c72c0a..28c8ccd1c28a8d 100644 --- a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts @@ -125,7 +125,6 @@ describe('esArchiver: createCreateIndexStream()', () => { ]); sinon.assert.calledWith(client.indices.create as sinon.SinonSpy, { - method: 'PUT', index: 'index', body: { settings: undefined, diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts index ca89278305813b..b45a8b18a5776a 100644 --- a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts @@ -9,7 +9,8 @@ import { Transform, Readable } from 'stream'; import { inspect } from 'util'; -import { Client } from '@elastic/elasticsearch'; +import { estypes } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { ToolingLog } from '@kbn/dev-utils'; import { Stats } from '../stats'; @@ -18,12 +19,9 @@ import { deleteIndex } from './delete_index'; import { ES_CLIENT_HEADERS } from '../../client_headers'; interface DocRecord { - value: { + value: estypes.IndexState & { index: string; type: string; - settings: Record; - mappings: Record; - aliases: Record; }; } @@ -33,7 +31,7 @@ export function createCreateIndexStream({ skipExisting = false, log, }: { - client: Client; + client: KibanaClient; stats: Stats; skipExisting?: boolean; log: ToolingLog; @@ -66,7 +64,6 @@ export function createCreateIndexStream({ await client.indices.create( { - method: 'PUT', index, body: { settings, diff --git a/packages/kbn-es-archiver/src/lib/indices/delete_index.ts b/packages/kbn-es-archiver/src/lib/indices/delete_index.ts index b5641eec4b9da0..2a42d52e2ca80b 100644 --- a/packages/kbn-es-archiver/src/lib/indices/delete_index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/delete_index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { ToolingLog } from '@kbn/dev-utils'; import { Stats } from '../stats'; import { ES_CLIENT_HEADERS } from '../../client_headers'; @@ -15,7 +15,7 @@ import { ES_CLIENT_HEADERS } from '../../client_headers'; const PENDING_SNAPSHOT_STATUSES = ['INIT', 'STARTED', 'WAITING']; export async function deleteIndex(options: { - client: Client; + client: KibanaClient; stats: Stats; index: string | string[]; log: ToolingLog; @@ -84,7 +84,7 @@ export function isDeleteWhileSnapshotInProgressError(error: any) { * snapshotting this index to complete. */ export async function waitForSnapshotCompletion( - client: Client, + client: KibanaClient, index: string | string[], log: ToolingLog ) { diff --git a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts index db065274a7b3b1..e1552b5ed1e3b2 100644 --- a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts @@ -7,7 +7,7 @@ */ import { Transform } from 'stream'; -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { ToolingLog } from '@kbn/dev-utils'; import { Stats } from '../stats'; @@ -15,7 +15,7 @@ import { deleteIndex } from './delete_index'; import { cleanKibanaIndices } from './kibana_index'; export function createDeleteIndexStream( - client: Client, + client: KibanaClient, stats: Stats, log: ToolingLog, kibanaPluginIds: string[] diff --git a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts index 4e0319c52264f2..6619f1b3a601e2 100644 --- a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts @@ -7,11 +7,11 @@ */ import { Transform } from 'stream'; -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { Stats } from '../stats'; import { ES_CLIENT_HEADERS } from '../../client_headers'; -export function createGenerateIndexRecordsStream(client: Client, stats: Stats) { +export function createGenerateIndexRecordsStream(client: KibanaClient, stats: Stats) { return new Transform({ writableObjectMode: true, readableObjectMode: true, diff --git a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts index dc49085cbd4585..fbef255cd9ee5e 100644 --- a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts @@ -8,7 +8,7 @@ import { inspect } from 'util'; -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { ToolingLog } from '@kbn/dev-utils'; import { KbnClient } from '@kbn/test'; import { Stats } from '../stats'; @@ -23,7 +23,7 @@ export async function deleteKibanaIndices({ stats, log, }: { - client: Client; + client: KibanaClient; stats: Stats; log: ToolingLog; }) { @@ -67,22 +67,27 @@ export async function migrateKibanaIndex(kbnClient: KbnClient) { * with .kibana, then filters out any that aren't actually Kibana's core * index (e.g. we don't want to remove .kibana_task_manager or the like). */ -async function fetchKibanaIndices(client: Client) { - const resp = await client.cat.indices( +function isKibanaIndex(index?: string): index is string { + return Boolean( + index && + (/^\.kibana(:?_\d*)?$/.test(index) || + /^\.kibana(_task_manager)?_(pre)?\d+\.\d+\.\d+/.test(index)) + ); +} + +async function fetchKibanaIndices(client: KibanaClient) { + const resp = await client.cat.indices( { index: '.kibana*', format: 'json' }, { headers: ES_CLIENT_HEADERS, } ); - const isKibanaIndex = (index: string) => - /^\.kibana(:?_\d*)?$/.test(index) || - /^\.kibana(_task_manager)?_(pre)?\d+\.\d+\.\d+/.test(index); if (!Array.isArray(resp.body)) { throw new Error(`expected response to be an array ${inspect(resp.body)}`); } - return resp.body.map((x: { index: string }) => x.index).filter(isKibanaIndex); + return resp.body.map((x: { index?: string }) => x.index).filter(isKibanaIndex); } const delay = (delayInMs: number) => new Promise((resolve) => setTimeout(resolve, delayInMs)); @@ -93,7 +98,7 @@ export async function cleanKibanaIndices({ log, kibanaPluginIds, }: { - client: Client; + client: KibanaClient; stats: Stats; log: ToolingLog; kibanaPluginIds: string[]; @@ -149,7 +154,13 @@ export async function cleanKibanaIndices({ stats.deletedIndex('.kibana'); } -export async function createDefaultSpace({ index, client }: { index: string; client: Client }) { +export async function createDefaultSpace({ + index, + client, +}: { + index: string; + client: KibanaClient; +}) { await client.create( { index, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 396bf16cbdc6f2..5c034e68a3736a 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -11,6 +11,7 @@ import { ConfigDeprecationProvider } from '@kbn/config'; import { ConfigPath } from '@kbn/config'; import { DetailedPeerCertificate } from 'tls'; import { EnvironmentMode } from '@kbn/config'; +import { estypes } from '@elastic/elasticsearch'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; @@ -1225,12 +1226,12 @@ export interface SavedObjectsFindOptions { preference?: string; rootSearchFields?: string[]; search?: string; - searchAfter?: unknown[]; + searchAfter?: estypes.Id[]; searchFields?: string[]; // (undocumented) sortField?: string; // (undocumented) - sortOrder?: string; + sortOrder?: estypes.SortOrder; // (undocumented) type: string | string[]; typeToNamespacesMap?: Map; diff --git a/src/core/server/core_usage_data/core_usage_data_service.test.ts b/src/core/server/core_usage_data/core_usage_data_service.test.ts index dfd0a9efc90c10..1c28eca1f1decb 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.test.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.test.ts @@ -120,10 +120,10 @@ describe('CoreUsageDataService', () => { body: [ { name: '.kibana_task_manager_1', - 'docs.count': 10, - 'docs.deleted': 10, - 'store.size': 1000, - 'pri.store.size': 2000, + 'docs.count': '10', + 'docs.deleted': '10', + 'store.size': '1000', + 'pri.store.size': '2000', }, ], } as any); @@ -131,10 +131,10 @@ describe('CoreUsageDataService', () => { body: [ { name: '.kibana_1', - 'docs.count': 20, - 'docs.deleted': 20, - 'store.size': 2000, - 'pri.store.size': 4000, + 'docs.count': '20', + 'docs.deleted': '20', + 'store.size': '2000', + 'pri.store.size': '4000', }, ], } as any); diff --git a/src/core/server/core_usage_data/core_usage_data_service.ts b/src/core/server/core_usage_data/core_usage_data_service.ts index b9d8c9fc7e39fb..dff68bf1c524fe 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.ts @@ -118,10 +118,14 @@ export class CoreUsageDataService implements CoreService; }; -function createApiResponse(opts: Partial = {}): ApiResponse { +function createApiResponse>( + opts: Partial> = {} +): ApiResponse { return { - body: {}, + body: {} as any, statusCode: 200, headers: {}, warnings: [], diff --git a/src/core/server/elasticsearch/client/retry_call_cluster.test.ts b/src/core/server/elasticsearch/client/retry_call_cluster.test.ts index 7b442469838f66..636841316941bb 100644 --- a/src/core/server/elasticsearch/client/retry_call_cluster.test.ts +++ b/src/core/server/elasticsearch/client/retry_call_cluster.test.ts @@ -11,7 +11,7 @@ import { elasticsearchClientMock } from './mocks'; import { loggingSystemMock } from '../../logging/logging_system.mock'; import { retryCallCluster, migrationRetryCallCluster } from './retry_call_cluster'; -const dummyBody = { foo: 'bar' }; +const dummyBody: any = { foo: 'bar' }; const createErrorReturn = (err: any) => elasticsearchClientMock.createErrorTransportRequestPromise(err); @@ -29,7 +29,7 @@ describe('retryCallCluster', () => { client.asyncSearch.get.mockReturnValue(successReturn); - const result = await retryCallCluster(() => client.asyncSearch.get()); + const result = await retryCallCluster(() => client.asyncSearch.get({} as any)); expect(result.body).toEqual(dummyBody); }); @@ -44,7 +44,7 @@ describe('retryCallCluster', () => { ) .mockImplementationOnce(() => successReturn); - const result = await retryCallCluster(() => client.asyncSearch.get()); + const result = await retryCallCluster(() => client.asyncSearch.get({} as any)); expect(result.body).toEqual(dummyBody); }); diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts index 22dcc8022858c1..468a761781365c 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts @@ -117,6 +117,7 @@ describe('getSortedObjectsForExport()', () => { "keepAlive": "2m", }, "search": undefined, + "searchAfter": undefined, "sortField": "updated_at", "sortOrder": "desc", "type": Array [ @@ -145,7 +146,7 @@ describe('getSortedObjectsForExport()', () => { type = 'index-pattern', }: { attributes?: Record; - sort?: unknown[]; + sort?: string[]; type?: string; } = {} ) { @@ -461,6 +462,7 @@ describe('getSortedObjectsForExport()', () => { "keepAlive": "2m", }, "search": undefined, + "searchAfter": undefined, "sortField": "updated_at", "sortOrder": "desc", "type": Array [ @@ -617,6 +619,7 @@ describe('getSortedObjectsForExport()', () => { "keepAlive": "2m", }, "search": "foo", + "searchAfter": undefined, "sortField": "updated_at", "sortOrder": "desc", "type": Array [ @@ -710,6 +713,7 @@ describe('getSortedObjectsForExport()', () => { "keepAlive": "2m", }, "search": undefined, + "searchAfter": undefined, "sortField": "updated_at", "sortOrder": "desc", "type": Array [ @@ -808,6 +812,7 @@ describe('getSortedObjectsForExport()', () => { "keepAlive": "2m", }, "search": undefined, + "searchAfter": undefined, "sortField": "updated_at", "sortOrder": "desc", "type": Array [ diff --git a/src/core/server/saved_objects/import/lib/check_origin_conflicts.ts b/src/core/server/saved_objects/import/lib/check_origin_conflicts.ts index d35388ff947498..1952a04ab815ca 100644 --- a/src/core/server/saved_objects/import/lib/check_origin_conflicts.ts +++ b/src/core/server/saved_objects/import/lib/check_origin_conflicts.ts @@ -95,7 +95,7 @@ const checkOriginConflict = async ( perPage: 10, fields: ['title'], sortField: 'updated_at', - sortOrder: 'desc', + sortOrder: 'desc' as const, ...(namespace && { namespaces: [namespace] }), }; const findResult = await savedObjectsClient.find<{ title?: string }>(findOptions); diff --git a/src/core/server/saved_objects/mappings/types.ts b/src/core/server/saved_objects/mappings/types.ts index fa7531392d1229..25fb61de935187 100644 --- a/src/core/server/saved_objects/mappings/types.ts +++ b/src/core/server/saved_objects/mappings/types.ts @@ -102,7 +102,7 @@ export type SavedObjectsFieldMapping = /** @internal */ export interface IndexMapping { - dynamic?: string; + dynamic?: boolean | 'strict'; properties: SavedObjectsMappingProperties; _meta?: IndexMappingMeta; } diff --git a/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts b/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts index 63634bdb1754ea..5465da2f620adb 100644 --- a/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts +++ b/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts @@ -164,6 +164,7 @@ describe('diffMappings', () => { _meta: { migrationMappingPropertyHashes: { foo: 'bar' }, }, + // @ts-expect-error dynamic: 'abcde', properties: {}, }; diff --git a/src/core/server/saved_objects/migrations/core/call_cluster.ts b/src/core/server/saved_objects/migrations/core/call_cluster.ts index bbf39549457d8a..f37bbdd14a8994 100644 --- a/src/core/server/saved_objects/migrations/core/call_cluster.ts +++ b/src/core/server/saved_objects/migrations/core/call_cluster.ts @@ -12,11 +12,12 @@ * funcationality contained here. */ +import type { estypes } from '@elastic/elasticsearch'; import { IndexMapping } from '../../mappings'; export interface CallCluster { (path: 'bulk', opts: { body: object[] }): Promise; - (path: 'count', opts: CountOpts): Promise<{ count: number; _shards: ShardsInfo }>; + (path: 'count', opts: CountOpts): Promise<{ count: number; _shards: estypes.ShardStatistics }>; (path: 'clearScroll', opts: { scrollId: string }): Promise; (path: 'indices.create', opts: IndexCreationOpts): Promise; (path: 'indices.exists', opts: IndexOpts): Promise; @@ -143,7 +144,7 @@ export interface IndexSettingsResult { } export interface RawDoc { - _id: string; + _id: estypes.Id; _source: any; _type?: string; } @@ -153,14 +154,7 @@ export interface SearchResults { hits: RawDoc[]; }; _scroll_id?: string; - _shards: ShardsInfo; -} - -export interface ShardsInfo { - total: number; - successful: number; - skipped: number; - failed: number; + _shards: estypes.ShardStatistics; } export interface ErrorResponse { diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts index bfa686ac0cc47e..5cb2a88c4733f7 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { estypes } from '@elastic/elasticsearch'; import _ from 'lodash'; import { elasticsearchClientMock } from '../../../elasticsearch/client/mocks'; import * as Index from './elastic_index'; @@ -33,41 +34,6 @@ describe('ElasticIndex', () => { expect(client.indices.get).toHaveBeenCalledWith({ index: '.kibana-test' }, { ignore: [404] }); }); - test('fails if the index doc type is unsupported', async () => { - client.indices.get.mockImplementation((params) => { - const index = params!.index as string; - return elasticsearchClientMock.createSuccessTransportRequestPromise({ - [index]: { - aliases: { foo: index }, - mappings: { spock: { dynamic: 'strict', properties: { a: 'b' } } }, - }, - }); - }); - - await expect(Index.fetchInfo(client, '.baz')).rejects.toThrow( - /cannot be automatically migrated/ - ); - }); - - test('fails if there are multiple root types', async () => { - client.indices.get.mockImplementation((params) => { - const index = params!.index as string; - return elasticsearchClientMock.createSuccessTransportRequestPromise({ - [index]: { - aliases: { foo: index }, - mappings: { - doc: { dynamic: 'strict', properties: { a: 'b' } }, - doctor: { dynamic: 'strict', properties: { a: 'b' } }, - }, - }, - }); - }); - - await expect(Index.fetchInfo(client, '.baz')).rejects.toThrow( - /cannot be automatically migrated/ - ); - }); - test('decorates index info with exists and indexName', async () => { client.indices.get.mockImplementation((params) => { const index = params!.index as string; @@ -75,8 +41,9 @@ describe('ElasticIndex', () => { [index]: { aliases: { foo: index }, mappings: { dynamic: 'strict', properties: { a: 'b' } }, + settings: {}, }, - }); + } as estypes.GetIndexResponse); }); const info = await Index.fetchInfo(client, '.baz'); @@ -85,6 +52,7 @@ describe('ElasticIndex', () => { mappings: { dynamic: 'strict', properties: { a: 'b' } }, exists: true, indexName: '.baz', + settings: {}, }); }); }); @@ -134,7 +102,7 @@ describe('ElasticIndex', () => { test('removes existing alias', async () => { client.indices.getAlias.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ - '.my-fanci-index': '.muchacha', + '.my-fanci-index': { aliases: { '.muchacha': {} } }, }) ); @@ -157,7 +125,7 @@ describe('ElasticIndex', () => { test('allows custom alias actions', async () => { client.indices.getAlias.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ - '.my-fanci-index': '.muchacha', + '.my-fanci-index': { aliases: { '.muchacha': {} } }, }) ); @@ -185,14 +153,18 @@ describe('ElasticIndex', () => { test('it creates the destination index, then reindexes to it', async () => { client.indices.getAlias.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ - '.my-fanci-index': '.muchacha', + '.my-fanci-index': { aliases: { '.muchacha': {} } }, }) ); client.reindex.mockResolvedValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ task: 'abc' }) + elasticsearchClientMock.createSuccessTransportRequestPromise({ + task: 'abc', + } as estypes.ReindexResponse) ); client.tasks.get.mockResolvedValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ completed: true }) + elasticsearchClientMock.createSuccessTransportRequestPromise({ + completed: true, + } as estypes.GetTaskResponse) ); const info = { @@ -200,7 +172,7 @@ describe('ElasticIndex', () => { exists: true, indexName: '.ze-index', mappings: { - dynamic: 'strict', + dynamic: 'strict' as const, properties: { foo: { type: 'keyword' } }, }, }; @@ -259,13 +231,16 @@ describe('ElasticIndex', () => { test('throws error if re-index task fails', async () => { client.indices.getAlias.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ - '.my-fanci-index': '.muchacha', + '.my-fanci-index': { aliases: { '.muchacha': {} } }, }) ); client.reindex.mockResolvedValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ task: 'abc' }) + elasticsearchClientMock.createSuccessTransportRequestPromise({ + task: 'abc', + } as estypes.ReindexResponse) ); client.tasks.get.mockResolvedValue( + // @ts-expect-error @elastic/elasticsearch GetTaskResponse requires a `task` property even on errors elasticsearchClientMock.createSuccessTransportRequestPromise({ completed: true, error: { @@ -273,7 +248,7 @@ describe('ElasticIndex', () => { reason: 'all shards failed', failed_shards: [], }, - }) + } as estypes.GetTaskResponse) ); const info = { @@ -286,6 +261,7 @@ describe('ElasticIndex', () => { }, }; + // @ts-expect-error await expect(Index.convertToAlias(client, info, '.muchacha', 10)).rejects.toThrow( /Re-index failed \[search_phase_execution_exception\] all shards failed/ ); @@ -319,7 +295,9 @@ describe('ElasticIndex', () => { describe('write', () => { test('writes documents in bulk to the index', async () => { client.bulk.mockResolvedValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ items: [] }) + elasticsearchClientMock.createSuccessTransportRequestPromise({ + items: [] as any[], + } as estypes.BulkResponse) ); const index = '.myalias'; @@ -356,7 +334,7 @@ describe('ElasticIndex', () => { client.bulk.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ items: [{ index: { error: { type: 'shazm', reason: 'dern' } } }], - }) + } as estypes.BulkResponse) ); const index = '.myalias'; diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.ts b/src/core/server/saved_objects/migrations/core/elastic_index.ts index e42643565eb4fe..a5f3cb36e736b6 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.ts @@ -12,11 +12,12 @@ */ import _ from 'lodash'; +import { estypes } from '@elastic/elasticsearch'; import { MigrationEsClient } from './migration_es_client'; import { CountResponse, SearchResponse } from '../../../elasticsearch'; import { IndexMapping } from '../../mappings'; import { SavedObjectsMigrationVersion } from '../../types'; -import { AliasAction, RawDoc, ShardsInfo } from './call_cluster'; +import { AliasAction, RawDoc } from './call_cluster'; import { SavedObjectsRawDocSource } from '../../serialization'; const settings = { number_of_shards: 1, auto_expand_replicas: '0-1' }; @@ -46,6 +47,7 @@ export async function fetchInfo(client: MigrationEsClient, index: string): Promi const [indexName, indexInfo] = Object.entries(body)[0]; + // @ts-expect-error @elastic/elasticsearch IndexState.alias and IndexState.mappings should be required return assertIsSupportedIndex({ ...indexInfo, exists: true, indexName }); } @@ -142,7 +144,7 @@ export async function write(client: MigrationEsClient, index: string, docs: RawD return; } - const exception: any = new Error(err.index.error!.reason); + const exception: any = new Error(err.index!.error!.reason); exception.detail = err; throw exception; } @@ -322,7 +324,7 @@ function assertIsSupportedIndex(indexInfo: FullIndexInfo) { * Object indices should only ever have a single shard. This is more to handle * instances where customers manually expand the shards of an index. */ -function assertResponseIncludeAllShards({ _shards }: { _shards: ShardsInfo }) { +function assertResponseIncludeAllShards({ _shards }: { _shards: estypes.ShardStatistics }) { if (!_.has(_shards, 'total') || !_.has(_shards, 'successful')) { return; } @@ -375,11 +377,12 @@ async function reindex( await new Promise((r) => setTimeout(r, pollInterval)); const { body } = await client.tasks.get({ - task_id: task, + task_id: String(task), }); - if (body.error) { - const e = body.error; + // @ts-expect-error @elastic/elasticsearch GetTaskResponse doesn't contain `error` property + const e = body.error; + if (e) { throw new Error(`Re-index failed [${e.type}] ${e.reason} :: ${JSON.stringify(e)}`); } diff --git a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts index 0d1939231ce6c8..dd295efacf6b86 100644 --- a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts @@ -7,6 +7,7 @@ */ import _ from 'lodash'; +import type { estypes } from '@elastic/elasticsearch'; import { elasticsearchClientMock } from '../../../elasticsearch/client/mocks'; import { SavedObjectUnsanitizedDoc, SavedObjectsSerializer } from '../../serialization'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; @@ -443,23 +444,28 @@ function withIndex( elasticsearchClientMock.createSuccessTransportRequestPromise({ task: 'zeid', _shards: { successful: 1, total: 1 }, - }) + } as estypes.ReindexResponse) ); client.tasks.get.mockReturnValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ completed: true }) + elasticsearchClientMock.createSuccessTransportRequestPromise({ + completed: true, + } as estypes.GetTaskResponse) ); client.search.mockReturnValue( - elasticsearchClientMock.createSuccessTransportRequestPromise(searchResult(0)) + elasticsearchClientMock.createSuccessTransportRequestPromise(searchResult(0) as any) ); client.bulk.mockReturnValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ items: [] }) + elasticsearchClientMock.createSuccessTransportRequestPromise({ + items: [] as any[], + } as estypes.BulkResponse) ); client.count.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ count: numOutOfDate, _shards: { successful: 1, total: 1 }, - }) + } as estypes.CountResponse) ); + // @ts-expect-error client.scroll.mockImplementation(() => { if (scrollCallCounter <= docs.length) { const result = searchResult(scrollCallCounter); diff --git a/src/core/server/saved_objects/migrations/core/index_migrator.ts b/src/core/server/saved_objects/migrations/core/index_migrator.ts index 52f155e5d2de21..5bf5ae26f6a0ad 100644 --- a/src/core/server/saved_objects/migrations/core/index_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/index_migrator.ts @@ -134,7 +134,7 @@ async function deleteIndexTemplates({ client, log, obsoleteIndexTemplatePattern return; } - const { body: templates } = await client.cat.templates>({ + const { body: templates } = await client.cat.templates({ format: 'json', name: obsoleteIndexTemplatePattern, }); @@ -147,7 +147,7 @@ async function deleteIndexTemplates({ client, log, obsoleteIndexTemplatePattern log.info(`Removing index templates: ${templateNames}`); - return Promise.all(templateNames.map((name) => client.indices.deleteTemplate({ name }))); + return Promise.all(templateNames.map((name) => client.indices.deleteTemplate({ name: name! }))); } /** @@ -185,7 +185,13 @@ async function migrateSourceToDest(context: Context) { await Index.write( client, dest.indexName, - await migrateRawDocs(serializer, documentMigrator.migrateAndConvert, docs, log) + await migrateRawDocs( + serializer, + documentMigrator.migrateAndConvert, + // @ts-expect-error @elastic/elasticsearch `Hit._id` may be a string | number in ES, but we always expect strings in the SO index. + docs, + log + ) ); } } diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts index b8accc462df9a0..7ead37699980a5 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts @@ -7,13 +7,13 @@ */ import { take } from 'rxjs/operators'; +import { estypes, errors as esErrors } from '@elastic/elasticsearch'; import { elasticsearchClientMock } from '../../../elasticsearch/client/mocks'; import { KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator'; import { loggingSystemMock } from '../../../logging/logging_system.mock'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { SavedObjectsType } from '../../types'; -import { errors as esErrors } from '@elastic/elasticsearch'; import { DocumentMigrator } from '../core/document_migrator'; jest.mock('../core/document_migrator', () => { return { @@ -105,10 +105,7 @@ describe('KibanaMigrator', () => { const options = mockOptions(); options.client.cat.templates.mockReturnValue( - elasticsearchClientMock.createSuccessTransportRequestPromise( - { templates: [] }, - { statusCode: 404 } - ) + elasticsearchClientMock.createSuccessTransportRequestPromise([], { statusCode: 404 }) ); options.client.indices.get.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({}, { statusCode: 404 }) @@ -129,7 +126,8 @@ describe('KibanaMigrator', () => { options.client.cat.templates.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise( - { templates: [] }, + // @ts-expect-error + { templates: [] } as CatTemplatesResponse, { statusCode: 404 } ) ); @@ -155,7 +153,8 @@ describe('KibanaMigrator', () => { options.client.cat.templates.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise( - { templates: [] }, + // @ts-expect-error + { templates: [] } as CatTemplatesResponse, { statusCode: 404 } ) ); @@ -193,7 +192,8 @@ describe('KibanaMigrator', () => { options.client.cat.templates.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise( - { templates: [] }, + // @ts-expect-error + { templates: [] } as CatTemplatesResponse, { statusCode: 404 } ) ); @@ -323,7 +323,7 @@ describe('KibanaMigrator', () => { completed: true, error: { type: 'elatsicsearch_exception', reason: 'task failed with an error' }, failures: [], - task: { description: 'task description' }, + task: { description: 'task description' } as any, }) ); @@ -365,15 +365,17 @@ const mockV2MigrationOptions = () => { elasticsearchClientMock.createSuccessTransportRequestPromise({ acknowledged: true }) ); options.client.reindex.mockReturnValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ taskId: 'reindex_task_id' }) + elasticsearchClientMock.createSuccessTransportRequestPromise({ + taskId: 'reindex_task_id', + } as estypes.ReindexResponse) ); options.client.tasks.get.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ completed: true, error: undefined, failures: [], - task: { description: 'task description' }, - }) + task: { description: 'task description' } as any, + } as estypes.GetTaskResponse) ); return options; diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.ts b/src/core/server/saved_objects/migrationsv2/actions/index.ts index d025f104c6e3f1..22dfb03815052d 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.ts @@ -13,9 +13,10 @@ import { ElasticsearchClientError } from '@elastic/elasticsearch/lib/errors'; import { pipe } from 'fp-ts/lib/pipeable'; import { errors as EsErrors } from '@elastic/elasticsearch'; import { flow } from 'fp-ts/lib/function'; +import type { estypes } from '@elastic/elasticsearch'; import { ElasticsearchClient } from '../../../elasticsearch'; import { IndexMapping } from '../../mappings'; -import { SavedObjectsRawDoc } from '../../serialization'; +import { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '../../serialization'; import { catchRetryableEsClientErrors, RetryableEsClientError, @@ -56,20 +57,22 @@ export type FetchIndexResponse = Record< export const fetchIndices = ( client: ElasticsearchClient, indicesToFetch: string[] -): TaskEither.TaskEither => () => { - return client.indices - .get( - { - index: indicesToFetch, - ignore_unavailable: true, // Don't return an error for missing indices. Note this *will* include closed indices, the docs are misleading https://github.com/elastic/elasticsearch/issues/63607 - }, - { ignore: [404], maxRetries: 0 } - ) - .then(({ body }) => { - return Either.right(body); - }) - .catch(catchRetryableEsClientErrors); -}; +): TaskEither.TaskEither => + // @ts-expect-error @elastic/elasticsearch IndexState.alias and IndexState.mappings should be required + () => { + return client.indices + .get( + { + index: indicesToFetch, + ignore_unavailable: true, // Don't return an error for missing indices. Note this *will* include closed indices, the docs are misleading https://github.com/elastic/elasticsearch/issues/63607 + }, + { ignore: [404], maxRetries: 0 } + ) + .then(({ body }) => { + return Either.right(body); + }) + .catch(catchRetryableEsClientErrors); + }; /** * Sets a write block in place for the given index. If the response includes @@ -98,7 +101,7 @@ export const setWriteBlock = ( }, { maxRetries: 0 /** handle retry ourselves for now */ } ) - .then((res) => { + .then((res: any) => { return res.body.acknowledged === true ? Either.right('set_write_block_succeeded' as const) : Either.left({ @@ -134,7 +137,11 @@ export const removeWriteBlock = ( // Don't change any existing settings preserve_existing: true, body: { - 'index.blocks.write': false, + index: { + blocks: { + write: false, + }, + }, }, }, { maxRetries: 0 /** handle retry ourselves for now */ } @@ -285,7 +292,7 @@ interface WaitForTaskResponse { error: Option.Option<{ type: string; reason: string; index: string }>; completed: boolean; failures: Option.Option; - description: string; + description?: string; } /** @@ -299,12 +306,7 @@ const waitForTask = ( timeout: string ): TaskEither.TaskEither => () => { return client.tasks - .get<{ - completed: boolean; - response: { failures: any[] }; - task: { description: string }; - error: { type: string; reason: string; index: string }; - }>({ + .get({ task_id: taskId, wait_for_completion: true, timeout, @@ -314,6 +316,7 @@ const waitForTask = ( const failures = body.response?.failures ?? []; return Either.right({ completed: body.completed, + // @ts-expect-error @elastic/elasticsearch GetTaskResponse doesn't declare `error` property error: Option.fromNullable(body.error), failures: failures.length > 0 ? Option.some(failures) : Option.none, description: body.task.description, @@ -359,7 +362,7 @@ export const pickupUpdatedMappings = ( wait_for_completion: false, }) .then(({ body: { task: taskId } }) => { - return Either.right({ taskId }); + return Either.right({ taskId: String(taskId!) }); }) .catch(catchRetryableEsClientErrors); }; @@ -387,7 +390,6 @@ export const reindex = ( .reindex({ // Require targetIndex to be an alias. Prevents a new index from being // created if targetIndex doesn't exist. - // @ts-expect-error This API isn't documented require_alias: requireAlias, body: { // Ignore version conflicts from existing documents @@ -416,7 +418,7 @@ export const reindex = ( wait_for_completion: false, }) .then(({ body: { task: taskId } }) => { - return Either.right({ taskId }); + return Either.right({ taskId: String(taskId) }); }) .catch(catchRetryableEsClientErrors); }; @@ -624,7 +626,7 @@ export const createIndex = ( const aliasesObject = (aliases ?? []).reduce((acc, alias) => { acc[alias] = {}; return acc; - }, {} as Record); + }, {} as Record); return client.indices .create( @@ -727,7 +729,7 @@ export const updateAndPickupMappings = ( 'update_mappings_succeeded' > = () => { return client.indices - .putMapping, IndexMapping>({ + .putMapping({ index, timeout: DEFAULT_TIMEOUT, body: mappings, @@ -774,22 +776,16 @@ export const searchForOutdatedDocuments = ( query: Record ): TaskEither.TaskEither => () => { return client - .search<{ - // when `filter_path` is specified, ES doesn't return empty arrays, so if - // there are no search results res.body.hits will be undefined. - hits?: { - hits?: SavedObjectsRawDoc[]; - }; - }>({ + .search({ index, - // Optimize search performance by sorting by the "natural" index order - sort: ['_doc'], // Return the _seq_no and _primary_term so we can use optimistic // concurrency control for updates seq_no_primary_term: true, size: BATCH_SIZE, body: { query, + // Optimize search performance by sorting by the "natural" index order + sort: ['_doc'], }, // Return an error when targeting missing or closed indices allow_no_indices: false, @@ -811,7 +807,9 @@ export const searchForOutdatedDocuments = ( 'hits.hits._primary_term', ], }) - .then((res) => Either.right({ outdatedDocuments: res.body.hits?.hits ?? [] })) + .then((res) => + Either.right({ outdatedDocuments: (res.body.hits?.hits as SavedObjectsRawDoc[]) ?? [] }) + ) .catch(catchRetryableEsClientErrors); }; @@ -825,20 +823,7 @@ export const bulkOverwriteTransformedDocuments = ( transformedDocs: SavedObjectsRawDoc[] ): TaskEither.TaskEither => () => { return client - .bulk<{ - took: number; - errors: boolean; - items: [ - { - index: { - _id: string; - status: number; - // the filter_path ensures that only items with errors are returned - error: { type: string; reason: string }; - }; - } - ]; - }>({ + .bulk({ // Because we only add aliases in the MARK_VERSION_INDEX_READY step we // can't bulkIndex to an alias with require_alias=true. This means if // users tamper during this operation (delete indices or restore a @@ -880,7 +865,7 @@ export const bulkOverwriteTransformedDocuments = ( // Filter out version_conflict_engine_exception since these just mean // that another instance already updated these documents const errors = (res.body.items ?? []).filter( - (item) => item.index.error.type !== 'version_conflict_engine_exception' + (item) => item.index?.error?.type !== 'version_conflict_engine_exception' ); if (errors.length === 0) { return Either.right('bulk_index_succeeded' as const); diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts index 46cfd935f429b3..2c052a87d028b5 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts @@ -258,7 +258,7 @@ describe('migration actions', () => { index: 'clone_red_then_yellow_index', body: { // Enable all shard allocation so that the index status goes yellow - 'index.routing.allocation.enable': 'all', + index: { routing: { allocation: { enable: 'all' } } }, }, }); indexYellow = true; @@ -500,7 +500,7 @@ describe('migration actions', () => { // Create an index with incompatible mappings await createIndex(client, 'reindex_target_6', { - dynamic: 'false', + dynamic: false, properties: { title: { type: 'integer' } }, // integer is incompatible with string title })(); @@ -926,7 +926,7 @@ describe('migration actions', () => { index: 'red_then_yellow_index', body: { // Disable all shard allocation so that the index status is red - 'index.routing.allocation.enable': 'all', + index: { routing: { allocation: { enable: 'all' } } }, }, }); indexYellow = true; diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/migration.test.ts index 95a867934307a4..fd62fd107648e9 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/migration.test.ts @@ -162,7 +162,9 @@ describe('migration v2', () => { const expectedVersions = getExpectedVersionPerType(); const res = await esClient.search({ index: migratedIndex, - sort: ['_doc'], + body: { + sort: ['_doc'], + }, size: 10000, }); const allDocuments = res.body.hits.hits as SavedObjectsRawDoc[]; @@ -217,7 +219,9 @@ describe('migration v2', () => { const expectedVersions = getExpectedVersionPerType(); const res = await esClient.search({ index: migratedIndex, - sort: ['_doc'], + body: { + sort: ['_doc'], + }, size: 10000, }); const allDocuments = res.body.hits.hits as SavedObjectsRawDoc[]; diff --git a/src/core/server/saved_objects/migrationsv2/model.ts b/src/core/server/saved_objects/migrationsv2/model.ts index 6f915df9dd9588..2e92f34429ea9f 100644 --- a/src/core/server/saved_objects/migrationsv2/model.ts +++ b/src/core/server/saved_objects/migrationsv2/model.ts @@ -727,7 +727,6 @@ export const createInitialState = ({ }; const reindexTargetMappings: IndexMapping = { - // @ts-expect-error we don't allow plugins to set `dynamic` dynamic: false, properties: { type: { type: 'keyword' }, diff --git a/src/core/server/saved_objects/service/lib/included_fields.ts b/src/core/server/saved_objects/service/lib/included_fields.ts index 16e27bcc12b8f0..cef83f103ec539 100644 --- a/src/core/server/saved_objects/service/lib/included_fields.ts +++ b/src/core/server/saved_objects/service/lib/included_fields.ts @@ -12,7 +12,10 @@ function toArray(value: string | string[]): string[] { /** * Provides an array of paths for ES source filtering */ -export function includedFields(type: string | string[] = '*', fields?: string[] | string) { +export function includedFields( + type: string | string[] = '*', + fields?: string[] | string +): string[] | undefined { if (!fields || fields.length === 0) { return; } diff --git a/src/core/server/saved_objects/service/lib/point_in_time_finder.ts b/src/core/server/saved_objects/service/lib/point_in_time_finder.ts index b8f459151e7b35..9a8dcceafebb28 100644 --- a/src/core/server/saved_objects/service/lib/point_in_time_finder.ts +++ b/src/core/server/saved_objects/service/lib/point_in_time_finder.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import type { estypes } from '@elastic/elasticsearch'; import type { Logger } from '../../../logging'; import type { SavedObjectsFindOptions, SavedObjectsClientContract } from '../../types'; import type { SavedObjectsFindResponse } from '../'; @@ -96,12 +96,12 @@ export class PointInTimeFinder implements ISavedObjectsPointInTimeFinder { await this.open(); let lastResultsCount: number; - let lastHitSortValue: unknown[] | undefined; + let lastHitSortValue: estypes.Id[] | undefined; do { const results = await this.findNext({ findOptions: this.#findOptions, id: this.#pitId, - ...(lastHitSortValue ? { searchAfter: lastHitSortValue } : {}), + searchAfter: lastHitSortValue, }); this.#pitId = results.pit_id; lastResultsCount = results.saved_objects.length; @@ -159,7 +159,7 @@ export class PointInTimeFinder implements ISavedObjectsPointInTimeFinder { }: { findOptions: SavedObjectsFindOptions; id?: string; - searchAfter?: unknown[]; + searchAfter?: estypes.Id[]; }) { try { return await this.#client.find({ @@ -168,8 +168,8 @@ export class PointInTimeFinder implements ISavedObjectsPointInTimeFinder { sortOrder: 'desc', // Bump keep_alive by 2m on every new request to allow for the ES client // to make multiple retries in the event of a network failure. - ...(id ? { pit: { id, keepAlive: '2m' } } : {}), - ...(searchAfter ? { searchAfter } : {}), + pit: id ? { id, keepAlive: '2m' } : undefined, + searchAfter, ...findOptions, }); } catch (e) { @@ -181,7 +181,7 @@ export class PointInTimeFinder implements ISavedObjectsPointInTimeFinder { } } - private getLastHitSortValue(res: SavedObjectsFindResponse): unknown[] | undefined { + private getLastHitSortValue(res: SavedObjectsFindResponse): estypes.Id[] | undefined { if (res.saved_objects.length < 1) { return undefined; } diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index bff23895fe4592..37572c83e4c885 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -2782,18 +2782,20 @@ describe('SavedObjectsRepository', () => { await findSuccess({ type, fields: ['title'] }); expect(client.search).toHaveBeenCalledWith( expect.objectContaining({ - _source: [ - `${type}.title`, - 'namespace', - 'namespaces', - 'type', - 'references', - 'migrationVersion', - 'coreMigrationVersion', - 'updated_at', - 'originId', - 'title', - ], + body: expect.objectContaining({ + _source: [ + `${type}.title`, + 'namespace', + 'namespaces', + 'type', + 'references', + 'migrationVersion', + 'coreMigrationVersion', + 'updated_at', + 'originId', + 'title', + ], + }), }), expect.anything() ); @@ -3837,6 +3839,7 @@ describe('SavedObjectsRepository', () => { id: '6.0.0-alpha1', ...mockTimestampFields, version: mockVersion, + references: [], attributes: { buildNum: 8468, apiCallsCount: 100, diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index a302cfe5a1e6f0..aa1e62c1652cad 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -7,13 +7,9 @@ */ import { omit, isObject } from 'lodash'; -import { - ElasticsearchClient, - DeleteDocumentResponse, - GetResponse, - SearchResponse, -} from '../../../elasticsearch/'; -import { Logger } from '../../../logging'; +import type { estypes } from '@elastic/elasticsearch'; +import type { ElasticsearchClient } from '../../../elasticsearch/'; +import type { Logger } from '../../../logging'; import { getRootPropertiesObjects, IndexMapping } from '../../mappings'; import { ISavedObjectsPointInTimeFinder, @@ -397,7 +393,7 @@ export class SavedObjectsRepository { _source: ['type', 'namespaces'], })); const bulkGetResponse = bulkGetDocs.length - ? await this.client.mget( + ? await this.client.mget( { body: { docs: bulkGetDocs, @@ -425,8 +421,9 @@ export class SavedObjectsRepository { if (esRequestIndex !== undefined) { const indexFound = bulkGetResponse?.statusCode !== 404; const actualResult = indexFound ? bulkGetResponse?.body.docs[esRequestIndex] : undefined; - const docFound = indexFound && actualResult.found === true; - if (docFound && !this.rawDocExistsInNamespace(actualResult, namespace)) { + const docFound = indexFound && actualResult?.found === true; + // @ts-expect-error MultiGetHit._source is optional + if (docFound && !this.rawDocExistsInNamespace(actualResult!, namespace)) { const { id, type } = object; return { tag: 'Left' as 'Left', @@ -441,7 +438,10 @@ export class SavedObjectsRepository { }; } savedObjectNamespaces = - initialNamespaces || getSavedObjectNamespaces(namespace, docFound && actualResult); + initialNamespaces || + // @ts-expect-error MultiGetHit._source is optional + getSavedObjectNamespaces(namespace, docFound ? actualResult : undefined); + // @ts-expect-error MultiGetHit._source is optional versionProperties = getExpectedVersionProperties(version, actualResult); } else { if (this._registry.isSingleNamespace(object.type)) { @@ -500,7 +500,7 @@ export class SavedObjectsRepository { const { requestedId, rawMigratedDoc, esRequestIndex } = expectedResult.value; const { error, ...rawResponse } = Object.values( - bulkResponse?.body.items[esRequestIndex] + bulkResponse?.body.items[esRequestIndex] ?? {} )[0] as any; if (error) { @@ -564,10 +564,10 @@ export class SavedObjectsRepository { const bulkGetDocs = expectedBulkGetResults.filter(isRight).map(({ value: { type, id } }) => ({ _id: this._serializer.generateRawId(namespace, type, id), _index: this.getIndexForType(type), - _source: ['type', 'namespaces'], + _source: { includes: ['type', 'namespaces'] }, })); const bulkGetResponse = bulkGetDocs.length - ? await this.client.mget( + ? await this.client.mget( { body: { docs: bulkGetDocs, @@ -586,13 +586,14 @@ export class SavedObjectsRepository { const { type, id, esRequestIndex } = expectedResult.value; const doc = bulkGetResponse?.body.docs[esRequestIndex]; - if (doc.found) { + if (doc?.found) { errors.push({ id, type, error: { ...errorContent(SavedObjectsErrorHelpers.createConflictError(type, id)), - ...(!this.rawDocExistsInNamespace(doc, namespace) && { + // @ts-expect-error MultiGetHit._source is optional + ...(!this.rawDocExistsInNamespace(doc!, namespace) && { metadata: { isNotOverwritable: true }, }), }, @@ -636,7 +637,7 @@ export class SavedObjectsRepository { } } - const { body, statusCode } = await this.client.delete( + const { body, statusCode } = await this.client.delete( { id: rawId, index: this.getIndexForType(type), @@ -652,6 +653,7 @@ export class SavedObjectsRepository { } const deleteDocNotFound = body.result === 'not_found'; + // @ts-expect-error 'error' does not exist on type 'DeleteResponse' const deleteIndexNotFound = body.error && body.error.type === 'index_not_found_exception'; if (deleteDocNotFound || deleteIndexNotFound) { // see "404s from missing index" above @@ -813,15 +815,18 @@ export class SavedObjectsRepository { const esOptions = { // If `pit` is provided, we drop the `index`, otherwise ES returns 400. - ...(pit ? {} : { index: this.getIndicesForTypes(allowedTypes) }), + index: pit ? undefined : this.getIndicesForTypes(allowedTypes), // If `searchAfter` is provided, we drop `from` as it will not be used for pagination. - ...(searchAfter ? {} : { from: perPage * (page - 1) }), + from: searchAfter ? undefined : perPage * (page - 1), _source: includedFields(type, fields), preference, rest_total_hits_as_int: true, size: perPage, body: { + size: perPage, seq_no_primary_term: true, + from: perPage * (page - 1), + _source: includedFields(type, fields), ...getSearchDsl(this._mappings, this._registry, { search, defaultSearchOperator, @@ -841,7 +846,7 @@ export class SavedObjectsRepository { }, }; - const { body, statusCode } = await this.client.search>(esOptions, { + const { body, statusCode } = await this.client.search(esOptions, { ignore: [404], }); if (statusCode === 404) { @@ -860,13 +865,15 @@ export class SavedObjectsRepository { per_page: perPage, total: body.hits.total, saved_objects: body.hits.hits.map( - (hit: SavedObjectsRawDoc): SavedObjectsFindResult => ({ + (hit: estypes.Hit): SavedObjectsFindResult => ({ + // @ts-expect-error @elastic/elasticsearch declared Id as string | number ...this._rawToSavedObject(hit), - score: (hit as any)._score, - ...((hit as any).sort && { sort: (hit as any).sort }), + score: hit._score!, + // @ts-expect-error @elastic/elasticsearch declared sort as string | number + sort: hit.sort, }) ), - ...(body.pit_id && { pit_id: body.pit_id }), + pit_id: body.pit_id, } as SavedObjectsFindResponse; } @@ -925,10 +932,10 @@ export class SavedObjectsRepository { .map(({ value: { type, id, fields } }) => ({ _id: this._serializer.generateRawId(namespace, type, id), _index: this.getIndexForType(type), - _source: includedFields(type, fields), + _source: { includes: includedFields(type, fields) }, })); const bulkGetResponse = bulkGetDocs.length - ? await this.client.mget( + ? await this.client.mget( { body: { docs: bulkGetDocs, @@ -947,7 +954,8 @@ export class SavedObjectsRepository { const { type, id, esRequestIndex } = expectedResult.value; const doc = bulkGetResponse?.body.docs[esRequestIndex]; - if (!doc.found || !this.rawDocExistsInNamespace(doc, namespace)) { + // @ts-expect-error MultiGetHit._source is optional + if (!doc?.found || !this.rawDocExistsInNamespace(doc, namespace)) { return ({ id, type, @@ -955,6 +963,7 @@ export class SavedObjectsRepository { } as any) as SavedObject; } + // @ts-expect-error MultiGetHit._source is optional return this.getSavedObjectFromSource(type, id, doc); }), }; @@ -980,7 +989,7 @@ export class SavedObjectsRepository { const namespace = normalizeNamespace(options.namespace); - const { body, statusCode } = await this.client.get>( + const { body, statusCode } = await this.client.get( { id: this._serializer.generateRawId(namespace, type, id), index: this.getIndexForType(type), @@ -988,9 +997,13 @@ export class SavedObjectsRepository { { ignore: [404] } ); - const docNotFound = body.found === false; const indexNotFound = statusCode === 404; - if (docNotFound || indexNotFound || !this.rawDocExistsInNamespace(body, namespace)) { + + if ( + !isFoundGetResponse(body) || + indexNotFound || + !this.rawDocExistsInNamespace(body, namespace) + ) { // see "404s from missing index" above throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } @@ -1026,7 +1039,7 @@ export class SavedObjectsRepository { const time = this._getCurrentTime(); // retrieve the alias, and if it is not disabled, update it - const aliasResponse = await this.client.update( + const aliasResponse = await this.client.update<{ 'legacy-url-alias': LegacyUrlAlias }>( { id: rawAliasId, index: this.getIndexForType(LEGACY_URL_ALIAS_TYPE), @@ -1059,15 +1072,16 @@ export class SavedObjectsRepository { if ( aliasResponse.statusCode === 404 || - aliasResponse.body.get.found === false || - aliasResponse.body.get._source[LEGACY_URL_ALIAS_TYPE]?.disabled === true + aliasResponse.body.get?.found === false || + aliasResponse.body.get?._source[LEGACY_URL_ALIAS_TYPE]?.disabled === true ) { // no legacy URL alias exists, or one exists but it's disabled; just attempt to get the object return this.resolveExactMatch(type, id, options); } - const legacyUrlAlias: LegacyUrlAlias = aliasResponse.body.get._source[LEGACY_URL_ALIAS_TYPE]; + + const legacyUrlAlias: LegacyUrlAlias = aliasResponse.body.get!._source[LEGACY_URL_ALIAS_TYPE]; const objectIndex = this.getIndexForType(type); - const bulkGetResponse = await this.client.mget( + const bulkGetResponse = await this.client.mget( { body: { docs: [ @@ -1090,23 +1104,28 @@ export class SavedObjectsRepository { const exactMatchDoc = bulkGetResponse?.body.docs[0]; const aliasMatchDoc = bulkGetResponse?.body.docs[1]; const foundExactMatch = + // @ts-expect-error MultiGetHit._source is optional exactMatchDoc.found && this.rawDocExistsInNamespace(exactMatchDoc, namespace); const foundAliasMatch = + // @ts-expect-error MultiGetHit._source is optional aliasMatchDoc.found && this.rawDocExistsInNamespace(aliasMatchDoc, namespace); if (foundExactMatch && foundAliasMatch) { return { + // @ts-expect-error MultiGetHit._source is optional saved_object: this.getSavedObjectFromSource(type, id, exactMatchDoc), outcome: 'conflict', aliasTargetId: legacyUrlAlias.targetId, }; } else if (foundExactMatch) { return { + // @ts-expect-error MultiGetHit._source is optional saved_object: this.getSavedObjectFromSource(type, id, exactMatchDoc), outcome: 'exactMatch', }; } else if (foundAliasMatch) { return { + // @ts-expect-error MultiGetHit._source is optional saved_object: this.getSavedObjectFromSource(type, legacyUrlAlias.targetId, aliasMatchDoc), outcome: 'aliasMatch', aliasTargetId: legacyUrlAlias.targetId, @@ -1153,7 +1172,7 @@ export class SavedObjectsRepository { }; const { body } = await this.client - .update({ + .update({ id: this._serializer.generateRawId(namespace, type, id), index: this.getIndexForType(type), ...getExpectedVersionProperties(version, preflightResult), @@ -1173,11 +1192,11 @@ export class SavedObjectsRepository { throw err; }); - const { originId } = body.get._source; - let namespaces = []; + const { originId } = body.get?._source ?? {}; + let namespaces: string[] = []; if (!this._registry.isNamespaceAgnostic(type)) { - namespaces = body.get._source.namespaces ?? [ - SavedObjectsUtils.namespaceIdToString(body.get._source.namespace), + namespaces = body.get?._source.namespaces ?? [ + SavedObjectsUtils.namespaceIdToString(body.get?._source.namespace), ]; } @@ -1185,7 +1204,6 @@ export class SavedObjectsRepository { id, type, updated_at: time, - // @ts-expect-error update doesn't have _seq_no, _primary_term as Record / any in LP version: encodeHitVersion(body), namespaces, ...(originId && { originId }), @@ -1325,7 +1343,7 @@ export class SavedObjectsRepository { return { namespaces: doc.namespaces }; } else { // if there are no namespaces remaining, delete the saved object - const { body, statusCode } = await this.client.delete( + const { body, statusCode } = await this.client.delete( { id: this._serializer.generateRawId(undefined, type, id), refresh, @@ -1343,6 +1361,7 @@ export class SavedObjectsRepository { } const deleteDocNotFound = body.result === 'not_found'; + // @ts-expect-error const deleteIndexNotFound = body.error && body.error.type === 'index_not_found_exception'; if (deleteDocNotFound || deleteIndexNotFound) { // see "404s from missing index" above @@ -1477,9 +1496,10 @@ export class SavedObjectsRepository { if (esRequestIndex !== undefined) { const indexFound = bulkGetResponse?.statusCode !== 404; const actualResult = indexFound ? bulkGetResponse?.body.docs[esRequestIndex] : undefined; - const docFound = indexFound && actualResult.found === true; + const docFound = indexFound && actualResult?.found === true; if ( !docFound || + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source !this.rawDocExistsInNamespace(actualResult, getNamespaceId(objectNamespace)) ) { return { @@ -1491,10 +1511,13 @@ export class SavedObjectsRepository { }, }; } - namespaces = actualResult._source.namespaces ?? [ - SavedObjectsUtils.namespaceIdToString(actualResult._source.namespace), + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source + namespaces = actualResult!._source.namespaces ?? [ + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source + SavedObjectsUtils.namespaceIdToString(actualResult!._source.namespace), ]; - versionProperties = getExpectedVersionProperties(version, actualResult); + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source + versionProperties = getExpectedVersionProperties(version, actualResult!); } else { if (this._registry.isSingleNamespace(type)) { // if `objectNamespace` is undefined, fall back to `options.namespace` @@ -1543,7 +1566,7 @@ export class SavedObjectsRepository { } const { type, id, namespaces, documentToSave, esRequestIndex } = expectedResult.value; - const response = bulkUpdateResponse?.body.items[esRequestIndex]; + const response = bulkUpdateResponse?.body.items[esRequestIndex] ?? {}; // When a bulk update operation is completed, any fields specified in `_sourceIncludes` will be found in the "get" value of the // returned object. We need to retrieve the `originId` if it exists so we can return it to the consumer. const { error, _seq_no: seqNo, _primary_term: primaryTerm, get } = Object.values( @@ -1636,7 +1659,7 @@ export class SavedObjectsRepository { } return { - updated: body.updated, + updated: body.updated!, }; } @@ -1745,7 +1768,7 @@ export class SavedObjectsRepository { const raw = this._serializer.savedObjectToRaw(migrated as SavedObjectSanitizedDoc); - const { body } = await this.client.update({ + const { body } = await this.client.update({ id: raw._id, index: this.getIndexForType(type), refresh, @@ -1783,17 +1806,16 @@ export class SavedObjectsRepository { }, }); - const { originId } = body.get._source; + const { originId } = body.get?._source ?? {}; return { id, type, ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), ...(originId && { originId }), updated_at: time, - references: body.get._source.references, - // @ts-expect-error + references: body.get?._source.references ?? [], version: encodeHitVersion(body), - attributes: body.get._source[type], + attributes: body.get?._source[type], }; } @@ -1852,9 +1874,13 @@ export class SavedObjectsRepository { const { body, statusCode, - } = await this.client.openPointInTime(esOptions, { - ignore: [404], - }); + } = await this.client.openPointInTime( + // @ts-expect-error @elastic/elasticsearch OpenPointInTimeRequest.index expected to accept string[] + esOptions, + { + ignore: [404], + } + ); if (statusCode === 404) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(); } @@ -1912,6 +1938,7 @@ export class SavedObjectsRepository { const { body } = await this.client.closePointInTime({ body: { id }, }); + return body; } @@ -2054,7 +2081,7 @@ export class SavedObjectsRepository { throw new Error(`Cannot make preflight get request for non-multi-namespace type '${type}'.`); } - const { body, statusCode } = await this.client.get>( + const { body, statusCode } = await this.client.get( { id: this._serializer.generateRawId(undefined, type, id), index: this.getIndexForType(type), @@ -2065,8 +2092,7 @@ export class SavedObjectsRepository { ); const indexFound = statusCode !== 404; - const docFound = indexFound && body.found === true; - if (docFound) { + if (indexFound && isFoundGetResponse(body)) { if (!this.rawDocExistsInNamespace(body, namespace)) { throw SavedObjectsErrorHelpers.createConflictError(type, id); } @@ -2091,7 +2117,7 @@ export class SavedObjectsRepository { } const rawId = this._serializer.generateRawId(undefined, type, id); - const { body, statusCode } = await this.client.get>( + const { body, statusCode } = await this.client.get( { id: rawId, index: this.getIndexForType(type), @@ -2100,17 +2126,20 @@ export class SavedObjectsRepository { ); const indexFound = statusCode !== 404; - const docFound = indexFound && body.found === true; - if (!docFound || !this.rawDocExistsInNamespace(body, namespace)) { + if ( + !indexFound || + !isFoundGetResponse(body) || + !this.rawDocExistsInNamespace(body, namespace) + ) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - return body as SavedObjectsRawDoc; + return body; } private getSavedObjectFromSource( type: string, id: string, - doc: { _seq_no: number; _primary_term: number; _source: SavedObjectsRawDocSource } + doc: { _seq_no?: number; _primary_term?: number; _source: SavedObjectsRawDocSource } ): SavedObject { const { originId, updated_at: updatedAt } = doc._source; @@ -2220,3 +2249,15 @@ const normalizeNamespace = (namespace?: string) => { const errorContent = (error: DecoratedError) => error.output.payload; const unique = (array: string[]) => [...new Set(array)]; + +/** + * Type and type guard function for converting a possibly not existant doc to an existant doc. + */ +type GetResponseFound = estypes.GetResponse & + Required< + Pick, '_primary_term' | '_seq_no' | '_version' | '_source'> + >; + +const isFoundGetResponse = ( + doc: estypes.GetResponse +): doc is GetResponseFound => doc.found; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts index 267d671361184e..b15560b82ab317 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts @@ -86,7 +86,7 @@ describe('getSearchDsl', () => { const opts = { type: 'foo', sortField: 'bar', - sortOrder: 'baz', + sortOrder: 'asc' as const, pit: { id: 'abc123' }, }; @@ -109,10 +109,10 @@ describe('getSearchDsl', () => { it('returns searchAfter if provided', () => { getQueryParams.mockReturnValue({ a: 'a' }); getSortingParams.mockReturnValue({ b: 'b' }); - expect(getSearchDsl(mappings, registry, { type: 'foo', searchAfter: [1, 'bar'] })).toEqual({ + expect(getSearchDsl(mappings, registry, { type: 'foo', searchAfter: ['1', 'bar'] })).toEqual({ a: 'a', b: 'b', - search_after: [1, 'bar'], + search_after: ['1', 'bar'], }); }); @@ -123,14 +123,14 @@ describe('getSearchDsl', () => { expect( getSearchDsl(mappings, registry, { type: 'foo', - searchAfter: [1, 'bar'], + searchAfter: ['1', 'bar'], pit: { id: 'abc123' }, }) ).toEqual({ a: 'a', b: 'b', pit: { id: 'abc123' }, - search_after: [1, 'bar'], + search_after: ['1', 'bar'], }); }); }); diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts index 9820544f02bd12..64b3dd428fb8bf 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -8,6 +8,7 @@ import Boom from '@hapi/boom'; +import type { estypes } from '@elastic/elasticsearch'; import { IndexMapping } from '../../../mappings'; import { SavedObjectsPitParams } from '../../../types'; import { getQueryParams, HasReferenceQueryParams, SearchOperator } from './query_params'; @@ -23,9 +24,9 @@ interface GetSearchDslOptions { defaultSearchOperator?: SearchOperator; searchFields?: string[]; rootSearchFields?: string[]; - searchAfter?: unknown[]; + searchAfter?: estypes.Id[]; sortField?: string; - sortOrder?: string; + sortOrder?: estypes.SortOrder; namespaces?: string[]; pit?: SavedObjectsPitParams; typeToNamespacesMap?: Map; @@ -80,6 +81,6 @@ export function getSearchDsl( }), ...getSortingParams(mappings, type, sortField, sortOrder), ...(pit ? getPitParams(pit) : {}), - ...(searchAfter ? { search_after: searchAfter } : {}), + search_after: searchAfter, }; } diff --git a/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.ts index e3bfba6a80f59d..64849c308f3f09 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { estypes } from '@elastic/elasticsearch'; import Boom from '@hapi/boom'; import { getProperty, IndexMapping } from '../../../mappings'; @@ -15,8 +16,8 @@ export function getSortingParams( mappings: IndexMapping, type: string | string[], sortField?: string, - sortOrder?: string -) { + sortOrder?: estypes.SortOrder +): { sort?: estypes.SortContainer[] } { if (!sortField) { return {}; } diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 9fa2896b7bbfe9..9a0ccb88d35558 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -162,7 +162,7 @@ export interface SavedObjectsFindResult extends SavedObject { * await savedObjectsClient.closePointInTime(page2.pit_id); * ``` */ - sort?: unknown[]; + sort?: string[]; } /** diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 11a694c72f29ff..ecda120e025d89 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { estypes } from '@elastic/elasticsearch'; import { SavedObjectsClient } from './service/saved_objects_client'; import { SavedObjectsTypeMappingDefinition } from './mappings'; import { SavedObjectMigrationMap } from './migrations'; @@ -79,7 +80,7 @@ export interface SavedObjectsFindOptions { page?: number; perPage?: number; sortField?: string; - sortOrder?: string; + sortOrder?: estypes.SortOrder; /** * An array of fields to include in the results * @example @@ -93,7 +94,7 @@ export interface SavedObjectsFindOptions { /** * Use the sort values from the previous page to retrieve the next page of results. */ - searchAfter?: unknown[]; + searchAfter?: estypes.Id[]; /** * The fields to perform the parsed query against. Unlike the `searchFields` argument, these are expected to be root fields and will not * be modified. If used in conjunction with `searchFields`, both are concatenated together. diff --git a/src/core/server/saved_objects/version/encode_hit_version.ts b/src/core/server/saved_objects/version/encode_hit_version.ts index 614666c6e1da65..979df93dc57b56 100644 --- a/src/core/server/saved_objects/version/encode_hit_version.ts +++ b/src/core/server/saved_objects/version/encode_hit_version.ts @@ -12,6 +12,6 @@ import { encodeVersion } from './encode_version'; * Helper for encoding a version from a "hit" (hits.hits[#] from _search) or * "doc" (body from GET, update, etc) object */ -export function encodeHitVersion(response: { _seq_no: number; _primary_term: number }) { +export function encodeHitVersion(response: { _seq_no?: number; _primary_term?: number }) { return encodeVersion(response._seq_no, response._primary_term); } diff --git a/src/core/server/saved_objects/version/encode_version.ts b/src/core/server/saved_objects/version/encode_version.ts index fa778ee931e417..9c0b0a7428f389 100644 --- a/src/core/server/saved_objects/version/encode_version.ts +++ b/src/core/server/saved_objects/version/encode_version.ts @@ -13,7 +13,7 @@ import { encodeBase64 } from './base64'; * that can be used in the saved object API in place of numeric * version numbers */ -export function encodeVersion(seqNo: number, primaryTerm: number) { +export function encodeVersion(seqNo?: number, primaryTerm?: number) { if (!Number.isInteger(primaryTerm)) { throw new TypeError('_primary_term from elasticsearch must be an integer'); } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 3d2023108c46a0..73f8a440751620 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -50,6 +50,7 @@ import { DetailedPeerCertificate } from 'tls'; import { Duration } from 'moment'; import { Duration as Duration_2 } from 'moment-timezone'; import { EnvironmentMode } from '@kbn/config'; +import { estypes } from '@elastic/elasticsearch'; import { ExistsParams } from 'elasticsearch'; import { ExplainParams } from 'elasticsearch'; import { FieldStatsParams } from 'elasticsearch'; @@ -2507,12 +2508,12 @@ export interface SavedObjectsFindOptions { preference?: string; rootSearchFields?: string[]; search?: string; - searchAfter?: unknown[]; + searchAfter?: estypes.Id[]; searchFields?: string[]; // (undocumented) sortField?: string; // (undocumented) - sortOrder?: string; + sortOrder?: estypes.SortOrder; // (undocumented) type: string | string[]; typeToNamespacesMap?: Map; @@ -2543,7 +2544,7 @@ export interface SavedObjectsFindResponse { // @public (undocumented) export interface SavedObjectsFindResult extends SavedObject { score: number; - sort?: unknown[]; + sort?: string[]; } // @public diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts index dc1de8d1338f1e..12dc0c1b2599d7 100644 --- a/src/plugins/data/common/search/es_search/types.ts +++ b/src/plugins/data/common/search/es_search/types.ts @@ -5,19 +5,18 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import type { estypes } from '@elastic/elasticsearch'; -import { SearchResponse } from 'elasticsearch'; -import { Search } from '@elastic/elasticsearch/api/requestParams'; import { IKibanaSearchRequest, IKibanaSearchResponse } from '../types'; export const ES_SEARCH_STRATEGY = 'es'; -export type ISearchRequestParams> = { +export type ISearchRequestParams = { trackTotalHits?: boolean; -} & Search; +} & estypes.SearchRequest; export interface IEsSearchRequest extends IKibanaSearchRequest { indexType?: string; } -export type IEsSearchResponse = IKibanaSearchResponse>; +export type IEsSearchResponse = IKibanaSearchResponse>; diff --git a/src/plugins/data/common/search/expressions/utils/courier_inspector_stats.ts b/src/plugins/data/common/search/expressions/utils/courier_inspector_stats.ts index 6013b3d6c6f5f7..99acbce8935c4a 100644 --- a/src/plugins/data/common/search/expressions/utils/courier_inspector_stats.ts +++ b/src/plugins/data/common/search/expressions/utils/courier_inspector_stats.ts @@ -14,7 +14,7 @@ */ import { i18n } from '@kbn/i18n'; -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { ISearchSource } from 'src/plugins/data/public'; import { RequestStatistics } from 'src/plugins/inspector/common'; @@ -50,7 +50,7 @@ export function getRequestInspectorStats(searchSource: ISearchSource) { /** @public */ export function getResponseInspectorStats( - resp: SearchResponse, + resp: estypes.SearchResponse, searchSource?: ISearchSource ) { const lastRequest = diff --git a/src/plugins/data/common/search/search_source/fetch/request_error.ts b/src/plugins/data/common/search/search_source/fetch/request_error.ts index 14185d7d5afd38..d8c750d011b039 100644 --- a/src/plugins/data/common/search/search_source/fetch/request_error.ts +++ b/src/plugins/data/common/search/search_source/fetch/request_error.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { KbnError } from '../../../../../kibana_utils/common'; import { SearchError } from './types'; @@ -16,8 +16,8 @@ import { SearchError } from './types'; * @param {Object} resp - optional HTTP response */ export class RequestFailure extends KbnError { - public resp?: SearchResponse; - constructor(err: SearchError | null = null, resp?: SearchResponse) { + public resp?: estypes.SearchResponse; + constructor(err: SearchError | null = null, resp?: estypes.SearchResponse) { super(`Request to Elasticsearch failed: ${JSON.stringify(resp || err?.message)}`); this.resp = resp; diff --git a/src/plugins/data/common/search/search_source/fetch/types.ts b/src/plugins/data/common/search/search_source/fetch/types.ts index 2387d9dbffa3a7..8e8a9f1025b80e 100644 --- a/src/plugins/data/common/search/search_source/fetch/types.ts +++ b/src/plugins/data/common/search/search_source/fetch/types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { LegacyFetchHandlers } from '../legacy/types'; import { GetConfigFn } from '../../../types'; @@ -25,7 +25,10 @@ export interface FetchHandlers { * Callback which can be used to hook into responses, modify them, or perform * side effects like displaying UI errors on the client. */ - onResponse: (request: SearchRequest, response: SearchResponse) => SearchResponse; + onResponse: ( + request: SearchRequest, + response: estypes.SearchResponse + ) => estypes.SearchResponse; /** * These handlers are only used by the legacy defaultSearchStrategy and can be removed * once that strategy has been deprecated. diff --git a/src/plugins/data/common/search/search_source/legacy/call_client.ts b/src/plugins/data/common/search/search_source/legacy/call_client.ts index a288cdc22c576d..4c1156aac70153 100644 --- a/src/plugins/data/common/search/search_source/legacy/call_client.ts +++ b/src/plugins/data/common/search/search_source/legacy/call_client.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { FetchHandlers, SearchRequest } from '../fetch'; import { defaultSearchStrategy } from './default_search_strategy'; import { ISearchOptions } from '../../index'; @@ -21,7 +21,7 @@ export function callClient( [SearchRequest, ISearchOptions] > = searchRequests.map((request, i) => [request, requestsOptions[i]]); const requestOptionsMap = new Map(requestOptionEntries); - const requestResponseMap = new Map>>(); + const requestResponseMap = new Map>>(); const { searching, abort } = defaultSearchStrategy.search({ searchRequests, diff --git a/src/plugins/data/common/search/search_source/legacy/fetch_soon.ts b/src/plugins/data/common/search/search_source/legacy/fetch_soon.ts index e42ef6617594ab..ff8ae2d19bd56b 100644 --- a/src/plugins/data/common/search/search_source/legacy/fetch_soon.ts +++ b/src/plugins/data/common/search/search_source/legacy/fetch_soon.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { UI_SETTINGS } from '../../../constants'; import { FetchHandlers, SearchRequest } from '../fetch'; import { ISearchOptions } from '../../index'; @@ -57,9 +57,11 @@ async function delayedFetch( options: ISearchOptions, fetchHandlers: FetchHandlers, ms: number -): Promise> { +): Promise> { if (ms === 0) { - return callClient([request], [options], fetchHandlers)[0]; + return callClient([request], [options], fetchHandlers)[0] as Promise< + estypes.SearchResponse + >; } const i = requestsToFetch.length; diff --git a/src/plugins/data/common/search/search_source/legacy/types.ts b/src/plugins/data/common/search/search_source/legacy/types.ts index 5a60d1082b0ed4..a4328528fd6625 100644 --- a/src/plugins/data/common/search/search_source/legacy/types.ts +++ b/src/plugins/data/common/search/search_source/legacy/types.ts @@ -7,8 +7,7 @@ */ import { BehaviorSubject } from 'rxjs'; -import { ApiResponse } from '@elastic/elasticsearch'; -import { SearchResponse } from 'elasticsearch'; +import type { estypes, ApiResponse } from '@elastic/elasticsearch'; import { FetchHandlers, SearchRequest } from '../fetch'; interface MsearchHeaders { @@ -28,7 +27,7 @@ export interface MsearchRequestBody { // @internal export interface MsearchResponse { - body: ApiResponse<{ responses: Array> }>; + body: ApiResponse<{ responses: Array> }>; } // @internal @@ -51,6 +50,6 @@ export interface SearchStrategyProvider { } export interface SearchStrategyResponse { - searching: Promise>>; + searching: Promise>>; abort: () => void; } diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 5dc5a8ab2ce93c..4eae5629af3a6e 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -28,6 +28,7 @@ import { DetailedPeerCertificate } from 'tls'; import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; import { ErrorToastOptions } from 'src/core/public/notifications'; +import { estypes } from '@elastic/elasticsearch'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; import { EuiComboBoxProps } from '@elastic/eui'; @@ -92,8 +93,6 @@ import { SavedObjectsFindOptions } from 'kibana/public'; import { SavedObjectsFindResponse } from 'kibana/server'; import { SavedObjectsUpdateResponse } from 'kibana/server'; import { SchemaTypeError } from '@kbn/config-schema'; -import { Search } from '@elastic/elasticsearch/api/requestParams'; -import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; import { StartServicesAccessor } from 'kibana/public'; import { ToastInputFields } from 'src/core/public/notifications'; @@ -1128,7 +1127,7 @@ export interface IEsSearchRequest extends IKibanaSearchRequest = IKibanaSearchResponse>; +export type IEsSearchResponse = IKibanaSearchResponse>; // Warning: (ae-missing-release-tag) "IFieldFormat" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -2415,9 +2414,9 @@ export class SearchSource { createChild(options?: {}): SearchSource; createCopy(): SearchSource; destroy(): void; - fetch$(options?: ISearchOptions): import("rxjs").Observable>; + fetch$(options?: ISearchOptions): import("rxjs").Observable>; // @deprecated - fetch(options?: ISearchOptions): Promise>; + fetch(options?: ISearchOptions): Promise>; getField(field: K, recurse?: boolean): SearchSourceFields[K]; getFields(): SearchSourceFields; getId(): string; diff --git a/src/plugins/data/public/search/expressions/es_raw_response.ts b/src/plugins/data/public/search/expressions/es_raw_response.ts index 6b44a7afb6d673..2d12af017d88c5 100644 --- a/src/plugins/data/public/search/expressions/es_raw_response.ts +++ b/src/plugins/data/public/search/expressions/es_raw_response.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { ExpressionTypeDefinition } from '../../../../expressions/common'; const name = 'es_raw_response'; export interface EsRawResponse { type: typeof name; - body: SearchResponse; + body: estypes.SearchResponse; } // flattens elasticsearch object into table rows @@ -46,11 +46,11 @@ function flatten(obj: any, keyPrefix = '') { } } -const parseRawDocs = (hits: SearchResponse['hits']) => { +const parseRawDocs = (hits: estypes.SearchResponse['hits']) => { return hits.hits.map((hit) => hit.fields || hit._source).filter((hit) => hit); }; -const convertResult = (body: SearchResponse) => { +const convertResult = (body: estypes.SearchResponse) => { return !body.aggregations ? parseRawDocs(body.hits) : flatten(body.aggregations); }; diff --git a/src/plugins/data/public/search/fetch/handle_response.tsx b/src/plugins/data/public/search/fetch/handle_response.tsx index 00d5b11089d62c..57ee5737e50a29 100644 --- a/src/plugins/data/public/search/fetch/handle_response.tsx +++ b/src/plugins/data/public/search/fetch/handle_response.tsx @@ -9,13 +9,13 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer } from '@elastic/eui'; -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { ShardFailureOpenModalButton } from '../../ui/shard_failure_modal'; import { toMountPoint } from '../../../../kibana_react/public'; import { getNotifications } from '../../services'; import { SearchRequest } from '..'; -export function handleResponse(request: SearchRequest, response: SearchResponse) { +export function handleResponse(request: SearchRequest, response: estypes.SearchResponse) { if (response.timed_out) { getNotifications().toasts.addWarning({ title: i18n.translate('data.search.searchSource.fetch.requestTimedOutNotificationMessage', { diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx index f510420cb30e8a..8e6ad4bc92c8f3 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx @@ -21,14 +21,14 @@ import { EuiButtonEmpty, EuiCallOut, } from '@elastic/eui'; -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { ShardFailureTable } from './shard_failure_table'; import { ShardFailureRequest } from './shard_failure_types'; export interface Props { onClose: () => void; request: ShardFailureRequest; - response: SearchResponse; + response: estypes.SearchResponse; title: string; } diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.tsx index 0907d6607579f9..a230378d6c3d3c 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.tsx @@ -9,8 +9,8 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, EuiTextAlign } from '@elastic/eui'; +import type { estypes } from '@elastic/elasticsearch'; -import { SearchResponse } from 'elasticsearch'; import { getOverlays } from '../../services'; import { toMountPoint } from '../../../../kibana_react/public'; import { ShardFailureModal } from './shard_failure_modal'; @@ -19,7 +19,7 @@ import { ShardFailureRequest } from './shard_failure_types'; // @internal export interface ShardFailureOpenModalButtonProps { request: ShardFailureRequest; - response: SearchResponse; + response: estypes.SearchResponse; title: string; } diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index 489a23eb83897c..bdcc13ce4c061a 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -12,7 +12,8 @@ import { IRouter, SharedGlobalConfig } from 'kibana/server'; import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; -import { IFieldType, Filter } from '../index'; +import type { estypes } from '@elastic/elasticsearch'; +import type { IFieldType } from '../index'; import { findIndexPatternById, getFieldByName } from '../index_patterns'; import { getRequestAbortedSignal } from '../lib'; @@ -73,7 +74,7 @@ async function getBody( { timeout, terminate_after }: Record, field: IFieldType | string, query: string, - filters: Filter[] = [] + filters: estypes.QueryContainer[] = [] ) { const isFieldObject = (f: any): f is IFieldType => Boolean(f && f.name); @@ -82,7 +83,7 @@ async function getBody( q.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`); // Helps ensure that the regex is not evaluated eagerly against the terms dictionary - const executionHint = 'map'; + const executionHint = 'map' as const; // We don't care about the accuracy of the counts, just the content of the terms, so this reduces // the amount of information that needs to be transmitted to the coordinating node diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts index db950e7aa48f94..69a9280dd93d88 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts @@ -8,17 +8,6 @@ import { ElasticsearchClient } from 'kibana/server'; import { convertEsError } from './errors'; -import { FieldCapsResponse } from './field_capabilities'; - -export interface IndicesAliasResponse { - [index: string]: IndexAliasResponse; -} - -export interface IndexAliasResponse { - aliases: { - [aliasName: string]: Record; - }; -} /** * Call the index.getAlias API for a list of indices. @@ -67,7 +56,7 @@ export async function callFieldCapsApi( fieldCapsOptions: { allow_no_indices: boolean } = { allow_no_indices: false } ) { try { - return await callCluster.fieldCaps({ + return await callCluster.fieldCaps({ index: indices, fields: '*', ignore_unavailable: true, diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts index 31fd60b0382aa7..c4c1ffa3cf9f91 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts @@ -7,23 +7,11 @@ */ import { uniq } from 'lodash'; +import type { estypes } from '@elastic/elasticsearch'; import { castEsToKbnFieldTypeName } from '../../../../../common'; import { shouldReadFieldFromDocValues } from './should_read_field_from_doc_values'; import { FieldDescriptor } from '../../../fetcher'; -interface FieldCapObject { - type: string; - searchable: boolean; - aggregatable: boolean; - indices?: string[]; - non_searchable_indices?: string[]; - non_aggregatable_indices?: string[]; -} - -export interface FieldCapsResponse { - fields: Record>; -} - /** * Read the response from the _field_caps API to determine the type and * "aggregatable"/"searchable" status of each field. @@ -80,7 +68,9 @@ export interface FieldCapsResponse { * @param {FieldCapsResponse} fieldCapsResponse * @return {Array} */ -export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): FieldDescriptor[] { +export function readFieldCapsResponse( + fieldCapsResponse: estypes.FieldCapabilitiesResponse +): FieldDescriptor[] { const capsByNameThenType = fieldCapsResponse.fields; const kibanaFormattedCaps = Object.keys(capsByNameThenType).reduce<{ diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/index.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/index.ts index d7150d81e3803b..773a615727ee5f 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/index.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/index.ts @@ -7,5 +7,4 @@ */ export { getFieldCapabilities } from './field_capabilities'; -export { FieldCapsResponse } from './field_caps_response'; export { shouldReadFieldFromDocValues } from './should_read_field_from_doc_values'; diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts index d01f74429c3a53..32b9d8c7f893f3 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts @@ -12,7 +12,7 @@ import moment from 'moment'; import { ElasticsearchClient } from 'kibana/server'; import { timePatternToWildcard } from './time_pattern_to_wildcard'; -import { callIndexAliasApi, IndicesAliasResponse } from './es_api'; +import { callIndexAliasApi } from './es_api'; /** * Convert a time pattern into a list of indexes it could @@ -28,7 +28,7 @@ import { callIndexAliasApi, IndicesAliasResponse } from './es_api'; export async function resolveTimePattern(callCluster: ElasticsearchClient, timePattern: string) { const aliases = await callIndexAliasApi(callCluster, timePatternToWildcard(timePattern)); - const allIndexDetails = chain(aliases.body) + const allIndexDetails = chain(aliases.body) .reduce( (acc: string[], index: any, indexName: string) => acc.concat(indexName, Object.keys(index.aliases || {})), diff --git a/src/plugins/data/server/search/collectors/fetch.ts b/src/plugins/data/server/search/collectors/fetch.ts index 6dfc29e2cf2a66..aed35d73c7eb64 100644 --- a/src/plugins/data/server/search/collectors/fetch.ts +++ b/src/plugins/data/server/search/collectors/fetch.ts @@ -9,18 +9,16 @@ import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; import { SharedGlobalConfig } from 'kibana/server'; -import { SearchResponse } from 'elasticsearch'; import { CollectorFetchContext } from 'src/plugins/usage_collection/server'; import { CollectedUsage, ReportedUsage } from './register'; interface SearchTelemetry { 'search-telemetry': CollectedUsage; } -type ESResponse = SearchResponse; export function fetchProvider(config$: Observable) { return async ({ esClient }: CollectorFetchContext): Promise => { const config = await config$.pipe(first()).toPromise(); - const { body: esResponse } = await esClient.search( + const { body: esResponse } = await esClient.search( { index: config.kibana.index, body: { @@ -37,7 +35,7 @@ export function fetchProvider(config$: Observable) { averageDuration: null, }; } - const { successCount, errorCount, totalDuration } = esResponse.hits.hits[0]._source[ + const { successCount, errorCount, totalDuration } = esResponse.hits.hits[0]._source![ 'search-telemetry' ]; const averageDuration = totalDuration / successCount; diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index cc81dce94c4ec6..1afe6275452483 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -8,7 +8,6 @@ import { from, Observable } from 'rxjs'; import { first, tap } from 'rxjs/operators'; -import type { SearchResponse } from 'elasticsearch'; import type { Logger, SharedGlobalConfig } from 'kibana/server'; import type { ISearchStrategy } from '../types'; import type { SearchUsage } from '../collectors'; @@ -44,7 +43,7 @@ export const esSearchStrategyProvider = ( ...getShardTimeout(config), ...request.params, }; - const promise = esClient.asCurrentUser.search>(params); + const promise = esClient.asCurrentUser.search(params); const { body } = await shimAbortSignal(promise, abortSignal); const response = shimHitsTotal(body, options); return toKibanaSearchResponse(response); diff --git a/src/plugins/data/server/search/es_search/response_utils.ts b/src/plugins/data/server/search/es_search/response_utils.ts index 975ce392656b12..3bee63624ef673 100644 --- a/src/plugins/data/server/search/es_search/response_utils.ts +++ b/src/plugins/data/server/search/es_search/response_utils.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { ISearchOptions } from '../../../common'; /** @@ -14,7 +14,7 @@ import { ISearchOptions } from '../../../common'; * not included as it is already included in `successful`. * @internal */ -export function getTotalLoaded(response: SearchResponse) { +export function getTotalLoaded(response: estypes.SearchResponse) { const { total, failed, successful } = response._shards; const loaded = failed + successful; return { total, loaded }; @@ -24,7 +24,7 @@ export function getTotalLoaded(response: SearchResponse) { * Get the Kibana representation of this response (see `IKibanaSearchResponse`). * @internal */ -export function toKibanaSearchResponse(rawResponse: SearchResponse) { +export function toKibanaSearchResponse(rawResponse: estypes.SearchResponse) { return { rawResponse, isPartial: false, @@ -41,7 +41,7 @@ export function toKibanaSearchResponse(rawResponse: SearchResponse) { * @internal */ export function shimHitsTotal( - response: SearchResponse, + response: estypes.SearchResponse, { legacyHitsTotal = true }: ISearchOptions = {} ) { if (!legacyHitsTotal) return response; diff --git a/src/plugins/data/server/search/routes/call_msearch.ts b/src/plugins/data/server/search/routes/call_msearch.ts index 8648d3f4526fec..0c238adf831bd9 100644 --- a/src/plugins/data/server/search/routes/call_msearch.ts +++ b/src/plugins/data/server/search/routes/call_msearch.ts @@ -8,7 +8,6 @@ import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; -import { SearchResponse } from 'elasticsearch'; import { IUiSettingsClient, IScopedClusterClient, SharedGlobalConfig } from 'src/core/server'; import type { MsearchRequestBody, MsearchResponse } from '../../../common/search/search_source'; @@ -66,6 +65,7 @@ export function getCallMsearch(dependencies: CallMsearchDependencies) { try { const promise = esClient.asCurrentUser.msearch( { + // @ts-expect-error @elastic/elasticsearch client types don't support plain string bodies body: convertRequestBody(params.body, timeout), }, { @@ -78,9 +78,7 @@ export function getCallMsearch(dependencies: CallMsearchDependencies) { body: { ...response, body: { - responses: response.body.responses?.map((r: SearchResponse) => - shimHitsTotal(r) - ), + responses: response.body.responses?.map((r) => shimHitsTotal(r)), }, }, }; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index e04fcdfa08f363..12458d7a74d9fb 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -25,6 +25,7 @@ import { ElasticsearchClient as ElasticsearchClient_2 } from 'kibana/server'; import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; import { ErrorToastOptions } from 'src/core/public/notifications'; +import { estypes } from '@elastic/elasticsearch'; import { ExecutionContext } from 'src/plugins/expressions/common'; import { ExpressionAstExpression } from 'src/plugins/expressions/common'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; @@ -64,7 +65,6 @@ import { SavedObjectsFindOptions } from 'kibana/server'; import { SavedObjectsFindResponse } from 'kibana/server'; import { SavedObjectsUpdateResponse } from 'kibana/server'; import { Search } from '@elastic/elasticsearch/api/requestParams'; -import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; import { SharedGlobalConfig as SharedGlobalConfig_2 } from 'kibana/server'; import { ToastInputFields } from 'src/core/public/notifications'; @@ -596,7 +596,7 @@ export function getTime(indexPattern: IIndexPattern | undefined, timeRange: Time }): import("../..").RangeFilter | undefined; // @internal -export function getTotalLoaded(response: SearchResponse): { +export function getTotalLoaded(response: estypes.SearchResponse): { total: number; loaded: number; }; @@ -631,7 +631,7 @@ export interface IEsSearchRequest extends IKibanaSearchRequest = IKibanaSearchResponse>; +export type IEsSearchResponse = IKibanaSearchResponse>; // Warning: (ae-missing-release-tag) "IFieldFormatsRegistry" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1384,30 +1384,26 @@ export function searchUsageObserver(logger: Logger_2, usage?: SearchUsage, { isR export const shimAbortSignal: (promise: TransportRequestPromise, signal?: AbortSignal | undefined) => TransportRequestPromise; // @internal -export function shimHitsTotal(response: SearchResponse, { legacyHitsTotal }?: ISearchOptions): { +export function shimHitsTotal(response: estypes.SearchResponse, { legacyHitsTotal }?: ISearchOptions): { hits: { total: any; - max_score: number; - hits: { - _index: string; - _type: string; - _id: string; - _score: number; - _source: unknown; - _version?: number | undefined; - _explanation?: import("elasticsearch").Explanation | undefined; - fields?: any; - highlight?: any; - inner_hits?: any; - matched_queries?: string[] | undefined; - sort?: string[] | undefined; - }[]; + hits: estypes.Hit[]; + max_score?: number | undefined; }; took: number; timed_out: boolean; + _shards: estypes.ShardStatistics; + aggregations?: Record | undefined; + _clusters?: estypes.ClusterStatistics | undefined; + documents?: unknown[] | undefined; + fields?: Record | undefined; + max_score?: number | undefined; + num_reduce_phases?: number | undefined; + profile?: estypes.Profile | undefined; + pit_id?: string | undefined; _scroll_id?: string | undefined; - _shards: import("elasticsearch").ShardsResponse; - aggregations?: any; + suggest?: Record[]> | undefined; + terminated_early?: boolean | undefined; }; // Warning: (ae-missing-release-tag) "shouldReadFieldFromDocValues" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1425,10 +1421,10 @@ export type TimeRange = { }; // @internal -export function toKibanaSearchResponse(rawResponse: SearchResponse): { +export function toKibanaSearchResponse(rawResponse: estypes.SearchResponse): { total: number; loaded: number; - rawResponse: SearchResponse; + rawResponse: estypes.SearchResponse; isPartial: boolean; isRunning: boolean; }; diff --git a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx b/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx index ba4d56b9355123..0202f88e0e9029 100644 --- a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx @@ -9,6 +9,7 @@ import angular, { auto, ICompileService, IScope } from 'angular'; import { render } from 'react-dom'; import React, { useRef, useEffect, useState, useCallback } from 'react'; +import type { estypes } from '@elastic/elasticsearch'; import { EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { getServices, IIndexPattern } from '../../../kibana_services'; @@ -20,7 +21,7 @@ export interface DocTableLegacyProps { searchDescription?: string; searchTitle?: string; onFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; - rows: Array>; + rows: estypes.Hit[]; indexPattern: IIndexPattern; minimumVisibleRows: number; onAddColumn?: (column: string) => void; diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx b/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx index 8dded3598c2793..5031f78c49fcca 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx @@ -87,6 +87,7 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { minimumVisibleRows, useNewFieldsApi, } = renderProps; + // @ts-expect-error doesn't implement full DocTableLegacyProps interface return { columns, indexPattern, diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx index 87b9c6243abd84..dbc94e5021294c 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx @@ -140,7 +140,7 @@ export function DiscoverGridFlyout({ iconType="documents" flush="left" href={getContextUrl( - hit._id, + String(hit._id), indexPattern.id, columns, services.filterManager, diff --git a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx index f1025a0881d1f6..74cf083d82653f 100644 --- a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { ReactWrapper, shallow } from 'enzyme'; import { getRenderCellValueFn } from './get_render_cell_value'; import { indexPatternMock } from '../../../__mocks__/index_pattern'; +import { ElasticSearchHit } from '../../doc_views/doc_views_types'; jest.mock('../../../../../kibana_react/public', () => ({ useUiSetting: () => true, @@ -26,7 +27,7 @@ jest.mock('../../../kibana_services', () => ({ }), })); -const rowsSource = [ +const rowsSource: ElasticSearchHit[] = [ { _id: '1', _index: 'test', @@ -34,12 +35,12 @@ const rowsSource = [ _score: 1, _source: { bytes: 100, extension: '.gz' }, highlight: { - extension: '@kibana-highlighted-field.gz@/kibana-highlighted-field', + extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], }, }, ]; -const rowsFields = [ +const rowsFields: ElasticSearchHit[] = [ { _id: '1', _index: 'test', @@ -48,12 +49,12 @@ const rowsFields = [ _source: undefined, fields: { bytes: [100], extension: ['.gz'] }, highlight: { - extension: '@kibana-highlighted-field.gz@/kibana-highlighted-field', + extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], }, }, ]; -const rowsFieldsWithTopLevelObject = [ +const rowsFieldsWithTopLevelObject: ElasticSearchHit[] = [ { _id: '1', _index: 'test', @@ -62,7 +63,7 @@ const rowsFieldsWithTopLevelObject = [ _source: undefined, fields: { 'object.value': [100], extension: ['.gz'] }, highlight: { - extension: '@kibana-highlighted-field.gz@/kibana-highlighted-field', + extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], }, }, ]; @@ -167,7 +168,9 @@ describe('Discover grid cell rendering', function () { }, "_type": "test", "highlight": Object { - "extension": "@kibana-highlighted-field.gz@/kibana-highlighted-field", + "extension": Array [ + "@kibana-highlighted-field.gz@/kibana-highlighted-field", + ], }, } } @@ -264,7 +267,9 @@ describe('Discover grid cell rendering', function () { ], }, "highlight": Object { - "extension": "@kibana-highlighted-field.gz@/kibana-highlighted-field", + "extension": Array [ + "@kibana-highlighted-field.gz@/kibana-highlighted-field", + ], }, } } diff --git a/src/plugins/discover/public/application/components/table/table.test.tsx b/src/plugins/discover/public/application/components/table/table.test.tsx index 6de41aa0643a58..8997c1d13a4747 100644 --- a/src/plugins/discover/public/application/components/table/table.test.tsx +++ b/src/plugins/discover/public/application/components/table/table.test.tsx @@ -87,7 +87,7 @@ describe('DocViewTable at Discover', () => { scripted: 123, _underscore: 123, }, - }; + } as any; const props = { hit, @@ -185,7 +185,7 @@ describe('DocViewTable at Discover Context', () => { Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. \ Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut', }, - }; + } as any; const props = { hit, columns: ['extension'], diff --git a/src/plugins/discover/public/application/doc_views/doc_views_types.ts b/src/plugins/discover/public/application/doc_views/doc_views_types.ts index b06b242ee9ea3c..02ac951f7f57ca 100644 --- a/src/plugins/discover/public/application/doc_views/doc_views_types.ts +++ b/src/plugins/discover/public/application/doc_views/doc_views_types.ts @@ -8,7 +8,7 @@ import { ComponentType } from 'react'; import { IScope } from 'angular'; -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { IndexPattern } from '../../../../data/public'; export interface AngularDirective { @@ -18,7 +18,7 @@ export interface AngularDirective { export type AngularScope = IScope; -export type ElasticSearchHit = SearchResponse['hits']['hits'][number]; +export type ElasticSearchHit = estypes.SearchResponse['hits']['hits'][number]; export interface FieldMapping { filterable?: boolean; diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index 829d23fa071fb4..e7349ed22355ab 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -357,7 +357,7 @@ export class SearchEmbeddable // Apply the changes to the angular scope this.searchScope.$apply(() => { this.searchScope!.hits = resp.hits.hits; - this.searchScope!.totalHitCount = resp.hits.total; + this.searchScope!.totalHitCount = resp.hits.total as number; this.searchScope!.isLoading = false; }); } catch (error) { diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 189f71b85206bc..b9719542adc813 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -15,6 +15,7 @@ import * as CSS from 'csstype'; import { DetailedPeerCertificate } from 'tls'; import { EmbeddableStart as EmbeddableStart_2 } from 'src/plugins/embeddable/public/plugin'; import { EnvironmentMode } from '@kbn/config'; +import { estypes } from '@elastic/elasticsearch'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; diff --git a/src/plugins/index_pattern_management/server/routes/preview_scripted_field.test.ts b/src/plugins/index_pattern_management/server/routes/preview_scripted_field.test.ts index 385b4f04c40f15..1343b20365a442 100644 --- a/src/plugins/index_pattern_management/server/routes/preview_scripted_field.test.ts +++ b/src/plugins/index_pattern_management/server/routes/preview_scripted_field.test.ts @@ -46,8 +46,8 @@ describe('preview_scripted_field route', () => { expect(mockClient.search.mock.calls[0][0]).toMatchInlineSnapshot(` Object { - "_source": undefined, "body": Object { + "_source": undefined, "query": Object { "match_all": Object {}, }, @@ -59,10 +59,10 @@ describe('preview_scripted_field route', () => { }, }, }, + "size": 10, + "timeout": "30s", }, "index": "kibana_sample_data_logs", - "size": 10, - "timeout": "30s", } `); @@ -102,12 +102,12 @@ describe('preview_scripted_field route', () => { expect(mockClient.search.mock.calls[0][0]).toMatchInlineSnapshot(` Object { - "_source": Array [ - "a", - "b", - "c", - ], "body": Object { + "_source": Array [ + "a", + "b", + "c", + ], "query": Object { "bool": Object { "some": "query", @@ -121,10 +121,10 @@ describe('preview_scripted_field route', () => { }, }, }, + "size": 10, + "timeout": "30s", }, "index": "kibana_sample_data_logs", - "size": 10, - "timeout": "30s", } `); }); diff --git a/src/plugins/index_pattern_management/server/routes/preview_scripted_field.ts b/src/plugins/index_pattern_management/server/routes/preview_scripted_field.ts index 276f6dc0db8bf7..cc161859f4189e 100644 --- a/src/plugins/index_pattern_management/server/routes/preview_scripted_field.ts +++ b/src/plugins/index_pattern_management/server/routes/preview_scripted_field.ts @@ -30,10 +30,10 @@ export function registerPreviewScriptedFieldRoute(router: IRouter): void { try { const response = await client.search({ index, - _source: additionalFields && additionalFields.length > 0 ? additionalFields : undefined, - size: 10, - timeout: '30s', body: { + _source: additionalFields && additionalFields.length > 0 ? additionalFields : undefined, + size: 10, + timeout: '30s', query: query ?? { match_all: {} }, script_fields: { [name]: { diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts index 52ba793882a1d8..42363f71ef87aa 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts @@ -58,6 +58,7 @@ export async function getSavedObjectsCounts( }; const { body } = await esClient.search(savedObjectCountSearchParams); const buckets: Array<{ key: string; doc_count: number }> = + // @ts-expect-error @elastic/elasticsearch Aggregate does not include `buckets` body.aggregations?.types?.buckets || []; // Initialise the object with all zeros for all the types diff --git a/src/plugins/security_oss/server/check_cluster_data.test.ts b/src/plugins/security_oss/server/check_cluster_data.test.ts index 0670eb3116b071..9e9459a68754cc 100644 --- a/src/plugins/security_oss/server/check_cluster_data.test.ts +++ b/src/plugins/security_oss/server/check_cluster_data.test.ts @@ -27,6 +27,7 @@ describe('checkClusterForUserData', () => { it('returns false if data only exists in system indices', async () => { const esClient = elasticsearchServiceMock.createElasticsearchClient(); esClient.cat.indices.mockResolvedValue( + // @ts-expect-error @elastic/elasticsearch ES types don't support array response format elasticsearchServiceMock.createApiResponse({ body: [ { @@ -55,6 +56,7 @@ describe('checkClusterForUserData', () => { it('returns true if data exists in non-system indices', async () => { const esClient = elasticsearchServiceMock.createElasticsearchClient(); esClient.cat.indices.mockResolvedValue( + // @ts-expect-error @elastic/elasticsearch ES types don't support array response format elasticsearchServiceMock.createApiResponse({ body: [ { @@ -85,6 +87,7 @@ describe('checkClusterForUserData', () => { ) .mockRejectedValueOnce(new Error('something terrible happened')) .mockResolvedValueOnce( + // @ts-expect-error @elastic/elasticsearch ES types don't support array response format elasticsearchServiceMock.createApiResponse({ body: [ { @@ -95,6 +98,7 @@ describe('checkClusterForUserData', () => { }) ) .mockResolvedValueOnce( + // @ts-expect-error @elastic/elasticsearch ES types don't support array response format elasticsearchServiceMock.createApiResponse({ body: [ { diff --git a/src/plugins/security_oss/server/check_cluster_data.ts b/src/plugins/security_oss/server/check_cluster_data.ts index c8c30196b485c0..19a4145333dd0c 100644 --- a/src/plugins/security_oss/server/check_cluster_data.ts +++ b/src/plugins/security_oss/server/check_cluster_data.ts @@ -14,17 +14,15 @@ export const createClusterDataCheck = () => { return async function doesClusterHaveUserData(esClient: ElasticsearchClient, log: Logger) { if (!clusterHasUserData) { try { - const indices = await esClient.cat.indices< - Array<{ index: string; ['docs.count']: string }> - >({ + const indices = await esClient.cat.indices({ format: 'json', h: ['index', 'docs.count'], }); clusterHasUserData = indices.body.some((indexCount) => { const isInternalIndex = - indexCount.index.startsWith('.') || indexCount.index.startsWith('kibana_sample_'); + indexCount.index?.startsWith('.') || indexCount.index?.startsWith('kibana_sample_'); - return !isInternalIndex && parseInt(indexCount['docs.count'], 10) > 0; + return !isInternalIndex && parseInt(indexCount['docs.count']!, 10) > 0; }); } catch (e) { log.warn(`Error encountered while checking cluster for user data: ${e}`); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts b/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts index 437d76fe7ccf2b..3f93bde1e7e625 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts @@ -8,22 +8,6 @@ import { ElasticsearchClient } from 'src/core/server'; -// This can be removed when the ES client improves the types -export interface ESClusterInfo { - cluster_uuid: string; - cluster_name: string; - version: { - number: string; - build_flavor?: string; - build_type?: string; - build_hash?: string; - build_date?: string; - build_snapshot?: boolean; - lucene_version?: string; - minimum_wire_compatibility_version?: string; - minimum_index_compatibility_version?: string; - }; -} /** * Get the cluster info from the connected cluster. * @@ -32,6 +16,6 @@ export interface ESClusterInfo { * @param {function} esClient The asInternalUser handler (exposed for testing) */ export async function getClusterInfo(esClient: ElasticsearchClient) { - const { body } = await esClient.info(); + const { body } = await esClient.info(); return body; } diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts index 42ccbcc46c4627..c79c46072e11be 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts @@ -261,14 +261,16 @@ export async function getDataTelemetry(esClient: ElasticsearchClient) { const indices = indexNames.map((name) => { const baseIndexInfo = { name, - isECS: !!indexMappings[name]?.mappings?.properties.ecs?.properties.version?.type, + isECS: !!indexMappings[name]?.mappings?.properties?.ecs?.properties?.version?.type, shipper: indexMappings[name]?.mappings?._meta?.beat, packageName: indexMappings[name]?.mappings?._meta?.package?.name, managedBy: indexMappings[name]?.mappings?._meta?.managed_by, dataStreamDataset: - indexMappings[name]?.mappings?.properties.data_stream?.properties.dataset?.value, + // @ts-expect-error @elastic/elasticsearch PropertyBase doesn't decalre value + indexMappings[name]?.mappings?.properties?.data_stream?.properties?.dataset?.value, dataStreamType: - indexMappings[name]?.mappings?.properties.data_stream?.properties.type?.value, + // @ts-expect-error @elastic/elasticsearch PropertyBase doesn't decalre value + indexMappings[name]?.mappings?.properties?.data_stream?.properties?.type?.value, }; const stats = (indexStats?.indices || {})[name]; diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts index 47c6736ff9aea4..edf8dbb30809bf 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts @@ -7,6 +7,7 @@ */ import { merge, omit } from 'lodash'; +import type { estypes } from '@elastic/elasticsearch'; import { getLocalStats, handleLocalStats } from './get_local_stats'; import { @@ -34,35 +35,33 @@ function mockGetLocalStats(clusterInfo: any, clusterStats: any) { esClient.cluster.stats // @ts-expect-error we only care about the response body .mockResolvedValue({ body: { ...clusterStats } }); - esClient.nodes.usage.mockResolvedValue( + esClient.nodes.usage.mockResolvedValue({ // @ts-expect-error we only care about the response body - { - body: { - cluster_name: 'testCluster', - nodes: { - some_node_id: { - timestamp: 1588617023177, - since: 1588616945163, - rest_actions: { - nodes_usage_action: 1, - create_index_action: 1, - document_get_action: 1, - search_action: 19, - nodes_info_action: 36, + body: { + cluster_name: 'testCluster', + nodes: { + some_node_id: { + timestamp: 1588617023177, + since: 1588616945163, + rest_actions: { + nodes_usage_action: 1, + create_index_action: 1, + document_get_action: 1, + search_action: 19, + nodes_info_action: 36, + }, + aggregations: { + scripted_metric: { + other: 7, }, - aggregations: { - terms: { - bytes: 2, - }, - scripted_metric: { - other: 7, - }, + terms: { + bytes: 2, }, }, }, }, - } - ); + }, + }); // @ts-expect-error we only care about the response body esClient.indices.getMapping.mockResolvedValue({ body: { mappings: {} } }); // @ts-expect-error we only care about the response body @@ -188,7 +187,7 @@ describe('get_local_stats', () => { describe('handleLocalStats', () => { it('returns expected object without xpack or kibana data', () => { const result = handleLocalStats( - clusterInfo, + clusterInfo as estypes.RootNodeInfoResponse, clusterStatsWithNodesUsage, void 0, void 0, @@ -205,7 +204,7 @@ describe('get_local_stats', () => { it('returns expected object with xpack', () => { const result = handleLocalStats( - clusterInfo, + clusterInfo as estypes.RootNodeInfoResponse, clusterStatsWithNodesUsage, void 0, void 0, diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts index 710d836576d105..67f9ebb8ff3e45 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts @@ -6,11 +6,12 @@ * Side Public License, v 1. */ +import type { estypes } from '@elastic/elasticsearch'; import { StatsGetter, StatsCollectionContext, } from 'src/plugins/telemetry_collection_manager/server'; -import { getClusterInfo, ESClusterInfo } from './get_cluster_info'; +import { getClusterInfo } from './get_cluster_info'; import { getClusterStats } from './get_cluster_stats'; import { getKibana, handleKibanaStats, KibanaUsageStats } from './get_kibana'; import { getNodesUsage } from './get_nodes_usage'; @@ -27,7 +28,7 @@ import { getDataTelemetry, DATA_TELEMETRY_ID, DataTelemetryPayload } from './get */ export function handleLocalStats( // eslint-disable-next-line @typescript-eslint/naming-convention - { cluster_name, cluster_uuid, version }: ESClusterInfo, + { cluster_name, cluster_uuid, version }: estypes.RootNodeInfoResponse, { _nodes, cluster_name: clusterName, ...clusterStats }: any, kibana: KibanaUsageStats | undefined, dataTelemetry: DataTelemetryPayload | undefined, diff --git a/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts index 18c6d16447238e..e46d4be5407349 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts @@ -16,7 +16,7 @@ export interface NodeAggregation { // we set aggregations as an optional type because it was only added in v7.8.0 export interface NodeObj { node_id?: string; - timestamp: number; + timestamp: number | string; since: number; rest_actions: { [key: string]: number; @@ -46,9 +46,10 @@ export type NodesUsageGetter = ( export async function fetchNodesUsage( esClient: ElasticsearchClient ): Promise { - const { body } = await esClient.nodes.usage({ + const { body } = await esClient.nodes.usage({ timeout: TIMEOUT, }); + // @ts-expect-error TODO: Does the client parse `timestamp` to a Date object? Expected a number return body; } diff --git a/src/plugins/vis_type_vega/public/data_model/types.ts b/src/plugins/vis_type_vega/public/data_model/types.ts index 042ffac583e987..8590b51d3b5ffe 100644 --- a/src/plugins/vis_type_vega/public/data_model/types.ts +++ b/src/plugins/vis_type_vega/public/data_model/types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SearchResponse, SearchParams } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { Filter } from 'src/plugins/data/public'; import { DslQuery } from 'src/plugins/data/common'; @@ -17,7 +17,7 @@ import { EmsFileParser } from './ems_file_parser'; import { UrlParser } from './url_parser'; interface Body { - aggs?: SearchParams['body']['aggs']; + aggs?: Record; query?: Query; timeout?: string; } @@ -76,7 +76,7 @@ interface Projection { interface RequestDataObject { name?: string; url?: TUrlData; - values: SearchResponse; + values: estypes.SearchResponse; } type ContextVarsObjectProps = diff --git a/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.ts b/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.ts index 8d3512aa2138e5..d5f8d978d52521 100644 --- a/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.ts +++ b/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.ts @@ -7,14 +7,12 @@ */ import { parse } from 'hjson'; -import { SearchResponse } from 'elasticsearch'; import { ElasticsearchClient, SavedObject } from 'src/core/server'; import { VegaSavedObjectAttributes, VisTypeVegaPluginSetupDependencies } from '../types'; type UsageCollectorDependencies = Pick; -type ESResponse = SearchResponse<{ visualization: { visState: string } }>; type VegaType = 'vega' | 'vega-lite'; function isVegaType(attributes: any): attributes is VegaSavedObjectAttributes { @@ -80,7 +78,9 @@ export const getStats = async ( }, }; - const { body: esResponse } = await esClient.search(searchParams); + const { body: esResponse } = await esClient.search<{ visualization: { visState: string } }>( + searchParams + ); const size = esResponse?.hits?.hits?.length ?? 0; if (!size) { diff --git a/src/plugins/visualizations/server/usage_collector/get_usage_collector.ts b/src/plugins/visualizations/server/usage_collector/get_usage_collector.ts index 164d5b5fa72ac6..89e1e7f03e1495 100644 --- a/src/plugins/visualizations/server/usage_collector/get_usage_collector.ts +++ b/src/plugins/visualizations/server/usage_collector/get_usage_collector.ts @@ -61,7 +61,7 @@ export async function getStats( // `map` to get the raw types const visSummaries: VisSummary[] = esResponse.hits.hits.map((hit) => { - const spacePhrases = hit._id.split(':'); + const spacePhrases = hit._id.toString().split(':'); const lastUpdated: string = get(hit, '_source.updated_at'); const space = spacePhrases.length === 3 ? spacePhrases[0] : 'default'; // if in a custom space, the format of a saved object ID is space:type:id const visualization = get(hit, '_source.visualization', { visState: '{}' }); diff --git a/test/api_integration/apis/home/sample_data.ts b/test/api_integration/apis/home/sample_data.ts index b889b59fdaf329..99327901ec8c35 100644 --- a/test/api_integration/apis/home/sample_data.ts +++ b/test/api_integration/apis/home/sample_data.ts @@ -48,12 +48,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should load elasticsearch index containing sample data with dates relative to current time', async () => { - const { body: resp } = await es.search({ + const { body: resp } = await es.search<{ timestamp: string }>({ index: 'kibana_sample_data_flights', }); const doc = resp.hits.hits[0]; - const docMilliseconds = Date.parse(doc._source.timestamp); + const docMilliseconds = Date.parse(doc._source!.timestamp); const nowMilliseconds = Date.now(); const delta = Math.abs(nowMilliseconds - docMilliseconds); expect(delta).to.be.lessThan(MILLISECOND_IN_WEEK * 4); @@ -66,12 +66,12 @@ export default function ({ getService }: FtrProviderContext) { .post(`/api/sample_data/flights?now=${nowString}`) .set('kbn-xsrf', 'kibana'); - const { body: resp } = await es.search({ + const { body: resp } = await es.search<{ timestamp: string }>({ index: 'kibana_sample_data_flights', }); const doc = resp.hits.hits[0]; - const docMilliseconds = Date.parse(doc._source.timestamp); + const docMilliseconds = Date.parse(doc._source!.timestamp); const nowMilliseconds = Date.parse(nowString); const delta = Math.abs(nowMilliseconds - docMilliseconds); expect(delta).to.be.lessThan(MILLISECOND_IN_WEEK * 4); diff --git a/test/api_integration/apis/saved_objects/migrations.ts b/test/api_integration/apis/saved_objects/migrations.ts index 1f1f1a5c98cd64..87997ab4231a26 100644 --- a/test/api_integration/apis/saved_objects/migrations.ts +++ b/test/api_integration/apis/saved_objects/migrations.ts @@ -15,7 +15,7 @@ import { set } from '@elastic/safer-lodash-set'; import _ from 'lodash'; import expect from '@kbn/expect'; import { ElasticsearchClient, SavedObjectsType } from 'src/core/server'; -import { SearchResponse } from '../../../../src/core/server/elasticsearch/client'; + import { DocumentMigrator, IndexMigrator, @@ -113,7 +113,7 @@ export default ({ getService }: FtrProviderContext) => { await esClient.indices.putTemplate({ name: 'migration_test_a_template', body: { - index_patterns: 'migration_test_a', + index_patterns: ['migration_test_a'], mappings: { dynamic: 'strict', properties: { baz: { type: 'text' } }, @@ -125,7 +125,7 @@ export default ({ getService }: FtrProviderContext) => { await esClient.indices.putTemplate({ name: 'migration_a_template', body: { - index_patterns: index, + index_patterns: [index], mappings: { dynamic: 'strict', properties: { baz: { type: 'text' } }, @@ -744,7 +744,7 @@ async function migrateIndex({ } async function fetchDocs(esClient: ElasticsearchClient, index: string) { - const { body } = await esClient.search>({ index }); + const { body } = await esClient.search({ index }); return body.hits.hits .map((h) => ({ diff --git a/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts b/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts index 2c58794c96eca0..a76d09481eca1a 100644 --- a/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts +++ b/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts @@ -26,15 +26,13 @@ export default function optInTest({ getService }: FtrProviderContext) { await supertest.put('/api/telemetry/v2/userHasSeenNotice').set('kbn-xsrf', 'xxx').expect(200); const { - body: { - _source: { telemetry }, - }, - } = await client.get({ + body: { _source }, + } = await client.get<{ telemetry: { userHasSeenNotice: boolean } }>({ index: '.kibana', id: 'telemetry:telemetry', }); - expect(telemetry.userHasSeenNotice).to.be(true); + expect(_source?.telemetry.userHasSeenNotice).to.be(true); }); }); } diff --git a/test/api_integration/apis/ui_metric/ui_metric.ts b/test/api_integration/apis/ui_metric/ui_metric.ts index 99007376e1ea49..47d10da9a1b29c 100644 --- a/test/api_integration/apis/ui_metric/ui_metric.ts +++ b/test/api_integration/apis/ui_metric/ui_metric.ts @@ -102,12 +102,12 @@ export default function ({ getService }: FtrProviderContext) { body: { hits: { hits }, }, - } = await es.search({ index: '.kibana', q: 'type:ui-metric' }); + } = await es.search({ index: '.kibana', q: 'type:ui-metric' }); const countTypeEvent = hits.find( (hit: { _id: string }) => hit._id === `ui-metric:myApp:${uniqueEventName}` ); - expect(countTypeEvent._source['ui-metric'].count).to.eql(3); + expect(countTypeEvent?._source['ui-metric'].count).to.eql(3); }); }); } diff --git a/test/common/services/elasticsearch.ts b/test/common/services/elasticsearch.ts index 99335f84058284..7b8ff6bd6c8f4c 100644 --- a/test/common/services/elasticsearch.ts +++ b/test/common/services/elasticsearch.ts @@ -10,10 +10,14 @@ import { format as formatUrl } from 'url'; import fs from 'fs'; import { Client } from '@elastic/elasticsearch'; import { CA_CERT_PATH } from '@kbn/dev-utils'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { FtrProviderContext } from '../ftr_provider_context'; -export function ElasticsearchProvider({ getService }: FtrProviderContext) { +/* + registers Kibana-specific @elastic/elasticsearch client instance. + */ +export function ElasticsearchProvider({ getService }: FtrProviderContext): KibanaClient { const config = getService('config'); if (process.env.TEST_CLOUD) { diff --git a/typings/elasticsearch/aggregations.d.ts b/typings/elasticsearch/aggregations.d.ts deleted file mode 100644 index 2b501c94889f48..00000000000000 --- a/typings/elasticsearch/aggregations.d.ts +++ /dev/null @@ -1,466 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Unionize, UnionToIntersection } from 'utility-types'; -import { ESSearchHit, MaybeReadonlyArray, ESSourceOptions, ESHitsOf } from '.'; - -export type SortOrder = 'asc' | 'desc'; -type SortInstruction = Record; -export type SortOptions = SortOrder | SortInstruction | SortInstruction[]; - -type Script = - | string - | { - lang?: string; - id?: string; - source?: string; - params?: Record; - }; - -type BucketsPath = string | Record; - -type AggregationSourceOptions = - | { - field: string; - missing?: unknown; - } - | { - script: Script; - }; - -interface MetricsAggregationResponsePart { - value: number | null; -} -interface DateHistogramBucket { - doc_count: number; - key: number; - key_as_string: string; -} - -type GetCompositeKeys< - TAggregationOptionsMap extends AggregationOptionsMap -> = TAggregationOptionsMap extends { - composite: { sources: Array }; -} - ? keyof Source - : never; - -type CompositeOptionsSource = Record< - string, - | { - terms: ({ field: string } | { script: Script }) & { - missing_bucket?: boolean; - }; - } - | undefined ->; - -export interface AggregationOptionsByType { - terms: { - size?: number; - order?: SortOptions; - execution_hint?: 'map' | 'global_ordinals'; - } & AggregationSourceOptions; - date_histogram: { - format?: string; - min_doc_count?: number; - extended_bounds?: { - min: number; - max: number; - }; - } & ({ calendar_interval: string } | { fixed_interval: string }) & - AggregationSourceOptions; - histogram: { - interval: number; - min_doc_count?: number; - extended_bounds?: { - min?: number | string; - max?: number | string; - }; - } & AggregationSourceOptions; - avg: AggregationSourceOptions; - max: AggregationSourceOptions; - min: AggregationSourceOptions; - sum: AggregationSourceOptions; - value_count: AggregationSourceOptions; - cardinality: AggregationSourceOptions & { - precision_threshold?: number; - }; - percentiles: { - percents?: number[]; - hdr?: { number_of_significant_value_digits: number }; - } & AggregationSourceOptions; - stats: { - field: string; - }; - extended_stats: { - field: string; - }; - string_stats: { field: string }; - top_hits: { - from?: number; - size?: number; - sort?: SortOptions; - _source?: ESSourceOptions; - fields?: MaybeReadonlyArray; - docvalue_fields?: MaybeReadonlyArray; - }; - filter: Record; - filters: { - filters: Record | any[]; - }; - sampler: { - shard_size?: number; - }; - derivative: { - buckets_path: BucketsPath; - }; - bucket_script: { - buckets_path: BucketsPath; - script?: Script; - }; - composite: { - size?: number; - sources: CompositeOptionsSource[]; - after?: Record; - }; - diversified_sampler: { - shard_size?: number; - max_docs_per_value?: number; - } & ({ script: Script } | { field: string }); // TODO use MetricsAggregationOptions if possible - scripted_metric: { - params?: Record; - init_script?: Script; - map_script: Script; - combine_script: Script; - reduce_script: Script; - }; - date_range: { - format?: string; - ranges: Array< - | { from: string | number } - | { to: string | number } - | { from: string | number; to: string | number } - >; - keyed?: boolean; - } & AggregationSourceOptions; - range: { - field: string; - ranges: Array< - | { key?: string; from: string | number } - | { key?: string; to: string | number } - | { key?: string; from: string | number; to: string | number } - >; - keyed?: boolean; - }; - auto_date_histogram: { - buckets: number; - } & AggregationSourceOptions; - percentile_ranks: { - values: Array; - keyed?: boolean; - hdr?: { number_of_significant_value_digits: number }; - } & AggregationSourceOptions; - bucket_sort: { - sort?: SortOptions; - from?: number; - size?: number; - }; - significant_terms: { - size?: number; - field?: string; - background_filter?: Record; - } & AggregationSourceOptions; - bucket_selector: { - buckets_path: { - [x: string]: string; - }; - script: string; - }; - top_metrics: { - metrics: { field: string } | MaybeReadonlyArray<{ field: string }>; - sort: SortOptions; - }; - avg_bucket: { - buckets_path: string; - gap_policy?: 'skip' | 'insert_zeros'; - format?: string; - }; - rate: { - unit: 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; - } & ( - | { - field: string; - mode: 'sum' | 'value_count'; - } - | {} - ); -} - -type AggregationType = keyof AggregationOptionsByType; - -type AggregationOptionsMap = Unionize< - { - [TAggregationType in AggregationType]: AggregationOptionsByType[TAggregationType]; - } -> & { aggs?: AggregationInputMap }; - -interface DateRangeBucket { - key: string; - to?: number; - from?: number; - to_as_string?: string; - from_as_string?: string; - doc_count: number; -} - -export interface AggregationInputMap { - [key: string]: AggregationOptionsMap; -} - -type SubAggregationResponseOf< - TAggregationInputMap extends AggregationInputMap | undefined, - TDocument -> = TAggregationInputMap extends AggregationInputMap - ? AggregationResponseMap - : {}; - -interface AggregationResponsePart { - terms: { - buckets: Array< - { - doc_count: number; - key: string | number; - } & SubAggregationResponseOf - >; - doc_count_error_upper_bound?: number; - sum_other_doc_count?: number; - }; - histogram: { - buckets: Array< - { - doc_count: number; - key: number; - } & SubAggregationResponseOf - >; - }; - date_histogram: { - buckets: Array< - DateHistogramBucket & SubAggregationResponseOf - >; - }; - avg: MetricsAggregationResponsePart; - sum: MetricsAggregationResponsePart; - max: MetricsAggregationResponsePart; - min: MetricsAggregationResponsePart; - value_count: { value: number }; - cardinality: { - value: number; - }; - percentiles: { - values: Record; - }; - stats: { - count: number; - min: number | null; - max: number | null; - avg: number | null; - sum: number | null; - }; - extended_stats: { - count: number; - min: number | null; - max: number | null; - avg: number | null; - sum: number | null; - sum_of_squares: number | null; - variance: number | null; - std_deviation: number | null; - std_deviation_bounds: { - upper: number | null; - lower: number | null; - }; - }; - string_stats: { - count: number; - min_length: number; - max_length: number; - avg_length: number; - entropy: number; - }; - top_hits: { - hits: { - total: { - value: number; - relation: 'eq' | 'gte'; - }; - max_score: number | null; - hits: TAggregationOptionsMap extends { top_hits: AggregationOptionsByType['top_hits'] } - ? ESHitsOf - : ESSearchHit[]; - }; - }; - filter: { - doc_count: number; - } & SubAggregationResponseOf; - filters: TAggregationOptionsMap extends { filters: { filters: any[] } } - ? Array< - { doc_count: number } & AggregationResponseMap - > - : TAggregationOptionsMap extends { - filters: { - filters: Record; - }; - } - ? { - buckets: { - [key in keyof TAggregationOptionsMap['filters']['filters']]: { - doc_count: number; - } & SubAggregationResponseOf; - }; - } - : never; - sampler: { - doc_count: number; - } & SubAggregationResponseOf; - derivative: - | { - value: number; - } - | undefined; - bucket_script: - | { - value: number | null; - } - | undefined; - composite: { - after_key: { - [key in GetCompositeKeys]: TAggregationOptionsMap; - }; - buckets: Array< - { - key: Record, string | number>; - doc_count: number; - } & SubAggregationResponseOf - >; - }; - diversified_sampler: { - doc_count: number; - } & AggregationResponseMap; - scripted_metric: { - value: unknown; - }; - date_range: { - buckets: TAggregationOptionsMap extends { date_range: { keyed: true } } - ? Record - : { buckets: DateRangeBucket[] }; - }; - range: { - buckets: TAggregationOptionsMap extends { range: { keyed: true } } - ? Record< - string, - DateRangeBucket & SubAggregationResponseOf - > - : Array< - DateRangeBucket & SubAggregationResponseOf - >; - }; - auto_date_histogram: { - buckets: Array< - DateHistogramBucket & AggregationResponseMap - >; - interval: string; - }; - - percentile_ranks: { - values: TAggregationOptionsMap extends { - percentile_ranks: { keyed: false }; - } - ? Array<{ key: number; value: number }> - : Record; - }; - significant_terms: { - doc_count: number; - bg_count: number; - buckets: Array< - { - score: number; - bg_count: number; - doc_count: number; - key: string | number; - } & SubAggregationResponseOf - >; - }; - bucket_sort: undefined; - bucket_selector: undefined; - top_metrics: { - top: [ - { - sort: [string | number]; - metrics: UnionToIntersection< - TAggregationOptionsMap extends { - top_metrics: { metrics: { field: infer TFieldName } }; - } - ? TopMetricsMap - : TAggregationOptionsMap extends { - top_metrics: { metrics: MaybeReadonlyArray<{ field: infer TFieldName }> }; - } - ? TopMetricsMap - : TopMetricsMap - >; - } - ]; - }; - avg_bucket: { - value: number | null; - }; - rate: { - value: number | null; - }; -} - -type TopMetricsMap = TFieldName extends string - ? Record - : Record; - -// Type for debugging purposes. If you see an error in AggregationResponseMap -// similar to "cannot be used to index type", uncomment the type below and hover -// over it to see what aggregation response types are missing compared to the -// input map. - -// type MissingAggregationResponseTypes = Exclude< -// AggregationType, -// keyof AggregationResponsePart<{}, unknown> -// >; - -// ensures aggregations work with requests where aggregation options are a union type, -// e.g. { transaction_groups: { composite: any } | { terms: any } }. -// Union keys are not included in keyof. The type will fall back to keyof T if -// UnionToIntersection fails, which happens when there are conflicts between the union -// types, e.g. { foo: string; bar?: undefined } | { foo?: undefined; bar: string }; -export type ValidAggregationKeysOf< - T extends Record -> = keyof (UnionToIntersection extends never ? T : UnionToIntersection); - -export type AggregationResultOf< - TAggregationOptionsMap extends AggregationOptionsMap, - TDocument -> = AggregationResponsePart[AggregationType & - ValidAggregationKeysOf]; - -export type AggregationResponseMap< - TAggregationInputMap extends AggregationInputMap | undefined, - TDocument -> = TAggregationInputMap extends AggregationInputMap - ? { - [TName in keyof TAggregationInputMap]: AggregationResponsePart< - TAggregationInputMap[TName], - TDocument - >[AggregationType & ValidAggregationKeysOf]; - } - : undefined; diff --git a/typings/elasticsearch/index.d.ts b/typings/elasticsearch/index.d.ts index a84d4148f6fe77..7eaf762d353ac4 100644 --- a/typings/elasticsearch/index.d.ts +++ b/typings/elasticsearch/index.d.ts @@ -5,136 +5,28 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import { estypes } from '@elastic/elasticsearch'; +import { InferSearchResponseOf, AggregateOf as AggregationResultOf, SearchHit } from './search'; -import { ValuesType } from 'utility-types'; -import { Explanation, SearchParams, SearchResponse } from 'elasticsearch'; -import { RequestParams } from '@elastic/elasticsearch'; -import { AggregationResponseMap, AggregationInputMap, SortOptions } from './aggregations'; -export { - AggregationInputMap, - AggregationOptionsByType, - AggregationResponseMap, - AggregationResultOf, - SortOptions, - ValidAggregationKeysOf, -} from './aggregations'; +export type ESFilter = estypes.QueryContainer; +export type ESSearchRequest = estypes.SearchRequest; +export type AggregationOptionsByType = Required; // Typings for Elasticsearch queries and aggregations. These are intended to be // moved to the Elasticsearch JS client at some point (see #77720.) export type MaybeReadonlyArray = T[] | readonly T[]; -interface CollapseQuery { - field: string; - inner_hits?: { - name: string; - size?: number; - sort?: SortOptions; - _source?: - | string - | string[] - | { - includes?: string | string[]; - excludes?: string | string[]; - }; - collapse?: { - field: string; - }; - }; - max_concurrent_group_searches?: number; -} - export type ESSourceOptions = boolean | string | string[]; -export type ESHitsOf< - TOptions extends - | { - size?: number; - _source?: ESSourceOptions; - docvalue_fields?: MaybeReadonlyArray; - fields?: MaybeReadonlyArray; - } - | undefined, - TDocument extends unknown -> = Array< - ESSearchHit< - TOptions extends { _source: false } ? undefined : TDocument, - TOptions extends { fields: MaybeReadonlyArray } ? TOptions['fields'] : undefined, - TOptions extends { docvalue_fields: MaybeReadonlyArray } - ? TOptions['docvalue_fields'] - : undefined - > ->; - -export interface ESSearchBody { - query?: any; - size?: number; - from?: number; - aggs?: AggregationInputMap; - track_total_hits?: boolean | number; - collapse?: CollapseQuery; - search_after?: Array; - _source?: ESSourceOptions; -} - -export type ESSearchRequest = RequestParams.Search; - export interface ESSearchOptions { restTotalHitsAsInt: boolean; } -export type ESSearchHit< - TSource extends any = unknown, - TFields extends MaybeReadonlyArray | undefined = undefined, - TDocValueFields extends MaybeReadonlyArray | undefined = undefined -> = { - _index: string; - _type: string; - _id: string; - _score: number; - _version?: number; - _explanation?: Explanation; - highlight?: any; - inner_hits?: any; - matched_queries?: string[]; - sort?: string[]; -} & (TSource extends false ? {} : { _source: TSource }) & - (TFields extends MaybeReadonlyArray - ? { - fields: Partial, unknown[]>>; - } - : {}) & - (TDocValueFields extends MaybeReadonlyArray - ? { - fields: Partial, unknown[]>>; - } - : {}); - export type ESSearchResponse< - TDocument, - TSearchRequest extends ESSearchRequest, - TOptions extends ESSearchOptions = { restTotalHitsAsInt: false } -> = Omit, 'aggregations' | 'hits'> & - (TSearchRequest extends { body: { aggs: AggregationInputMap } } - ? { - aggregations?: AggregationResponseMap; - } - : {}) & { - hits: Omit['hits'], 'total' | 'hits'> & - (TOptions['restTotalHitsAsInt'] extends true - ? { - total: number; - } - : { - total: { - value: number; - relation: 'eq' | 'gte'; - }; - }) & { hits: ESHitsOf }; - }; + TDocument = unknown, + TSearchRequest extends ESSearchRequest = ESSearchRequest, + TOptions extends { restTotalHitsAsInt: boolean } = { restTotalHitsAsInt: false } +> = InferSearchResponseOf; -export interface ESFilter { - [key: string]: { - [key: string]: string | string[] | number | boolean | Record | ESFilter[]; - }; -} +export { InferSearchResponseOf, AggregationResultOf, SearchHit }; diff --git a/typings/elasticsearch/search.d.ts b/typings/elasticsearch/search.d.ts new file mode 100644 index 00000000000000..fce08df1c0a04f --- /dev/null +++ b/typings/elasticsearch/search.d.ts @@ -0,0 +1,577 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ValuesType } from 'utility-types'; +import { estypes } from '@elastic/elasticsearch'; + +type InvalidAggregationRequest = unknown; + +// ensures aggregations work with requests where aggregation options are a union type, +// e.g. { transaction_groups: { composite: any } | { terms: any } }. +// Union keys are not included in keyof, but extends iterates over the types in a union. +type ValidAggregationKeysOf> = T extends T ? keyof T : never; + +type KeyOfSource = Record< + keyof T, + (T extends Record ? null : never) | string | number +>; + +type KeysOfSources = T extends [infer U, ...infer V] + ? KeyOfSource & KeysOfSources + : T extends Array + ? KeyOfSource + : {}; + +type CompositeKeysOf< + TAggregationContainer extends estypes.AggregationContainer +> = TAggregationContainer extends { + composite: { sources: [...infer TSource] }; +} + ? KeysOfSources + : unknown; + +type Source = estypes.SourceFilter | boolean | estypes.Fields; + +type ValueTypeOfField = T extends Record + ? ValuesType + : T extends string[] | number[] + ? ValueTypeOfField> + : T extends { field: estypes.Field } + ? T['field'] + : T extends string | number + ? T + : never; + +type MaybeArray = T | T[]; + +type Fields = MaybeArray; +type DocValueFields = MaybeArray; + +export type SearchHit< + TSource extends any = unknown, + TFields extends Fields | undefined = undefined, + TDocValueFields extends DocValueFields | undefined = undefined +> = Omit & + (TSource extends false ? {} : { _source: TSource }) & + (TFields extends estypes.Fields + ? { + fields: Partial, unknown[]>>; + } + : {}) & + (TDocValueFields extends DocValueFields + ? { + fields: Partial, unknown[]>>; + } + : {}); + +type HitsOf< + TOptions extends + | { _source?: Source; fields?: Fields; docvalue_fields?: DocValueFields } + | undefined, + TDocument extends unknown +> = Array< + SearchHit< + TOptions extends { _source: false } ? undefined : TDocument, + TOptions extends { fields: estypes.Fields } ? TOptions['fields'] : undefined, + TOptions extends { docvalue_fields: DocValueFields } ? TOptions['docvalue_fields'] : undefined + > +>; + +type AggregationTypeName = Exclude; + +type AggregationMap = Partial>; + +type TopLevelAggregationRequest = Pick; + +type MaybeKeyed< + TAggregationContainer, + TBucket, + TKeys extends string = string +> = TAggregationContainer extends Record + ? Record + : { buckets: TBucket[] }; + +export type AggregateOf< + TAggregationContainer extends estypes.AggregationContainer, + TDocument +> = (Record & { + adjacency_matrix: { + buckets: Array< + { + key: string; + doc_count: number; + } & SubAggregateOf + >; + }; + auto_date_histogram: { + interval: string; + buckets: Array< + { + key: number; + key_as_string: string; + doc_count: number; + } & SubAggregateOf + >; + }; + avg: { + value: number | null; + value_as_string?: string; + }; + avg_bucket: { + value: number | null; + }; + boxplot: { + min: number | null; + max: number | null; + q1: number | null; + q2: number | null; + q3: number | null; + }; + bucket_script: { + value: unknown; + }; + cardinality: { + value: number; + }; + children: { + doc_count: number; + } & SubAggregateOf; + composite: { + after_key: CompositeKeysOf; + buckets: Array< + { + doc_count: number; + key: CompositeKeysOf; + } & SubAggregateOf + >; + }; + cumulative_cardinality: { + value: number; + }; + cumulative_sum: { + value: number; + }; + date_histogram: MaybeKeyed< + TAggregationContainer, + { + key: number; + key_as_string: string; + doc_count: number; + } & SubAggregateOf + >; + date_range: MaybeKeyed< + TAggregationContainer, + Partial<{ from: string | number; from_as_string: string }> & + Partial<{ to: string | number; to_as_string: string }> & { + doc_count: number; + key: string; + } + >; + derivative: + | { + value: number | null; + } + | undefined; + extended_stats: { + count: number; + min: number | null; + max: number | null; + avg: number | null; + sum: number; + sum_of_squares: number | null; + variance: number | null; + variance_population: number | null; + variance_sampling: number | null; + std_deviation: number | null; + std_deviation_population: number | null; + std_deviation_sampling: number | null; + std_deviation_bounds: { + upper: number | null; + lower: number | null; + upper_population: number | null; + lower_population: number | null; + upper_sampling: number | null; + lower_sampling: number | null; + }; + } & ( + | { + min_as_string: string; + max_as_string: string; + avg_as_string: string; + sum_of_squares_as_string: string; + variance_population_as_string: string; + variance_sampling_as_string: string; + std_deviation_as_string: string; + std_deviation_population_as_string: string; + std_deviation_sampling_as_string: string; + std_deviation_bounds_as_string: { + upper: string; + lower: string; + upper_population: string; + lower_population: string; + upper_sampling: string; + lower_sampling: string; + }; + } + | {} + ); + extended_stats_bucket: { + count: number; + min: number | null; + max: number | null; + avg: number | null; + sum: number | null; + sum_of_squares: number | null; + variance: number | null; + variance_population: number | null; + variance_sampling: number | null; + std_deviation: number | null; + std_deviation_population: number | null; + std_deviation_sampling: number | null; + std_deviation_bounds: { + upper: number | null; + lower: number | null; + upper_population: number | null; + lower_population: number | null; + upper_sampling: number | null; + lower_sampling: number | null; + }; + }; + filter: { + doc_count: number; + } & SubAggregateOf; + filters: { + buckets: TAggregationContainer extends { filters: { filters: any[] } } + ? Array< + { + doc_count: number; + } & SubAggregateOf + > + : TAggregationContainer extends { filters: { filters: Record } } + ? { + [key in keyof TAggregationContainer['filters']['filters']]: { + doc_count: number; + } & SubAggregateOf; + } & + (TAggregationContainer extends { filters: { other_bucket_key: infer TOtherBucketKey } } + ? Record< + TOtherBucketKey & string, + { doc_count: number } & SubAggregateOf + > + : unknown) & + (TAggregationContainer extends { filters: { other_bucket: true } } + ? { _other: { doc_count: number } & SubAggregateOf } + : unknown) + : unknown; + }; + geo_bounds: { + top_left: { + lat: number | null; + lon: number | null; + }; + bottom_right: { + lat: number | null; + lon: number | null; + }; + }; + geo_centroid: { + count: number; + location: { + lat: number; + lon: number; + }; + }; + geo_distance: MaybeKeyed< + TAggregationContainer, + { + from: number; + to?: number; + doc_count: number; + } & SubAggregateOf + >; + geo_hash: { + buckets: Array< + { + doc_count: number; + key: string; + } & SubAggregateOf + >; + }; + geotile_grid: { + buckets: Array< + { + doc_count: number; + key: string; + } & SubAggregateOf + >; + }; + global: { + doc_count: number; + } & SubAggregateOf; + histogram: MaybeKeyed< + TAggregationContainer, + { + key: number; + doc_count: number; + } & SubAggregateOf + >; + ip_range: MaybeKeyed< + TAggregationContainer, + { + key: string; + from?: string; + to?: string; + doc_count: number; + }, + TAggregationContainer extends { ip_range: { ranges: Array } } + ? TRangeType extends { key: infer TKeys } + ? TKeys + : string + : string + >; + inference: { + value: number; + prediction_probability: number; + prediction_score: number; + }; + max: { + value: number | null; + value_as_string?: string; + }; + max_bucket: { + value: number | null; + }; + min: { + value: number | null; + value_as_string?: string; + }; + min_bucket: { + value: number | null; + }; + median_absolute_deviation: { + value: number | null; + }; + moving_avg: + | { + value: number | null; + } + | undefined; + moving_fn: { + value: number | null; + }; + moving_percentiles: TAggregationContainer extends Record + ? Array<{ key: number; value: number | null }> + : Record | undefined; + missing: { + doc_count: number; + } & SubAggregateOf; + nested: { + doc_count: number; + } & SubAggregateOf; + normalize: { + value: number | null; + // TODO: should be perhaps based on input? ie when `format` is specified + value_as_string?: string; + }; + parent: { + doc_count: number; + } & SubAggregateOf; + percentiles: { + values: TAggregationContainer extends Record + ? Array<{ key: number; value: number | null }> + : Record; + }; + percentile_ranks: { + values: TAggregationContainer extends Record + ? Array<{ key: number; value: number | null }> + : Record; + }; + percentiles_bucket: { + values: TAggregationContainer extends Record + ? Array<{ key: number; value: number | null }> + : Record; + }; + range: MaybeKeyed< + TAggregationContainer, + { + key: string; + from?: number; + to?: number; + doc_count: number; + }, + TAggregationContainer extends { range: { ranges: Array } } + ? TRangeType extends { key: infer TKeys } + ? TKeys + : string + : string + >; + rare_terms: Array< + { + key: string | number; + doc_count: number; + } & SubAggregateOf + >; + rate: { + value: number | null; + }; + reverse_nested: { + doc_count: number; + } & SubAggregateOf; + sampler: { + doc_count: number; + } & SubAggregateOf; + scripted_metric: { + value: unknown; + }; + serial_diff: { + value: number | null; + // TODO: should be perhaps based on input? ie when `format` is specified + value_as_string?: string; + }; + significant_terms: { + doc_count: number; + bg_count: number; + buckets: Array< + { + key: string | number; + score: number; + doc_count: number; + bg_count: number; + } & SubAggregateOf + >; + }; + significant_text: { + doc_count: number; + buckets: Array<{ + key: string; + doc_count: number; + score: number; + bg_count: number; + }>; + }; + stats: { + count: number; + min: number | null; + max: number | null; + avg: number | null; + sum: number; + } & ( + | { + min_as_string: string; + max_as_string: string; + avg_as_string: string; + sum_as_string: string; + } + | {} + ); + stats_bucket: { + count: number; + min: number | null; + max: number | null; + avg: number | null; + sum: number; + }; + string_stats: { + count: number; + min_length: number | null; + max_length: number | null; + avg_length: number | null; + entropy: number | null; + distribution: Record; + }; + sum: { + value: number | null; + value_as_string?: string; + }; + sum_bucket: { + value: number | null; + }; + terms: { + doc_count_error_upper_bound: number; + sum_other_doc_count: number; + buckets: Array< + { + doc_count: number; + key: string | number; + } & SubAggregateOf + >; + }; + top_hits: { + hits: { + total: { + value: number; + relation: 'eq' | 'gte'; + }; + max_score: number | null; + hits: TAggregationContainer extends { top_hits: estypes.TopHitsAggregation } + ? HitsOf + : estypes.HitsMetadata; + }; + }; + top_metrics: { + top: Array<{ + sort: number[] | string[]; + metrics: Record< + TAggregationContainer extends Record }> + ? TKeys + : string, + string | number | null + >; + }>; + }; + weighted_avg: { value: number | null }; + value_count: { + value: number; + }; + // t_test: {} not defined +})[ValidAggregationKeysOf & AggregationTypeName]; + +type AggregateOfMap = { + [TAggregationName in keyof TAggregationMap]: TAggregationMap[TAggregationName] extends estypes.AggregationContainer + ? AggregateOf + : never; // using never means we effectively ignore optional keys, using {} creates a union type of { ... } | {} +}; + +type SubAggregateOf = TAggregationRequest extends { + aggs?: AggregationMap; +} + ? AggregateOfMap + : TAggregationRequest extends { aggregations?: AggregationMap } + ? AggregateOfMap + : {}; + +type SearchResponseOf< + TAggregationRequest extends TopLevelAggregationRequest, + TDocument +> = SubAggregateOf; + +// if aggregation response cannot be inferred, fall back to unknown +type WrapAggregationResponse = keyof T extends never + ? { aggregations?: unknown } + : { aggregations?: T }; + +export type InferSearchResponseOf< + TDocument = unknown, + TSearchRequest extends estypes.SearchRequest = estypes.SearchRequest, + TOptions extends { restTotalHitsAsInt?: boolean } = {} +> = Omit, 'aggregations' | 'hits'> & + (TSearchRequest['body'] extends TopLevelAggregationRequest + ? WrapAggregationResponse> + : { aggregations?: InvalidAggregationRequest }) & { + hits: Omit['hits'], 'total' | 'hits'> & + (TOptions['restTotalHitsAsInt'] extends true + ? { + total: number; + } + : { + total: { + value: number; + relation: 'eq' | 'gte'; + }; + }) & { hits: HitsOf }; + }; diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 98b9b46fac48d6..a333d86b27129f 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -744,6 +744,7 @@ describe('getAll()', () => { }; unsecuredSavedObjectsClient.find.mockResolvedValueOnce(expectedResult); scopedClusterClient.asInternalUser.search.mockResolvedValueOnce( + // @ts-expect-error not full search response elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { '1': { doc_count: 6 }, @@ -817,6 +818,7 @@ describe('getAll()', () => { ], }); scopedClusterClient.asInternalUser.search.mockResolvedValueOnce( + // @ts-expect-error not full search response elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { '1': { doc_count: 6 }, @@ -877,6 +879,7 @@ describe('getAll()', () => { }; unsecuredSavedObjectsClient.find.mockResolvedValueOnce(expectedResult); scopedClusterClient.asInternalUser.search.mockResolvedValueOnce( + // @ts-expect-error not full search response elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { '1': { doc_count: 6 }, @@ -949,6 +952,7 @@ describe('getBulk()', () => { ], }); scopedClusterClient.asInternalUser.search.mockResolvedValueOnce( + // @ts-expect-error not full search response elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { '1': { doc_count: 6 }, @@ -1019,6 +1023,7 @@ describe('getBulk()', () => { ], }); scopedClusterClient.asInternalUser.search.mockResolvedValueOnce( + // @ts-expect-error not full search response elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { '1': { doc_count: 6 }, @@ -1076,6 +1081,7 @@ describe('getBulk()', () => { ], }); scopedClusterClient.asInternalUser.search.mockResolvedValueOnce( + // @ts-expect-error not full search response elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { '1': { doc_count: 6 }, diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 2e2b3e7a6d8142..d8dcde2fab103c 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -6,6 +6,7 @@ */ import Boom from '@hapi/boom'; +import type { estypes } from '@elastic/elasticsearch'; import { i18n } from '@kbn/i18n'; import { omitBy, isUndefined } from 'lodash'; @@ -509,7 +510,7 @@ async function injectExtraFindData( scopedClusterClient: IScopedClusterClient, actionResults: ActionResult[] ): Promise { - const aggs: Record = {}; + const aggs: Record = {}; for (const actionResult of actionResults) { aggs[actionResult.id] = { filter: { @@ -555,6 +556,7 @@ async function injectExtraFindData( }); return actionResults.map((actionResult) => ({ ...actionResult, + // @ts-expect-error aggegation type is not specified referencedByCount: aggregationResult.aggregations[actionResult.id].doc_count, })); } diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts index 67ba7ffea10e8f..f7b0e7de478d8d 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts @@ -93,8 +93,8 @@ async function executor( const err = find(result.items, 'index.error.reason'); if (err) { return wrapErr( - `${err.index.error!.reason}${ - err.index.error?.caused_by ? ` (${err.index.error?.caused_by?.reason})` : '' + `${err.index?.error?.reason}${ + err.index?.error?.caused_by ? ` (${err.index?.error?.caused_by?.reason})` : '' }`, actionId, logger diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts index a998fc7af0c99c..e4611857ca2794 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts @@ -13,6 +13,7 @@ describe('actions telemetry', () => { test('getTotalCount should replace first symbol . to __ for action types names', async () => { const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; mockEsClient.search.mockReturnValue( + // @ts-expect-error not full search response elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { byActionTypeId: { @@ -116,6 +117,7 @@ Object { test('getInUseTotalCount', async () => { const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; mockEsClient.search.mockReturnValue( + // @ts-expect-error not full search response elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { refs: { diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts index 6973a7e8dcbd29..8d028b176a00a6 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts @@ -53,21 +53,19 @@ export async function getTotalCount(esClient: ElasticsearchClient, kibanaIndex: }, }, }); - + // @ts-expect-error aggegation type is not specified + const aggs = searchResult.aggregations?.byActionTypeId.value?.types; return { - countTotal: Object.keys(searchResult.aggregations.byActionTypeId.value.types).reduce( - (total: number, key: string) => - parseInt(searchResult.aggregations.byActionTypeId.value.types[key], 0) + total, + countTotal: Object.keys(aggs).reduce( + (total: number, key: string) => parseInt(aggs[key], 0) + total, 0 ), - countByType: Object.keys(searchResult.aggregations.byActionTypeId.value.types).reduce( + countByType: Object.keys(aggs).reduce( // ES DSL aggregations are returned as `any` by esClient.search // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, - [replaceFirstAndLastDotSymbols(key)]: searchResult.aggregations.byActionTypeId.value.types[ - key - ], + [replaceFirstAndLastDotSymbols(key)]: aggs[key], }), {} ), @@ -161,9 +159,9 @@ export async function getInUseTotalCount( }, }); - const bulkFilter = Object.entries( - actionResults.aggregations.refs.actionRefIds.value.connectorIds - ).map(([key]) => ({ + // @ts-expect-error aggegation type is not specified + const aggs = actionResults.aggregations.refs.actionRefIds.value; + const bulkFilter = Object.entries(aggs.connectorIds).map(([key]) => ({ id: key, type: 'action', fields: ['id', 'actionTypeId'], @@ -179,7 +177,7 @@ export async function getInUseTotalCount( }, {} ); - return { countTotal: actionResults.aggregations.refs.actionRefIds.value.total, countByType }; + return { countTotal: aggs.total, countByType }; } function replaceFirstAndLastDotSymbols(strToReplace: string) { diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts index 1b1075f4d7cf12..1b29191d9063e0 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts @@ -8,6 +8,7 @@ import Boom from '@hapi/boom'; import { omit, isEqual, map, uniq, pick, truncate, trim } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { estypes } from '@elastic/elasticsearch'; import { Logger, SavedObjectsClientContract, @@ -100,7 +101,7 @@ export interface FindOptions extends IndexType { defaultSearchOperator?: 'AND' | 'OR'; searchFields?: string[]; sortField?: string; - sortOrder?: string; + sortOrder?: estypes.SortOrder; hasReference?: { type: string; id: string; diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts index 3c9decdf7ba96f..cce394d70ed6f1 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts @@ -13,6 +13,7 @@ describe('alerts telemetry', () => { test('getTotalCountInUse should replace first "." symbol to "__" in alert types names', async () => { const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; mockEsClient.search.mockReturnValue( + // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { byAlertTypeId: { diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts index 93bed31ce7d50a..46ac3e53895eba 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts @@ -246,50 +246,59 @@ export async function getTotalCountAggregations( }, }); - const totalAlertsCount = Object.keys(results.aggregations.byAlertTypeId.value.types).reduce( + const aggregations = results.aggregations as { + byAlertTypeId: { value: { types: Record } }; + throttleTime: { value: { min: number; max: number; totalCount: number; totalSum: number } }; + intervalTime: { value: { min: number; max: number; totalCount: number; totalSum: number } }; + connectorsAgg: { + connectors: { + value: { min: number; max: number; totalActionsCount: number; totalAlertsCount: number }; + }; + }; + }; + + const totalAlertsCount = Object.keys(aggregations.byAlertTypeId.value.types).reduce( (total: number, key: string) => - parseInt(results.aggregations.byAlertTypeId.value.types[key], 0) + total, + parseInt(aggregations.byAlertTypeId.value.types[key], 0) + total, 0 ); return { count_total: totalAlertsCount, - count_by_type: Object.keys(results.aggregations.byAlertTypeId.value.types).reduce( + count_by_type: Object.keys(aggregations.byAlertTypeId.value.types).reduce( // ES DSL aggregations are returned as `any` by esClient.search // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, - [replaceFirstAndLastDotSymbols(key)]: results.aggregations.byAlertTypeId.value.types[key], + [replaceFirstAndLastDotSymbols(key)]: aggregations.byAlertTypeId.value.types[key], }), {} ), throttle_time: { - min: `${results.aggregations.throttleTime.value.min}s`, + min: `${aggregations.throttleTime.value.min}s`, avg: `${ - results.aggregations.throttleTime.value.totalCount > 0 - ? results.aggregations.throttleTime.value.totalSum / - results.aggregations.throttleTime.value.totalCount + aggregations.throttleTime.value.totalCount > 0 + ? aggregations.throttleTime.value.totalSum / aggregations.throttleTime.value.totalCount : 0 }s`, - max: `${results.aggregations.throttleTime.value.max}s`, + max: `${aggregations.throttleTime.value.max}s`, }, schedule_time: { - min: `${results.aggregations.intervalTime.value.min}s`, + min: `${aggregations.intervalTime.value.min}s`, avg: `${ - results.aggregations.intervalTime.value.totalCount > 0 - ? results.aggregations.intervalTime.value.totalSum / - results.aggregations.intervalTime.value.totalCount + aggregations.intervalTime.value.totalCount > 0 + ? aggregations.intervalTime.value.totalSum / aggregations.intervalTime.value.totalCount : 0 }s`, - max: `${results.aggregations.intervalTime.value.max}s`, + max: `${aggregations.intervalTime.value.max}s`, }, connectors_per_alert: { - min: results.aggregations.connectorsAgg.connectors.value.min, + min: aggregations.connectorsAgg.connectors.value.min, avg: totalAlertsCount > 0 - ? results.aggregations.connectorsAgg.connectors.value.totalActionsCount / totalAlertsCount + ? aggregations.connectorsAgg.connectors.value.totalActionsCount / totalAlertsCount : 0, - max: results.aggregations.connectorsAgg.connectors.value.max, + max: aggregations.connectorsAgg.connectors.value.max, }, }; } @@ -308,20 +317,23 @@ export async function getTotalCountInUse(esClient: ElasticsearchClient, kibanaIn }, }, }); + + const aggregations = searchResult.aggregations as { + byAlertTypeId: { value: { types: Record } }; + }; + return { - countTotal: Object.keys(searchResult.aggregations.byAlertTypeId.value.types).reduce( + countTotal: Object.keys(aggregations.byAlertTypeId.value.types).reduce( (total: number, key: string) => - parseInt(searchResult.aggregations.byAlertTypeId.value.types[key], 0) + total, + parseInt(aggregations.byAlertTypeId.value.types[key], 0) + total, 0 ), - countByType: Object.keys(searchResult.aggregations.byAlertTypeId.value.types).reduce( + countByType: Object.keys(aggregations.byAlertTypeId.value.types).reduce( // ES DSL aggregations are returned as `any` by esClient.search // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, - [replaceFirstAndLastDotSymbols(key)]: searchResult.aggregations.byAlertTypeId.value.types[ - key - ], + [replaceFirstAndLastDotSymbols(key)]: aggregations.byAlertTypeId.value.types[key], }), {} ), diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.test.tsx index 4b925e914bfa57..f286f963b4fa0f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.test.tsx @@ -36,7 +36,7 @@ mockEmbeddable.getEmbeddableFactory = jest.fn().mockImplementation(() => ({ }), })); -const mockCore: () => [any] = () => { +const mockCore: () => any[] = () => { const core = { embeddable: mockEmbeddable, }; diff --git a/x-pack/plugins/apm/scripts/create-functional-tests-archive/index.ts b/x-pack/plugins/apm/scripts/create-functional-tests-archive/index.ts index 7eb6aba0d005ca..7f62fd39980606 100644 --- a/x-pack/plugins/apm/scripts/create-functional-tests-archive/index.ts +++ b/x-pack/plugins/apm/scripts/create-functional-tests-archive/index.ts @@ -10,6 +10,7 @@ import { execSync } from 'child_process'; import moment from 'moment'; import path from 'path'; import fs from 'fs'; +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { getEsClient } from '../shared/get_es_client'; import { parseIndexUrl } from '../shared/parse_index_url'; @@ -116,7 +117,7 @@ async function run() { const query = { bool: { - should: should.map(({ bool }) => ({ bool })), + should: should.map(({ bool }) => ({ bool })) as QueryContainer[], minimum_should_match: 1, }, }; diff --git a/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts b/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts index e4aedf452002dd..25554eeeaf81d9 100644 --- a/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts +++ b/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts @@ -13,7 +13,6 @@ // - Validate whether we can run the queries we want to on the telemetry data import { merge, chunk, flatten, omit } from 'lodash'; -import { Client } from '@elastic/elasticsearch'; import { argv } from 'yargs'; import { Logger } from 'kibana/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -26,6 +25,7 @@ import { generateSampleDocuments } from './generate-sample-documents'; import { readKibanaConfig } from '../shared/read-kibana-config'; import { getHttpAuth } from '../shared/get-http-auth'; import { createOrUpdateIndex } from '../shared/create-or-update-index'; +import { getEsClient } from '../shared/get_es_client'; async function uploadData() { const githubToken = process.env.GITHUB_TOKEN; @@ -43,8 +43,8 @@ async function uploadData() { const httpAuth = getHttpAuth(config); - const client = new Client({ - nodes: [config['elasticsearch.hosts']], + const client = getEsClient({ + node: config['elasticsearch.hosts'], ...(httpAuth ? { auth: { ...httpAuth, username: 'elastic' }, @@ -83,10 +83,10 @@ async function uploadData() { apmAgentConfigurationIndex: '.apm-agent-configuration', }, search: (body) => { - return unwrapEsResponse(client.search(body as any)); + return unwrapEsResponse(client.search(body)) as Promise; }, indicesStats: (body) => { - return unwrapEsResponse(client.indices.stats(body)); + return unwrapEsResponse(client.indices.stats(body)); }, transportRequest: ((params) => { return unwrapEsResponse( diff --git a/x-pack/plugins/apm/server/lib/alerts/alerting_es_client.ts b/x-pack/plugins/apm/server/lib/alerts/alerting_es_client.ts index c4fef64f515d1e..9a0ba514bb4794 100644 --- a/x-pack/plugins/apm/server/lib/alerts/alerting_es_client.ts +++ b/x-pack/plugins/apm/server/lib/alerts/alerting_es_client.ts @@ -25,8 +25,8 @@ export function alertingEsClient( >, params: TParams ): Promise>> { - return services.scopedClusterClient.asCurrentUser.search({ + return (services.scopedClusterClient.asCurrentUser.search({ ...params, ignore_unavailable: true, - }); + }) as unknown) as Promise>>; } diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts index bea90109725d0c..508b9419344cde 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { MetricsAggregationResponsePart } from '../../../../../../../typings/elasticsearch/aggregations'; +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { PROCESSOR_EVENT, SERVICE_NAME, @@ -45,7 +45,7 @@ export function getTransactionDurationChartPreview({ : []), ...rangeQuery(start, end), ...environmentQuery(environment), - ], + ] as QueryContainer[], }, }; @@ -85,7 +85,7 @@ export function getTransactionDurationChartPreview({ const x = bucket.key; const y = aggregationType === 'avg' - ? (bucket.agg as MetricsAggregationResponsePart).value + ? (bucket.agg as { value: number | null }).value : (bucket.agg as { values: Record }).values[ percentilesKey ]; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.test.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.test.ts index 167cb133102f26..d7dd7aee3ca25e 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.test.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.test.ts @@ -54,10 +54,20 @@ describe('Error count alert', () => { services.scopedClusterClient.asCurrentUser.search.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ hits: { + hits: [], total: { + relation: 'eq', value: 0, }, }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, }) ); @@ -89,7 +99,9 @@ describe('Error count alert', () => { services.scopedClusterClient.asCurrentUser.search.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ hits: { + hits: [], total: { + relation: 'eq', value: 2, }, }, @@ -111,6 +123,14 @@ describe('Error count alert', () => { ], }, }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, }) ); @@ -177,7 +197,9 @@ describe('Error count alert', () => { services.scopedClusterClient.asCurrentUser.search.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ hits: { + hits: [], total: { + relation: 'eq', value: 2, }, }, @@ -193,6 +215,14 @@ describe('Error count alert', () => { ], }, }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, }) ); diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.test.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.test.ts index c18f29b6267e08..148cd813a8a220 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.test.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.test.ts @@ -52,10 +52,20 @@ describe('Transaction error rate alert', () => { services.scopedClusterClient.asCurrentUser.search.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ hits: { + hits: [], total: { + relation: 'eq', value: 0, }, }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, }) ); @@ -87,7 +97,9 @@ describe('Transaction error rate alert', () => { services.scopedClusterClient.asCurrentUser.search.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ hits: { + hits: [], total: { + relation: 'eq', value: 4, }, }, @@ -126,6 +138,14 @@ describe('Transaction error rate alert', () => { ], }, }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, }) ); @@ -196,7 +216,9 @@ describe('Transaction error rate alert', () => { services.scopedClusterClient.asCurrentUser.search.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ hits: { + hits: [], total: { + relation: 'eq', value: 4, }, }, @@ -221,6 +243,14 @@ describe('Transaction error rate alert', () => { ], }, }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, }) ); @@ -274,8 +304,10 @@ describe('Transaction error rate alert', () => { services.scopedClusterClient.asCurrentUser.search.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ hits: { + hits: [], total: { value: 4, + relation: 'eq', }, }, aggregations: { @@ -286,6 +318,14 @@ describe('Transaction error rate alert', () => { buckets: [{ key: 'foo' }, { key: 'bar' }], }, }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, }) ); diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts index 98063e3e1e3fd1..87686d2c30cae4 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts @@ -7,7 +7,7 @@ import { merge } from 'lodash'; import { Logger } from 'kibana/server'; -import { RequestParams } from '@elastic/elasticsearch'; +import { IndicesStats } from '@elastic/elasticsearch/api/requestParams'; import { ESSearchRequest, ESSearchResponse, @@ -22,7 +22,7 @@ type TelemetryTaskExecutor = (params: { params: TSearchRequest ): Promise>; indicesStats( - params: RequestParams.IndicesStats + params: IndicesStats // promise returned by client has an abort property // so we cannot use its ReturnType ): Promise<{ diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts index e9744c6614641b..e2a39b521466a4 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts @@ -4,10 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { ValuesType } from 'utility-types'; import { flatten, merge, sortBy, sum, pickBy } from 'lodash'; -import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch/aggregations'; +import { CompositeAggregationSource } from '@elastic/elasticsearch/api/types'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { ProcessorEvent } from '../../../../common/processor_event'; import { TelemetryTask } from '.'; import { AGENT_NAMES, RUM_AGENT_NAMES } from '../../../../common/agent_name'; @@ -60,9 +59,7 @@ export const tasks: TelemetryTask[] = [ // the transaction count for that time range. executor: async ({ indices, search }) => { async function getBucketCountFromPaginatedQuery( - sources: Array< - ValuesType[string] - >, + sources: CompositeAggregationSource[], prevResult?: { transaction_count: number; expected_metric_document_count: number; @@ -151,7 +148,7 @@ export const tasks: TelemetryTask[] = [ }, size: 1, sort: { - '@timestamp': 'desc', + '@timestamp': 'desc' as const, }, }, }) @@ -317,7 +314,7 @@ export const tasks: TelemetryTask[] = [ service_environments: { composite: { size: 1000, - sources: [ + sources: asMutableArray([ { [SERVICE_ENVIRONMENT]: { terms: { @@ -333,7 +330,7 @@ export const tasks: TelemetryTask[] = [ }, }, }, - ], + ] as const), }, }, }, diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts index 848da41bc8372b..88ef1203bae9ff 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts @@ -76,7 +76,7 @@ export async function createApmTelemetry({ }); const search: CollectTelemetryParams['search'] = (params) => - unwrapEsResponse(esClient.asInternalUser.search(params)); + unwrapEsResponse(esClient.asInternalUser.search(params)) as any; const indicesStats: CollectTelemetryParams['indicesStats'] = (params) => unwrapEsResponse(esClient.asInternalUser.indices.stats(params)); diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts index f613a0dbca4025..c668f3bb287138 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts @@ -11,7 +11,7 @@ import { processSignificantTermAggs, TopSigTerm, } from '../process_significant_term_aggs'; -import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch/aggregations'; +import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch'; import { ESFilter } from '../../../../../../../typings/elasticsearch'; import { environmentQuery, diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts index b800a21ffc3413..50613c10ff7c0d 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts @@ -6,7 +6,7 @@ */ import { isEmpty, dropRightWhile } from 'lodash'; -import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch/aggregations'; +import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch'; import { ESFilter } from '../../../../../../../typings/elasticsearch'; import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts index 6afca46ec7391e..9472d385a26c60 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch/aggregations'; +import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch'; import { ESFilter } from '../../../../../../../typings/elasticsearch'; import { environmentQuery, @@ -73,6 +73,10 @@ export async function getCorrelationsForSlowTransactions({ setup, }); + if (!durationForPercentile) { + return {}; + } + const response = await withApmSpan('get_significant_terms', () => { const params = { apm: { events: [ProcessorEvent.transaction] }, diff --git a/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts b/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts index 2732cd45c342e7..94ed3dc3b69996 100644 --- a/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts +++ b/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts @@ -9,7 +9,7 @@ import { orderBy } from 'lodash'; import { AggregationOptionsByType, AggregationResultOf, -} from '../../../../../../typings/elasticsearch/aggregations'; +} from '../../../../../../typings/elasticsearch'; export interface TopSigTerm { fieldName: string; diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts b/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts index d4ad2c8a9b2cbb..57fb4861809932 100644 --- a/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts +++ b/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { ERROR_GROUP_ID, SERVICE_NAME, @@ -54,10 +55,10 @@ export function getErrorGroupSample({ should: [{ term: { [TRANSACTION_SAMPLED]: true } }], }, }, - sort: [ + sort: asMutableArray([ { _score: 'desc' }, // sort by _score first to ensure that errors with transaction.sampled:true ends up on top { '@timestamp': { order: 'desc' } }, // sort by timestamp to get the most recent error - ], + ] as const), }, }; diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts index 1c262ebf882b2e..f5b22e5349756c 100644 --- a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts +++ b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { SortOptions } from '../../../../../../typings/elasticsearch/aggregations'; import { ERROR_CULPRIT, ERROR_EXC_HANDLED, @@ -48,7 +47,7 @@ export function getErrorGroups({ serviceName, }); - const order: SortOptions = sortByLatestOccurrence + const order = sortByLatestOccurrence ? { max_timestamp: sortDirection, } diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index e04b3a70a75933..e20103cc6ddca1 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -13,7 +13,7 @@ import { } from '../../../../../../../../src/core/server'; import { ESSearchRequest, - ESSearchResponse, + InferSearchResponseOf, } from '../../../../../../../../typings/elasticsearch'; import { unwrapEsResponse } from '../../../../../../observability/server'; import { ProcessorEvent } from '../../../../../common/processor_event'; @@ -54,7 +54,7 @@ type ESSearchRequestOf = Omit< type TypedSearchResponse< TParams extends APMEventESSearchRequest -> = ESSearchResponse< +> = InferSearchResponseOf< TypeOfProcessorEvent>, ESSearchRequestOf >; diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts index 4faf80d7ca8db8..2e83baece01a9f 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts @@ -6,8 +6,12 @@ */ import { KibanaRequest } from 'src/core/server'; -import { RequestParams } from '@elastic/elasticsearch'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; +import { + CreateIndexRequest, + DeleteRequest, + IndexRequest, +} from '@elastic/elasticsearch/api/types'; import { unwrapEsResponse } from '../../../../../../observability/server'; import { APMRequestHandlerContext } from '../../../../routes/typings'; import { @@ -21,7 +25,7 @@ import { } from '../call_async_with_debug'; import { cancelEsRequestOnAbort } from '../cancel_es_request_on_abort'; -export type APMIndexDocumentParams = RequestParams.Index; +export type APMIndexDocumentParams = IndexRequest; export type APMInternalClient = ReturnType; @@ -73,14 +77,14 @@ export function createInternalESClient({ params, }); }, - delete: (params: RequestParams.Delete): Promise<{ result: string }> => { + delete: (params: DeleteRequest): Promise<{ result: string }> => { return callEs({ operationName: 'delete', cb: () => asInternalUser.delete(params), params, }); }, - indicesCreate: (params: RequestParams.IndicesCreate) => { + indicesCreate: (params: CreateIndexRequest) => { return callEs({ operationName: 'indices.create', cb: () => asInternalUser.indices.create(params), diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts index 30b81a15f5efa5..51f386d59c04ab 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -157,7 +157,9 @@ describe('setupRequest', () => { apm: { events: [ProcessorEvent.transaction], }, - body: { query: { bool: { filter: [{ term: 'someTerm' }] } } }, + body: { + query: { bool: { filter: [{ term: { field: 'someTerm' } }] } }, + }, }); const params = mockContext.core.elasticsearch.client.asCurrentUser.search.mock @@ -166,7 +168,7 @@ describe('setupRequest', () => { query: { bool: { filter: [ - { term: 'someTerm' }, + { term: { field: 'someTerm' } }, { terms: { [PROCESSOR_EVENT]: ['transaction'] } }, { range: { 'observer.version_major': { gte: 7 } } }, ], @@ -183,7 +185,9 @@ describe('setupRequest', () => { apm: { events: [ProcessorEvent.error], }, - body: { query: { bool: { filter: [{ term: 'someTerm' }] } } }, + body: { + query: { bool: { filter: [{ term: { field: 'someTerm' } }] } }, + }, }, { includeLegacyData: true, @@ -196,7 +200,7 @@ describe('setupRequest', () => { query: { bool: { filter: [ - { term: 'someTerm' }, + { term: { field: 'someTerm' } }, { terms: { [PROCESSOR_EVENT]: ['error'], diff --git a/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts index 9bec5eb4a247c6..11d65b7697e9a8 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts @@ -10,7 +10,7 @@ import { EventOutcome } from '../../../common/event_outcome'; import { AggregationOptionsByType, AggregationResultOf, -} from '../../../../../../typings/elasticsearch/aggregations'; +} from '../../../../../../typings/elasticsearch'; export const getOutcomeAggregation = () => ({ terms: { diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts index 9f83af989fc571..3b3ef8b9c4bcf5 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts @@ -7,6 +7,7 @@ import { sum, round } from 'lodash'; import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { isFiniteNumber } from '../../../../../../common/utils/is_finite_number'; import { Setup, SetupTimeRange } from '../../../../helpers/setup_request'; import { getMetricsDateHistogramParams } from '../../../../helpers/metrics'; import { ChartBase } from '../../../types'; @@ -126,10 +127,9 @@ export async function fetchAndTransformGcMetrics({ const data = timeseriesData.buckets.map((bucket) => { // derivative/value will be undefined for the first hit and if the `max` value is null const bucketValue = bucket.value?.value; - const y = - bucketValue !== null && bucketValue !== undefined && bucket.value - ? round(bucketValue * (60 / bucketSize), 1) - : null; + const y = isFiniteNumber(bucketValue) + ? round(bucketValue * (60 / bucketSize), 1) + : null; return { y, diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts index df51bf30c1a53d..0f1d7146f84596 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts @@ -144,12 +144,16 @@ export async function getPageLoadDistribution({ } // calculate the diff to get actual page load on specific duration value - let pageDist = pageDistVals.map(({ key, value }, index: number, arr) => { - return { - x: microToSec(key), - y: index === 0 ? value : value - arr[index - 1].value, - }; - }); + let pageDist = pageDistVals.map( + ({ key, value: maybeNullValue }, index: number, arr) => { + // FIXME: values from percentile* aggs can be null + const value = maybeNullValue!; + return { + x: microToSec(key), + y: index === 0 ? value : value - arr[index - 1].value!, + }; + } + ); pageDist = removeZeroesFromTail(pageDist); diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts index 12b7961d516101..6a6caab9537333 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts @@ -98,10 +98,12 @@ export const getPageLoadDistBreakdown = async ({ return pageDistBreakdowns?.map(({ key, page_dist: pageDist }) => { let seriesData = pageDist.values?.map( - ({ key: pKey, value }, index: number, arr) => { + ({ key: pKey, value: maybeNullValue }, index: number, arr) => { + // FIXME: values from percentile* aggs can be null + const value = maybeNullValue!; return { x: microToSec(pKey), - y: index === 0 ? value : value - arr[index - 1].value, + y: index === 0 ? value : value - arr[index - 1].value!, }; } ); diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts index 88228f33dd3af6..9bde701df56721 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts @@ -117,7 +117,7 @@ export async function getWebCoreVitals({ } = response.aggregations ?? {}; const getRanksPercentages = ( - ranks?: Array<{ key: number; value: number }> + ranks?: Array<{ key: number; value: number | null }> ) => { const ranksVal = ranks?.map(({ value }) => value?.toFixed(0) ?? 0) ?? []; return [ diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index 8c97a3993e8c09..bcddbff34a8f64 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -7,6 +7,7 @@ import Boom from '@hapi/boom'; import { sortBy, uniqBy } from 'lodash'; +import { estypes } from '@elastic/elasticsearch'; import { ESSearchResponse } from '../../../../../../typings/elasticsearch'; import { MlPluginSetup } from '../../../../ml/server'; import { PromiseReturnType } from '../../../../observability/typings/common'; @@ -63,7 +64,7 @@ export async function getServiceAnomalies({ by_field_value: [TRANSACTION_REQUEST, TRANSACTION_PAGE_LOAD], }, }, - ], + ] as estypes.QueryContainer[], }, }, aggs: { @@ -73,7 +74,7 @@ export async function getServiceAnomalies({ sources: [ { serviceName: { terms: { field: 'partition_field_value' } } }, { jobId: { terms: { field: 'job_id' } } }, - ], + ] as Array>, }, aggs: { metrics: { @@ -83,7 +84,7 @@ export async function getServiceAnomalies({ { field: 'by_field_value' }, { field: 'result_type' }, { field: 'record_score' }, - ] as const, + ], sort: { record_score: 'desc' as const, }, diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts index 8bc1b1f0562f54..fa04b963388b20 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts @@ -7,6 +7,7 @@ import Boom from '@hapi/boom'; import { sortBy, take, uniq } from 'lodash'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { ESFilter } from '../../../../../../typings/elasticsearch'; import { SERVICE_ENVIRONMENT, @@ -73,7 +74,7 @@ export function getTraceSampleIds({ aggs: { connections: { composite: { - sources: [ + sources: asMutableArray([ { [SPAN_DESTINATION_SERVICE_RESOURCE]: { terms: { @@ -96,7 +97,7 @@ export function getTraceSampleIds({ }, }, }, - ], + ] as const), size: fingerprintBucketSize, }, aggs: { diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index 7e7e073c0d2f6e..9d05369aca840f 100644 --- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -131,9 +131,11 @@ Array [ }, "sample": Object { "top_metrics": Object { - "metrics": Object { - "field": "agent.name", - }, + "metrics": Array [ + Object { + "field": "agent.name", + }, + ], "sort": Object { "@timestamp": "desc", }, @@ -213,9 +215,11 @@ Array [ }, "latest": Object { "top_metrics": Object { - "metrics": Object { - "field": "agent.name", - }, + "metrics": Array [ + Object { + "field": "agent.name", + }, + ], "sort": Object { "@timestamp": "desc", }, diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts index e0329e5f60e198..3e1a8f26de6b42 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts @@ -57,17 +57,17 @@ export function getStoredAnnotations({ const response: ESSearchResponse< ESAnnotation, { body: typeof body } - > = await unwrapEsResponse( - client.search({ + > = await (unwrapEsResponse( + client.search({ index: annotationsClient.index, body, }) - ); + ) as any); return response.hits.hits.map((hit) => { return { type: AnnotationType.VERSION, - id: hit._id, + id: hit._id as string, '@timestamp': new Date(hit._source['@timestamp']).getTime(), text: hit._source.message, }; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts index e41a88649c5ffe..db491012c986b1 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts @@ -6,6 +6,7 @@ */ import { isEqual, keyBy, mapValues } from 'lodash'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { pickKeys } from '../../../../common/utils/pick_keys'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; import { @@ -58,7 +59,7 @@ export const getDestinationMap = ({ connections: { composite: { size: 1000, - sources: [ + sources: asMutableArray([ { [SPAN_DESTINATION_SERVICE_RESOURCE]: { terms: { field: SPAN_DESTINATION_SERVICE_RESOURCE }, @@ -67,16 +68,18 @@ export const getDestinationMap = ({ // make sure we get samples for both successful // and failed calls { [EVENT_OUTCOME]: { terms: { field: EVENT_OUTCOME } } }, - ], + ] as const), }, aggs: { sample: { top_hits: { size: 1, _source: [SPAN_TYPE, SPAN_SUBTYPE, SPAN_ID], - sort: { - '@timestamp': 'desc', - }, + sort: [ + { + '@timestamp': 'desc' as const, + }, + ], }, }, }, @@ -123,12 +126,12 @@ export const getDestinationMap = ({ }, }, size: outgoingConnections.length, - docvalue_fields: [ + docvalue_fields: asMutableArray([ SERVICE_NAME, SERVICE_ENVIRONMENT, AGENT_NAME, PARENT_ID, - ] as const, + ] as const), _source: false, }, }) diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts index 676ba1625cc612..7729822df30ca0 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts @@ -90,11 +90,11 @@ export async function getServiceErrorGroups({ sample: { top_hits: { size: 1, - _source: [ + _source: ([ ERROR_LOG_MESSAGE, ERROR_EXC_MESSAGE, '@timestamp', - ], + ] as any) as string, sort: { '@timestamp': 'desc', }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts index a71772d1429cb0..e2341b306a8781 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts @@ -6,7 +6,6 @@ */ import { ProcessorEvent } from '../../../common/processor_event'; -import { SortOptions } from '../../../../../../typings/elasticsearch'; import { AGENT, CLOUD, @@ -96,7 +95,7 @@ export function getServiceMetadataDetails({ terms: { field: SERVICE_VERSION, size: 10, - order: { _key: 'desc' } as SortOptions, + order: { _key: 'desc' as const }, }, }, availabilityZones: { diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts index 10c7420d0f3b04..1e36df379e9646 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts @@ -108,9 +108,9 @@ export async function getServiceTransactionStats({ }, sample: { top_metrics: { - metrics: { field: AGENT_NAME } as const, + metrics: [{ field: AGENT_NAME } as const], sort: { - '@timestamp': 'desc', + '@timestamp': 'desc' as const, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts index cabd44c1e6907a..906cc62e64d1a4 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts @@ -59,7 +59,7 @@ export function getServicesFromMetricDocuments({ }, latest: { top_metrics: { - metrics: { field: AGENT_NAME } as const, + metrics: [{ field: AGENT_NAME } as const], sort: { '@timestamp': 'desc' }, }, }, diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/convert_settings_to_string.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/convert_settings_to_string.ts index 2d6eff33b5b4e0..0b826ea10b6c4f 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/convert_settings_to_string.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/convert_settings_to_string.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { ESSearchHit } from '../../../../../../../typings/elasticsearch'; +import { SearchHit } from '../../../../../../../typings/elasticsearch'; import { AgentConfiguration } from '../../../../common/agent_configuration/configuration_types'; // needed for backwards compatability // All settings except `transaction_sample_rate` and `transaction_max_spans` are stored as strings (they are stored as float and integer respectively) export function convertConfigSettingsToString( - hit: ESSearchHit + hit: SearchHit ) { const config = hit._source; diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts index 4f2225e3cec18f..7ec850717dab1c 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts @@ -8,7 +8,7 @@ import { ElasticsearchClient, Logger } from 'src/core/server'; import { createOrUpdateIndex, - MappingsDefinition, + Mappings, } from '../../../../../observability/server'; import { APMConfig } from '../../..'; import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; @@ -31,7 +31,7 @@ export async function createApmAgentConfigurationIndex({ }); } -const mappings: MappingsDefinition = { +const mappings: Mappings = { dynamic: 'strict', dynamic_templates: [ { diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts index 972c076d88e767..9fd4849c7640a8 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESSearchHit } from '../../../../../../../typings/elasticsearch'; +import { SearchHit } from '../../../../../../../typings/elasticsearch'; import { AgentConfiguration } from '../../../../common/agent_configuration/configuration_types'; import { SERVICE_ENVIRONMENT, @@ -46,9 +46,7 @@ export function findExactConfiguration({ params ); - const hit = resp.hits.hits[0] as - | ESSearchHit - | undefined; + const hit = resp.hits.hits[0] as SearchHit | undefined; if (!hit) { return; diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts index 12ba0939508e3f..7454128a741d5c 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESSearchHit } from '../../../../../../../typings/elasticsearch'; +import { SearchHit } from '../../../../../../../typings/elasticsearch'; import { SERVICE_NAME, SERVICE_ENVIRONMENT, @@ -75,9 +75,7 @@ export async function searchConfigurations({ params ); - const hit = resp.hits.hits[0] as - | ESSearchHit - | undefined; + const hit = resp.hits.hits[0] as SearchHit | undefined; if (!hit) { return; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts index 037f54344d770d..3965e363499fc7 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts @@ -6,9 +6,10 @@ */ import { ElasticsearchClient, Logger } from 'src/core/server'; +import { PropertyBase } from '@elastic/elasticsearch/api/types'; import { createOrUpdateIndex, - MappingsDefinition, + Mappings, } from '../../../../../observability/server'; import { APMConfig } from '../../..'; import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; @@ -31,7 +32,7 @@ export const createApmCustomLinkIndex = async ({ }); }; -const mappings: MappingsDefinition = { +const mappings: Mappings = { dynamic: 'strict', properties: { '@timestamp': { @@ -45,7 +46,8 @@ const mappings: MappingsDefinition = { type: 'keyword', }, }, - }, + // FIXME: PropertyBase type is missing .fields + } as PropertyBase, url: { type: 'keyword', }, diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts index 8e343ecfe6a642..d3d9b452853540 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts @@ -6,6 +6,7 @@ */ import * as t from 'io-ts'; +import { compact } from 'lodash'; import { Setup } from '../../helpers/setup_request'; import { ProcessorEvent } from '../../../../common/processor_event'; import { filterOptionsRt } from './custom_link_types'; @@ -22,15 +23,15 @@ export function getTransaction({ return withApmSpan('get_transaction_for_custom_link', async () => { const { apmEventClient } = setup; - const esFilters = Object.entries(filters) - // loops through the filters splitting the value by comma and removing white spaces - .map(([key, value]) => { - if (value) { - return { terms: { [key]: splitFilterValueByComma(value) } }; - } - }) - // removes filters without value - .filter((value) => value); + const esFilters = compact( + Object.entries(filters) + // loops through the filters splitting the value by comma and removing white spaces + .map(([key, value]) => { + if (value) { + return { terms: { [key]: splitFilterValueByComma(value) } }; + } + }) + ); const params = { terminateAfter: 1, diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts index 7437b8328b876f..f6b41f462c99ff 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts @@ -6,6 +6,7 @@ */ import * as t from 'io-ts'; +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { CustomLink, CustomLinkES, @@ -31,7 +32,7 @@ export function listCustomLinks({ should: [ { term: { [key]: value } }, { bool: { must_not: [{ exists: { field: key } }] } }, - ], + ] as QueryContainer[], }, }; }); @@ -48,7 +49,7 @@ export function listCustomLinks({ sort: [ { 'label.keyword': { - order: 'asc', + order: 'asc' as const, }, }, ], diff --git a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts index 0b158d9e572850..a946fa66a3b92e 100644 --- a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts +++ b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { ProcessorEvent } from '../../../common/processor_event'; import { TRACE_ID, @@ -75,7 +76,7 @@ export async function getTraceItems( filter: [ { term: { [TRACE_ID]: traceId } }, ...rangeQuery(start, end), - ], + ] as QueryContainer[], should: { exists: { field: PARENT_ID }, }, diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap index 34c2f39ca04c09..a4ff487645a4b5 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap @@ -14,9 +14,11 @@ Array [ "aggs": Object { "transaction_type": Object { "top_metrics": Object { - "metrics": Object { - "field": "transaction.type", - }, + "metrics": Array [ + Object { + "field": "transaction.type", + }, + ], "sort": Object { "@timestamp": "desc", }, @@ -210,9 +212,11 @@ Array [ "aggs": Object { "transaction_type": Object { "top_metrics": Object { - "metrics": Object { - "field": "transaction.type", - }, + "metrics": Array [ + Object { + "field": "transaction.type", + }, + ], "sort": Object { "@timestamp": "desc", }, diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts index 6308236000a53c..c1bf363b49d1c0 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -8,6 +8,7 @@ import { sortBy, take } from 'lodash'; import moment from 'moment'; import { Unionize } from 'utility-types'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { AggregationOptionsByType } from '../../../../../../typings/elasticsearch'; import { PromiseReturnType } from '../../../../observability/typings/common'; import { @@ -132,14 +133,14 @@ export function transactionGroupsFetcher( ...(isTopTraces ? { composite: { - sources: [ + sources: asMutableArray([ { [SERVICE_NAME]: { terms: { field: SERVICE_NAME } } }, { [TRANSACTION_NAME]: { terms: { field: TRANSACTION_NAME }, }, }, - ], + ] as const), size, }, } diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts index 5409f919bf8950..86be82faee5782 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts @@ -6,9 +6,9 @@ */ import { merge } from 'lodash'; +import { estypes } from '@elastic/elasticsearch'; import { TRANSACTION_TYPE } from '../../../common/elasticsearch_fieldnames'; import { arrayUnionToCallable } from '../../../common/utils/array_union_to_callable'; -import { AggregationInputMap } from '../../../../../../typings/elasticsearch'; import { TransactionGroupRequestBase, TransactionGroupSetup } from './fetcher'; import { getTransactionDurationFieldForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { withApmSpan } from '../../utils/with_apm_span'; @@ -23,8 +23,8 @@ type BucketKey = string | Record; function mergeRequestWithAggs< TRequestBase extends TransactionGroupRequestBase, - TInputMap extends AggregationInputMap ->(request: TRequestBase, aggs: TInputMap) { + TAggregationMap extends Record +>(request: TRequestBase, aggs: TAggregationMap) { return merge({}, request, { body: { aggs: { @@ -71,11 +71,13 @@ export function getCounts({ request, setup }: MetricParams) { transaction_type: { top_metrics: { sort: { - '@timestamp': 'desc', + '@timestamp': 'desc' as const, }, - metrics: { - field: TRANSACTION_TYPE, - } as const, + metrics: [ + { + field: TRANSACTION_TYPE, + } as const, + ], }, }, }); diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts index 1771b5ead68a7a..1a586d1d4dbb6a 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { withApmSpan } from '../../../../utils/with_apm_span'; import { SERVICE_NAME, @@ -86,7 +86,7 @@ export async function getBuckets({ ...rangeQuery(start, end), ...environmentQuery(environment), ...kqlQuery(kuery), - ]; + ] as QueryContainer[]; async function getSamplesForDistributionBuckets() { const response = await withApmSpan( @@ -106,7 +106,7 @@ export async function getBuckets({ should: [ { term: { [TRACE_ID]: traceId } }, { term: { [TRANSACTION_ID]: transactionId } }, - ], + ] as QueryContainer[], }, }, aggs: { @@ -122,7 +122,7 @@ export async function getBuckets({ _source: [TRANSACTION_ID, TRACE_ID], size: 10, sort: { - _score: 'desc', + _score: 'desc' as const, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts index a35780539a2567..8b068fd6bd2fb2 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts @@ -5,9 +5,11 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { ESSearchResponse } from '../../../../../../../typings/elasticsearch'; import { PromiseReturnType } from '../../../../../observability/typings/common'; import { rangeQuery } from '../../../../server/utils/queries'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup } from '../../helpers/setup_request'; @@ -42,7 +44,7 @@ export function anomalySeriesFetcher({ { term: { partition_field_value: serviceName } }, { term: { by_field_value: transactionType } }, ...rangeQuery(start, end, 'timestamp'), - ], + ] as QueryContainer[], }, }, aggs: { @@ -60,11 +62,11 @@ export function anomalySeriesFetcher({ aggs: { anomaly_score: { top_metrics: { - metrics: [ + metrics: asMutableArray([ { field: 'record_score' }, { field: 'timestamp' }, { field: 'bucket_span' }, - ] as const, + ] as const), sort: { record_score: 'desc' as const, }, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts index a089850e427e64..b4323ae7f51e28 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts @@ -12,6 +12,7 @@ import { import { rangeQuery } from '../../../../server/utils/queries'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { ProcessorEvent } from '../../../../common/processor_event'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { withApmSpan } from '../../../utils/with_apm_span'; export function getTransaction({ @@ -34,11 +35,11 @@ export function getTransaction({ size: 1, query: { bool: { - filter: [ + filter: asMutableArray([ { term: { [TRANSACTION_ID]: transactionId } }, { term: { [TRACE_ID]: traceId } }, ...rangeQuery(start, end), - ], + ]), }, }, }, diff --git a/x-pack/plugins/apm/server/projections/metrics.ts b/x-pack/plugins/apm/server/projections/metrics.ts index ca43d0a8fb3c84..68056f091c8738 100644 --- a/x-pack/plugins/apm/server/projections/metrics.ts +++ b/x-pack/plugins/apm/server/projections/metrics.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { Setup, SetupTimeRange } from '../../server/lib/helpers/setup_request'; import { SERVICE_NAME, @@ -51,7 +52,7 @@ export function getMetricsProjection({ ...rangeQuery(start, end), ...environmentQuery(environment), ...kqlQuery(kuery), - ]; + ] as QueryContainer[]; return { apm: { diff --git a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts index 9c6ea6bc83511d..29fc85128ff3f0 100644 --- a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts +++ b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @@ -46,9 +46,7 @@ export function getRumPageLoadTransactionsProjection({ ? [ { wildcard: { - 'url.full': { - value: `*${urlQuery}*`, - }, + 'url.full': `*${urlQuery}*`, }, }, ] @@ -92,9 +90,7 @@ export function getRumErrorsProjection({ ? [ { wildcard: { - 'url.full': { - value: `*${urlQuery}*`, - }, + 'url.full': `*${urlQuery}*`, }, }, ] diff --git a/x-pack/plugins/apm/server/projections/transactions.ts b/x-pack/plugins/apm/server/projections/transactions.ts index 7955518d56f038..dd16b0b910abfe 100644 --- a/x-pack/plugins/apm/server/projections/transactions.ts +++ b/x-pack/plugins/apm/server/projections/transactions.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { Setup, SetupTimeRange } from '../../server/lib/helpers/setup_request'; import { SERVICE_NAME, @@ -61,7 +62,7 @@ export function getTransactionsProjection({ ...rangeQuery(start, end), ...environmentQuery(environment), ...kqlQuery(kuery), - ], + ] as QueryContainer[], }; return { diff --git a/x-pack/plugins/apm/server/projections/typings.ts b/x-pack/plugins/apm/server/projections/typings.ts index 558f165d43cf5c..bb90aa0bf5eb4b 100644 --- a/x-pack/plugins/apm/server/projections/typings.ts +++ b/x-pack/plugins/apm/server/projections/typings.ts @@ -4,20 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { - AggregationOptionsByType, - AggregationInputMap, - ESSearchBody, -} from '../../../../../typings/elasticsearch'; +import { estypes } from '@elastic/elasticsearch'; +import { AggregationOptionsByType } from '../../../../../typings/elasticsearch'; import { APMEventESSearchRequest } from '../lib/helpers/create_es_client/create_apm_event_client'; export type Projection = Omit & { - body: Omit & { + body: Omit< + Required['body'], + 'aggs' | 'aggregations' + > & { aggs?: { [key: string]: { terms: AggregationOptionsByType['terms'] & { field: string }; - aggs?: AggregationInputMap; + aggs?: Record; }; }; }; diff --git a/x-pack/plugins/apm/server/projections/util/merge_projection/index.test.ts b/x-pack/plugins/apm/server/projections/util/merge_projection/index.test.ts index c38859056afeb4..16b2c6f8e60619 100644 --- a/x-pack/plugins/apm/server/projections/util/merge_projection/index.test.ts +++ b/x-pack/plugins/apm/server/projections/util/merge_projection/index.test.ts @@ -13,11 +13,11 @@ describe('mergeProjection', () => { mergeProjection( { apm: { events: [] }, - body: { query: { bool: { must: [{ terms: ['a'] }] } } }, + body: { query: { bool: { must: [{ terms: { field: ['a'] } }] } } }, }, { apm: { events: [] }, - body: { query: { bool: { must: [{ term: 'b' }] } } }, + body: { query: { bool: { must: [{ term: { field: 'b' } }] } } }, } ) ).toEqual({ @@ -29,7 +29,7 @@ describe('mergeProjection', () => { bool: { must: [ { - term: 'b', + term: { field: 'b' }, }, ], }, diff --git a/x-pack/plugins/apm/server/projections/util/merge_projection/index.ts b/x-pack/plugins/apm/server/projections/util/merge_projection/index.ts index 7f087070648626..fb2d981dd4a1f5 100644 --- a/x-pack/plugins/apm/server/projections/util/merge_projection/index.ts +++ b/x-pack/plugins/apm/server/projections/util/merge_projection/index.ts @@ -7,20 +7,12 @@ import { cloneDeep, isPlainObject, mergeWith } from 'lodash'; import { DeepPartial } from 'utility-types'; -import { - AggregationInputMap, - ESSearchBody, -} from '../../../../../../../typings/elasticsearch'; import { APMEventESSearchRequest } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { Projection } from '../../typings'; type PlainObject = Record; -type SourceProjection = Omit, 'body'> & { - body: Omit, 'aggs'> & { - aggs?: AggregationInputMap; - }; -}; +type SourceProjection = DeepPartial; type DeepMerge = U extends PlainObject ? T extends PlainObject diff --git a/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts b/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts index 17b714f8b72b0f..144d77df064c75 100644 --- a/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts +++ b/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; import { get } from 'lodash'; import { MakeSchemaFrom } from 'src/plugins/usage_collection/server'; import { collectFns } from './collector_helpers'; @@ -125,12 +124,10 @@ const customElementCollector: TelemetryCollector = async function customElementC body: { query: { bool: { filter: { term: { type: CUSTOM_ELEMENT_TYPE } } } } }, }; - const { body: esResponse } = await esClient.search>( - customElementParams - ); + const { body: esResponse } = await esClient.search(customElementParams); if (get(esResponse, 'hits.hits.length') > 0) { - const customElements = esResponse.hits.hits.map((hit) => hit._source[CUSTOM_ELEMENT_TYPE]); + const customElements = esResponse.hits.hits.map((hit) => hit._source![CUSTOM_ELEMENT_TYPE]); return summarizeCustomElements(customElements); } diff --git a/x-pack/plugins/canvas/server/collectors/workpad_collector.ts b/x-pack/plugins/canvas/server/collectors/workpad_collector.ts index 7cb1d17dff4379..7342cb5d40357f 100644 --- a/x-pack/plugins/canvas/server/collectors/workpad_collector.ts +++ b/x-pack/plugins/canvas/server/collectors/workpad_collector.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; import { sum as arraySum, min as arrayMin, max as arrayMax, get } from 'lodash'; import { MakeSchemaFrom } from 'src/plugins/usage_collection/server'; import { CANVAS_TYPE } from '../../common/lib/constants'; @@ -230,7 +229,6 @@ export function summarizeWorkpads(workpadDocs: CanvasWorkpad[]): WorkpadTelemetr variables: variableInfo, }; } -type ESResponse = SearchResponse; const workpadCollector: TelemetryCollector = async function (kibanaIndex, esClient) { const searchParams = { @@ -241,10 +239,10 @@ const workpadCollector: TelemetryCollector = async function (kibanaIndex, esClie body: { query: { bool: { filter: { term: { type: CANVAS_TYPE } } } } }, }; - const { body: esResponse } = await esClient.search(searchParams); + const { body: esResponse } = await esClient.search(searchParams); if (get(esResponse, 'hits.hits.length') > 0) { - const workpads = esResponse.hits.hits.map((hit) => hit._source[CANVAS_TYPE]); + const workpads = esResponse.hits.hits.map((hit) => hit._source![CANVAS_TYPE]); return summarizeWorkpads(workpads); } diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts index 6ce4db61ab9563..db8e841f45ee4a 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.ts @@ -84,8 +84,9 @@ export class AlertService { return; } - const results = await scopedClusterClient.mget({ body: { docs } }); + const results = await scopedClusterClient.mget({ body: { docs } }); + // @ts-expect-error @elastic/elasticsearch _source is optional return results.body; } catch (error) { throw createCaseError({ diff --git a/x-pack/plugins/data_enhanced/server/collectors/fetch.ts b/x-pack/plugins/data_enhanced/server/collectors/fetch.ts index 428de148fdd4f0..f0a1ebf52f6a1e 100644 --- a/x-pack/plugins/data_enhanced/server/collectors/fetch.ts +++ b/x-pack/plugins/data_enhanced/server/collectors/fetch.ts @@ -36,7 +36,8 @@ export function fetchProvider(config$: Observable, logger: L }, }); - const { buckets } = esResponse.aggregations.persisted; + // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregations + const buckets: SessionPersistedTermsBucket[] = esResponse.aggregations!.persisted.buckets; if (!buckets.length) { return { transientCount: 0, persistedCount: 0, totalCount: 0 }; } diff --git a/x-pack/plugins/data_enhanced/server/routes/session.ts b/x-pack/plugins/data_enhanced/server/routes/session.ts index 185032bd25bb63..0b786f44454a98 100644 --- a/x-pack/plugins/data_enhanced/server/routes/session.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.ts @@ -98,7 +98,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: page: schema.maybe(schema.number()), perPage: schema.maybe(schema.number()), sortField: schema.maybe(schema.string()), - sortOrder: schema.maybe(schema.string()), + sortOrder: schema.maybe(schema.oneOf([schema.literal('desc'), schema.literal('asc')])), filter: schema.maybe(schema.string()), searchFields: schema.maybe(schema.arrayOf(schema.string())), search: schema.maybe(schema.string()), diff --git a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts index 526d01dade7a74..9c1bedc4d5f1cf 100644 --- a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { ApiResponse } from '@elastic/elasticsearch'; import { tap } from 'rxjs/operators'; import type { IScopedClusterClient, Logger } from 'kibana/server'; import type { ISearchStrategy } from '../../../../../src/plugins/data/server'; @@ -51,13 +51,10 @@ export const eqlSearchStrategyProvider = ( ...request.params, }; const promise = id - ? client.get({ ...params, id }, request.options) - : client.search( - params as EqlSearchStrategyRequest['params'], - request.options - ); + ? client.get({ ...params, id }, request.options) + : client.search(params as EqlSearchStrategyRequest['params'], request.options); const response = await shimAbortSignal(promise, options.abortSignal); - return toEqlKibanaSearchResponse(response); + return toEqlKibanaSearchResponse(response as ApiResponse); }; const cancel = async () => { diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts index d529e981aaea10..2ae79f4e144e01 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts @@ -347,7 +347,7 @@ describe('ES search strategy', () => { expect(mockGetCaller).toBeCalled(); const request = mockGetCaller.mock.calls[0][0]; - expect(request).toEqual({ id, keep_alive: keepAlive }); + expect(request).toEqual({ id, body: { keep_alive: keepAlive } }); }); it('throws normalized error on ElasticsearchClientError', async () => { diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index fc1cc63146358e..aec2e7bd533ec5 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -34,7 +34,6 @@ import { getIgnoreThrottled, } from './request_utils'; import { toAsyncKibanaSearchResponse } from './response_utils'; -import { AsyncSearchResponse } from './types'; import { ConfigSchema } from '../../config'; import { getKbnServerError, KbnServerError } from '../../../../../src/plugins/kibana_utils/server'; @@ -66,12 +65,14 @@ export const enhancedEsSearchStrategyProvider = ( ...(await getDefaultAsyncSubmitParams(uiSettingsClient, config, options)), ...request.params, }; - const promise = id - ? client.get({ ...params, id }) - : client.submit(params); + const promise = id ? client.get({ ...params, id }) : client.submit(params); const { body } = await shimAbortSignal(promise, options.abortSignal); const response = shimHitsTotal(body.response, options); - return toAsyncKibanaSearchResponse({ ...body, response }); + + return toAsyncKibanaSearchResponse( + // @ts-expect-error @elastic/elasticsearch start_time_in_millis expected to be number + { ...body, response } + ); }; const cancel = async () => { @@ -167,7 +168,7 @@ export const enhancedEsSearchStrategyProvider = ( extend: async (id, keepAlive, options, { esClient }) => { logger.debug(`extend ${id} by ${keepAlive}`); try { - await esClient.asCurrentUser.asyncSearch.get({ id, keep_alive: keepAlive }); + await esClient.asCurrentUser.asyncSearch.get({ id, body: { keep_alive: keepAlive } }); } catch (e) { throw getKbnServerError(e); } diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts index 9fe4c293a87413..dffccbee9db924 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts @@ -18,6 +18,7 @@ export async function getSearchStatus( ): Promise> { // TODO: Handle strategies other than the default one try { + // @ts-expect-error @elastic/elasticsearch status method is not defined const apiResponse: ApiResponse = await client.asyncSearch.status({ id: asyncId, }); diff --git a/x-pack/plugins/data_enhanced/server/search/types.ts b/x-pack/plugins/data_enhanced/server/search/types.ts index df3ca86466aa98..e2a4e2ce74f151 100644 --- a/x-pack/plugins/data_enhanced/server/search/types.ts +++ b/x-pack/plugins/data_enhanced/server/search/types.ts @@ -5,11 +5,12 @@ * 2.0. */ +import type { estypes } from '@elastic/elasticsearch'; import { SearchResponse, ShardsResponse } from 'elasticsearch'; export interface AsyncSearchResponse { id?: string; - response: SearchResponse; + response: estypes.SearchResponse; start_time_in_millis: number; expiration_time_in_millis: number; is_partial: boolean; diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index 73bf17195a5db5..efe3186f978055 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -327,7 +327,15 @@ describe('queryEventsBySavedObject', () => { asApiResponse({ hits: { hits: [], - total: { value: 0 }, + total: { relation: 'eq', value: 0 }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + successful: 0, + total: 0, + skipped: 0, }, }) ); @@ -391,11 +399,13 @@ describe('queryEventsBySavedObject', () => { }, }, "size": 10, - "sort": Object { - "@timestamp": Object { - "order": "asc", + "sort": Array [ + Object { + "@timestamp": Object { + "order": "asc", + }, }, - }, + ], }, "index": "index-name", "track_total_hits": true, @@ -408,7 +418,15 @@ describe('queryEventsBySavedObject', () => { asApiResponse({ hits: { hits: [], - total: { value: 0 }, + total: { relation: 'eq', value: 0 }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + successful: 0, + total: 0, + skipped: 0, }, }) ); @@ -474,11 +492,13 @@ describe('queryEventsBySavedObject', () => { }, }, "size": 10, - "sort": Object { - "@timestamp": Object { - "order": "asc", + "sort": Array [ + Object { + "@timestamp": Object { + "order": "asc", + }, }, - }, + ], }, "index": "index-name", "track_total_hits": true, @@ -491,7 +511,15 @@ describe('queryEventsBySavedObject', () => { asApiResponse({ hits: { hits: [], - total: { value: 0 }, + total: { relation: 'eq', value: 0 }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + successful: 0, + total: 0, + skipped: 0, }, }) ); @@ -507,7 +535,7 @@ describe('queryEventsBySavedObject', () => { expect(query).toMatchObject({ index: 'index-name', body: { - sort: { 'event.end': { order: 'desc' } }, + sort: [{ 'event.end': { order: 'desc' } }], }, }); }); @@ -517,7 +545,15 @@ describe('queryEventsBySavedObject', () => { asApiResponse({ hits: { hits: [], - total: { value: 0 }, + total: { relation: 'eq', value: 0 }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + successful: 0, + total: 0, + skipped: 0, }, }) ); @@ -591,11 +627,13 @@ describe('queryEventsBySavedObject', () => { }, }, "size": 10, - "sort": Object { - "@timestamp": Object { - "order": "asc", + "sort": Array [ + Object { + "@timestamp": Object { + "order": "asc", + }, }, - }, + ], }, "index": "index-name", "track_total_hits": true, @@ -608,7 +646,15 @@ describe('queryEventsBySavedObject', () => { asApiResponse({ hits: { hits: [], - total: { value: 0 }, + total: { relation: 'eq', value: 0 }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + successful: 0, + total: 0, + skipped: 0, }, }) ); @@ -690,11 +736,13 @@ describe('queryEventsBySavedObject', () => { }, }, "size": 10, - "sort": Object { - "@timestamp": Object { - "order": "asc", + "sort": Array [ + Object { + "@timestamp": Object { + "order": "asc", + }, }, - }, + ], }, "index": "index-name", "track_total_hits": true, diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index f025801a459553..5d7be2278d55dc 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -7,9 +7,10 @@ import { Subject } from 'rxjs'; import { bufferTime, filter as rxFilter, switchMap } from 'rxjs/operators'; -import { reject, isUndefined } from 'lodash'; +import { reject, isUndefined, isNumber } from 'lodash'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { Logger, ElasticsearchClient } from 'src/core/server'; +import { estypes } from '@elastic/elasticsearch'; import { EsContext } from '.'; import { IEvent, IValidatedEvent, SAVED_OBJECT_REL_PRIMARY } from '../types'; import { FindOptionsType } from '../event_log_client'; @@ -135,14 +136,13 @@ export class ClusterClientAdapter { } public async doesIndexTemplateExist(name: string): Promise { - let result; try { const esClient = await this.elasticsearchClientPromise; - result = (await esClient.indices.existsTemplate({ name })).body; + const { body } = await esClient.indices.existsTemplate({ name }); + return body as boolean; } catch (err) { throw new Error(`error checking existance of index template: ${err.message}`); } - return result as boolean; } public async createIndexTemplate(name: string, template: Record): Promise { @@ -162,20 +162,16 @@ export class ClusterClientAdapter { } public async doesAliasExist(name: string): Promise { - let result; try { const esClient = await this.elasticsearchClientPromise; - result = (await esClient.indices.existsAlias({ name })).body; + const { body } = await esClient.indices.existsAlias({ name }); + return body as boolean; } catch (err) { throw new Error(`error checking existance of initial index: ${err.message}`); } - return result as boolean; } - public async createIndex( - name: string, - body: string | Record = {} - ): Promise { + public async createIndex(name: string, body: Record = {}): Promise { try { const esClient = await this.elasticsearchClientPromise; await esClient.indices.create({ @@ -228,64 +224,67 @@ export class ClusterClientAdapter { }); throw err; } - const body = { - size: perPage, - from: (page - 1) * perPage, - sort: { [sort_field]: { order: sort_order } }, - query: { - bool: { - filter: dslFilterQuery, - must: reject( - [ - { - nested: { - path: 'kibana.saved_objects', - query: { - bool: { - must: [ - { - term: { - 'kibana.saved_objects.rel': { - value: SAVED_OBJECT_REL_PRIMARY, - }, - }, - }, - { - term: { - 'kibana.saved_objects.type': { - value: type, - }, - }, - }, - { - terms: { - // default maximum of 65,536 terms, configurable by index.max_terms_count - 'kibana.saved_objects.id': ids, - }, - }, - namespaceQuery, - ], + const musts: estypes.QueryContainer[] = [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.rel': { + value: SAVED_OBJECT_REL_PRIMARY, }, }, }, - }, - start && { - range: { - '@timestamp': { - gte: start, + { + term: { + 'kibana.saved_objects.type': { + value: type, + }, }, }, - }, - end && { - range: { - '@timestamp': { - lte: end, + { + terms: { + // default maximum of 65,536 terms, configurable by index.max_terms_count + 'kibana.saved_objects.id': ids, }, }, - }, - ], - isUndefined - ), + namespaceQuery, + ], + }, + }, + }, + }, + ]; + if (start) { + musts.push({ + range: { + '@timestamp': { + gte: start, + }, + }, + }); + } + if (end) { + musts.push({ + range: { + '@timestamp': { + lte: end, + }, + }, + }); + } + + const body: estypes.SearchRequest['body'] = { + size: perPage, + from: (page - 1) * perPage, + sort: [{ [sort_field]: { order: sort_order } }], + query: { + bool: { + filter: dslFilterQuery, + must: reject(musts, isUndefined), }, }, }; @@ -295,7 +294,7 @@ export class ClusterClientAdapter { body: { hits: { hits, total }, }, - } = await esClient.search({ + } = await esClient.search({ index, track_total_hits: true, body, @@ -303,8 +302,8 @@ export class ClusterClientAdapter { return { page, per_page: perPage, - total: total.value, - data: hits.map((hit: { _source: unknown }) => hit._source) as IValidatedEvent[], + total: isNumber(total) ? total : total.value, + data: hits.map((hit) => hit._source), }; } catch (err) { throw new Error( diff --git a/x-pack/plugins/file_upload/server/analyze_file.tsx b/x-pack/plugins/file_upload/server/analyze_file.tsx index 394573eb0cca5a..22396970834928 100644 --- a/x-pack/plugins/file_upload/server/analyze_file.tsx +++ b/x-pack/plugins/file_upload/server/analyze_file.tsx @@ -6,13 +6,7 @@ */ import { IScopedClusterClient } from 'kibana/server'; -import { - AnalysisResult, - FormattedOverrides, - InputData, - InputOverrides, - FindFileStructureResponse, -} from '../common'; +import { AnalysisResult, FormattedOverrides, InputData, InputOverrides } from '../common'; export async function analyzeFile( client: IScopedClusterClient, @@ -20,9 +14,7 @@ export async function analyzeFile( overrides: InputOverrides ): Promise { overrides.explain = overrides.explain === undefined ? 'true' : overrides.explain; - const { - body, - } = await client.asInternalUser.textStructure.findStructure({ + const { body } = await client.asInternalUser.textStructure.findStructure({ body: data, ...overrides, }); @@ -31,6 +23,7 @@ export async function analyzeFile( return { ...(hasOverrides && { overrides: reducedOverrides }), + // @ts-expect-error type incompatible with FindFileStructureResponse results: body, }; } diff --git a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts index a928c80f6dd81a..c684c050036124 100644 --- a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts @@ -96,6 +96,7 @@ export const getListHandler: RequestHandler = async (context, request, response) // Query backing indices to extract data stream dataset, namespace, and type values const { body: { + // @ts-expect-error @elastic/elasticsearch aggregations are not typed aggregations: { dataset, namespace, type }, }, } = await esClient.search({ diff --git a/x-pack/plugins/fleet/server/services/agents/actions.ts b/x-pack/plugins/fleet/server/services/agents/actions.ts index 3be69893ab252d..bcece7283270b8 100644 --- a/x-pack/plugins/fleet/server/services/agents/actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/actions.ts @@ -359,7 +359,7 @@ export async function getLatestConfigChangeAction( search: policyId, searchFields: ['policy_id'], sortField: 'created_at', - sortOrder: 'DESC', + sortOrder: 'desc', }); if (res.saved_objects[0]) { diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 9aa7bbc9f2b18b..b89b2b6d351b8b 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -6,11 +6,11 @@ */ import Boom from '@hapi/boom'; -import type { SearchResponse, MGetResponse, GetResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; import type { AgentSOAttributes, Agent, BulkActionResult, ListWithKuery } from '../../types'; -import type { ESSearchResponse } from '../../../../../../typings/elasticsearch'; + import { appContextService, agentPolicyService } from '../../services'; import type { FleetServerAgent } from '../../../common'; import { isAgentUpgradeable, SO_SEARCH_LIMIT } from '../../../common'; @@ -120,7 +120,7 @@ export async function getAgentsByKuery( const kueryNode = _joinFilters(filters); const body = kueryNode ? { query: esKuery.toElasticsearchQuery(kueryNode) } : {}; - const res = await esClient.search>({ + const res = await esClient.search({ index: AGENTS_INDEX, from: (page - 1) * perPage, size: perPage, @@ -140,7 +140,7 @@ export async function getAgentsByKuery( return { agents, - total: res.body.hits.total.value, + total: (res.body.hits.total as estypes.TotalHits).value, page, perPage, }; @@ -183,13 +183,14 @@ export async function countInactiveAgents( track_total_hits: true, body, }); + // @ts-expect-error value is number | TotalHits return res.body.hits.total.value; } export async function getAgentById(esClient: ElasticsearchClient, agentId: string) { const agentNotFoundError = new AgentNotFoundError(`Agent ${agentId} not found`); try { - const agentHit = await esClient.get({ + const agentHit = await esClient.get({ index: AGENTS_INDEX, id: agentId, }); @@ -210,16 +211,16 @@ export async function getAgentById(esClient: ElasticsearchClient, agentId: strin export function isAgentDocument( maybeDocument: any -): maybeDocument is GetResponse { +): maybeDocument is estypes.MultiGetHit { return '_id' in maybeDocument && '_source' in maybeDocument; } -export type ESAgentDocumentResult = GetResponse; +export type ESAgentDocumentResult = estypes.MultiGetHit; export async function getAgentDocuments( esClient: ElasticsearchClient, agentIds: string[] ): Promise { - const res = await esClient.mget>({ + const res = await esClient.mget({ index: AGENTS_INDEX, body: { docs: agentIds.map((_id) => ({ _id })) }, }); @@ -247,7 +248,7 @@ export async function getAgentByAccessAPIKeyId( esClient: ElasticsearchClient, accessAPIKeyId: string ): Promise { - const res = await esClient.search>({ + const res = await esClient.search({ index: AGENTS_INDEX, q: `access_api_key_id:${escapeSearchQueryPhrase(accessAPIKeyId)}`, }); @@ -309,10 +310,11 @@ export async function bulkUpdateAgents( }); return { - items: res.body.items.map((item: { update: { _id: string; error?: Error } }) => ({ - id: item.update._id, - success: !item.update.error, - error: item.update.error, + items: res.body.items.map((item: estypes.BulkResponseItemContainer) => ({ + id: item.update!._id as string, + success: !item.update!.error, + // @ts-expect-error ErrorCause is not assignable to Error + error: item.update!.error as Error, })), }; } diff --git a/x-pack/plugins/fleet/server/services/agents/helpers.ts b/x-pack/plugins/fleet/server/services/agents/helpers.ts index 89f37a01a6b002..c003d3d546e832 100644 --- a/x-pack/plugins/fleet/server/services/agents/helpers.ts +++ b/x-pack/plugins/fleet/server/services/agents/helpers.ts @@ -5,25 +5,26 @@ * 2.0. */ -import type { GetResponse, SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; -import type { ESSearchHit } from '../../../../../../typings/elasticsearch'; +import type { SearchHit } from '../../../../../../typings/elasticsearch'; import type { Agent, AgentSOAttributes, FleetServerAgent } from '../../types'; type FleetServerAgentESResponse = - | GetResponse - | ESSearchHit - | SearchResponse['hits']['hits'][0]; + | estypes.MultiGetHit + | estypes.SearchResponse['hits']['hits'][0] + | SearchHit; export function searchHitToAgent(hit: FleetServerAgentESResponse): Agent { + // @ts-expect-error @elastic/elasticsearch MultiGetHit._source is optional return { id: hit._id, ...hit._source, - policy_revision: hit._source.policy_revision_idx, + policy_revision: hit._source?.policy_revision_idx, current_error_events: [], access_api_key: undefined, status: undefined, - packages: hit._source.packages ?? [], + packages: hit._source?.packages ?? [], }; } diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts index 987f4615872335..f040ba57c38be6 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts @@ -47,7 +47,6 @@ describe('reassignAgent (singular)', () => { expect(esClient.update).toBeCalledTimes(1); const calledWith = esClient.update.mock.calls[0]; expect(calledWith[0]?.id).toBe(agentInUnmanagedDoc._id); - // @ts-expect-error expect(calledWith[0]?.body?.doc).toHaveProperty('policy_id', unmanagedAgentPolicySO.id); }); @@ -91,7 +90,6 @@ describe('reassignAgents (plural)', () => { // calls ES update with correct values const calledWith = esClient.bulk.mock.calls[0][0]; // only 1 are unmanaged and bulk write two line per update - // @ts-expect-error expect(calledWith.body.length).toBe(2); // @ts-expect-error expect(calledWith.body[0].update._id).toEqual(agentInUnmanagedDoc._id); @@ -150,8 +148,8 @@ function createClientsMock() { throw new Error(`${id} not found`); } }); - // @ts-expect-error esClientMock.bulk.mockResolvedValue({ + // @ts-expect-error not full interface body: { items: [] }, }); diff --git a/x-pack/plugins/fleet/server/services/agents/status.test.ts b/x-pack/plugins/fleet/server/services/agents/status.test.ts index b11ea7ae7f87ca..35300dfc027696 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.test.ts @@ -12,8 +12,8 @@ import { getAgentStatusById } from './status'; describe('Agent status service', () => { it('should return inactive when agent is not active', async () => { const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - // @ts-expect-error mockElasticsearchClient.get.mockResolvedValue({ + // @ts-expect-error not full interface body: { _id: 'id', _source: { @@ -29,8 +29,8 @@ describe('Agent status service', () => { it('should return online when agent is active', async () => { const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - // @ts-expect-error mockElasticsearchClient.get.mockResolvedValue({ + // @ts-expect-error not full interface body: { _id: 'id', _source: { @@ -47,8 +47,8 @@ describe('Agent status service', () => { it('should return enrolling when agent is active but never checkin', async () => { const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - // @ts-expect-error mockElasticsearchClient.get.mockResolvedValue({ + // @ts-expect-error not full interface body: { _id: 'id', _source: { @@ -64,8 +64,8 @@ describe('Agent status service', () => { it('should return unenrolling when agent is unenrolling', async () => { const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - // @ts-expect-error mockElasticsearchClient.get.mockResolvedValue({ + // @ts-expect-error not full interface body: { _id: 'id', diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts index 23ba8ac7bbd7ff..3d0692c242096e 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts @@ -65,10 +65,8 @@ describe('unenrollAgents (plural)', () => { // calls ES update with correct values const calledWith = esClient.bulk.mock.calls[1][0]; const ids = calledWith?.body - // @ts-expect-error .filter((i: any) => i.update !== undefined) .map((i: any) => i.update._id); - // @ts-expect-error const docs = calledWith?.body.filter((i: any) => i.doc).map((i: any) => i.doc); expect(ids).toHaveLength(2); expect(ids).toEqual(idsToUnenroll); @@ -90,10 +88,8 @@ describe('unenrollAgents (plural)', () => { const onlyUnmanaged = [agentInUnmanagedDoc._id, agentInUnmanagedDoc2._id]; const calledWith = esClient.bulk.mock.calls[1][0]; const ids = calledWith?.body - // @ts-expect-error .filter((i: any) => i.update !== undefined) .map((i: any) => i.update._id); - // @ts-expect-error const docs = calledWith?.body.filter((i: any) => i.doc).map((i: any) => i.doc); expect(ids).toHaveLength(onlyUnmanaged.length); expect(ids).toEqual(onlyUnmanaged); @@ -149,8 +145,8 @@ function createClientMock() { throw new Error('not found'); } }); - // @ts-expect-error esClientMock.bulk.mockResolvedValue({ + // @ts-expect-error not full interface body: { items: [] }, }); diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts index 4365c3913f4332..b3edb20d51c4f6 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts @@ -8,7 +8,6 @@ import uuid from 'uuid'; import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; -import type { GetResponse } from 'elasticsearch'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; @@ -42,10 +41,12 @@ export async function listEnrollmentApiKeys( q: kuery, }); + // @ts-expect-error @elastic/elasticsearch const items = res.body.hits.hits.map(esDocToEnrollmentApiKey); return { items, + // @ts-expect-error value is number | TotalHits total: res.body.hits.total.value, page, perPage, @@ -57,11 +58,12 @@ export async function getEnrollmentAPIKey( id: string ): Promise { try { - const res = await esClient.get>({ + const res = await esClient.get({ index: ENROLLMENT_API_KEYS_INDEX, id, }); + // @ts-expect-error esDocToEnrollmentApiKey doesn't accept optional _source return esDocToEnrollmentApiKey(res.body); } catch (e) { if (e instanceof ResponseError && e.statusCode === 404) { @@ -226,11 +228,12 @@ export async function generateEnrollmentAPIKey( } export async function getEnrollmentAPIKeyById(esClient: ElasticsearchClient, apiKeyId: string) { - const res = await esClient.search>({ + const res = await esClient.search({ index: ENROLLMENT_API_KEYS_INDEX, q: `api_key_id:${escapeSearchQueryPhrase(apiKeyId)}`, }); + // @ts-expect-error esDocToEnrollmentApiKey doesn't accept optional _source const [enrollmentAPIKey] = res.body.hits.hits.map(esDocToEnrollmentApiKey); if (enrollmentAPIKey?.api_key_id !== apiKeyId) { diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts index 9a12f6a3c0bdfc..77785aeb026c12 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts @@ -16,7 +16,6 @@ import type { ElasticsearchClient } from 'kibana/server'; import type { ListResult } from '../../../common'; import { FLEET_SERVER_ARTIFACTS_INDEX } from '../../../common'; -import type { ESSearchHit, ESSearchResponse } from '../../../../../../typings/elasticsearch'; import { ArtifactsElasticsearchError } from '../../errors'; @@ -38,11 +37,12 @@ export const getArtifact = async ( id: string ): Promise => { try { - const esData = await esClient.get>({ + const esData = await esClient.get({ index: FLEET_SERVER_ARTIFACTS_INDEX, id, }); + // @ts-expect-error @elastic/elasticsearch _source is optional return esSearchHitToArtifact(esData.body); } catch (e) { if (isElasticsearchItemNotFoundError(e)) { @@ -92,9 +92,7 @@ export const listArtifacts = async ( const { perPage = 20, page = 1, kuery = '', sortField = 'created', sortOrder = 'asc' } = options; try { - const searchResult = await esClient.search< - ESSearchResponse - >({ + const searchResult = await esClient.search({ index: FLEET_SERVER_ARTIFACTS_INDEX, sort: `${sortField}:${sortOrder}`, q: kuery, @@ -103,9 +101,11 @@ export const listArtifacts = async ( }); return { + // @ts-expect-error @elastic/elasticsearch _source is optional items: searchResult.body.hits.hits.map((hit) => esSearchHitToArtifact(hit)), page, perPage, + // @ts-expect-error doesn't handle total as number total: searchResult.body.hits.total.value, }; } catch (e) { diff --git a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts index 43aa111f2efcf6..3b81e47577ff7f 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { ESSearchHit } from '../../../../../../typings/elasticsearch'; +import type { SearchHit } from '../../../../../../typings/elasticsearch'; import type { Artifact, ArtifactElasticsearchProperties, NewArtifact } from './types'; import { ARTIFACT_DOWNLOAD_RELATIVE_PATH } from './constants'; export const esSearchHitToArtifact = < - T extends Pick, '_id' | '_source'> + T extends Pick, '_id' | '_source'> >({ _id: id, _source: { diff --git a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts index 5569e4ac77d20f..1a10f93f678b3f 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts @@ -10,7 +10,7 @@ import type { ApiResponse } from '@elastic/elasticsearch'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; -import type { ESSearchHit, ESSearchResponse } from '../../../../../../typings/elasticsearch'; +import type { SearchHit, ESSearchResponse } from '../../../../../../typings/elasticsearch'; import type { Artifact, ArtifactElasticsearchProperties, ArtifactsClientInterface } from './types'; import { newArtifactToElasticsearchProperties } from './mappings'; @@ -77,7 +77,7 @@ export const generateEsRequestErrorApiResponseMock = ( ); }; -export const generateArtifactEsGetSingleHitMock = (): ESSearchHit => { +export const generateArtifactEsGetSingleHitMock = (): SearchHit => { const { id, created, ...newArtifact } = generateArtifactMock(); const _source = { ...newArtifactToElasticsearchProperties(newArtifact), diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index 456ed95a8c3e45..0b95f8d76627a9 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -497,6 +497,7 @@ const updateExistingDataStream = async ({ await esClient.indices.putMapping({ index: dataStreamName, body: mappings, + // @ts-expect-error @elastic/elasticsearch doesn't declare it on PutMappingRequest write_index_only: true, }); // if update fails, rollover data stream diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts index cbd09b8d1e7a8b..7d62c0ef41c8db 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts @@ -123,6 +123,7 @@ async function handleTransformInstall({ await esClient.transform.putTransform({ transform_id: transform.installationName, defer_validation: true, + // @ts-expect-error expect object, but given a string body: transform.content, }); } catch (err) { diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts index 248c03e43add9a..39681401ac9555 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts @@ -55,6 +55,7 @@ export const deleteTransforms = async (esClient: ElasticsearchClient, transformI await esClient.transport.request( { method: 'DELETE', + // @ts-expect-error @elastic/elasticsearch Transform is empty interface path: `/${transform?.dest?.index}`, }, { diff --git a/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts b/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts index 1d5a788c3a2c20..f078b214e4dfdb 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts @@ -169,6 +169,7 @@ async function migrateAgentPolicies() { track_total_hits: true, }); + // @ts-expect-error value is number | TotalHits if (res.body.hits.total.value === 0) { return agentPolicyService.createFleetPolicyChangeFleetServer( soClient, diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts index a18459d5d21b94..77f14decc56429 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts @@ -24,6 +24,7 @@ async function addLifecyclePolicy( }, }; + // @ts-expect-error @elastic/elasticsearch UpdateIndexSettingsRequest does not support index property return client.indices.putSettings({ index: indexName, body }); } diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts index 72768a1540adce..069adc139a86d7 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts @@ -17,6 +17,7 @@ async function deletePolicies(client: ElasticsearchClient, policyName: string): ignore: [404], }; + // @ts-expect-error @elastic/elasticsearch DeleteSnapshotLifecycleRequest.policy_id is required return client.ilm.deleteLifecycle({ policy: policyName }, options); } diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts index 75320b6f2d242a..9aa5e3c24d010b 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts @@ -33,6 +33,7 @@ async function fetchPolicies(client: ElasticsearchClient): Promise( + const response = await client.indices.getIndexTemplate( { name: templateName, }, options ); - const { index_templates: templates } = response.body; - return templates?.find((template) => template.name === templateName)?.index_template; + const { index_templates: templates } = response.body as { + index_templates: TemplateFromEs[]; + }; + return templates.find((template) => template.name === templateName)?.index_template; } async function updateIndexTemplate( diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts index 77917db95a1161..9573b9cc6436f2 100644 --- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts @@ -91,7 +91,7 @@ const getDataStreamsStats = (client: ElasticsearchClient, name = '*') => { }; const getDataStreamsPrivileges = (client: ElasticsearchClient, names: string[]) => { - return client.security.hasPrivileges({ + return client.security.hasPrivileges({ body: { index: [ { @@ -143,6 +143,7 @@ export function registerGetAllRoute({ dataStreams = enhanceDataStreams({ dataStreams, dataStreamsStats, + // @ts-expect-error PrivilegesFromEs incompatible with ApplicationsPrivileges dataStreamsPrivileges, }); @@ -195,6 +196,7 @@ export function registerGetOneRoute({ const enhancedDataStreams = enhanceDataStreams({ dataStreams, dataStreamsStats, + // @ts-expect-error PrivilegesFromEs incompatible with ApplicationsPrivileges dataStreamsPrivileges, }); const body = deserializeDataStream(enhancedDataStreams[0]); diff --git a/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts b/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts index 5369deb1034eef..43f0b12a23f237 100644 --- a/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts +++ b/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts @@ -6,7 +6,7 @@ */ import { encode } from 'rison-node'; -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { FetchData, FetchDataParams, LogsFetchDataResponse } from '../../../observability/public'; import { DEFAULT_SOURCE_ID } from '../../common/constants'; import { callFetchLogSourceConfigurationAPI } from '../containers/logs/log_source/api/fetch_log_source_configuration'; @@ -81,7 +81,7 @@ async function fetchLogsOverview( dataPlugin: InfraClientStartDeps['data'] ): Promise { return new Promise((resolve, reject) => { - let esResponse: SearchResponse | undefined; + let esResponse: estypes.SearchResponse | undefined; dataPlugin.search .search({ @@ -99,7 +99,7 @@ async function fetchLogsOverview( (error) => reject(error), () => { if (esResponse?.aggregations) { - resolve(processLogsOverviewAggregations(esResponse!.aggregations)); + resolve(processLogsOverviewAggregations(esResponse!.aggregations as any)); } else { resolve({ stats: {}, series: {} }); } diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts index 2cb00644f56d4a..451b2284ba3101 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -9,9 +9,9 @@ import { IndicesExistsAlias, IndicesGet, MlGetBuckets, - Msearch, } from '@elastic/elasticsearch/api/requestParams'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; +import { estypes } from '@elastic/elasticsearch'; import { InfraRouteConfig, InfraTSVBResponse, @@ -153,7 +153,7 @@ export class KibanaFramework { apiResult = elasticsearch.client.asCurrentUser.msearch({ ...params, ...frozenIndicesParams, - } as Msearch); + } as estypes.MultiSearchRequest); break; case 'fieldCaps': apiResult = elasticsearch.client.asCurrentUser.fieldCaps({ diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts index 615de182662f11..439764f80186e1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts @@ -107,6 +107,7 @@ const getData = async ( const client = async ( options: CallWithRequestParams ): Promise> => + // @ts-expect-error @elastic/elasticsearch SearchResponse.body.timeout is not required (await esClient.search(options)).body as InfraDatabaseSearchResponse; const metrics = [ diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts index b7d3dbb1f7adbd..f6214edc5d0ab9 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts @@ -127,6 +127,7 @@ const getMetric: ( (response) => response.aggregations?.groupings?.after_key ); const compositeBuckets = (await getAllCompositeData( + // @ts-expect-error @elastic/elasticsearch SearchResponse.body.timeout is not required (body) => esClient.search({ body, index }), searchBody, bucketSelector, @@ -147,7 +148,12 @@ const getMetric: ( index, }); - return { [UNGROUPED_FACTORY_KEY]: getValuesFromAggregations(result.aggregations, aggType) }; + return { + [UNGROUPED_FACTORY_KEY]: getValuesFromAggregations( + (result.aggregations! as unknown) as Aggregation, + aggType + ), + }; } catch (e) { if (timeframe) { // This code should only ever be reached when previewing the alert, not executing it diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts index 161685aac29ad4..32bb0596ab5617 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts @@ -98,6 +98,7 @@ export const logEntriesSearchStrategyProvider = ({ map( ([{ configuration }, messageFormattingRules]): IEsSearchRequest => { return { + // @ts-expect-error @elastic/elasticsearch declares indices_boost as Record params: createGetLogEntriesQuery( configuration.logAlias, params.startTimestamp, diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts index 85eacba823b2bd..b6073f1bbe4c97 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts @@ -65,6 +65,7 @@ export const logEntrySearchStrategyProvider = ({ sourceConfiguration$.pipe( map( ({ configuration }): IEsSearchRequest => ({ + // @ts-expect-error @elastic/elasticsearch declares indices_boost as Record params: createGetLogEntryQuery( configuration.logAlias, params.logEntryId, diff --git a/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts b/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts index aa640f106d1ee9..6ae7232d77a177 100644 --- a/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts +++ b/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RequestParams } from '@elastic/elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import * as rt from 'io-ts'; import { LogEntryAfterCursor, @@ -31,7 +31,7 @@ export const createGetLogEntriesQuery = ( fields: string[], query?: JsonObject, highlightTerm?: string -): RequestParams.AsyncSearchSubmit> => { +): estypes.AsyncSearchSubmitRequest => { const sortDirection = getSortDirection(cursor); const highlightQuery = createHighlightQuery(highlightTerm, fields); @@ -51,6 +51,7 @@ export const createGetLogEntriesQuery = ( ], }, }, + // @ts-expect-error @elastic/elasticsearch doesn't declare body.fields on AsyncSearchSubmitRequest fields, _source: false, ...createSortClause(sortDirection, timestampField, tiebreakerField), diff --git a/x-pack/plugins/infra/server/services/log_entries/queries/log_entry.ts b/x-pack/plugins/infra/server/services/log_entries/queries/log_entry.ts index 51714be775e977..85af8b92fe080c 100644 --- a/x-pack/plugins/infra/server/services/log_entries/queries/log_entry.ts +++ b/x-pack/plugins/infra/server/services/log_entries/queries/log_entry.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RequestParams } from '@elastic/elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import * as rt from 'io-ts'; import { jsonArrayRT } from '../../../../common/typed_json'; import { @@ -18,7 +18,7 @@ export const createGetLogEntryQuery = ( logEntryId: string, timestampField: string, tiebreakerField: string -): RequestParams.AsyncSearchSubmit> => ({ +): estypes.AsyncSearchSubmitRequest => ({ index: logEntryIndex, terminate_after: 1, track_scores: false, @@ -30,6 +30,7 @@ export const createGetLogEntryQuery = ( values: [logEntryId], }, }, + // @ts-expect-error @elastic/elasticsearch doesn't declare body.fields on AsyncSearchSubmitRequest fields: ['*'], sort: [{ [timestampField]: 'desc' }, { [tiebreakerField]: 'desc' }], _source: false, diff --git a/x-pack/plugins/lens/server/routes/existing_fields.test.ts b/x-pack/plugins/lens/server/routes/existing_fields.test.ts index 3f3e94099f6669..57d8ebf678d614 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.test.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.test.ts @@ -22,7 +22,7 @@ describe('existingFields', () => { } function searchResults(fields: Record = {}) { - return { fields }; + return { fields, _index: '_index', _id: '_id' }; } it('should handle root level fields', () => { @@ -77,7 +77,13 @@ describe('existingFields', () => { it('supports meta fields', () => { const result = existingFields( - [{ _mymeta: 'abc', ...searchResults({ bar: ['scriptvalue'] }) }], + [ + { + // @ts-expect-error _mymeta is not defined on estypes.Hit + _mymeta: 'abc', + ...searchResults({ bar: ['scriptvalue'] }), + }, + ], [field({ name: '_mymeta', isMeta: true })] ); diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index 8a2db992a839da..2e6d6128352315 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -6,7 +6,7 @@ */ import Boom from '@hapi/boom'; -import { errors } from '@elastic/elasticsearch'; +import { errors, estypes } from '@elastic/elasticsearch'; import { schema } from '@kbn/config-schema'; import { RequestHandlerContext, ElasticsearchClient } from 'src/core/server'; import { CoreSetup, Logger } from 'src/core/server'; @@ -192,18 +192,19 @@ async function fetchIndexPatternStats({ _source: false, runtime_mappings: runtimeFields.reduce((acc, field) => { if (!field.runtimeField) return acc; + // @ts-expect-error @elastic/elasticsearch StoredScript.language is required acc[field.name] = field.runtimeField; return acc; - }, {} as Record), + }, {} as Record), script_fields: scriptedFields.reduce((acc, field) => { acc[field.name] = { script: { - lang: field.lang, - source: field.script, + lang: field.lang!, + source: field.script!, }, }; return acc; - }, {} as Record), + }, {} as Record), }, }); return result.hits.hits; @@ -212,10 +213,7 @@ async function fetchIndexPatternStats({ /** * Exported only for unit tests. */ -export function existingFields( - docs: Array<{ fields: Record; [key: string]: unknown }>, - fields: Field[] -): string[] { +export function existingFields(docs: estypes.Hit[], fields: Field[]): string[] { const missingFields = new Set(fields); for (const doc of docs) { @@ -224,7 +222,7 @@ export function existingFields( } missingFields.forEach((field) => { - let fieldStore: Record = doc.fields; + let fieldStore = doc.fields!; if (field.isMeta) { fieldStore = doc; } diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index 49ea8c2076f7aa..6cddd2c60f4165 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { errors } from '@elastic/elasticsearch'; +import { errors, estypes } from '@elastic/elasticsearch'; import DateMath from '@elastic/datemath'; import { schema } from '@kbn/config-schema'; import { CoreSetup } from 'src/core/server'; @@ -79,13 +78,14 @@ export async function initFieldsRoute(setup: CoreSetup) { }, }; - const search = async (aggs: unknown) => { + const search = async (aggs: Record) => { const { body: result } = await requestClient.search({ index: indexPattern.title, track_total_hits: true, body: { query, aggs, + // @ts-expect-error @elastic/elasticsearch StoredScript.language is required runtime_mappings: field.runtimeField ? { [fieldName]: field.runtimeField } : {}, }, size: 0, @@ -135,7 +135,7 @@ export async function initFieldsRoute(setup: CoreSetup) { } export async function getNumberHistogram( - aggSearchWithBody: (body: unknown) => Promise, + aggSearchWithBody: (aggs: Record) => Promise, field: IFieldType, useTopHits = true ): Promise { @@ -179,7 +179,10 @@ export async function getNumberHistogram( const terms = 'top_values' in minMaxResult.aggregations!.sample ? minMaxResult.aggregations!.sample.top_values - : { buckets: [] }; + : { + buckets: [] as Array<{ doc_count: number; key: string | number }>, + }; + const topValuesBuckets = { buckets: terms.buckets.map((bucket) => ({ count: bucket.doc_count, @@ -241,7 +244,7 @@ export async function getNumberHistogram( } export async function getStringSamples( - aggSearchWithBody: (body: unknown) => unknown, + aggSearchWithBody: (aggs: Record) => unknown, field: IFieldType ): Promise { const fieldRef = getFieldRef(field); @@ -280,7 +283,7 @@ export async function getStringSamples( // This one is not sampled so that it returns the full date range export async function getDateHistogram( - aggSearchWithBody: (body: unknown) => unknown, + aggSearchWithBody: (aggs: Record) => unknown, field: IFieldType, range: { fromDate: string; toDate: string } ): Promise { diff --git a/x-pack/plugins/lens/server/usage/task.ts b/x-pack/plugins/lens/server/usage/task.ts index d583e1628cbe8e..9c9ab7fd0b350f 100644 --- a/x-pack/plugins/lens/server/usage/task.ts +++ b/x-pack/plugins/lens/server/usage/task.ts @@ -137,14 +137,17 @@ export async function getDailyEvents( const byDateByType: Record> = {}; const suggestionsByDate: Record> = {}; + // @ts-expect-error no way to declare aggregations for search response metrics.aggregations!.daily.buckets.forEach((daily) => { const byType: Record = byDateByType[daily.key] || {}; + // @ts-expect-error no way to declare aggregations for search response daily.groups.buckets.regularEvents.names.buckets.forEach((bucket) => { byType[bucket.key] = (bucket.sums.value || 0) + (byType[daily.key] || 0); }); byDateByType[daily.key] = byType; const suggestionsByType: Record = suggestionsByDate[daily.key] || {}; + // @ts-expect-error no way to declare aggregations for search response daily.groups.buckets.suggestionEvents.names.buckets.forEach((bucket) => { suggestionsByType[bucket.key] = (bucket.sums.value || 0) + (suggestionsByType[daily.key] || 0); diff --git a/x-pack/plugins/lens/server/usage/visualization_counts.ts b/x-pack/plugins/lens/server/usage/visualization_counts.ts index 5b084ecfef5e41..3b9bb99caf5b81 100644 --- a/x-pack/plugins/lens/server/usage/visualization_counts.ts +++ b/x-pack/plugins/lens/server/usage/visualization_counts.ts @@ -50,6 +50,7 @@ export async function getVisualizationCounts( }, }); + // @ts-expect-error @elastic/elasticsearch no way to declare aggregations for search response const buckets = results.aggregations.groups.buckets; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/x-pack/plugins/lists/server/services/items/create_list_item.test.ts b/x-pack/plugins/lists/server/services/items/create_list_item.test.ts index a13163d8f774a5..acfed44c5259e2 100644 --- a/x-pack/plugins/lists/server/services/items/create_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_item.test.ts @@ -28,6 +28,7 @@ describe('crete_list_item', () => { const options = getCreateListItemOptionsMock(); const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.index.mockReturnValue( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ _id: 'elastic-id-123' }) ); const listItem = await createListItem({ ...options, esClient }); @@ -54,6 +55,7 @@ describe('crete_list_item', () => { options.id = undefined; const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.index.mockReturnValue( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ _id: 'elastic-id-123' }) ); const list = await createListItem({ ...options, esClient }); diff --git a/x-pack/plugins/lists/server/services/items/create_list_item.ts b/x-pack/plugins/lists/server/services/items/create_list_item.ts index a5369bbfe7ca41..cf8a43be796dff 100644 --- a/x-pack/plugins/lists/server/services/items/create_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_item.ts @@ -6,7 +6,6 @@ */ import uuid from 'uuid'; -import { CreateDocumentResponse } from 'elasticsearch'; import { ElasticsearchClient } from 'kibana/server'; import { @@ -69,7 +68,7 @@ export const createListItem = async ({ ...baseBody, ...elasticQuery, }; - const { body: response } = await esClient.index({ + const { body: response } = await esClient.index({ body, id, index: listItemIndex, diff --git a/x-pack/plugins/lists/server/services/items/find_list_item.test.ts b/x-pack/plugins/lists/server/services/items/find_list_item.test.ts index 29e6f2f8450023..c76d1c505df0c4 100644 --- a/x-pack/plugins/lists/server/services/items/find_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/find_list_item.test.ts @@ -20,9 +20,11 @@ describe('find_list_item', () => { const options = getFindListItemOptionsMock(); const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.count.mockReturnValue( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ count: 1 }) ); esClient.search.mockReturnValue( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ _scroll_id: '123', _shards: getShardMock(), diff --git a/x-pack/plugins/lists/server/services/items/find_list_item.ts b/x-pack/plugins/lists/server/services/items/find_list_item.ts index 727c55d53e459d..3e37ccb0cfb1f3 100644 --- a/x-pack/plugins/lists/server/services/items/find_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/find_list_item.ts @@ -6,7 +6,6 @@ */ import { ElasticsearchClient } from 'kibana/server'; -import { SearchResponse } from 'elasticsearch'; import { Filter, @@ -75,8 +74,9 @@ export const findListItem = async ({ sortOrder, }); - const { body: respose } = await esClient.count<{ count: number }>({ + const { body: respose } = await esClient.count({ body: { + // @ts-expect-error GetQueryFilterReturn is not assignable to QueryContainer query, }, ignore_unavailable: true, @@ -87,8 +87,9 @@ export const findListItem = async ({ // Note: This typing of response = await esClient> // is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have // to explicitly define the type . - const { body: response } = await esClient.search>({ + const { body: response } = await esClient.search({ body: { + // @ts-expect-error GetQueryFilterReturn is not assignable to QueryContainer query, search_after: scroll.searchAfter, sort: getSortWithTieBreaker({ sortField, sortOrder }), diff --git a/x-pack/plugins/lists/server/services/items/get_list_item.ts b/x-pack/plugins/lists/server/services/items/get_list_item.ts index eb05a899478a53..519ebaedfddbc7 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item.ts @@ -6,7 +6,6 @@ */ import { ElasticsearchClient } from 'kibana/server'; -import { SearchResponse } from 'elasticsearch'; import { Id, ListItemSchema, SearchEsListItemSchema } from '../../../common/schemas'; import { transformElasticToListItem } from '../utils'; @@ -26,7 +25,7 @@ export const getListItem = async ({ // Note: This typing of response = await esClient> // is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have // to explicitly define the type . - const { body: listItemES } = await esClient.search>({ + const { body: listItemES } = await esClient.search({ body: { query: { term: { @@ -40,6 +39,7 @@ export const getListItem = async ({ }); if (listItemES.hits.hits.length) { + // @ts-expect-error @elastic/elasticsearch _source is optional const type = findSourceType(listItemES.hits.hits[0]._source); if (type != null) { const listItems = transformElasticToListItem({ response: listItemES, type }); diff --git a/x-pack/plugins/lists/server/services/items/update_list_item.test.ts b/x-pack/plugins/lists/server/services/items/update_list_item.test.ts index ae6b6ad3faecf9..195bce879f34d6 100644 --- a/x-pack/plugins/lists/server/services/items/update_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/update_list_item.test.ts @@ -34,6 +34,7 @@ describe('update_list_item', () => { const options = getUpdateListItemOptionsMock(); const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.update.mockReturnValue( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ _id: 'elastic-id-123' }) ); const updatedList = await updateListItem({ ...options, esClient }); diff --git a/x-pack/plugins/lists/server/services/items/update_list_item.ts b/x-pack/plugins/lists/server/services/items/update_list_item.ts index 645508691acc81..89c7e77707d8fd 100644 --- a/x-pack/plugins/lists/server/services/items/update_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/update_list_item.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { CreateDocumentResponse } from 'elasticsearch'; import { ElasticsearchClient } from 'kibana/server'; import { @@ -62,7 +61,7 @@ export const updateListItem = async ({ ...elasticQuery, }; - const { body: response } = await esClient.update({ + const { body: response } = await esClient.update({ ...decodeVersion(_version), body: { doc, diff --git a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts index b096adb2d1a132..ee4f3af9cdd5c0 100644 --- a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts +++ b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts @@ -305,7 +305,9 @@ describe('write_list_items_to_stream', () => { test('it will throw an exception with a status code if the hit_source is not a data type we expect', () => { const options = getWriteResponseHitsToStreamOptionsMock(); + // @ts-expect-error _source is optional options.response.hits.hits[0]._source.ip = undefined; + // @ts-expect-error _source is optional options.response.hits.hits[0]._source.keyword = undefined; const expected = `Encountered an error where hit._source was an unexpected type: ${JSON.stringify( options.response.hits.hits[0]._source diff --git a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts index 9bdcb58835ab0b..3679680ad79bd1 100644 --- a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts +++ b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts @@ -7,7 +7,7 @@ import { PassThrough } from 'stream'; -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { ElasticsearchClient } from 'kibana/server'; import { SearchEsListItemSchema } from '../../../common/schemas'; @@ -95,8 +95,9 @@ export const writeNextResponse = async ({ export const getSearchAfterFromResponse = ({ response, }: { - response: SearchResponse; + response: estypes.SearchResponse; }): string[] | undefined => + // @ts-expect-error @elastic/elasticsearch SortResults contains null response.hits.hits.length > 0 ? response.hits.hits[response.hits.hits.length - 1].sort : undefined; @@ -115,7 +116,7 @@ export const getResponse = async ({ listId, listItemIndex, size = SIZE, -}: GetResponseOptions): Promise> => { +}: GetResponseOptions): Promise> => { return (( await esClient.search({ body: { @@ -131,11 +132,11 @@ export const getResponse = async ({ index: listItemIndex, size, }) - ).body as unknown) as SearchResponse; + ).body as unknown) as estypes.SearchResponse; }; export interface WriteResponseHitsToStreamOptions { - response: SearchResponse; + response: estypes.SearchResponse; stream: PassThrough; stringToAppend: string | null | undefined; } @@ -148,6 +149,7 @@ export const writeResponseHitsToStream = ({ const stringToAppendOrEmpty = stringToAppend ?? ''; response.hits.hits.forEach((hit) => { + // @ts-expect-error @elastic/elasticsearch _source is optional const value = findSourceValue(hit._source); if (value != null) { stream.push(`${value}${stringToAppendOrEmpty}`); diff --git a/x-pack/plugins/lists/server/services/lists/create_list.test.ts b/x-pack/plugins/lists/server/services/lists/create_list.test.ts index 6fc556955fae3b..e6213a1c6eabe9 100644 --- a/x-pack/plugins/lists/server/services/lists/create_list.test.ts +++ b/x-pack/plugins/lists/server/services/lists/create_list.test.ts @@ -29,6 +29,7 @@ describe('crete_list', () => { const options = getCreateListOptionsMock(); const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.index.mockReturnValue( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ _id: 'elastic-id-123' }) ); const list = await createList({ ...options, esClient }); @@ -44,6 +45,7 @@ describe('crete_list', () => { }; const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.index.mockReturnValue( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ _id: 'elastic-id-123' }) ); const list = await createList({ ...options, esClient }); @@ -74,6 +76,7 @@ describe('crete_list', () => { options.id = undefined; const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.index.mockReturnValue( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ _id: 'elastic-id-123' }) ); const list = await createList({ ...options, esClient }); diff --git a/x-pack/plugins/lists/server/services/lists/create_list.ts b/x-pack/plugins/lists/server/services/lists/create_list.ts index 2671a23266ec96..baed699dc992fa 100644 --- a/x-pack/plugins/lists/server/services/lists/create_list.ts +++ b/x-pack/plugins/lists/server/services/lists/create_list.ts @@ -6,7 +6,6 @@ */ import uuid from 'uuid'; -import { CreateDocumentResponse } from 'elasticsearch'; import { ElasticsearchClient } from 'kibana/server'; import { encodeHitVersion } from '../utils/encode_hit_version'; @@ -73,7 +72,7 @@ export const createList = async ({ updated_by: user, version, }; - const { body: response } = await esClient.index({ + const { body: response } = await esClient.index({ body, id, index: listIndex, diff --git a/x-pack/plugins/lists/server/services/lists/find_list.ts b/x-pack/plugins/lists/server/services/lists/find_list.ts index c5a398b0a1ad04..9c61d36dc0cd35 100644 --- a/x-pack/plugins/lists/server/services/lists/find_list.ts +++ b/x-pack/plugins/lists/server/services/lists/find_list.ts @@ -6,7 +6,6 @@ */ import { ElasticsearchClient } from 'kibana/server'; -import { SearchResponse } from 'elasticsearch'; import { Filter, @@ -66,6 +65,7 @@ export const findList = async ({ const { body: totalCount } = await esClient.count({ body: { + // @ts-expect-error GetQueryFilterReturn is not compatible with QueryContainer query, }, ignore_unavailable: true, @@ -76,8 +76,9 @@ export const findList = async ({ // Note: This typing of response = await esClient> // is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have // to explicitly define the type . - const { body: response } = await esClient.search>({ + const { body: response } = await esClient.search({ body: { + // @ts-expect-error GetQueryFilterReturn is not compatible with QueryContainer query, search_after: scroll.searchAfter, sort: getSortWithTieBreaker({ sortField, sortOrder }), diff --git a/x-pack/plugins/lists/server/services/lists/get_list.ts b/x-pack/plugins/lists/server/services/lists/get_list.ts index 50e6d08dd80ffd..6f18d143df00bc 100644 --- a/x-pack/plugins/lists/server/services/lists/get_list.ts +++ b/x-pack/plugins/lists/server/services/lists/get_list.ts @@ -6,7 +6,6 @@ */ import { ElasticsearchClient } from 'kibana/server'; -import { SearchResponse } from 'elasticsearch'; import { Id, ListSchema, SearchEsListSchema } from '../../../common/schemas'; import { transformElasticToList } from '../utils/transform_elastic_to_list'; @@ -25,7 +24,7 @@ export const getList = async ({ // Note: This typing of response = await esClient> // is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have // to explicitly define the type . - const { body: response } = await esClient.search>({ + const { body: response } = await esClient.search({ body: { query: { term: { diff --git a/x-pack/plugins/lists/server/services/lists/update_list.test.ts b/x-pack/plugins/lists/server/services/lists/update_list.test.ts index e2d3b09fe518ac..8cc1c60ecc23dd 100644 --- a/x-pack/plugins/lists/server/services/lists/update_list.test.ts +++ b/x-pack/plugins/lists/server/services/lists/update_list.test.ts @@ -34,6 +34,7 @@ describe('update_list', () => { const options = getUpdateListOptionsMock(); const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.update.mockReturnValue( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ _id: 'elastic-id-123' }) ); const updatedList = await updateList({ ...options, esClient }); @@ -51,6 +52,7 @@ describe('update_list', () => { const options = getUpdateListOptionsMock(); const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.update.mockReturnValue( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ _id: 'elastic-id-123' }) ); const updatedList = await updateList({ ...options, esClient }); diff --git a/x-pack/plugins/lists/server/services/lists/update_list.ts b/x-pack/plugins/lists/server/services/lists/update_list.ts index aa4eb9a8d834fd..f98e40b04b6d73 100644 --- a/x-pack/plugins/lists/server/services/lists/update_list.ts +++ b/x-pack/plugins/lists/server/services/lists/update_list.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { CreateDocumentResponse } from 'elasticsearch'; import { ElasticsearchClient } from 'kibana/server'; import { decodeVersion } from '../utils/decode_version'; @@ -61,7 +60,7 @@ export const updateList = async ({ updated_at: updatedAt, updated_by: user, }; - const { body: response } = await esClient.update({ + const { body: response } = await esClient.update({ ...decodeVersion(_version), body: { doc }, id, diff --git a/x-pack/plugins/lists/server/services/utils/get_search_after_scroll.ts b/x-pack/plugins/lists/server/services/utils/get_search_after_scroll.ts index 34359a7a9c697e..ae37e47861845a 100644 --- a/x-pack/plugins/lists/server/services/utils/get_search_after_scroll.ts +++ b/x-pack/plugins/lists/server/services/utils/get_search_after_scroll.ts @@ -6,7 +6,6 @@ */ import { ElasticsearchClient } from 'kibana/server'; -import { SearchResponse } from 'elasticsearch'; import { Filter, SortFieldOrUndefined, SortOrderOrUndefined } from '../../../common/schemas'; import { Scroll } from '../lists/types'; @@ -40,9 +39,10 @@ export const getSearchAfterScroll = async ({ const query = getQueryFilter({ filter }); let newSearchAfter = searchAfter; for (let i = 0; i < hops; ++i) { - const { body: response } = await esClient.search>>({ + const { body: response } = await esClient.search>({ body: { _source: getSourceWithTieBreaker({ sortField }), + // @ts-expect-error Filter is not assignale to QueryContainer query, search_after: newSearchAfter, sort: getSortWithTieBreaker({ sortField, sortOrder }), diff --git a/x-pack/plugins/lists/server/services/utils/get_search_after_with_tie_breaker.ts b/x-pack/plugins/lists/server/services/utils/get_search_after_with_tie_breaker.ts index 87749ed6fdb3b2..3cd902aeeb36e5 100644 --- a/x-pack/plugins/lists/server/services/utils/get_search_after_with_tie_breaker.ts +++ b/x-pack/plugins/lists/server/services/utils/get_search_after_with_tie_breaker.ts @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { SortFieldOrUndefined } from '../../../common/schemas'; @@ -14,7 +13,7 @@ export type TieBreaker = T & { }; interface GetSearchAfterWithTieBreakerOptions { - response: SearchResponse>; + response: estypes.SearchResponse>; sortField: SortFieldOrUndefined; } @@ -27,14 +26,18 @@ export const getSearchAfterWithTieBreaker = ({ } else { const lastEsElement = response.hits.hits[response.hits.hits.length - 1]; if (sortField == null) { + // @ts-expect-error @elastic/elasticsearch _source is optional return [lastEsElement._source.tie_breaker_id]; } else { - const [[, sortValue]] = Object.entries(lastEsElement._source).filter( - ([key]) => key === sortField - ); + const [[, sortValue]] = Object.entries( + // @ts-expect-error @elastic/elasticsearch _source is optional + lastEsElement._source + ).filter(([key]) => key === sortField); if (typeof sortValue === 'string') { + // @ts-expect-error @elastic/elasticsearch _source is optional return [sortValue, lastEsElement._source.tie_breaker_id]; } else { + // @ts-expect-error @elastic/elasticsearch _source is optional return [lastEsElement._source.tie_breaker_id]; } } diff --git a/x-pack/plugins/lists/server/services/utils/get_sort_with_tie_breaker.ts b/x-pack/plugins/lists/server/services/utils/get_sort_with_tie_breaker.ts index 3fd886f8f6919e..97cfe3dd8e6347 100644 --- a/x-pack/plugins/lists/server/services/utils/get_sort_with_tie_breaker.ts +++ b/x-pack/plugins/lists/server/services/utils/get_sort_with_tie_breaker.ts @@ -4,25 +4,21 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import type { estypes } from '@elastic/elasticsearch'; import { SortFieldOrUndefined, SortOrderOrUndefined } from '../../../common/schemas'; -export interface SortWithTieBreakerReturn { - tie_breaker_id: 'asc'; - [key: string]: string; -} - export const getSortWithTieBreaker = ({ sortField, sortOrder, }: { sortField: SortFieldOrUndefined; sortOrder: SortOrderOrUndefined; -}): SortWithTieBreakerReturn[] | undefined => { - const ascOrDesc = sortOrder ?? 'asc'; +}): estypes.SortCombinations[] => { + const ascOrDesc = sortOrder ?? ('asc' as const); if (sortField != null) { - return [{ [sortField]: ascOrDesc, tie_breaker_id: 'asc' }]; + return [{ [sortField]: ascOrDesc, tie_breaker_id: 'asc' as const }]; } else { - return [{ tie_breaker_id: 'asc' }]; + return [{ tie_breaker_id: 'asc' as const }]; } }; diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts index 3dd0f083797f10..4f0f8fe49a9d0c 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { SearchEsListItemSchema, SearchListItemArraySchema, Type } from '../../../common/schemas'; import { transformElasticHitsToListItem } from './transform_elastic_to_list_item'; export interface TransformElasticMSearchToListItemOptions { - response: SearchResponse; + response: estypes.SearchResponse; type: Type; value: unknown[]; } diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts index fa77336fb7724e..4ed08f70219aff 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts @@ -5,19 +5,20 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { ListArraySchema, SearchEsListSchema } from '../../../common/schemas'; import { encodeHitVersion } from './encode_hit_version'; export interface TransformElasticToListOptions { - response: SearchResponse; + response: estypes.SearchResponse; } export const transformElasticToList = ({ response, }: TransformElasticToListOptions): ListArraySchema => { + // @ts-expect-error created_at is incompatible return response.hits.hits.map((hit) => { return { _version: encodeHitVersion(hit), diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts index 8c1949ed90cdad..436987e71dd221 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { ListItemArraySchema, SearchEsListItemSchema, Type } from '../../../common/schemas'; import { ErrorWithStatusCode } from '../../error_with_status_code'; @@ -14,12 +14,12 @@ import { encodeHitVersion } from './encode_hit_version'; import { findSourceValue } from './find_source_value'; export interface TransformElasticToListItemOptions { - response: SearchResponse; + response: estypes.SearchResponse; type: Type; } export interface TransformElasticHitToListItemOptions { - hits: SearchResponse['hits']['hits']; + hits: Array>; type: Type; } @@ -35,22 +35,21 @@ export const transformElasticHitsToListItem = ({ type, }: TransformElasticHitToListItemOptions): ListItemArraySchema => { return hits.map((hit) => { + const { _id, _source } = hit; const { - _id, - _source: { - /* eslint-disable @typescript-eslint/naming-convention */ - created_at, - deserializer, - serializer, - updated_at, - updated_by, - created_by, - list_id, - tie_breaker_id, - meta, - /* eslint-enable @typescript-eslint/naming-convention */ - }, - } = hit; + /* eslint-disable @typescript-eslint/naming-convention */ + created_at, + deserializer, + serializer, + updated_at, + updated_by, + created_by, + list_id, + tie_breaker_id, + meta, + /* eslint-enable @typescript-eslint/naming-convention */ + } = _source!; // eslint-disable-line @typescript-eslint/no-non-null-assertion + // @ts-expect-error _source is optional const value = findSourceValue(hit._source); if (value == null) { throw new ErrorWithStatusCode(`Was expected ${type} to not be null/undefined`, 400); diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts index 0936cdc50b4c03..c55a564951c4ec 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts @@ -285,11 +285,29 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource const abortController = new AbortController(); registerCancelCallback(() => abortController.abort()); const esResp = await searchSource.fetch({ abortSignal: abortController.signal }); - if (!esResp.aggregations.fitToBounds.bounds) { + + if (!esResp.aggregations) { + return null; + } + + const fitToBounds = esResp.aggregations.fitToBounds as { + bounds?: { + top_left: { + lat: number; + lon: number; + }; + bottom_right: { + lat: number; + lon: number; + }; + }; + }; + + if (!fitToBounds.bounds) { // aggregations.fitToBounds is empty object when there are no matching documents return null; } - esBounds = esResp.aggregations.fitToBounds.bounds; + esBounds = fitToBounds.bounds; } catch (error) { if (error.name === 'AbortError') { throw new DataRequestAbortError(); diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts index 3274261cdba56a..d6ebf2fb216b2a 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -179,6 +179,7 @@ export async function getTile({ [KBN_TOO_MANY_FEATURES_PROPERTY]: true, }, geometry: esBboxToGeoJsonPolygon( + // @ts-expect-error @elastic/elasticsearch no way to declare aggregations for search response bboxResponse.rawResponse.aggregations.data_bounds.bounds, tileToESBbox(x, y, z) ), @@ -199,6 +200,7 @@ export async function getTile({ // Todo: pass in epochMillies-fields const featureCollection = hitsToGeoJson( + // @ts-expect-error hitsToGeoJson should be refactored to accept estypes.Hit documentsResponse.rawResponse.hits.hits, (hit: Record) => { return flattenHit(geometryFieldName, hit); diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts index ed9c9e75897497..77d453b68edc53 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts @@ -5,33 +5,41 @@ * 2.0. */ -import { IndexPatternTitle } from '../kibana'; -import { RuntimeMappings } from '../fields'; -import { JobId } from './job'; +import { estypes } from '@elastic/elasticsearch'; +// import { IndexPatternTitle } from '../kibana'; +// import { RuntimeMappings } from '../fields'; +// import { JobId } from './job'; export type DatafeedId = string; -export interface Datafeed { - datafeed_id: DatafeedId; - aggregations?: Aggregation; - aggs?: Aggregation; - chunking_config?: ChunkingConfig; - frequency?: string; - indices: IndexPatternTitle[]; - indexes?: IndexPatternTitle[]; // The datafeed can contain indexes and indices - job_id: JobId; - query: object; - query_delay?: string; - script_fields?: Record; - runtime_mappings?: RuntimeMappings; - scroll_size?: number; - delayed_data_check_config?: object; - indices_options?: IndicesOptions; -} +export type Datafeed = estypes.Datafeed; +// export interface Datafeed extends estypes.DatafeedConfig { +// runtime_mappings?: RuntimeMappings; +// aggs?: Aggregation; +// } +// export interface Datafeed { +// datafeed_id: DatafeedId; +// aggregations?: Aggregation; +// aggs?: Aggregation; +// chunking_config?: ChunkingConfig; +// frequency?: string; +// indices: IndexPatternTitle[]; +// indexes?: IndexPatternTitle[]; // The datafeed can contain indexes and indices +// job_id: JobId; +// query: object; +// query_delay?: string; +// script_fields?: Record; +// runtime_mappings?: RuntimeMappings; +// scroll_size?: number; +// delayed_data_check_config?: object; +// indices_options?: IndicesOptions; +// } -export interface ChunkingConfig { - mode: 'auto' | 'manual' | 'off'; - time_span?: string; -} +export type ChunkingConfig = estypes.ChunkingConfig; + +// export interface ChunkingConfig { +// mode: 'auto' | 'manual' | 'off'; +// time_span?: string; +// } export type Aggregation = Record< string, @@ -45,9 +53,10 @@ export type Aggregation = Record< } >; -export interface IndicesOptions { - expand_wildcards?: 'all' | 'open' | 'closed' | 'hidden' | 'none'; - ignore_unavailable?: boolean; - allow_no_indices?: boolean; - ignore_throttled?: boolean; -} +export type IndicesOptions = estypes.IndicesOptions; +// export interface IndicesOptions { +// expand_wildcards?: 'all' | 'open' | 'closed' | 'hidden' | 'none'; +// ignore_unavailable?: boolean; +// allow_no_indices?: boolean; +// ignore_throttled?: boolean; +// } diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts index a4b0a5c5c60685..5e1d5e009a7642 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { UrlConfig } from '../custom_urls'; import { CREATED_BY_LABEL } from '../../constants/new_job'; @@ -19,79 +20,87 @@ export interface CustomSettings { }; } -export interface Job { - job_id: JobId; - analysis_config: AnalysisConfig; - analysis_limits?: AnalysisLimits; - background_persist_interval?: string; - custom_settings?: CustomSettings; - data_description: DataDescription; - description: string; - groups: string[]; - model_plot_config?: ModelPlotConfig; - model_snapshot_retention_days?: number; - daily_model_snapshot_retention_after_days?: number; - renormalization_window_days?: number; - results_index_name?: string; - results_retention_days?: number; +export type Job = estypes.Job; +// export interface Job { +// job_id: JobId; +// analysis_config: AnalysisConfig; +// analysis_limits?: AnalysisLimits; +// background_persist_interval?: string; +// custom_settings?: CustomSettings; +// data_description: DataDescription; +// description: string; +// groups: string[]; +// model_plot_config?: ModelPlotConfig; +// model_snapshot_retention_days?: number; +// daily_model_snapshot_retention_after_days?: number; +// renormalization_window_days?: number; +// results_index_name?: string; +// results_retention_days?: number; - // optional properties added when the job has been created - create_time?: number; - finished_time?: number; - job_type?: 'anomaly_detector'; - job_version?: string; - model_snapshot_id?: string; - deleting?: boolean; -} +// // optional properties added when the job has been created +// create_time?: number; +// finished_time?: number; +// job_type?: 'anomaly_detector'; +// job_version?: string; +// model_snapshot_id?: string; +// deleting?: boolean; +// } -export interface AnalysisConfig { - bucket_span: BucketSpan; - categorization_field_name?: string; - categorization_filters?: string[]; - categorization_analyzer?: object | string; - detectors: Detector[]; - influencers: string[]; - latency?: number; - multivariate_by_fields?: boolean; - summary_count_field_name?: string; - per_partition_categorization?: PerPartitionCategorization; -} +export type AnalysisConfig = estypes.AnalysisConfig; +// export interface AnalysisConfig { +// bucket_span: BucketSpan; +// categorization_field_name?: string; +// categorization_filters?: string[]; +// categorization_analyzer?: object | string; +// detectors: Detector[]; +// influencers: string[]; +// latency?: number; +// multivariate_by_fields?: boolean; +// summary_count_field_name?: string; +// per_partition_categorization?: PerPartitionCategorization; +// } -export interface Detector { - by_field_name?: string; - detector_description?: string; - detector_index?: number; - exclude_frequent?: string; - field_name?: string; - function: string; - over_field_name?: string; - partition_field_name?: string; - use_null?: boolean; - custom_rules?: CustomRule[]; -} -export interface AnalysisLimits { - categorization_examples_limit?: number; - model_memory_limit: string; -} +export type Detector = estypes.Detector; +// export interface Detector { +// by_field_name?: string; +// detector_description?: string; +// detector_index?: number; +// exclude_frequent?: string; +// field_name?: string; +// function: string; +// over_field_name?: string; +// partition_field_name?: string; +// use_null?: boolean; +// custom_rules?: CustomRule[]; +// } -export interface DataDescription { - format?: string; - time_field: string; - time_format?: string; -} +export type AnalysisLimits = estypes.AnalysisLimits; +// export interface AnalysisLimits { +// categorization_examples_limit?: number; +// model_memory_limit: string; +// } -export interface ModelPlotConfig { - enabled?: boolean; - annotations_enabled?: boolean; - terms?: string; -} +export type DataDescription = estypes.DataDescription; +// export interface DataDescription { +// format?: string; +// time_field: string; +// time_format?: string; +// } +export type ModelPlotConfig = estypes.ModelPlotConfig; +// export interface ModelPlotConfig { +// enabled?: boolean; +// annotations_enabled?: boolean; +// terms?: string; +// } + +export type CustomRule = estypes.DetectionRule; // TODO, finish this when it's needed -export interface CustomRule { - actions: string[]; - scope?: object; - conditions: any[]; -} +// export interface CustomRule { +// actions: string[]; +// scope?: object; +// conditions: any[]; +// } export interface PerPartitionCategorization { enabled?: boolean; diff --git a/x-pack/plugins/ml/common/types/fields.ts b/x-pack/plugins/ml/common/types/fields.ts index 047852534965c2..f9f7f8fc7ead69 100644 --- a/x-pack/plugins/ml/common/types/fields.ts +++ b/x-pack/plugins/ml/common/types/fields.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { ES_FIELD_TYPES } from '../../../../../src/plugins/data/common'; import { ML_JOB_AGGREGATION, @@ -120,4 +121,4 @@ export interface RuntimeField { }; } -export type RuntimeMappings = Record; +export type RuntimeMappings = estypes.RuntimeFields; diff --git a/x-pack/plugins/ml/common/util/job_utils.ts b/x-pack/plugins/ml/common/util/job_utils.ts index 4b80661f13c093..10f5fb975ef5e1 100644 --- a/x-pack/plugins/ml/common/util/job_utils.ts +++ b/x-pack/plugins/ml/common/util/job_utils.ts @@ -733,7 +733,7 @@ export function validateGroupNames(job: Job): ValidationResults { * @return {Duration} the parsed interval, or null if it does not represent a valid * time interval. */ -export function parseTimeIntervalForJob(value: string | undefined): Duration | null { +export function parseTimeIntervalForJob(value: string | number | undefined): Duration | null { if (value === undefined) { return null; } @@ -748,7 +748,7 @@ export function parseTimeIntervalForJob(value: string | undefined): Duration | n // Checks that the value for a field which represents a time interval, // such as a job bucket span or datafeed query delay, is valid. -function isValidTimeInterval(value: string | undefined): boolean { +function isValidTimeInterval(value: string | number | undefined): boolean { if (value === undefined) { return true; } diff --git a/x-pack/plugins/ml/common/util/parse_interval.ts b/x-pack/plugins/ml/common/util/parse_interval.ts index c3013ef447792d..6ca280dc12ebda 100644 --- a/x-pack/plugins/ml/common/util/parse_interval.ts +++ b/x-pack/plugins/ml/common/util/parse_interval.ts @@ -34,7 +34,10 @@ const SUPPORT_ES_DURATION_UNITS: SupportedUnits[] = ['ms', 's', 'm', 'h', 'd']; // to work with units less than 'day'. // 3. Fractional intervals e.g. 1.5h or 4.5d are not allowed, in line with the behaviour // of the Elasticsearch date histogram aggregation. -export function parseInterval(interval: string, checkValidEsUnit = false): Duration | null { +export function parseInterval( + interval: string | number, + checkValidEsUnit = false +): Duration | null { const matches = String(interval).trim().match(INTERVAL_STRING_RE); if (!Array.isArray(matches) || matches.length < 3) { return null; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts index 69750b0ab1aaa7..312776f0d6a078 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -110,6 +110,7 @@ export const getRuntimeFieldsMapping = ( if (isPopulatedObject(ipRuntimeMappings)) { indexPatternFields.forEach((ipField) => { if (ipRuntimeMappings.hasOwnProperty(ipField)) { + // @ts-expect-error combinedRuntimeMappings[ipField] = ipRuntimeMappings[ipField]; } }); diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/chart_loader.ts b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/chart_loader.ts index a3753c8f000ae9..4788254e97d1e8 100644 --- a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/chart_loader.ts +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/chart_loader.ts @@ -30,6 +30,7 @@ export function chartLoaderProvider(mlResultsService: MlResultsService) { job.data_counts.earliest_record_timestamp, job.data_counts.latest_record_timestamp, intervalMs, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options job.datafeed_config.indices_options ); if (resp.error !== undefined) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts index 2ca95a14fb8124..aac36f3e4f573f 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; import { JobCreator } from './job_creator'; @@ -28,7 +29,7 @@ export interface RichDetector { byField: SplitField; overField: SplitField; partitionField: SplitField; - excludeFrequent: string | null; + excludeFrequent: estypes.ExcludeFrequent | null; description: string | null; customRules: CustomRule[] | null; } @@ -56,7 +57,7 @@ export class AdvancedJobCreator extends JobCreator { byField: SplitField, overField: SplitField, partitionField: SplitField, - excludeFrequent: string | null, + excludeFrequent: estypes.ExcludeFrequent | null, description: string | null ) { // addDetector doesn't support adding new custom rules. @@ -83,7 +84,7 @@ export class AdvancedJobCreator extends JobCreator { byField: SplitField, overField: SplitField, partitionField: SplitField, - excludeFrequent: string | null, + excludeFrequent: estypes.ExcludeFrequent | null, description: string | null, index: number ) { @@ -114,7 +115,7 @@ export class AdvancedJobCreator extends JobCreator { byField: SplitField, overField: SplitField, partitionField: SplitField, - excludeFrequent: string | null, + excludeFrequent: estypes.ExcludeFrequent | null, description: string | null, customRules: CustomRule[] | null ): { detector: Detector; richDetector: RichDetector } { @@ -182,6 +183,7 @@ export class AdvancedJobCreator extends JobCreator { timeFieldName: this.timeFieldName, query: this.query, runtimeMappings: this.datafeedConfig.runtime_mappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options indicesOptions: this.datafeedConfig.indices_options, }); this.setTimeRange(start.epoch, end.epoch); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index ec5cb59964ffd5..13d46faaf21cf6 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -246,12 +246,20 @@ export class JobCreator { private _initModelPlotConfig() { // initialize configs to false if they are missing if (this._job_config.model_plot_config === undefined) { - this._job_config.model_plot_config = {}; + this._job_config.model_plot_config = { + enabled: false, + }; } - if (this._job_config.model_plot_config.enabled === undefined) { + if ( + this._job_config.model_plot_config !== undefined && + this._job_config.model_plot_config.enabled === undefined + ) { this._job_config.model_plot_config.enabled = false; } - if (this._job_config.model_plot_config.annotations_enabled === undefined) { + if ( + this._job_config.model_plot_config !== undefined && + this._job_config.model_plot_config.annotations_enabled === undefined + ) { this._job_config.model_plot_config.annotations_enabled = false; } } @@ -636,6 +644,7 @@ export class JobCreator { this._job_config.custom_settings !== undefined && this._job_config.custom_settings[setting] !== undefined ) { + // @ts-expect-error return this._job_config.custom_settings[setting]; } return null; @@ -710,6 +719,7 @@ export class JobCreator { this._datafeed_config.runtime_mappings = {}; } Object.entries(runtimeFieldMap).forEach(([key, val]) => { + // @ts-expect-error this._datafeed_config.runtime_mappings![key] = val; }); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/default_configs.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/default_configs.ts index 201a304fd33560..bf354b8ad984f9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/default_configs.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/default_configs.ts @@ -11,6 +11,7 @@ import { Job, Datafeed, Detector } from '../../../../../../../common/types/anoma import { splitIndexPatternNames } from '../../../../../../../common/util/job_utils'; export function createEmptyJob(): Job { + // @ts-expect-error return { job_id: '', description: '', @@ -27,6 +28,7 @@ export function createEmptyJob(): Job { } export function createEmptyDatafeed(indexPatternTitle: IndexPatternTitle): Datafeed { + // @ts-expect-error return { datafeed_id: '', job_id: '', diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.test.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.test.ts index 43e7d4e45b6e0c..c67a93c5e06262 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.test.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.test.ts @@ -9,6 +9,7 @@ import { Job, Datafeed } from '../../../../../../../common/types/anomaly_detecti import { filterRuntimeMappings } from './filter_runtime_mappings'; function getJob(): Job { + // @ts-expect-error return { job_id: 'test', description: '', @@ -53,12 +54,14 @@ function getDatafeed(): Datafeed { runtime_mappings: { responsetime_big: { type: 'double', + // @ts-expect-error @elastic/elasticsearch StoredScript.language is required script: { source: "emit(doc['responsetime'].value * 100.0)", }, }, airline_lower: { type: 'keyword', + // @ts-expect-error @elastic/elasticsearch StoredScript.language is required script: { source: "emit(doc['airline'].value.toLowerCase())", }, diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts index 641eda3dbf3e81..b51cd9b99792b9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts @@ -52,6 +52,7 @@ export class CategorizationExamplesLoader { this._jobCreator.end, analyzer, this._jobCreator.runtimeMappings ?? undefined, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options this._jobCreator.datafeedConfig.indices_options ); return resp; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts index a01581f7526c5a..6d5fa26af70241 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts @@ -257,6 +257,7 @@ export class ResultsLoader { const fieldValues = await this._chartLoader.loadFieldExampleValues( this._jobCreator.splitField, this._jobCreator.runtimeMappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options this._jobCreator.datafeedConfig.indices_options ); if (fieldValues.length > 0) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx index 751f12e14f6b50..10c160f58ff771 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import React, { FC, Fragment, useState, useContext, useEffect } from 'react'; import { EuiComboBox, @@ -170,6 +171,7 @@ export const AdvancedDetectorModal: FC = ({ byField, overField, partitionField, + // @ts-expect-error excludeFrequent: excludeFrequentOption.label !== '' ? excludeFrequentOption.label : null, description: descriptionOption !== '' ? descriptionOption : null, customRules: null, @@ -343,7 +345,9 @@ function createFieldOption(field: Field | null): EuiComboBoxOptionOption { }; } -function createExcludeFrequentOption(excludeFrequent: string | null): EuiComboBoxOptionOption { +function createExcludeFrequentOption( + excludeFrequent: estypes.ExcludeFrequent | null +): EuiComboBoxOptionOption { if (excludeFrequent === null) { return emptyOption; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts index 85083146c13789..113bde6fbf93d9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts @@ -42,6 +42,7 @@ export function useEstimateBucketSpan() { splitField: undefined, timeField: mlContext.currentIndexPattern.timeFieldName, runtimeMappings: jobCreator.runtimeMappings ?? undefined, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options indicesOptions: jobCreator.datafeedConfig.indices_options, }; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx index da9f306cf30e61..f3396a95738a6e 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx @@ -55,6 +55,7 @@ export const CategorizationDetectorsSummary: FC = () => { jobCreator.start, jobCreator.end, chartInterval.getInterval().asMilliseconds(), + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ); setEventRateChartData(resp); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx index 46eb4b88d0518d..05154964690305 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx @@ -114,6 +114,7 @@ export const MultiMetricDetectors: FC = ({ setIsValid }) => { .loadFieldExampleValues( splitField, jobCreator.runtimeMappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ) .then(setFieldValues) @@ -145,6 +146,7 @@ export const MultiMetricDetectors: FC = ({ setIsValid }) => { fieldValues.length > 0 ? fieldValues[0] : null, cs.intervalMs, jobCreator.runtimeMappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ); setLineChartsData(resp); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx index a4c344d16482be..dc76fc01781123 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx @@ -44,6 +44,7 @@ export const MultiMetricDetectorsSummary: FC = () => { const tempFieldValues = await chartLoader.loadFieldExampleValues( jobCreator.splitField, jobCreator.runtimeMappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ); setFieldValues(tempFieldValues); @@ -78,6 +79,7 @@ export const MultiMetricDetectorsSummary: FC = () => { fieldValues.length > 0 ? fieldValues[0] : null, cs.intervalMs, jobCreator.runtimeMappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ); setLineChartsData(resp); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx index a7eaaff6111837..7f5a06925c7e8c 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx @@ -161,6 +161,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { jobCreator.splitField, cs.intervalMs, jobCreator.runtimeMappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ); @@ -184,6 +185,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { fields: await chartLoader.loadFieldExampleValues( field, jobCreator.runtimeMappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ), }; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx index 55a9d37d1115cc..31b436944a5b08 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx @@ -79,6 +79,7 @@ export const PopulationDetectorsSummary: FC = () => { jobCreator.splitField, cs.intervalMs, jobCreator.runtimeMappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ); @@ -102,6 +103,7 @@ export const PopulationDetectorsSummary: FC = () => { fields: await chartLoader.loadFieldExampleValues( field, jobCreator.runtimeMappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ), }; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx index 0e09a81908e838..c5c5cd4d8b7446 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx @@ -94,6 +94,7 @@ export const SingleMetricDetectors: FC = ({ setIsValid }) => { null, cs.intervalMs, jobCreator.runtimeMappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ); if (resp[DTR_IDX] !== undefined) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx index ced94b2095f722..5e64f4ef189845 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx @@ -60,6 +60,7 @@ export const SingleMetricDetectorsSummary: FC = () => { null, cs.intervalMs, jobCreator.runtimeMappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ); if (resp[DTR_IDX] !== undefined) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx index d2cf6b7a004713..b57fd45019abe0 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx @@ -48,6 +48,7 @@ export const TimeRangeStep: FC = ({ setCurrentStep, isCurrentStep }) jobCreator.start, jobCreator.end, chartInterval.getInterval().asMilliseconds(), + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ); setEventRateChartData(resp); diff --git a/x-pack/plugins/ml/public/application/util/string_utils.ts b/x-pack/plugins/ml/public/application/util/string_utils.ts index 9cd22d1d6ce76c..b981bbb8fe1a61 100644 --- a/x-pack/plugins/ml/public/application/util/string_utils.ts +++ b/x-pack/plugins/ml/public/application/util/string_utils.ts @@ -74,7 +74,7 @@ export function detectorToString(dtr: Detector): string { txt += PARTITION_FIELD_OPTION + quoteField(dtr.partition_field_name); } - if (dtr.exclude_frequent !== undefined && dtr.exclude_frequent !== '') { + if (dtr.exclude_frequent !== undefined) { txt += EXCLUDE_FREQUENT_OPTION + dtr.exclude_frequent; } diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts index ce615418967210..81529669749bce 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts @@ -398,6 +398,7 @@ export function alertingServiceProvider(mlClient: MlClient, esClient: Elasticsea const response = await mlClient.anomalySearch( { + // @ts-expect-error body: requestBody, }, jobIds @@ -425,7 +426,8 @@ export function alertingServiceProvider(mlClient: MlClient, esClient: Elasticsea .filter((v) => v.doc_count > 0 && v[resultsLabel.aggGroupLabel].doc_count > 0) // Map response .map(formatter) - : [formatter(result as AggResultsResponse)] + : // @ts-expect-error + [formatter(result as AggResultsResponse)] ).filter(isDefined); }; diff --git a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts index 278dd19f74acc3..6e76a536feb253 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts @@ -6,17 +6,10 @@ */ import { IScopedClusterClient } from 'kibana/server'; - import { JobSavedObjectService } from '../../saved_objects'; import { JobType } from '../../../common/types/saved_objects'; -import { - Job, - JobStats, - Datafeed, - DatafeedStats, -} from '../../../common/types/anomaly_detection_jobs'; -import { Calendar } from '../../../common/types/calendars'; +import { Job, Datafeed } from '../../../common/types/anomaly_detection_jobs'; import { searchProvider } from './search'; import { DataFrameAnalyticsConfig } from '../../../common/types/data_frame_analytics'; @@ -109,7 +102,7 @@ export function getMlClient( // similar to groupIdsCheck above, however we need to load the jobs first to get the groups information const ids = getADJobIdsFromRequest(p); if (ids.length) { - const { body } = await mlClient.getJobs<{ jobs: Job[] }>(...p); + const { body } = await mlClient.getJobs(...p); await groupIdsCheck(p, body.jobs, filteredJobIds); } } @@ -131,6 +124,7 @@ export function getMlClient( } } + // @ts-expect-error promise and TransportRequestPromise are incompatible. missing abort return { async closeJob(...p: Parameters) { await jobIdsCheck('anomaly-detector', p); @@ -152,7 +146,7 @@ export function getMlClient( // deleted initially and could still fail. return resp; }, - async deleteDatafeed(...p: any) { + async deleteDatafeed(...p: Parameters) { await datafeedIdsCheck(p); const resp = await mlClient.deleteDatafeed(...p); const [datafeedId] = getDatafeedIdsFromRequest(p); @@ -213,7 +207,7 @@ export function getMlClient( return mlClient.getCalendarEvents(...p); }, async getCalendars(...p: Parameters) { - const { body } = await mlClient.getCalendars<{ calendars: Calendar[] }, any>(...p); + const { body } = await mlClient.getCalendars(...p); const { body: { jobs: allJobs }, } = await mlClient.getJobs<{ jobs: Job[] }>(); @@ -263,9 +257,9 @@ export function getMlClient( // this should use DataFrameAnalyticsStats, but needs a refactor to move DataFrameAnalyticsStats to common await jobIdsCheck('data-frame-analytics', p, true); try { - const { body } = await mlClient.getDataFrameAnalyticsStats<{ - data_frame_analytics: DataFrameAnalyticsConfig[]; - }>(...p); + const { body } = ((await mlClient.getDataFrameAnalyticsStats(...p)) as unknown) as { + body: { data_frame_analytics: DataFrameAnalyticsConfig[] }; + }; const jobs = await jobSavedObjectService.filterJobsForSpace( 'data-frame-analytics', body.data_frame_analytics, @@ -282,8 +276,8 @@ export function getMlClient( async getDatafeedStats(...p: Parameters) { await datafeedIdsCheck(p, true); try { - const { body } = await mlClient.getDatafeedStats<{ datafeeds: DatafeedStats[] }>(...p); - const datafeeds = await jobSavedObjectService.filterDatafeedsForSpace( + const { body } = await mlClient.getDatafeedStats(...p); + const datafeeds = await jobSavedObjectService.filterDatafeedsForSpace( 'anomaly-detector', body.datafeeds, 'datafeed_id' @@ -299,7 +293,7 @@ export function getMlClient( async getDatafeeds(...p: Parameters) { await datafeedIdsCheck(p, true); try { - const { body } = await mlClient.getDatafeeds<{ datafeeds: Datafeed[] }>(...p); + const { body } = await mlClient.getDatafeeds(...p); const datafeeds = await jobSavedObjectService.filterDatafeedsForSpace( 'anomaly-detector', body.datafeeds, @@ -322,8 +316,8 @@ export function getMlClient( }, async getJobStats(...p: Parameters) { try { - const { body } = await mlClient.getJobStats<{ jobs: JobStats[] }>(...p); - const jobs = await jobSavedObjectService.filterJobsForSpace( + const { body } = await mlClient.getJobStats(...p); + const jobs = await jobSavedObjectService.filterJobsForSpace( 'anomaly-detector', body.jobs, 'job_id' diff --git a/x-pack/plugins/ml/server/lib/ml_client/search.ts b/x-pack/plugins/ml/server/lib/ml_client/search.ts index 158de0017fbbf0..3062a70d9a975a 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/search.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/search.ts @@ -7,11 +7,10 @@ import Boom from '@hapi/boom'; import { IScopedClusterClient } from 'kibana/server'; -import { RequestParams, ApiResponse } from '@elastic/elasticsearch'; +import { estypes, ApiResponse } from '@elastic/elasticsearch'; import { JobSavedObjectService } from '../../saved_objects'; import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns'; -import type { SearchResponse7 } from '../../../common/types/es_client'; import type { JobType } from '../../../common/types/saved_objects'; export function searchProvider( @@ -29,12 +28,12 @@ export function searchProvider( } async function anomalySearch( - searchParams: RequestParams.Search, + searchParams: estypes.SearchRequest, jobIds: string[] - ): Promise>> { + ): Promise>> { await jobIdsCheck('anomaly-detector', jobIds); const { asInternalUser } = client; - const resp = await asInternalUser.search>({ + const resp = await asInternalUser.search({ ...searchParams, index: ML_RESULTS_INDEX_PATTERN, }); diff --git a/x-pack/plugins/ml/server/lib/node_utils.ts b/x-pack/plugins/ml/server/lib/node_utils.ts index b76245fb9796cc..82e5d7f4698498 100644 --- a/x-pack/plugins/ml/server/lib/node_utils.ts +++ b/x-pack/plugins/ml/server/lib/node_utils.ts @@ -17,7 +17,7 @@ export async function getMlNodeCount(client: IScopedClusterClient): Promise { if (body.nodes[k].attributes !== undefined) { - const maxOpenJobs = body.nodes[k].attributes['ml.max_open_jobs']; + const maxOpenJobs = +body.nodes[k].attributes['ml.max_open_jobs']; if (maxOpenJobs !== null && maxOpenJobs > 0) { count++; } diff --git a/x-pack/plugins/ml/server/lib/query_utils.ts b/x-pack/plugins/ml/server/lib/query_utils.ts index 265962bb8432c7..dd4dc01498dbba 100644 --- a/x-pack/plugins/ml/server/lib/query_utils.ts +++ b/x-pack/plugins/ml/server/lib/query_utils.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; /* * Contains utility functions for building and processing queries. */ @@ -40,7 +41,10 @@ export function buildBaseFilterCriteria( // Wraps the supplied aggregations in a sampler aggregation. // A supplied samplerShardSize (the shard_size parameter of the sampler aggregation) // of less than 1 indicates no sampling, and the aggs are returned as-is. -export function buildSamplerAggregation(aggs: object, samplerShardSize: number) { +export function buildSamplerAggregation( + aggs: any, + samplerShardSize: number +): Record { if (samplerShardSize < 1) { return aggs; } diff --git a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts index f5c64cfa51efcc..2d5bd1f6f6e453 100644 --- a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts +++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts @@ -290,11 +290,13 @@ export function annotationProvider({ asInternalUser }: IScopedClusterClient) { try { const { body } = await asInternalUser.search(params); + // @ts-expect-error TODO fix search response types if (body.error !== undefined && body.message !== undefined) { // No need to translate, this will not be exposed in the UI. throw new Error(`Annotations couldn't be retrieved from Elasticsearch.`); } + // @ts-expect-error TODO fix search response types const docs: Annotations = get(body, ['hits', 'hits'], []).map((d: EsResult) => { // get the original source document and the document id, we need it // to identify the annotation when editing/deleting it. diff --git a/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts index 2efc2f905d9bb9..1f5bbe8ac0fd49 100644 --- a/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts +++ b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import numeral from '@elastic/numeral'; import { IScopedClusterClient } from 'kibana/server'; import { MLCATEGORY } from '../../../common/constants/field_types'; @@ -89,12 +89,15 @@ const cardinalityCheckProvider = (client: IScopedClusterClient) => { new Set() ); - const maxBucketFieldCardinalities: string[] = influencers.filter( + const normalizedInfluencers: estypes.Field[] = Array.isArray(influencers) + ? influencers + : [influencers]; + const maxBucketFieldCardinalities = normalizedInfluencers.filter( (influencerField) => !!influencerField && !excludedKeywords.has(influencerField) && !overallCardinalityFields.has(influencerField) - ) as string[]; + ); if (overallCardinalityFields.size > 0) { overallCardinality = await fieldsService.getCardinalityOfFields( @@ -116,7 +119,7 @@ const cardinalityCheckProvider = (client: IScopedClusterClient) => { timeFieldName, earliestMs, latestMs, - bucketSpan, + bucketSpan as string, // update to Time type datafeedConfig ); } diff --git a/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts index 982485ab737aea..96bd74b9880a6f 100644 --- a/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts +++ b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts @@ -5,14 +5,17 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { difference } from 'lodash'; -import { EventManager, CalendarEvent } from './event_manager'; +import { EventManager } from './event_manager'; import type { MlClient } from '../../lib/ml_client'; +type ScheduledEvent = estypes.ScheduledEvent; + interface BasicCalendar { job_ids: string[]; description?: string; - events: CalendarEvent[]; + events: ScheduledEvent[]; } export interface Calendar extends BasicCalendar { @@ -37,23 +40,24 @@ export class CalendarManager { calendar_id: calendarId, }); - const calendars = body.calendars; + const calendars = body.calendars as Calendar[]; const calendar = calendars[0]; // Endpoint throws a 404 if calendar is not found. calendar.events = await this._eventManager.getCalendarEvents(calendarId); return calendar; } async getAllCalendars() { + // @ts-expect-error missing size argument const { body } = await this._mlClient.getCalendars({ size: 1000 }); - const events: CalendarEvent[] = await this._eventManager.getAllEvents(); - const calendars: Calendar[] = body.calendars; + const events: ScheduledEvent[] = await this._eventManager.getAllEvents(); + const calendars: Calendar[] = body.calendars as Calendar[]; calendars.forEach((cal) => (cal.events = [])); // loop events and combine with related calendars events.forEach((event) => { const calendar = calendars.find((cal) => cal.calendar_id === event.calendar_id); - if (calendar) { + if (calendar && calendar.events) { calendar.events.push(event); } }); @@ -98,7 +102,7 @@ export class CalendarManager { ); // if an event in the original calendar cannot be found, it must have been deleted - const eventsToRemove: CalendarEvent[] = origCalendar.events.filter( + const eventsToRemove: ScheduledEvent[] = origCalendar.events.filter( (event) => calendar.events.find((e) => this._eventManager.isEqual(e, event)) === undefined ); diff --git a/x-pack/plugins/ml/server/models/calendar/event_manager.ts b/x-pack/plugins/ml/server/models/calendar/event_manager.ts index 42ad3b99184c6e..c870d67524135d 100644 --- a/x-pack/plugins/ml/server/models/calendar/event_manager.ts +++ b/x-pack/plugins/ml/server/models/calendar/event_manager.ts @@ -5,16 +5,11 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { GLOBAL_CALENDAR } from '../../../common/constants/calendars'; import type { MlClient } from '../../lib/ml_client'; -export interface CalendarEvent { - calendar_id?: string; - event_id?: string; - description: string; - start_time: number; - end_time: number; -} +type ScheduledEvent = estypes.ScheduledEvent; export class EventManager { private _mlClient: MlClient; @@ -39,7 +34,7 @@ export class EventManager { return body.events; } - async addEvents(calendarId: string, events: CalendarEvent[]) { + async addEvents(calendarId: string, events: ScheduledEvent[]) { const body = { events }; return await this._mlClient.postCalendarEvents({ @@ -55,7 +50,7 @@ export class EventManager { }); } - isEqual(ev1: CalendarEvent, ev2: CalendarEvent) { + isEqual(ev1: ScheduledEvent, ev2: ScheduledEvent) { return ( ev1.event_id === ev2.event_id && ev1.description === ev2.description && diff --git a/x-pack/plugins/ml/server/models/calendar/index.ts b/x-pack/plugins/ml/server/models/calendar/index.ts index 26fb1bbe2c2359..c5177dd675ca1c 100644 --- a/x-pack/plugins/ml/server/models/calendar/index.ts +++ b/x-pack/plugins/ml/server/models/calendar/index.ts @@ -6,4 +6,3 @@ */ export { CalendarManager, Calendar, FormCalendar } from './calendar_manager'; -export { CalendarEvent } from './event_manager'; diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts index 0cbbf67dbbfac9..516823ff787582 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts @@ -79,9 +79,9 @@ export function analyticsAuditMessagesProvider({ asInternalUser }: IScopedCluste }, }); - let messages = []; - if (body.hits.total.value > 0) { - messages = body.hits.hits.map((hit: Message) => hit._source); + let messages: JobMessage[] = []; + if (typeof body.hits.total !== 'number' && body.hits.total.value > 0) { + messages = (body.hits.hits as Message[]).map((hit) => hit._source); messages.reverse(); } return messages; diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/validation.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/validation.ts index 371446638814a5..4c79855f39e894 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/validation.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/validation.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import type { estypes } from '@elastic/elasticsearch'; import { IScopedClusterClient } from 'kibana/server'; import { getAnalysisType } from '../../../common/util/analytics_utils'; import { @@ -197,7 +198,7 @@ async function getValidationCheckMessages( analyzedFields: string[], index: string | string[], analysisConfig: AnalysisConfig, - query: unknown = defaultQuery + query: estypes.QueryContainer = defaultQuery ) { const analysisType = getAnalysisType(analysisConfig); const depVar = getDependentVar(analysisConfig); @@ -241,9 +242,11 @@ async function getValidationCheckMessages( }, }); + // @ts-expect-error const totalDocs = body.hits.total.value; if (body.aggregations) { + // @ts-expect-error Object.entries(body.aggregations).forEach(([aggName, { doc_count: docCount, value }]) => { const empty = docCount / totalDocs; diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 4e99330610fca7..21ed258a0b764d 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -288,6 +288,7 @@ export class DataRecognizer { body: searchBody, }); + // @ts-expect-error fix search response return body.hits.total.value > 0; } @@ -864,10 +865,10 @@ export class DataRecognizer { try { const duration: { start: string; end?: string } = { start: '0' }; if (start !== undefined) { - duration.start = (start as unknown) as string; + duration.start = String(start); } if (end !== undefined) { - duration.end = (end as unknown) as string; + duration.end = String(end); } const { diff --git a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index 2a820e0629b757..679b7b3f12a23d 100644 --- a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -654,6 +654,7 @@ export class DataVisualizer { }); const aggregations = body.aggregations; + // @ts-expect-error fix search response const totalCount = body.hits.total.value; const stats = { totalCount, @@ -739,6 +740,7 @@ export class DataVisualizer { size, body: searchBody, }); + // @ts-expect-error fix search response return body.hits.total.value > 0; } @@ -1213,6 +1215,7 @@ export class DataVisualizer { fieldName: field, examples: [] as any[], }; + // @ts-expect-error fix search response if (body.hits.total.value > 0) { const hits = body.hits.hits; for (let i = 0; i < hits.length; i++) { diff --git a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts index 8e4dbaf23212fe..c2b95d9a585849 100644 --- a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts +++ b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts @@ -184,6 +184,7 @@ export function fieldsServiceProvider({ asCurrentUser }: IScopedClusterClient) { } = await asCurrentUser.search({ index, body, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options ...(datafeedConfig?.indices_options ?? {}), }); @@ -192,6 +193,7 @@ export function fieldsServiceProvider({ asCurrentUser }: IScopedClusterClient) { } const aggResult = fieldsToAgg.reduce((obj, field) => { + // @ts-expect-error fix search aggregation response obj[field] = (aggregations[field] || { value: 0 }).value; return obj; }, {} as { [field: string]: number }); @@ -247,10 +249,14 @@ export function fieldsServiceProvider({ asCurrentUser }: IScopedClusterClient) { }); if (aggregations && aggregations.earliest && aggregations.latest) { + // @ts-expect-error fix search aggregation response obj.start.epoch = aggregations.earliest.value; + // @ts-expect-error fix search aggregation response obj.start.string = aggregations.earliest.value_as_string; + // @ts-expect-error fix search aggregation response obj.end.epoch = aggregations.latest.value; + // @ts-expect-error fix search aggregation response obj.end.string = aggregations.latest.value_as_string; } return obj; @@ -400,6 +406,7 @@ export function fieldsServiceProvider({ asCurrentUser }: IScopedClusterClient) { } = await asCurrentUser.search({ index, body, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options ...(datafeedConfig?.indices_options ?? {}), }); @@ -408,6 +415,7 @@ export function fieldsServiceProvider({ asCurrentUser }: IScopedClusterClient) { } const aggResult = fieldsToAgg.reduce((obj, field) => { + // @ts-expect-error fix search aggregation response obj[field] = (aggregations[getMaxBucketAggKey(field)] || { value: 0 }).value ?? 0; return obj; }, {} as { [field: string]: number }); diff --git a/x-pack/plugins/ml/server/models/filter/filter_manager.ts b/x-pack/plugins/ml/server/models/filter/filter_manager.ts index c0c8b53e7aac67..2a250872607955 100644 --- a/x-pack/plugins/ml/server/models/filter/filter_manager.ts +++ b/x-pack/plugins/ml/server/models/filter/filter_manager.ts @@ -5,10 +5,12 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import Boom from '@hapi/boom'; import type { MlClient } from '../../lib/ml_client'; -import { DetectorRule, DetectorRuleScope } from '../../../common/types/detector_rules'; +// import { DetectorRule, DetectorRuleScope } from '../../../common/types/detector_rules'; +import { Job } from '../../../common/types/anomaly_detection_jobs'; export interface Filter { filter_id: string; @@ -46,17 +48,17 @@ interface FiltersInUse { [id: string]: FilterUsage; } -interface PartialDetector { - detector_description: string; - custom_rules: DetectorRule[]; -} +// interface PartialDetector { +// detector_description: string; +// custom_rules: DetectorRule[]; +// } -interface PartialJob { - job_id: string; - analysis_config: { - detectors: PartialDetector[]; - }; -} +// interface PartialJob { +// job_id: string; +// analysis_config: { +// detectors: PartialDetector[]; +// }; +// } export class FilterManager { constructor(private _mlClient: MlClient) {} @@ -69,15 +71,23 @@ export class FilterManager { this._mlClient.getFilters({ filter_id: filterId }), ]); - if (results[FILTERS] && results[FILTERS].body.filters.length) { + if ( + results[FILTERS] && + (results[FILTERS].body as estypes.GetFiltersResponse).filters.length + ) { let filtersInUse: FiltersInUse = {}; - if (results[JOBS] && results[JOBS].body.jobs) { - filtersInUse = this.buildFiltersInUse(results[JOBS].body.jobs); + if (results[JOBS] && (results[JOBS].body as estypes.GetJobsResponse).jobs) { + filtersInUse = this.buildFiltersInUse( + (results[JOBS].body as estypes.GetJobsResponse).jobs + ); } - const filter = results[FILTERS].body.filters[0]; - filter.used_by = filtersInUse[filter.filter_id]; - return filter; + const filter = (results[FILTERS].body as estypes.GetFiltersResponse).filters[0]; + return { + ...filter, + used_by: filtersInUse[filter.filter_id], + item_count: 0, + } as FilterStats; } else { throw Boom.notFound(`Filter with the id "${filterId}" not found`); } @@ -105,8 +115,8 @@ export class FilterManager { // Build a map of filter_ids against jobs and detectors using that filter. let filtersInUse: FiltersInUse = {}; - if (results[JOBS] && results[JOBS].body.jobs) { - filtersInUse = this.buildFiltersInUse(results[JOBS].body.jobs); + if (results[JOBS] && (results[JOBS].body as estypes.GetJobsResponse).jobs) { + filtersInUse = this.buildFiltersInUse((results[JOBS].body as estypes.GetJobsResponse).jobs); } // For each filter, return just @@ -115,8 +125,8 @@ export class FilterManager { // item_count // jobs using the filter const filterStats: FilterStats[] = []; - if (results[FILTERS] && results[FILTERS].body.filters) { - results[FILTERS].body.filters.forEach((filter: Filter) => { + if (results[FILTERS] && (results[FILTERS].body as estypes.GetFiltersResponse).filters) { + (results[FILTERS].body as estypes.GetFiltersResponse).filters.forEach((filter: Filter) => { const stats: FilterStats = { filter_id: filter.filter_id, description: filter.description, @@ -173,7 +183,7 @@ export class FilterManager { return body; } - buildFiltersInUse(jobsList: PartialJob[]) { + buildFiltersInUse(jobsList: Job[]) { // Build a map of filter_ids against jobs and detectors using that filter. const filtersInUse: FiltersInUse = {}; jobsList.forEach((job) => { @@ -183,7 +193,7 @@ export class FilterManager { const rules = detector.custom_rules; rules.forEach((rule) => { if (rule.scope) { - const ruleScope: DetectorRuleScope = rule.scope; + const ruleScope = rule.scope; const scopeFields = Object.keys(ruleScope); scopeFields.forEach((scopeField) => { const filter = ruleScope[scopeField]; diff --git a/x-pack/plugins/ml/server/models/job_service/datafeeds.ts b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts index cf9b2027225c9c..8279571adbae27 100644 --- a/x-pack/plugins/ml/server/models/job_service/datafeeds.ts +++ b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { i18n } from '@kbn/i18n'; import { IScopedClusterClient } from 'kibana/server'; import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states'; @@ -27,7 +28,8 @@ export interface MlDatafeedsStatsResponse { interface Results { [id: string]: { - started: boolean; + started?: estypes.StartDatafeedResponse['started']; + stopped?: estypes.StopDatafeedResponse['stopped']; error?: any; }; } @@ -105,8 +107,10 @@ export function datafeedsProvider(client: IScopedClusterClient, mlClient: MlClie async function startDatafeed(datafeedId: string, start?: number, end?: number) { return mlClient.startDatafeed({ datafeed_id: datafeedId, - start: (start as unknown) as string, - end: (end as unknown) as string, + body: { + start: start !== undefined ? String(start) : undefined, + end: end !== undefined ? String(end) : undefined, + }, }); } @@ -115,18 +119,16 @@ export function datafeedsProvider(client: IScopedClusterClient, mlClient: MlClie for (const datafeedId of datafeedIds) { try { - const { body } = await mlClient.stopDatafeed<{ - started: boolean; - }>({ + const { body } = await mlClient.stopDatafeed({ datafeed_id: datafeedId, }); - results[datafeedId] = body; + results[datafeedId] = { stopped: body.stopped }; } catch (error) { if (isRequestTimeout(error)) { return fillResultsWithTimeouts(results, datafeedId, datafeedIds, DATAFEED_STATE.STOPPED); } else { results[datafeedId] = { - started: false, + stopped: false, error: error.body, }; } @@ -175,9 +177,7 @@ export function datafeedsProvider(client: IScopedClusterClient, mlClient: MlClie // get all the datafeeds and match it with the jobId const { body: { datafeeds }, - } = await mlClient.getDatafeeds( - excludeGenerated ? { exclude_generated: true } : {} - ); + } = await mlClient.getDatafeeds(excludeGenerated ? { exclude_generated: true } : {}); // for (const result of datafeeds) { if (result.job_id === jobId) { return result; @@ -190,7 +190,7 @@ export function datafeedsProvider(client: IScopedClusterClient, mlClient: MlClie try { const { body: { datafeeds: datafeedsResults }, - } = await mlClient.getDatafeeds({ + } = await mlClient.getDatafeeds({ datafeed_id: assumedDefaultDatafeedId, ...(excludeGenerated ? { exclude_generated: true } : {}), }); @@ -220,6 +220,7 @@ export function datafeedsProvider(client: IScopedClusterClient, mlClient: MlClie job.data_description.time_field, query, datafeed.runtime_mappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options datafeed.indices_options ); @@ -351,6 +352,7 @@ export function datafeedsProvider(client: IScopedClusterClient, mlClient: MlClie const data = { index: datafeed.indices, body, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options ...(datafeed.indices_options ?? {}), }; diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts index ac3e00a918da81..d0d824a88f5a96 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.ts +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -143,7 +143,10 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) { throw Boom.notFound(`Cannot find datafeed for job ${jobId}`); } - const { body } = await mlClient.stopDatafeed({ datafeed_id: datafeedId, force: true }); + const { body } = await mlClient.stopDatafeed({ + datafeed_id: datafeedId, + body: { force: true }, + }); if (body.stopped !== true) { return { success: false }; } @@ -316,6 +319,7 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) { (ds) => ds.datafeed_id === datafeed.datafeed_id ); if (datafeedStats) { + // @ts-expect-error datafeeds[datafeed.job_id] = { ...datafeed, ...datafeedStats }; } } @@ -384,6 +388,7 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) { if (jobStatsResults && jobStatsResults.jobs) { const jobStats = jobStatsResults.jobs.find((js) => js.job_id === tempJob.job_id); if (jobStats !== undefined) { + // @ts-expect-error tempJob = { ...tempJob, ...jobStats }; if (jobStats.node) { tempJob.node = jobStats.node; @@ -417,13 +422,20 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) { const detailed = true; const jobIds: string[] = []; try { - const { body } = await asInternalUser.tasks.list({ actions, detailed }); - Object.keys(body.nodes).forEach((nodeId) => { - const tasks = body.nodes[nodeId].tasks; - Object.keys(tasks).forEach((taskId) => { - jobIds.push(tasks[taskId].description.replace(/^delete-job-/, '')); - }); + const { body } = await asInternalUser.tasks.list({ + // @ts-expect-error @elastic-elasticsearch expects it to be a string + actions, + detailed, }); + + if (body.nodes) { + Object.keys(body.nodes).forEach((nodeId) => { + const tasks = body.nodes![nodeId].tasks; + Object.keys(tasks).forEach((taskId) => { + jobIds.push(tasks[taskId].description!.replace(/^delete-job-/, '')); + }); + }); + } } catch (e) { // if the user doesn't have permission to load the task list, // use the jobs list to get the ids of deleting jobs diff --git a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts index f1f5d98b96a535..425dff89032a3f 100644 --- a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts +++ b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts @@ -73,8 +73,9 @@ export function modelSnapshotProvider(client: IScopedClusterClient, mlClient: Ml if (replay && model.snapshot_id === snapshotId && snapshot.model_snapshots.length) { // create calendar before starting restarting the datafeed if (calendarEvents !== undefined && calendarEvents.length) { + const calendarId = String(Date.now()); const calendar: FormCalendar = { - calendarId: String(Date.now()), + calendarId, job_ids: [jobId], description: i18n.translate( 'xpack.ml.models.jobService.revertModelSnapshot.autoCreatedCalendar.description', @@ -83,16 +84,18 @@ export function modelSnapshotProvider(client: IScopedClusterClient, mlClient: Ml } ), events: calendarEvents.map((s) => ({ + calendar_id: calendarId, + event_id: '', description: s.description, - start_time: s.start, - end_time: s.end, + start_time: `${s.start}`, + end_time: `${s.end}`, })), }; const cm = new CalendarManager(mlClient); await cm.newCalendar(calendar); } - forceStartDatafeeds([datafeedId], snapshot.model_snapshots[0].latest_record_time_stamp, end); + forceStartDatafeeds([datafeedId], +snapshot.model_snapshots[0].latest_record_time_stamp, end); } return { success: true }; diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts index 37fa6753627732..b0ee20763f4305 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts @@ -127,7 +127,7 @@ export function categorizationExamplesProvider({ async function loadTokens(examples: string[], analyzer: CategorizationAnalyzer) { const { body: { tokens }, - } = await asInternalUser.indices.analyze<{ tokens: Token[] }>({ + } = await asInternalUser.indices.analyze({ body: { ...getAnalyzer(analyzer), text: examples, @@ -139,19 +139,21 @@ export function categorizationExamplesProvider({ const tokensPerExample: Token[][] = examples.map((e) => []); - tokens.forEach((t, i) => { - for (let g = 0; g < sumLengths.length; g++) { - if (t.start_offset <= sumLengths[g] + g) { - const offset = g > 0 ? sumLengths[g - 1] + g : 0; - tokensPerExample[g].push({ - ...t, - start_offset: t.start_offset - offset, - end_offset: t.end_offset - offset, - }); - break; + if (tokens !== undefined) { + tokens.forEach((t, i) => { + for (let g = 0; g < sumLengths.length; g++) { + if (t.start_offset <= sumLengths[g] + g) { + const offset = g > 0 ? sumLengths[g - 1] + g : 0; + tokensPerExample[g].push({ + ...t, + start_offset: t.start_offset - offset, + end_offset: t.end_offset - offset, + }); + break; + } } - } - }); + }); + } return tokensPerExample; } diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts index 6637faeba094de..851336056a7f51 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts @@ -81,6 +81,7 @@ export function topCategoriesProvider(mlClient: MlClient) { const catCounts: Array<{ id: CategoryId; count: number; + // @ts-expect-error }> = body.aggregations?.cat_count?.buckets.map((c: any) => ({ id: c.key, count: c.doc_count, @@ -125,6 +126,7 @@ export function topCategoriesProvider(mlClient: MlClient) { [] ); + // @ts-expect-error return body.hits.hits?.map((c: { _source: Category }) => c._source) || []; } diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts index 7ce54cd2f9c5e2..0287c2af11a7e7 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts @@ -5,13 +5,14 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { IScopedClusterClient } from 'kibana/server'; import { cloneDeep } from 'lodash'; import { SavedObjectsClientContract } from 'kibana/server'; import { Field, FieldId, NewJobCaps, RollupFields } from '../../../../common/types/fields'; import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/common'; import { combineFieldsAndAggs } from '../../../../common/util/fields_utils'; -import { rollupServiceProvider, RollupJob } from './rollup'; +import { rollupServiceProvider } from './rollup'; import { aggregations, mlOnlyAggregations } from '../../../../common/constants/aggregation_types'; const supportedTypes: string[] = [ @@ -109,7 +110,9 @@ class FieldsService { this._mlClusterClient, this._savedObjectsClient ); - const rollupConfigs: RollupJob[] | null = await rollupService.getRollupJobs(); + const rollupConfigs: + | estypes.RollupCapabilitiesJob[] + | null = await rollupService.getRollupJobs(); // if a rollup index has been specified, yet there are no // rollup configs, return with no results @@ -131,14 +134,16 @@ class FieldsService { } } -function combineAllRollupFields(rollupConfigs: RollupJob[]): RollupFields { +function combineAllRollupFields(rollupConfigs: estypes.RollupCapabilitiesJob[]): RollupFields { const rollupFields: RollupFields = {}; rollupConfigs.forEach((conf) => { Object.keys(conf.fields).forEach((fieldName) => { if (rollupFields[fieldName] === undefined) { + // @ts-expect-error fix type. our RollupFields type is better rollupFields[fieldName] = conf.fields[fieldName]; } else { const aggs = conf.fields[fieldName]; + // @ts-expect-error fix type. our RollupFields type is better aggs.forEach((agg) => { if (rollupFields[fieldName].find((f) => f.agg === agg.agg) === null) { rollupFields[fieldName].push(agg); diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts index 3b480bae2199eb..d83f7afb4cdf6c 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { IScopedClusterClient } from 'kibana/server'; import { SavedObject } from 'kibana/server'; import { IndexPatternAttributes } from 'src/plugins/data/server'; @@ -26,7 +27,7 @@ export async function rollupServiceProvider( const rollupIndexPatternObject = await loadRollupIndexPattern(indexPattern, savedObjectsClient); let jobIndexPatterns: string[] = [indexPattern]; - async function getRollupJobs(): Promise { + async function getRollupJobs(): Promise { if (rollupIndexPatternObject !== null) { const parsedTypeMetaData = JSON.parse(rollupIndexPatternObject.attributes.typeMeta); const rollUpIndex: string = parsedTypeMetaData.params.rollup_index; @@ -36,7 +37,7 @@ export async function rollupServiceProvider( const indexRollupCaps = rollupCaps[rollUpIndex]; if (indexRollupCaps && indexRollupCaps.rollup_jobs) { - jobIndexPatterns = indexRollupCaps.rollup_jobs.map((j: RollupJob) => j.index_pattern); + jobIndexPatterns = indexRollupCaps.rollup_jobs.map((j) => j.index_pattern); return indexRollupCaps.rollup_jobs; } diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts index 2beade7f5dbc46..94e9a8dc7bffbb 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts @@ -69,6 +69,7 @@ export async function validateJob( timeField, job.datafeed_config.query, job.datafeed_config.runtime_mappings, + // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options job.datafeed_config.indices_options ); diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts index 853f96ad777434..44c5e3cabb18fa 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts @@ -148,8 +148,7 @@ describe('ML - validateModelMemoryLimit', () => { it('Called with no duration or split and mml above limit', () => { const job = getJobConfig(); const duration = undefined; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '31mb'; + job.analysis_limits!.model_memory_limit = '31mb'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -166,8 +165,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(10); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '20mb'; + job.analysis_limits!.model_memory_limit = '20mb'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -184,8 +182,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(2); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '30mb'; + job.analysis_limits!.model_memory_limit = '30mb'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -202,8 +199,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(2); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '10mb'; + job.analysis_limits!.model_memory_limit = '10mb'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -222,8 +218,7 @@ describe('ML - validateModelMemoryLimit', () => { const duration = { start: 0, end: 1 }; // @ts-expect-error delete mlInfoResponse.limits.max_model_memory_limit; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '10mb'; + job.analysis_limits!.model_memory_limit = '10mb'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -239,8 +234,7 @@ describe('ML - validateModelMemoryLimit', () => { it('Called with no duration or split and mml above limit, no max setting', () => { const job = getJobConfig(); const duration = undefined; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '31mb'; + job.analysis_limits!.model_memory_limit = '31mb'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -256,8 +250,7 @@ describe('ML - validateModelMemoryLimit', () => { it('Called with no duration or split and mml above limit, no max setting, above effective max mml', () => { const job = getJobConfig(); const duration = undefined; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '41mb'; + job.analysis_limits!.model_memory_limit = '41mb'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -274,8 +267,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(1); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '20mb'; + job.analysis_limits!.model_memory_limit = '20mb'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -292,8 +284,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(1); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '0mb'; + job.analysis_limits!.model_memory_limit = '0mb'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -310,8 +301,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(1); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '10mbananas'; + job.analysis_limits!.model_memory_limit = '10mbananas'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -328,8 +318,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(1); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '10'; + job.analysis_limits!.model_memory_limit = '10'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -346,8 +335,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(1); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = 'mb'; + job.analysis_limits!.model_memory_limit = 'mb'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -364,8 +352,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(1); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = 'asdf'; + job.analysis_limits!.model_memory_limit = 'asdf'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -382,8 +369,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(1); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '1023KB'; + job.analysis_limits!.model_memory_limit = '1023KB'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -400,8 +386,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(1); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '1024KB'; + job.analysis_limits!.model_memory_limit = '1024KB'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -418,8 +403,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(1); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '6MB'; + job.analysis_limits!.model_memory_limit = '6MB'; return validateModelMemoryLimit( getMockMlClusterClient(), @@ -436,8 +420,7 @@ describe('ML - validateModelMemoryLimit', () => { const dtrs = createDetectors(1); const job = getJobConfig(['instance'], dtrs); const duration = { start: 0, end: 1 }; - // @ts-expect-error - job.analysis_limits.model_memory_limit = '20MB'; + job.analysis_limits!.model_memory_limit = '20MB'; return validateModelMemoryLimit( getMockMlClusterClient(), diff --git a/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts index 81e26808bd2461..be1786d64f2aa6 100644 --- a/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts +++ b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts @@ -10,7 +10,6 @@ import { PARTITION_FIELDS } from '../../../common/constants/anomalies'; import { PartitionFieldsType } from '../../../common/types/anomalies'; import { CriteriaField } from './results_service'; import { FieldConfig, FieldsConfig } from '../../routes/schemas/results_service_schema'; -import { Job } from '../../../common/types/anomaly_detection_jobs'; import type { MlClient } from '../../lib/ml_client'; type SearchTerm = @@ -151,7 +150,7 @@ export const getPartitionFieldsValuesFactory = (mlClient: MlClient) => throw Boom.notFound(`Job with the id "${jobId}" not found`); } - const job: Job = jobsResponse.jobs[0]; + const job = jobsResponse.jobs[0]; const isModelPlotEnabled = job?.model_plot_config?.enabled; const isAnomalousOnly = (Object.entries(fieldsConfig) as Array<[string, FieldConfig]>).some( diff --git a/x-pack/plugins/ml/server/models/results_service/results_service.ts b/x-pack/plugins/ml/server/models/results_service/results_service.ts index 3d1db66cc24cc1..1996acd2cdb066 100644 --- a/x-pack/plugins/ml/server/models/results_service/results_service.ts +++ b/x-pack/plugins/ml/server/models/results_service/results_service.ts @@ -183,6 +183,7 @@ export function resultsServiceProvider(mlClient: MlClient) { anomalies: [], interval: 'second', }; + // @ts-expect-error update to correct search response if (body.hits.total.value > 0) { let records: AnomalyRecordDoc[] = []; body.hits.hits.forEach((hit: any) => { @@ -401,6 +402,7 @@ export function resultsServiceProvider(mlClient: MlClient) { ); const examplesByCategoryId: { [key: string]: any } = {}; + // @ts-expect-error update to correct search response if (body.hits.total.value > 0) { body.hits.hits.forEach((hit: any) => { if (maxExamples) { @@ -437,6 +439,7 @@ export function resultsServiceProvider(mlClient: MlClient) { ); const definition = { categoryId, terms: null, regex: null, examples: [] }; + // @ts-expect-error update to correct search response if (body.hits.total.value > 0) { const source = body.hits.hits[0]._source; definition.categoryId = source.category_id; @@ -576,6 +579,7 @@ export function resultsServiceProvider(mlClient: MlClient) { ); if (fieldToBucket === JOB_ID) { finalResults = { + // @ts-expect-error update search response jobs: results.aggregations?.unique_terms?.buckets.map( (b: { key: string; doc_count: number }) => b.key ), @@ -588,6 +592,7 @@ export function resultsServiceProvider(mlClient: MlClient) { }, {} ); + // @ts-expect-error update search response results.aggregations.jobs.buckets.forEach( (bucket: { key: string | number; unique_stopped_partitions: { buckets: any[] } }) => { jobs[bucket.key] = bucket.unique_stopped_partitions.buckets.map((b) => b.key); diff --git a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts index aa7adbe2db6605..ed583bd9e3eb18 100644 --- a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts @@ -5,8 +5,8 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { schema } from '@kbn/config-schema'; -import { RequestParams } from '@elastic/elasticsearch'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { @@ -23,8 +23,6 @@ import { updateModelSnapshotSchema, } from './schemas/anomaly_detectors_schema'; -import { Job, JobStats } from '../../common/types/anomaly_detection_jobs'; - /** * Routes for the anomaly detectors */ @@ -49,7 +47,7 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { }, routeGuard.fullLicenseAPIGuard(async ({ mlClient, response }) => { try { - const { body } = await mlClient.getJobs<{ jobs: Job[] }>(); + const { body } = await mlClient.getJobs(); return response.ok({ body, }); @@ -81,7 +79,7 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response }) => { try { const { jobId } = request.params; - const { body } = await mlClient.getJobs<{ jobs: Job[] }>({ job_id: jobId }); + const { body } = await mlClient.getJobs({ job_id: jobId }); return response.ok({ body, }); @@ -111,7 +109,7 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { }, routeGuard.fullLicenseAPIGuard(async ({ mlClient, response }) => { try { - const { body } = await mlClient.getJobStats<{ jobs: JobStats[] }>(); + const { body } = await mlClient.getJobStats(); return response.ok({ body, }); @@ -283,7 +281,7 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { }, routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response }) => { try { - const options: RequestParams.MlCloseJob = { + const options: estypes.CloseJobRequest = { job_id: request.params.jobId, }; const force = request.query.force; @@ -321,7 +319,7 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { }, routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response }) => { try { - const options: RequestParams.MlDeleteJob = { + const options: estypes.DeleteJobRequest = { job_id: request.params.jobId, wait_for_completion: false, }; @@ -395,7 +393,9 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { const duration = request.body.duration; const { body } = await mlClient.forecast({ job_id: jobId, - duration, + body: { + duration, + }, }); return response.ok({ body, @@ -513,10 +513,12 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { try { const { body } = await mlClient.getOverallBuckets({ job_id: request.params.jobId, - top_n: request.body.topN, - bucket_span: request.body.bucketSpan, - start: request.body.start, - end: request.body.end, + body: { + top_n: request.body.topN, + bucket_span: request.body.bucketSpan, + start: request.body.start !== undefined ? String(request.body.start) : undefined, + end: request.body.end !== undefined ? String(request.body.end) : undefined, + }, }); return response.ok({ body, diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index 448d798845e188..520f8ce6fb0a98 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -28,7 +28,6 @@ import { AnalyticsManager } from '../models/data_frame_analytics/analytics_manag import { validateAnalyticsJob } from '../models/data_frame_analytics/validation'; import { DeleteDataFrameAnalyticsWithIndexStatus } from '../../common/types/data_frame_analytics'; import { getAuthorizationHeader } from '../lib/request_authorization'; -import { DataFrameAnalyticsConfig } from '../../common/types/data_frame_analytics'; import type { MlClient } from '../lib/ml_client'; function getIndexPatternId(context: RequestHandlerContext, patternName: string) { @@ -603,14 +602,10 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense, routeGuard }: Rout for (const id of analyticsIds) { try { const { body } = allSpaces - ? await client.asInternalUser.ml.getDataFrameAnalytics<{ - data_frame_analytics: DataFrameAnalyticsConfig[]; - }>({ + ? await client.asInternalUser.ml.getDataFrameAnalytics({ id, }) - : await mlClient.getDataFrameAnalytics<{ - data_frame_analytics: DataFrameAnalyticsConfig[]; - }>({ + : await mlClient.getDataFrameAnalytics({ id, }); results[id] = body.data_frame_analytics.length > 0; diff --git a/x-pack/plugins/ml/server/routes/datafeeds.ts b/x-pack/plugins/ml/server/routes/datafeeds.ts index 90d90a0e2b1e4d..2013af3ee87352 100644 --- a/x-pack/plugins/ml/server/routes/datafeeds.ts +++ b/x-pack/plugins/ml/server/routes/datafeeds.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { RequestParams } from '@elastic/elasticsearch'; +import { estypes } from '@elastic/elasticsearch'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { @@ -16,8 +16,6 @@ import { } from './schemas/datafeeds_schema'; import { getAuthorizationHeader } from '../lib/request_authorization'; -import { Datafeed, DatafeedStats } from '../../common/types/anomaly_detection_jobs'; - /** * Routes for datafeed service */ @@ -39,7 +37,7 @@ export function dataFeedRoutes({ router, routeGuard }: RouteInitialization) { }, routeGuard.fullLicenseAPIGuard(async ({ mlClient, response }) => { try { - const { body } = await mlClient.getDatafeeds<{ datafeeds: Datafeed[] }>(); + const { body } = await mlClient.getDatafeeds(); return response.ok({ body, }); @@ -99,9 +97,7 @@ export function dataFeedRoutes({ router, routeGuard }: RouteInitialization) { }, routeGuard.fullLicenseAPIGuard(async ({ mlClient, response }) => { try { - const { body } = await mlClient.getDatafeedStats<{ - datafeeds: DatafeedStats[]; - }>(); + const { body } = await mlClient.getDatafeedStats(); return response.ok({ body, }); @@ -251,7 +247,7 @@ export function dataFeedRoutes({ router, routeGuard }: RouteInitialization) { }, routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response }) => { try { - const options: RequestParams.MlDeleteDatafeed = { + const options: estypes.DeleteDatafeedRequest = { datafeed_id: request.params.datafeedId, }; const force = request.query.force; @@ -298,8 +294,10 @@ export function dataFeedRoutes({ router, routeGuard }: RouteInitialization) { const { body } = await mlClient.startDatafeed({ datafeed_id: datafeedId, - start, - end, + body: { + start: start !== undefined ? String(start) : undefined, + end: end !== undefined ? String(end) : undefined, + }, }); return response.ok({ diff --git a/x-pack/plugins/ml/server/routes/trained_models.ts b/x-pack/plugins/ml/server/routes/trained_models.ts index bfa7137b3e6d6b..dbfc2195a12e1e 100644 --- a/x-pack/plugins/ml/server/routes/trained_models.ts +++ b/x-pack/plugins/ml/server/routes/trained_models.ts @@ -13,7 +13,6 @@ import { optionalModelIdSchema, } from './schemas/inference_schema'; import { modelsProvider } from '../models/data_frame_analytics'; -import { InferenceConfigResponse } from '../../common/types/trained_models'; export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) { /** @@ -38,7 +37,7 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) try { const { modelId } = request.params; const { with_pipelines: withPipelines, ...query } = request.query; - const { body } = await mlClient.getTrainedModels({ + const { body } = await mlClient.getTrainedModels({ size: 1000, ...query, ...(modelId ? { model_id: modelId } : {}), @@ -85,7 +84,7 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) tags: ['access:ml:canGetDataFrameAnalytics'], }, }, - routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response }) => { + routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response }) => { try { const { modelId } = request.params; const { body } = await mlClient.getTrainedModelsStats({ diff --git a/x-pack/plugins/ml/server/saved_objects/checks.ts b/x-pack/plugins/ml/server/saved_objects/checks.ts index 04d5e53a5568ba..6b24ef000b6951 100644 --- a/x-pack/plugins/ml/server/saved_objects/checks.ts +++ b/x-pack/plugins/ml/server/saved_objects/checks.ts @@ -10,8 +10,6 @@ import { IScopedClusterClient, KibanaRequest } from 'kibana/server'; import type { JobSavedObjectService } from './service'; import { JobType, DeleteJobCheckResponse } from '../../common/types/saved_objects'; -import { Job } from '../../common/types/anomaly_detection_jobs'; -import { Datafeed } from '../../common/types/anomaly_detection_jobs'; import { DataFrameAnalyticsConfig } from '../../common/types/data_frame_analytics'; import { ResolveMlCapabilities } from '../../common/types/capabilities'; @@ -51,13 +49,13 @@ export function checksFactory( const jobObjects = await jobSavedObjectService.getAllJobObjects(undefined, false); // load all non-space jobs and datafeeds - const { body: adJobs } = await client.asInternalUser.ml.getJobs<{ jobs: Job[] }>(); - const { body: datafeeds } = await client.asInternalUser.ml.getDatafeeds<{ - datafeeds: Datafeed[]; - }>(); - const { body: dfaJobs } = await client.asInternalUser.ml.getDataFrameAnalytics<{ - data_frame_analytics: DataFrameAnalyticsConfig[]; - }>(); + const { body: adJobs } = await client.asInternalUser.ml.getJobs(); + const { body: datafeeds } = await client.asInternalUser.ml.getDatafeeds(); + const { + body: dfaJobs, + } = ((await client.asInternalUser.ml.getDataFrameAnalytics()) as unknown) as { + body: { data_frame_analytics: DataFrameAnalyticsConfig[] }; + }; const savedObjectsStatus: JobSavedObjectStatus[] = jobObjects.map( ({ attributes, namespaces }) => { diff --git a/x-pack/plugins/ml/server/shared_services/providers/system.ts b/x-pack/plugins/ml/server/shared_services/providers/system.ts index 021bf5b12625ba..1e3dcd7de52408 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/system.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/system.ts @@ -7,7 +7,6 @@ import { KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; -import { RequestParams } from '@elastic/elasticsearch'; import { MlLicense } from '../../../common/license'; import { CloudSetup } from '../../../../cloud/server'; import { spacesUtilsProvider } from '../../lib/spaces_utils'; @@ -24,10 +23,7 @@ export interface MlSystemProvider { ): { mlCapabilities(): Promise; mlInfo(): Promise; - mlAnomalySearch( - searchParams: RequestParams.Search, - jobIds: string[] - ): Promise>; + mlAnomalySearch(searchParams: any, jobIds: string[]): Promise>; }; } @@ -73,10 +69,7 @@ export function getMlSystemProvider( }; }); }, - async mlAnomalySearch( - searchParams: RequestParams.Search, - jobIds: string[] - ): Promise> { + async mlAnomalySearch(searchParams: any, jobIds: string[]): Promise> { return await getGuards(request, savedObjectsClient) .isFullLicense() .hasMlCapabilities(['canAccessML']) diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_available_ccs.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_available_ccs.test.ts index ecfb5fc50a16d6..603c66d2d05f2b 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_available_ccs.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_available_ccs.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { elasticsearchServiceMock } from 'src/core/server/mocks'; @@ -25,7 +26,7 @@ describe('fetchAvailableCcs', () => { elasticsearchClientMock.createSuccessTransportRequestPromise({ [connectedRemote]: { connected: true, - }, + } as estypes.RemoteInfo, }) ); @@ -40,7 +41,7 @@ describe('fetchAvailableCcs', () => { elasticsearchClientMock.createSuccessTransportRequestPromise({ [disconnectedRemote]: { connected: false, - }, + } as estypes.RemoteInfo, }) ); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts index 330be4e90ed567..fb67cd18059501 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts @@ -69,8 +69,8 @@ export async function fetchCCRReadExceptions( sort: [ { timestamp: { - order: 'desc', - unmapped_type: 'long', + order: 'desc' as const, + unmapped_type: 'long' as const, }, }, ], @@ -95,6 +95,7 @@ export async function fetchCCRReadExceptions( const { body: response } = await esClient.search(params); const stats: CCRReadExceptionsStats[] = []; + // @ts-expect-error @elastic/elasticsearch Aggregate does not specify buckets const { buckets: remoteClusterBuckets = [] } = response.aggregations.remote_clusters; if (!remoteClusterBuckets.length) { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.test.ts index d326c7f4bedda5..08ecaef33085bf 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from '../../../../../../src/core/server/elasticsearch/client/mocks'; import { fetchClusterHealth } from './fetch_cluster_health'; @@ -29,7 +30,7 @@ describe('fetchClusterHealth', () => { }, ], }, - }) + } as estypes.SearchResponse) ); const clusters = [{ clusterUuid, clusterName: 'foo' }]; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts index be91aaa6ec983e..ddbf4e3d4b3c1b 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts @@ -25,8 +25,8 @@ export async function fetchClusterHealth( sort: [ { timestamp: { - order: 'desc', - unmapped_type: 'long', + order: 'desc' as const, + unmapped_type: 'long' as const, }, }, ], @@ -59,11 +59,11 @@ export async function fetchClusterHealth( }, }; - const { body: response } = await esClient.search(params); - return response.hits.hits.map((hit: { _source: ElasticsearchSource; _index: string }) => { + const { body: response } = await esClient.search(params); + return response.hits.hits.map((hit) => { return { - health: hit._source.cluster_state?.status, - clusterUuid: hit._source.cluster_uuid, + health: hit._source!.cluster_state?.status, + clusterUuid: hit._source!.cluster_uuid, ccs: hit._index.includes(':') ? hit._index.split(':')[0] : undefined, } as AlertClusterHealth; }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts index 54aa2e68d4ef25..75991e892d419f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { elasticsearchServiceMock } from 'src/core/server/mocks'; @@ -28,7 +29,7 @@ describe('fetchClusters', () => { }, ], }, - }) + } as estypes.SearchResponse) ); const index = '.monitoring-es-*'; const result = await fetchClusters(esClient, index); @@ -57,7 +58,7 @@ describe('fetchClusters', () => { }, ], }, - }) + } as estypes.SearchResponse) ); const index = '.monitoring-es-*'; const result = await fetchClusters(esClient, index); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts index 2ff9ae3854e4ad..0fb9dd5298e9e1 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from '../../../../../../src/core/server/elasticsearch/client/mocks'; import { fetchCpuUsageNodeStats } from './fetch_cpu_usage_node_stats'; @@ -24,6 +25,7 @@ describe('fetchCpuUsageNodeStats', () => { it('fetch normal stats', async () => { esClient.search.mockReturnValue( + // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { @@ -77,6 +79,7 @@ describe('fetchCpuUsageNodeStats', () => { it('fetch container stats', async () => { esClient.search.mockReturnValue( + // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { @@ -143,6 +146,7 @@ describe('fetchCpuUsageNodeStats', () => { it('fetch properly return ccs', async () => { esClient.search.mockReturnValue( + // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { @@ -193,7 +197,9 @@ describe('fetchCpuUsageNodeStats', () => { let params = null; esClient.search.mockImplementation((...args) => { params = args[0]; - return elasticsearchClientMock.createSuccessTransportRequestPromise({}); + return elasticsearchClientMock.createSuccessTransportRequestPromise( + {} as estypes.SearchResponse + ); }); await fetchCpuUsageNodeStats(esClient, clusters, index, startMs, endMs, size); expect(params).toStrictEqual({ diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts index 1dfbe381b99568..07ca3572ad6b3f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts @@ -120,14 +120,14 @@ export async function fetchCpuUsageNodeStats( usage_deriv: { derivative: { buckets_path: 'average_usage', - gap_policy: 'skip', + gap_policy: 'skip' as const, unit: NORMALIZED_DERIVATIVE_UNIT, }, }, periods_deriv: { derivative: { buckets_path: 'average_periods', - gap_policy: 'skip', + gap_policy: 'skip' as const, unit: NORMALIZED_DERIVATIVE_UNIT, }, }, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts index 7664d73f6009b0..8faf79fc4b59c6 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts @@ -25,6 +25,7 @@ describe('fetchDiskUsageNodeStats', () => { it('fetch normal stats', async () => { esClient.search.mockReturnValue( + // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts index aea4ede825d672..30daee225fcb4c 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts @@ -101,7 +101,8 @@ export async function fetchDiskUsageNodeStats( const { body: response } = await esClient.search(params); const stats: AlertDiskUsageNodeStats[] = []; - const { buckets: clusterBuckets = [] } = response.aggregations.clusters; + // @ts-expect-error @elastic/elasticsearch Aggregate does not define buckets + const { buckets: clusterBuckets = [] } = response.aggregations!.clusters; if (!clusterBuckets.length) { return stats; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.test.ts index be501ee3d52801..d1051748536364 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.test.ts @@ -9,6 +9,7 @@ import { elasticsearchClientMock } from '../../../../../../src/core/server/elasticsearch/client/mocks'; import { elasticsearchServiceMock } from 'src/core/server/mocks'; import { fetchElasticsearchVersions } from './fetch_elasticsearch_versions'; +import { estypes } from '@elastic/elasticsearch'; describe('fetchElasticsearchVersions', () => { const esClient = elasticsearchServiceMock.createScopedClusterClient().asCurrentUser; @@ -41,7 +42,7 @@ describe('fetchElasticsearchVersions', () => { }, ], }, - }) + } as estypes.SearchResponse) ); const result = await fetchElasticsearchVersions(esClient, clusters, index, size); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts index b4b7739f6731b7..111ef5b0c120d4 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts @@ -26,8 +26,8 @@ export async function fetchElasticsearchVersions( sort: [ { timestamp: { - order: 'desc', - unmapped_type: 'long', + order: 'desc' as const, + unmapped_type: 'long' as const, }, }, ], @@ -60,13 +60,13 @@ export async function fetchElasticsearchVersions( }, }; - const { body: response } = await esClient.search(params); - return response.hits.hits.map((hit: { _source: ElasticsearchSource; _index: string }) => { - const versions = hit._source.cluster_stats?.nodes?.versions; + const { body: response } = await esClient.search(params); + return response.hits.hits.map((hit) => { + const versions = hit._source!.cluster_stats?.nodes?.versions ?? []; return { versions, - clusterUuid: hit._source.cluster_uuid, - ccs: hit._index.includes(':') ? hit._index.split(':')[0] : null, + clusterUuid: hit._source!.cluster_uuid, + ccs: hit._index.includes(':') ? hit._index.split(':')[0] : undefined, }; }); } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts index dfba0c42eef3dc..f51e1cde47f8d0 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts @@ -88,8 +88,8 @@ export async function fetchIndexShardSize( sort: [ { timestamp: { - order: 'desc', - unmapped_type: 'long', + order: 'desc' as const, + unmapped_type: 'long' as const, }, }, ], @@ -116,6 +116,7 @@ export async function fetchIndexShardSize( const { body: response } = await esClient.search(params); const stats: IndexShardSizeStats[] = []; + // @ts-expect-error @elastic/elasticsearch Aggregate does not specify buckets const { buckets: clusterBuckets = [] } = response.aggregations.clusters; const validIndexPatterns = memoizedIndexPatterns(shardIndexPatterns); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts index 901851d7665127..2b966b16f2f5c6 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts @@ -23,6 +23,7 @@ describe('fetchKibanaVersions', () => { it('fetch as expected', async () => { esClient.search.mockReturnValue( + // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { index: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts index a4e1e606702ecd..cb2f201e2586ec 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts @@ -70,7 +70,7 @@ export async function fetchKibanaVersions( field: 'kibana_stats.kibana.version', size: 1, order: { - latest_report: 'desc', + latest_report: 'desc' as const, }, }, aggs: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts index 69a42812bfe885..3c12c70bf17133 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts @@ -8,6 +8,7 @@ import { fetchLicenses } from './fetch_licenses'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from '../../../../../../src/core/server/elasticsearch/client/mocks'; import { elasticsearchServiceMock } from 'src/core/server/mocks'; +import { estypes } from '@elastic/elasticsearch'; describe('fetchLicenses', () => { const clusterName = 'MyCluster'; @@ -32,7 +33,7 @@ describe('fetchLicenses', () => { }, ], }, - }) + } as estypes.SearchResponse) ); const clusters = [{ clusterUuid, clusterName }]; const index = '.monitoring-es-*'; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts index 5cd4378f0a7470..5178b6c4c53a71 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts @@ -6,7 +6,7 @@ */ import { ElasticsearchClient } from 'kibana/server'; import { AlertLicense, AlertCluster } from '../../../common/types/alerts'; -import { ElasticsearchResponse } from '../../../common/types/es'; +import { ElasticsearchSource } from '../../../common/types/es'; export async function fetchLicenses( esClient: ElasticsearchClient, @@ -25,8 +25,8 @@ export async function fetchLicenses( sort: [ { timestamp: { - order: 'desc', - unmapped_type: 'long', + order: 'desc' as const, + unmapped_type: 'long' as const, }, }, ], @@ -59,15 +59,15 @@ export async function fetchLicenses( }, }; - const { body: response } = await esClient.search(params); + const { body: response } = await esClient.search(params); return ( response?.hits?.hits.map((hit) => { - const rawLicense = hit._source.license ?? {}; + const rawLicense = hit._source!.license ?? {}; const license: AlertLicense = { status: rawLicense.status ?? '', type: rawLicense.type ?? '', expiryDateMS: rawLicense.expiry_date_in_millis ?? 0, - clusterUuid: hit._source.cluster_uuid, + clusterUuid: hit._source!.cluster_uuid, ccs: hit._index, }; return license; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts index e35de6e68866d1..d7d4e6531f58e2 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts @@ -23,6 +23,7 @@ describe('fetchLogstashVersions', () => { it('fetch as expected', async () => { esClient.search.mockReturnValue( + // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { index: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts index 6090ba36d97490..6fb54857d40e43 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts @@ -70,7 +70,7 @@ export async function fetchLogstashVersions( field: 'logstash_stats.logstash.version', size: 1, order: { - latest_report: 'desc', + latest_report: 'desc' as const, }, }, aggs: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts index 77c17a8ebf3eff..aad4638bf83599 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts @@ -94,6 +94,7 @@ export async function fetchMemoryUsageNodeStats( const { body: response } = await esClient.search(params); const stats: AlertMemoryUsageNodeStats[] = []; + // @ts-expect-error @elastic/elasticsearch Aggregate does not define buckets const { buckets: clusterBuckets = [] } = response.aggregations.clusters; if (!clusterBuckets.length) { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts index 2388abf024eb95..c8d15acf8ff73a 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts @@ -56,6 +56,7 @@ describe('fetchMissingMonitoringData', () => { ]; esClient.search.mockReturnValue( + // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { @@ -114,6 +115,7 @@ describe('fetchMissingMonitoringData', () => { }, ]; esClient.search.mockReturnValue( + // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts index cb274848e6c5af..a7b4a3a0232072 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts @@ -99,8 +99,8 @@ export async function fetchMissingMonitoringData( sort: [ { timestamp: { - order: 'desc', - unmapped_type: 'long', + order: 'desc' as const, + unmapped_type: 'long' as const, }, }, ], diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts index a97594c8ca9956..ff3a8d4aa7ef8f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts @@ -36,8 +36,8 @@ export async function fetchNodesFromClusterStats( sort: [ { timestamp: { - order: 'desc', - unmapped_type: 'long', + order: 'desc' as const, + unmapped_type: 'long' as const, }, }, ], @@ -71,8 +71,8 @@ export async function fetchNodesFromClusterStats( sort: [ { timestamp: { - order: 'desc', - unmapped_type: 'long', + order: 'desc' as const, + unmapped_type: 'long' as const, }, }, ], @@ -90,6 +90,7 @@ export async function fetchNodesFromClusterStats( const { body: response } = await esClient.search(params); const nodes = []; + // @ts-expect-error @elastic/elasticsearch Aggregate does not define buckets const clusterBuckets = response.aggregations.clusters.buckets; for (const clusterBucket of clusterBuckets) { const clusterUuid = clusterBucket.key; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts index 5770721195e141..b63244dab719d8 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts @@ -13,13 +13,13 @@ const invalidNumberValue = (value: number) => { return isNaN(value) || value === undefined || value === null; }; -const getTopHits = (threadType: string, order: string) => ({ +const getTopHits = (threadType: string, order: 'asc' | 'desc') => ({ top_hits: { sort: [ { timestamp: { order, - unmapped_type: 'long', + unmapped_type: 'long' as const, }, }, ], @@ -81,10 +81,10 @@ export async function fetchThreadPoolRejectionStats( }, aggs: { most_recent: { - ...getTopHits(threadType, 'desc'), + ...getTopHits(threadType, 'desc' as const), }, least_recent: { - ...getTopHits(threadType, 'asc'), + ...getTopHits(threadType, 'asc' as const), }, }, }, @@ -96,6 +96,7 @@ export async function fetchThreadPoolRejectionStats( const { body: response } = await esClient.search(params); const stats: AlertThreadPoolRejectionsStats[] = []; + // @ts-expect-error @elastic/elasticsearch Aggregate does not specify buckets const { buckets: clusterBuckets = [] } = response.aggregations.clusters; if (!clusterBuckets.length) { diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index e118d17e17c3fd..2676e40a4902f4 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -8,7 +8,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { PluginInitializerContext } from 'src/core/server'; import { ObservabilityPlugin, ObservabilityPluginSetup } from './plugin'; -import { createOrUpdateIndex, MappingsDefinition } from './utils/create_or_update_index'; +import { createOrUpdateIndex, Mappings } from './utils/create_or_update_index'; import { ScopedAnnotationsClient } from './lib/annotations/bootstrap_annotations'; import { unwrapEsResponse, WrappedElasticsearchClientError } from './utils/unwrap_es_response'; @@ -29,7 +29,7 @@ export const plugin = (initContext: PluginInitializerContext) => export { createOrUpdateIndex, - MappingsDefinition, + Mappings, ObservabilityPluginSetup, ScopedAnnotationsClient, unwrapEsResponse, diff --git a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts index 7abd68cb9f16c9..39a594dcc86ca9 100644 --- a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts +++ b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts @@ -23,27 +23,6 @@ type CreateParams = t.TypeOf; type DeleteParams = t.TypeOf; type GetByIdParams = t.TypeOf; -interface IndexDocumentResponse { - _shards: { - total: number; - failed: number; - successful: number; - }; - _index: string; - _type: string; - _id: string; - _version: number; - _seq_no: number; - _primary_term: number; - result: string; -} - -export interface GetResponse { - _id: string; - _index: string; - _source: Annotation; -} - export function createAnnotationsClient(params: { index: string; esClient: ElasticsearchClient; @@ -95,7 +74,7 @@ export function createAnnotationsClient(params: { }; const body = await unwrapEsResponse( - esClient.index({ + esClient.index({ index, body: annotation, refresh: 'wait_for', @@ -103,18 +82,18 @@ export function createAnnotationsClient(params: { ); return ( - await esClient.get({ + await esClient.get({ index, id: body._id, }) - ).body; + ).body as { _id: string; _index: string; _source: Annotation }; } ), getById: ensureGoldLicense(async (getByIdParams: GetByIdParams) => { const { id } = getByIdParams; return unwrapEsResponse( - esClient.get({ + esClient.get({ id, index, }) diff --git a/x-pack/plugins/observability/server/lib/annotations/mappings.ts b/x-pack/plugins/observability/server/lib/annotations/mappings.ts index 3313c411b58890..da72afdbecb33a 100644 --- a/x-pack/plugins/observability/server/lib/annotations/mappings.ts +++ b/x-pack/plugins/observability/server/lib/annotations/mappings.ts @@ -6,7 +6,7 @@ */ export const mappings = { - dynamic: 'strict', + dynamic: 'strict' as const, properties: { annotation: { properties: { @@ -45,4 +45,4 @@ export const mappings = { }, }, }, -} as const; +}; diff --git a/x-pack/plugins/observability/server/utils/create_or_update_index.ts b/x-pack/plugins/observability/server/utils/create_or_update_index.ts index cc6504fd4d4fd9..19b14ef8b24375 100644 --- a/x-pack/plugins/observability/server/utils/create_or_update_index.ts +++ b/x-pack/plugins/observability/server/utils/create_or_update_index.ts @@ -4,24 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { CreateIndexRequest, PutMappingRequest } from '@elastic/elasticsearch/api/types'; import pRetry from 'p-retry'; import { Logger, ElasticsearchClient } from 'src/core/server'; -export interface MappingsObject { - type: string; - ignore_above?: number; - scaling_factor?: number; - ignore_malformed?: boolean; - coerce?: boolean; - fields?: Record; -} - -export interface MappingsDefinition { - dynamic?: boolean | 'strict'; - properties: Record; - dynamic_templates?: any[]; -} +export type Mappings = Required['body']['mappings'] & + Required['body']; export async function createOrUpdateIndex({ index, @@ -30,7 +18,7 @@ export async function createOrUpdateIndex({ logger, }: { index: string; - mappings: MappingsDefinition; + mappings: Mappings; client: ElasticsearchClient; logger: Logger; }) { @@ -59,7 +47,8 @@ export async function createOrUpdateIndex({ }); if (!result.body.acknowledged) { - const resultError = result && result.body.error && JSON.stringify(result.body.error); + const bodyWithError: { body?: { error: any } } = result as any; + const resultError = JSON.stringify(bodyWithError?.body?.error); throw new Error(resultError); } }, @@ -82,9 +71,9 @@ function createNewIndex({ }: { index: string; client: ElasticsearchClient; - mappings: MappingsDefinition; + mappings: Required['body']['mappings']; }) { - return client.indices.create<{ acknowledged: boolean; error: any }>({ + return client.indices.create({ index, body: { // auto_expand_replicas: Allows cluster to not have replicas for this index @@ -101,9 +90,9 @@ function updateExistingIndex({ }: { index: string; client: ElasticsearchClient; - mappings: MappingsDefinition; + mappings: PutMappingRequest['body']; }) { - return client.indices.putMapping<{ acknowledged: boolean; error: any }>({ + return client.indices.putMapping({ index, body: mappings, }); diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts index 561866d5077a6b..b24e4f28d89f11 100644 --- a/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { IEsSearchResponse } from '../../../../../../../src/plugins/data/common'; import { Inspect, Maybe, PageInfoPaginated } from '../../common'; import { RequestOptions, RequestOptionsPaginated } from '../..'; -export type ActionEdges = SearchResponse['hits']['hits']; +export type ActionEdges = estypes.SearchResponse['hits']['hits']; -export type ActionResultEdges = SearchResponse['hits']['hits']; +export type ActionResultEdges = estypes.SearchResponse['hits']['hits']; export interface ActionsStrategyResponse extends IEsSearchResponse { edges: ActionEdges; totalCount: number; diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts index add8598bb77ad6..035774aaffe36d 100644 --- a/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; +import { estypes } from '@elastic/elasticsearch'; import { IEsSearchResponse } from '../../../../../../../src/plugins/data/common'; import { Inspect, Maybe, PageInfoPaginated } from '../../common'; import { RequestOptionsPaginated } from '../..'; -export type ResultEdges = SearchResponse['hits']['hits']; +export type ResultEdges = estypes.SearchResponse['hits']['hits']; export interface ResultsStrategyResponse extends IEsSearchResponse { edges: ResultEdges; diff --git a/x-pack/plugins/osquery/public/action_results/action_results_table.tsx b/x-pack/plugins/osquery/public/action_results/action_results_table.tsx index 1dd5b63eedc23a..1880cec0ae8e20 100644 --- a/x-pack/plugins/osquery/public/action_results/action_results_table.tsx +++ b/x-pack/plugins/osquery/public/action_results/action_results_table.tsx @@ -107,7 +107,7 @@ const ActionResultsTableComponent: React.FC = ({ action if (columnId === 'status') { // eslint-disable-next-line react-hooks/rules-of-hooks const linkProps = useRouterNavigate( - `/live_query/${actionId}/results/${value.fields.agent_id[0]}` + `/live_query/${actionId}/results/${value.fields?.agent_id[0]}` ); return ( @@ -122,7 +122,7 @@ const ActionResultsTableComponent: React.FC = ({ action // eslint-disable-next-line react-hooks/rules-of-hooks const { data: allResultsData } = useAllResults({ actionId, - agentId: value.fields.agent_id[0], + agentId: value.fields?.agent_id[0], activePage: pagination.pageIndex, limit: pagination.pageSize, direction: Direction.asc, @@ -133,7 +133,7 @@ const ActionResultsTableComponent: React.FC = ({ action } if (columnId === 'agent_status') { - const agentIdValue = value.fields.agent_id[0]; + const agentIdValue = value.fields?.agent_id[0]; // @ts-expect-error update types const agent = find(['_id', agentIdValue], agentsData?.agents); const online = agent?.active; @@ -143,7 +143,7 @@ const ActionResultsTableComponent: React.FC = ({ action } if (columnId === 'agent') { - const agentIdValue = value.fields.agent_id[0]; + const agentIdValue = value.fields?.agent_id[0]; // @ts-expect-error update types const agent = find(['_id', agentIdValue], agentsData?.agents); const agentName = agent?.local_metadata.host.name; @@ -156,6 +156,7 @@ const ActionResultsTableComponent: React.FC = ({ action } if (columnId === '@timestamp') { + // @ts-expect-error fields is optional return value.fields['@timestamp']; } diff --git a/x-pack/plugins/osquery/public/actions/actions_table.tsx b/x-pack/plugins/osquery/public/actions/actions_table.tsx index 986b46b1a40899..ca856938496514 100644 --- a/x-pack/plugins/osquery/public/actions/actions_table.tsx +++ b/x-pack/plugins/osquery/public/actions/actions_table.tsx @@ -62,6 +62,7 @@ const ActionsTableComponent = () => { () => ({ rowIndex, columnId }) => { // eslint-disable-next-line react-hooks/rules-of-hooks const data = useContext(DataContext); + // @ts-expect-error fields is optional const value = data[rowIndex].fields[columnId]; if (columnId === 'action_id') { diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx index 4c2048148f745f..7557828c4407cc 100644 --- a/x-pack/plugins/osquery/public/results/results_table.tsx +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -68,9 +68,11 @@ const ResultsTableComponent: React.FC = ({ actionId, // eslint-disable-next-line react-hooks/rules-of-hooks const data = useContext(DataContext); + // @ts-expect-error fields is optional const value = data[rowIndex].fields[columnId]; if (columnId === 'agent.name') { + // @ts-expect-error fields is optional const agentIdValue = data[rowIndex].fields['agent.id']; // eslint-disable-next-line react-hooks/rules-of-hooks const linkProps = useRouterNavigate(`/live_query/${actionId}/results/${agentIdValue}`); diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/index.ts index bc0af9a1d0dcc7..e05bd15bcc7225 100644 --- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/index.ts +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/index.ts @@ -36,6 +36,7 @@ export const allActions: OsqueryFactory = { ...response, inspect, edges: response.rawResponse.hits.hits, + // @ts-expect-error doesn't handle case when total TotalHits totalCount: response.rawResponse.hits.total, pageInfo: { activePage: activePage ?? 0, diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/index.ts index dbcc03006399af..50fcf938bdd793 100644 --- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/index.ts +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/index.ts @@ -37,6 +37,7 @@ export const actionResults: OsqueryFactory = { ...response, inspect, edges: response.rawResponse.hits.hits, + // @ts-expect-error doesn't handle case when total TotalHits totalCount: response.rawResponse.hits.total, pageInfo: { activePage: activePage ?? 0, diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/index.ts index 1f7fbccb686827..7e0532dfec1af0 100644 --- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/index.ts +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/index.ts @@ -37,7 +37,11 @@ export const allAgents: OsqueryFactory = { return { ...response, inspect, - edges: response.rawResponse.hits.hits.map((hit) => ({ _id: hit._id, ...hit._source })), + edges: response.rawResponse.hits.hits.map((hit) => ({ + _id: hit._id, + ...hit._source, + })) as Agent[], + // @ts-expect-error doesn't handle case when total TotalHits totalCount: response.rawResponse.hits.total, pageInfo: { activePage: activePage ?? 0, diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/index.ts index 7b9a24e4a06533..93cba882e39ed5 100644 --- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/index.ts +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/index.ts @@ -36,6 +36,7 @@ export const allResults: OsqueryFactory = { ...response, inspect, edges: response.rawResponse.hits.hits, + // @ts-expect-error doesn't handle case when total TotalHits totalCount: response.rawResponse.hits.total, pageInfo: { activePage: activePage ?? 0, diff --git a/x-pack/plugins/painless_lab/server/routes/api/execute.ts b/x-pack/plugins/painless_lab/server/routes/api/execute.ts index e77f4fd4a05b5a..03168098052008 100644 --- a/x-pack/plugins/painless_lab/server/routes/api/execute.ts +++ b/x-pack/plugins/painless_lab/server/routes/api/execute.ts @@ -27,6 +27,7 @@ export function registerExecuteRoute({ router, license }: RouteDependencies) { try { const client = ctx.core.elasticsearch.client.asCurrentUser; const response = await client.scriptsPainlessExecute({ + // @ts-expect-error `ExecutePainlessScriptRequest.body` does not allow `string` body, }); diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts index 370fc42921acf4..85c5379a63b7fd 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts @@ -91,7 +91,7 @@ export class CsvGenerator { }; const results = ( await this.clients.data.search(searchParams, { strategy: ES_SEARCH_STRATEGY }).toPromise() - ).rawResponse; + ).rawResponse as SearchResponse; return results; } diff --git a/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts index 1eb11033368019..f07da188f3e62e 100644 --- a/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts +++ b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts @@ -4,9 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { get } from 'lodash'; -import { ElasticsearchClient, SearchResponse } from 'kibana/server'; +import { ElasticsearchClient } from 'kibana/server'; import { ReportingConfig } from '../'; import { ExportTypesRegistry } from '../lib/export_types_registry'; import { GetLicense } from './'; @@ -19,7 +19,7 @@ import { KeyCountBucket, RangeStats, ReportingUsageType, - ReportingUsageSearchResponse, + // ReportingUsageSearchResponse, StatusByAppBucket, } from './types'; @@ -100,7 +100,8 @@ type RangeStatSets = Partial & { last7Days: Partial; }; -type ESResponse = Partial>; +// & ReportingUsageSearchResponse +type ESResponse = Partial; async function handleResponse(response: ESResponse): Promise> { const buckets = get(response, 'aggregations.ranges.buckets'); diff --git a/x-pack/plugins/saved_objects_tagging/server/usage/fetch_tag_usage_data.ts b/x-pack/plugins/saved_objects_tagging/server/usage/fetch_tag_usage_data.ts index 3e745458f66074..06230172d52bd3 100644 --- a/x-pack/plugins/saved_objects_tagging/server/usage/fetch_tag_usage_data.ts +++ b/x-pack/plugins/saved_objects_tagging/server/usage/fetch_tag_usage_data.ts @@ -11,26 +11,22 @@ import { TaggingUsageData, ByTypeTaggingUsageData } from './types'; /** * Manual type reflection of the `tagDataAggregations` resulting payload */ -interface AggregatedTagUsageResponseBody { - aggregations: { - by_type: { - buckets: Array<{ - key: string; +interface AggregatedTagUsage { + buckets: Array<{ + key: string; + doc_count: number; + nested_ref: { + tag_references: { doc_count: number; - nested_ref: { - tag_references: { + tag_id: { + buckets: Array<{ + key: string; doc_count: number; - tag_id: { - buckets: Array<{ - key: string; - doc_count: number; - }>; - }; - }; + }>; }; - }>; + }; }; - }; + }>; } export const fetchTagUsageData = async ({ @@ -40,7 +36,7 @@ export const fetchTagUsageData = async ({ esClient: ElasticsearchClient; kibanaIndex: string; }): Promise => { - const { body } = await esClient.search({ + const { body } = await esClient.search({ index: [kibanaIndex], ignore_unavailable: true, filter_path: 'aggregations', @@ -59,7 +55,7 @@ export const fetchTagUsageData = async ({ const allUsedTags = new Set(); let totalTaggedObjects = 0; - const typeBuckets = body.aggregations.by_type.buckets; + const typeBuckets = (body.aggregations!.by_type as AggregatedTagUsage).buckets; typeBuckets.forEach((bucket) => { const type = bucket.key; const taggedDocCount = bucket.doc_count; diff --git a/x-pack/plugins/searchprofiler/server/routes/profile.ts b/x-pack/plugins/searchprofiler/server/routes/profile.ts index cdc420667f9e10..cbe0b75bc9eda0 100644 --- a/x-pack/plugins/searchprofiler/server/routes/profile.ts +++ b/x-pack/plugins/searchprofiler/server/routes/profile.ts @@ -33,16 +33,15 @@ export const register = ({ router, getLicenseStatus, log }: RouteDependencies) = body: { query, index }, } = request; - const parsed = { - // Activate profiler mode for this query. - profile: true, - ...query, - }; - const body = { index, - body: JSON.stringify(parsed, null, 2), + body: { + // Activate profiler mode for this query. + profile: true, + ...query, + }, }; + try { const client = ctx.core.elasticsearch.client.asCurrentUser; const resp = await client.search(body); diff --git a/x-pack/plugins/security/common/model/authenticated_user.mock.ts b/x-pack/plugins/security/common/model/authenticated_user.mock.ts index 3715245b37e61a..6dad3886401ac1 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.mock.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.mock.ts @@ -7,7 +7,10 @@ import type { AuthenticatedUser } from './authenticated_user'; -export function mockAuthenticatedUser(user: Partial = {}) { +// We omit `roles` here since the original interface defines this field as `readonly string[]` that makes it hard to use +// in various mocks that expect mutable string array. +type AuthenticatedUserProps = Partial & { roles: string[] }>; +export function mockAuthenticatedUser(user: AuthenticatedUserProps = {}) { return { username: 'user', email: 'email', @@ -18,6 +21,7 @@ export function mockAuthenticatedUser(user: Partial = {}) { lookup_realm: { name: 'native1', type: 'native' }, authentication_provider: { type: 'basic', name: 'basic1' }, authentication_type: 'realm', + metadata: { _reserved: false }, ...user, }; } diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts index dda3a159036965..a1b9671ab6bd76 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts @@ -64,7 +64,14 @@ describe('API Keys', () => { it('returns true when the operation completes without error', async () => { mockLicense.isEnabled.mockReturnValue(true); mockClusterClient.asInternalUser.security.invalidateApiKey.mockResolvedValue( - securityMock.createApiResponse({ body: {} }) + securityMock.createApiResponse({ + body: { + invalidated_api_keys: [], + previously_invalidated_api_keys: [], + error_count: 0, + error_details: [], + }, + }) ); const result = await apiKeys.areAPIKeysEnabled(); expect(mockClusterClient.asInternalUser.security.invalidateApiKey).toHaveBeenCalledTimes(1); @@ -105,7 +112,14 @@ describe('API Keys', () => { it('calls `invalidateApiKey` with proper parameters', async () => { mockLicense.isEnabled.mockReturnValue(true); mockClusterClient.asInternalUser.security.invalidateApiKey.mockResolvedValueOnce( - securityMock.createApiResponse({ body: {} }) + securityMock.createApiResponse({ + body: { + invalidated_api_keys: [], + previously_invalidated_api_keys: [], + error_count: 0, + error_details: [], + }, + }) ); const result = await apiKeys.areAPIKeysEnabled(); @@ -133,6 +147,7 @@ describe('API Keys', () => { mockLicense.isEnabled.mockReturnValue(true); mockScopedClusterClient.asCurrentUser.security.createApiKey.mockResolvedValueOnce( + // @ts-expect-error @elastic/elsticsearch CreateApiKeyResponse.expiration: number securityMock.createApiResponse({ body: { id: '123', @@ -302,6 +317,7 @@ describe('API Keys', () => { invalidated_api_keys: ['api-key-id-1'], previously_invalidated_api_keys: [], error_count: 0, + error_details: [], }, }) ); @@ -312,6 +328,7 @@ describe('API Keys', () => { invalidated_api_keys: ['api-key-id-1'], previously_invalidated_api_keys: [], error_count: 0, + error_details: [], }); expect(mockScopedClusterClient.asCurrentUser.security.invalidateApiKey).toHaveBeenCalledWith({ body: { @@ -328,6 +345,7 @@ describe('API Keys', () => { invalidated_api_keys: ['api-key-id-1'], previously_invalidated_api_keys: [], error_count: 0, + error_details: [], }, }) ); @@ -339,6 +357,7 @@ describe('API Keys', () => { invalidated_api_keys: ['api-key-id-1'], previously_invalidated_api_keys: [], error_count: 0, + error_details: [], }); expect(mockScopedClusterClient.asCurrentUser.security.invalidateApiKey).toHaveBeenCalledWith({ body: { @@ -364,6 +383,7 @@ describe('API Keys', () => { invalidated_api_keys: ['api-key-id-1'], previously_invalidated_api_keys: [], error_count: 0, + error_details: [], }, }) ); @@ -372,6 +392,7 @@ describe('API Keys', () => { invalidated_api_keys: ['api-key-id-1'], previously_invalidated_api_keys: [], error_count: 0, + error_details: [], }); expect(mockClusterClient.asInternalUser.security.invalidateApiKey).toHaveBeenCalledWith({ body: { @@ -388,6 +409,7 @@ describe('API Keys', () => { invalidated_api_keys: ['api-key-id-1'], previously_invalidated_api_keys: [], error_count: 0, + error_details: [], }, }) ); @@ -399,6 +421,7 @@ describe('API Keys', () => { invalidated_api_keys: ['api-key-id-1'], previously_invalidated_api_keys: [], error_count: 0, + error_details: [], }); expect(mockClusterClient.asInternalUser.security.invalidateApiKey).toHaveBeenCalledWith({ body: { diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts index bdf549095c3c0f..7396519acf9eaf 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts @@ -109,7 +109,7 @@ export interface InvalidateAPIKeyResult { error_details?: Array<{ type: string; reason: string; - caused_by: { + caused_by?: { type: string; reason: string; }; @@ -145,7 +145,11 @@ export class APIKeys { ); try { - await this.clusterClient.asInternalUser.security.invalidateApiKey({ body: { ids: [id] } }); + await this.clusterClient.asInternalUser.security.invalidateApiKey({ + body: { + ids: [id], + }, + }); return true; } catch (e) { if (this.doesErrorIndicateAPIKeysAreDisabled(e)) { @@ -171,12 +175,12 @@ export class APIKeys { this.logger.debug('Trying to create an API key'); // User needs `manage_api_key` privilege to use this API - let result; + let result: CreateAPIKeyResult; try { result = ( await this.clusterClient .asScoped(request) - .asCurrentUser.security.createApiKey({ body: params }) + .asCurrentUser.security.createApiKey({ body: params }) ).body; this.logger.debug('API key was created successfully'); } catch (e) { @@ -207,10 +211,11 @@ export class APIKeys { const params = this.getGrantParams(createParams, authorizationHeader); // User needs `manage_api_key` or `grant_api_key` privilege to use this API - let result; + let result: GrantAPIKeyResult; try { result = ( - await this.clusterClient.asInternalUser.security.grantApiKey({ + await this.clusterClient.asInternalUser.security.grantApiKey({ + // @ts-expect-error @elastic/elasticsearch api_key.role_descriptors body: params, }) ).body; @@ -235,15 +240,15 @@ export class APIKeys { this.logger.debug(`Trying to invalidate ${params.ids.length} an API key as current user`); - let result; + let result: InvalidateAPIKeyResult; try { // User needs `manage_api_key` privilege to use this API result = ( - await this.clusterClient - .asScoped(request) - .asCurrentUser.security.invalidateApiKey({ - body: { ids: params.ids }, - }) + await this.clusterClient.asScoped(request).asCurrentUser.security.invalidateApiKey({ + body: { + ids: params.ids, + }, + }) ).body; this.logger.debug( `API keys by ids=[${params.ids.join(', ')}] was invalidated successfully as current user` @@ -271,12 +276,14 @@ export class APIKeys { this.logger.debug(`Trying to invalidate ${params.ids.length} API keys`); - let result; + let result: InvalidateAPIKeyResult; try { // Internal user needs `cluster:admin/xpack/security/api_key/invalidate` privilege to use this API result = ( - await this.clusterClient.asInternalUser.security.invalidateApiKey({ - body: { ids: params.ids }, + await this.clusterClient.asInternalUser.security.invalidateApiKey({ + body: { + ids: params.ids, + }, }) ).body; this.logger.debug(`API keys by ids=[${params.ids.join(', ')}] was invalidated successfully`); diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts index 20946ff6f5e800..18d567a143fee8 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.ts @@ -113,10 +113,11 @@ export abstract class BaseAuthenticationProvider { */ protected async getUser(request: KibanaRequest, authHeaders: Headers = {}) { return this.authenticationInfoToAuthenticatedUser( + // @ts-expect-error @elastic/elasticsearch `AuthenticateResponse` type doesn't define `authentication_type` and `enabled`. ( await this.options.client .asScoped({ headers: { ...request.headers, ...authHeaders } }) - .asCurrentUser.security.authenticate() + .asCurrentUser.security.authenticate() ).body ); } diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index 1bcb845ca5c084..f5c02953cebd36 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -46,7 +46,7 @@ describe('KerberosAuthenticationProvider', () => { const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( - securityMock.createApiResponse({ body: {} }) + securityMock.createApiResponse({ body: mockAuthenticatedUser() }) ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); @@ -122,6 +122,7 @@ describe('KerberosAuthenticationProvider', () => { }); mockOptions.client.asInternalUser.security.getToken.mockResolvedValue( + // @ts-expect-error not full interface securityMock.createApiResponse({ body: { access_token: 'some-token', @@ -156,6 +157,7 @@ describe('KerberosAuthenticationProvider', () => { }); mockOptions.client.asInternalUser.security.getToken.mockResolvedValue( + // @ts-expect-error not full interface securityMock.createApiResponse({ body: { access_token: 'some-token', diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index 0de8e8e10a630c..75dc2a8f479694 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -155,9 +155,13 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { authentication: AuthenticationInfo; }; try { + // @ts-expect-error authentication.email can be optional tokens = ( - await this.options.client.asInternalUser.security.getToken({ - body: { grant_type: '_kerberos', kerberos_ticket: kerberosTicket }, + await this.options.client.asInternalUser.security.getToken({ + body: { + grant_type: '_kerberos', + kerberos_ticket: kerberosTicket, + }, }) ).body; } catch (err) { diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index f35545e5e5f3ac..ebeca42682eb94 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -11,7 +11,6 @@ import Boom from '@hapi/boom'; import type { KibanaRequest } from 'src/core/server'; import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; -import type { AuthenticatedUser } from '../../../common/model'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; import { securityMock } from '../../mocks'; import { AuthenticationResult } from '../authentication_result'; @@ -24,7 +23,7 @@ import { OIDCAuthenticationProvider, OIDCLogin } from './oidc'; describe('OIDCAuthenticationProvider', () => { let provider: OIDCAuthenticationProvider; let mockOptions: MockAuthenticationProviderOptions; - let mockUser: AuthenticatedUser; + let mockUser: ReturnType; let mockScopedClusterClient: ReturnType< typeof elasticsearchServiceMock.createScopedClusterClient >; diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index 50d9ab33fd96fc..bd51a0f8153297 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -10,7 +10,6 @@ import Boom from '@hapi/boom'; import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; -import type { AuthenticatedUser } from '../../../common/model'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; import { securityMock } from '../../mocks'; import { AuthenticationResult } from '../authentication_result'; @@ -22,7 +21,7 @@ import { SAMLAuthenticationProvider, SAMLLogin } from './saml'; describe('SAMLAuthenticationProvider', () => { let provider: SAMLAuthenticationProvider; let mockOptions: MockAuthenticationProviderOptions; - let mockUser: AuthenticatedUser; + let mockUser: ReturnType; let mockScopedClusterClient: ReturnType< typeof elasticsearchServiceMock.createScopedClusterClient >; diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index 4d802506071216..84a1649540267d 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -50,6 +50,7 @@ describe('TokenAuthenticationProvider', () => { const authorization = `Bearer ${tokenPair.accessToken}`; mockOptions.client.asInternalUser.security.getToken.mockResolvedValue( + // @ts-expect-error not full interface securityMock.createApiResponse({ body: { access_token: tokenPair.accessToken, diff --git a/x-pack/plugins/security/server/authentication/providers/token.ts b/x-pack/plugins/security/server/authentication/providers/token.ts index f202a0bd43fcff..43338a8f6400fb 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.ts @@ -72,16 +72,21 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { refresh_token: refreshToken, authentication: authenticationInfo, } = ( - await this.options.client.asInternalUser.security.getToken<{ - access_token: string; - refresh_token: string; - authentication: AuthenticationInfo; - }>({ body: { grant_type: 'password', username, password } }) + await this.options.client.asInternalUser.security.getToken({ + body: { + grant_type: 'password', + username, + password, + }, + }) ).body; this.logger.debug('Get token API request to Elasticsearch successful'); return AuthenticationResult.succeeded( - this.authenticationInfoToAuthenticatedUser(authenticationInfo), + this.authenticationInfoToAuthenticatedUser( + // @ts-expect-error @elastic/elasticsearch GetUserAccessTokenResponse declares authentication: string, but expected AuthenticatedUser + authenticationInfo as AuthenticationInfo + ), { authHeaders: { authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(), diff --git a/x-pack/plugins/security/server/authentication/tokens.test.ts b/x-pack/plugins/security/server/authentication/tokens.test.ts index a6d52e355b1451..d02b368635e6fa 100644 --- a/x-pack/plugins/security/server/authentication/tokens.test.ts +++ b/x-pack/plugins/security/server/authentication/tokens.test.ts @@ -109,6 +109,9 @@ describe('Tokens', () => { access_token: tokenPair.accessToken, refresh_token: tokenPair.refreshToken, authentication: authenticationInfo, + type: 'Bearer', + expires_in: 1200, + scope: 'FULL', }, }) ); @@ -197,7 +200,14 @@ describe('Tokens', () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; mockElasticsearchClient.security.invalidateToken.mockResolvedValue( - securityMock.createApiResponse({ body: { invalidated_tokens: 1 } }) + securityMock.createApiResponse({ + body: { + invalidated_tokens: 1, + previously_invalidated_tokens: 0, + error_count: 0, + error_details: [], + }, + }) ); await expect(tokens.invalidate(tokenPair)).resolves.toBe(undefined); @@ -215,7 +225,14 @@ describe('Tokens', () => { const tokenPair = { accessToken: 'foo' }; mockElasticsearchClient.security.invalidateToken.mockResolvedValue( - securityMock.createApiResponse({ body: { invalidated_tokens: 1 } }) + securityMock.createApiResponse({ + body: { + invalidated_tokens: 1, + previously_invalidated_tokens: 0, + error_count: 0, + error_details: [], + }, + }) ); await expect(tokens.invalidate(tokenPair)).resolves.toBe(undefined); @@ -230,7 +247,14 @@ describe('Tokens', () => { const tokenPair = { refreshToken: 'foo' }; mockElasticsearchClient.security.invalidateToken.mockResolvedValue( - securityMock.createApiResponse({ body: { invalidated_tokens: 1 } }) + securityMock.createApiResponse({ + body: { + invalidated_tokens: 1, + previously_invalidated_tokens: 0, + error_count: 0, + error_details: [], + }, + }) ); await expect(tokens.invalidate(tokenPair)).resolves.toBe(undefined); @@ -274,7 +298,14 @@ describe('Tokens', () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; mockElasticsearchClient.security.invalidateToken.mockResolvedValue( - securityMock.createApiResponse({ body: { invalidated_tokens: 5 } }) + securityMock.createApiResponse({ + body: { + invalidated_tokens: 5, + previously_invalidated_tokens: 0, + error_count: 0, + error_details: [], + }, + }) ); await expect(tokens.invalidate(tokenPair)).resolves.toBe(undefined); diff --git a/x-pack/plugins/security/server/authentication/tokens.ts b/x-pack/plugins/security/server/authentication/tokens.ts index e3c4644775613a..8f6dd9275e59cd 100644 --- a/x-pack/plugins/security/server/authentication/tokens.ts +++ b/x-pack/plugins/security/server/authentication/tokens.ts @@ -60,16 +60,22 @@ export class Tokens { refresh_token: refreshToken, authentication: authenticationInfo, } = ( - await this.options.client.security.getToken<{ - access_token: string; - refresh_token: string; - authentication: AuthenticationInfo; - }>({ body: { grant_type: 'refresh_token', refresh_token: existingRefreshToken } }) + await this.options.client.security.getToken({ + body: { + grant_type: 'refresh_token', + refresh_token: existingRefreshToken, + }, + }) ).body; this.logger.debug('Access token has been successfully refreshed.'); - return { accessToken, refreshToken, authenticationInfo }; + return { + accessToken, + refreshToken, + // @ts-expect-error @elastic/elasticsearch decalred GetUserAccessTokenResponse.authentication: string + authenticationInfo: authenticationInfo as AuthenticationInfo, + }; } catch (err) { this.logger.debug(`Failed to refresh access token: ${err.message}`); diff --git a/x-pack/plugins/security/server/authorization/check_privileges.test.ts b/x-pack/plugins/security/server/authorization/check_privileges.test.ts index 2c1deed0f8c307..75c8229bb37d69 100644 --- a/x-pack/plugins/security/server/authorization/check_privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/check_privileges.test.ts @@ -68,8 +68,8 @@ describe('#atSpace', () => { const expectedIndexPrivilegePayload = Object.entries( options.elasticsearchPrivileges?.index ?? {} - ).map(([names, indexPrivileges]) => ({ - names, + ).map(([name, indexPrivileges]) => ({ + names: [name], privileges: indexPrivileges, })); @@ -78,7 +78,7 @@ describe('#atSpace', () => { body: { cluster: options.elasticsearchPrivileges?.cluster, index: expectedIndexPrivilegePayload, - applications: [ + application: [ { application, resources: [`space:${options.spaceId}`], @@ -914,8 +914,8 @@ describe('#atSpaces', () => { const expectedIndexPrivilegePayload = Object.entries( options.elasticsearchPrivileges?.index ?? {} - ).map(([names, indexPrivileges]) => ({ - names, + ).map(([name, indexPrivileges]) => ({ + names: [name], privileges: indexPrivileges, })); @@ -924,7 +924,7 @@ describe('#atSpaces', () => { body: { cluster: options.elasticsearchPrivileges?.cluster, index: expectedIndexPrivilegePayload, - applications: [ + application: [ { application, resources: options.spaceIds.map((spaceId) => `space:${spaceId}`), @@ -2118,8 +2118,8 @@ describe('#globally', () => { const expectedIndexPrivilegePayload = Object.entries( options.elasticsearchPrivileges?.index ?? {} - ).map(([names, indexPrivileges]) => ({ - names, + ).map(([name, indexPrivileges]) => ({ + names: [name], privileges: indexPrivileges, })); @@ -2128,7 +2128,7 @@ describe('#globally', () => { body: { cluster: options.elasticsearchPrivileges?.cluster, index: expectedIndexPrivilegePayload, - applications: [ + application: [ { application, resources: [GLOBAL_RESOURCE], diff --git a/x-pack/plugins/security/server/authorization/check_privileges.ts b/x-pack/plugins/security/server/authorization/check_privileges.ts index 0fc11cddf9bbc8..3a35cf164ad85c 100644 --- a/x-pack/plugins/security/server/authorization/check_privileges.ts +++ b/x-pack/plugins/security/server/authorization/check_privileges.ts @@ -51,22 +51,22 @@ export function checkPrivilegesWithRequestFactory( const allApplicationPrivileges = uniq([actions.version, actions.login, ...kibanaPrivileges]); const clusterClient = await getClusterClient(); - const { body: hasPrivilegesResponse } = await clusterClient - .asScoped(request) - .asCurrentUser.security.hasPrivileges({ - body: { - cluster: privileges.elasticsearch?.cluster, - index: Object.entries(privileges.elasticsearch?.index ?? {}).map( - ([names, indexPrivileges]) => ({ - names, - privileges: indexPrivileges, - }) - ), - applications: [ - { application: applicationName, resources, privileges: allApplicationPrivileges }, - ], - }, - }); + const { body } = await clusterClient.asScoped(request).asCurrentUser.security.hasPrivileges({ + body: { + cluster: privileges.elasticsearch?.cluster, + index: Object.entries(privileges.elasticsearch?.index ?? {}).map( + ([name, indexPrivileges]) => ({ + names: [name], + privileges: indexPrivileges, + }) + ), + application: [ + { application: applicationName, resources, privileges: allApplicationPrivileges }, + ], + }, + }); + + const hasPrivilegesResponse: HasPrivilegesResponse = body; validateEsPrivilegeResponse( hasPrivilegesResponse, diff --git a/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts b/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts index e2539695d54eea..e3a586062ae4c8 100644 --- a/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts +++ b/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts @@ -6,7 +6,6 @@ */ import type { RouteDefinitionParams } from '../..'; -import type { BuiltinESPrivileges } from '../../../../common/model'; export function defineGetBuiltinPrivilegesRoutes({ router }: RouteDefinitionParams) { router.get( @@ -14,7 +13,7 @@ export function defineGetBuiltinPrivilegesRoutes({ router }: RouteDefinitionPara async (context, request, response) => { const { body: privileges, - } = await context.core.elasticsearch.client.asCurrentUser.security.getBuiltinPrivileges(); + } = await context.core.elasticsearch.client.asCurrentUser.security.getBuiltinPrivileges(); // Exclude the `none` privilege, as it doesn't make sense as an option within the Kibana UI privileges.cluster = privileges.cluster.filter((privilege) => privilege !== 'none'); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index 3bfc5e5d4dda39..01d32f7fb82339 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -10,7 +10,6 @@ import { schema } from '@kbn/config-schema'; import type { RouteDefinitionParams } from '../..'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; -import type { ElasticsearchRole } from './model'; import { transformElasticsearchRoleToRole } from './model'; export function defineGetRolesRoutes({ router, authz }: RouteDefinitionParams) { @@ -25,14 +24,15 @@ export function defineGetRolesRoutes({ router, authz }: RouteDefinitionParams) { try { const { body: elasticsearchRoles, - } = await context.core.elasticsearch.client.asCurrentUser.security.getRole< - Record - >({ name: request.params.name }); + } = await context.core.elasticsearch.client.asCurrentUser.security.getRole({ + name: request.params.name, + }); const elasticsearchRole = elasticsearchRoles[request.params.name]; if (elasticsearchRole) { return response.ok({ body: transformElasticsearchRoleToRole( + // @ts-expect-error @elastic/elasticsearch `XPackRole` type doesn't define `applications` and `transient_metadata`. elasticsearchRole, request.params.name, authz.applicationName diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts index 2994afd40f880e..4d458be4e332fb 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts @@ -26,7 +26,12 @@ export function defineGetAllRolesRoutes({ router, authz }: RouteDefinitionParams return response.ok({ body: Object.entries(elasticsearchRoles) .map(([roleName, elasticsearchRole]) => - transformElasticsearchRoleToRole(elasticsearchRole, roleName, authz.applicationName) + transformElasticsearchRoleToRole( + // @ts-expect-error @elastic/elasticsearch `XPackRole` type doesn't define `applications` and `transient_metadata`. + elasticsearchRole, + roleName, + authz.applicationName + ) ) .sort((roleA, roleB) => { if (roleA.name < roleB.name) { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts index f033b668050671..74a035cdd0cb62 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts @@ -25,7 +25,7 @@ export type ElasticsearchRole = Pick, name: string, application: string ): Role { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index aefcc0c72c6db4..09bcb6b8c505ce 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -12,7 +12,6 @@ import type { KibanaFeature } from '../../../../../features/common'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import type { RouteDefinitionParams } from '../../index'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; -import type { ElasticsearchRole } from './model'; import { getPutPayloadSchema, transformPutPayloadToElasticsearchRole } from './model'; const roleGrantsSubFeaturePrivileges = ( @@ -65,13 +64,15 @@ export function definePutRolesRoutes({ try { const { body: rawRoles, - } = await context.core.elasticsearch.client.asCurrentUser.security.getRole< - Record - >({ name: request.params.name }, { ignore: [404] }); + } = await context.core.elasticsearch.client.asCurrentUser.security.getRole( + { name: request.params.name }, + { ignore: [404] } + ); const body = transformPutPayloadToElasticsearchRole( request.body, authz.applicationName, + // @ts-expect-error @elastic/elasticsearch `XPackRole` type doesn't define `applications`. rawRoles[name] ? rawRoles[name].applications : [] ); @@ -79,6 +80,7 @@ export function definePutRolesRoutes({ getFeatures(), context.core.elasticsearch.client.asCurrentUser.security.putRole({ name: request.params.name, + // @ts-expect-error RoleIndexPrivilege is not compatible. grant is required in IndicesPrivileges.field_security body, }), ]); diff --git a/x-pack/plugins/security/server/routes/indices/get_fields.ts b/x-pack/plugins/security/server/routes/indices/get_fields.ts index 3ed7493ea1f0ea..63704682e36351 100644 --- a/x-pack/plugins/security/server/routes/indices/get_fields.ts +++ b/x-pack/plugins/security/server/routes/indices/get_fields.ts @@ -10,20 +10,6 @@ import { schema } from '@kbn/config-schema'; import { wrapIntoCustomErrorResponse } from '../../errors'; import type { RouteDefinitionParams } from '../index'; -interface FieldMappingResponse { - [indexName: string]: { - mappings: { - [fieldName: string]: { - mapping: { - [fieldName: string]: { - type: string; - }; - }; - }; - }; - }; -} - export function defineGetFieldsRoutes({ router }: RouteDefinitionParams) { router.get( { @@ -34,14 +20,12 @@ export function defineGetFieldsRoutes({ router }: RouteDefinitionParams) { try { const { body: indexMappings, - } = await context.core.elasticsearch.client.asCurrentUser.indices.getFieldMapping( - { - index: request.params.query, - fields: '*', - allow_no_indices: false, - include_defaults: true, - } - ); + } = await context.core.elasticsearch.client.asCurrentUser.indices.getFieldMapping({ + index: request.params.query, + fields: '*', + allow_no_indices: false, + include_defaults: true, + }); // The flow is the following (see response format at https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html): // 1. Iterate over all matched indices. @@ -52,7 +36,13 @@ export function defineGetFieldsRoutes({ router }: RouteDefinitionParams) { new Set( Object.values(indexMappings).flatMap((indexMapping) => { return Object.keys(indexMapping.mappings).filter((fieldName) => { - const mappingValues = Object.values(indexMapping.mappings[fieldName].mapping); + const mappingValues = Object.values( + // `FieldMapping` type from `TypeFieldMappings` --> `GetFieldMappingResponse` is not correct and + // doesn't have any properties. + (indexMapping.mappings[fieldName] as { + mapping: Record; + }).mapping + ); const hasMapping = mappingValues.length > 0; const isRuntimeField = hasMapping && mappingValues[0]?.type === 'runtime'; diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.ts b/x-pack/plugins/security/server/routes/role_mapping/get.ts index d060825a989d57..67cd8975b76ebb 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/get.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/get.ts @@ -12,10 +12,6 @@ import type { RoleMapping } from '../../../common/model'; import { wrapError } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; -interface RoleMappingsResponse { - [roleMappingName: string]: Omit; -} - export function defineRoleMappingGetRoutes(params: RouteDefinitionParams) { const { logger, router } = params; @@ -32,7 +28,7 @@ export function defineRoleMappingGetRoutes(params: RouteDefinitionParams) { const expectSingleEntity = typeof request.params.name === 'string'; try { - const roleMappingsResponse = await context.core.elasticsearch.client.asCurrentUser.security.getRoleMapping( + const roleMappingsResponse = await context.core.elasticsearch.client.asCurrentUser.security.getRoleMapping( { name: request.params.name } ); @@ -40,7 +36,8 @@ export function defineRoleMappingGetRoutes(params: RouteDefinitionParams) { return { name, ...mapping, - role_templates: (mapping.role_templates || []).map((entry) => { + // @ts-expect-error @elastic/elasticsearch `XPackRoleMapping` type doesn't define `role_templates` property. + role_templates: (mapping.role_templates || []).map((entry: RoleTemplate) => { return { ...entry, template: tryParseRoleTemplate(entry.template as string), diff --git a/x-pack/plugins/security/server/routes/users/get.ts b/x-pack/plugins/security/server/routes/users/get.ts index 28165ef32356d3..81502044ef94d2 100644 --- a/x-pack/plugins/security/server/routes/users/get.ts +++ b/x-pack/plugins/security/server/routes/users/get.ts @@ -24,9 +24,7 @@ export function defineGetUserRoutes({ router }: RouteDefinitionParams) { const username = request.params.username; const { body: users, - } = await context.core.elasticsearch.client.asCurrentUser.security.getUser< - Record - >({ + } = await context.core.elasticsearch.client.asCurrentUser.security.getUser({ username, }); diff --git a/x-pack/plugins/security/server/session_management/session_index.test.ts b/x-pack/plugins/security/server/session_management/session_index.test.ts index b5b4f644389020..11fb4ca27f5902 100644 --- a/x-pack/plugins/security/server/session_management/session_index.test.ts +++ b/x-pack/plugins/security/server/session_management/session_index.test.ts @@ -41,7 +41,7 @@ describe('Session index', () => { name: indexTemplateName, }); expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledWith({ - index: getSessionIndexTemplate(indexName).index_patterns, + index: getSessionIndexTemplate(indexName).index_patterns[0], }); } @@ -93,7 +93,7 @@ describe('Session index', () => { body: expectedIndexTemplate, }); expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith({ - index: expectedIndexTemplate.index_patterns, + index: expectedIndexTemplate.index_patterns[0], }); }); @@ -126,7 +126,7 @@ describe('Session index', () => { assertExistenceChecksPerformed(); expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith({ - index: getSessionIndexTemplate(indexName).index_patterns, + index: getSessionIndexTemplate(indexName).index_patterns[0], }); }); @@ -166,7 +166,7 @@ describe('Session index', () => { const now = 123456; beforeEach(() => { mockElasticsearchClient.deleteByQuery.mockResolvedValue( - securityMock.createApiResponse({ body: {} }) + securityMock.createApiResponse({ body: {} as any }) ); jest.spyOn(Date, 'now').mockImplementation(() => now); }); @@ -600,7 +600,10 @@ describe('Session index', () => { it('returns `null` if index is not found', async () => { mockElasticsearchClient.get.mockResolvedValue( - securityMock.createApiResponse({ statusCode: 404, body: { status: 404 } }) + securityMock.createApiResponse({ + statusCode: 404, + body: { _index: 'my-index', _type: '_doc', _id: '0', found: false }, + }) ); await expect(sessionIndex.get('some-sid')).resolves.toBeNull(); @@ -608,7 +611,10 @@ describe('Session index', () => { it('returns `null` if session index value document is not found', async () => { mockElasticsearchClient.get.mockResolvedValue( - securityMock.createApiResponse({ body: { status: 200, found: false } }) + securityMock.createApiResponse({ + statusCode: 200, + body: { _index: 'my-index', _type: '_doc', _id: '0', found: false }, + }) ); await expect(sessionIndex.get('some-sid')).resolves.toBeNull(); @@ -625,9 +631,12 @@ describe('Session index', () => { mockElasticsearchClient.get.mockResolvedValue( securityMock.createApiResponse({ + statusCode: 200, body: { found: true, - status: 200, + _index: 'my-index', + _type: '_doc', + _id: '0', _source: indexDocumentSource, _primary_term: 1, _seq_no: 456, @@ -670,7 +679,17 @@ describe('Session index', () => { it('properly stores session value in the index', async () => { mockElasticsearchClient.create.mockResolvedValue( - securityMock.createApiResponse({ body: { _primary_term: 321, _seq_no: 654 } }) + securityMock.createApiResponse({ + body: { + _shards: { total: 1, failed: 0, successful: 1, skipped: 0 }, + _index: 'my-index', + _id: 'W0tpsmIBdwcYyG50zbta', + _version: 1, + _primary_term: 321, + _seq_no: 654, + result: 'created', + }, + }) ); const sid = 'some-long-sid'; @@ -708,7 +727,7 @@ describe('Session index', () => { await expect(sessionIndex.update(sessionIndexMock.createValue())).rejects.toBe(failureReason); }); - it('refetches latest session value if update fails due to conflict', async () => { + it('re-fetches latest session value if update fails due to conflict', async () => { const latestSessionValue = { usernameHash: 'some-username-hash', provider: { type: 'basic', name: 'basic1' }, @@ -719,17 +738,31 @@ describe('Session index', () => { mockElasticsearchClient.get.mockResolvedValue( securityMock.createApiResponse({ + statusCode: 200, body: { - found: true, - status: 200, + _index: 'my-index', + _type: '_doc', + _id: '0', _source: latestSessionValue, _primary_term: 321, _seq_no: 654, + found: true, }, }) ); mockElasticsearchClient.index.mockResolvedValue( - securityMock.createApiResponse({ statusCode: 409, body: { status: 409 } }) + securityMock.createApiResponse({ + statusCode: 409, + body: { + _shards: { total: 1, failed: 0, successful: 1, skipped: 0 }, + _index: 'my-index', + _id: 'W0tpsmIBdwcYyG50zbta', + _version: 1, + _primary_term: 321, + _seq_no: 654, + result: 'updated', + }, + }) ); const sid = 'some-long-sid'; @@ -763,7 +796,18 @@ describe('Session index', () => { it('properly stores session value in the index', async () => { mockElasticsearchClient.index.mockResolvedValue( - securityMock.createApiResponse({ body: { _primary_term: 321, _seq_no: 654, status: 200 } }) + securityMock.createApiResponse({ + statusCode: 200, + body: { + _shards: { total: 1, failed: 0, successful: 1, skipped: 0 }, + _index: 'my-index', + _id: 'W0tpsmIBdwcYyG50zbta', + _version: 1, + _primary_term: 321, + _seq_no: 654, + result: 'created', + }, + }) ); const sid = 'some-long-sid'; diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index 1b5c820ec47106..d7a4c3e2520bf2 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -39,7 +39,7 @@ const SESSION_INDEX_TEMPLATE_VERSION = 1; */ export function getSessionIndexTemplate(indexName: string) { return Object.freeze({ - index_patterns: indexName, + index_patterns: [indexName], order: 1000, settings: { index: { @@ -52,7 +52,7 @@ export function getSessionIndexTemplate(indexName: string) { }, }, mappings: { - dynamic: 'strict', + dynamic: 'strict' as 'strict', properties: { usernameHash: { type: 'keyword' }, provider: { properties: { name: { type: 'keyword' }, type: { type: 'keyword' } } }, @@ -151,13 +151,16 @@ export class SessionIndex { */ async get(sid: string) { try { - const { body: response } = await this.options.elasticsearchClient.get( + const { + body: response, + statusCode, + } = await this.options.elasticsearchClient.get( { id: sid, index: this.indexName }, { ignore: [404] } ); const docNotFound = response.found === false; - const indexNotFound = response.status === 404; + const indexNotFound = statusCode === 404; if (docNotFound || indexNotFound) { this.options.logger.debug('Cannot find session value with the specified ID.'); return null; @@ -215,7 +218,7 @@ export class SessionIndex { async update(sessionValue: Readonly) { const { sid, metadata, ...sessionValueToStore } = sessionValue; try { - const { body: response } = await this.options.elasticsearchClient.index( + const { body: response, statusCode } = await this.options.elasticsearchClient.index( { id: sid, index: this.indexName, @@ -230,7 +233,7 @@ export class SessionIndex { // We don't want to override changes that were made after we fetched session value or // re-create it if has been deleted already. If we detect such a case we discard changes and // return latest copy of the session value instead or `null` if doesn't exist anymore. - const sessionIndexValueUpdateConflict = response.status === 409; + const sessionIndexValueUpdateConflict = statusCode === 409; if (sessionIndexValueUpdateConflict) { this.options.logger.debug( 'Cannot update session value due to conflict, session either does not exist or was already updated.' @@ -457,7 +460,7 @@ export class SessionIndex { { ignore: [409, 404] } ); - if (response.deleted > 0) { + if (response.deleted! > 0) { this.options.logger.debug( `Cleaned up ${response.deleted} invalid or expired session values.` ); diff --git a/x-pack/plugins/security_solution/common/search_strategy/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/common/index.ts index 05c47605b4951c..e27e9b5173fd59 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/common/index.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { IEsSearchResponse } from '../../../../../../src/plugins/data/common'; export type Maybe = T | null; @@ -70,10 +70,7 @@ export interface PaginationInputPaginated { querySize: number; } -export interface DocValueFields { - field: string; - format?: string | null; -} +export type DocValueFields = estypes.DocValueField; export interface Explanation { value: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index 319933be5c79e5..2160ed6170e29e 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { IEsSearchRequest } from '../../../../../../src/plugins/data/common'; import { ESQuery } from '../../typed_json'; import { @@ -64,13 +64,7 @@ import { MatrixHistogramRequestOptions, MatrixHistogramStrategyResponse, } from './matrix_histogram'; -import { - DocValueFields, - TimerangeInput, - SortField, - PaginationInput, - PaginationInputPaginated, -} from '../common'; +import { TimerangeInput, SortField, PaginationInput, PaginationInputPaginated } from '../common'; export * from './hosts'; export * from './matrix_histogram'; @@ -87,7 +81,7 @@ export interface RequestBasicOptions extends IEsSearchRequest { timerange: TimerangeInput; filterQuery: ESQuery | string | undefined; defaultIndex: string[]; - docValueFields?: DocValueFields[]; + docValueFields?: estypes.DocValueField[]; factoryQueryType?: FactoryQueryTypes; } diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index 6daced08be2823..f66b060b166bf8 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -94,7 +94,7 @@ export const getDocValueFields = memoizeOne( ...accumulator, { field: field.name, - format: field.format, + format: field.format ? field.format : undefined, }, ]; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity.ts index cc8b99875758d1..a266cff73344f4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity.ts @@ -6,9 +6,8 @@ */ import _ from 'lodash'; -import { RequestHandler, SearchResponse } from 'kibana/server'; +import { RequestHandler } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; -import { ApiResponse } from '@elastic/elasticsearch'; import { validateEntities } from '../../../../common/endpoint/schema/resolver'; import { ResolverEntityIndex, ResolverSchema } from '../../../../common/endpoint/types'; @@ -88,9 +87,7 @@ export function handleEntities(): RequestHandler - > = await context.core.elasticsearch.client.asCurrentUser.search({ + const queryResponse = await context.core.elasticsearch.client.asCurrentUser.search({ ignore_unavailable: true, index: indices, body: { @@ -102,6 +99,7 @@ export function handleEntities(): RequestHandler { const parsedFilters = EventsQuery.buildFilters(filter); - const response: ApiResponse< - SearchResponse - > = await client.asCurrentUser.search(this.buildSearch(parsedFilters)); + const response = await client.asCurrentUser.search( + this.buildSearch(parsedFilters) + ); + // @ts-expect-error @elastic/elasticsearch _source is optional return response.body.hits.hits.map((hit) => hit._source); } } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts index 8edfa81efbca66..bf9b3ce6aa8f3a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; -import { ApiResponse } from '@elastic/elasticsearch'; +import type { ApiResponse, estypes } from '@elastic/elasticsearch'; import { IScopedClusterClient } from 'src/core/server'; import { FieldsObject, ResolverSchema } from '../../../../../../common/endpoint/types'; import { JsonObject, JsonValue } from '../../../../../../../../../src/plugins/kibana_utils/common'; @@ -198,7 +197,7 @@ export class DescendantsQuery { return []; } - let response: ApiResponse>; + let response: ApiResponse>; if (this.schema.ancestry) { response = await client.asCurrentUser.search({ body: this.queryWithAncestryArray(validNodes, this.schema.ancestry, limit), @@ -219,6 +218,7 @@ export class DescendantsQuery { * * So the schema fields are flattened ('process.parent.entity_id') */ + // @ts-expect-error @elastic/elasticsearch _source is optional return response.body.hits.hits.map((hit) => hit.fields); } } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts index d04f0d8ee3bc99..f9780d1469756e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts @@ -5,8 +5,6 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; -import { ApiResponse } from '@elastic/elasticsearch'; import { IScopedClusterClient } from 'src/core/server'; import { FieldsObject, ResolverSchema } from '../../../../../../common/endpoint/types'; import { JsonObject, JsonValue } from '../../../../../../../../../src/plugins/kibana_utils/common'; @@ -93,7 +91,7 @@ export class LifecycleQuery { return []; } - const response: ApiResponse> = await client.asCurrentUser.search({ + const response = await client.asCurrentUser.search({ body: this.query(validNodes), index: this.indexPatterns, }); @@ -106,6 +104,7 @@ export class LifecycleQuery { * * So the schema fields are flattened ('process.parent.entity_id') */ + // @ts-expect-error @elastic/elasticsearch _source is optional return response.body.hits.hits.map((hit) => hit.fields); } } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts index 5d9db0c148d424..24c97ad88b26ae 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts @@ -5,8 +5,6 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; -import { ApiResponse } from '@elastic/elasticsearch'; import { IScopedClusterClient } from 'src/core/server'; import { JsonObject } from '../../../../../../../../../src/plugins/kibana_utils/common'; import { EventStats, ResolverSchema } from '../../../../../../common/endpoint/types'; @@ -125,11 +123,12 @@ export class StatsQuery { } // leaving unknown here because we don't actually need the hits part of the body - const response: ApiResponse> = await client.asCurrentUser.search({ + const response = await client.asCurrentUser.search({ body: this.query(nodes), index: this.indexPatterns, }); + // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response return response.body.aggregations?.ids?.buckets.reduce( (cummulative: Record, bucket: CategoriesAgg) => ({ ...cummulative, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts index 98a8f8c28d30de..48bbbdcbf3a48d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts @@ -27,6 +27,7 @@ export const deleteAllIndex = async ( { ignore: [404] } ); + // @ts-expect-error status doesn't exist on response if (resp.status === 404) { return true; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/index/get_index_exists.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/index/get_index_exists.test.ts index 488ba0dab0b973..abe587ec825c03 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/index/get_index_exists.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/index/get_index_exists.test.ts @@ -21,6 +21,7 @@ describe('get_index_exists', () => { test('it should return a true if you have _shards', async () => { const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.search.mockReturnValue( + // @ts-expect-error not full interface elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } }) ); const indexExists = await getIndexExists(esClient, 'some-index'); @@ -30,6 +31,7 @@ describe('get_index_exists', () => { test('it should return a false if you do NOT have _shards', async () => { const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.search.mockReturnValue( + // @ts-expect-error not full interface elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } }) ); const indexExists = await getIndexExists(esClient, 'some-index'); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/index/get_index_exists.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/index/get_index_exists.ts index b86b58897ee621..cc7f22064572c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/index/get_index_exists.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/index/get_index_exists.ts @@ -15,8 +15,10 @@ export const getIndexExists = async ( const { body: response } = await esClient.search({ index, size: 0, - terminate_after: 1, allow_no_indices: true, + body: { + terminate_after: 1, + }, }); return response._shards.total > 0; } catch (err) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration.ts index d1d43ded321019..bf091ef2508afc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration.ts @@ -73,7 +73,7 @@ export const createMigration = async ({ return { destinationIndex: migrationIndex, sourceIndex: index, - taskId: response.body.task, + taskId: String(response.body.task!), version, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_index_versions_by_index.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_index_versions_by_index.test.ts index 32b3ccbf17b57d..8e99cb32390e1f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_index_versions_by_index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_index_versions_by_index.test.ts @@ -43,9 +43,9 @@ describe('getIndexVersionsByIndex', () => { }); it('properly transforms the response', async () => { - // @ts-expect-error mocking only what we need esClient.indices.getMapping.mockResolvedValue({ body: { + // @ts-expect-error mocking only what we need index1: { mappings: { _meta: { version: 3 } } }, }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.test.ts index 24d8fab9c50de9..2cdfa2c13e7b59 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.test.ts @@ -16,8 +16,8 @@ describe('getSignalVersionsByIndex', () => { }); it('properly transforms the elasticsearch aggregation', async () => { - // @ts-expect-error mocking only what we need esClient.search.mockResolvedValueOnce({ + // @ts-expect-error mocking only what we need body: { aggregations: { signals_indices: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.ts index ea064a93978796..784164e430ff0f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.ts @@ -50,7 +50,7 @@ export const getSignalVersionsByIndex = async ({ esClient: ElasticsearchClient; index: string[]; }): Promise => { - const { body } = await esClient.search({ + const response = await esClient.search({ index, size: 0, body: { @@ -71,6 +71,9 @@ export const getSignalVersionsByIndex = async ({ }, }, }); + + // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response + const body = response.body as SignalVersionsAggResponse; const indexBuckets = body.aggregations.signals_indices.buckets; return index.reduce((agg, _index) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signals_indices_in_range.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signals_indices_in_range.ts index 3135bfec4e0e75..3c9132fc81279a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signals_indices_in_range.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signals_indices_in_range.ts @@ -42,7 +42,7 @@ export const getSignalsIndicesInRange = async ({ return []; } - const response = await esClient.search({ + const response = await esClient.search({ index, body: { aggs: { @@ -60,6 +60,7 @@ export const getSignalsIndicesInRange = async ({ '@timestamp': { gte: from, lte: 'now', + // @ts-expect-error format doesn't exist in RangeQuery format: 'strict_date_optional_time', }, }, @@ -71,5 +72,7 @@ export const getSignalsIndicesInRange = async ({ }, }); - return response.body.aggregations.indexes.buckets.map((bucket) => bucket.key); + // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response + const body = response.body as IndexesResponse; + return body.aggregations.indexes.buckets.map((bucket) => bucket.key); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/get_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/get_signals.ts index b411ac2c69ef28..398438234ed71d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/get_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/get_signals.ts @@ -6,7 +6,7 @@ */ import { ElasticsearchClient } from 'kibana/server'; -import { SignalSearchResponse } from '../signals/types'; +import type { SignalSearchResponse, SignalSource } from '../signals/types'; import { buildSignalsSearchQuery } from './build_signals_query'; interface GetSignalsParams { @@ -38,7 +38,7 @@ export const getSignals = async ({ size, }); - const { body: result } = await esClient.search(query); + const { body: result } = await esClient.search(query); return result; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index a40cb998eb408f..799fb3814f1f0d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -91,6 +91,7 @@ export const rulesNotificationAlertType = ({ signalsCount, resultsLink, ruleParams, + // @ts-expect-error @elastic/elasticsearch _source is optional signals, }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_index_version.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_index_version.ts index 5c626cbe33ac16..b333ef999a6aef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_index_version.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_index_version.ts @@ -5,26 +5,15 @@ * 2.0. */ -import { ApiResponse } from '@elastic/elasticsearch'; import { get } from 'lodash'; import { ElasticsearchClient } from '../../../../../../../../src/core/server'; import { readIndex } from '../../index/read_index'; -interface IndicesAliasResponse { - [index: string]: IndexAliasResponse; -} - -interface IndexAliasResponse { - aliases: { - [aliasName: string]: Record; - }; -} - export const getIndexVersion = async ( esClient: ElasticsearchClient, index: string ): Promise => { - const { body: indexAlias }: ApiResponse = await esClient.indices.getAlias({ + const { body: indexAlias } = await esClient.indices.getAlias({ index, }); const writeIndex = Object.keys(indexAlias).find( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 4106d7532f7bb1..8c9b19a0929d2e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -6,7 +6,7 @@ */ import { set } from '@elastic/safer-lodash-set'; -import { +import type { SignalSourceHit, SignalSearchResponse, BulkResponse, @@ -261,7 +261,9 @@ export const sampleWrappedSignalHit = (): WrappedSignalHit => { export const sampleDocWithAncestors = (): SignalSearchResponse => { const sampleDoc = sampleDocNoSortId(); delete sampleDoc.sort; + // @ts-expect-error @elastic/elasticsearch _source is optional delete sampleDoc._source.source; + // @ts-expect-error @elastic/elasticsearch _source is optional sampleDoc._source.signal = { parent: { id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts index 362c368881b37f..708aefc4d86147 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -33,6 +33,7 @@ describe('buildBulkBody', () => { test('bulk body builds well-defined body', () => { const sampleParams = sampleRuleAlertParams(); const doc = sampleDocNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional delete doc._source.source; const fakeSignalSourceHit = buildBulkBody({ doc, @@ -143,6 +144,7 @@ describe('buildBulkBody', () => { const baseDoc = sampleDocNoSortId(); const doc: SignalSourceHit = { ...baseDoc, + // @ts-expect-error @elastic/elasticsearch _source is optional _source: { ...baseDoc._source, threshold_result: { @@ -155,6 +157,7 @@ describe('buildBulkBody', () => { }, }, }; + // @ts-expect-error @elastic/elasticsearch _source is optional delete doc._source.source; const fakeSignalSourceHit = buildBulkBody({ doc, @@ -271,7 +274,9 @@ describe('buildBulkBody', () => { test('bulk body builds original_event if it exists on the event to begin with', () => { const sampleParams = sampleRuleAlertParams(); const doc = sampleDocNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional delete doc._source.source; + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.event = { action: 'socket_opened', module: 'system', @@ -384,7 +389,9 @@ describe('buildBulkBody', () => { test('bulk body builds original_event if it exists on the event to begin with but no kind information', () => { const sampleParams = sampleRuleAlertParams(); const doc = sampleDocNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional delete doc._source.source; + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.event = { action: 'socket_opened', module: 'system', @@ -495,7 +502,9 @@ describe('buildBulkBody', () => { test('bulk body builds original_event if it exists on the event to begin with with only kind information', () => { const sampleParams = sampleRuleAlertParams(); const doc = sampleDocNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional delete doc._source.source; + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.event = { kind: 'event', }; @@ -599,6 +608,7 @@ describe('buildBulkBody', () => { test('bulk body builds "original_signal" if it exists already as a numeric', () => { const sampleParams = sampleRuleAlertParams(); const sampleDoc = sampleDocNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional delete sampleDoc._source.source; const doc = ({ ...sampleDoc, @@ -702,6 +712,7 @@ describe('buildBulkBody', () => { test('bulk body builds "original_signal" if it exists already as an object', () => { const sampleParams = sampleRuleAlertParams(); const sampleDoc = sampleDocNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional delete sampleDoc._source.source; const doc = ({ ...sampleDoc, @@ -1053,6 +1064,7 @@ describe('buildSignalFromSequence', () => { describe('buildSignalFromEvent', () => { test('builds a basic signal from a single event', () => { const ancestor = sampleDocWithAncestors().hits.hits[0]; + // @ts-expect-error @elastic/elasticsearch _source is optional delete ancestor._source.source; const ruleSO = sampleRuleSO(); const signal = buildSignalFromEvent(ancestor, ruleSO, true); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts index fb72bf9c59b4ec..3a6cbf5ccd34bd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts @@ -73,6 +73,7 @@ export const buildBulkBody = ({ ...buildSignal([doc], rule), ...additionalSignalFields(doc), }; + // @ts-expect-error @elastic/elasticsearch _source is optional delete doc._source.threshold_result; const event = buildEventTypeSignal(doc); const signalHit: SignalHit = { @@ -163,7 +164,8 @@ export const buildSignalFromEvent = ( applyOverrides: boolean ): SignalHit => { const rule = applyOverrides - ? buildRuleWithOverrides(ruleSO, event._source) + ? // @ts-expect-error @elastic/elasticsearch _source is optional + buildRuleWithOverrides(ruleSO, event._source) : buildRuleWithoutOverrides(ruleSO); const signal: Signal = { ...buildSignal([event], rule), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts index 0ae81770e83c29..185c1654429210 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts @@ -16,6 +16,7 @@ describe('buildEventTypeSignal', () => { test('it returns the event appended of kind signal if it does not exist', () => { const doc = sampleDocNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional delete doc._source.event; const eventType = buildEventTypeSignal(doc); const expected: object = { kind: 'signal' }; @@ -24,6 +25,7 @@ describe('buildEventTypeSignal', () => { test('it returns the event appended of kind signal if it is an empty object', () => { const doc = sampleDocNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.event = {}; const eventType = buildEventTypeSignal(doc); const expected: object = { kind: 'signal' }; @@ -32,6 +34,7 @@ describe('buildEventTypeSignal', () => { test('it returns the event with kind signal and other properties if they exist', () => { const doc = sampleDocNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.event = { action: 'socket_opened', module: 'system', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts index bc267ba59b9c3e..374788f31a359c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts @@ -4,12 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { BaseSignalHit } from './types'; export const buildEventTypeSignal = (doc: BaseSignalHit): object => { - if (doc._source.event != null && doc._source.event instanceof Object) { - return { ...doc._source.event, kind: 'signal' }; + if (doc._source?.event != null && doc._source?.event instanceof Object) { + return { ...doc._source!.event, kind: 'signal' }; } else { return { kind: 'signal' }; } @@ -25,5 +24,5 @@ export const buildEventTypeSignal = (doc: BaseSignalHit): object => { * @param doc The document which might be a signal or it might be a regular log */ export const isEventTypeSignal = (doc: BaseSignalHit): boolean => { - return doc._source.signal?.rule?.id != null && typeof doc._source.signal?.rule?.id === 'string'; + return doc._source?.signal?.rule?.id != null && typeof doc._source?.signal?.rule?.id === 'string'; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts index bce9adc9f0f882..e086c862262c16 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts @@ -4,18 +4,18 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { SortOrderOrUndefined, TimestampOverrideOrUndefined, } from '../../../../common/detection_engine/schemas/common/schemas'; interface BuildEventsSearchQuery { - aggregations?: unknown; + aggregations?: Record; index: string[]; from: string; to: string; - filter: unknown; + filter?: estypes.QueryContainer; size: number; sortOrder?: SortOrderOrUndefined; searchAfterSortId: string | number | undefined; @@ -48,7 +48,7 @@ export const buildEventsSearchQuery = ({ ? timestampOverride : '@timestamp'; - const rangeFilter: unknown[] = [ + const rangeFilter: estypes.QueryContainer[] = [ { range: { [sortField]: { @@ -70,7 +70,9 @@ export const buildEventsSearchQuery = ({ }, }); } - const filterWithTime = [filter, { bool: { filter: rangeFilter } }]; + // @ts-expect-error undefined in not assignable to QueryContainer + // but tests contain undefined, so I suppose it's desired behaviour + const filterWithTime: estypes.QueryContainer[] = [filter, { bool: { filter: rangeFilter } }]; const searchQuery = { allow_no_indices: true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts index 48e04df3704ab1..757e6728f244ed 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts @@ -417,6 +417,7 @@ describe('buildRuleWithOverrides', () => { query: 'host.name: Braden', }, ]; + // @ts-expect-error @elastic/elasticsearch _source is optional const rule = buildRuleWithOverrides(ruleSO, sampleDocNoSortId()._source); const expected: RulesSchema = { ...expectedRule(), @@ -433,6 +434,7 @@ describe('buildRuleWithOverrides', () => { `${INTERNAL_RULE_ID_KEY}:rule-1`, `${INTERNAL_IMMUTABLE_KEY}:true`, ]; + // @ts-expect-error @elastic/elasticsearch _source is optional const rule = buildRuleWithOverrides(ruleSO, sampleDocNoSortId()._source); expect(rule).toEqual(expectedRule()); }); @@ -440,6 +442,7 @@ describe('buildRuleWithOverrides', () => { test('it applies rule name override in buildRule', () => { const ruleSO = sampleRuleSO(); ruleSO.attributes.params.ruleNameOverride = 'someKey'; + // @ts-expect-error @elastic/elasticsearch _source is optional const rule = buildRuleWithOverrides(ruleSO, sampleDocNoSortId()._source); const expected = { ...expectedRule(), @@ -465,7 +468,9 @@ describe('buildRuleWithOverrides', () => { }, ]; const doc = sampleDocNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.new_risk_score = newRiskScore; + // @ts-expect-error @elastic/elasticsearch _source is optional const rule = buildRuleWithOverrides(ruleSO, doc._source); const expected = { ...expectedRule(), @@ -490,6 +495,7 @@ describe('buildRuleWithOverrides', () => { }, ]; const doc = sampleDocSeverity(Number(eventSeverity)); + // @ts-expect-error @elastic/elasticsearch _source is optional const rule = buildRuleWithOverrides(ruleSO, doc._source); const expected = { ...expectedRule(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts index 0681a5dddb127a..7755f2af70d844 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts @@ -47,18 +47,21 @@ export const buildRule = ({ throttle, }: BuildRuleParams): RulesSchema => { const { riskScore, riskScoreMeta } = buildRiskScoreFromMapping({ + // @ts-expect-error @elastic/elasticsearch _source is optional eventSource: doc._source, riskScore: ruleParams.riskScore, riskScoreMapping: ruleParams.riskScoreMapping, }); const { severity, severityMeta } = buildSeverityFromMapping({ + // @ts-expect-error @elastic/elasticsearch _source is optional eventSource: doc._source, severity: ruleParams.severity, severityMapping: ruleParams.severityMapping, }); const { ruleName, ruleNameMeta } = buildRuleNameFromMapping({ + // @ts-expect-error @elastic/elasticsearch _source is optional eventSource: doc._source, ruleName: name, ruleNameMapping: ruleParams.ruleNameOverride, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts index 98b0d41bb0bf8a..6408b5fe9de103 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts @@ -28,6 +28,7 @@ describe('buildSignal', () => { test('it builds a signal as expected without original_event if event does not exist', () => { const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + // @ts-expect-error @elastic/elasticsearch _source is optional delete doc._source.event; const rule = getRulesSchemaMock(); const signal = { @@ -104,6 +105,7 @@ describe('buildSignal', () => { test('it builds a signal as expected with original_event if is present', () => { const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.event = { action: 'socket_opened', dataset: 'socket', @@ -191,6 +193,7 @@ describe('buildSignal', () => { test('it builds a ancestor correctly if the parent does not exist', () => { const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.event = { action: 'socket_opened', dataset: 'socket', @@ -209,12 +212,14 @@ describe('buildSignal', () => { test('it builds a ancestor correctly if the parent does exist', () => { const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.event = { action: 'socket_opened', dataset: 'socket', kind: 'event', module: 'system', }; + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.signal = { parents: [ { @@ -250,6 +255,7 @@ describe('buildSignal', () => { test('it builds a signal ancestor correctly if the parent does not exist', () => { const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.event = { action: 'socket_opened', dataset: 'socket', @@ -270,12 +276,14 @@ describe('buildSignal', () => { test('it builds a signal ancestor correctly if the parent does exist', () => { const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.event = { action: 'socket_opened', dataset: 'socket', kind: 'event', module: 'system', }; + // @ts-expect-error @elastic/elasticsearch _source is optional doc._source.signal = { parents: [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts index 78ff0e8e1e5dd8..237536a99c0f0e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts @@ -17,15 +17,15 @@ import { Signal, Ancestor, BaseSignalHit, ThresholdResult } from './types'; * @param doc The parent signal or event */ export const buildParent = (doc: BaseSignalHit): Ancestor => { - if (doc._source.signal != null) { + if (doc._source?.signal != null) { return { - rule: doc._source.signal.rule.id, + rule: doc._source?.signal.rule.id, id: doc._id, type: 'signal', index: doc._index, // We first look for signal.depth and use that if it exists. If it doesn't exist, this should be a pre-7.10 signal // and should have signal.parent.depth instead. signal.parent.depth in this case is treated as equivalent to signal.depth. - depth: doc._source.signal.depth ?? doc._source.signal.parent?.depth ?? 1, + depth: doc._source?.signal.depth ?? doc._source?.signal.parent?.depth ?? 1, }; } else { return { @@ -44,7 +44,7 @@ export const buildParent = (doc: BaseSignalHit): Ancestor => { */ export const buildAncestors = (doc: BaseSignalHit): Ancestor[] => { const newAncestor = buildParent(doc); - const existingAncestors = doc._source.signal?.ancestors; + const existingAncestors = doc._source?.signal?.ancestors; if (existingAncestors != null) { return [...existingAncestors, newAncestor]; } else { @@ -59,6 +59,7 @@ export const buildAncestors = (doc: BaseSignalHit): Ancestor[] => { * @param doc The source index doc to a signal. */ export const removeClashes = (doc: BaseSignalHit): BaseSignalHit => { + // @ts-expect-error @elastic/elasticsearch _source is optional const { signal, ...noSignal } = doc._source; if (signal == null || isEventTypeSignal(doc)) { return doc; @@ -105,16 +106,17 @@ const isThresholdResult = (thresholdResult: SearchTypes): thresholdResult is Thr * @param doc The parent signal/event of the new signal to be built. */ export const additionalSignalFields = (doc: BaseSignalHit) => { - const thresholdResult = doc._source.threshold_result; + const thresholdResult = doc._source?.threshold_result; if (thresholdResult != null && !isThresholdResult(thresholdResult)) { throw new Error(`threshold_result failed to validate: ${thresholdResult}`); } return { parent: buildParent(removeClashes(doc)), + // @ts-expect-error @elastic/elasticsearch _source is optional original_time: doc._source['@timestamp'], // This field has already been replaced with timestampOverride, if provided. - original_event: doc._source.event ?? undefined, + original_event: doc._source?.event ?? undefined, threshold_result: thresholdResult, original_signal: - doc._source.signal != null && !isEventTypeSignal(doc) ? doc._source.signal : undefined, + doc._source?.signal != null && !isEventTypeSignal(doc) ? doc._source?.signal : undefined, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts index c6a8c211417b8c..a5e05d07ee1e17 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { flow, omit } from 'lodash/fp'; import set from 'set-value'; @@ -19,7 +19,6 @@ import { RuleTypeParams, RefreshTypes } from '../types'; import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create'; import { AnomalyResults, Anomaly } from '../../machine_learning'; import { BuildRuleMessage } from './rule_messages'; -import { SearchResponse } from '../../types'; interface BulkCreateMlSignalsParams { actions: RuleAlertAction[]; @@ -72,12 +71,18 @@ export const transformAnomalyFieldsToEcs = (anomaly: Anomaly): EcsAnomaly => { return flow(omitDottedFields, setNestedFields, setTimestamp)(anomaly); }; -const transformAnomalyResultsToEcs = (results: AnomalyResults): SearchResponse => { +const transformAnomalyResultsToEcs = ( + results: AnomalyResults +): estypes.SearchResponse => { const transformedHits = results.hits.hits.map(({ _source, ...rest }) => ({ ...rest, - _source: transformAnomalyFieldsToEcs(_source), + _source: transformAnomalyFieldsToEcs( + // @ts-expect-error @elastic/elasticsearch _source is optional + _source + ), })); + // @ts-expect-error Anomaly is not assignable to EcsAnomaly return { ...results, hits: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events.ts index 421ed91278f4cb..a50d40e33a7171 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events.ts @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { SearchResponse } from '../../../types'; +import type { estypes } from '@elastic/elasticsearch'; import { FilterEventsOptions } from './types'; /** @@ -17,7 +16,7 @@ import { FilterEventsOptions } from './types'; export const filterEvents = ({ events, fieldAndSetTuples, -}: FilterEventsOptions): SearchResponse['hits']['hits'] => { +}: FilterEventsOptions): Array> => { return events.filter((item) => { return fieldAndSetTuples .map((tuple) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events_against_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events_against_list.ts index bb8fc5afb1ddfc..1320122626bfde 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events_against_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events_against_list.ts @@ -4,13 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { ExceptionListItemSchema, entriesList } from '../../../../../../lists/common/schemas'; import { hasLargeValueList } from '../../../../../common/detection_engine/utils'; import { FilterEventsAgainstListOptions } from './types'; import { filterEvents } from './filter_events'; import { createFieldAndSetTuples } from './create_field_and_set_tuples'; -import { SearchResponse } from '../../../types'; /** * Filters events against a large value based list. It does this through these @@ -39,7 +38,7 @@ export const filterEventsAgainstList = async ({ logger, eventSearchResult, buildRuleMessage, -}: FilterEventsAgainstListOptions): Promise> => { +}: FilterEventsAgainstListOptions): Promise> => { try { const atLeastOneLargeValueList = exceptionsList.some(({ entries }) => hasLargeValueList(entries) @@ -56,9 +55,9 @@ export const filterEventsAgainstList = async ({ return listItem.entries.every((entry) => entriesList.is(entry)); }); - const res = await valueListExceptionItems.reduce['hits']['hits']>>( + const res = await valueListExceptionItems.reduce>>>( async ( - filteredAccum: Promise['hits']['hits']>, + filteredAccum: Promise>>, exceptionItem: ExceptionListItemSchema ) => { const events = await filteredAccum; @@ -76,7 +75,7 @@ export const filterEventsAgainstList = async ({ ); return filteredEvents; }, - Promise.resolve['hits']['hits']>(eventSearchResult.hits.hits) + Promise.resolve>>(eventSearchResult.hits.hits) ); return { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/types.ts index d3bae848ab2a74..e1618d217d0dc0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/types.ts @@ -4,24 +4,23 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { Logger } from 'src/core/server'; import { ListClient } from '../../../../../../lists/server'; import { BuildRuleMessage } from '../rule_messages'; import { ExceptionListItemSchema, Type } from '../../../../../../lists/common/schemas'; -import { SearchResponse } from '../../../types'; export interface FilterEventsAgainstListOptions { listClient: ListClient; exceptionsList: ExceptionListItemSchema[]; logger: Logger; - eventSearchResult: SearchResponse; + eventSearchResult: estypes.SearchResponse; buildRuleMessage: BuildRuleMessage; } export interface CreateSetToFilterAgainstOptions { - events: SearchResponse['hits']['hits']; + events: Array>; field: string; listId: string; listType: Type; @@ -31,12 +30,12 @@ export interface CreateSetToFilterAgainstOptions { } export interface FilterEventsOptions { - events: SearchResponse['hits']['hits']; + events: Array>; fieldAndSetTuples: FieldSet[]; } export interface CreateFieldAndSetTuplesOptions { - events: SearchResponse['hits']['hits']; + events: Array>; exceptionItem: ExceptionListItemSchema; listClient: ListClient; logger: Logger; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.test.ts index c21b4df1ac2c7f..88ce9de15cff80 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.test.ts @@ -195,6 +195,7 @@ interface TestCase { function testIt({ fieldValue, scoreDefault, scoreMapping, expected }: TestCase) { const result = buildRiskScoreFromMapping({ + // @ts-expect-error @elastic/elasticsearch _source is optional eventSource: sampleDocRiskScore(fieldValue)._source, riskScore: scoreDefault, riskScoreMapping: scoreMapping, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_rule_name_from_mapping.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_rule_name_from_mapping.test.ts index 23e5aecc5c5536..b6281b637d4340 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_rule_name_from_mapping.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_rule_name_from_mapping.test.ts @@ -15,6 +15,7 @@ describe('buildRuleNameFromMapping', () => { test('rule name defaults to provided if mapping is incomplete', () => { const ruleName = buildRuleNameFromMapping({ + // @ts-expect-error @elastic/elasticsearch _source is optional eventSource: sampleDocNoSortId()._source, ruleName: 'rule-name', ruleNameMapping: 'message', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts index 1264592331df0d..cfd4b81ae3bc8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts @@ -148,6 +148,7 @@ interface TestCase { function testIt({ fieldName, fieldValue, severityDefault, severityMapping, expected }: TestCase) { const result = buildSeverityFromMapping({ + // @ts-expect-error @elastic/elasticsearch _source is optional eventSource: sampleDocSeverity(fieldValue, fieldName)._source, severity: severityDefault, severityMapping, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index ccefa24e2018c2..6deb45095ec360 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -68,6 +68,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -88,6 +89,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -108,6 +110,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -128,6 +131,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -198,6 +202,7 @@ describe('searchAfterAndBulkCreate', () => { ) ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -218,6 +223,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -238,6 +244,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -309,6 +316,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -532,6 +540,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -617,6 +626,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -802,6 +812,7 @@ describe('searchAfterAndBulkCreate', () => { test('if returns false when singleSearchAfter throws an exception', async () => { mockService.scopedClusterClient.asCurrentUser.search .mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -906,6 +917,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -926,6 +938,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -946,6 +959,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -1010,6 +1024,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -1030,6 +1045,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -1050,6 +1066,7 @@ describe('searchAfterAndBulkCreate', () => { ); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index 1dd3a2d2173a82..cfe30a66023813 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -95,6 +95,7 @@ export const searchAfterAndBulkCreate = async ({ to: tuple.to.toISOString(), services, logger, + // @ts-expect-error please, declare a type explicitly instead of unknown filter, pageSize: Math.ceil(Math.min(tuple.maxSignals, pageSize)), timestampOverride: ruleParams.timestampOverride, @@ -104,6 +105,7 @@ export const searchAfterAndBulkCreate = async ({ // call this function setSortIdOrExit() const lastSortId = searchResultB?.hits?.hits[searchResultB.hits.hits.length - 1]?.sort; if (lastSortId != null && lastSortId.length !== 0) { + // @ts-expect-error @elastic/elasticsearch SortResults contains null not assignable to backupSortId backupSortId = lastSortId[0]; hasBackupSortId = true; } else { @@ -135,6 +137,7 @@ export const searchAfterAndBulkCreate = async ({ to: tuple.to.toISOString(), services, logger, + // @ts-expect-error please, declare a type explicitly instead of unknown filter, pageSize: Math.ceil(Math.min(tuple.maxSignals, pageSize)), timestampOverride: ruleParams.timestampOverride, @@ -155,6 +158,7 @@ export const searchAfterAndBulkCreate = async ({ const lastSortId = searchResult.hits.hits[searchResult.hits.hits.length - 1]?.sort; if (lastSortId != null && lastSortId.length !== 0) { + // @ts-expect-error @elastic/elasticsearch SortResults contains null not assignable to sortId sortId = lastSortId[0]; hasSortId = true; } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/send_telemetry_events.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/send_telemetry_events.ts index 22dc8847087ed4..f7d21adc4bea97 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/send_telemetry_events.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/send_telemetry_events.ts @@ -16,7 +16,8 @@ export interface SearchResultWithSource { } export function selectEvents(filteredEvents: SignalSearchResponse): TelemetryEvent[] { - const sources = filteredEvents.hits.hits.map(function ( + // @ts-expect-error @elastic/elasticsearch _source is optional + const sources: TelemetryEvent[] = filteredEvents.hits.hits.map(function ( obj: SearchResultWithSource ): TelemetryEvent { return obj._source; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index bcd04ed5e15cd6..930cafe57fb0a3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -6,6 +6,7 @@ */ import moment from 'moment'; +import type { estypes } from '@elastic/elasticsearch'; import { loggingSystemMock } from 'src/core/server/mocks'; import { getResult, @@ -149,12 +150,13 @@ describe('rules_notification_alert_type', () => { }, }) ); - const value: Partial = { + const value: Partial> = { statusCode: 200, body: { indices: ['index1', 'index2', 'index3', 'index4'], fields: { '@timestamp': { + // @ts-expect-error not full interface date: { indices: ['index1', 'index2', 'index3', 'index4'], searchable: true, @@ -165,7 +167,7 @@ describe('rules_notification_alert_type', () => { }, }; alertServices.scopedClusterClient.asCurrentUser.fieldCaps.mockResolvedValue( - value as ApiResponse + value as ApiResponse ); const ruleAlert = getResult(); alertServices.savedObjectsClient.get.mockResolvedValue({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts index eecedb02b26879..b9a771ac0299ea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -142,6 +142,7 @@ describe('singleBulkCreate', () => { test('create successful bulk create', async () => { const sampleParams = sampleRuleAlertParams(); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not compatible response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -179,6 +180,7 @@ describe('singleBulkCreate', () => { test('create successful bulk create with docs with no versioning', async () => { const sampleParams = sampleRuleAlertParams(); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce( + // @ts-expect-error not compatible response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ took: 100, errors: false, @@ -216,6 +218,7 @@ describe('singleBulkCreate', () => { test('create unsuccessful bulk create due to empty search results', async () => { const sampleParams = sampleRuleAlertParams(); mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValue( + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise(false) ); const { success, createdItemsCount } = await singleBulkCreate({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts index 6c791bc4d0ee3e..8a0788f6d42e6d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts @@ -12,7 +12,7 @@ import { AlertInstanceState, AlertServices, } from '../../../../../alerting/server'; -import { SignalSearchResponse, BulkResponse, SignalHit, WrappedSignalHit } from './types'; +import { SignalHit, SignalSearchResponse, WrappedSignalHit } from './types'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams, RefreshTypes } from '../types'; import { generateId, makeFloatString, errorAggregator } from './utils'; @@ -56,12 +56,12 @@ export const filterDuplicateRules = ( signalSearchResponse: SignalSearchResponse ) => { return signalSearchResponse.hits.hits.filter((doc) => { - if (doc._source.signal == null || !isEventTypeSignal(doc)) { + if (doc._source?.signal == null || !isEventTypeSignal(doc)) { return true; } else { return !( - doc._source.signal.ancestors.some((ancestor) => ancestor.rule === ruleId) || - doc._source.signal.rule.id === ruleId + doc._source?.signal.ancestors.some((ancestor) => ancestor.rule === ruleId) || + doc._source?.signal.rule.id === ruleId ); } }); @@ -158,7 +158,7 @@ export const singleBulkCreate = async ({ }), ]); const start = performance.now(); - const { body: response } = await services.scopedClusterClient.asCurrentUser.bulk({ + const { body: response } = await services.scopedClusterClient.asCurrentUser.bulk({ index: signalsIndex, refresh, body: bulkBody, @@ -244,7 +244,7 @@ export const bulkInsertSignals = async ( doc._source, ]); const start = performance.now(); - const { body: response } = await services.scopedClusterClient.asCurrentUser.bulk({ + const { body: response } = await services.scopedClusterClient.asCurrentUser.bulk({ refresh, body: bulkBody, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts index a325903c66ec02..cbffac6e7b4553 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { sampleDocSearchResultsNoSortId, mockLogger, @@ -12,7 +12,6 @@ import { } from './__mocks__/es_results'; import { singleSearchAfter } from './single_search_after'; import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; -import { ShardError } from '../../types'; import { buildRuleMessageFactory } from './rule_messages'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; @@ -69,7 +68,7 @@ describe('singleSearchAfter', () => { expect(searchErrors).toEqual([]); }); test('if singleSearchAfter will return an error array', async () => { - const errors: ShardError[] = [ + const errors: estypes.ShardFailure[] = [ { shard: 1, index: 'index-123', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts index b35c68c8deacd5..9dcec1861f15dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { performance } from 'perf_hooks'; import { AlertInstanceContext, @@ -12,7 +12,7 @@ import { AlertServices, } from '../../../../../alerting/server'; import { Logger } from '../../../../../../../src/core/server'; -import { SignalSearchResponse } from './types'; +import type { SignalSearchResponse, SignalSource } from './types'; import { BuildRuleMessage } from './rule_messages'; import { buildEventsSearchQuery } from './build_events_query'; import { createErrorsFromShard, makeFloatString } from './utils'; @@ -22,7 +22,7 @@ import { } from '../../../../common/detection_engine/schemas/common/schemas'; interface SingleSearchAfterParams { - aggregations?: unknown; + aggregations?: Record; searchAfterSortId: string | undefined; index: string[]; from: string; @@ -31,7 +31,7 @@ interface SingleSearchAfterParams { logger: Logger; pageSize: number; sortOrder?: SortOrderOrUndefined; - filter: unknown; + filter?: estypes.QueryContainer; timestampOverride: TimestampOverrideOrUndefined; buildRuleMessage: BuildRuleMessage; excludeDocsWithTimestampOverride: boolean; @@ -74,9 +74,7 @@ export const singleSearchAfter = async ({ const start = performance.now(); const { body: nextSearchAfterResult, - } = await services.scopedClusterClient.asCurrentUser.search( - searchAfterQuery - ); + } = await services.scopedClusterClient.asCurrentUser.search(searchAfterQuery); const end = performance.now(); const searchErrors = createErrorsFromShard({ errors: nextSearchAfterResult._shards.failures ?? [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.mock.ts index 266903f568792c..e39b78b4f4a447 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.mock.ts @@ -4,11 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { ThreatMapping } from '../../../../../common/detection_engine/schemas/types/threat_mapping'; import { Filter } from 'src/plugins/data/common'; -import { SearchResponse } from 'elasticsearch'; import { ThreatListDoc, ThreatListItem } from './types'; export const getThreatMappingMock = (): ThreatMapping => { @@ -62,7 +61,7 @@ export const getThreatMappingMock = (): ThreatMapping => { ]; }; -export const getThreatListSearchResponseMock = (): SearchResponse => ({ +export const getThreatListSearchResponseMock = (): estypes.SearchResponse => ({ took: 0, timed_out: false, _shards: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts index e0be48458b0493..01465729413317 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts @@ -172,6 +172,7 @@ export const createThreatSignals = async ({ language: threatLanguage, threatFilters, index: threatIndex, + // @ts-expect-error@elastic/elasticsearch SortResults might contain null searchAfter: threatList.hits.hits[threatList.hits.hits.length - 1].sort, sortField: undefined, sortOrder: undefined, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts index bc2be4ecaab329..83a3ce8cb773f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts @@ -101,7 +101,7 @@ export const enrichSignalThreatMatches = async ( return { ...signalHit, _source: { - ...signalHit._source, + ...signalHit._source!, threat: { ...threat, indicator: [...existingIndicators, ...matchedIndicators[i]], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts index a2a51d3a060c17..c3d3d6c6a99e13 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts @@ -5,15 +5,14 @@ * 2.0. */ -import { ApiResponse } from '@elastic/elasticsearch'; -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { getQueryFilter } from '../../../../../common/detection_engine/get_query_filter'; import { GetSortWithTieBreakerOptions, GetThreatListOptions, SortWithTieBreaker, ThreatListCountOptions, - ThreatListItem, + ThreatListDoc, } from './types'; /** @@ -35,7 +34,7 @@ export const getThreatList = async ({ listClient, buildRuleMessage, logger, -}: GetThreatListOptions): Promise> => { +}: GetThreatListOptions): Promise> => { const calculatedPerPage = perPage ?? MAX_PER_PAGE; if (calculatedPerPage > 10000) { throw new TypeError('perPage cannot exceed the size of 10000'); @@ -53,8 +52,9 @@ export const getThreatList = async ({ `Querying the indicator items from the index: "${index}" with searchAfter: "${searchAfter}" for up to ${calculatedPerPage} indicator items` ) ); - const { body: response } = await esClient.search>({ + const { body: response } = await esClient.search({ body: { + // @ts-expect-error ESBoolQuery is not assignale to QueryContainer query: queryFilter, fields: [ { @@ -123,12 +123,9 @@ export const getThreatListCount = async ({ index, exceptionItems ); - const { - body: response, - }: ApiResponse<{ - count: number; - }> = await esClient.count({ + const { body: response } = await esClient.count({ body: { + // @ts-expect-error ESBoolQuery is not assignale to QueryContainer query: queryFilter, }, ignore_unavailable: true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts index 0c14f906742d4d..65b59d4df07911 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts @@ -4,9 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { SearchResponse } from 'elasticsearch'; - +import type { estypes } from '@elastic/elasticsearch'; import { ListClient } from '../../../../../../lists/server'; import { Type, @@ -187,7 +185,7 @@ export interface ThreatListDoc { * This is an ECS document being returned, but the user could return or use non-ecs based * documents potentially. */ -export type ThreatListItem = SearchResponse['hits']['hits'][number]; +export type ThreatListItem = estypes.Hit; export interface ThreatIndicator { [key: string]: unknown; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts index 43158d1e897832..8e5e31cc87b4f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts @@ -7,7 +7,6 @@ import { get } from 'lodash/fp'; import set from 'set-value'; - import { normalizeThresholdField } from '../../../../../common/detection_engine/utils'; import { ThresholdNormalized, @@ -29,10 +28,10 @@ import { getThresholdTermsHash, } from '../utils'; import { BuildRuleMessage } from '../rule_messages'; -import { +import type { MultiAggBucket, - SignalSearchResponse, SignalSource, + SignalSearchResponse, ThresholdSignalHistory, } from '../types'; @@ -141,7 +140,8 @@ const getTransformedHits = ( // Recurse through the nested buckets and collect each unique combination of terms. Collect the // cardinality and document count from the leaf buckets and return a signal for each set of terms. - return getCombinations(results.aggregations[aggParts.name].buckets, 0, aggParts.field).reduce( + // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response + return getCombinations(results.aggregations![aggParts.name].buckets, 0, aggParts.field).reduce( (acc: Array>, bucket) => { const hit = bucket.topThresholdHits?.hits.hits[0]; if (hit == null) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts index 7dda263dd9f0a6..efcdb85e9b2c73 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts @@ -20,7 +20,7 @@ import { import { Logger } from '../../../../../../../../src/core/server'; import { BuildRuleMessage } from '../rule_messages'; import { singleSearchAfter } from '../single_search_after'; -import { SignalSearchResponse } from '../types'; +import type { SignalSearchResponse } from '../types'; interface FindThresholdSignalsParams { from: string; @@ -56,7 +56,7 @@ export const findThresholdSignals = async ({ sort: [ { [timestampOverride ?? '@timestamp']: { - order: 'desc', + order: 'desc' as const, }, }, ], @@ -137,6 +137,7 @@ export const findThresholdSignals = async ({ to, services, logger, + // @ts-expect-error refactor to pass type explicitly instead of unknown filter, pageSize: 1, sortOrder: 'desc', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts index 73068a73a38a3b..4dd21938690dba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts @@ -34,7 +34,7 @@ export const getThresholdBucketFilters = async ({ bucket.terms.forEach((term) => { if (term.field != null) { - (filter.bool.filter as ESFilter[]).push({ + (filter.bool!.filter as ESFilter[]).push({ term: { [term.field]: `${term.value}`, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index 013d4a07cbeb79..559c5875c90d00 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { estypes } from '@elastic/elasticsearch'; import { DslQuery, Filter } from 'src/plugins/data/common'; import moment, { Moment } from 'moment'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; @@ -17,7 +18,7 @@ import { AlertExecutorOptions, AlertServices, } from '../../../../../alerting/server'; -import { BaseSearchResponse, SearchHit, SearchResponse, TermAggregationBucket } from '../../types'; +import { BaseSearchResponse, SearchHit, TermAggregationBucket } from '../../types'; import { EqlSearchResponse, BaseHit, @@ -150,11 +151,10 @@ export interface GetResponse { _source: SearchTypes; } -export type EventSearchResponse = SearchResponse; -export type SignalSearchResponse = SearchResponse; -export type SignalSourceHit = SignalSearchResponse['hits']['hits'][number]; +export type SignalSearchResponse = estypes.SearchResponse; +export type SignalSourceHit = estypes.Hit; export type WrappedSignalHit = BaseHit; -export type BaseSignalHit = BaseHit; +export type BaseSignalHit = estypes.Hit; export type EqlSignalSearchResponse = EqlSearchResponse; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index 6d7948baa9681d..249305ebcd9a1d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -1133,6 +1133,7 @@ describe('utils', () => { test('result with error will create success: false within the result set', () => { const searchResult = sampleDocSearchResultsNoSortIdNoHits(); searchResult._shards.failed = 1; + // @ts-expect-error not full interface searchResult._shards.failures = [{ reason: { reason: 'Not a sort failure' } }]; const { success } = createSearchAfterReturnTypeFromResponse({ searchResult, @@ -1144,6 +1145,7 @@ describe('utils', () => { test('result with error will create success: false within the result set if failed is 2 or more', () => { const searchResult = sampleDocSearchResultsNoSortIdNoHits(); searchResult._shards.failed = 2; + // @ts-expect-error not full interface searchResult._shards.failures = [{ reason: { reason: 'Not a sort failure' } }]; const { success } = createSearchAfterReturnTypeFromResponse({ searchResult, @@ -1156,7 +1158,9 @@ describe('utils', () => { const searchResult = sampleDocSearchResultsNoSortIdNoHits(); searchResult._shards.failed = 2; searchResult._shards.failures = [ + // @ts-expect-error not full interface { reason: { reason: 'Not a sort failure' } }, + // @ts-expect-error not full interface { reason: { reason: 'No mapping found for [@timestamp] in order to sort on' } }, ]; const { success } = createSearchAfterReturnTypeFromResponse({ @@ -1180,7 +1184,9 @@ describe('utils', () => { const searchResult = sampleDocSearchResultsNoSortIdNoHits(); searchResult._shards.failed = 2; searchResult._shards.failures = [ + // @ts-expect-error not full interface { reason: { reason: 'No mapping found for [event.ingested] in order to sort on' } }, + // @ts-expect-error not full interface { reason: { reason: 'No mapping found for [@timestamp] in order to sort on' } }, ]; const { success } = createSearchAfterReturnTypeFromResponse({ @@ -1192,6 +1198,7 @@ describe('utils', () => { test('It will not set an invalid date time stamp from a non-existent @timestamp when the index is not 100% ECS compliant', () => { const searchResult = sampleDocSearchResultsNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional (searchResult.hits.hits[0]._source['@timestamp'] as unknown) = undefined; if (searchResult.hits.hits[0].fields != null) { (searchResult.hits.hits[0].fields['@timestamp'] as unknown) = undefined; @@ -1205,6 +1212,7 @@ describe('utils', () => { test('It will not set an invalid date time stamp from a null @timestamp when the index is not 100% ECS compliant', () => { const searchResult = sampleDocSearchResultsNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional (searchResult.hits.hits[0]._source['@timestamp'] as unknown) = null; if (searchResult.hits.hits[0].fields != null) { (searchResult.hits.hits[0].fields['@timestamp'] as unknown) = null; @@ -1218,6 +1226,7 @@ describe('utils', () => { test('It will not set an invalid date time stamp from an invalid @timestamp string', () => { const searchResult = sampleDocSearchResultsNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional (searchResult.hits.hits[0]._source['@timestamp'] as unknown) = 'invalid'; if (searchResult.hits.hits[0].fields != null) { (searchResult.hits.hits[0].fields['@timestamp'] as unknown) = ['invalid']; @@ -1233,6 +1242,7 @@ describe('utils', () => { describe('lastValidDate', () => { test('It returns undefined if the search result contains a null timestamp', () => { const searchResult = sampleDocSearchResultsNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional (searchResult.hits.hits[0]._source['@timestamp'] as unknown) = null; if (searchResult.hits.hits[0].fields != null) { (searchResult.hits.hits[0].fields['@timestamp'] as unknown) = null; @@ -1243,6 +1253,7 @@ describe('utils', () => { test('It returns undefined if the search result contains a undefined timestamp', () => { const searchResult = sampleDocSearchResultsNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional (searchResult.hits.hits[0]._source['@timestamp'] as unknown) = undefined; if (searchResult.hits.hits[0].fields != null) { (searchResult.hits.hits[0].fields['@timestamp'] as unknown) = undefined; @@ -1253,6 +1264,7 @@ describe('utils', () => { test('It returns undefined if the search result contains an invalid string value', () => { const searchResult = sampleDocSearchResultsNoSortId(); + // @ts-expect-error @elastic/elasticsearch _source is optional (searchResult.hits.hits[0]._source['@timestamp'] as unknown) = 'invalid value'; if (searchResult.hits.hits[0].fields != null) { (searchResult.hits.hits[0].fields['@timestamp'] as unknown) = ['invalid value']; @@ -1286,7 +1298,7 @@ describe('utils', () => { test('It returns timestampOverride date time if set', () => { const override = '2020-10-07T19:20:28.049Z'; const searchResult = sampleDocSearchResultsNoSortId(); - searchResult.hits.hits[0]._source.different_timestamp = new Date(override).toISOString(); + searchResult.hits.hits[0]._source!.different_timestamp = new Date(override).toISOString(); const date = lastValidDate({ searchResult, timestampOverride: 'different_timestamp' }); expect(date?.toISOString()).toEqual(override); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index fa2fa1f102bd14..28edd97de0a0e9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -9,6 +9,7 @@ import { createHash } from 'crypto'; import moment from 'moment'; import uuidv5 from 'uuid/v5'; import dateMath from '@elastic/datemath'; +import type { estypes } from '@elastic/elasticsearch'; import { isEmpty, partition } from 'lodash'; import { ApiResponse, Context } from '@elastic/elasticsearch/lib/Transport'; @@ -27,7 +28,6 @@ import { ExceptionListClient, ListClient, ListPluginSetup } from '../../../../.. import { ExceptionListItemSchema } from '../../../../../lists/common/schemas'; import { ListArray } from '../../../../common/detection_engine/schemas/types/lists'; import { - BulkResponse, BulkResponseErrorAggregation, SignalHit, SearchAfterAndBulkCreateReturnType, @@ -408,7 +408,7 @@ export const makeFloatString = (num: number): string => Number(num).toFixed(2); * @returns The aggregated example as shown above. */ export const errorAggregator = ( - response: BulkResponse, + response: estypes.BulkResponse, ignoreStatusCodes: number[] ): BulkResponseErrorAggregation => { return response.items.reduce((accum, item) => { @@ -568,7 +568,7 @@ export const lastValidDate = ({ searchResult, timestampOverride, }: { - searchResult: SignalSearchResponse; + searchResult: estypes.SearchResponse; timestampOverride: TimestampOverrideOrUndefined; }): Date | undefined => { if (searchResult.hits.hits.length === 0) { @@ -579,7 +579,8 @@ export const lastValidDate = ({ const timestampValue = lastRecord.fields != null && lastRecord.fields[timestamp] != null ? lastRecord.fields[timestamp][0] - : lastRecord._source[timestamp]; + : // @ts-expect-error @elastic/elasticsearch _source is optional + lastRecord._source[timestamp]; const lastTimestamp = typeof timestampValue === 'string' || typeof timestampValue === 'number' ? timestampValue @@ -599,7 +600,7 @@ export const createSearchAfterReturnTypeFromResponse = ({ searchResult, timestampOverride, }: { - searchResult: SignalSearchResponse; + searchResult: estypes.SearchResponse; timestampOverride: TimestampOverrideOrUndefined; }): SearchAfterAndBulkCreateReturnType => { return createSearchAfterReturnType({ @@ -730,6 +731,7 @@ export const mergeSearchResults = (searchResults: SignalSearchResponse[]) => { total: newShards.total + existingShards.total, successful: newShards.successful + existingShards.successful, failed: newShards.failed + existingShards.failed, + // @ts-expect-error @elastic/elaticsearch skipped is optional in ShardStatistics skipped: newShards.skipped + existingShards.skipped, failures: [ ...(existingShards.failures != null ? existingShards.failures : []), @@ -741,7 +743,7 @@ export const mergeSearchResults = (searchResults: SignalSearchResponse[]) => { total: createTotalHitsFromSearchResult({ searchResult: prev }) + createTotalHitsFromSearchResult({ searchResult: next }), - max_score: Math.max(newHits.max_score, existingHits.max_score), + max_score: Math.max(newHits.max_score!, existingHits.max_score!), hits: [...existingHits.hits, ...newHits.hits], }, }; @@ -751,7 +753,7 @@ export const mergeSearchResults = (searchResults: SignalSearchResponse[]) => { export const createTotalHitsFromSearchResult = ({ searchResult, }: { - searchResult: SignalSearchResponse; + searchResult: { hits: { total: number | { value: number } } }; }): number => { const totalHits = typeof searchResult.hits.total === 'number' diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts index 6aac390a3f842b..db42dc2720b2a8 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts @@ -5,19 +5,18 @@ * 2.0. */ -import { RequestParams } from '@elastic/elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { buildExceptionFilter } from '../../../common/shared_imports'; import { ExceptionListItemSchema } from '../../../../lists/common'; import { AnomalyRecordDoc as Anomaly } from '../../../../ml/server'; -import { SearchResponse } from '../types'; export { Anomaly }; -export type AnomalyResults = SearchResponse; +export type AnomalyResults = estypes.SearchResponse; type MlAnomalySearch = ( - searchParams: RequestParams.Search, + searchParams: estypes.SearchRequest, jobIds: string[] -) => Promise>; +) => Promise>; export interface AnomaliesSearchParams { jobIds: string[]; @@ -67,7 +66,7 @@ export const getAnomalies = async ( include_unmapped: true, }, ], - sort: [{ record_score: { order: 'desc' } }], + sort: [{ record_score: { order: 'desc' as const } }], }, }, params.jobIds diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts index 892801a3aed0b6..5d8540f8860772 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts @@ -58,7 +58,7 @@ export const buildHostsQuery = ({ sort: [ { '@timestamp': { - order: 'desc', + order: 'desc' as const, }, }, ], diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.ts index d1925e84bc5e0a..e960067713bda2 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.ts @@ -6,7 +6,7 @@ */ import { isEmpty } from 'lodash/fp'; - +import type { estypes } from '@elastic/elasticsearch'; import { HostAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts/authentications'; import { sourceFieldsMap, hostFieldsMap } from '../../../../../../../common/ecs/ecs_fields'; @@ -33,7 +33,10 @@ export const buildQuery = ({ defaultIndex, docValueFields, }: HostAuthenticationsRequestOptions) => { - const esFields = reduceFields(authenticationsFields, { ...hostFieldsMap, ...sourceFieldsMap }); + const esFields = reduceFields(authenticationsFields, { + ...hostFieldsMap, + ...sourceFieldsMap, + }) as string[]; const filter = [ ...createQueryFilterClauses(filterQuery), @@ -69,7 +72,10 @@ export const buildQuery = ({ terms: { size: querySize, field: 'user.name', - order: [{ 'successes.doc_count': 'desc' }, { 'failures.doc_count': 'desc' }], + order: [ + { 'successes.doc_count': 'desc' as const }, + { 'failures.doc_count': 'desc' as const }, + ] as estypes.TermsAggregationOrder, }, aggs: { failures: { @@ -83,7 +89,7 @@ export const buildQuery = ({ top_hits: { size: 1, _source: esFields, - sort: [{ '@timestamp': { order: 'desc' } }], + sort: [{ '@timestamp': { order: 'desc' as const } }], }, }, }, @@ -99,7 +105,7 @@ export const buildQuery = ({ top_hits: { size: 1, _source: esFields, - sort: [{ '@timestamp': { order: 'desc' } }], + sort: [{ '@timestamp': { order: 'desc' as const } }], }, }, }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts index a5c82688e01ba2..01473a4368dbca 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts @@ -54,7 +54,7 @@ export const buildHostsKpiAuthenticationsQuery = ({ authentication_success_histogram: { auto_date_histogram: { field: '@timestamp', - buckets: '6', + buckets: 6, }, aggs: { count: { @@ -76,7 +76,7 @@ export const buildHostsKpiAuthenticationsQuery = ({ authentication_failure_histogram: { auto_date_histogram: { field: '@timestamp', - buckets: '6', + buckets: 6, }, aggs: { count: { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts index 0e0cbd8a2649d0..5ea2d1aa407805 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts @@ -41,7 +41,7 @@ export const buildHostsKpiHostsQuery = ({ hosts_histogram: { auto_date_histogram: { field: '@timestamp', - buckets: '6', + buckets: 6, }, aggs: { count: { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts index a702982ab8253d..0471644d11bbea 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts @@ -41,7 +41,7 @@ export const buildHostsKpiUniqueIpsQuery = ({ unique_source_ips_histogram: { auto_date_histogram: { field: '@timestamp', - buckets: '6', + buckets: 6, }, aggs: { count: { @@ -59,7 +59,7 @@ export const buildHostsKpiUniqueIpsQuery = ({ unique_destination_ips_histogram: { auto_date_histogram: { field: '@timestamp', - buckets: '6', + buckets: 6, }, aggs: { count: { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts index e040a94fb35150..c58e450806849a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts @@ -24,6 +24,7 @@ export const hostOverview: SecuritySolutionFactory = { options: HostOverviewRequestOptions, response: IEsSearchResponse ): Promise => { + // @ts-expect-error @elastic/elasticsearch no way to declare type for aggregations const aggregations: OverviewHostHit = get('aggregations', response.rawResponse) || {}; const inspect = { dsl: [inspectStringifyObject(buildOverviewHostQuery(options))], diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts index 2c237ab75bcbbe..897ae633076a21 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts @@ -291,7 +291,8 @@ export const buildOverviewHostQuery = ({ }, size: 0, }, - }; + } as const; + // @ts-expect-error @elastic-elasticsearch readonly [] is not assignable to mutable QueryContainer[] return dslQuery; }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.ts index f036d7c943663e..1df0d60d54a88a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; import { reduceFields } from '../../../../../../utils/build_query/reduce_fields'; import { @@ -24,8 +24,8 @@ export const buildQuery = ({ const processUserFields = reduceFields(uncommonProcessesFields, { ...processFieldsMap, ...userFieldsMap, - }); - const hostFields = reduceFields(uncommonProcessesFields, hostFieldsMap); + }) as string[]; + const hostFields = reduceFields(uncommonProcessesFields, hostFieldsMap) as string[]; const filter = [ ...createQueryFilterClauses(filterQuery), { @@ -60,21 +60,21 @@ export const buildQuery = ({ field: 'process.name', order: [ { - host_count: 'asc', + host_count: 'asc' as const, }, { - _count: 'asc', + _count: 'asc' as const, }, { - _key: 'asc', + _key: 'asc' as const, }, - ], + ] as estypes.TermsAggregationOrder, }, aggregations: { process: { top_hits: { size: 1, - sort: [{ '@timestamp': { order: 'desc' } }], + sort: [{ '@timestamp': { order: 'desc' as const } }], _source: processUserFields, }, }, @@ -120,7 +120,7 @@ export const buildQuery = ({ 'event.action': 'executed', }, }, - ], + ] as estypes.QueryContainer[], }, }, { @@ -146,7 +146,7 @@ export const buildQuery = ({ 'event.action': 'process_started', }, }, - ], + ] as estypes.QueryContainer[], }, }, { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.ts index dfb157e148c9ed..40b22a31691b63 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.ts @@ -63,6 +63,7 @@ export const matrixHistogram: SecuritySolutionFactory = { results: { hits: { total: { value: 1, relation: 'eq' }, - max_score: null, + max_score: undefined, hits: [ { _index: 'auditbeat-7.8.0-2020.11.23-000004', _id: 'wRCuOnYB7WTwW_GluxL8', - _score: null, + _score: undefined, _source: { host: { hostname: 'internal-ci-immutable-rm-ubuntu-2004-big2-1607296224012102773', @@ -188,12 +188,12 @@ export const mockSearchStrategyResponse: IEsSearchResponse = { results: { hits: { total: { value: 5, relation: 'eq' }, - max_score: null, + max_score: undefined, hits: [ { _index: 'filebeat-8.0.0-2020.09.02-000001', _id: 'dd4fa2d4bd-1523631609876537', - _score: null, + _score: undefined, _source: { destination: { geo: { @@ -217,12 +217,12 @@ export const mockSearchStrategyResponse: IEsSearchResponse = { results: { hits: { total: { value: 5, relation: 'eq' }, - max_score: null, + max_score: undefined, hits: [ { _index: 'filebeat-8.0.0-2020.09.02-000001', _id: 'dd4fa2d4bd-1523631609876537', - _score: null, + _score: undefined, _source: { destination: { as: { number: 15169, organization: { name: 'Google LLC' } } }, }, @@ -244,12 +244,12 @@ export const mockSearchStrategyResponse: IEsSearchResponse = { results: { hits: { total: { value: 5, relation: 'eq' }, - max_score: null, + max_score: undefined, hits: [ { _index: 'filebeat-8.0.0-2020.09.02-000001', _id: 'dd4fa2d4bd-1523631486500511', - _score: null, + _score: undefined, _source: { source: { geo: { @@ -273,12 +273,12 @@ export const mockSearchStrategyResponse: IEsSearchResponse = { results: { hits: { total: { value: 5, relation: 'eq' }, - max_score: null, + max_score: undefined, hits: [ { _index: 'filebeat-8.0.0-2020.09.02-000001', _id: 'dd4fa2d4bd-1523631486500511', - _score: null, + _score: undefined, _source: { source: { as: { number: 15169, organization: { name: 'Google LLC' } } }, }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts index d1d0c44d9b61b0..e5a508663a2e0b 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts @@ -40,7 +40,7 @@ const getAggs = (type: string, ip: string) => { _source: [`${type}.as`], sort: [ { - '@timestamp': 'desc', + '@timestamp': 'desc' as const, }, ], }, @@ -60,7 +60,7 @@ const getAggs = (type: string, ip: string) => { _source: [`${type}.geo`], sort: [ { - '@timestamp': 'desc', + '@timestamp': 'desc' as const, }, ], }, @@ -87,7 +87,7 @@ const getHostAggs = (ip: string) => { _source: ['host'], sort: [ { - '@timestamp': 'desc', + '@timestamp': 'desc' as const, }, ], }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts index 5ab2beaabd3cb7..9a73fb30a074d8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts @@ -24,6 +24,7 @@ import { getDnsEdges } from './helpers'; import { buildDnsQuery } from './query.dns_network.dsl'; export const networkDns: SecuritySolutionFactory = { + // @ts-expect-error dns_name_query_count is incompatbile. Maybe' is not assignable to type 'string | undefined buildDsl: (options: NetworkDnsRequestOptions) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/index.ts index 259b45f436124c..6a36e113b62a7a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/index.ts @@ -24,6 +24,7 @@ import { getHttpEdges } from './helpers'; import { buildHttpQuery } from './query.http_network.dsl'; export const networkHttp: SecuritySolutionFactory = { + // @ts-expect-error dns_name_query_count is not conpatible with @elastic/elasticsearch buildDsl: (options: NetworkHttpRequestOptions) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts index 9f14b16971b5f9..7ef0e6e3035281 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts @@ -28,6 +28,7 @@ export const networkKpiDns: SecuritySolutionFactory = { return { ...response, inspect, + // @ts-expect-error code doesn't handle TotalHits dnsQueries: response.rawResponse.hits.total, }; }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts index 2956110239e695..5327a2396cdace 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts @@ -28,6 +28,7 @@ export const networkKpiNetworkEvents: SecuritySolutionFactory = { + // @ts-expect-error auto_date_histogram.buckets is incompatible buildDsl: (options: NetworkKpiUniquePrivateIpsRequestOptions) => buildUniquePrivateIpsQuery(options), parse: async ( diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts index 7a7650b0f77d7a..1f85a119f3c8e6 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts @@ -24,6 +24,7 @@ export const networkOverview: SecuritySolutionFactory = options: NetworkOverviewRequestOptions, response: IEsSearchResponse ): Promise => { + // @ts-expect-error @elastic/elasticsearch no way to declare type for aggregations const aggregations: OverviewNetworkHit = get('aggregations', response.rawResponse) || {}; const inspect = { dsl: [inspectStringifyObject(buildOverviewNetworkQuery(options))], diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/index.ts index 05058e3ee7a2dc..172c864f7ee4f2 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/index.ts @@ -53,6 +53,7 @@ export const timelineEventsAll: SecuritySolutionTimelineFactory( getDataFromSourceHits, + // @ts-expect-error @elastic/elasticsearch _source is optional _source ); const fieldsData = await getDataSafety( diff --git a/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts b/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts index 4236c782d6c685..211c477027eec9 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts @@ -9,7 +9,6 @@ import { ElasticsearchClient, SavedObjectsClientContract, KibanaRequest, - SearchResponse, } from '../../../../../../src/core/server'; import { MlPluginSetup } from '../../../../ml/server'; import { SIGNALS_ID, INTERNAL_IMMUTABLE_KEY } from '../../../common/constants'; @@ -167,14 +166,13 @@ export const getRulesUsage = async ( }; try { - const { body: ruleResults } = await esClient.search>( - ruleSearchOptions - ); + const { body: ruleResults } = await esClient.search(ruleSearchOptions); if (ruleResults.hits?.hits?.length > 0) { rulesUsage = ruleResults.hits.hits.reduce((usage, hit) => { - const isElastic = isElasticRule(hit._source.alert.tags); - const isEnabled = hit._source.alert.enabled; + // @ts-expect-error _source is optional + const isElastic = isElasticRule(hit._source?.alert.tags); + const isEnabled = Boolean(hit._source?.alert.enabled); return updateRulesUsage({ isElastic, isEnabled }, usage); }, initialRulesUsage); diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts index c0cf71fab05584..ef001edd429fd6 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts @@ -50,6 +50,7 @@ async function getSpacesUsage( let resp: SpacesAggregationResponse | undefined; try { + // @ts-expect-error `SearchResponse['hits']['total']` incorrectly expects `number` type instead of `{ value: number }`. ({ body: resp } = await esClient.search({ index: kibanaIndex, body: { diff --git a/x-pack/plugins/stack_alerts/common/build_sorted_events_query.ts b/x-pack/plugins/stack_alerts/common/build_sorted_events_query.ts index a4fb54a06ace82..34851073bd8a2f 100644 --- a/x-pack/plugins/stack_alerts/common/build_sorted_events_query.ts +++ b/x-pack/plugins/stack_alerts/common/build_sorted_events_query.ts @@ -4,18 +4,21 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; +import type { ESSearchRequest } from '../../../../typings/elasticsearch'; -import { ESSearchBody, ESSearchRequest } from '../../../../typings/elasticsearch'; -import { SortOrder } from '../../../../typings/elasticsearch/aggregations'; - -type BuildSortedEventsQueryOpts = Pick & - Pick, 'index' | 'size'>; +interface BuildSortedEventsQueryOpts { + aggs?: Record; + track_total_hits: boolean | number; + index: estypes.Indices; + size: number; +} export interface BuildSortedEventsQuery extends BuildSortedEventsQueryOpts { filter: unknown; from: string; to: string; - sortOrder?: SortOrder | undefined; + sortOrder?: 'asc' | 'desc'; searchAfterSortId: string | number | undefined; timeField: string; } diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.tsx index 09487f1ebe9365..f89f1133d0248b 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.tsx @@ -23,6 +23,8 @@ import { EuiLink, } from '@elastic/eui'; import { DocLinksStart, HttpSetup } from 'kibana/public'; +import type { estypes } from '@elastic/elasticsearch'; + import { XJson } from '../../../../../../src/plugins/es_ui_shared/public'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { @@ -39,6 +41,10 @@ import { buildSortedEventsQuery } from '../../../common/build_sorted_events_quer import { EsQueryAlertParams } from './types'; import { IndexSelectPopover } from '../components/index_select_popover'; +function totalHitsToNumber(total: estypes.HitsMetadata['total']): number { + return typeof total === 'number' ? total : total.value; +} + const DEFAULT_VALUES = { THRESHOLD_COMPARATOR: COMPARATORS.GREATER_THAN, QUERY: `{ @@ -191,7 +197,7 @@ export const EsQueryAlertTypeExpression: React.FunctionComponent< setTestQueryResult( i18n.translate('xpack.stackAlerts.esQuery.ui.numQueryMatchesText', { defaultMessage: 'Query matched {count} documents in the last {window}.', - values: { count: hits.total, window }, + values: { count: totalHitsToNumber(hits.total), window }, }) ); } catch (err) { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts index f0596a9fcb9644..bc5d5c41c5cce8 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts @@ -6,9 +6,9 @@ */ import { i18n } from '@kbn/i18n'; +import type { estypes } from '@elastic/elasticsearch'; import { AlertExecutorOptions, AlertInstanceContext } from '../../../../alerting/server'; import { EsQueryAlertParams } from './alert_type_params'; -import { ESSearchHit } from '../../../../../../typings/elasticsearch'; // alert type context provided to actions @@ -29,7 +29,7 @@ export interface EsQueryAlertActionContext extends AlertInstanceContext { // threshold conditions conditions: string; // query matches - hits: ESSearchHit[]; + hits: estypes.Hit[]; } export function addMessages( diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts index 66984e46de6027..92f6e731f7114a 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts @@ -135,6 +135,7 @@ describe('alertType', () => { const searchResult: ESSearchResponse = generateResults([]); alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + // @ts-expect-error not compatible agregations type elasticsearchClientMock.createSuccessTransportRequestPromise(searchResult) ); @@ -194,6 +195,7 @@ describe('alertType', () => { }, ]); alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + // @ts-expect-error not compatible response type elasticsearchClientMock.createSuccessTransportRequestPromise(searchResult) ); @@ -247,6 +249,7 @@ describe('alertType', () => { const newestDocumentTimestamp = previousTimestamp + 1000; alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + // @ts-expect-error not compatible response type elasticsearchClientMock.createSuccessTransportRequestPromise( generateResults([ { @@ -309,6 +312,7 @@ describe('alertType', () => { const oldestDocumentTimestamp = Date.now(); alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + // @ts-expect-error not compatible response type elasticsearchClientMock.createSuccessTransportRequestPromise( generateResults([ { @@ -370,6 +374,7 @@ describe('alertType', () => { const oldestDocumentTimestamp = Date.now(); alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + // @ts-expect-error not compatible response type elasticsearchClientMock.createSuccessTransportRequestPromise( generateResults([ { @@ -413,6 +418,7 @@ describe('alertType', () => { const newestDocumentTimestamp = oldestDocumentTimestamp + 5000; alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + // @ts-expect-error not compatible response type elasticsearchClientMock.createSuccessTransportRequestPromise( generateResults([ { @@ -458,6 +464,7 @@ describe('alertType', () => { const oldestDocumentTimestamp = Date.now(); alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + // @ts-expect-error not compatible response type elasticsearchClientMock.createSuccessTransportRequestPromise( generateResults( [ @@ -521,6 +528,7 @@ describe('alertType', () => { const oldestDocumentTimestamp = Date.now(); alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + // @ts-expect-error not compatible response type elasticsearchClientMock.createSuccessTransportRequestPromise( generateResults( [ diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts index d1cbeeb46fac04..990ab9c1f60027 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import type { estypes } from '@elastic/elasticsearch'; import { Logger } from 'src/core/server'; import { AlertType, AlertExecutorOptions } from '../../types'; import { ActionContext, EsQueryAlertActionContext, addMessages } from './action_context'; @@ -18,7 +19,6 @@ import { STACK_ALERTS_FEATURE_ID } from '../../../common'; import { ComparatorFns, getHumanReadableComparator } from '../lib'; import { parseDuration } from '../../../../alerting/server'; import { buildSortedEventsQuery } from '../../../common/build_sorted_events_query'; -import { ESSearchHit } from '../../../../../../typings/elasticsearch'; export const ES_QUERY_ID = '.es-query'; @@ -217,7 +217,7 @@ export function getAlertType( const { body: searchResult } = await esClient.search(query); if (searchResult.hits.hits.length > 0) { - const numMatches = searchResult.hits.total.value; + const numMatches = (searchResult.hits.total as estypes.TotalHits).value; logger.debug(`alert ${ES_QUERY_ID}:${alertId} "${name}" query has ${numMatches} matches`); // apply the alert condition @@ -251,7 +251,7 @@ export function getAlertType( // update the timestamp based on the current search results const firstValidTimefieldSort = getValidTimefieldSort( - searchResult.hits.hits.find((hit: ESSearchHit) => getValidTimefieldSort(hit.sort))?.sort + searchResult.hits.hits.find((hit) => getValidTimefieldSort(hit.sort))?.sort ); if (firstValidTimefieldSort) { timestamp = firstValidTimefieldSort; @@ -265,7 +265,7 @@ export function getAlertType( } } -function getValidTimefieldSort(sortValues: Array = []): undefined | string { +function getValidTimefieldSort(sortValues: Array = []): undefined | string { for (const sortValue of sortValues) { const sortDate = tryToParseAsDate(sortValue); if (sortDate) { @@ -273,7 +273,7 @@ function getValidTimefieldSort(sortValues: Array = []): undefin } } } -function tryToParseAsDate(sortValue?: string | number): undefined | string { +function tryToParseAsDate(sortValue?: string | number | null): undefined | string { const sortDate = typeof sortValue === 'string' ? Date.parse(sortValue) : sortValue; if (sortDate && !isNaN(sortDate)) { return new Date(sortDate).toISOString(); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts index a416056217442e..1e26ea09618d5a 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts @@ -7,8 +7,7 @@ import { ElasticsearchClient } from 'kibana/server'; import { Logger } from 'src/core/server'; -import { ApiResponse } from '@elastic/elasticsearch'; -import { SearchResponse } from 'elasticsearch'; +import type { ApiResponse, estypes } from '@elastic/elasticsearch'; import { Query, IIndexPattern, @@ -110,7 +109,7 @@ export async function executeEsQueryFactory( return async ( gteDateTime: Date | null, ltDateTime: Date | null - ): Promise> | undefined> => { + ): Promise | undefined> => { let esFormattedQuery; if (indexQuery) { const gteEpochDateTime = gteDateTime ? new Date(gteDateTime).getTime() : null; @@ -194,7 +193,7 @@ export async function executeEsQueryFactory( }, }; - let esResult: ApiResponse> | undefined; + let esResult: estypes.SearchResponse | undefined; try { ({ body: esResult } = await esClient.search(esQuery)); } catch (err) { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts index 866b25d239db74..15a6564395c165 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts @@ -7,8 +7,7 @@ import _ from 'lodash'; import { Logger } from 'src/core/server'; -import { ApiResponse } from '@elastic/elasticsearch'; -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { executeEsQueryFactory, getShapesFilters, OTHER_CATEGORY } from './es_query_builder'; import { AlertServices } from '../../../../alerting/server'; import { @@ -23,7 +22,7 @@ export type LatestEntityLocation = GeoContainmentInstanceState; // Flatten agg results and get latest locations for each entity export function transformResults( - results: SearchResponse | undefined, + results: estypes.SearchResponse | undefined, dateField: string, geoField: string ): Map { @@ -164,7 +163,7 @@ export const getGeoContainmentExecutor = (log: Logger): GeoContainmentAlertType[ ); // Start collecting data only on the first cycle - let currentIntervalResults: ApiResponse> | undefined; + let currentIntervalResults: estypes.SearchResponse | undefined; if (!currIntervalStartTime) { log.debug(`alert ${GEO_CONTAINMENT_ID}:${alertId} alert initialized. Collecting data`); // Consider making first time window configurable? @@ -177,6 +176,7 @@ export const getGeoContainmentExecutor = (log: Logger): GeoContainmentAlertType[ } const currLocationMap: Map = transformResults( + // @ts-expect-error body doesn't exist on currentIntervalResults currentIntervalResults?.body, params.dateField, params.geoField diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts index 46d8478a7ecfa9..05b74f4340d095 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts @@ -6,78 +6,88 @@ */ import { first, take, bufferCount } from 'rxjs/operators'; -import { loggingSystemMock } from '../../../../../src/core/server/mocks'; +import { loggingSystemMock, elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; import { - WorkloadAggregation, + TaskTypeAggregation, + WorkloadAggregationResponse, + ScheduleDensityHistogram, createWorkloadAggregator, padBuckets, estimateRecurringTaskScheduling, } from './workload_statistics'; import { ConcreteTaskInstance } from '../task'; -import { AggregationResultOf, ESSearchResponse } from '../../../../../typings/elasticsearch'; + import { times } from 'lodash'; import { taskStoreMock } from '../task_store.mock'; import { of, Subject } from 'rxjs'; import { sleep } from '../test_utils'; +import { estypes } from '@elastic/elasticsearch'; + +type ResponseWithAggs = Omit, 'aggregations'> & { + aggregations: WorkloadAggregationResponse; +}; -type MockESResult = ESSearchResponse< - ConcreteTaskInstance, - { - body: WorkloadAggregation; - } ->; +const asApiResponse = (body: ResponseWithAggs) => + elasticsearchServiceMock + .createSuccessTransportRequestPromise(body as estypes.SearchResponse) + .then((res) => res.body as ResponseWithAggs); describe('Workload Statistics Aggregator', () => { test('queries the Task Store at a fixed interval for the current workload', async () => { const taskStore = taskStoreMock.create({}); - taskStore.aggregate.mockResolvedValue({ - hits: { - hits: [], - max_score: 0, - total: { value: 0, relation: 'eq' }, - }, - took: 1, - timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 1, - failed: 0, - }, - aggregations: { - taskType: { - buckets: [], - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, + taskStore.aggregate.mockResolvedValue( + asApiResponse({ + hits: { + hits: [], + max_score: 0, + total: { value: 0, relation: 'eq' }, }, - schedule: { - buckets: [], - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, + took: 1, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 1, + failed: 0, }, - idleTasks: { - doc_count: 0, - overdue: { - doc_count: 0, + aggregations: { + taskType: { + buckets: [], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, }, - scheduleDensity: { - buckets: [ - { - key: '2020-10-02T15:18:37.274Z-2020-10-02T15:19:36.274Z', - from: 1.601651917274e12, - from_as_string: '2020-10-02T15:18:37.274Z', - to: 1.601651976274e12, - to_as_string: '2020-10-02T15:19:36.274Z', - doc_count: 0, - histogram: { - buckets: [], + schedule: { + buckets: [], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + }, + // The `FiltersAggregate` doesn't cover the case of a nested `AggregationContainer`, in which `FiltersAggregate` + // would not have a `buckets` property, but rather a keyed property that's inferred from the request. + // @ts-expect-error + idleTasks: { + doc_count: 0, + overdue: { + doc_count: 0, + }, + scheduleDensity: { + buckets: [ + { + key: '2020-10-02T15:18:37.274Z-2020-10-02T15:19:36.274Z', + from: 1.601651917274e12, + from_as_string: '2020-10-02T15:18:37.274Z', + to: 1.601651976274e12, + to_as_string: '2020-10-02T15:19:36.274Z', + doc_count: 0, + histogram: { + buckets: [], + }, }, - }, - ], + ], + }, }, }, - }, - } as MockESResult); + }) + ); const workloadAggregator = createWorkloadAggregator( taskStore, @@ -146,8 +156,8 @@ describe('Workload Statistics Aggregator', () => { }); }); - const mockAggregatedResult: () => MockESResult = () => - ({ + const mockAggregatedResult = () => + asApiResponse({ hits: { hits: [], max_score: 0, @@ -228,6 +238,9 @@ describe('Workload Statistics Aggregator', () => { }, ], }, + // The `FiltersAggregate` doesn't cover the case of a nested `AggregationContainer`, in which `FiltersAggregate` + // would not have a `buckets` property, but rather a keyed property that's inferred from the request. + // @ts-expect-error idleTasks: { doc_count: 13, overdue: { @@ -240,7 +253,7 @@ describe('Workload Statistics Aggregator', () => { }, }, }, - } as MockESResult); + }); test('returns a summary of the workload by task type', async () => { const taskStore = taskStoreMock.create({}); @@ -440,16 +453,20 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate .mockResolvedValueOnce( - setTaskTypeCount(mockAggregatedResult(), 'alerting_telemetry', { - idle: 2, - }) + mockAggregatedResult().then((res) => + setTaskTypeCount(res, 'alerting_telemetry', { + idle: 2, + }) + ) ) .mockRejectedValueOnce(new Error('Elasticsearch has gone poof')) .mockResolvedValueOnce( - setTaskTypeCount(mockAggregatedResult(), 'alerting_telemetry', { - idle: 1, - failed: 1, - }) + mockAggregatedResult().then((res) => + setTaskTypeCount(res, 'alerting_telemetry', { + idle: 1, + failed: 1, + }) + ) ); const logger = loggingSystemMock.create().get(); const workloadAggregator = createWorkloadAggregator(taskStore, of(true), 10, 3000, logger); @@ -502,7 +519,7 @@ describe('Workload Statistics Aggregator', () => { reject(new Error(`Elasticsearch is still poof`)); } - return setTaskTypeCount(mockAggregatedResult(), 'alerting_telemetry', { + return setTaskTypeCount(await mockAggregatedResult(), 'alerting_telemetry', { idle: 2, }); }); @@ -631,6 +648,7 @@ describe('padBuckets', () => { padBuckets(10, 3000, { key: '2020-10-02T19:47:28.128Z-2020-10-02T19:48:28.128Z', from: 1601668048128, + // @ts-expect-error @elastic/elasticsearch doesn't decalre from_as_string property from_as_string: '2020-10-02T19:47:28.128Z', to: 1601668108128, to_as_string: '2020-10-02T19:48:28.128Z', @@ -647,6 +665,7 @@ describe('padBuckets', () => { padBuckets(10, 3000, { key: '2020-10-02T19:47:28.128Z-2020-10-02T19:48:28.128Z', from: 1601668046000, + // @ts-expect-error @elastic/elasticsearch doesn't decalre from_as_string property from_as_string: '2020-10-02T19:47:26.000Z', to: 1601668076000, to_as_string: '2020-10-02T19:47:56.000Z', @@ -724,6 +743,7 @@ describe('padBuckets', () => { padBuckets(10, 3000, { key: '2020-10-02T20:39:45.793Z-2020-10-02T20:40:14.793Z', from: 1601671183000, + // @ts-expect-error @elastic/elasticsearch doesn't decalre from_as_string property from_as_string: '2020-10-02T20:39:43.000Z', to: 1601671213000, to_as_string: '2020-10-02T20:40:13.000Z', @@ -753,6 +773,7 @@ describe('padBuckets', () => { padBuckets(20, 3000, { key: '2020-10-02T20:39:45.793Z-2020-10-02T20:40:14.793Z', from: 1601671185793, + // @ts-expect-error @elastic/elasticsearch doesn't decalre from_as_string property from_as_string: '2020-10-02T20:39:45.793Z', to: 1601671245793, to_as_string: '2020-10-02T20:40:45.793Z', @@ -782,6 +803,7 @@ describe('padBuckets', () => { padBuckets(20, 3000, { key: '2021-02-02T10:08:32.161Z-2021-02-02T10:09:32.161Z', from: 1612260512161, + // @ts-expect-error @elastic/elasticsearch doesn't decalre from_as_string property from_as_string: '2021-02-02T10:08:32.161Z', to: 1612260572161, to_as_string: '2021-02-02T10:09:32.161Z', @@ -898,16 +920,12 @@ describe('padBuckets', () => { }); function setTaskTypeCount( - { aggregations }: MockESResult, + { aggregations, ...rest }: ResponseWithAggs, taskType: string, status: Record ) { - const taskTypes = aggregations!.taskType as AggregationResultOf< - WorkloadAggregation['aggs']['taskType'], - {} - >; const buckets = [ - ...taskTypes.buckets.filter(({ key }) => key !== taskType), + ...(aggregations.taskType as TaskTypeAggregation).buckets.filter(({ key }) => key !== taskType), { key: taskType, doc_count: Object.values(status).reduce((sum, count) => sum + count, 0), @@ -920,9 +938,14 @@ function setTaskTypeCount( }, }, ]; - return ({ + return { + ...rest, hits: { - total: { value: buckets.reduce((sum, bucket) => sum + bucket.doc_count, 0) }, + ...rest.hits, + total: { + value: buckets.reduce((sum, bucket) => sum + bucket.doc_count, 0), + relation: 'eq' as estypes.TotalHitsRelation, + }, }, aggregations: { ...aggregations, @@ -931,7 +954,7 @@ function setTaskTypeCount( buckets, }, }, - } as {}) as MockESResult; + }; } /** * @@ -951,7 +974,7 @@ function mockHistogram( to: number, interval: number, foundBuckets: Array -) { +): ScheduleDensityHistogram { const now = Date.now(); const fromDate = new Date(now + from); const toDate = new Date(now + to); diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts index 08850c86505191..c79b310822c3ec 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts @@ -10,6 +10,7 @@ import { mergeMap, map, filter, switchMap, catchError } from 'rxjs/operators'; import { Logger } from 'src/core/server'; import { JsonObject } from 'src/plugins/kibana_utils/common'; import { keyBy, mapValues } from 'lodash'; +import { estypes } from '@elastic/elasticsearch'; import { AggregatedStatProvider } from './runtime_statistics_aggregator'; import { parseIntervalAsSecond, asInterval, parseIntervalAsMillisecond } from '../lib/intervals'; import { AggregationResultOf } from '../../../../../typings/elasticsearch'; @@ -85,9 +86,11 @@ export interface WorkloadAggregation { // The type of a bucket in the scheduleDensity range aggregation type ScheduleDensityResult = AggregationResultOf< + // @ts-expect-error AggregationRange reqires from: number WorkloadAggregation['aggs']['idleTasks']['aggs']['scheduleDensity'], {} >['buckets'][0]; +// @ts-expect-error cannot infer histogram type ScheduledIntervals = ScheduleDensityResult['histogram']['buckets'][0]; // Set an upper bound just in case a customer sets a really high refresh rate @@ -134,7 +137,9 @@ export function createWorkloadAggregator( field: 'task.runAt', ranges: [ { + // @ts-expect-error @elastic/elasticsearch The `AggregationRange` type only supports `double` for `from` and `to` but it can be a string too for time based ranges from: `now`, + // @ts-expect-error @elastic/elasticsearch The `AggregationRange` type only supports `double` for `from` and `to` but it can be a string too for time based ranges to: `now+${asInterval(scheduleDensityBuckets * pollInterval)}`, }, ], @@ -170,19 +175,11 @@ export function createWorkloadAggregator( map((result) => { const { aggregations, - hits: { - total: { value: count }, - }, + hits: { total }, } = result; + const count = typeof total === 'number' ? total : total.value; - if ( - !( - aggregations?.taskType && - aggregations?.schedule && - aggregations?.idleTasks?.overdue && - aggregations?.idleTasks?.scheduleDensity - ) - ) { + if (!hasAggregations(aggregations)) { throw new Error(`Invalid workload: ${JSON.stringify(result)}`); } @@ -240,7 +237,9 @@ export function padBuckets( pollInterval: number, scheduleDensity: ScheduleDensityResult ): number[] { + // @ts-expect-error cannot infer histogram if (scheduleDensity.from && scheduleDensity.to && scheduleDensity.histogram?.buckets?.length) { + // @ts-expect-error cannot infer histogram const { histogram, from, to } = scheduleDensity; const firstBucket = histogram.buckets[0].key; const lastBucket = histogram.buckets[histogram.buckets.length - 1].key; @@ -354,3 +353,84 @@ export function summarizeWorkloadStat( status: HealthStatus.OK, }; } + +function hasAggregations( + aggregations?: Record +): aggregations is WorkloadAggregationResponse { + return !!( + aggregations?.taskType && + aggregations?.schedule && + (aggregations?.idleTasks as IdleTasksAggregation)?.overdue && + (aggregations?.idleTasks as IdleTasksAggregation)?.scheduleDensity + ); +} +export interface WorkloadAggregationResponse { + taskType: TaskTypeAggregation; + schedule: ScheduleAggregation; + idleTasks: IdleTasksAggregation; + [otherAggs: string]: estypes.Aggregate; +} +export interface TaskTypeAggregation extends estypes.FiltersAggregate { + buckets: Array<{ + doc_count: number; + key: string | number; + status: { + buckets: Array<{ + doc_count: number; + key: string | number; + }>; + doc_count_error_upper_bound?: number | undefined; + sum_other_doc_count?: number | undefined; + }; + }>; + doc_count_error_upper_bound?: number | undefined; + sum_other_doc_count?: number | undefined; +} +export interface ScheduleAggregation extends estypes.FiltersAggregate { + buckets: Array<{ + doc_count: number; + key: string | number; + }>; + doc_count_error_upper_bound?: number | undefined; + sum_other_doc_count?: number | undefined; +} + +export type ScheduleDensityHistogram = DateRangeBucket & { + histogram: { + buckets: Array< + DateHistogramBucket & { + interval: { + buckets: Array<{ + doc_count: number; + key: string | number; + }>; + doc_count_error_upper_bound?: number | undefined; + sum_other_doc_count?: number | undefined; + }; + } + >; + }; +}; +export interface IdleTasksAggregation extends estypes.FiltersAggregate { + doc_count: number; + scheduleDensity: { + buckets: ScheduleDensityHistogram[]; + }; + overdue: { + doc_count: number; + }; +} + +interface DateHistogramBucket { + doc_count: number; + key: number; + key_as_string: string; +} +interface DateRangeBucket { + key: string; + to?: number; + from?: number; + to_as_string?: string; + from_as_string?: string; + doc_count: number; +} diff --git a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts index 57a4ab320367d4..9e31ab9f0cb4e2 100644 --- a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts +++ b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts @@ -6,7 +6,7 @@ */ import _ from 'lodash'; -import { asUpdateByQuery, shouldBeOneOf, mustBeAllOf } from './query_clauses'; +import { shouldBeOneOf, mustBeAllOf } from './query_clauses'; import { updateFieldsAndMarkAsFailed, @@ -41,25 +41,23 @@ describe('mark_available_tasks_as_claimed', () => { retryAt: claimOwnershipUntil, }; - expect( - asUpdateByQuery({ - query: mustBeAllOf( - // Either a task with idle status and runAt <= now or - // status running or claiming with a retryAt <= now. - shouldBeOneOf(IdleTaskWithExpiredRunAt, RunningOrClaimingTaskWithExpiredRetryAt) - ), - update: updateFieldsAndMarkAsFailed( - fieldUpdates, - claimTasksById || [], - definitions.getAllTypes(), - [], - Array.from(definitions).reduce((accumulator, [type, { maxAttempts }]) => { - return { ...accumulator, [type]: maxAttempts || defaultMaxAttempts }; - }, {}) - ), - sort: SortByRunAtAndRetryAt, - }) - ).toEqual({ + expect({ + query: mustBeAllOf( + // Either a task with idle status and runAt <= now or + // status running or claiming with a retryAt <= now. + shouldBeOneOf(IdleTaskWithExpiredRunAt, RunningOrClaimingTaskWithExpiredRetryAt) + ), + script: updateFieldsAndMarkAsFailed( + fieldUpdates, + claimTasksById || [], + definitions.getAllTypes(), + [], + Array.from(definitions).reduce((accumulator, [type, { maxAttempts }]) => { + return { ...accumulator, [type]: maxAttempts || defaultMaxAttempts }; + }, {}) + ), + sort: SortByRunAtAndRetryAt, + }).toEqual({ query: { bool: { must: [ @@ -114,7 +112,6 @@ if (doc['task.runAt'].size()!=0) { }, }, }, - seq_no_primary_term: true, script: { source: ` if (params.claimableTaskTypes.contains(ctx._source.task.taskType)) { diff --git a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts index 8598980a4e2363..2437e893abf373 100644 --- a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts +++ b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts @@ -4,27 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { - SortClause, + ScriptBasedSortClause, ScriptClause, - ExistsFilter, - TermFilter, - RangeFilter, mustBeAllOf, MustCondition, - BoolClauseWithAnyCondition, - ShouldCondition, - FilterCondition, + MustNotCondition, } from './query_clauses'; -export const TaskWithSchedule: ExistsFilter = { - exists: { field: 'task.schedule' }, -}; -export function taskWithLessThanMaxAttempts( - type: string, - maxAttempts: number -): MustCondition { +export function taskWithLessThanMaxAttempts(type: string, maxAttempts: number): MustCondition { return { bool: { must: [ @@ -41,7 +30,7 @@ export function taskWithLessThanMaxAttempts( }; } -export function tasksOfType(taskTypes: string[]): ShouldCondition { +export function tasksOfType(taskTypes: string[]): estypes.QueryContainer { return { bool: { should: [...taskTypes].map((type) => ({ term: { 'task.taskType': type } })), @@ -51,7 +40,7 @@ export function tasksOfType(taskTypes: string[]): ShouldCondition { export function tasksClaimedByOwner( taskManagerId: string, - ...taskFilters: Array | ShouldCondition> + ...taskFilters: estypes.QueryContainer[] ) { return mustBeAllOf( { @@ -64,15 +53,13 @@ export function tasksClaimedByOwner( ); } -export const IdleTaskWithExpiredRunAt: MustCondition = { +export const IdleTaskWithExpiredRunAt: MustCondition = { bool: { must: [{ term: { 'task.status': 'idle' } }, { range: { 'task.runAt': { lte: 'now' } } }], }, }; -// TODO: Fix query clauses to support this -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const InactiveTasks: BoolClauseWithAnyCondition = { +export const InactiveTasks: MustNotCondition = { bool: { must_not: [ { @@ -85,7 +72,7 @@ export const InactiveTasks: BoolClauseWithAnyCondition = { }, }; -export const RunningOrClaimingTaskWithExpiredRetryAt: MustCondition = { +export const RunningOrClaimingTaskWithExpiredRetryAt: MustCondition = { bool: { must: [ { @@ -98,7 +85,7 @@ export const RunningOrClaimingTaskWithExpiredRetryAt: MustCondition; export const updateFieldsAndMarkAsFailed = ( fieldUpdates: { diff --git a/x-pack/plugins/task_manager/server/queries/query_clauses.test.ts b/x-pack/plugins/task_manager/server/queries/query_clauses.test.ts index f144c8921263fd..5f99682718f1dd 100644 --- a/x-pack/plugins/task_manager/server/queries/query_clauses.test.ts +++ b/x-pack/plugins/task_manager/server/queries/query_clauses.test.ts @@ -6,29 +6,21 @@ */ import _ from 'lodash'; -import { - MustCondition, - shouldBeOneOf, - mustBeAllOf, - ExistsFilter, - TermFilter, - RangeFilter, - matchesClauses, -} from './query_clauses'; +import { MustCondition, shouldBeOneOf, mustBeAllOf, matchesClauses } from './query_clauses'; describe('matchesClauses', () => { test('merges multiple types of Bool Clauses into one', () => { - const TaskWithSchedule: ExistsFilter = { + const TaskWithSchedule = { exists: { field: 'task.schedule' }, }; - const IdleTaskWithExpiredRunAt: MustCondition = { + const IdleTaskWithExpiredRunAt: MustCondition = { bool: { must: [{ term: { 'task.status': 'idle' } }, { range: { 'task.runAt': { lte: 'now' } } }], }, }; - const RunningTask: MustCondition = { + const RunningTask: MustCondition = { bool: { must: [{ term: { 'task.status': 'running' } }], }, @@ -37,10 +29,7 @@ describe('matchesClauses', () => { expect( matchesClauses( mustBeAllOf(TaskWithSchedule), - shouldBeOneOf( - RunningTask, - IdleTaskWithExpiredRunAt - ) + shouldBeOneOf(RunningTask, IdleTaskWithExpiredRunAt) ) ).toMatchObject({ bool: { diff --git a/x-pack/plugins/task_manager/server/queries/query_clauses.ts b/x-pack/plugins/task_manager/server/queries/query_clauses.ts index 4d59bec4e36255..97c41295eece32 100644 --- a/x-pack/plugins/task_manager/server/queries/query_clauses.ts +++ b/x-pack/plugins/task_manager/server/queries/query_clauses.ts @@ -5,167 +5,27 @@ * 2.0. */ -/** - * Terminology - * =========== - * The terms for the differenct clauses in an Elasticsearch query can be confusing, here are some - * clarifications that might help you understand the Typescript types we use here. - * - * Given the following Query: - * { - * "query": { (1) - * "bool": { (2) - * "must": - * [ - * (3) { "term" : { "tag" : "wow" } }, - * { "term" : { "tag" : "elasticsearch" } }, - * { - * "must" : { "term" : { "user" : "kimchy" } } - * } - * ] - * } - * } - * } - * - * These are referred to as: - * (1). BoolClause / BoolClauseWithAnyCondition - * (2). BoolCondition / AnyBoolCondition - * (3). BoolClauseFilter - * - */ - -export interface TermFilter { - term: { [field: string]: string | string[] }; -} -export interface RangeFilter { - range: { - [field: string]: { lte: string | number } | { lt: string | number } | { gt: string | number }; - }; -} -export interface ExistsFilter { - exists: { field: string }; -} - -type BoolClauseFilter = TermFilter | RangeFilter | ExistsFilter; -type BoolClauseFiltering = - | BoolClauseWithAnyCondition - | PinnedQuery - | T; +import { estypes } from '@elastic/elasticsearch'; -enum Conditions { - Should = 'should', - Must = 'must', - MustNot = 'must_not', - Filter = 'filter', +export interface MustCondition { + bool: Pick; } - -/** - * Describe a specific BoolClause Condition with a BoolClauseFilter on it, such as: - * ``` - * { - * must : [ - * T, ... - * ] - * } - * ``` - */ -type BoolCondition = { - [c in C]: Array>; -}; - -/** - * Describe a Bool clause with a specific Condition, such as: - * ``` - * { - * // described by BoolClause - * bool: { - * // described by BoolCondition - * must: [ - * T, ... - * ] - * } - * } - * ``` - */ -interface BoolClause { - bool: BoolCondition; +export interface MustNotCondition { + bool: Pick; } -/** - * Describe a Bool clause with mixed Conditions, such as: - * ``` - * { - * // described by BoolClause<...> - * bool: { - * // described by BoolCondition - * must : { - * ... - * }, - * // described by BoolCondition - * should : { - * ... - * } - * } - * } - * ``` - */ -type AnyBoolCondition = { - [Condition in Conditions]?: Array>; -}; - -/** - * Describe a Bool Condition with any Condition on it, so it can handle both: - * ``` - * { - * bool: { - * must : { - * ... - * } - * } - * } - * ``` - * - * and: - * - * ``` - * { - * bool: { - * must_not : { - * ... - * } - * } - * } - * ``` - */ -export interface BoolClauseWithAnyCondition { - bool: AnyBoolCondition; -} - -/** - * Describe the various Bool Clause Conditions we support, as specified in the Conditions enum - */ -export type ShouldCondition = BoolClause; -export type MustCondition = BoolClause; -export type MustNotCondition = BoolClause; -export type FilterCondition = BoolClause; - -export interface SortClause { +export interface ScriptBasedSortClause { _script: { type: string; order: string; - script: { - lang: string; - source: string; - params?: { [param: string]: string | string[] }; - }; + script: ScriptClause; }; } -export type SortOptions = string | SortClause | Array; export interface ScriptClause { source: string; lang: string; - params: { + params?: { [field: string]: | string | number @@ -175,33 +35,16 @@ export interface ScriptClause { }; } -export interface UpdateByQuery { - query: PinnedQuery | BoolClauseWithAnyCondition; - sort: SortOptions; - seq_no_primary_term: true; - script: ScriptClause; -} - -export interface PinnedQuery { - pinned: PinnedClause; -} +export type PinnedQuery = Pick; -export interface PinnedClause { - ids: string[]; - organic: BoolClauseWithAnyCondition; -} - -export function matchesClauses( - ...clauses: Array> -): BoolClauseWithAnyCondition { +type BoolClause = Pick; +export function matchesClauses(...clauses: BoolClause[]): BoolClause { return { bool: Object.assign({}, ...clauses.map((clause) => clause.bool)), }; } -export function shouldBeOneOf( - ...should: Array> -): ShouldCondition { +export function shouldBeOneOf(...should: estypes.QueryContainer[]) { return { bool: { should, @@ -209,9 +52,7 @@ export function shouldBeOneOf( }; } -export function mustBeAllOf( - ...must: Array> -): MustCondition { +export function mustBeAllOf(...must: estypes.QueryContainer[]) { return { bool: { must, @@ -219,9 +60,7 @@ export function mustBeAllOf( }; } -export function filterDownBy( - ...filter: Array> -): FilterCondition { +export function filterDownBy(...filter: estypes.QueryContainer[]) { return { bool: { filter, @@ -229,10 +68,10 @@ export function filterDownBy( }; } -export function asPinnedQuery( - ids: PinnedClause['ids'], - organic: PinnedClause['organic'] -): PinnedQuery { +export function asPinnedQuery( + ids: estypes.PinnedQuery['ids'], + organic: estypes.PinnedQuery['organic'] +): Pick { return { pinned: { ids, @@ -240,20 +79,3 @@ export function asPinnedQuery( }, }; } - -export function asUpdateByQuery({ - query, - update, - sort, -}: { - query: UpdateByQuery['query']; - update: UpdateByQuery['script']; - sort: UpdateByQuery['sort']; -}): UpdateByQuery { - return { - query, - sort, - seq_no_primary_term: true, - script: update, - }; -} diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts index bd1171d7fd2f82..8f1332ccd1f9f7 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts @@ -15,7 +15,7 @@ import { SearchOpts, StoreOpts, UpdateByQueryOpts, UpdateByQuerySearchOpts } fro import { asTaskClaimEvent, ClaimTaskErr, TaskClaimErrorType, TaskEvent } from '../task_events'; import { asOk, asErr } from '../lib/result_type'; import { TaskTypeDictionary } from '../task_type_dictionary'; -import { BoolClauseWithAnyCondition, TermFilter } from '../queries/query_clauses'; +import type { MustNotCondition } from '../queries/query_clauses'; import { mockLogger } from '../test_utils'; import { TaskClaiming, OwnershipClaimingOpts, TaskClaimingOpts } from './task_claiming'; import { Observable } from 'rxjs'; @@ -177,7 +177,7 @@ describe('TaskClaiming', () => { result, args: { search: store.fetch.mock.calls[index][0] as SearchOpts & { - query: BoolClauseWithAnyCondition; + query: MustNotCondition; }, updateByQuery: store.updateByQuery.mock.calls[index] as [ UpdateByQuerySearchOpts, @@ -767,12 +767,12 @@ if (doc['task.runAt'].size()!=0) { ).map( (result, index) => (store.updateByQuery.mock.calls[index][0] as { - query: BoolClauseWithAnyCondition; + query: MustNotCondition; size: number; sort: string | string[]; script: { params: { - claimableTaskTypes: string[]; + [claimableTaskTypes: string]: string[]; }; }; }).script.params.claimableTaskTypes diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.ts index b4e11dbf81eb10..dce78242816583 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.ts @@ -27,13 +27,11 @@ import { } from '../task_events'; import { - asUpdateByQuery, shouldBeOneOf, mustBeAllOf, filterDownBy, asPinnedQuery, matchesClauses, - SortOptions, } from './query_clauses'; import { @@ -50,6 +48,7 @@ import { correctVersionConflictsForContinuation, TaskStore, UpdateByQueryResult, + SearchOpts, } from '../task_store'; import { FillPoolResult } from '../lib/fill_pool'; @@ -375,21 +374,21 @@ export class TaskClaiming { // the score seems to favor newer documents rather than older documents, so // if there are not pinned tasks being queried, we do NOT want to sort by score // at all, just by runAt/retryAt. - const sort: SortOptions = [SortByRunAtAndRetryAt]; + const sort: NonNullable = [SortByRunAtAndRetryAt]; if (claimTasksById && claimTasksById.length) { sort.unshift('_score'); } const apmTrans = apm.startTransaction(`taskManager markAvailableTasksAsClaimed`, 'taskManager'); const result = await this.taskStore.updateByQuery( - asUpdateByQuery({ + { query: matchesClauses( claimTasksById && claimTasksById.length ? mustBeAllOf(asPinnedQuery(claimTasksById, queryForScheduledTasks)) : queryForScheduledTasks, filterDownBy(InactiveTasks) ), - update: updateFieldsAndMarkAsFailed( + script: updateFieldsAndMarkAsFailed( { ownerId: this.taskStore.taskManagerId, retryAt: claimOwnershipUntil, @@ -400,7 +399,7 @@ export class TaskClaiming { pick(this.taskMaxAttempts, taskTypesToClaim) ), sort, - }), + }, { max_docs: size, } diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index 25ee8cb0e23745..a44bddcdb82014 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import _ from 'lodash'; import { first } from 'rxjs/operators'; @@ -24,7 +24,6 @@ import { SavedObjectsErrorHelpers, } from 'src/core/server'; import { TaskTypeDictionary } from './task_type_dictionary'; -import { RequestEvent } from '@elastic/elasticsearch/lib/Transport'; import { mockLogger } from './test_utils'; const savedObjectsClient = savedObjectsRepositoryMock.create(); @@ -206,8 +205,8 @@ describe('TaskStore', () => { }); }); - async function testFetch(opts?: SearchOpts, hits: unknown[] = []) { - esClient.search.mockResolvedValue(asApiResponse({ hits: { hits } })); + async function testFetch(opts?: SearchOpts, hits: Array> = []) { + esClient.search.mockResolvedValue(asApiResponse({ hits: { hits, total: hits.length } })); const result = await store.fetch(opts); @@ -564,9 +563,17 @@ describe('TaskStore', () => { }); }); -const asApiResponse = (body: T): RequestEvent => - ({ - body, - } as RequestEvent); +const asApiResponse = (body: Pick) => + elasticsearchServiceMock.createSuccessTransportRequestPromise({ + hits: body.hits, + took: 0, + timed_out: false, + _shards: { + failed: 0, + successful: body.hits.hits.length, + total: 0, + skipped: 0, + }, + }); const randomId = () => `id-${_.random(1, 20)}`; diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index 083ce1507e6e5a..af0adad43baa46 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -10,7 +10,9 @@ */ import { Subject } from 'rxjs'; import { omit, defaults } from 'lodash'; -import { ReindexResponseBase, SearchResponse, UpdateDocumentByQueryResponse } from 'elasticsearch'; + +import type { estypes } from '@elastic/elasticsearch'; + import { SavedObject, SavedObjectsSerializer, @@ -31,7 +33,6 @@ import { } from './task'; import { TaskTypeDictionary } from './task_type_dictionary'; -import { ESSearchResponse, ESSearchBody } from '../../../../typings/elasticsearch'; export interface StoreOpts { esClient: ElasticsearchClient; @@ -43,18 +44,21 @@ export interface StoreOpts { } export interface SearchOpts { - sort?: string | object | object[]; - query?: object; + search_after?: Array; size?: number; + sort?: estypes.Sort; + query?: estypes.QueryContainer; seq_no_primary_term?: boolean; - search_after?: unknown[]; } -export type AggregationOpts = Pick, 'aggs'> & - Pick; +export interface AggregationOpts { + aggs: Record; + query?: estypes.QueryContainer; + size?: number; +} export interface UpdateByQuerySearchOpts extends SearchOpts { - script?: object; + script?: estypes.Script; } export interface UpdateByQueryOpts extends SearchOpts { @@ -304,7 +308,7 @@ export class TaskStore { body: { hits: { hits: tasks }, }, - } = await this.esClient.search>({ + } = await this.esClient.search({ index: this.index, ignore_unavailable: true, body: { @@ -315,7 +319,9 @@ export class TaskStore { return { docs: tasks + // @ts-expect-error @elastic/elasticsearch `Hid._id` expected to be `string` .filter((doc) => this.serializer.isRawSavedObject(doc)) + // @ts-expect-error @elastic/elasticsearch `Hid._id` expected to be `string` .map((doc) => this.serializer.rawToSavedObject(doc)) .map((doc) => omit(doc, 'namespace') as SavedObject) .map(savedObjectToConcreteTaskInstance), @@ -330,10 +336,8 @@ export class TaskStore { aggs, query, size = 0, - }: TSearchRequest): Promise> { - const { body } = await this.esClient.search< - ESSearchResponse - >({ + }: TSearchRequest): Promise> { + const { body } = await this.esClient.search({ index: this.index, ignore_unavailable: true, body: ensureAggregationOnlyReturnsTaskObjects({ @@ -355,14 +359,14 @@ export class TaskStore { const { // eslint-disable-next-line @typescript-eslint/naming-convention body: { total, updated, version_conflicts }, - } = await this.esClient.updateByQuery({ + } = await this.esClient.updateByQuery({ index: this.index, ignore_unavailable: true, refresh: true, - max_docs, conflicts: 'proceed', body: { ...opts, + max_docs, query, }, }); @@ -374,7 +378,9 @@ export class TaskStore { ); return { + // @ts-expect-error @elastic/elasticsearch declares UpdateByQueryResponse.total as optional total, + // @ts-expect-error @elastic/elasticsearch declares UpdateByQueryResponse.total as optional updated, version_conflicts: conflictsCorrectedForContinuation, }; @@ -393,11 +399,13 @@ export class TaskStore { * `max_docs`, but we bias in favour of over zealous `version_conflicts` as that's the best indicator we * have for an unhealthy cluster distribution of Task Manager polling intervals */ + export function correctVersionConflictsForContinuation( - updated: ReindexResponseBase['updated'], - versionConflicts: ReindexResponseBase['version_conflicts'], + updated: estypes.ReindexResponse['updated'], + versionConflicts: estypes.ReindexResponse['version_conflicts'], maxDocs?: number -) { +): number { + // @ts-expect-error estypes.ReindexResponse['updated'] and estypes.ReindexResponse['version_conflicts'] can be undefined return maxDocs && versionConflicts + updated > maxDocs ? maxDocs - updated : versionConflicts; } diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts index 64d9aee7b0ac7d..3db9f06bd8bf6a 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts @@ -4,30 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { ElasticsearchClient } from 'src/core/server'; -// From https://www.elastic.co/guide/en/elasticsearch/reference/current/get-license.html -export interface ESLicense { - status: string; - uid: string; - hkey: string; - type: string; - issue_date: string; - issue_date_in_millis: number; - expiry_date: string; - expiry_date_in_millis: number; - max_nodes: number; - issued_to: string; - issuer: string; - start_date_in_millis: number; - max_resource_units: number; -} +export type ESLicense = estypes.LicenseInformation; let cachedLicense: ESLicense | undefined; async function fetchLicense(esClient: ElasticsearchClient, local: boolean) { - const { body } = await esClient.license.get<{ license: ESLicense }>({ + const { body } = await esClient.license.get({ local, // For versions >= 7.6 and < 8.0, this flag is needed otherwise 'platinum' is returned for 'enterprise' license. accept_enterprise: true, diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.test.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.test.ts index 12f2f24502ce0b..6ddb10e825684e 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.test.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { estypes } from '@elastic/elasticsearch'; import { coreMock, elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; import { getStatsWithXpack } from './get_stats_with_xpack'; @@ -81,8 +82,8 @@ function mockEsClient() { body: { cluster_uuid: 'test', cluster_name: 'test', - version: { number: '8.0.0' }, - }, + version: { number: '8.0.0' } as estypes.ElasticsearchVersionInfo, + } as estypes.RootNodeInfoResponse, } ); diff --git a/x-pack/plugins/transform/server/routes/api/field_histograms.ts b/x-pack/plugins/transform/server/routes/api/field_histograms.ts index bfe2f470785690..bb2a1b278a5c20 100644 --- a/x-pack/plugins/transform/server/routes/api/field_histograms.ts +++ b/x-pack/plugins/transform/server/routes/api/field_histograms.ts @@ -41,6 +41,7 @@ export function registerFieldHistogramsRoutes({ router, license }: RouteDependen query, fields, samplerShardSize, + // @ts-expect-error script is not compatible with StoredScript from @elastic/elasticsearch: string is not supported runtimeMappings ); diff --git a/x-pack/plugins/transform/server/routes/api/privileges.ts b/x-pack/plugins/transform/server/routes/api/privileges.ts index d54a62b27f2629..f1900079ec9c49 100644 --- a/x-pack/plugins/transform/server/routes/api/privileges.ts +++ b/x-pack/plugins/transform/server/routes/api/privileges.ts @@ -32,7 +32,6 @@ export function registerPrivilegesRoute({ router, license }: RouteDependencies) const { body: { has_all_requested: hasAllPrivileges, cluster }, } = await ctx.core.elasticsearch.client.asCurrentUser.security.hasPrivileges({ - method: 'POST', body: { cluster: APP_CLUSTER_PRIVILEGES, }, @@ -45,9 +44,7 @@ export function registerPrivilegesRoute({ router, license }: RouteDependencies) // Get all index privileges the user has const { body: { indices }, - } = await ctx.core.elasticsearch.client.asCurrentUser.security.getUserPrivileges({ - method: 'GET', - }); + } = await ctx.core.elasticsearch.client.asCurrentUser.security.getUserPrivileges(); // Check if they have all the required index privileges for at least one index const oneIndexWithAllPrivileges = indices.find(({ privileges }: { privileges: string[] }) => { diff --git a/x-pack/plugins/transform/server/routes/api/transforms.ts b/x-pack/plugins/transform/server/routes/api/transforms.ts index 93f5caf7cf5b09..1dd136cb484fa4 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms.ts @@ -206,6 +206,7 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) { await ctx.core.elasticsearch.client.asCurrentUser.transform .putTransform({ + // @ts-expect-error @elastic/elasticsearch max_page_search_size is required in TransformPivot body: req.body, transform_id: transformId, }) @@ -250,6 +251,7 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) { const { body, } = await ctx.core.elasticsearch.client.asCurrentUser.transform.updateTransform({ + // @ts-expect-error query doesn't satisfy QueryContainer from @elastic/elasticsearch body: req.body, transform_id: transformId, }); @@ -452,9 +454,12 @@ async function deleteTransforms( transform_id: transformId, }); const transformConfig = body.transforms[0]; + // @ts-expect-error @elastic/elasticsearch doesn't provide typings for Transform destinationIndex = Array.isArray(transformConfig.dest.index) - ? transformConfig.dest.index[0] - : transformConfig.dest.index; + ? // @ts-expect-error @elastic/elasticsearch doesn't provide typings for Transform + transformConfig.dest.index[0] + : // @ts-expect-error @elastic/elasticsearch doesn't provide typings for Transform + transformConfig.dest.index; } catch (getTransformConfigError) { transformDeleted.error = getTransformConfigError.meta.body.error; results[transformId] = { @@ -539,6 +544,7 @@ const previewTransformHandler: RequestHandler< try { const reqBody = req.body; const { body } = await ctx.core.elasticsearch.client.asCurrentUser.transform.previewTransform({ + // @ts-expect-error max_page_search_size is required in TransformPivot body: reqBody, }); if (isLatestTransform(reqBody)) { diff --git a/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts b/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts index 4a6b2c674e4297..b762f20b55b481 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts @@ -6,7 +6,7 @@ */ import { transformIdParamSchema, TransformIdParamSchema } from '../../../common/api_schemas/common'; -import { AuditMessage, TransformMessage } from '../../../common/types/messages'; +import { AuditMessage } from '../../../common/types/messages'; import { RouteDependencies } from '../../types'; @@ -78,19 +78,28 @@ export function registerTransformsAuditMessagesRoutes({ router, license }: Route } try { - const { body: resp } = await ctx.core.elasticsearch.client.asCurrentUser.search({ + const { + body: resp, + } = await ctx.core.elasticsearch.client.asCurrentUser.search({ index: ML_DF_NOTIFICATION_INDEX_PATTERN, ignore_unavailable: true, size: SIZE, body: { - sort: [{ timestamp: { order: 'desc' } }, { transform_id: { order: 'asc' } }], + sort: [ + { timestamp: { order: 'desc' as const } }, + { transform_id: { order: 'asc' as const } }, + ], query, }, }); - let messages: TransformMessage[] = []; - if (resp.hits.total.value > 0) { - messages = resp.hits.hits.map((hit: AuditMessage) => hit._source); + let messages: AuditMessage[] = []; + // TODO: remove typeof checks when appropriate overloading is added for the `search` API + if ( + (typeof resp.hits.total === 'number' && resp.hits.total > 0) || + (typeof resp.hits.total === 'object' && resp.hits.total.value > 0) + ) { + messages = resp.hits.hits.map((hit) => hit._source!); messages.reverse(); } return res.ok({ body: messages }); diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts index 78462d9969929a..98212f1dc6aaf6 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { Logger, ElasticsearchClient } from 'kibana/server'; import { DEFAULT_GROUPS } from '../index'; import { getDateRangeInfo } from './date_range_info'; @@ -127,14 +127,14 @@ export async function timeSeriesQuery( const logPrefix = 'indexThreshold timeSeriesQuery: callCluster'; logger.debug(`${logPrefix} call: ${JSON.stringify(esQuery)}`); - let esResult: SearchResponse; + let esResult: estypes.SearchResponse; // note there are some commented out console.log()'s below, which are left // in, as they are VERY useful when debugging these queries; debug logging // isn't as nice since it's a single long JSON line. // console.log('time_series_query.ts request\n', JSON.stringify(esQuery, null, 4)); try { - esResult = (await esClient.search>(esQuery, { ignore: [404] })).body; + esResult = (await esClient.search(esQuery, { ignore: [404] })).body; } catch (err) { // console.log('time_series_query.ts error\n', JSON.stringify(err, null, 4)); logger.warn(`${logPrefix} error: ${err.message}`); @@ -149,7 +149,7 @@ export async function timeSeriesQuery( function getResultFromEs( isCountAgg: boolean, isGroupAgg: boolean, - esResult: SearchResponse + esResult: estypes.SearchResponse ): TimeSeriesResult { const aggregations = esResult?.aggregations || {}; @@ -164,6 +164,7 @@ function getResultFromEs( delete aggregations.dateAgg; } + // @ts-expect-error @elastic/elasticsearch Aggregate does not specify buckets const groupBuckets = aggregations.groupAgg?.buckets || []; const result: TimeSeriesResult = { results: [], diff --git a/x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts b/x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts index c029f5b8bdaed0..3245a9b8a983f7 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts @@ -105,7 +105,9 @@ async function getIndicesFromPattern( return []; } - return (response.aggregations as IndiciesAggregation).indices.buckets.map((bucket) => bucket.key); + return ((response.aggregations as unknown) as IndiciesAggregation).indices.buckets.map( + (bucket) => bucket.key + ); } async function getAliasesFromPattern( @@ -118,14 +120,15 @@ async function getAliasesFromPattern( }; const result: string[] = []; - const { body: response } = await esClient.indices.getAlias(params); + const response = await esClient.indices.getAlias(params); + const responseBody = response.body; - if (response.status === 404) { + if (response.statusCode === 404) { return result; } - for (const index of Object.keys(response)) { - const aliasRecord = response[index]; + for (const index of Object.keys(responseBody)) { + const aliasRecord = responseBody[index]; if (aliasRecord.aliases) { const aliases = Object.keys(aliasRecord.aliases); result.push(...aliases); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts index d352ab77b56f36..d2595916496a5b 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts @@ -15,7 +15,7 @@ export const esIndicesStateCheck = async ( asCurrentUser: ElasticsearchClient, indices: string[] ): Promise => { - const { body: response } = await asCurrentUser.indices.resolveIndex({ + const { body: response } = await asCurrentUser.indices.resolveIndex({ name: '*', expand_wildcards: 'all', }); @@ -23,7 +23,7 @@ export const esIndicesStateCheck = async ( const result: StatusCheckResult = {}; indices.forEach((index) => { - result[index] = getIndexState(index, response); + result[index] = getIndexState(index, response as ResolveIndexResponseFromES); }); return result; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts index 9ab8d0aa7cffb6..2620fe31d62777 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts @@ -31,6 +31,7 @@ describe('getUpgradeAssistantStatus', () => { const esClient = elasticsearchServiceMock.createScopedClusterClient(); esClient.asCurrentUser.migration.deprecations.mockResolvedValue( + // @ts-expect-error not full interface asApiResponse(deprecationsResponse) ); @@ -48,6 +49,7 @@ describe('getUpgradeAssistantStatus', () => { it('returns readyForUpgrade === false when critical issues found', async () => { esClient.asCurrentUser.migration.deprecations.mockResolvedValue( + // @ts-expect-error not full interface asApiResponse({ cluster_settings: [{ level: 'critical', message: 'Do count me', url: 'https://...' }], node_settings: [], @@ -64,6 +66,7 @@ describe('getUpgradeAssistantStatus', () => { it('returns readyForUpgrade === true when no critical issues found', async () => { esClient.asCurrentUser.migration.deprecations.mockResolvedValue( + // @ts-expect-error not full interface asApiResponse({ cluster_settings: [{ level: 'warning', message: 'Do not count me', url: 'https://...' }], node_settings: [], @@ -80,6 +83,7 @@ describe('getUpgradeAssistantStatus', () => { it('filters out security realm deprecation on Cloud', async () => { esClient.asCurrentUser.migration.deprecations.mockResolvedValue( + // @ts-expect-error not full interface asApiResponse({ cluster_settings: [ { diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts index 3486603341674d..e775190d426dfd 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts @@ -19,9 +19,7 @@ export async function getUpgradeAssistantStatus( dataClient: IScopedClusterClient, isCloudEnabled: boolean ): Promise { - const { - body: deprecations, - } = await dataClient.asCurrentUser.migration.deprecations(); + const { body: deprecations } = await dataClient.asCurrentUser.migration.deprecations(); const cluster = getClusterDeprecations(deprecations, isCloudEnabled); const indices = getCombinedIndexInfos(deprecations); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts index 592c2d15b9c0c5..cffd49e5bd38a4 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts @@ -283,6 +283,7 @@ describe('ReindexActions', () => { it('returns flat settings', async () => { clusterClient.asCurrentUser.indices.get.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ myIndex: { settings: { 'index.mySetting': '1' }, diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts index a91cf8ddeada9d..ae0a6f97e29d55 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts @@ -105,6 +105,7 @@ describe('reindexService', () => { it('calls security API with basic requirements', async () => { clusterClient.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ has_all_requested: true }) ); @@ -130,6 +131,7 @@ describe('reindexService', () => { it('includes manage_ml for ML indices', async () => { clusterClient.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ has_all_requested: true }) ); @@ -154,6 +156,7 @@ describe('reindexService', () => { it('includes checking for permissions on the baseName which could be an alias', async () => { clusterClient.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ has_all_requested: true }) ); @@ -183,6 +186,7 @@ describe('reindexService', () => { it('includes manage_watcher for watcher indices', async () => { clusterClient.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ has_all_requested: true, }) @@ -440,6 +444,7 @@ describe('reindexService', () => { }, } as any); + // @ts-expect-error not full interface clusterClient.asCurrentUser.tasks.cancel.mockResolvedValueOnce(asApiResponse(true)); await service.cancelReindexing('myIndex'); @@ -564,6 +569,7 @@ describe('reindexService', () => { f() ); clusterClient.asCurrentUser.nodes.info.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ nodes: { nodeX: { version: '6.7.0-alpha' } } }) ); clusterClient.asCurrentUser.ml.setUpgradeMode.mockResolvedValueOnce( @@ -596,6 +602,7 @@ describe('reindexService', () => { ); clusterClient.asCurrentUser.nodes.info.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ nodes: { nodeX: { version: '6.7.0-alpha' } } }) ); clusterClient.asCurrentUser.ml.setUpgradeMode.mockResolvedValueOnce( @@ -646,6 +653,7 @@ describe('reindexService', () => { f() ); clusterClient.asCurrentUser.nodes.info.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ nodes: { nodeX: { version: '6.7.0' } } }) ); clusterClient.asCurrentUser.ml.setUpgradeMode.mockResolvedValueOnce( @@ -670,6 +678,7 @@ describe('reindexService', () => { f() ); clusterClient.asCurrentUser.nodes.info.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ nodes: { nodeX: { version: '6.6.0' } } }) ); @@ -784,7 +793,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.readonly); expect(clusterClient.asCurrentUser.indices.putSettings).toHaveBeenCalledWith({ index: 'myIndex', - body: { 'index.blocks.write': true }, + body: { index: { blocks: { write: true } } }, }); }); @@ -823,6 +832,7 @@ describe('reindexService', () => { it('creates new index with settings and mappings and updates lastCompletedStep', async () => { actions.getFlatSettings.mockResolvedValueOnce(settingsMappings); clusterClient.asCurrentUser.indices.create.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ acknowledged: true }) ); const updatedOp = await service.processNextStep(reindexOp); @@ -839,10 +849,12 @@ describe('reindexService', () => { it('fails if create index is not acknowledged', async () => { clusterClient.asCurrentUser.indices.get.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ myIndex: settingsMappings }) ); clusterClient.asCurrentUser.indices.create.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ acknowledged: false }) ); const updatedOp = await service.processNextStep(reindexOp); @@ -854,6 +866,7 @@ describe('reindexService', () => { it('fails if create index fails', async () => { clusterClient.asCurrentUser.indices.get.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ myIndex: settingsMappings }) ); @@ -872,7 +885,7 @@ describe('reindexService', () => { // Original index should have been set back to allow reads. expect(clusterClient.asCurrentUser.indices.putSettings).toHaveBeenCalledWith({ index: 'myIndex', - body: { 'index.blocks.write': false }, + body: { index: { blocks: { write: false } } }, }); }); }); @@ -932,6 +945,7 @@ describe('reindexService', () => { describe('reindex task is not complete', () => { it('updates reindexTaskPercComplete', async () => { clusterClient.asCurrentUser.tasks.get.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ completed: false, task: { status: { created: 10, total: 100 } }, @@ -947,6 +961,7 @@ describe('reindexService', () => { describe('reindex task is complete', () => { it('deletes task, updates reindexTaskPercComplete, updates lastCompletedStep', async () => { clusterClient.asCurrentUser.tasks.get.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ completed: true, task: { status: { created: 100, total: 100 } }, @@ -954,12 +969,14 @@ describe('reindexService', () => { ); clusterClient.asCurrentUser.count.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ count: 100, }) ); clusterClient.asCurrentUser.delete.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ result: 'deleted', }) @@ -976,6 +993,7 @@ describe('reindexService', () => { it('fails if docs created is less than count in source index', async () => { clusterClient.asCurrentUser.tasks.get.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ completed: true, task: { status: { created: 95, total: 95 } }, @@ -983,6 +1001,7 @@ describe('reindexService', () => { ); clusterClient.asCurrentUser.count.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ count: 100, }) @@ -999,6 +1018,7 @@ describe('reindexService', () => { describe('reindex task is cancelled', () => { it('deletes task, updates status to cancelled', async () => { clusterClient.asCurrentUser.tasks.get.mockResolvedValueOnce( + // @ts-expect-error not full interface asApiResponse({ completed: true, task: { status: { created: 100, total: 100, canceled: 'by user request' } }, @@ -1006,6 +1026,7 @@ describe('reindexService', () => { ); clusterClient.asCurrentUser.delete.mockResolvedValue( + // @ts-expect-error not full interface asApiResponse({ result: 'deleted' }) ); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts index 1b5f91e0c53b84..b9d5b42a65250f 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts @@ -226,7 +226,7 @@ export const reindexServiceFactory = ( if (reindexOp.attributes.lastCompletedStep >= ReindexStep.readonly) { await esClient.indices.putSettings({ index: reindexOp.attributes.indexName, - body: { 'index.blocks.write': false }, + body: { index: { blocks: { write: false } } }, }); } @@ -290,7 +290,7 @@ export const reindexServiceFactory = ( const { indexName } = reindexOp.attributes; const { body: putReadonly } = await esClient.indices.putSettings({ index: indexName, - body: { 'index.blocks.write': true }, + body: { index: { blocks: { write: true } } }, }); if (!putReadonly.acknowledged) { @@ -363,7 +363,10 @@ export const reindexServiceFactory = ( return actions.updateReindexOp(reindexOp, { lastCompletedStep: ReindexStep.reindexStarted, - reindexTaskId: startReindexResponse.task, + reindexTaskId: + startReindexResponse.task === undefined + ? startReindexResponse.task + : String(startReindexResponse.task), reindexTaskPercComplete: 0, reindexOptions: { ...(reindexOptions ?? {}), @@ -389,11 +392,11 @@ export const reindexServiceFactory = ( if (!taskResponse.completed) { // Updated the percent complete - const perc = taskResponse.task.status.created / taskResponse.task.status.total; + const perc = taskResponse.task.status!.created / taskResponse.task.status!.total; return actions.updateReindexOp(reindexOp, { reindexTaskPercComplete: perc, }); - } else if (taskResponse.task.status.canceled === 'by user request') { + } else if (taskResponse.task.status?.canceled === 'by user request') { // Set the status to cancelled reindexOp = await actions.updateReindexOp(reindexOp, { status: ReindexStatus.cancelled, @@ -403,9 +406,11 @@ export const reindexServiceFactory = ( reindexOp = await cleanupChanges(reindexOp); } else { // Check that it reindexed all documents - const { body: count } = await esClient.count({ index: reindexOp.attributes.indexName }); + const { + body: { count }, + } = await esClient.count({ index: reindexOp.attributes.indexName }); - if (taskResponse.task.status.created < count) { + if (taskResponse.task.status!.created < count) { // Include the entire task result in the error message. This should be guaranteed // to be JSON-serializable since it just came back from Elasticsearch. throw error.reindexTaskFailed(`Reindexing failed: ${JSON.stringify(taskResponse)}`); diff --git a/x-pack/plugins/uptime/common/utils/as_mutable_array.ts b/x-pack/plugins/uptime/common/utils/as_mutable_array.ts new file mode 100644 index 00000000000000..ce1d7e607ec4c5 --- /dev/null +++ b/x-pack/plugins/uptime/common/utils/as_mutable_array.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// Sometimes we use `as const` to have a more specific type, +// because TypeScript by default will widen the value type of an +// array literal. Consider the following example: +// +// const filter = [ +// { term: { 'agent.name': 'nodejs' } }, +// { range: { '@timestamp': { gte: 'now-15m ' }} +// ]; + +// The result value type will be: + +// const filter: ({ +// term: { +// 'agent.name'?: string +// }; +// range?: undefined +// } | { +// term?: undefined; +// range: { +// '@timestamp': { +// gte: string +// } +// } +// })[]; + +// This can sometimes leads to issues. In those cases, we can +// use `as const`. However, the Readonly type is not compatible +// with Array. This function returns a mutable version of a type. + +export function asMutableArray>( + arr: T +): T extends Readonly<[...infer U]> ? U : unknown[] { + return arr as any; +} diff --git a/x-pack/plugins/uptime/server/lib/lib.ts b/x-pack/plugins/uptime/server/lib/lib.ts index 1a7cef504b0190..12e58b94cfa500 100644 --- a/x-pack/plugins/uptime/server/lib/lib.ts +++ b/x-pack/plugins/uptime/server/lib/lib.ts @@ -16,7 +16,7 @@ import { UMBackendFrameworkAdapter } from './adapters'; import { UMLicenseCheck } from './domains'; import { UptimeRequests } from './requests'; import { savedObjectsAdapter } from './saved_objects'; -import { ESSearchResponse } from '../../../../../typings/elasticsearch'; +import { ESSearchRequest, ESSearchResponse } from '../../../../../typings/elasticsearch'; export interface UMDomainLibs { requests: UptimeRequests; @@ -54,7 +54,9 @@ export function createUptimeESClient({ return { baseESClient: esClient, - async search(params: TParams): Promise<{ body: ESSearchResponse }> { + async search( + params: TParams + ): Promise<{ body: ESSearchResponse }> { let res: any; let esError: any; const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index 2824fa9af35ad4..1b20ed9085fefc 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -48,7 +48,7 @@ export const getCerts: UMElasticsearchQueryFn = asyn { multi_match: { query: escape(search), - type: 'phrase_prefix', + type: 'phrase_prefix' as const, fields: [ 'monitor.id.text', 'monitor.name.text', @@ -98,7 +98,7 @@ export const getCerts: UMElasticsearchQueryFn = asyn field: 'monitor.id', }, name: 'monitors', - sort: [{ 'monitor.id': 'asc' }], + sort: [{ 'monitor.id': 'asc' as const }], }, }, aggs: { @@ -154,7 +154,7 @@ export const getCerts: UMElasticsearchQueryFn = asyn const sha1 = server?.hash?.sha1; const sha256 = server?.hash?.sha256; - const monitors = hit.inner_hits.monitors.hits.hits.map((monitor: any) => ({ + const monitors = hit.inner_hits!.monitors.hits.hits.map((monitor: any) => ({ name: monitor._source?.monitor.name, id: monitor._source?.monitor.id, url: monitor._source?.url?.full, diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts index de37688b155f5a..560653950c6f88 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { UMElasticsearchQueryFn } from '../adapters/framework'; import { SyntheticsJourneyApiResponse } from '../../../common/runtime_types'; @@ -30,7 +31,7 @@ export const getJourneyDetails: UMElasticsearchQueryFn< 'synthetics.type': 'journey/start', }, }, - ], + ] as QueryContainer[], }, }, size: 1, @@ -55,7 +56,7 @@ export const getJourneyDetails: UMElasticsearchQueryFn< 'synthetics.type': 'journey/start', }, }, - ], + ] as QueryContainer[], }, }, _source: ['@timestamp', 'monitor.check_group'], @@ -78,7 +79,7 @@ export const getJourneyDetails: UMElasticsearchQueryFn< ], }, }, - sort: [{ '@timestamp': { order: 'desc' } }], + sort: [{ '@timestamp': { order: 'desc' as const } }], }; const nextParams = { @@ -97,7 +98,7 @@ export const getJourneyDetails: UMElasticsearchQueryFn< ], }, }, - sort: [{ '@timestamp': { order: 'asc' } }], + sort: [{ '@timestamp': { order: 'asc' as const } }], }; const { body: previousJourneyResult } = await uptimeEsClient.search({ body: previousParams }); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts index 9865bd95fe9613..5c4e263468947b 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts @@ -5,6 +5,9 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; +import { SearchHit } from '../../../../../../typings/elasticsearch'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { UMElasticsearchQueryFn } from '../adapters/framework'; import { Ping } from '../../../common/runtime_types'; @@ -35,10 +38,13 @@ export const getJourneyFailedSteps: UMElasticsearchQueryFn { + return ((result.hits.hits as Array>).map((h) => { const source = h._source as Ping & { '@timestamp': string }; return { ...source, diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts index faa260eb9abd47..6a533d558c7211 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { UMElasticsearchQueryFn } from '../adapters/framework'; import { Ping } from '../../../common/runtime_types/ping'; @@ -33,7 +34,7 @@ export const getJourneyScreenshot: UMElasticsearchQueryFn< 'synthetics.type': 'step/screenshot', }, }, - ], + ] as QueryContainer[], }, }, aggs: { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts index 43d17cb9381597..bfb8f4d881c3d8 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts @@ -5,6 +5,9 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; +import { SearchHit } from 'typings/elasticsearch/search'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { UMElasticsearchQueryFn } from '../adapters/framework'; import { Ping } from '../../../common/runtime_types'; @@ -42,10 +45,13 @@ export const getJourneySteps: UMElasticsearchQueryFn (h?._source as Ping).synthetics?.type === 'step/screenshot') - .map((h) => (h?._source as Ping).synthetics?.step?.index as number); + const screenshotIndexes: number[] = (result.hits.hits as Array>) + .filter((h) => h._source?.synthetics?.type === 'step/screenshot') + .map((h) => h._source?.synthetics?.step?.index as number); - return (result.hits.hits - .filter((h) => (h?._source as Ping).synthetics?.type !== 'step/screenshot') + return ((result.hits.hits as Array>) + .filter((h) => h._source?.synthetics?.type !== 'step/screenshot') .map((h) => { const source = h._source as Ping & { '@timestamp': string }; return { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts index 82958167341c09..6f88e7e37e55e6 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { UMElasticsearchQueryFn } from '../adapters/framework'; import { Ping } from '../../../common/runtime_types/ping'; @@ -18,7 +19,7 @@ export const getStepLastSuccessfulStep: UMElasticsearchQueryFn< GetStepScreenshotParams, any > = async ({ uptimeEsClient, monitorId, stepIndex, timestamp }) => { - const lastSuccessCheckParams = { + const lastSuccessCheckParams: estypes.SearchRequest['body'] = { size: 1, sort: [ { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts index 14190413ac7e7e..ad8b345d7b276d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { UMElasticsearchQueryFn } from '../adapters'; import { Ping } from '../../../common/runtime_types'; @@ -44,20 +45,20 @@ export const getLatestMonitor: UMElasticsearchQueryFn = async ({ uptimeEsClient, monitorId, dateStart, dateEnd }) => { - const sortOptions: SortOptions = [ + const sortOptions = [ { '@timestamp': { - order: 'desc', + order: 'desc' as const, }, }, ]; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts index ef90794e634b77..16aca6f8238fbe 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts @@ -152,7 +152,7 @@ export const getHistogramForMonitors = async ( }; const { body: result } = await queryContext.search({ body: params }); - const histoBuckets: any[] = result.aggregations?.histogram.buckets ?? []; + const histoBuckets: any[] = (result.aggregations as any)?.histogram.buckets ?? []; const simplified = histoBuckets.map((histoBucket: any): { timestamp: number; byId: any } => { const byId: { [key: string]: number } = {}; histoBucket.by_id.buckets.forEach((idBucket: any) => { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts index b588127b064830..8b46070f701f2d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts @@ -6,6 +6,8 @@ */ import { JsonObject } from 'src/plugins/kibana_utils/public'; +import { QueryContainer } from '@elastic/elasticsearch/api/types'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { UMElasticsearchQueryFn } from '../adapters'; import { Ping } from '../../../common/runtime_types/ping'; @@ -68,7 +70,7 @@ export const getMonitorStatus: UMElasticsearchQueryFn< }, // append user filters, if defined ...(filters?.bool ? [filters] : []), - ], + ] as QueryContainer[], }, }, size: 0, @@ -81,7 +83,7 @@ export const getMonitorStatus: UMElasticsearchQueryFn< * to tell Elasticsearch where it should start on subsequent queries. */ ...(afterKey ? { after: afterKey } : {}), - sources: [ + sources: asMutableArray([ { monitorId: { terms: { @@ -104,7 +106,7 @@ export const getMonitorStatus: UMElasticsearchQueryFn< }, }, }, - ], + ] as const), }, aggs: { fields: { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts b/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts index 970af80576cad8..246b2001a93810 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { UMElasticsearchQueryFn } from '../adapters/framework'; import { NetworkEvent } from '../../../common/runtime_types'; @@ -29,7 +30,7 @@ export const getNetworkEvents: UMElasticsearchQueryFn< { term: { 'synthetics.type': 'journey/network_info' } }, { term: { 'monitor.check_group': checkGroup } }, { term: { 'synthetics.step.index': Number(stepIndex) } }, - ], + ] as QueryContainer[], }, }, // NOTE: This limit may need tweaking in the future. Users can technically perform multiple diff --git a/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts index d59da8029f1b95..0362ac30ac7132 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts @@ -53,7 +53,7 @@ export const getPingHistogram: UMElasticsearchQueryFn< { multi_match: { query: escape(query), - type: 'phrase_prefix', + type: 'phrase_prefix' as const, fields: ['monitor.id.text', 'monitor.name.text', 'url.full.text'], }, }, @@ -68,7 +68,7 @@ export const getPingHistogram: UMElasticsearchQueryFn< date_histogram: { field: '@timestamp', fixed_interval: bucketSize || minInterval + 'ms', - missing: 0, + missing: '0', }, aggs: { down: { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_pings.ts b/x-pack/plugins/uptime/server/lib/requests/get_pings.ts index 453663ba737fbd..cf7a85e60f753a 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_pings.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_pings.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { UMElasticsearchQueryFn } from '../adapters/framework'; import { GetPingsParams, @@ -73,13 +74,13 @@ export const getPings: UMElasticsearchQueryFn = a { range: { '@timestamp': { gte: from, lte: to } } }, ...(monitorId ? [{ term: { 'monitor.id': monitorId } }] : []), ...(status ? [{ term: { 'monitor.status': status } }] : []), - ], + ] as QueryContainer[], ...REMOVE_NON_SUMMARY_BROWSER_CHECKS, }, }, sort: [{ '@timestamp': { order: (sort ?? 'desc') as 'asc' | 'desc' } }], ...((locations ?? []).length > 0 - ? { post_filter: { terms: { 'observer.geo.name': locations } } } + ? { post_filter: { terms: { 'observer.geo.name': (locations as unknown) as string[] } } } : {}), }; diff --git a/x-pack/plugins/uptime/server/lib/requests/helper.ts b/x-pack/plugins/uptime/server/lib/requests/helper.ts index e3969f84c84852..c637c050946671 100644 --- a/x-pack/plugins/uptime/server/lib/requests/helper.ts +++ b/x-pack/plugins/uptime/server/lib/requests/helper.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SearchResponse } from '@elastic/elasticsearch/api/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ElasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { @@ -49,7 +50,7 @@ export const setupMockEsCompositeQuery = ( }, }; esMock.search.mockResolvedValueOnce({ - body: mockResponse, + body: (mockResponse as unknown) as SearchResponse, statusCode: 200, headers: {}, warnings: [], diff --git a/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts b/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts index 639a24a2bdffa4..179e9e809e59b9 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts @@ -22,7 +22,7 @@ export const findPotentialMatches = async ( const { body: queryResult } = await query(queryContext, searchAfter, size); const monitorIds: string[] = []; - (queryResult.aggregations?.monitors.buckets ?? []).forEach((b) => { + (queryResult.aggregations?.monitors.buckets ?? []).forEach((b: any) => { const monitorId = b.key.monitor_id; monitorIds.push(monitorId as string); }); @@ -40,7 +40,8 @@ const query = async (queryContext: QueryContext, searchAfter: any, size: number) body, }; - return await queryContext.search(params); + const response = await queryContext.search(params); + return response; }; const queryBody = async (queryContext: QueryContext, searchAfter: any, size: number) => { @@ -77,7 +78,9 @@ const queryBody = async (queryContext: QueryContext, searchAfter: any, size: num size, sources: [ { - monitor_id: { terms: { field: 'monitor.id', order: queryContext.cursorOrder() } }, + monitor_id: { + terms: { field: 'monitor.id' as const, order: queryContext.cursorOrder() }, + }, }, ], }, diff --git a/x-pack/test/accessibility/apps/index_lifecycle_management.ts b/x-pack/test/accessibility/apps/index_lifecycle_management.ts index d6ba222e50eb44..0305de740dc99a 100644 --- a/x-pack/test/accessibility/apps/index_lifecycle_management.ts +++ b/x-pack/test/accessibility/apps/index_lifecycle_management.ts @@ -55,6 +55,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { + // @ts-expect-error @elastic/elasticsearch DeleteSnapshotLifecycleRequest.policy_id is required await esClient.ilm.deleteLifecycle({ policy: TEST_POLICY_NAME }); }); diff --git a/x-pack/test/accessibility/apps/ml.ts b/x-pack/test/accessibility/apps/ml.ts index 164c7032d9dd36..41f8d5e56f8e6a 100644 --- a/x-pack/test/accessibility/apps/ml.ts +++ b/x-pack/test/accessibility/apps/ml.ts @@ -114,10 +114,11 @@ export default function ({ getService }: FtrProviderContext) { description: 'Test calendar', }); await ml.api.createCalendarEvents(calendarId, [ + // @ts-expect-error not full interface { description: eventDescription, - start_time: 1513641600000, - end_time: 1513728000000, + start_time: '1513641600000', + end_time: '1513728000000', }, ]); diff --git a/x-pack/test/api_integration/apis/es/has_privileges.ts b/x-pack/test/api_integration/apis/es/has_privileges.ts index 7403d27c4dd4bc..4cd1f70d4fffc0 100644 --- a/x-pack/test/api_integration/apis/es/has_privileges.ts +++ b/x-pack/test/api_integration/apis/es/has_privileges.ts @@ -32,6 +32,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'hp_read_user', body: { cluster: [], + // @ts-expect-error unknown property index: [], applications: [ { diff --git a/x-pack/test/api_integration/apis/lens/telemetry.ts b/x-pack/test/api_integration/apis/lens/telemetry.ts index 56b44234f065c0..32b6a446a57b29 100644 --- a/x-pack/test/api_integration/apis/lens/telemetry.ts +++ b/x-pack/test/api_integration/apis/lens/telemetry.ts @@ -7,7 +7,6 @@ import moment from 'moment'; import expect from '@kbn/expect'; -import { Client } from '@elastic/elasticsearch'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -20,7 +19,7 @@ const COMMON_HEADERS = { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es: Client = getService('es'); + const es = getService('es'); async function assertExpectedSavedObjects(num: number) { // Make sure that new/deleted docs are available to search @@ -42,6 +41,7 @@ export default ({ getService }: FtrProviderContext) => { beforeEach(async () => { await es.deleteByQuery({ index: '.kibana', + // @ts-expect-error @elastic/elasticsearch DeleteByQueryRequest doesn't accept q parameter q: 'type:lens-ui-telemetry', wait_for_completion: true, refresh: true, @@ -52,6 +52,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await es.deleteByQuery({ index: '.kibana', + // @ts-expect-error @elastic/elasticsearch DeleteByQueryRequest doesn't accept q parameter q: 'type:lens-ui-telemetry', wait_for_completion: true, refresh: true, diff --git a/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts index d1527db6b66ea3..21c1c1efbf9e8e 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts @@ -25,6 +25,7 @@ export default ({ getService }: FtrProviderContext) => { before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); await ml.testResources.setKibanaTimeZoneToUTC(); + // @ts-expect-error not full interface await ml.api.createAnomalyDetectionJob(testJobConfig); }); diff --git a/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts index 422d00a21ce159..f6c4c98e1f7ea3 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts @@ -25,6 +25,7 @@ export default ({ getService }: FtrProviderContext) => { for (let i = 0; i < testSetupJobConfigs.length; i++) { const job = testSetupJobConfigs[i]; const annotationToIndex = testSetupAnnotations[i]; + // @ts-expect-error not full interface await ml.api.createAnomalyDetectionJob(job); await ml.api.indexAnnotation(annotationToIndex); } diff --git a/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts index ac8438170c8824..c0e81955ad5957 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts @@ -26,6 +26,7 @@ export default ({ getService }: FtrProviderContext) => { for (let i = 0; i < testSetupJobConfigs.length; i++) { const job = testSetupJobConfigs[i]; const annotationToIndex = testSetupAnnotations[i]; + // @ts-expect-error not full interface await ml.api.createAnomalyDetectionJob(job); await ml.api.indexAnnotation(annotationToIndex); } diff --git a/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts index 41406cb3443883..346f4562b77bb1 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts @@ -38,6 +38,7 @@ export default ({ getService }: FtrProviderContext) => { for (let i = 0; i < testSetupJobConfigs.length; i++) { const job = testSetupJobConfigs[i]; const annotationToIndex = testSetupAnnotations[i]; + // @ts-expect-error not full interface await ml.api.createAnomalyDetectionJob(job); await ml.api.indexAnnotation(annotationToIndex); } @@ -54,7 +55,7 @@ export default ({ getService }: FtrProviderContext) => { const originalAnnotation = annotationsForJob[0]; const annotationUpdateRequestBody = { ...commonAnnotationUpdateRequestBody, - job_id: originalAnnotation._source.job_id, + job_id: originalAnnotation._source?.job_id, _id: originalAnnotation._id, }; @@ -85,7 +86,7 @@ export default ({ getService }: FtrProviderContext) => { const originalAnnotation = annotationsForJob[0]; const annotationUpdateRequestBody = { ...commonAnnotationUpdateRequestBody, - job_id: originalAnnotation._source.job_id, + job_id: originalAnnotation._source?.job_id, _id: originalAnnotation._id, }; @@ -116,7 +117,7 @@ export default ({ getService }: FtrProviderContext) => { const annotationUpdateRequestBody = { ...commonAnnotationUpdateRequestBody, - job_id: originalAnnotation._source.job_id, + job_id: originalAnnotation._source?.job_id, _id: originalAnnotation._id, }; @@ -143,7 +144,7 @@ export default ({ getService }: FtrProviderContext) => { timestamp: Date.now(), end_timestamp: Date.now(), annotation: 'Updated annotation', - job_id: originalAnnotation._source.job_id, + job_id: originalAnnotation._source?.job_id, type: ANNOTATION_TYPE.ANNOTATION, event: 'model_change', detector_index: 2, diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts index acbfcbe7acd475..03ee82eed27d02 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts @@ -52,6 +52,7 @@ export default ({ getService }: FtrProviderContext) => { ]; for (const jobConfig of mockJobConfigs) { + // @ts-expect-error not full interface await ml.api.createAnomalyDetectionJob(jobConfig); } } diff --git a/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts index fac62237aa74e5..01b87e6059a29d 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts @@ -52,7 +52,11 @@ export default ({ getService }: FtrProviderContext) => { expect(createdCalendar.description).to.eql(requestBody.description); expect(createdCalendar.job_ids).to.eql(requestBody.job_ids); - await ml.api.waitForEventsToExistInCalendar(calendarId, requestBody.events); + await ml.api.waitForEventsToExistInCalendar( + calendarId, + // @ts-expect-error not full interface + requestBody.events + ); }); it('should not create new calendar for user without required permission', async () => { diff --git a/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts index a2e1709731aa74..dfbffad9dafdd0 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts @@ -34,6 +34,7 @@ export default ({ getService }: FtrProviderContext) => { beforeEach(async () => { await ml.api.createCalendar(calendarId, testCalendar); + // @ts-expect-error not full interface await ml.api.createCalendarEvents(calendarId, testEvents); }); diff --git a/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts index 243a40abe97a4a..175c678317e6c1 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts @@ -35,6 +35,7 @@ export default ({ getService }: FtrProviderContext) => { beforeEach(async () => { for (const testCalendar of testCalendars) { await ml.api.createCalendar(testCalendar.calendar_id, testCalendar); + // @ts-expect-error not full interface await ml.api.createCalendarEvents(testCalendar.calendar_id, testEvents); } }); @@ -54,6 +55,7 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.have.length(testCalendars.length); expect(body[0].events).to.have.length(testEvents.length); + // @ts-expect-error not full interface ml.api.assertAllEventsExistInCalendar(testEvents, body[0]); }); @@ -66,6 +68,7 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.have.length(testCalendars.length); expect(body[0].events).to.have.length(testEvents.length); + // @ts-expect-error not full interface ml.api.assertAllEventsExistInCalendar(testEvents, body[0]); }); @@ -89,6 +92,7 @@ export default ({ getService }: FtrProviderContext) => { beforeEach(async () => { await ml.api.createCalendar(calendarId, testCalendar); + // @ts-expect-error not full interface await ml.api.createCalendarEvents(calendarId, testEvents); }); @@ -106,6 +110,7 @@ export default ({ getService }: FtrProviderContext) => { expect(body.job_ids).to.eql(testCalendar.job_ids); expect(body.description).to.eql(testCalendar.description); expect(body.events).to.have.length(testEvents.length); + // @ts-expect-error not full interface ml.api.assertAllEventsExistInCalendar(testEvents, body); }); @@ -119,6 +124,7 @@ export default ({ getService }: FtrProviderContext) => { expect(body.job_ids).to.eql(testCalendar.job_ids); expect(body.description).to.eql(testCalendar.description); expect(body.events).to.have.length(testEvents.length); + // @ts-expect-error not full interface ml.api.assertAllEventsExistInCalendar(testEvents, body); }); diff --git a/x-pack/test/api_integration/apis/ml/calendars/helpers.ts b/x-pack/test/api_integration/apis/ml/calendars/helpers.ts index 7ddfc4b41679d8..f80c985c2676eb 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/helpers.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/helpers.ts @@ -5,13 +5,16 @@ * 2.0. */ -import { Calendar, CalendarEvent } from '../../../../../plugins/ml/server/models/calendar'; +import { estypes } from '@elastic/elasticsearch'; +import { Calendar } from '../../../../../plugins/ml/server/models/calendar'; + +type ScheduledEvent = estypes.ScheduledEvent; export const assertAllEventsExistInCalendar = ( - eventsToCheck: CalendarEvent[], + eventsToCheck: ScheduledEvent[], calendar: Calendar ): boolean => { - const updatedCalendarEvents = calendar.events as CalendarEvent[]; + const updatedCalendarEvents = calendar.events; let allEventsAreUpdated = true; for (const eventToCheck of eventsToCheck) { // if at least one of the events that we need to check is not in the updated events diff --git a/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts index 1ca9a663199436..4798a3407be115 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts @@ -42,6 +42,7 @@ export default ({ getService }: FtrProviderContext) => { beforeEach(async () => { await ml.api.createCalendar(calendarId, originalCalendar); + // @ts-expect-error not full interface await ml.api.createCalendarEvents(calendarId, originalEvents); }); @@ -70,6 +71,7 @@ export default ({ getService }: FtrProviderContext) => { expect(updatedEvents).to.have.length(updateCalendarRequestBody.events.length); await ml.api.waitForEventsToExistInCalendar( updatedCalendar.calendar_id, + // @ts-expect-error not full interface updateCalendarRequestBody.events ); }); diff --git a/x-pack/test/api_integration/apis/ml/jobs/common_jobs.ts b/x-pack/test/api_integration/apis/ml/jobs/common_jobs.ts index 5039ea550d47e6..2136e16e3c1534 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/common_jobs.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/common_jobs.ts @@ -7,6 +7,7 @@ import { Job, Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; +// @ts-expect-error not full interface export const SINGLE_METRIC_JOB_CONFIG: Job = { job_id: `jobs_summary_fq_single_${Date.now()}`, description: 'mean(responsetime) on farequote dataset with 15m bucket span', @@ -26,6 +27,7 @@ export const SINGLE_METRIC_JOB_CONFIG: Job = { model_plot_config: { enabled: true }, }; +// @ts-expect-error not full interface export const MULTI_METRIC_JOB_CONFIG: Job = { job_id: `jobs_summary_fq_multi_${Date.now()}`, description: 'mean(responsetime) partition=airline on farequote dataset with 1h bucket span', @@ -40,6 +42,7 @@ export const MULTI_METRIC_JOB_CONFIG: Job = { model_plot_config: { enabled: true }, }; +// @ts-expect-error not full interface export const DATAFEED_CONFIG: Datafeed = { datafeed_id: 'REPLACE', indices: ['ft_farequote'], diff --git a/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts b/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts index fbca2f2abcce39..1505e6fbe09bea 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts @@ -16,6 +16,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); const ml = getService('ml'); + // @ts-expect-error not full interface const JOB_CONFIG: Job = { job_id: `fq_multi_1_ae`, description: @@ -35,6 +36,7 @@ export default ({ getService }: FtrProviderContext) => { model_plot_config: { enabled: true }, }; + // @ts-expect-error not full interface const DATAFEED_CONFIG: Datafeed = { datafeed_id: 'datafeed-fq_multi_1_se', indices: ['ft_farequote'], diff --git a/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts b/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts index 9952dca651ba09..ef677969d006f0 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts @@ -43,6 +43,7 @@ export default ({ getService }: FtrProviderContext) => { daily_model_snapshot_retention_after_days: 1, allow_lazy_open: false, }; + // @ts-expect-error not full interface const testDatafeedConfig: Datafeed = { datafeed_id: `datafeed-${jobId}`, indices: ['ft_module_sample_logs'], @@ -54,6 +55,7 @@ export default ({ getService }: FtrProviderContext) => { before(async () => { await esArchiver.loadIfNeeded('ml/module_sample_logs'); await ml.testResources.setKibanaTimeZoneToUTC(); + // @ts-expect-error not full interface await ml.api.createAndRunAnomalyDetectionLookbackJob(testJobConfig, testDatafeedConfig); }); diff --git a/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts b/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts index 7b03e0e3091dec..d00999b06b588c 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts @@ -40,6 +40,7 @@ export default ({ getService }: FtrProviderContext) => { daily_model_snapshot_retention_after_days: 1, allow_lazy_open: false, }; + // @ts-expect-error not full interface const datafeedConfig: Datafeed = { datafeed_id: `datafeed-${jobId}`, indices: ['ft_module_sample_logs'], @@ -50,6 +51,7 @@ export default ({ getService }: FtrProviderContext) => { return { testDescription: `stop_on_warn is ${stopOnWarn}`, jobId, + // @ts-expect-error not full interface jobConfig: { job_id: jobId, ...commonJobConfig, diff --git a/x-pack/test/api_integration/apis/security/roles.ts b/x-pack/test/api_integration/apis/security/roles.ts index 440bb4ca32f189..09b2d2eef9fbe4 100644 --- a/x-pack/test/api_integration/apis/security/roles.ts +++ b/x-pack/test/api_integration/apis/security/roles.ts @@ -312,6 +312,7 @@ export default function ({ getService }: FtrProviderContext) { metadata: { foo: 'test-metadata', }, + // @ts-expect-error @elastic/elasticsearch PutRoleRequest.body doesn't declare `transient_metadata` property transient_metadata: { enabled: true, }, diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts b/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts index 7055c60b6cd348..a85e8ef82fc8c2 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts @@ -37,7 +37,7 @@ export default function ({ getService }: FtrProviderContext) { let stats: Record; before('disable monitoring and pull local stats', async () => { - await es.cluster.put_settings({ body: disableCollection }); + await es.cluster.putSettings({ body: disableCollection }); await new Promise((r) => setTimeout(r, 1000)); const { body } = await supertest diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts b/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts index 749bea29613488..9e9211c4b58938 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts @@ -25,15 +25,13 @@ export default function optInTest({ getService }: FtrProviderContext) { await supertest.put('/api/telemetry/v2/userHasSeenNotice').set('kbn-xsrf', 'xxx').expect(200); const { - body: { - _source: { telemetry }, - }, - } = await client.get({ + body: { _source }, + } = await client.get<{ telemetry: { userHasSeenNotice: boolean } }>({ index: '.kibana', id: 'telemetry:telemetry', }); - expect(telemetry.userHasSeenNotice).to.be(true); + expect(_source?.telemetry.userHasSeenNotice).to.be(true); }); }); } diff --git a/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts index 950fde37e30784..c202111f0e5e4d 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts @@ -438,10 +438,10 @@ export default ({ getService }: FtrProviderContext): void => { }); // There should be no change in their status since syncing is disabled - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses.open ); - expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be( CaseStatuses.open ); @@ -471,10 +471,10 @@ export default ({ getService }: FtrProviderContext): void => { }); // There should still be no change in their status since syncing is disabled - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses.open ); - expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be( CaseStatuses.open ); @@ -500,10 +500,10 @@ export default ({ getService }: FtrProviderContext): void => { }); // alerts should be updated now that the - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses.closed ); - expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be( CaseStatuses['in-progress'] ); }); @@ -573,10 +573,10 @@ export default ({ getService }: FtrProviderContext): void => { let signals = await getSignals(); // There should be no change in their status since syncing is disabled expect( - signals.get(defaultSignalsIndex)?.get(signalIDInFirstIndex)?._source.signal.status + signals.get(defaultSignalsIndex)?.get(signalIDInFirstIndex)?._source?.signal.status ).to.be(CaseStatuses.open); expect( - signals.get(signalsIndex2)?.get(signalIDInSecondIndex)?._source.signal.status + signals.get(signalsIndex2)?.get(signalIDInSecondIndex)?._source?.signal.status ).to.be(CaseStatuses.open); const updatedIndWithStatus: CasesResponse = (await setStatus({ @@ -597,10 +597,10 @@ export default ({ getService }: FtrProviderContext): void => { // There should still be no change in their status since syncing is disabled expect( - signals.get(defaultSignalsIndex)?.get(signalIDInFirstIndex)?._source.signal.status + signals.get(defaultSignalsIndex)?.get(signalIDInFirstIndex)?._source?.signal.status ).to.be(CaseStatuses.open); expect( - signals.get(signalsIndex2)?.get(signalIDInSecondIndex)?._source.signal.status + signals.get(signalsIndex2)?.get(signalIDInSecondIndex)?._source?.signal.status ).to.be(CaseStatuses.open); // turn on the sync settings @@ -623,15 +623,15 @@ export default ({ getService }: FtrProviderContext): void => { // alerts should be updated now that the expect( - signals.get(defaultSignalsIndex)?.get(signalIDInFirstIndex)?._source.signal.status + signals.get(defaultSignalsIndex)?.get(signalIDInFirstIndex)?._source?.signal.status ).to.be(CaseStatuses.closed); expect( - signals.get(signalsIndex2)?.get(signalIDInSecondIndex)?._source.signal.status + signals.get(signalsIndex2)?.get(signalIDInSecondIndex)?._source?.signal.status ).to.be(CaseStatuses.closed); // the duplicate signal id in the other index should not be affect (so its status should be open) expect( - signals.get(defaultSignalsIndex)?.get(signalIDInSecondIndex)?._source.signal.status + signals.get(defaultSignalsIndex)?.get(signalIDInSecondIndex)?._source?.signal.status ).to.be(CaseStatuses.open); }); }); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts index d647bb09f804a0..e5cc2489a12e99 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts @@ -96,7 +96,7 @@ export default function ({ getService }: FtrProviderContext) { let signals = await getSignalsWithES({ es, indices: defaultSignalsIndex, ids: signalID }); - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses.open ); @@ -116,7 +116,7 @@ export default function ({ getService }: FtrProviderContext) { signals = await getSignalsWithES({ es, indices: defaultSignalsIndex, ids: signalID }); - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses['in-progress'] ); }); @@ -156,10 +156,10 @@ export default function ({ getService }: FtrProviderContext) { ids: [signalID, signalID2], }); - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses.open ); - expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be( CaseStatuses.open ); @@ -183,10 +183,10 @@ export default function ({ getService }: FtrProviderContext) { ids: [signalID, signalID2], }); - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses['in-progress'] ); - expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be( CaseStatuses['in-progress'] ); }); @@ -244,10 +244,10 @@ export default function ({ getService }: FtrProviderContext) { }); // There should be no change in their status since syncing is disabled - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses.open ); - expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be( CaseStatuses.open ); @@ -272,10 +272,10 @@ export default function ({ getService }: FtrProviderContext) { }); // There still should be no change in their status since syncing is disabled - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses.open ); - expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be( CaseStatuses.open ); @@ -302,10 +302,10 @@ export default function ({ getService }: FtrProviderContext) { ids: [signalID, signalID2], }); - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses.closed ); - expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be( CaseStatuses['in-progress'] ); }); @@ -366,10 +366,10 @@ export default function ({ getService }: FtrProviderContext) { }); // There should be no change in their status since syncing is disabled - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses.open ); - expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be( CaseStatuses.open ); @@ -408,10 +408,10 @@ export default function ({ getService }: FtrProviderContext) { }); // There should still be no change in their status since syncing is disabled - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses.open ); - expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be( CaseStatuses.open ); @@ -453,10 +453,10 @@ export default function ({ getService }: FtrProviderContext) { }); // alerts should be updated now that the - expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal.status).to.be( CaseStatuses['in-progress'] ); - expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source.signal.status).to.be( + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal.status).to.be( CaseStatuses.closed ); }); diff --git a/x-pack/test/case_api_integration/common/lib/utils.ts b/x-pack/test/case_api_integration/common/lib/utils.ts index 96806af37e2c12..6fb108f69ad220 100644 --- a/x-pack/test/case_api_integration/common/lib/utils.ts +++ b/x-pack/test/case_api_integration/common/lib/utils.ts @@ -6,10 +6,11 @@ */ import expect from '@kbn/expect'; -import { Client } from '@elastic/elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; + import * as st from 'supertest'; import supertestAsPromised from 'supertest-as-promised'; -import { Explanation, SearchResponse } from 'elasticsearch'; import { CASES_URL, SUB_CASES_PATCH_DEL_URL } from '../../../../plugins/cases/common/constants'; import { CasesConfigureRequest, @@ -28,19 +29,11 @@ import { getSubCasesUrl } from '../../../../plugins/cases/common/api/helpers'; import { ContextTypeGeneratedAlertType } from '../../../../plugins/cases/server/connectors'; import { SignalHit } from '../../../../plugins/security_solution/server/lib/detection_engine/signals/types'; -interface Hit { - _index: string; - _type: string; - _id: string; - _score: number; - _source: T; - _version?: number; - _explanation?: Explanation; - fields?: any; - highlight?: any; - inner_hits?: any; - matched_queries?: string[]; - sort?: string[]; +function toArray(input: T | T[]): T[] { + if (Array.isArray(input)) { + return input; + } + return [input]; } /** @@ -51,11 +44,11 @@ export const getSignalsWithES = async ({ indices, ids, }: { - es: Client; + es: KibanaClient; indices: string | string[]; ids: string | string[]; -}): Promise>>> => { - const signals = await es.search>({ +}): Promise>>> => { + const signals = await es.search({ index: indices, body: { size: 10000, @@ -64,7 +57,7 @@ export const getSignalsWithES = async ({ filter: [ { ids: { - values: ids, + values: toArray(ids), }, }, ], @@ -76,13 +69,13 @@ export const getSignalsWithES = async ({ return signals.body.hits.hits.reduce((acc, hit) => { let indexMap = acc.get(hit._index); if (indexMap === undefined) { - indexMap = new Map>([[hit._id, hit]]); + indexMap = new Map>([[hit._id, hit]]); } else { indexMap.set(hit._id, hit); } acc.set(hit._index, indexMap); return acc; - }, new Map>>()); + }, new Map>>()); }; interface SetStatusCasesParams { @@ -346,7 +339,7 @@ export const removeServerGeneratedPropertiesFromConfigure = ( return rest; }; -export const deleteAllCaseItems = async (es: Client) => { +export const deleteAllCaseItems = async (es: KibanaClient) => { await Promise.all([ deleteCases(es), deleteSubCases(es), @@ -356,9 +349,10 @@ export const deleteAllCaseItems = async (es: Client) => { ]); }; -export const deleteCasesUserActions = async (es: Client): Promise => { +export const deleteCasesUserActions = async (es: KibanaClient): Promise => { await es.deleteByQuery({ index: '.kibana', + // @ts-expect-error @elastic/elasticsearch DeleteByQueryRequest doesn't accept q parameter q: 'type:cases-user-actions', wait_for_completion: true, refresh: true, @@ -366,9 +360,10 @@ export const deleteCasesUserActions = async (es: Client): Promise => { }); }; -export const deleteCases = async (es: Client): Promise => { +export const deleteCases = async (es: KibanaClient): Promise => { await es.deleteByQuery({ index: '.kibana', + // @ts-expect-error @elastic/elasticsearch DeleteByQueryRequest doesn't accept q parameter q: 'type:cases', wait_for_completion: true, refresh: true, @@ -380,9 +375,10 @@ export const deleteCases = async (es: Client): Promise => { * Deletes all sub cases in the .kibana index. This uses ES to perform the delete and does * not go through the case API. */ -export const deleteSubCases = async (es: Client): Promise => { +export const deleteSubCases = async (es: KibanaClient): Promise => { await es.deleteByQuery({ index: '.kibana', + // @ts-expect-error @elastic/elasticsearch DeleteByQueryRequest doesn't accept q parameter q: 'type:cases-sub-case', wait_for_completion: true, refresh: true, @@ -390,9 +386,10 @@ export const deleteSubCases = async (es: Client): Promise => { }); }; -export const deleteComments = async (es: Client): Promise => { +export const deleteComments = async (es: KibanaClient): Promise => { await es.deleteByQuery({ index: '.kibana', + // @ts-expect-error @elastic/elasticsearch DeleteByQueryRequest doesn't accept q parameter q: 'type:cases-comments', wait_for_completion: true, refresh: true, @@ -400,9 +397,10 @@ export const deleteComments = async (es: Client): Promise => { }); }; -export const deleteConfiguration = async (es: Client): Promise => { +export const deleteConfiguration = async (es: KibanaClient): Promise => { await es.deleteByQuery({ index: '.kibana', + // @ts-expect-error @elastic/elasticsearch DeleteByQueryRequest doesn't accept q parameter q: 'type:cases-configure', wait_for_completion: true, refresh: true, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_signals_migrations.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_signals_migrations.ts index dd0052b03382a9..8f7d2a0c01771c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_signals_migrations.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_signals_migrations.ts @@ -99,6 +99,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body: migrationResults } = await es.search({ index: newIndex }); expect(migrationResults.hits.hits).length(1); + // @ts-expect-error _source has unknown type const migratedSignal = migrationResults.hits.hits[0]._source.signal; expect(migratedSignal._meta.version).to.equal(SIGNALS_TEMPLATE_VERSION); }); diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 683d57081a2674..a9c128ee877034 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -6,7 +6,8 @@ */ import { KbnClient } from '@kbn/test'; -import { ApiResponse, Client } from '@elastic/elasticsearch'; +import type { ApiResponse } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { SuperTest } from 'supertest'; import supertestAsPromised from 'supertest-as-promised'; import { Context } from '@elastic/elasticsearch/lib/Transport'; @@ -383,7 +384,7 @@ export const deleteAllAlerts = async ( ); }; -export const downgradeImmutableRule = async (es: Client, ruleId: string): Promise => { +export const downgradeImmutableRule = async (es: KibanaClient, ruleId: string): Promise => { return countDownES(async () => { return es.updateByQuery({ index: '.kibana', @@ -408,9 +409,10 @@ export const downgradeImmutableRule = async (es: Client, ruleId: string): Promis * Remove all timelines from the .kibana index * @param es The ElasticSearch handle */ -export const deleteAllTimelines = async (es: Client): Promise => { +export const deleteAllTimelines = async (es: KibanaClient): Promise => { await es.deleteByQuery({ index: '.kibana', + // @ts-expect-error @elastic/elasticsearch DeleteByQueryRequest doesn't accept q parameter q: 'type:siem-ui-timeline', wait_for_completion: true, refresh: true, @@ -423,10 +425,11 @@ export const deleteAllTimelines = async (es: Client): Promise => { * This will retry 20 times before giving up and hopefully still not interfere with other tests * @param es The ElasticSearch handle */ -export const deleteAllRulesStatuses = async (es: Client): Promise => { +export const deleteAllRulesStatuses = async (es: KibanaClient): Promise => { return countDownES(async () => { return es.deleteByQuery({ index: '.kibana', + // @ts-expect-error @elastic/elasticsearch DeleteByQueryRequest doesn't accept q parameter q: 'type:siem-detection-engine-rule-status', wait_for_completion: true, refresh: true, @@ -1176,7 +1179,7 @@ export const getIndexNameFromLoad = (loadResponse: Record): str * @param esClient elasticsearch {@link Client} * @param index name of the index to query */ -export const waitForIndexToPopulate = async (es: Client, index: string): Promise => { +export const waitForIndexToPopulate = async (es: KibanaClient, index: string): Promise => { await waitFor(async () => { const response = await es.count<{ count: number }>({ index }); return response.body.count > 0; diff --git a/x-pack/test/fleet_api_integration/apis/agents/acks.ts b/x-pack/test/fleet_api_integration/apis/agents/acks.ts index 6b68d8b28a43ea..427c9c2394d9d1 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/acks.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/acks.ts @@ -35,6 +35,7 @@ export default function (providerContext: FtrProviderContext) { index: '.fleet-agents', id: 'agent1', }); + // @ts-expect-error has unknown type agentDoc.access_api_key_id = apiKey.id; await esClient.update({ index: '.fleet-agents', @@ -239,11 +240,11 @@ export default function (providerContext: FtrProviderContext) { }) .expect(200); - const res = await esClient.get({ + const res = await esClient.get<{ upgraded_at: unknown }>({ index: '.fleet-agents', id: 'agent1', }); - expect(res.body._source.upgraded_at).to.be.ok(); + expect(res.body._source?.upgraded_at).to.be.ok(); }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/agents/checkin.ts b/x-pack/test/fleet_api_integration/apis/agents/checkin.ts index 778063dff72c70..9150b81abf366c 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/checkin.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/checkin.ts @@ -38,6 +38,7 @@ export default function (providerContext: FtrProviderContext) { index: '.fleet-agents', id: 'agent1', }); + // @ts-expect-error agentDoc has unknown type agentDoc.access_api_key_id = apiKey.id; await esClient.update({ index: '.fleet-agents', diff --git a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts index 8924c090da0891..09a0d3c927e4cb 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts @@ -45,9 +45,11 @@ export default function (providerContext: FtrProviderContext) { index: '.fleet-agents', id: 'agent1', }); - // @ts-ignore + // @ts-expect-error agentDoc has unknown type agentDoc.access_api_key_id = accessAPIKeyId; + // @ts-expect-error agentDoc has unknown type agentDoc.default_api_key_id = outputAPIKeyBody.id; + // @ts-expect-error agentDoc has unknown type agentDoc.default_api_key = Buffer.from( `${outputAPIKeyBody.id}:${outputAPIKeyBody.api_key}` ).toString('base64'); diff --git a/x-pack/test/fleet_api_integration/apis/fleet_setup.ts b/x-pack/test/fleet_api_integration/apis/fleet_setup.ts index 237b9617dafb90..c9709475d182d9 100644 --- a/x-pack/test/fleet_api_integration/apis/fleet_setup.ts +++ b/x-pack/test/fleet_api_integration/apis/fleet_setup.ts @@ -80,6 +80,7 @@ export default function (providerContext: FtrProviderContext) { applications: [], run_as: [], metadata: {}, + // @ts-expect-error @elastic/elasticsearch PutRoleRequest.body doesn't declare transient_metadata property transient_metadata: { enabled: true }, }, }); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/aggregated_scripted_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection/aggregated_scripted_job.ts index e63aff3a081864..980060fea1c472 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/aggregated_scripted_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/aggregated_scripted_job.ts @@ -16,6 +16,7 @@ export default function ({ getService }: FtrProviderContext) { const supportedTestSuites = [ { suiteTitle: 'supported job with aggregation field', + // @ts-expect-error not convertable to Job type jobConfig: { job_id: `fq_supported_aggs_${ts}`, job_type: 'anomaly_detector', @@ -102,6 +103,7 @@ export default function ({ getService }: FtrProviderContext) { }, { suiteTitle: 'supported job with scripted field', + // @ts-expect-error not convertable to Job type jobConfig: { job_id: `fq_supported_script_${ts}`, job_type: 'anomaly_detector', @@ -176,6 +178,7 @@ export default function ({ getService }: FtrProviderContext) { const unsupportedTestSuites = [ { suiteTitle: 'unsupported job with bucket_script aggregation field', + // @ts-expect-error not convertable to Job type jobConfig: { job_id: `fq_unsupported_aggs_${ts}`, job_type: 'anomaly_detector', @@ -280,6 +283,7 @@ export default function ({ getService }: FtrProviderContext) { }, { suiteTitle: 'unsupported job with partition by of a scripted field', + // @ts-expect-error not convertable to Job type jobConfig: { job_id: `fq_unsupported_script_${ts}`, job_type: 'anomaly_detector', diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts b/x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts index 81d05ee94e6c49..97cc6be95ed934 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts @@ -8,6 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { Job, Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; +// @ts-expect-error doesn't implement the full interface const JOB_CONFIG: Job = { job_id: `fq_single_1_smv`, description: 'mean(responsetime) on farequote dataset with 15m bucket span', @@ -27,6 +28,7 @@ const JOB_CONFIG: Job = { model_plot_config: { enabled: true }, }; +// @ts-expect-error doesn't implement the full interface const DATAFEED_CONFIG: Datafeed = { datafeed_id: 'datafeed-fq_single_1_smv', indices: ['ft_farequote'], diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts b/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts index ff38544fa8c030..a08a5cfca2afde 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { Job, Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; +// @ts-expect-error not full interface const JOB_CONFIG: Job = { job_id: `fq_multi_1_ae`, description: @@ -28,6 +29,7 @@ const JOB_CONFIG: Job = { model_plot_config: { enabled: true }, }; +// @ts-expect-error not full interface const DATAFEED_CONFIG: Datafeed = { datafeed_id: 'datafeed-fq_multi_1_se', indices: ['ft_farequote'], diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts b/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts index ec25c997e27706..950ad2a702b06f 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts @@ -8,6 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { Job, Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; +// @ts-expect-error not full interface const JOB_CONFIG: Job = { job_id: `fq_single_1_smv`, description: 'mean(responsetime) on farequote dataset with 15m bucket span', @@ -27,6 +28,7 @@ const JOB_CONFIG: Job = { model_plot_config: { enabled: true }, }; +// @ts-expect-error not full interface const DATAFEED_CONFIG: Datafeed = { datafeed_id: 'datafeed-fq_single_1_smv', indices: ['ft_farequote'], @@ -91,6 +93,7 @@ export default function ({ getService }: FtrProviderContext) { }); describe('with entity fields', function () { + // @ts-expect-error not full interface const jobConfig: Job = { job_id: `ecom_01`, description: @@ -121,6 +124,7 @@ export default function ({ getService }: FtrProviderContext) { model_plot_config: { enabled: true }, }; + // @ts-expect-error not full interface const datafeedConfig: Datafeed = { datafeed_id: 'datafeed-ecom_01', indices: ['ft_ecommerce'], diff --git a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts index 8d29b611c0bf54..ac8ff055209c9f 100644 --- a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts @@ -136,10 +136,11 @@ export default function ({ getService }: FtrProviderContext) { description: 'Test calendar', }); await ml.api.createCalendarEvents(calendarId, [ + // @ts-expect-error not full interface { description: eventDescription, - start_time: 1513641600000, - end_time: 1513728000000, + start_time: '1513641600000', + end_time: '1513728000000', }, ]); diff --git a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts index 71ac9c00032dcd..95d0d209164296 100644 --- a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts @@ -137,10 +137,11 @@ export default function ({ getService }: FtrProviderContext) { description: 'Test calendar', }); await ml.api.createCalendarEvents(calendarId, [ + // @ts-expect-error not full interface { description: eventDescription, - start_time: 1513641600000, - end_time: 1513728000000, + start_time: '1513641600000', + end_time: '1513728000000', }, ]); diff --git a/x-pack/test/functional/apps/ml/settings/calendar_creation.ts b/x-pack/test/functional/apps/ml/settings/calendar_creation.ts index 968eafab21ab47..2beae2ad007b57 100644 --- a/x-pack/test/functional/apps/ml/settings/calendar_creation.ts +++ b/x-pack/test/functional/apps/ml/settings/calendar_creation.ts @@ -21,6 +21,7 @@ export default function ({ getService }: FtrProviderContext) { await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); await asyncForEach(jobConfigs, async (jobConfig) => { + // @ts-expect-error not full interface await ml.api.createAnomalyDetectionJob(jobConfig); }); await ml.testResources.setKibanaTimeZoneToUTC(); diff --git a/x-pack/test/functional/apps/ml/settings/calendar_edit.ts b/x-pack/test/functional/apps/ml/settings/calendar_edit.ts index 9242c2e1b594c1..1237cd8d71b8c4 100644 --- a/x-pack/test/functional/apps/ml/settings/calendar_edit.ts +++ b/x-pack/test/functional/apps/ml/settings/calendar_edit.ts @@ -27,6 +27,7 @@ export default function ({ getService }: FtrProviderContext) { await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); await asyncForEach(jobConfigs, async (jobConfig) => { + // @ts-expect-error not full interface await ml.api.createAnomalyDetectionJob(jobConfig); }); @@ -34,6 +35,7 @@ export default function ({ getService }: FtrProviderContext) { job_ids: jobConfigs.map((c) => c.job_id), description: 'Test calendar', }); + // @ts-expect-error not full interface await ml.api.createCalendarEvents(calendarId, testEvents); await ml.testResources.setKibanaTimeZoneToUTC(); diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index c9cfe1fee4ef9a..7d09deff6f6b77 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -5,10 +5,10 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import expect from '@kbn/expect'; import { ProvidedType } from '@kbn/test/types/ftr'; -import { IndexDocumentParams } from 'elasticsearch'; -import { Calendar, CalendarEvent } from '../../../../plugins/ml/server/models/calendar/index'; +import { Calendar } from '../../../../plugins/ml/server/models/calendar/index'; import { Annotation } from '../../../../plugins/ml/common/types/annotations'; import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -24,18 +24,8 @@ import { } from '../../../../plugins/ml/common/constants/index_patterns'; import { COMMON_REQUEST_HEADERS } from '../../../functional/services/ml/common_api'; -interface EsIndexResult { - _index: string; - _id: string; - _version: number; - result: string; - _shards: any; - _seq_no: number; - _primary_term: number; -} - export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { - const es = getService('legacyEs'); + const es = getService('es'); const log = getService('log'); const retry = getService('retry'); const esSupertest = getService('esSupertest'); @@ -44,7 +34,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { return { async hasJobResults(jobId: string): Promise { - const response = await es.search({ + const { body } = await es.search({ index: '.ml-anomalies-*', body: { size: 1, @@ -56,7 +46,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, }); - return response.hits.hits.length > 0; + return body.hits.hits.length > 0; }, async assertJobResultsExist(jobId: string) { @@ -84,7 +74,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, async hasDetectorResults(jobId: string, detectorIndex: number): Promise { - const response = await es.search({ + const { body } = await es.search({ index: '.ml-anomalies-*', body: { size: 1, @@ -112,7 +102,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, }); - return response.hits.hits.length > 0; + return body.hits.hits.length > 0; }, async assertDetectorResultsExist(jobId: string, detectorIndex: number) { @@ -133,13 +123,13 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async createIndices(indices: string) { log.debug(`Creating indices: '${indices}'...`); - if ((await es.indices.exists({ index: indices, allowNoIndices: false })) === true) { + if ((await es.indices.exists({ index: indices, allow_no_indices: false })).body === true) { log.debug(`Indices '${indices}' already exist. Nothing to create.`); return; } - const createResponse = await es.indices.create({ index: indices }); - expect(createResponse) + const { body } = await es.indices.create({ index: indices }); + expect(body) .to.have.property('acknowledged') .eql(true, 'Response for create request indices should be acknowledged.'); @@ -149,15 +139,15 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async deleteIndices(indices: string) { log.debug(`Deleting indices: '${indices}'...`); - if ((await es.indices.exists({ index: indices, allowNoIndices: false })) === false) { + if ((await es.indices.exists({ index: indices, allow_no_indices: false })).body === false) { log.debug(`Indices '${indices}' don't exist. Nothing to delete.`); return; } - const deleteResponse = await es.indices.delete({ + const { body } = await es.indices.delete({ index: indices, }); - expect(deleteResponse) + expect(body) .to.have.property('acknowledged') .eql(true, 'Response for delete request should be acknowledged.'); @@ -316,7 +306,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async assertIndicesExist(indices: string) { await retry.tryForTime(30 * 1000, async () => { - if ((await es.indices.exists({ index: indices, allowNoIndices: false })) === true) { + if ((await es.indices.exists({ index: indices, allow_no_indices: false })).body === true) { return true; } else { throw new Error(`indices '${indices}' should exist`); @@ -326,7 +316,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async assertIndicesNotToExist(indices: string) { await retry.tryForTime(30 * 1000, async () => { - if ((await es.indices.exists({ index: indices, allowNoIndices: false })) === false) { + if ((await es.indices.exists({ index: indices, allow_no_indices: false })).body === false) { return true; } else { throw new Error(`indices '${indices}' should not exist`); @@ -336,14 +326,14 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async assertIndicesNotEmpty(indices: string) { await retry.tryForTime(30 * 1000, async () => { - const response = await es.search({ + const { body } = await es.search({ index: indices, body: { size: 1, }, }); - if (response.hits.hits.length > 0) { + if (body.hits.hits.length > 0) { return true; } else { throw new Error(`indices '${indices}' should not be empty`); @@ -393,7 +383,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }); }, - async createCalendarEvents(calendarId: string, events: CalendarEvent[]) { + async createCalendarEvents(calendarId: string, events: estypes.ScheduledEvent[]) { log.debug(`Creating events for calendar with id '${calendarId}'...`); await esSupertest.post(`/_ml/calendars/${calendarId}/events`).send({ events }).expect(200); await this.waitForEventsToExistInCalendar(calendarId, events); @@ -405,10 +395,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, assertAllEventsExistInCalendar: ( - eventsToCheck: CalendarEvent[], + eventsToCheck: estypes.ScheduledEvent[], calendar: Calendar ): boolean => { - const updatedCalendarEvents = calendar.events as CalendarEvent[]; + const updatedCalendarEvents = calendar.events; let allEventsAreUpdated = true; for (const eventToCheck of eventsToCheck) { // if at least one of the events that we need to check is not in the updated events @@ -417,8 +407,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { updatedCalendarEvents.findIndex( (updatedEvent) => updatedEvent.description === eventToCheck.description && - updatedEvent.start_time === eventToCheck.start_time && - updatedEvent.end_time === eventToCheck.end_time + // updatedEvent are fetched with suptertest which converts start_time and end_time to number + // sometimes eventToCheck declared manually with types incompatible with estypes.ScheduledEvent + String(updatedEvent.start_time) === String(eventToCheck.start_time) && + String(updatedEvent.end_time) === String(eventToCheck.end_time) ) < 0 ) { allEventsAreUpdated = false; @@ -436,7 +428,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async waitForEventsToExistInCalendar( calendarId: string, - eventsToCheck: CalendarEvent[], + eventsToCheck: estypes.ScheduledEvent[], errorMsg?: string ) { await retry.waitForWithTimeout(`'${calendarId}' events to exist`, 5 * 1000, async () => { @@ -805,7 +797,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async getAnnotations(jobId: string) { log.debug(`Fetching annotations for job '${jobId}'...`); - const results = await es.search({ + const { body } = await es.search({ index: ML_ANNOTATIONS_INDEX_ALIAS_READ, body: { query: { @@ -815,16 +807,16 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, }, }); - expect(results).to.not.be(undefined); - expect(results).to.have.property('hits'); + expect(body).to.not.be(undefined); + expect(body).to.have.property('hits'); log.debug('> Annotations fetched.'); - return results.hits.hits; + return body.hits.hits; }, async getAnnotationById(annotationId: string): Promise { log.debug(`Fetching annotation '${annotationId}'...`); - const result = await es.search({ + const { body } = await es.search({ index: ML_ANNOTATIONS_INDEX_ALIAS_READ, body: { size: 1, @@ -836,9 +828,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, }); log.debug('> Annotation fetched.'); - // @ts-ignore due to outdated type for hits.total - if (result.hits.total.value === 1) { - return result?.hits?.hits[0]?._source as Annotation; + + // @ts-expect-error doesn't handle total as number + if (body.hits.total.value === 1) { + return body?.hits?.hits[0]?._source as Annotation; } return undefined; }, @@ -846,15 +839,15 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async indexAnnotation(annotationRequestBody: Partial) { log.debug(`Indexing annotation '${JSON.stringify(annotationRequestBody)}'...`); // @ts-ignore due to outdated type for IndexDocumentParams.type - const params: IndexDocumentParams> = { + const params = { index: ML_ANNOTATIONS_INDEX_ALIAS_WRITE, body: annotationRequestBody, refresh: 'wait_for', - }; - const results: EsIndexResult = await es.index(params); - await this.waitForAnnotationToExist(results._id); + } as const; + const { body } = await es.index(params); + await this.waitForAnnotationToExist(body._id); log.debug('> Annotation indexed.'); - return results; + return body; }, async waitForAnnotationToExist(annotationId: string, errorMsg?: string) { diff --git a/x-pack/test/functional/services/ml/common_config.ts b/x-pack/test/functional/services/ml/common_config.ts index a16c0b88de900a..af3d393a16754e 100644 --- a/x-pack/test/functional/services/ml/common_config.ts +++ b/x-pack/test/functional/services/ml/common_config.ts @@ -12,6 +12,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { Job, Datafeed } from '../../../../plugins/ml/common/types/anomaly_detection_jobs'; import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common'; +// @ts-expect-error not full interface const FQ_SM_JOB_CONFIG: Job = { job_id: ``, description: 'mean(responsetime) on farequote dataset with 15m bucket span', @@ -31,6 +32,7 @@ const FQ_SM_JOB_CONFIG: Job = { model_plot_config: { enabled: true }, }; +// @ts-expect-error not full interface const FQ_MM_JOB_CONFIG: Job = { job_id: `fq_multi_1_ae`, description: @@ -50,6 +52,7 @@ const FQ_MM_JOB_CONFIG: Job = { model_plot_config: { enabled: true }, }; +// @ts-expect-error not full interface const FQ_DATAFEED_CONFIG: Datafeed = { datafeed_id: '', indices: ['ft_farequote'], diff --git a/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts index c3859e1044b4fa..8fcf8be9fa4930 100644 --- a/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts @@ -80,8 +80,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { testJobId = job.job_id; // Set up jobs + // @ts-expect-error not full interface await ml.api.createAnomalyDetectionJob(job); await ml.api.openAnomalyDetectionJob(job.job_id); + // @ts-expect-error not full interface await ml.api.createDatafeed(datafeed); await ml.api.startDatafeed(datafeed.datafeed_id); await ml.api.waitForDatafeedState(datafeed.datafeed_id, DATAFEED_STATE.STARTED); diff --git a/x-pack/test/lists_api_integration/utils.ts b/x-pack/test/lists_api_integration/utils.ts index 7a0b1b7f7934a6..0512cede0a84f6 100644 --- a/x-pack/test/lists_api_integration/utils.ts +++ b/x-pack/test/lists_api_integration/utils.ts @@ -7,7 +7,7 @@ import { SuperTest } from 'supertest'; import supertestAsPromised from 'supertest-as-promised'; -import { Client } from '@elastic/elasticsearch'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { getImportListItemAsBuffer } from '../../plugins/lists/common/schemas/request/import_list_item_schema.mock'; import { @@ -157,10 +157,11 @@ export const binaryToString = (res: any, callback: any): void => { * This will retry 20 times before giving up and hopefully still not interfere with other tests * @param es The ElasticSearch handle */ -export const deleteAllExceptions = async (es: Client): Promise => { +export const deleteAllExceptions = async (es: KibanaClient): Promise => { return countDownES(async () => { return es.deleteByQuery({ index: '.kibana', + // @ts-expect-error @elastic/elasticsearch DeleteByQueryRequest doesn't accept q parameter q: 'type:exception-list or type:exception-list-agnostic', wait_for_completion: true, refresh: true, diff --git a/x-pack/test/observability_api_integration/trial/tests/annotations.ts b/x-pack/test/observability_api_integration/trial/tests/annotations.ts index 928d160a5df9ed..1ea3460060bc9f 100644 --- a/x-pack/test/observability_api_integration/trial/tests/annotations.ts +++ b/x-pack/test/observability_api_integration/trial/tests/annotations.ts @@ -8,7 +8,6 @@ import expect from '@kbn/expect'; import { JsonObject } from 'src/plugins/kibana_utils/common'; import { Annotation } from '../../../../plugins/observability/common/annotations'; -import { ESSearchHit } from '../../../../../typings/elasticsearch'; import { FtrProviderContext } from '../../common/ftr_provider_context'; const DEFAULT_INDEX_NAME = 'observability-annotations'; @@ -153,6 +152,7 @@ export default function annotationApiTests({ getService }: FtrProviderContext) { track_total_hits: true, }); + // @ts-expect-error doesn't handle number expect(search.body.hits.total.value).to.be(1); expect(search.body.hits.hits[0]._source).to.eql(response.body._source); @@ -236,16 +236,15 @@ export default function annotationApiTests({ getService }: FtrProviderContext) { }, }); - const initialSearch = await es.search({ + const initialSearch = await es.search({ index: DEFAULT_INDEX_NAME, track_total_hits: true, }); + // @ts-expect-error doesn't handler number expect(initialSearch.body.hits.total.value).to.be(2); - const [id1, id2] = initialSearch.body.hits.hits.map( - (hit: ESSearchHit) => hit._id - ); + const [id1, id2] = initialSearch.body.hits.hits.map((hit) => hit._id); expect( ( @@ -261,6 +260,7 @@ export default function annotationApiTests({ getService }: FtrProviderContext) { track_total_hits: true, }); + // @ts-expect-error doesn't handler number expect(searchAfterFirstDelete.body.hits.total.value).to.be(1); expect(searchAfterFirstDelete.body.hits.hits[0]._id).to.be(id2); @@ -279,6 +279,7 @@ export default function annotationApiTests({ getService }: FtrProviderContext) { track_total_hits: true, }); + // @ts-expect-error doesn't handle number expect(searchAfterSecondDelete.body.hits.total.value).to.be(0); }); }); diff --git a/x-pack/test/saved_object_api_integration/common/lib/create_users_and_roles.ts b/x-pack/test/saved_object_api_integration/common/lib/create_users_and_roles.ts index 38e566a7305069..2ba5ce3f7d5b17 100644 --- a/x-pack/test/saved_object_api_integration/common/lib/create_users_and_roles.ts +++ b/x-pack/test/saved_object_api_integration/common/lib/create_users_and_roles.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { SuperTest } from 'supertest'; -import { Client } from '@elastic/elasticsearch'; +import type { SuperTest } from 'supertest'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { AUTHENTICATION } from './authentication'; -export const createUsersAndRoles = async (es: Client, supertest: SuperTest) => { +export const createUsersAndRoles = async (es: KibanaClient, supertest: SuperTest) => { await supertest .put('/api/security/role/kibana_legacy_user') .send({ diff --git a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts index 84f84e87521221..bb46beef41449a 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts @@ -43,8 +43,10 @@ export default function ({ getService }: FtrProviderContext) { } async function getNumberOfSessionDocuments() { - return (await es.search({ index: '.kibana_security_session*' })).body.hits.total - .value as number; + return ( + // @ts-expect-error doesn't handle total as number + (await es.search({ index: '.kibana_security_session*' })).body.hits.total.value as number + ); } async function loginWithSAML(providerName: string) { diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts index 3d24efc9b8e749..0b17f037dfbd99 100644 --- a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts @@ -38,8 +38,10 @@ export default function ({ getService }: FtrProviderContext) { } async function getNumberOfSessionDocuments() { - return (await es.search({ index: '.kibana_security_session*' })).body.hits.total - .value as number; + return ( + // @ts-expect-error doesn't handle total as number + (await es.search({ index: '.kibana_security_session*' })).body.hits.total.value as number + ); } async function loginWithSAML(providerName: string) { diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/package.ts b/x-pack/test/security_solution_endpoint_api_int/apis/package.ts index 5d93b820f30164..d46b7723fcafe3 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/package.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/package.ts @@ -6,7 +6,6 @@ */ import expect from '@kbn/expect'; -import { SearchResponse } from 'elasticsearch'; import { ResolverPaginatedEvents, SafeEndpointEvent, @@ -49,7 +48,7 @@ export default function ({ getService }: FtrProviderContext) { const generator = new EndpointDocGenerator('data'); const searchForID = async (id: string) => { - return es.search>({ + return es.search({ index: eventsIndexPattern, body: { query: { @@ -57,7 +56,7 @@ export default function ({ getService }: FtrProviderContext) { filter: [ { ids: { - values: id, + values: [id], }, }, ], @@ -89,7 +88,7 @@ export default function ({ getService }: FtrProviderContext) { ); // ensure that the event was inserted into ES - expect(eventWithBothIPs.body.hits.hits[0]._source.event?.id).to.be( + expect(eventWithBothIPs.body.hits.hits[0]._source?.event?.id).to.be( eventWithoutNetworkObject.event?.id ); }); @@ -175,7 +174,7 @@ export default function ({ getService }: FtrProviderContext) { it('sets the event.ingested field', async () => { const resp = await searchForID(genData.eventsInfo[0]._id); - expect(resp.body.hits.hits[0]._source.event.ingested).to.not.be(undefined); + expect(resp.body.hits.hits[0]._source?.event.ingested).to.not.be(undefined); }); }); @@ -221,11 +220,11 @@ export default function ({ getService }: FtrProviderContext) { networkIndexData.eventsInfo[0]._id ); // Should be 'United States' - expect(eventWithBothIPs.body.hits.hits[0]._source.source.geo?.country_name).to.not.be( + expect(eventWithBothIPs.body.hits.hits[0]._source?.source.geo?.country_name).to.not.be( undefined ); // should be 'Iceland' - expect(eventWithBothIPs.body.hits.hits[0]._source.destination.geo?.country_name).to.not.be( + expect(eventWithBothIPs.body.hits.hits[0]._source?.destination.geo?.country_name).to.not.be( undefined ); @@ -233,18 +232,18 @@ export default function ({ getService }: FtrProviderContext) { networkIndexData.eventsInfo[1]._id ); // Should be 'United States' - expect(eventWithBothIPs.body.hits.hits[0]._source.source.geo?.country_name).to.not.be( + expect(eventWithBothIPs.body.hits.hits[0]._source?.source.geo?.country_name).to.not.be( undefined ); - expect(eventWithSourceOnly.body.hits.hits[0]._source.destination?.geo).to.be(undefined); + expect(eventWithSourceOnly.body.hits.hits[0]._source?.destination?.geo).to.be(undefined); }); it('does not set geoip fields for events in indices other than the network index', async () => { const eventWithBothIPs = await searchForID( processIndexData.eventsInfo[0]._id ); - expect(eventWithBothIPs.body.hits.hits[0]._source.source.geo).to.be(undefined); - expect(eventWithBothIPs.body.hits.hits[0]._source.destination.geo).to.be(undefined); + expect(eventWithBothIPs.body.hits.hits[0]._source?.source.geo).to.be(undefined); + expect(eventWithBothIPs.body.hits.hits[0]._source?.destination.geo).to.be(undefined); }); }); }); diff --git a/x-pack/test/security_solution_endpoint_api_int/services/resolver.ts b/x-pack/test/security_solution_endpoint_api_int/services/resolver.ts index add6539470cc1c..156043bd3c9183 100644 --- a/x-pack/test/security_solution_endpoint_api_int/services/resolver.ts +++ b/x-pack/test/security_solution_endpoint_api_int/services/resolver.ts @@ -49,14 +49,6 @@ interface BulkCreateHeader { }; } -interface BulkResponse { - items: Array<{ - create: { - _id: string; - }; - }>; -} - export function ResolverGeneratorProvider({ getService }: FtrProviderContext) { const client = getService('es'); @@ -69,12 +61,13 @@ export function ResolverGeneratorProvider({ getService }: FtrProviderContext) { array.push({ create: { _index: eventsIndex } }, doc); return array; }, []); - const bulkResp = await client.bulk({ body, refresh: true }); + const bulkResp = await client.bulk({ body, refresh: true }); const eventsInfo = events.map((event: Event, i: number) => { - return { event, _id: bulkResp.body.items[i].create._id }; + return { event, _id: bulkResp.body.items[i].create?._id }; }); + // @ts-expect-error @elastic/elasticsearch expected BulkResponseItemBase._id: string return { eventsInfo, indices: [eventsIndex] }; }, async createTrees( diff --git a/yarn.lock b/yarn.lock index 74bca3901dfe1f..0d7afc4293e70e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1378,16 +1378,16 @@ version "0.0.0" uid "" -"@elastic/elasticsearch@npm:@elastic/elasticsearch-canary@^8.0.0-canary.3": - version "8.0.0-canary.3" - resolved "https://registry.yarnpkg.com/@elastic/elasticsearch-canary/-/elasticsearch-canary-8.0.0-canary.3.tgz#b06b95b1370417ac700f30277814fbe7ad532760" - integrity sha512-D8kiFxip0IATzXS+5MAA3+4jnTPiJrpkW+FVNc9e3eq8iCIW/BIv9kPqEH55N/RlJxFwGTz5W4jmmFqBeamNFA== +"@elastic/elasticsearch@npm:@elastic/elasticsearch-canary@^8.0.0-canary.4": + version "8.0.0-canary.4" + resolved "https://registry.yarnpkg.com/@elastic/elasticsearch-canary/-/elasticsearch-canary-8.0.0-canary.4.tgz#6f1a592974941baae347eb8c66a2006848349717" + integrity sha512-UexFloloyvGOhvMc1ePRHCy89sjQL6rPTTZkXAB/GcC8rCvA3mgSnIY2+Ylvctdv9o4l+M4Bkw8azICulyhwMg== dependencies: - debug "^4.1.1" + debug "^4.3.1" hpagent "^0.1.1" - ms "^2.1.1" + ms "^2.1.3" pump "^3.0.0" - secure-json-parse "^2.1.0" + secure-json-parse "^2.3.1" "@elastic/ems-client@7.12.0": version "7.12.0" @@ -11177,7 +11177,7 @@ debug@3.X, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, dependencies: ms "^2.1.1" -debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0: +debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -20069,6 +20069,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + msgpackr-extract@^0.3.5, msgpackr-extract@^0.3.6: version "0.3.6" resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-0.3.6.tgz#f20c0a278e44377471b1fa2a3a75a32c87693755" @@ -24953,10 +24958,10 @@ scss-tokenizer@^0.2.3: js-base64 "^2.1.8" source-map "^0.4.2" -secure-json-parse@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.1.0.tgz#ae76f5624256b5c497af887090a5d9e156c9fb20" - integrity sha512-GckO+MS/wT4UogDyoI/H/S1L0MCcKS1XX/vp48wfmU7Nw4woBmb8mIpu4zPBQjKlRT88/bt9xdoV4111jPpNJA== +secure-json-parse@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.3.1.tgz#908aa5e806e223ff8d179d37ad95c2433f5f147d" + integrity sha512-5uGhQLHSC9tVa7RGPkSwxbZVsJCZvIODOadAimCXkU1aCa1fWdszj2DktcutK8A7dD58PoRdxTYiy0jFl6qjnw== seed-random@~2.2.0: version "2.2.0" From 1e87cef3e0659d1764d5a0eddb5bb0189535547a Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Thu, 25 Mar 2021 12:34:38 +0100 Subject: [PATCH 89/93] handling references for kibana_context and get_index_pattern expression functions (#95224) --- ...-public.executioncontext.getsavedobject.md | 13 -- ...ins-expressions-public.executioncontext.md | 1 - ...-server.executioncontext.getsavedobject.md | 13 -- ...ins-expressions-server.executioncontext.md | 1 - .../expressions/load_index_pattern.ts | 26 +++ .../search/expressions/kibana_context.ts | 167 +++++++++++------- .../search/expressions/kibana_context.ts | 39 ++++ .../data/public/search/search_service.ts | 8 +- .../search/expressions/kibana_context.ts | 46 +++++ .../data/server/search/search_service.ts | 4 +- .../expressions/common/execution/types.ts | 15 -- .../expressions/common/executor/executor.ts | 12 +- src/plugins/expressions/public/plugin.ts | 13 +- src/plugins/expressions/public/public.api.md | 3 - src/plugins/expressions/server/server.api.md | 3 - 15 files changed, 234 insertions(+), 130 deletions(-) delete mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.getsavedobject.md delete mode 100644 docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.getsavedobject.md create mode 100644 src/plugins/data/public/search/expressions/kibana_context.ts create mode 100644 src/plugins/data/server/search/expressions/kibana_context.ts diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.getsavedobject.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.getsavedobject.md deleted file mode 100644 index dffce4a0917182..00000000000000 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.getsavedobject.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExecutionContext](./kibana-plugin-plugins-expressions-public.executioncontext.md) > [getSavedObject](./kibana-plugin-plugins-expressions-public.executioncontext.getsavedobject.md) - -## ExecutionContext.getSavedObject property - -Allows to fetch saved objects from ElasticSearch. In browser `getSavedObject` function is provided automatically by the Expressions plugin. On the server the caller of the expression has to provide this context function. The reason is because on the browser we always know the user who tries to fetch a saved object, thus saved object client is scoped automatically to that user. However, on the server we can scope that saved object client to any user, or even not scope it at all and execute it as an "internal" user. - -Signature: - -```typescript -getSavedObject?: (type: string, id: string) => Promise>; -``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md index 901b46f0888d46..1388e04c315e23 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md @@ -18,7 +18,6 @@ export interface ExecutionContextAbortSignal | Adds ability to abort current execution. | | [getKibanaRequest](./kibana-plugin-plugins-expressions-public.executioncontext.getkibanarequest.md) | () => KibanaRequest | Getter to retrieve the KibanaRequest object inside an expression function. Useful for functions which are running on the server and need to perform operations that are scoped to a specific user. | -| [getSavedObject](./kibana-plugin-plugins-expressions-public.executioncontext.getsavedobject.md) | <T extends SavedObjectAttributes = SavedObjectAttributes>(type: string, id: string) => Promise<SavedObject<T>> | Allows to fetch saved objects from ElasticSearch. In browser getSavedObject function is provided automatically by the Expressions plugin. On the server the caller of the expression has to provide this context function. The reason is because on the browser we always know the user who tries to fetch a saved object, thus saved object client is scoped automatically to that user. However, on the server we can scope that saved object client to any user, or even not scope it at all and execute it as an "internal" user. | | [getSearchContext](./kibana-plugin-plugins-expressions-public.executioncontext.getsearchcontext.md) | () => ExecutionContextSearch | Get search context of the expression. | | [getSearchSessionId](./kibana-plugin-plugins-expressions-public.executioncontext.getsearchsessionid.md) | () => string | undefined | Search context in which expression should operate. | | [inspectorAdapters](./kibana-plugin-plugins-expressions-public.executioncontext.inspectoradapters.md) | InspectorAdapters | Adapters for inspector plugin. | diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.getsavedobject.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.getsavedobject.md deleted file mode 100644 index b8c8f4f3bb0675..00000000000000 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.getsavedobject.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExecutionContext](./kibana-plugin-plugins-expressions-server.executioncontext.md) > [getSavedObject](./kibana-plugin-plugins-expressions-server.executioncontext.getsavedobject.md) - -## ExecutionContext.getSavedObject property - -Allows to fetch saved objects from ElasticSearch. In browser `getSavedObject` function is provided automatically by the Expressions plugin. On the server the caller of the expression has to provide this context function. The reason is because on the browser we always know the user who tries to fetch a saved object, thus saved object client is scoped automatically to that user. However, on the server we can scope that saved object client to any user, or even not scope it at all and execute it as an "internal" user. - -Signature: - -```typescript -getSavedObject?: (type: string, id: string) => Promise>; -``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md index 39018599a2c92d..8503f81ad7d25d 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md @@ -18,7 +18,6 @@ export interface ExecutionContextAbortSignal | Adds ability to abort current execution. | | [getKibanaRequest](./kibana-plugin-plugins-expressions-server.executioncontext.getkibanarequest.md) | () => KibanaRequest | Getter to retrieve the KibanaRequest object inside an expression function. Useful for functions which are running on the server and need to perform operations that are scoped to a specific user. | -| [getSavedObject](./kibana-plugin-plugins-expressions-server.executioncontext.getsavedobject.md) | <T extends SavedObjectAttributes = SavedObjectAttributes>(type: string, id: string) => Promise<SavedObject<T>> | Allows to fetch saved objects from ElasticSearch. In browser getSavedObject function is provided automatically by the Expressions plugin. On the server the caller of the expression has to provide this context function. The reason is because on the browser we always know the user who tries to fetch a saved object, thus saved object client is scoped automatically to that user. However, on the server we can scope that saved object client to any user, or even not scope it at all and execute it as an "internal" user. | | [getSearchContext](./kibana-plugin-plugins-expressions-server.executioncontext.getsearchcontext.md) | () => ExecutionContextSearch | Get search context of the expression. | | [getSearchSessionId](./kibana-plugin-plugins-expressions-server.executioncontext.getsearchsessionid.md) | () => string | undefined | Search context in which expression should operate. | | [inspectorAdapters](./kibana-plugin-plugins-expressions-server.executioncontext.inspectoradapters.md) | InspectorAdapters | Adapters for inspector plugin. | diff --git a/src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts b/src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts index 37a28fea533429..1c50f0704910a8 100644 --- a/src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts +++ b/src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { IndexPatternsContract } from '../index_patterns'; import { IndexPatternSpec } from '..'; +import { SavedObjectReference } from '../../../../../core/types'; const name = 'indexPatternLoad'; const type = 'index_pattern'; @@ -57,4 +58,29 @@ export const getIndexPatternLoadMeta = (): Omit< }), }, }, + extract(state) { + const refName = 'indexPatternLoad.id'; + const references: SavedObjectReference[] = [ + { + name: refName, + type: 'search', + id: state.id[0] as string, + }, + ]; + return { + state: { + ...state, + id: [refName], + }, + references, + }; + }, + + inject(state, references) { + const reference = references.find((ref) => ref.name === 'indexPatternLoad.id'); + if (reference) { + state.id[0] = reference.id; + } + return state; + }, }); diff --git a/src/plugins/data/common/search/expressions/kibana_context.ts b/src/plugins/data/common/search/expressions/kibana_context.ts index 98d7a2c45b4fc1..22a7150d4a64e1 100644 --- a/src/plugins/data/common/search/expressions/kibana_context.ts +++ b/src/plugins/data/common/search/expressions/kibana_context.ts @@ -15,6 +15,14 @@ import { Query, uniqFilters } from '../../query'; import { ExecutionContextSearch, KibanaContext, KibanaFilter } from './kibana_context_type'; import { KibanaQueryOutput } from './kibana_context_type'; import { KibanaTimerangeOutput } from './timerange'; +import { SavedObjectReference } from '../../../../../core/types'; +import { SavedObjectsClientCommon } from '../../index_patterns'; +import { Filter } from '../../es_query/filters'; + +/** @internal */ +export interface KibanaContextStartDependencies { + savedObjectsClient: SavedObjectsClientCommon; +} interface Arguments { q?: KibanaQueryOutput | null; @@ -40,75 +48,108 @@ const mergeQueries = (first: Query | Query[] = [], second: Query | Query[]) => (n: any) => JSON.stringify(n.query) ); -export const kibanaContextFunction: ExpressionFunctionKibanaContext = { - name: 'kibana_context', - type: 'kibana_context', - inputTypes: ['kibana_context', 'null'], - help: i18n.translate('data.search.functions.kibana_context.help', { - defaultMessage: 'Updates kibana global context', - }), - args: { - q: { - types: ['kibana_query', 'null'], - aliases: ['query', '_'], - default: null, - help: i18n.translate('data.search.functions.kibana_context.q.help', { - defaultMessage: 'Specify Kibana free form text query', - }), - }, - filters: { - types: ['kibana_filter', 'null'], - multi: true, - help: i18n.translate('data.search.functions.kibana_context.filters.help', { - defaultMessage: 'Specify Kibana generic filters', - }), +export const getKibanaContextFn = ( + getStartDependencies: ( + getKibanaRequest: ExecutionContext['getKibanaRequest'] + ) => Promise +) => { + const kibanaContextFunction: ExpressionFunctionKibanaContext = { + name: 'kibana_context', + type: 'kibana_context', + inputTypes: ['kibana_context', 'null'], + help: i18n.translate('data.search.functions.kibana_context.help', { + defaultMessage: 'Updates kibana global context', + }), + args: { + q: { + types: ['kibana_query', 'null'], + aliases: ['query', '_'], + default: null, + help: i18n.translate('data.search.functions.kibana_context.q.help', { + defaultMessage: 'Specify Kibana free form text query', + }), + }, + filters: { + types: ['kibana_filter', 'null'], + multi: true, + help: i18n.translate('data.search.functions.kibana_context.filters.help', { + defaultMessage: 'Specify Kibana generic filters', + }), + }, + timeRange: { + types: ['timerange', 'null'], + default: null, + help: i18n.translate('data.search.functions.kibana_context.timeRange.help', { + defaultMessage: 'Specify Kibana time range filter', + }), + }, + savedSearchId: { + types: ['string', 'null'], + default: null, + help: i18n.translate('data.search.functions.kibana_context.savedSearchId.help', { + defaultMessage: 'Specify saved search ID to be used for queries and filters', + }), + }, }, - timeRange: { - types: ['timerange', 'null'], - default: null, - help: i18n.translate('data.search.functions.kibana_context.timeRange.help', { - defaultMessage: 'Specify Kibana time range filter', - }), + + extract(state) { + const references: SavedObjectReference[] = []; + if (state.savedSearchId.length && typeof state.savedSearchId[0] === 'string') { + const refName = 'kibana_context.savedSearchId'; + references.push({ + name: refName, + type: 'search', + id: state.savedSearchId[0] as string, + }); + return { + state: { + ...state, + savedSearchId: [refName], + }, + references, + }; + } + return { state, references }; }, - savedSearchId: { - types: ['string', 'null'], - default: null, - help: i18n.translate('data.search.functions.kibana_context.savedSearchId.help', { - defaultMessage: 'Specify saved search ID to be used for queries and filters', - }), + + inject(state, references) { + const reference = references.find((r) => r.name === 'kibana_context.savedSearchId'); + if (reference) { + state.savedSearchId[0] = reference.id; + } + return state; }, - }, - async fn(input, args, { getSavedObject }) { - const timeRange = args.timeRange || input?.timeRange; - let queries = mergeQueries(input?.query, args?.q || []); - let filters = [...(input?.filters || []), ...(args?.filters?.map(unboxExpressionValue) || [])]; + async fn(input, args, { getKibanaRequest }) { + const { savedObjectsClient } = await getStartDependencies(getKibanaRequest); - if (args.savedSearchId) { - if (typeof getSavedObject !== 'function') { - throw new Error( - '"getSavedObject" function not available in execution context. ' + - 'When you execute expression you need to add extra execution context ' + - 'as the third argument and provide "getSavedObject" implementation.' - ); - } - const obj = await getSavedObject('search', args.savedSearchId); - const search = obj.attributes.kibanaSavedObjectMeta as { searchSourceJSON: string }; - const { query, filter } = getParsedValue(search.searchSourceJSON, {}); + const timeRange = args.timeRange || input?.timeRange; + let queries = mergeQueries(input?.query, args?.q || []); + let filters = [ + ...(input?.filters || []), + ...((args?.filters?.map(unboxExpressionValue) || []) as Filter[]), + ]; - if (query) { - queries = mergeQueries(queries, query); - } - if (filter) { - filters = [...filters, ...(Array.isArray(filter) ? filter : [filter])]; + if (args.savedSearchId) { + const obj = await savedObjectsClient.get('search', args.savedSearchId); + const search = (obj.attributes as any).kibanaSavedObjectMeta.searchSourceJSON as string; + const { query, filter } = getParsedValue(search, {}); + + if (query) { + queries = mergeQueries(queries, query); + } + if (filter) { + filters = [...filters, ...(Array.isArray(filter) ? filter : [filter])]; + } } - } - return { - type: 'kibana_context', - query: queries, - filters: uniqFilters(filters).filter((f: any) => !f.meta?.disabled), - timeRange, - }; - }, + return { + type: 'kibana_context', + query: queries, + filters: uniqFilters(filters).filter((f: any) => !f.meta?.disabled), + timeRange, + }; + }, + }; + return kibanaContextFunction; }; diff --git a/src/plugins/data/public/search/expressions/kibana_context.ts b/src/plugins/data/public/search/expressions/kibana_context.ts new file mode 100644 index 00000000000000..e7ce8edf3080a4 --- /dev/null +++ b/src/plugins/data/public/search/expressions/kibana_context.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { StartServicesAccessor } from 'src/core/public'; +import { getKibanaContextFn } from '../../../common/search/expressions'; +import { DataPublicPluginStart, DataStartDependencies } from '../../types'; +import { SavedObjectsClientCommon } from '../../../common/index_patterns'; + +/** + * This is some glue code that takes in `core.getStartServices`, extracts the dependencies + * needed for this function, and wraps them behind a `getStartDependencies` function that + * is then called at runtime. + * + * We do this so that we can be explicit about exactly which dependencies the function + * requires, without cluttering up the top-level `plugin.ts` with this logic. It also + * makes testing the expression function a bit easier since `getStartDependencies` is + * the only thing you should need to mock. + * + * @param getStartServices - core's StartServicesAccessor for this plugin + * + * @internal + */ +export function getKibanaContext({ + getStartServices, +}: { + getStartServices: StartServicesAccessor; +}) { + return getKibanaContextFn(async () => { + const [core] = await getStartServices(); + return { + savedObjectsClient: (core.savedObjects.client as unknown) as SavedObjectsClientCommon, + }; + }); +} diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 94fa5b7230f69f..a3acd775ee8920 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -21,7 +21,6 @@ import { handleResponse } from './fetch'; import { kibana, kibanaContext, - kibanaContextFunction, ISearchGeneric, SearchSourceDependencies, SearchSourceService, @@ -52,6 +51,7 @@ import { import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn'; import { DataPublicPluginStart, DataStartDependencies } from '../types'; import { NowProviderInternalContract } from '../now_provider'; +import { getKibanaContext } from './expressions/kibana_context'; /** @internal */ export interface SearchServiceSetupDependencies { @@ -110,7 +110,11 @@ export class SearchService implements Plugin { }) ); expressions.registerFunction(kibana); - expressions.registerFunction(kibanaContextFunction); + expressions.registerFunction( + getKibanaContext({ getStartServices } as { + getStartServices: StartServicesAccessor; + }) + ); expressions.registerFunction(luceneFunction); expressions.registerFunction(kqlFunction); expressions.registerFunction(kibanaTimerangeFunction); diff --git a/src/plugins/data/server/search/expressions/kibana_context.ts b/src/plugins/data/server/search/expressions/kibana_context.ts new file mode 100644 index 00000000000000..c8fdbf4764b0e3 --- /dev/null +++ b/src/plugins/data/server/search/expressions/kibana_context.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { StartServicesAccessor } from 'src/core/server'; +import { getKibanaContextFn } from '../../../common/search/expressions'; +import { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; +import { SavedObjectsClientCommon } from '../../../common/index_patterns'; + +/** + * This is some glue code that takes in `core.getStartServices`, extracts the dependencies + * needed for this function, and wraps them behind a `getStartDependencies` function that + * is then called at runtime. + * + * We do this so that we can be explicit about exactly which dependencies the function + * requires, without cluttering up the top-level `plugin.ts` with this logic. It also + * makes testing the expression function a bit easier since `getStartDependencies` is + * the only thing you should need to mock. + * + * @param getStartServices - core's StartServicesAccessor for this plugin + * + * @internal + */ +export function getKibanaContext({ + getStartServices, +}: { + getStartServices: StartServicesAccessor; +}) { + return getKibanaContextFn(async (getKibanaRequest) => { + const request = getKibanaRequest && getKibanaRequest(); + if (!request) { + throw new Error('KIBANA_CONTEXT_KIBANA_REQUEST_MISSING'); + } + + const [{ savedObjects }] = await getStartServices(); + return { + savedObjectsClient: (savedObjects.getScopedClient( + request + ) as any) as SavedObjectsClientCommon, + }; + }); +} diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 4dcab4eda34d1d..fdf0b66197b343 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -53,7 +53,6 @@ import { ISearchOptions, kibana, kibanaContext, - kibanaContextFunction, kibanaTimerangeFunction, kibanaFilterFunction, kqlFunction, @@ -75,6 +74,7 @@ import { ConfigSchema } from '../../config'; import { ISearchSessionService, SearchSessionService } from './session'; import { KbnServerError } from '../../../kibana_utils/server'; import { registerBsearchRoute } from './routes/bsearch'; +import { getKibanaContext } from './expressions/kibana_context'; type StrategyMap = Record>; @@ -154,7 +154,7 @@ export class SearchService implements Plugin { expressions.registerFunction(luceneFunction); expressions.registerFunction(kqlFunction); expressions.registerFunction(kibanaTimerangeFunction); - expressions.registerFunction(kibanaContextFunction); + expressions.registerFunction(getKibanaContext({ getStartServices: core.getStartServices })); expressions.registerFunction(fieldFunction); expressions.registerFunction(rangeFunction); expressions.registerFunction(kibanaFilterFunction); diff --git a/src/plugins/expressions/common/execution/types.ts b/src/plugins/expressions/common/execution/types.ts index a897ef5222bfae..d9c8682567b30d 100644 --- a/src/plugins/expressions/common/execution/types.ts +++ b/src/plugins/expressions/common/execution/types.ts @@ -11,7 +11,6 @@ import type { KibanaRequest } from 'src/core/server'; import { ExpressionType, SerializableState } from '../expression_types'; import { Adapters, RequestAdapter } from '../../../inspector/common'; -import { SavedObject, SavedObjectAttributes } from '../../../../core/public'; import { TablesAdapter } from '../util/tables_adapter'; /** @@ -59,20 +58,6 @@ export interface ExecutionContext< */ getKibanaRequest?: () => KibanaRequest; - /** - * Allows to fetch saved objects from ElasticSearch. In browser `getSavedObject` - * function is provided automatically by the Expressions plugin. On the server - * the caller of the expression has to provide this context function. The - * reason is because on the browser we always know the user who tries to - * fetch a saved object, thus saved object client is scoped automatically to - * that user. However, on the server we can scope that saved object client to - * any user, or even not scope it at all and execute it as an "internal" user. - */ - getSavedObject?: ( - type: string, - id: string - ) => Promise>; - /** * Returns the state (true|false) of the sync colors across panels switch. */ diff --git a/src/plugins/expressions/common/executor/executor.ts b/src/plugins/expressions/common/executor/executor.ts index 7962fe723d19fc..255de31f7239b1 100644 --- a/src/plugins/expressions/common/executor/executor.ts +++ b/src/plugins/expressions/common/executor/executor.ts @@ -215,17 +215,25 @@ export class Executor = Record { - link.arguments = fn.inject(link.arguments, references); + link.arguments = fn.inject( + link.arguments, + references + .filter((r) => r.name.includes(`l${linkId}_`)) + .map((r) => ({ ...r, name: r.name.replace(`l${linkId}_`, '') })) + ); + linkId++; }); } public extract(ast: ExpressionAstExpression) { + let linkId = 0; const allReferences: SavedObjectReference[] = []; const newAst = this.walkAst(cloneDeep(ast), (fn, link) => { const { state, references } = fn.extract(link.arguments); link.arguments = state; - allReferences.push(...references); + allReferences.push(...references.map((r) => ({ ...r, name: `l${linkId++}_${r.name}` }))); }); return { state: newAst, references: allReferences }; } diff --git a/src/plugins/expressions/public/plugin.ts b/src/plugins/expressions/public/plugin.ts index 2bff5e09352e41..2410ad87413129 100644 --- a/src/plugins/expressions/public/plugin.ts +++ b/src/plugins/expressions/public/plugin.ts @@ -7,12 +7,7 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { - ExpressionsService, - ExpressionsServiceSetup, - ExecutionContext, - ExpressionsServiceStart, -} from '../common'; +import { ExpressionsService, ExpressionsServiceSetup, ExpressionsServiceStart } from '../common'; import { setRenderersRegistry, setNotifications, setExpressionsService } from './services'; import { ReactExpressionRenderer } from './react_expression_renderer'; import { ExpressionLoader, IExpressionLoader, loader } from './loader'; @@ -42,14 +37,8 @@ export class ExpressionsPublicPlugin implements Plugin { - const [start] = await core.getStartServices(); - return start.savedObjects.client.get(type, id); - }; - executor.extendContext({ environment: 'client', - getSavedObject, }); } diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 029d727e82e740..b3e7803f97c38c 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -137,9 +137,6 @@ export type ExecutionContainer = StateContainer { abortSignal: AbortSignal; getKibanaRequest?: () => KibanaRequest; - // Warning: (ae-forgotten-export) The symbol "SavedObjectAttributes" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "SavedObject" needs to be exported by the entry point index.d.ts - getSavedObject?: (type: string, id: string) => Promise>; getSearchContext: () => ExecutionContextSearch; getSearchSessionId: () => string | undefined; inspectorAdapters: InspectorAdapters; diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index de9797843a4ab9..2d873fa5183069 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -135,9 +135,6 @@ export type ExecutionContainer = StateContainer { abortSignal: AbortSignal; getKibanaRequest?: () => KibanaRequest; - // Warning: (ae-forgotten-export) The symbol "SavedObjectAttributes" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "SavedObject" needs to be exported by the entry point index.d.ts - getSavedObject?: (type: string, id: string) => Promise>; getSearchContext: () => ExecutionContextSearch; getSearchSessionId: () => string | undefined; inspectorAdapters: InspectorAdapters; From 9d47330ccffc0694e226299addfbbcafb8891b83 Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Thu, 25 Mar 2021 08:30:46 -0400 Subject: [PATCH 90/93] [alerting] add user facing doc on event log ILM policy (#92736) resolves https://github.com/elastic/kibana/issues/82435 Just provided a brief description, name of the policy, mentioned we create it but never modify it, provided the default values, and mentioned it could be updated by customers for their environment. Not sure we want to provide more info than that. --- .../alerting-production-considerations.asciidoc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/user/production-considerations/alerting-production-considerations.asciidoc b/docs/user/production-considerations/alerting-production-considerations.asciidoc index 57c255c809dc5a..6294a4fe6f14ae 100644 --- a/docs/user/production-considerations/alerting-production-considerations.asciidoc +++ b/docs/user/production-considerations/alerting-production-considerations.asciidoc @@ -49,3 +49,16 @@ It is difficult to predict how much throughput is needed to ensure all rules and By counting rules as recurring tasks and actions as non-recurring tasks, a rough throughput <> as a _tasks per minute_ measurement. Predicting the buffer required to account for actions depends heavily on the rule types you use, the amount of alerts they might detect, and the number of actions you might choose to assign to action groups. With that in mind, regularly <> of your Task Manager instances. + +[float] +[[event-log-ilm]] +=== Event log index lifecycle managment + +Alerts and actions log activity in a set of "event log" indices. These indices are configured with an index lifecycle management (ILM) policy, which you can customize. The default policy rolls over the index when it reaches 50GB, or after 30 days. Indices over 90 days old are deleted. + +The name of the index policy is `kibana-event-log-policy`. {kib} creates the index policy on startup, if it doesn't already exist. The index policy can be customized for your environment, but {kib} never modifies the index policy after creating it. + +Because Kibana uses the documents to display historic data, you should set the delete phase longer than you would like the historic data to be shown. For example, if you would like to see one month's worth of historic data, you should set the delete phase to at least one month. + +For more information on index lifecycle management, see: +{ref}/index-lifecycle-management.html[Index Lifecycle Policies]. From 80e53d5fe68e0918b7a79518883e9b0b7dce2617 Mon Sep 17 00:00:00 2001 From: igoristic Date: Thu, 25 Mar 2021 09:02:51 -0400 Subject: [PATCH 91/93] [Monitoring] Remove license check for alerting (#94874) * Removed license check for alerting * Fixed tests and CR feedback * Fixed test --- .../components/cluster/listing/listing.js | 13 +- .../__fixtures__/create_stubs.js | 34 --- .../cluster_alerts/alerts_cluster_search.js | 227 ----------------- .../alerts_cluster_search.test.js | 194 --------------- .../alerts_clusters_aggregation.js | 127 ---------- .../alerts_clusters_aggregation.test.js | 235 ------------------ .../server/cluster_alerts/check_license.js | 111 --------- .../cluster_alerts/check_license.test.js | 149 ----------- .../verify_monitoring_license.js | 48 ---- .../verify_monitoring_license.test.js | 88 ------- .../monitoring/server/deprecations.test.js | 17 -- .../plugins/monitoring/server/deprecations.ts | 4 +- .../lib/cluster/get_clusters_from_request.js | 49 +--- .../translations/translations/ja-JP.json | 9 - .../translations/translations/zh-CN.json | 9 - .../cluster/fixtures/multicluster.json | 6 +- .../standalone_cluster/fixtures/clusters.json | 12 +- .../apps/monitoring/cluster/list.js | 2 +- 18 files changed, 11 insertions(+), 1323 deletions(-) delete mode 100644 x-pack/plugins/monitoring/server/cluster_alerts/__fixtures__/create_stubs.js delete mode 100644 x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.js delete mode 100644 x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.test.js delete mode 100644 x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.js delete mode 100644 x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.test.js delete mode 100644 x-pack/plugins/monitoring/server/cluster_alerts/check_license.js delete mode 100644 x-pack/plugins/monitoring/server/cluster_alerts/check_license.test.js delete mode 100644 x-pack/plugins/monitoring/server/cluster_alerts/verify_monitoring_license.js delete mode 100644 x-pack/plugins/monitoring/server/cluster_alerts/verify_monitoring_license.test.js diff --git a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js index d4b8ea4a76e435..12cfc4f1328634 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js @@ -41,17 +41,14 @@ const IsClusterSupported = ({ isSupported, children }) => { * completely */ const IsAlertsSupported = (props) => { - const { alertsMeta = { enabled: true }, clusterMeta = { enabled: true } } = props.cluster.alerts; - if (alertsMeta.enabled && clusterMeta.enabled) { + const { alertsMeta = { enabled: true } } = props.cluster.alerts; + if (alertsMeta.enabled) { return {props.children}; } - const message = - alertsMeta.message || - clusterMeta.message || - i18n.translate('xpack.monitoring.cluster.listing.unknownHealthMessage', { - defaultMessage: 'Unknown', - }); + const message = i18n.translate('xpack.monitoring.cluster.listing.unknownHealthMessage', { + defaultMessage: 'Unknown', + }); return ( diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/__fixtures__/create_stubs.js b/x-pack/plugins/monitoring/server/cluster_alerts/__fixtures__/create_stubs.js deleted file mode 100644 index cf8aba8ca70085..00000000000000 --- a/x-pack/plugins/monitoring/server/cluster_alerts/__fixtures__/create_stubs.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import sinon from 'sinon'; - -export function createStubs(mockQueryResult, featureStub) { - const callWithRequestStub = sinon.stub().returns(Promise.resolve(mockQueryResult)); - const getClusterStub = sinon.stub().returns({ callWithRequest: callWithRequestStub }); - const configStub = sinon.stub().returns({ - get: sinon.stub().withArgs('xpack.monitoring.cluster_alerts.enabled').returns(true), - }); - return { - callWithRequestStub, - mockReq: { - server: { - config: configStub, - plugins: { - monitoring: { - info: { - feature: featureStub, - }, - }, - elasticsearch: { - getCluster: getClusterStub, - }, - }, - }, - }, - }; -} diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.js b/x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.js deleted file mode 100644 index 05f0524c125210..00000000000000 --- a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.js +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { get } from 'lodash'; -import moment from 'moment'; -import { verifyMonitoringLicense } from './verify_monitoring_license'; -import { i18n } from '@kbn/i18n'; - -/** - * Retrieve any statically defined cluster alerts (not indexed) for the {@code cluster}. - * - * In the future, if we add other static cluster alerts, then we should probably just return an array. - * It may also make sense to put this into its own file in the future. - * - * @param {Object} cluster The cluster object containing the cluster's license. - * @return {Object} The alert to use for the cluster. {@code null} if none. - */ -export function staticAlertForCluster(cluster) { - const clusterNeedsTLSEnabled = get(cluster, 'license.cluster_needs_tls', false); - - if (clusterNeedsTLSEnabled) { - const versionParts = get(cluster, 'version', '').split('.'); - const version = versionParts.length > 1 ? `${versionParts[0]}.${versionParts[1]}` : 'current'; - - return { - metadata: { - severity: 0, - cluster_uuid: cluster.cluster_uuid, - link: `https://www.elastic.co/guide/en/x-pack/${version}/ssl-tls.html`, - }, - update_timestamp: cluster.timestamp, - timestamp: get(cluster, 'license.issue_date', cluster.timestamp), - prefix: i18n.translate('xpack.monitoring.clusterAlerts.clusterNeedsTSLEnabledDescription', { - defaultMessage: - 'Configuring TLS will be required to apply a Gold or Platinum license when security is enabled.', - }), - message: i18n.translate('xpack.monitoring.clusterAlerts.seeDocumentationDescription', { - defaultMessage: 'See documentation for details.', - }), - }; - } - - return null; -} - -/** - * Append the static alert(s) for this {@code cluster}, limiting the response to {@code size} {@code alerts}. - * - * @param {Object} cluster The cluster object containing the cluster's license. - * @param {Array} alerts The existing cluster alerts. - * @param {Number} size The maximum size. - * @return {Array} The alerts array (modified or not). - */ -export function appendStaticAlerts(cluster, alerts, size) { - const staticAlert = staticAlertForCluster(cluster); - - if (staticAlert) { - // we can put it over any resolved alert, or anything with a lower severity (which is currently none) - // the alerts array is pre-sorted from highest severity to lowest; unresolved alerts are at the bottom - const alertIndex = alerts.findIndex( - (alert) => alert.resolved_timestamp || alert.metadata.severity < staticAlert.metadata.severity - ); - - if (alertIndex !== -1) { - // we can put it in the place of this alert - alerts.splice(alertIndex, 0, staticAlert); - } else { - alerts.push(staticAlert); - } - - // chop off the last item if necessary (when size is < alerts.length) - return alerts.slice(0, size); - } - - return alerts; -} - -/** - * Create a filter that should be used when no time range is supplied and thus only un-resolved cluster alerts should - * be returned. - * - * @return {Object} Query to restrict to un-resolved cluster alerts. - */ -export function createFilterForUnresolvedAlerts() { - return { - bool: { - must_not: { - exists: { - field: 'resolved_timestamp', - }, - }, - }, - }; -} - -/** - * Create a filter that should be used when {@code options} has start or end times. - * - * This enables us to search for cluster alerts that have been resolved within the given time frame, while also - * grabbing any un-resolved cluster alerts. - * - * @param {Object} options The options for the cluster search. - * @return {Object} Query to restrict to un-resolved cluster alerts or cluster alerts resolved within the time range. - */ -export function createFilterForTime(options) { - const timeFilter = {}; - - if (options.start) { - timeFilter.gte = moment.utc(options.start).valueOf(); - } - - if (options.end) { - timeFilter.lte = moment.utc(options.end).valueOf(); - } - - return { - bool: { - should: [ - { - range: { - resolved_timestamp: { - format: 'epoch_millis', - ...timeFilter, - }, - }, - }, - { - bool: { - must_not: { - exists: { - field: 'resolved_timestamp', - }, - }, - }, - }, - ], - }, - }; -} - -/** - * @param {Object} req Request object from the API route - * @param {String} cluster The cluster being checked - */ -export async function alertsClusterSearch(req, alertsIndex, cluster, checkLicense, options = {}) { - const verification = await verifyMonitoringLicense(req.server); - - if (!verification.enabled) { - return Promise.resolve({ message: verification.message }); - } - - const license = get(cluster, 'license', {}); - const prodLicenseInfo = checkLicense(license.type, license.status === 'active', 'production'); - - if (prodLicenseInfo.clusterAlerts.enabled) { - const config = req.server.config(); - const size = options.size || config.get('monitoring.ui.max_bucket_size'); - - const params = { - index: alertsIndex, - ignoreUnavailable: true, - filterPath: 'hits.hits._source', - body: { - size, - query: { - bool: { - must: [ - { - // This will cause anything un-resolved to be sorted above anything that is resolved - // From there, those items are sorted by their severity, then by their timestamp (age) - function_score: { - boost_mode: 'max', - functions: [ - { - filter: { - bool: { - must_not: [ - { - exists: { - field: 'resolved_timestamp', - }, - }, - ], - }, - }, - weight: 2, - }, - ], - }, - }, - ], - filter: [ - { - term: { 'metadata.cluster_uuid': cluster.cluster_uuid }, - }, - ], - }, - }, - sort: [ - '_score', - { 'metadata.severity': { order: 'desc' } }, - { timestamp: { order: 'asc' } }, - ], - }, - }; - - if (options.start || options.end) { - params.body.query.bool.filter.push(createFilterForTime(options)); - } else { - params.body.query.bool.filter.push(createFilterForUnresolvedAlerts()); - } - - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then((result) => { - const hits = get(result, 'hits.hits', []); - const alerts = hits.map((alert) => alert._source); - - return appendStaticAlerts(cluster, alerts, size); - }); - } - - return Promise.resolve({ message: prodLicenseInfo.message }); -} diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.test.js b/x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.test.js deleted file mode 100644 index 8b655e23cb4300..00000000000000 --- a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_cluster_search.test.js +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { createStubs } from './__fixtures__/create_stubs'; -import { alertsClusterSearch } from './alerts_cluster_search'; - -const mockAlerts = [ - { - metadata: { - severity: 1, - }, - }, - { - metadata: { - severity: -1, - }, - }, - { - metadata: { - severity: 2000, - }, - resolved_timestamp: 'now', - }, -]; - -const mockQueryResult = { - hits: { - hits: [ - { - _source: mockAlerts[0], - }, - { - _source: mockAlerts[1], - }, - { - _source: mockAlerts[2], - }, - ], - }, -}; - -// TODO: tests were not running and are not up to date. -describe.skip('Alerts Cluster Search', () => { - describe('License checks pass', () => { - const featureStub = sinon.stub().returns({ - getLicenseCheckResults: () => ({ clusterAlerts: { enabled: true } }), - }); - const checkLicense = () => ({ clusterAlerts: { enabled: true } }); - - it('max hit count option', () => { - const { mockReq, callWithRequestStub } = createStubs(mockQueryResult, featureStub); - return alertsClusterSearch( - mockReq, - '.monitoring-alerts', - { cluster_uuid: 'cluster-1234' }, - checkLicense - ).then((alerts) => { - expect(alerts).to.eql(mockAlerts); - expect(callWithRequestStub.getCall(0).args[2].body.size).to.be.undefined; - }); - }); - - it('set hit count option', () => { - const { mockReq, callWithRequestStub } = createStubs(mockQueryResult, featureStub); - return alertsClusterSearch( - mockReq, - '.monitoring-alerts', - { cluster_uuid: 'cluster-1234' }, - checkLicense, - { size: 3 } - ).then((alerts) => { - expect(alerts).to.eql(mockAlerts); - expect(callWithRequestStub.getCall(0).args[2].body.size).to.be(3); - }); - }); - - it('should report static info-level alert in the right location', () => { - const { mockReq, callWithRequestStub } = createStubs(mockQueryResult, featureStub); - const cluster = { - cluster_uuid: 'cluster-1234', - timestamp: 'fake-timestamp', - version: '6.1.0-throwmeaway2', - license: { - cluster_needs_tls: true, - issue_date: 'fake-issue_date', - }, - }; - return alertsClusterSearch(mockReq, '.monitoring-alerts', cluster, checkLicense, { - size: 3, - }).then((alerts) => { - expect(alerts).to.have.length(3); - expect(alerts[0]).to.eql(mockAlerts[0]); - expect(alerts[1]).to.eql({ - metadata: { - severity: 0, - - cluster_uuid: cluster.cluster_uuid, - link: 'https://www.elastic.co/guide/en/x-pack/6.1/ssl-tls.html', - }, - update_timestamp: cluster.timestamp, - timestamp: cluster.license.issue_date, - prefix: - 'Configuring TLS will be required to apply a Gold or Platinum license when security is enabled.', - message: 'See documentation for details.', - }); - expect(alerts[2]).to.eql(mockAlerts[1]); - expect(callWithRequestStub.getCall(0).args[2].body.size).to.be(3); - }); - }); - - it('should report static info-level alert at the end if necessary', () => { - const { mockReq, callWithRequestStub } = createStubs({ hits: { hits: [] } }, featureStub); - const cluster = { - cluster_uuid: 'cluster-1234', - timestamp: 'fake-timestamp', - version: '6.1.0-throwmeaway2', - license: { - cluster_needs_tls: true, - issue_date: 'fake-issue_date', - }, - }; - return alertsClusterSearch(mockReq, '.monitoring-alerts', cluster, checkLicense, { - size: 3, - }).then((alerts) => { - expect(alerts).to.have.length(1); - expect(alerts[0]).to.eql({ - metadata: { - severity: 0, - cluster_uuid: cluster.cluster_uuid, - link: 'https://www.elastic.co/guide/en/x-pack/6.1/ssl-tls.html', - }, - update_timestamp: cluster.timestamp, - timestamp: cluster.license.issue_date, - prefix: - 'Configuring TLS will be required to apply a Gold or Platinum license when security is enabled.', - message: 'See documentation for details.', - }); - expect(callWithRequestStub.getCall(0).args[2].body.size).to.be(3); - }); - }); - }); - - describe('License checks fail', () => { - it('monitoring cluster license checks fail', () => { - const featureStub = sinon.stub().returns({ - getLicenseCheckResults: () => ({ - message: 'monitoring cluster license check fail', - clusterAlerts: { enabled: false }, - }), - }); - const checkLicense = sinon.stub(); - const { mockReq, callWithRequestStub } = createStubs({}, featureStub); - return alertsClusterSearch( - mockReq, - '.monitoring-alerts', - { cluster_uuid: 'cluster-1234' }, - checkLicense - ).then((alerts) => { - const result = { message: 'monitoring cluster license check fail' }; - expect(alerts).to.eql(result); - expect(checkLicense.called).to.be(false); - expect(callWithRequestStub.called).to.be(false); - }); - }); - - it('production cluster license checks fail', () => { - // monitoring cluster passes - const featureStub = sinon.stub().returns({ - getLicenseCheckResults: () => ({ clusterAlerts: { enabled: true } }), - }); - const checkLicense = sinon - .stub() - .returns({ clusterAlerts: { enabled: false }, message: 'prod goes boom' }); - const { mockReq, callWithRequestStub } = createStubs({}, featureStub); - return alertsClusterSearch( - mockReq, - '.monitoring-alerts', - { cluster_uuid: 'cluster-1234' }, - checkLicense - ).then((alerts) => { - const result = { message: 'prod goes boom' }; - expect(alerts).to.eql(result); - expect(checkLicense.calledOnce).to.be(true); - expect(callWithRequestStub.called).to.be(false); - }); - }); - }); -}); diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.js b/x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.js deleted file mode 100644 index 5c4194d063612b..00000000000000 --- a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.js +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { get, find } from 'lodash'; -import { verifyMonitoringLicense } from './verify_monitoring_license'; -import { i18n } from '@kbn/i18n'; - -export async function alertsClustersAggregation(req, alertsIndex, clusters, checkLicense) { - const verification = await verifyMonitoringLicense(req.server); - - if (!verification.enabled) { - // return metadata detailing that alerts is disabled because of the monitoring cluster license - return Promise.resolve({ alertsMeta: verification }); - } - - const params = { - index: alertsIndex, - ignoreUnavailable: true, - filterPath: 'aggregations', - body: { - size: 0, - query: { - bool: { - must_not: [ - { - exists: { field: 'resolved_timestamp' }, - }, - ], - }, - }, - aggs: { - group_by_cluster: { - terms: { - field: 'metadata.cluster_uuid', - size: 10, - }, - aggs: { - group_by_severity: { - range: { - field: 'metadata.severity', - ranges: [ - { - key: 'low', - to: 1000, - }, - { - key: 'medium', - from: 1000, - to: 2000, - }, - { - key: 'high', - from: 2000, - }, - ], - }, - }, - }, - }, - }, - }, - }; - - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then((result) => { - const buckets = get(result.aggregations, 'group_by_cluster.buckets'); - const meta = { alertsMeta: { enabled: true } }; - - return clusters.reduce((reClusters, cluster) => { - let alerts; - - const license = cluster.license || {}; - // check the license type of the production cluster for alerts feature support - const prodLicenseInfo = checkLicense(license.type, license.status === 'active', 'production'); - if (prodLicenseInfo.clusterAlerts.enabled) { - const clusterNeedsTLS = get(license, 'cluster_needs_tls', false); - const staticAlertCount = clusterNeedsTLS ? 1 : 0; - const bucket = find(buckets, { key: cluster.cluster_uuid }); - const bucketDocCount = get(bucket, 'doc_count', 0); - let severities = {}; - - if (bucket || staticAlertCount > 0) { - if (bucketDocCount > 0 || staticAlertCount > 0) { - const groupBySeverityBuckets = get(bucket, 'group_by_severity.buckets', []); - const lowGroup = find(groupBySeverityBuckets, { key: 'low' }) || {}; - const mediumGroup = find(groupBySeverityBuckets, { key: 'medium' }) || {}; - const highGroup = find(groupBySeverityBuckets, { key: 'high' }) || {}; - severities = { - low: (lowGroup.doc_count || 0) + staticAlertCount, - medium: mediumGroup.doc_count || 0, - high: highGroup.doc_count || 0, - }; - } - - alerts = { - count: bucketDocCount + staticAlertCount, - ...severities, - }; - } - } else { - // add metadata to the cluster's alerts object detailing that alerts are disabled because of the prod cluster license - alerts = { - clusterMeta: { - enabled: false, - message: i18n.translate( - 'xpack.monitoring.clusterAlerts.unsupportedClusterAlertsDescription', - { - defaultMessage: - 'Cluster [{clusterName}] license type [{licenseType}] does not support Cluster Alerts', - values: { - clusterName: cluster.cluster_name, - licenseType: `${license.type}`, - }, - } - ), - }, - }; - } - - return Object.assign(reClusters, { [cluster.cluster_uuid]: alerts }); - }, meta); - }); -} diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.test.js b/x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.test.js deleted file mode 100644 index fcf840ebf6636e..00000000000000 --- a/x-pack/plugins/monitoring/server/cluster_alerts/alerts_clusters_aggregation.test.js +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { merge } from 'lodash'; -import { createStubs } from './__fixtures__/create_stubs'; -import { alertsClustersAggregation } from './alerts_clusters_aggregation'; - -const clusters = [ - { - cluster_uuid: 'cluster-abc0', - cluster_name: 'cluster-abc0-name', - license: { type: 'test_license' }, - }, - { - cluster_uuid: 'cluster-abc1', - cluster_name: 'cluster-abc1-name', - license: { type: 'test_license' }, - }, - { - cluster_uuid: 'cluster-abc2', - cluster_name: 'cluster-abc2-name', - license: { type: 'test_license' }, - }, - { - cluster_uuid: 'cluster-abc3', - cluster_name: 'cluster-abc3-name', - license: { type: 'test_license' }, - }, - { cluster_uuid: 'cluster-no-license', cluster_name: 'cluster-no-license-name' }, - { cluster_uuid: 'cluster-invalid', cluster_name: 'cluster-invalid-name', license: {} }, -]; -const mockQueryResult = { - aggregations: { - group_by_cluster: { - buckets: [ - { - key: 'cluster-abc1', - doc_count: 1, - group_by_severity: { - buckets: [{ key: 'low', doc_count: 1 }], - }, - }, - { - key: 'cluster-abc2', - doc_count: 2, - group_by_severity: { - buckets: [{ key: 'medium', doc_count: 2 }], - }, - }, - { - key: 'cluster-abc3', - doc_count: 3, - group_by_severity: { - buckets: [{ key: 'high', doc_count: 3 }], - }, - }, - ], - }, - }, -}; - -// TODO: tests were not running and are not up to date. -describe.skip('Alerts Clusters Aggregation', () => { - describe('with alerts enabled', () => { - const featureStub = sinon.stub().returns({ - getLicenseCheckResults: () => ({ clusterAlerts: { enabled: true } }), - }); - const checkLicense = () => ({ clusterAlerts: { enabled: true } }); - - it('aggregates alert count summary by cluster', () => { - const { mockReq } = createStubs(mockQueryResult, featureStub); - return alertsClustersAggregation(mockReq, '.monitoring-alerts', clusters, checkLicense).then( - (result) => { - expect(result).to.eql({ - alertsMeta: { enabled: true }, - 'cluster-abc0': undefined, - 'cluster-abc1': { - count: 1, - high: 0, - low: 1, - medium: 0, - }, - 'cluster-abc2': { - count: 2, - high: 0, - low: 0, - medium: 2, - }, - 'cluster-abc3': { - count: 3, - high: 3, - low: 0, - medium: 0, - }, - 'cluster-no-license': undefined, - 'cluster-invalid': undefined, - }); - } - ); - }); - - it('aggregates alert count summary by cluster include static alert', () => { - const { mockReq } = createStubs(mockQueryResult, featureStub); - const clusterLicenseNeedsTLS = { license: { cluster_needs_tls: true } }; - const newClusters = Array.from(clusters); - - newClusters[0] = merge({}, clusters[0], clusterLicenseNeedsTLS); - newClusters[1] = merge({}, clusters[1], clusterLicenseNeedsTLS); - - return alertsClustersAggregation( - mockReq, - '.monitoring-alerts', - newClusters, - checkLicense - ).then((result) => { - expect(result).to.eql({ - alertsMeta: { enabled: true }, - 'cluster-abc0': { - count: 1, - high: 0, - medium: 0, - low: 1, - }, - 'cluster-abc1': { - count: 2, - high: 0, - low: 2, - medium: 0, - }, - 'cluster-abc2': { - count: 2, - high: 0, - low: 0, - medium: 2, - }, - 'cluster-abc3': { - count: 3, - high: 3, - low: 0, - medium: 0, - }, - 'cluster-no-license': undefined, - 'cluster-invalid': undefined, - }); - }); - }); - }); - - describe('with alerts disabled due to license', () => { - it('returns the input set if disabled because monitoring cluster checks', () => { - // monitoring clusters' license check to fail - const featureStub = sinon.stub().returns({ - getLicenseCheckResults: () => ({ - clusterAlerts: { enabled: false }, - message: 'monitoring cluster license is fail', - }), - }); - // prod clusters' license check to pass - const checkLicense = () => ({ clusterAlerts: { enabled: true } }); - const { mockReq } = createStubs(mockQueryResult, featureStub); - - return alertsClustersAggregation(mockReq, '.monitoring-alerts', clusters, checkLicense).then( - (result) => { - expect(result).to.eql({ - alertsMeta: { enabled: false, message: 'monitoring cluster license is fail' }, - }); - } - ); - }); - - it('returns the input set if disabled because production cluster checks', () => { - // monitoring clusters' license check to pass - const featureStub = sinon.stub().returns({ - getLicenseCheckResults: () => ({ clusterAlerts: { enabled: true } }), - }); - // prod clusters license check to fail - const checkLicense = () => ({ clusterAlerts: { enabled: false } }); - const { mockReq } = createStubs(mockQueryResult, featureStub); - - return alertsClustersAggregation(mockReq, '.monitoring-alerts', clusters, checkLicense).then( - (result) => { - expect(result).to.eql({ - alertsMeta: { enabled: true }, - 'cluster-abc0': { - clusterMeta: { - enabled: false, - message: - 'Cluster [cluster-abc0-name] license type [test_license] does not support Cluster Alerts', - }, - }, - 'cluster-abc1': { - clusterMeta: { - enabled: false, - message: - 'Cluster [cluster-abc1-name] license type [test_license] does not support Cluster Alerts', - }, - }, - 'cluster-abc2': { - clusterMeta: { - enabled: false, - message: - 'Cluster [cluster-abc2-name] license type [test_license] does not support Cluster Alerts', - }, - }, - 'cluster-abc3': { - clusterMeta: { - enabled: false, - message: - 'Cluster [cluster-abc3-name] license type [test_license] does not support Cluster Alerts', - }, - }, - 'cluster-no-license': { - clusterMeta: { - enabled: false, - message: `Cluster [cluster-no-license-name] license type [undefined] does not support Cluster Alerts`, - }, - }, - 'cluster-invalid': { - clusterMeta: { - enabled: false, - message: `Cluster [cluster-invalid-name] license type [undefined] does not support Cluster Alerts`, - }, - }, - }); - } - ); - }); - }); -}); diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/check_license.js b/x-pack/plugins/monitoring/server/cluster_alerts/check_license.js deleted file mode 100644 index 1010c7c8d50362..00000000000000 --- a/x-pack/plugins/monitoring/server/cluster_alerts/check_license.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { includes } from 'lodash'; -import { i18n } from '@kbn/i18n'; - -/** - * Function to do the work of checking license for cluster alerts feature support - * Can be used to power XpackInfo license check results as well as checking license of monitored clusters - * - * @param {String} type License type if a valid license. {@code null} if license was deleted. - * @param {Boolean} active Indicating that the overall license is active - * @param {String} clusterSource 'monitoring' or 'production' - * @param {Boolean} watcher {@code true} if Watcher is provided (or if its availability should not be checked) - */ -export function checkLicense(type, active, clusterSource, watcher = true) { - // return object, set up with safe defaults - const licenseInfo = { - clusterAlerts: { enabled: false }, - }; - - // Disabled because there is no license - if (!type) { - return Object.assign(licenseInfo, { - message: i18n.translate( - 'xpack.monitoring.clusterAlerts.checkLicense.licenseNotDeterminedDescription', - { - defaultMessage: `Cluster Alerts are not displayed because the [{clusterSource}] cluster's license could not be determined.`, - values: { - clusterSource, - }, - } - ), - }); - } - - // Disabled because the license type is not valid (basic) - if (!includes(['trial', 'standard', 'gold', 'platinum', 'enterprise'], type)) { - return Object.assign(licenseInfo, { - message: i18n.translate( - 'xpack.monitoring.clusterAlerts.checkLicense.licenseIsBasicDescription', - { - defaultMessage: `Cluster Alerts are not displayed if Watcher is disabled or the [{clusterSource}] cluster's current license is Basic.`, - values: { - clusterSource, - }, - } - ), - }); - } - - // Disabled because the license is inactive - if (!active) { - return Object.assign(licenseInfo, { - message: i18n.translate( - 'xpack.monitoring.clusterAlerts.checkLicense.licenseNotActiveDescription', - { - defaultMessage: `Cluster Alerts are not displayed because the [{clusterSource}] cluster's current license [{type}] is not active.`, - values: { - clusterSource, - type, - }, - } - ), - }); - } - - // Disabled because Watcher is not enabled (it may or may not be available) - if (!watcher) { - return Object.assign(licenseInfo, { - message: i18n.translate( - 'xpack.monitoring.clusterAlerts.checkLicense.watcherIsDisabledDescription', - { - defaultMessage: 'Cluster Alerts are not enabled because Watcher is disabled.', - } - ), - }); - } - - return Object.assign(licenseInfo, { clusterAlerts: { enabled: true } }); -} - -/** - * Function to "generate" license check results for {@code xpackInfo}. - * - * @param {Object} xpackInfo license information for the _Monitoring_ cluster - * @param {Function} _checkLicense Method exposed for easier unit testing - * @returns {Object} Response from {@code checker} - */ -export function checkLicenseGenerator(xpackInfo, _checkLicense = checkLicense) { - let type; - let active = false; - let watcher = false; - - if (xpackInfo && xpackInfo.license) { - const watcherFeature = xpackInfo.feature('watcher'); - - if (watcherFeature) { - watcher = watcherFeature.isEnabled(); - } - - type = xpackInfo.license.getType(); - active = xpackInfo.license.isActive(); - } - - return _checkLicense(type, active, 'monitoring', watcher); -} diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/check_license.test.js b/x-pack/plugins/monitoring/server/cluster_alerts/check_license.test.js deleted file mode 100644 index 2217d27dd0c008..00000000000000 --- a/x-pack/plugins/monitoring/server/cluster_alerts/check_license.test.js +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { checkLicense, checkLicenseGenerator } from './check_license'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -describe('Monitoring Check License', () => { - describe('License undeterminable', () => { - it('null active license - results false with a message', () => { - const result = checkLicense(null, true, 'test-cluster-abc'); - expect(result).to.eql({ - clusterAlerts: { enabled: false }, - message: `Cluster Alerts are not displayed because the [test-cluster-abc] cluster's license could not be determined.`, - }); - }); - }); - - describe('Inactive license', () => { - it('platinum inactive license - results false with a message', () => { - const result = checkLicense('platinum', false, 'test-cluster-def'); - expect(result).to.eql({ - clusterAlerts: { enabled: false }, - message: `Cluster Alerts are not displayed because the [test-cluster-def] cluster's current license [platinum] is not active.`, - }); - }); - }); - - describe('Active license', () => { - describe('Unsupported license types', () => { - it('basic active license - results false with a message', () => { - const result = checkLicense('basic', true, 'test-cluster-ghi'); - expect(result).to.eql({ - clusterAlerts: { enabled: false }, - message: `Cluster Alerts are not displayed if Watcher is disabled or the [test-cluster-ghi] cluster's current license is Basic.`, - }); - }); - }); - - describe('Supported license types', () => { - it('standard active license - results true with no message', () => { - const result = checkLicense('standard', true, 'test-cluster-jkl'); - expect(result).to.eql({ - clusterAlerts: { enabled: true }, - }); - }); - - it('gold active license - results true with no message', () => { - const result = checkLicense('gold', true, 'test-cluster-mno'); - expect(result).to.eql({ - clusterAlerts: { enabled: true }, - }); - }); - - it('platinum active license - results true with no message', () => { - const result = checkLicense('platinum', true, 'test-cluster-pqr'); - expect(result).to.eql({ - clusterAlerts: { enabled: true }, - }); - }); - - it('enterprise active license - results true with no message', () => { - const result = checkLicense('enterprise', true, 'test-cluster-pqr'); - expect(result).to.eql({ - clusterAlerts: { enabled: true }, - }); - }); - - describe('Watcher is not enabled', () => { - it('platinum active license - watcher disabled - results false with message', () => { - const result = checkLicense('platinum', true, 'test-cluster-pqr', false); - expect(result).to.eql({ - clusterAlerts: { enabled: false }, - message: 'Cluster Alerts are not enabled because Watcher is disabled.', - }); - }); - }); - }); - }); - - describe('XPackInfo checkLicenseGenerator', () => { - it('with deleted license', () => { - const expected = 123; - const checker = sinon.stub().returns(expected); - const result = checkLicenseGenerator(null, checker); - - expect(result).to.be(expected); - expect(checker.withArgs(undefined, false, 'monitoring', false).called).to.be(true); - }); - - it('license without watcher', () => { - const expected = 123; - const xpackInfo = { - license: { - getType: () => 'fake-type', - isActive: () => true, - }, - feature: () => null, - }; - const checker = sinon.stub().returns(expected); - const result = checkLicenseGenerator(xpackInfo, checker); - - expect(result).to.be(expected); - expect(checker.withArgs('fake-type', true, 'monitoring', false).called).to.be(true); - }); - - it('mock license with watcher', () => { - const expected = 123; - const feature = sinon - .stub() - .withArgs('watcher') - .returns({ isEnabled: () => true }); - const xpackInfo = { - license: { - getType: () => 'another-type', - isActive: () => true, - }, - feature, - }; - const checker = sinon.stub().returns(expected); - const result = checkLicenseGenerator(xpackInfo, checker); - - expect(result).to.be(expected); - expect(feature.withArgs('watcher').calledOnce).to.be(true); - expect(checker.withArgs('another-type', true, 'monitoring', true).called).to.be(true); - }); - - it('platinum license with watcher', () => { - const xpackInfo = { - license: { - getType: () => 'platinum', - isActive: () => true, - }, - feature: () => { - return { - isEnabled: () => true, - }; - }, - }; - const result = checkLicenseGenerator(xpackInfo); - - expect(result).to.eql({ clusterAlerts: { enabled: true } }); - }); - }); -}); diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/verify_monitoring_license.js b/x-pack/plugins/monitoring/server/cluster_alerts/verify_monitoring_license.js deleted file mode 100644 index e93db4ea960951..00000000000000 --- a/x-pack/plugins/monitoring/server/cluster_alerts/verify_monitoring_license.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { get } from 'lodash'; -import { i18n } from '@kbn/i18n'; - -/** - * Determine if an API for Cluster Alerts should respond based on the license and configuration of the monitoring cluster. - * - * Note: This does not guarantee that any production cluster has a valid license; only that Cluster Alerts in general can be used! - * - * @param {Object} server Server object containing config and plugins - * @return {Boolean} {@code true} to indicate that cluster alerts can be used. - */ -export async function verifyMonitoringLicense(server) { - const config = server.config(); - - // if cluster alerts are enabled, then ensure that we can use it according to the license - if (config.get('monitoring.cluster_alerts.enabled')) { - const xpackInfo = get(server.plugins.monitoring, 'info'); - if (xpackInfo) { - const licenseService = await xpackInfo.getLicenseService(); - const watcherFeature = licenseService.getWatcherFeature(); - return { - enabled: watcherFeature.isEnabled, - message: licenseService.getMessage(), - }; - } - - return { - enabled: false, - message: i18n.translate('xpack.monitoring.clusterAlerts.notDeterminedLicenseDescription', { - defaultMessage: 'Status of Cluster Alerts feature could not be determined.', - }), - }; - } - - return { - enabled: false, - message: i18n.translate('xpack.monitoring.clusterAlerts.disabledLicenseDescription', { - defaultMessage: 'Cluster Alerts feature is disabled.', - }), - }; -} diff --git a/x-pack/plugins/monitoring/server/cluster_alerts/verify_monitoring_license.test.js b/x-pack/plugins/monitoring/server/cluster_alerts/verify_monitoring_license.test.js deleted file mode 100644 index 6add3131bed96f..00000000000000 --- a/x-pack/plugins/monitoring/server/cluster_alerts/verify_monitoring_license.test.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { verifyMonitoringLicense } from './verify_monitoring_license'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -// TODO: tests were not running and are not up to date. -describe.skip('Monitoring Verify License', () => { - describe('Disabled by Configuration', () => { - const get = sinon.stub().withArgs('xpack.monitoring.cluster_alerts.enabled').returns(false); - const server = { config: sinon.stub().returns({ get }) }; - - it('verifyMonitoringLicense returns false without checking the license', () => { - const verification = verifyMonitoringLicense(server); - - expect(verification.enabled).to.be(false); - expect(verification.message).to.be('Cluster Alerts feature is disabled.'); - - expect(get.withArgs('xpack.monitoring.cluster_alerts.enabled').calledOnce).to.be(true); - }); - }); - - describe('Enabled by Configuration', () => { - it('verifyMonitoringLicense returns false if enabled by configuration, but not by license', () => { - const get = sinon.stub().withArgs('xpack.monitoring.cluster_alerts.enabled').returns(true); - const server = { - config: sinon.stub().returns({ get }), - plugins: { monitoring: { info: {} } }, - }; - const getLicenseCheckResults = sinon - .stub() - .returns({ clusterAlerts: { enabled: false }, message: 'failed!!' }); - const feature = sinon.stub().withArgs('monitoring').returns({ getLicenseCheckResults }); - - server.plugins.monitoring.info = { feature }; - - const verification = verifyMonitoringLicense(server); - - expect(verification.enabled).to.be(false); - expect(verification.message).to.be('failed!!'); - - expect(get.withArgs('xpack.monitoring.cluster_alerts.enabled').calledOnce).to.be(true); - expect(feature.withArgs('monitoring').calledOnce).to.be(true); - expect(getLicenseCheckResults.calledOnce).to.be(true); - }); - - it('verifyMonitoringLicense returns true if enabled by configuration and by license', () => { - const get = sinon.stub().withArgs('xpack.monitoring.cluster_alerts.enabled').returns(true); - const server = { - config: sinon.stub().returns({ get }), - plugins: { monitoring: { info: {} } }, - }; - const getLicenseCheckResults = sinon.stub().returns({ clusterAlerts: { enabled: true } }); - const feature = sinon.stub().withArgs('monitoring').returns({ getLicenseCheckResults }); - - server.plugins.monitoring.info = { feature }; - - const verification = verifyMonitoringLicense(server); - - expect(verification.enabled).to.be(true); - expect(verification.message).to.be.undefined; - - expect(get.withArgs('xpack.monitoring.cluster_alerts.enabled').calledOnce).to.be(true); - expect(feature.withArgs('monitoring').calledOnce).to.be(true); - expect(getLicenseCheckResults.calledOnce).to.be(true); - }); - }); - - it('Monitoring feature info cannot be determined', () => { - const get = sinon.stub().withArgs('xpack.monitoring.cluster_alerts.enabled').returns(true); - const server = { - config: sinon.stub().returns({ get }), - plugins: { monitoring: undefined }, // simulate race condition - }; - - const verification = verifyMonitoringLicense(server); - - expect(verification.enabled).to.be(false); - expect(verification.message).to.be('Status of Cluster Alerts feature could not be determined.'); - - expect(get.withArgs('xpack.monitoring.cluster_alerts.enabled').calledOnce).to.be(true); - }); -}); diff --git a/x-pack/plugins/monitoring/server/deprecations.test.js b/x-pack/plugins/monitoring/server/deprecations.test.js index 156fc76b6e0769..d7e1a2340d2952 100644 --- a/x-pack/plugins/monitoring/server/deprecations.test.js +++ b/x-pack/plugins/monitoring/server/deprecations.test.js @@ -36,25 +36,9 @@ describe('monitoring plugin deprecations', function () { expect(log).not.toHaveBeenCalled(); }); - it(`shouldn't log when cluster alerts are disabled`, function () { - const settings = { - cluster_alerts: { - enabled: false, - email_notifications: { - enabled: true, - }, - }, - }; - - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).not.toHaveBeenCalled(); - }); - it(`shouldn't log when email_address is specified`, function () { const settings = { cluster_alerts: { - enabled: true, email_notifications: { enabled: true, email_address: 'foo@bar.com', @@ -70,7 +54,6 @@ describe('monitoring plugin deprecations', function () { it(`should log when email_address is missing, but alerts/notifications are both enabled`, function () { const settings = { cluster_alerts: { - enabled: true, email_notifications: { enabled: true, }, diff --git a/x-pack/plugins/monitoring/server/deprecations.ts b/x-pack/plugins/monitoring/server/deprecations.ts index 47a01385c63081..a276cfcee0d35f 100644 --- a/x-pack/plugins/monitoring/server/deprecations.ts +++ b/x-pack/plugins/monitoring/server/deprecations.ts @@ -45,9 +45,7 @@ export const deprecations = ({ ), renameFromRoot('xpack.monitoring', 'monitoring'), (config, fromPath, logger) => { - const clusterAlertsEnabled = get(config, 'cluster_alerts.enabled'); - const emailNotificationsEnabled = - clusterAlertsEnabled && get(config, 'cluster_alerts.email_notifications.enabled'); + const emailNotificationsEnabled = get(config, 'cluster_alerts.email_notifications.enabled'); if (emailNotificationsEnabled && !get(config, CLUSTER_ALERTS_ADDRESS_CONFIG_KEY)) { logger( `Config key [${fromPath}.${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}] will be required for email notifications to work in 7.0."` diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js index b282cf94ade286..5143613a25b9c2 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js @@ -15,8 +15,6 @@ import { getKibanasForClusters } from '../kibana'; import { getLogstashForClusters } from '../logstash'; import { getLogstashPipelineIds } from '../logstash/get_pipeline_ids'; import { getBeatsForClusters } from '../beats'; -import { verifyMonitoringLicense } from '../../cluster_alerts/verify_monitoring_license'; -import { checkLicense as checkLicenseForAlerts } from '../../cluster_alerts/check_license'; import { getClustersSummary } from './get_clusters_summary'; import { STANDALONE_CLUSTER_CLUSTER_UUID, @@ -127,20 +125,7 @@ export async function getClustersFromRequest( clusters.map((cluster) => cluster.cluster_uuid) ); - const verification = await verifyMonitoringLicense(req.server); for (const cluster of clusters) { - if (!verification.enabled) { - // return metadata detailing that alerts is disabled because of the monitoring cluster license - cluster.alerts = { - alertsMeta: { - enabled: verification.enabled, - message: verification.message, // NOTE: this is only defined when the alert feature is disabled - }, - list: {}, - }; - continue; - } - if (!alertsClient) { cluster.alerts = { list: {}, @@ -148,17 +133,7 @@ export async function getClustersFromRequest( enabled: false, }, }; - continue; - } - - // check the license type of the production cluster for alerts feature support - const license = cluster.license || {}; - const prodLicenseInfo = checkLicenseForAlerts( - license.type, - license.status === 'active', - 'production' - ); - if (prodLicenseInfo.clusterAlerts.enabled) { + } else { try { cluster.alerts = { list: Object.keys(alertStatus).reduce((accum, alertName) => { @@ -190,29 +165,7 @@ export async function getClustersFromRequest( }, }; } - continue; } - - cluster.alerts = { - list: {}, - alertsMeta: { - enabled: false, - }, - clusterMeta: { - enabled: false, - message: i18n.translate( - 'xpack.monitoring.clusterAlerts.unsupportedClusterAlertsDescription', - { - defaultMessage: - 'Cluster [{clusterName}] license type [{licenseType}] does not support Cluster Alerts', - values: { - clusterName: cluster.cluster_name, - licenseType: `${license.type}`, - }, - } - ), - }, - }; } } } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9b7a25ff62661d..290ad19718efca 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15586,15 +15586,6 @@ "xpack.monitoring.cluster.overview.logstashPanel.withPersistentQueuesLabel": "永続キューあり", "xpack.monitoring.cluster.overview.pageTitle": "クラスターの概要", "xpack.monitoring.cluster.overviewTitle": "概要", - "xpack.monitoring.clusterAlerts.checkLicense.licenseIsBasicDescription": "Watcher が無効になっているか、[{clusterSource}] クラスターの現在のライセンスがベーシックの場合、クラスターアラートは表示されません。", - "xpack.monitoring.clusterAlerts.checkLicense.licenseNotActiveDescription": "[{clusterSource}] クラスターの現在のライセンス [{type}] がアクティブでないため、クラスターアラートは表示されません。", - "xpack.monitoring.clusterAlerts.checkLicense.licenseNotDeterminedDescription": "[{clusterSource}] クラスターのライセンスが確認できなかったため、クラスターアラートは表示されません。", - "xpack.monitoring.clusterAlerts.checkLicense.watcherIsDisabledDescription": "Watcher が無効なため、クラスターアラートを利用できません。", - "xpack.monitoring.clusterAlerts.clusterNeedsTSLEnabledDescription": "セキュリティが有効な場合、ゴールドまたはプラチナライセンスの適用に TLS の構成が必要です。", - "xpack.monitoring.clusterAlerts.disabledLicenseDescription": "クラスターアラート機能は無効になっています。", - "xpack.monitoring.clusterAlerts.notDeterminedLicenseDescription": "クラスターアラート機能のステータスが確認できませんでした。", - "xpack.monitoring.clusterAlerts.seeDocumentationDescription": "詳細はドキュメンテーションをご覧ください。", - "xpack.monitoring.clusterAlerts.unsupportedClusterAlertsDescription": "クラスター [{clusterName}] ライセンスタイプ [{licenseType}] はクラスターアラートをサポートしていません", "xpack.monitoring.clusterAlertsNavigation.clusterAlertsLinkText": "クラスターアラート", "xpack.monitoring.clustersNavigation.clustersLinkText": "クラスター", "xpack.monitoring.clusterStats.uuidNotFoundErrorMessage": "選択された時間範囲にクラスターが見つかりませんでした。UUID:{clusterUuid}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7bb27ee6626b8d..c982931f91e13d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15810,15 +15810,6 @@ "xpack.monitoring.cluster.overview.logstashPanel.withPersistentQueuesLabel": "持久性队列", "xpack.monitoring.cluster.overview.pageTitle": "集群概览", "xpack.monitoring.cluster.overviewTitle": "概览", - "xpack.monitoring.clusterAlerts.checkLicense.licenseIsBasicDescription": "如果禁用了 Watcher 或 [{clusterSource}] 集群的当前许可为基本级许可,则“集群告警”将不会显示。", - "xpack.monitoring.clusterAlerts.checkLicense.licenseNotActiveDescription": "因为 [{clusterSource}] 集群的当前许可 [{type}] 未处于活动状态,所以“集群告警”将不会显示。", - "xpack.monitoring.clusterAlerts.checkLicense.licenseNotDeterminedDescription": "因为无法确定 [{clusterSource}] 集群的许可,所以“集群告警”将不会显示。", - "xpack.monitoring.clusterAlerts.checkLicense.watcherIsDisabledDescription": "因为禁用了 Watcher,所以“集群告警”未启用。", - "xpack.monitoring.clusterAlerts.clusterNeedsTSLEnabledDescription": "启用安全性时,需要配置 TLS,才能应用黄金或白金许可。", - "xpack.monitoring.clusterAlerts.disabledLicenseDescription": "“集群告警”功能已禁用。", - "xpack.monitoring.clusterAlerts.notDeterminedLicenseDescription": "无法确定“集群告警”功能的状态。", - "xpack.monitoring.clusterAlerts.seeDocumentationDescription": "有关详情,请参阅文档。", - "xpack.monitoring.clusterAlerts.unsupportedClusterAlertsDescription": "集群 [{clusterName}] 许可类型 [{licenseType}] 不支持“集群告警”", "xpack.monitoring.clusterAlertsNavigation.clusterAlertsLinkText": "集群告警", "xpack.monitoring.clustersNavigation.clustersLinkText": "集群", "xpack.monitoring.clusterStats.uuidNotFoundErrorMessage": "在选定时间范围内找不到该集群。UUID:{clusterUuid}", diff --git a/x-pack/test/api_integration/apis/monitoring/cluster/fixtures/multicluster.json b/x-pack/test/api_integration/apis/monitoring/cluster/fixtures/multicluster.json index 48861c88e86adc..027dc898cacb5d 100644 --- a/x-pack/test/api_integration/apis/monitoring/cluster/fixtures/multicluster.json +++ b/x-pack/test/api_integration/apis/monitoring/cluster/fixtures/multicluster.json @@ -105,11 +105,7 @@ }, "alerts": { "alertsMeta": { - "enabled": false - }, - "clusterMeta": { - "enabled": false, - "message": "Cluster [clustertwo] license type [basic] does not support Cluster Alerts" + "enabled": true }, "list": {} }, diff --git a/x-pack/test/api_integration/apis/monitoring/standalone_cluster/fixtures/clusters.json b/x-pack/test/api_integration/apis/monitoring/standalone_cluster/fixtures/clusters.json index 602e6d5c2be4f2..c5006e8de824ca 100644 --- a/x-pack/test/api_integration/apis/monitoring/standalone_cluster/fixtures/clusters.json +++ b/x-pack/test/api_integration/apis/monitoring/standalone_cluster/fixtures/clusters.json @@ -105,11 +105,7 @@ }, "alerts": { "alertsMeta": { - "enabled": false - }, - "clusterMeta": { - "enabled": false, - "message": "Cluster [monitoring] license type [basic] does not support Cluster Alerts" + "enabled": true }, "list": {} }, @@ -176,11 +172,7 @@ }, "alerts": { "alertsMeta": { - "enabled": false - }, - "clusterMeta": { - "enabled": false, - "message": "Cluster [] license type [undefined] does not support Cluster Alerts" + "enabled": true }, "list": {} }, diff --git a/x-pack/test/functional/apps/monitoring/cluster/list.js b/x-pack/test/functional/apps/monitoring/cluster/list.js index bc08ce25ce90f8..e4f93042f0bf2f 100644 --- a/x-pack/test/functional/apps/monitoring/cluster/list.js +++ b/x-pack/test/functional/apps/monitoring/cluster/list.js @@ -109,7 +109,7 @@ export default function ({ getService, getPageObjects }) { it('primary basic cluster shows cluster metrics', async () => { expect(await clusterList.getClusterName(SUPPORTED_CLUSTER_UUID)).to.be('production'); - expect(await clusterList.getClusterStatus(SUPPORTED_CLUSTER_UUID)).to.be('N/A'); + expect(await clusterList.getClusterStatus(SUPPORTED_CLUSTER_UUID)).to.be('Clear'); expect(await clusterList.getClusterNodesCount(SUPPORTED_CLUSTER_UUID)).to.be('2'); expect(await clusterList.getClusterIndicesCount(SUPPORTED_CLUSTER_UUID)).to.be('4'); expect(await clusterList.getClusterDataSize(SUPPORTED_CLUSTER_UUID)).to.be('1.6 MB'); From 00c53c56b82e5f9a331c086ac50ba3a3ab4b458e Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Thu, 25 Mar 2021 09:15:59 -0400 Subject: [PATCH 92/93] [Fleet] Replace INTERNAL_POLICY_REASSIGN by POLICY_REASSIGN (#94116) --- .../fleet/common/types/models/agent.ts | 3 +-- .../agents/checkin/state_new_actions.ts | 21 +------------------ .../fleet/server/services/agents/reassign.ts | 4 ++-- .../fleet/server/types/models/agent.ts | 2 +- 4 files changed, 5 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index 35b123b2c64ea4..0629a67f0d8d30 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -36,8 +36,7 @@ export type AgentActionType = | 'UNENROLL' | 'UPGRADE' | 'SETTINGS' - // INTERNAL* actions are mean to interupt long polling calls these actions will not be distributed to the agent - | 'INTERNAL_POLICY_REASSIGN'; + | 'POLICY_REASSIGN'; export interface NewAgentAction { type: AgentActionType; diff --git a/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts index 7dc19f63a5adba..8810dd6ff1263c 100644 --- a/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts @@ -39,7 +39,7 @@ import { getAgentPolicyActionByIds, } from '../actions'; import { appContextService } from '../../app_context'; -import { getAgentById, updateAgent } from '../crud'; +import { updateAgent } from '../crud'; import { toPromiseAbortable, AbortError, createRateLimiter } from './rxjs_utils'; @@ -262,25 +262,6 @@ export function agentCheckinStateNewActionsFactory() { return EMPTY; } - const hasConfigReassign = newActions.some( - (action) => action.type === 'INTERNAL_POLICY_REASSIGN' - ); - if (hasConfigReassign) { - return from(getAgentById(esClient, agent.id)).pipe( - concatMap((refreshedAgent) => { - if (!refreshedAgent.policy_id) { - throw new Error('Agent does not have a policy assigned'); - } - const newAgentPolicy$ = getOrCreateAgentPolicyObservable(refreshedAgent.policy_id); - return newAgentPolicy$; - }), - rateLimiter(), - concatMap((policyAction) => - createAgentActionFromPolicyAction(soClient, esClient, agent, policyAction) - ) - ); - } - return of(newActions); }), filter((data) => data !== undefined), diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts index 5574c42ced0530..81b00663d7a8a7 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.ts @@ -44,7 +44,7 @@ export async function reassignAgent( await createAgentAction(soClient, esClient, { agent_id: agentId, created_at: new Date().toISOString(), - type: 'INTERNAL_POLICY_REASSIGN', + type: 'POLICY_REASSIGN', }); } @@ -164,7 +164,7 @@ export async function reassignAgents( agentsToUpdate.map((agent) => ({ agent_id: agent.id, created_at: now, - type: 'INTERNAL_POLICY_REASSIGN', + type: 'POLICY_REASSIGN', })) ); diff --git a/x-pack/plugins/fleet/server/types/models/agent.ts b/x-pack/plugins/fleet/server/types/models/agent.ts index b0b28fdb5b2c82..192bb83a88718e 100644 --- a/x-pack/plugins/fleet/server/types/models/agent.ts +++ b/x-pack/plugins/fleet/server/types/models/agent.ts @@ -70,7 +70,7 @@ export const NewAgentActionSchema = schema.oneOf([ schema.literal('POLICY_CHANGE'), schema.literal('UNENROLL'), schema.literal('UPGRADE'), - schema.literal('INTERNAL_POLICY_REASSIGN'), + schema.literal('POLICY_REASSIGN'), ]), data: schema.maybe(schema.any()), ack_data: schema.maybe(schema.any()), From e894ee973fe8ef16580ecf4a6952d9829927689e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Casper=20H=C3=BCbertz?= Date: Thu, 25 Mar 2021 14:29:51 +0100 Subject: [PATCH 93/93] [Observability] Change icon ref (#95367) --- x-pack/plugins/observability/public/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 81c174932914ba..5978c28b4e9396 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -39,7 +39,7 @@ export class Plugin implements PluginClass) => { // Load application bundle const { renderApp } = await import('./application');