From 09125323c2a4d4d9702e68755072f38ad10a2c6c Mon Sep 17 00:00:00 2001 From: Chris Davies Date: Wed, 27 Nov 2019 10:03:32 -0500 Subject: [PATCH 01/52] [Lens] Remove client-side reference to server source code (#51763) --- x-pack/legacy/plugins/lens/common/constants.ts | 2 +- x-pack/legacy/plugins/lens/index.ts | 4 +--- x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx | 3 ++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/lens/common/constants.ts b/x-pack/legacy/plugins/lens/common/constants.ts index c2eed1940fa1ab..57f2a633e45243 100644 --- a/x-pack/legacy/plugins/lens/common/constants.ts +++ b/x-pack/legacy/plugins/lens/common/constants.ts @@ -5,7 +5,7 @@ */ export const PLUGIN_ID = 'lens'; - +export const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations'; export const BASE_APP_URL = '/app/kibana'; export const BASE_API_URL = '/api/lens'; diff --git a/x-pack/legacy/plugins/lens/index.ts b/x-pack/legacy/plugins/lens/index.ts index a79b9907f64377..c4a684381b17ce 100644 --- a/x-pack/legacy/plugins/lens/index.ts +++ b/x-pack/legacy/plugins/lens/index.ts @@ -9,11 +9,9 @@ import { resolve } from 'path'; import { LegacyPluginInitializer } from 'src/legacy/types'; import KbnServer, { Server } from 'src/legacy/server/kbn_server'; import mappings from './mappings.json'; -import { PLUGIN_ID, getEditPath } from './common'; +import { PLUGIN_ID, getEditPath, NOT_INTERNATIONALIZED_PRODUCT_NAME } from './common'; import { lensServerPlugin } from './server'; -export const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations'; - export const lens: LegacyPluginInitializer = kibana => { return new kibana.Plugin({ id: PLUGIN_ID, diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index 93f5928f58aa17..f2678463f57da0 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -38,7 +38,7 @@ import { stopReportManager, trackUiEvent, } from '../lens_ui_telemetry'; -import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../index'; +import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../common'; import { KibanaLegacySetup } from '../../../../../../src/plugins/kibana_legacy/public'; import { EditorFrameStart } from '../types'; @@ -50,6 +50,7 @@ export interface LensPluginStartDependencies { data: DataPublicPluginStart; dataShim: DataStart; } + export class AppPlugin { private startDependencies: { data: DataPublicPluginStart; From dbca711524d23fc2e159b1f4db9ee63148ae80e2 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Wed, 27 Nov 2019 10:24:21 -0500 Subject: [PATCH 02/52] [SIEM][Detection Engine] Adds ecs threat properties to rules (#51782) * allows addition of ecs threat properties to rules and signals for mitre attack info * adds default empty array to threats on creation of rule, removes optional from update rules schema as it is implied, updates and adds relevant tests * adds sample rule with mitre attack threats property --- .../alerts/__mocks__/es_results.ts | 1 + .../detection_engine/alerts/create_rules.ts | 2 + .../alerts/rules_alert_type.ts | 1 + .../lib/detection_engine/alerts/types.ts | 14 + .../detection_engine/alerts/update_rules.ts | 2 + .../lib/detection_engine/alerts/utils.ts | 1 + .../routes/__mocks__/request_responses.ts | 22 ++ .../routes/create_rules_route.ts | 3 +- .../detection_engine/routes/schemas.test.ts | 364 +++++++++++++++++- .../lib/detection_engine/routes/schemas.ts | 27 ++ .../routes/update_rules_route.ts | 2 + .../lib/detection_engine/routes/utils.test.ts | 135 +++++++ .../lib/detection_engine/routes/utils.ts | 1 + .../scripts/rules/root_or_admin_threats.json | 43 +++ .../lib/detection_engine/signals_mapping.json | 3 + 15 files changed, 619 insertions(+), 2 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_threats.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts index 8080bd5ddd9139..bed466dd9b94f1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts @@ -35,6 +35,7 @@ export const sampleRuleAlertParams = ( filters: undefined, savedId: undefined, meta: undefined, + threats: undefined, }); export const sampleDocNoSortId = (someUuid: string = sampleIdGuid): SignalSourceHit => ({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts index 7c66714484383f..4418bbc52b57d6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts @@ -30,6 +30,7 @@ export const createRules = async ({ name, severity, tags, + threats, to, type, references, @@ -57,6 +58,7 @@ export const createRules = async ({ riskScore, severity, tags, + threats, to, type, references, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts index 91d7d18a4945cd..61fe9c7c226395 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts @@ -48,6 +48,7 @@ export const rulesAlertType = ({ riskScore: schema.number(), severity: schema.string(), tags: schema.arrayOf(schema.string(), { defaultValue: [] }), + threats: schema.nullable(schema.arrayOf(schema.object({}, { allowUnknowns: true }))), to: schema.string(), type: schema.string(), references: schema.arrayOf(schema.string(), { defaultValue: [] }), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts index 462a9b7d65ee22..c17e01f056f81b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts @@ -21,6 +21,19 @@ import { esFilters } from '../../../../../../../../src/plugins/data/server'; export type PartialFilter = Partial; +export interface ThreatParams { + framework: string; + tactic: { + id: string; + name: string; + reference: string; + }; + technique: { + id: string; + name: string; + reference: string; + }; +} export interface RuleAlertParams { description: string; enabled: boolean; @@ -44,6 +57,7 @@ export interface RuleAlertParams { severity: string; tags: string[]; to: string; + threats: ThreatParams[] | undefined | null; type: 'filter' | 'query' | 'saved_query'; } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts index 81360d78242302..d6b828642433de 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts @@ -65,6 +65,7 @@ export const updateRules = async ({ name, severity, tags, + threats, to, type, references, @@ -101,6 +102,7 @@ export const updateRules = async ({ riskScore, severity, tags, + threats, to, type, references, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts index c3988b8fea458c..9dedda6d79839e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts @@ -64,6 +64,7 @@ export const buildRule = ({ filters: ruleParams.filters, created_by: createdBy, updated_by: updatedBy, + threats: ruleParams.threats, }); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 4c49326fbb32a9..cf76987aa38ad2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -26,6 +26,13 @@ export const typicalPayload = (): Partial> = severity: 'high', query: 'user.name: root or user.name: admin', language: 'kuery', + threats: [ + { + framework: 'fake', + tactic: { id: 'fakeId', name: 'fakeName', reference: 'fakeRef' }, + technique: { id: 'techniqueId', name: 'techniqueName', reference: 'techniqueRef' }, + }, + ], }); export const typicalFilterPayload = (): Partial => ({ @@ -139,6 +146,21 @@ export const getResult = (): RuleAlertType => ({ tags: [], to: 'now', type: 'query', + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + }, + ], references: ['http://www.example.com', 'https://ww.example.com'], }, interval: '5m', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts index 7e1ac07e1f0aaa..4ff3a9b96b93e6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts @@ -50,11 +50,11 @@ export const createCreateRulesRoute: Hapi.ServerRoute = { name, severity, tags, + threats, to, type, references, } = request.payload; - const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; @@ -94,6 +94,7 @@ export const createCreateRulesRoute: Hapi.ServerRoute = { tags, to, type, + threats, references, }); return transformOrError(createdRule); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts index 6c7e5c4054326d..3c85618452d8cd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts @@ -5,7 +5,12 @@ */ import { createRulesSchema, updateRulesSchema, findRulesSchema, queryRulesSchema } from './schemas'; -import { RuleAlertParamsRest, FindParamsRest, UpdateRuleAlertParamsRest } from '../alerts/types'; +import { + RuleAlertParamsRest, + FindParamsRest, + UpdateRuleAlertParamsRest, + ThreatParams, +} from '../alerts/types'; describe('schemas', () => { describe('create rules schema', () => { @@ -240,6 +245,61 @@ describe('schemas', () => { }).error ).toBeFalsy(); }); + test('You can send in an empty array to threats', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [], + }).error + ).toBeFalsy(); + }); + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + threats: [ + { + framework: 'someFramework', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + technique: { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + }, + ], + }).error + ).toBeFalsy(); + }); test('If filter type is set then filter is required', () => { expect( @@ -736,6 +796,116 @@ describe('schemas', () => { ).toBeTruthy(); }); + test('You cannot send in an array of threats that are missing "framework"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + technique: { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); + test('You cannot send in an array of threats that are missing "tactic"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + technique: { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); + test('You cannot send in an array of threats that are missing "techniques"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); + test('You can optionally send in an array of false positives', () => { expect( createRulesSchema.validate>({ @@ -1810,6 +1980,198 @@ describe('schemas', () => { }).error ).toBeTruthy(); }); + + test('threats is not defaulted to empty array on update', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).value.threats + ).toBe(undefined); + }); + }); + test('threats is not defaulted to undefined on update with empty array', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [], + }).value.threats + ).toMatchObject([]); + }); + test('threats is valid when updated with all sub-objects', () => { + const expected: ThreatParams[] = [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + technique: { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + }, + ]; + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + technique: { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + }, + ], + }).value.threats + ).toMatchObject(expected); + }); + test('threats is invalid when updated with missing property framework', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + technique: { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); + test('threats is invalid when updated with missing tactic sub-object', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + technique: { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); + test('threats is invalid when updated with missing technique sub-object', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + }, + ], + }).error + ).toBeTruthy(); }); describe('find rules schema', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts index 664a98ad7d7ddd..0b4f1094549a4d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts @@ -50,6 +50,31 @@ const tags = Joi.array().items(Joi.string()); const fields = Joi.array() .items(Joi.string()) .single(); +const threat_framework = Joi.string(); +const threat_tactic_id = Joi.string(); +const threat_tactic_name = Joi.string(); +const threat_tactic_reference = Joi.string(); +const threat_tactic = Joi.object({ + id: threat_tactic_id.required(), + name: threat_tactic_name.required(), + reference: threat_tactic_reference.required(), +}); +const threat_technique_id = Joi.string(); +const threat_technique_name = Joi.string(); +const threat_technique_reference = Joi.string(); +const threat_technique = Joi.object({ + id: threat_technique_id.required(), + name: threat_technique_name.required(), + reference: threat_technique_reference.required(), +}); + +const threats = Joi.array().items( + Joi.object({ + framework: threat_framework.required(), + tactic: threat_tactic.required(), + technique: threat_technique.required(), + }) +); /* eslint-enable @typescript-eslint/camelcase */ export const createRulesSchema = Joi.object({ @@ -110,6 +135,7 @@ export const createRulesSchema = Joi.object({ tags: tags.default([]), to: to.required(), type: type.required(), + threats: threats.default([]), references: references.default([]), }); @@ -165,6 +191,7 @@ export const updateRulesSchema = Joi.object({ tags, to, type, + threats, references, }).xor('id', 'rule_id'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts index 1cc65054527c09..c5fb1675fb3430 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts @@ -50,6 +50,7 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { tags, to, type, + threats, references, } = request.payload; @@ -86,6 +87,7 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { tags, to, type, + threats, references, }); if (rule != null) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts index 632778d78dab7d..4ef2d87d1d736b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts @@ -39,6 +39,21 @@ describe('utils', () => { severity: 'high', updated_by: 'elastic', tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + }, + ], to: 'now', type: 'query', }); @@ -66,6 +81,21 @@ describe('utils', () => { severity: 'high', updated_by: 'elastic', tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + }, + ], to: 'now', type: 'query', }); @@ -95,6 +125,21 @@ describe('utils', () => { severity: 'high', updated_by: 'elastic', tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + }, + ], to: 'now', type: 'query', }); @@ -124,6 +169,21 @@ describe('utils', () => { severity: 'high', updated_by: 'elastic', tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + }, + ], to: 'now', type: 'query', }); @@ -151,6 +211,21 @@ describe('utils', () => { severity: 'high', updated_by: 'elastic', tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + }, + ], to: 'now', type: 'query', }); @@ -181,6 +256,21 @@ describe('utils', () => { severity: 'high', updated_by: 'elastic', tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + }, + ], to: 'now', type: 'query', }); @@ -211,6 +301,21 @@ describe('utils', () => { severity: 'high', updated_by: 'elastic', tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + }, + ], to: 'now', type: 'query', }); @@ -294,6 +399,21 @@ describe('utils', () => { tags: [], to: 'now', type: 'query', + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + }, + ], }, ], }); @@ -331,6 +451,21 @@ describe('utils', () => { tags: [], to: 'now', type: 'query', + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + }, + ], }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts index eb0ae49436bcaf..947fb27a89c3a8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -54,6 +54,7 @@ export const transformAlertToRule = (alert: RuleAlertType): Partial Date: Wed, 27 Nov 2019 16:47:22 +0100 Subject: [PATCH 03/52] Split legacy plugin discovery, expose SavedObjects scopedClient, wrappers, repository (#48882) * Split legacy plugin discovery, expose internal SavedObjectsClient * Expose internal SavedObjectsClient to plugins * Add more documentation * Expose client wrappers, repository, scoped client from SavedObjects * Remove unused onBeforeWrite * Refactor Service / Repository for testability * Bind exposed clientProvider methods * Fix eArchiver's KibanaMigrator * Cleanup * Use APICaller type * Expose SavedObjectsServiceStart to plugins * API documentation * Rename API methods to be verbs --- .../server/kibana-plugin-server.coresetup.md | 1 + ...na-plugin-server.coresetup.savedobjects.md | 13 ++ .../server/kibana-plugin-server.corestart.md | 7 + ...na-plugin-server.corestart.savedobjects.md | 13 ++ ...a-plugin-server.isavedobjectsrepository.md | 13 ++ .../core/server/kibana-plugin-server.md | 7 + ...server.savedobjectsclient._constructor_.md | 20 -- ...kibana-plugin-server.savedobjectsclient.md | 11 +- ...plugin-server.savedobjectsclientfactory.md | 15 ++ ...er.savedobjectsdeletebynamespaceoptions.md | 19 ++ ...objectsdeletebynamespaceoptions.refresh.md | 13 ++ ...ver.savedobjectsincrementcounteroptions.md | 20 ++ ...ncrementcounteroptions.migrationversion.md | 11 + ...dobjectsincrementcounteroptions.refresh.md | 13 ++ ...erver.savedobjectsrepository.bulkcreate.md | 27 +++ ...n-server.savedobjectsrepository.bulkget.md | 31 +++ ...erver.savedobjectsrepository.bulkupdate.md | 27 +++ ...in-server.savedobjectsrepository.create.md | 28 +++ ...in-server.savedobjectsrepository.delete.md | 28 +++ ...avedobjectsrepository.deletebynamespace.md | 27 +++ ...ugin-server.savedobjectsrepository.find.md | 24 +++ ...lugin-server.savedobjectsrepository.get.md | 28 +++ ...savedobjectsrepository.incrementcounter.md | 43 ++++ ...na-plugin-server.savedobjectsrepository.md | 31 +++ ...in-server.savedobjectsrepository.update.md | 29 +++ ...vedobjectsservicesetup.addclientwrapper.md | 13 ++ ...tsservicesetup.createinternalrepository.md | 18 ++ ...ectsservicesetup.createscopedrepository.md | 18 ++ ...-plugin-server.savedobjectsservicesetup.md | 35 ++++ ...vedobjectsservicesetup.setclientfactory.md | 13 ++ ...avedobjectsservicestart.getscopedclient.md | 15 ++ ...-plugin-server.savedobjectsservicestart.md | 20 ++ src/core/server/index.ts | 15 +- src/core/server/internal_types.ts | 8 +- src/core/server/legacy/legacy_service.test.ts | 25 ++- src/core/server/legacy/legacy_service.ts | 31 ++- src/core/server/mocks.ts | 18 +- src/core/server/plugins/plugin.test.ts | 2 + src/core/server/plugins/plugin_context.ts | 10 +- .../server/plugins/plugins_service.test.ts | 1 + src/core/server/plugins/plugins_service.ts | 4 +- .../server/plugins/plugins_system.test.ts | 8 +- src/core/server/saved_objects/index.ts | 14 +- .../migrations/core/build_index_map.ts | 1 + .../migrations/kibana/kibana_migrator.test.ts | 5 +- .../migrations/kibana/kibana_migrator.ts | 6 +- .../saved_objects_service.mock.ts | 30 ++- .../saved_objects_service.test.ts | 21 +- .../saved_objects/saved_objects_service.ts | 189 +++++++++++++++--- .../server/saved_objects/schema/schema.ts | 1 + .../server/saved_objects/service/index.ts | 1 + .../service/lib/create_repository.test.ts | 119 +++++++++++ .../service/lib/create_repository.ts | 61 ++++++ .../server/saved_objects/service/lib/index.ts | 8 +- .../service/lib/repository.mock.ts | 35 ++++ .../service/lib/repository.test.js | 110 +--------- .../saved_objects/service/lib/repository.ts | 36 +++- .../service/lib/scoped_client_provider.ts | 5 +- .../service/saved_objects_client.ts | 16 +- src/core/server/server.api.md | 67 ++++++- src/core/server/server.test.mocks.ts | 6 +- src/core/server/server.ts | 28 ++- src/es_archiver/lib/indices/kibana_index.js | 3 +- 63 files changed, 1251 insertions(+), 264 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md create mode 100644 docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md create mode 100644 docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md delete mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md create mode 100644 src/core/server/saved_objects/service/lib/create_repository.test.ts create mode 100644 src/core/server/saved_objects/service/lib/create_repository.ts create mode 100644 src/core/server/saved_objects/service/lib/repository.mock.ts diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index c51459bc41a434..1ad1641beb83bf 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -19,5 +19,6 @@ export interface CoreSetup | [context](./kibana-plugin-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-server.contextsetup.md) | | [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | [http](./kibana-plugin-server.coresetup.http.md) | HttpServiceSetup | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | +| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | | [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | UiSettingsServiceSetup | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md b/docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md new file mode 100644 index 00000000000000..96acc1ffce1944 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) + +## CoreSetup.savedObjects property + +[SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) + +Signature: + +```typescript +savedObjects: SavedObjectsServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-server.corestart.md b/docs/development/core/server/kibana-plugin-server.corestart.md index da80ae8be93afc..a675c45a298201 100644 --- a/docs/development/core/server/kibana-plugin-server.corestart.md +++ b/docs/development/core/server/kibana-plugin-server.corestart.md @@ -11,3 +11,10 @@ Context passed to the plugins `start` method. ```typescript export interface CoreStart ``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [savedObjects](./kibana-plugin-server.corestart.savedobjects.md) | SavedObjectsServiceStart | [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | + diff --git a/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md b/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md new file mode 100644 index 00000000000000..531b04e9eed07a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md) > [savedObjects](./kibana-plugin-server.corestart.savedobjects.md) + +## CoreStart.savedObjects property + +[SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) + +Signature: + +```typescript +savedObjects: SavedObjectsServiceStart; +``` diff --git a/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md b/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md new file mode 100644 index 00000000000000..7863d1b0ca49dd --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) + +## ISavedObjectsRepository type + +See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) + +Signature: + +```typescript +export declare type ISavedObjectsRepository = Pick; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 13e0ea3645f26a..38c7ad75d1db97 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -22,6 +22,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | | [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | | [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | +| [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | | [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | ## Enumerations @@ -96,6 +97,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientProviderOptions](./kibana-plugin-server.savedobjectsclientprovideroptions.md) | Options to control the creation of the Saved Objects Client. | | [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. | | [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | | +| [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) | | | [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) | | | [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) | Options controlling the export operation. | | [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | @@ -109,10 +111,13 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsImportRetry](./kibana-plugin-server.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | | [SavedObjectsImportUnknownError](./kibana-plugin-server.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | | [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-server.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | +| [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) | | | [SavedObjectsMigrationLogger](./kibana-plugin-server.savedobjectsmigrationlogger.md) | | | [SavedObjectsMigrationVersion](./kibana-plugin-server.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | | [SavedObjectsRawDoc](./kibana-plugin-server.savedobjectsrawdoc.md) | A raw document as represented directly in the saved object index. | | [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | +| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. | +| [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. | | [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) | | | [SavedObjectsUpdateResponse](./kibana-plugin-server.savedobjectsupdateresponse.md) | | | [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. | @@ -149,6 +154,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | | [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | | [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | +| [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | | [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | | [KnownHeaders](./kibana-plugin-server.knownheaders.md) | Set of well-known HTTP headers. | @@ -175,6 +181,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | +| [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | | [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | | [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md deleted file mode 100644 index 0bcca3ec57b546..00000000000000 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [(constructor)](./kibana-plugin-server.savedobjectsclient._constructor_.md) - -## SavedObjectsClient.(constructor) - -Constructs a new instance of the `SavedObjectsClient` class - -Signature: - -```typescript -constructor(repository: SavedObjectsRepository); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| repository | SavedObjectsRepository | | - diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md index cc00934a1e1fd5..17d29bb912c834 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md @@ -4,19 +4,12 @@ ## SavedObjectsClient class - Signature: ```typescript export declare class SavedObjectsClient ``` -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(repository)](./kibana-plugin-server.savedobjectsclient._constructor_.md) | | Constructs a new instance of the SavedObjectsClient class | - ## Properties | Property | Modifiers | Type | Description | @@ -37,3 +30,7 @@ export declare class SavedObjectsClient | [get(type, id, options)](./kibana-plugin-server.savedobjectsclient.get.md) | | Retrieves a single object | | [update(type, id, attributes, options)](./kibana-plugin-server.savedobjectsclient.update.md) | | Updates an SavedObject | +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class. + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md new file mode 100644 index 00000000000000..9e307597206800 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) + +## SavedObjectsClientFactory type + +Describes the factory used to create instances of the Saved Objects Client. + +Signature: + +```typescript +export declare type SavedObjectsClientFactory = ({ request, }: { + request: Request; +}) => SavedObjectsClientContract; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md new file mode 100644 index 00000000000000..df4ce1b4b84286 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) + +## SavedObjectsDeleteByNamespaceOptions interface + + +Signature: + +```typescript +export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [refresh](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md new file mode 100644 index 00000000000000..2332520ac388fc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) > [refresh](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md) + +## SavedObjectsDeleteByNamespaceOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md new file mode 100644 index 00000000000000..38ee40157888f5 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) + +## SavedObjectsIncrementCounterOptions interface + + +Signature: + +```typescript +export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [migrationVersion](./kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md) | SavedObjectsMigrationVersion | | +| [refresh](./kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md new file mode 100644 index 00000000000000..3b80dea4fecde6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) > [migrationVersion](./kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md) + +## SavedObjectsIncrementCounterOptions.migrationVersion property + +Signature: + +```typescript +migrationVersion?: SavedObjectsMigrationVersion; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md new file mode 100644 index 00000000000000..acd8d6f0916f9f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) > [refresh](./kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md) + +## SavedObjectsIncrementCounterOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md new file mode 100644 index 00000000000000..003bc6ac72466d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkCreate](./kibana-plugin-server.savedobjectsrepository.bulkcreate.md) + +## SavedObjectsRepository.bulkCreate() method + +Creates multiple documents at once + +Signature: + +```typescript +bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | Array<SavedObjectsBulkCreateObject<T>> | | +| options | SavedObjectsCreateOptions | | + +Returns: + +`Promise>` + +{promise} - {saved\_objects: \[\[{ id, type, version, references, attributes, error: { message } }\]} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md new file mode 100644 index 00000000000000..605984d5dea30d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md @@ -0,0 +1,31 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkGet](./kibana-plugin-server.savedobjectsrepository.bulkget.md) + +## SavedObjectsRepository.bulkGet() method + +Returns an array of objects by id + +Signature: + +```typescript +bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | SavedObjectsBulkGetObject[] | | +| options | SavedObjectsBaseOptions | | + +Returns: + +`Promise>` + +{promise} - { saved\_objects: \[{ id, type, version, attributes }\] } + +## Example + +bulkGet(\[ { id: 'one', type: 'config' }, { id: 'foo', type: 'index-pattern' } \]) + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md new file mode 100644 index 00000000000000..52a73c83b4c3a4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkUpdate](./kibana-plugin-server.savedobjectsrepository.bulkupdate.md) + +## SavedObjectsRepository.bulkUpdate() method + +Updates multiple objects in bulk + +Signature: + +```typescript +bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | Array<SavedObjectsBulkUpdateObject<T>> | | +| options | SavedObjectsBulkUpdateOptions | | + +Returns: + +`Promise>` + +{promise} - {saved\_objects: \[\[{ id, type, version, references, attributes, error: { message } }\]} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md new file mode 100644 index 00000000000000..3a731629156e23 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [create](./kibana-plugin-server.savedobjectsrepository.create.md) + +## SavedObjectsRepository.create() method + +Persists an object + +Signature: + +```typescript +create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| attributes | T | | +| options | SavedObjectsCreateOptions | | + +Returns: + +`Promise>` + +{promise} - { id, type, version, attributes } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md new file mode 100644 index 00000000000000..52c36d2da162d2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [delete](./kibana-plugin-server.savedobjectsrepository.delete.md) + +## SavedObjectsRepository.delete() method + +Deletes an object + +Signature: + +```typescript +delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| options | SavedObjectsDeleteOptions | | + +Returns: + +`Promise<{}>` + +{promise} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md new file mode 100644 index 00000000000000..ab6eb30e664f19 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [deleteByNamespace](./kibana-plugin-server.savedobjectsrepository.deletebynamespace.md) + +## SavedObjectsRepository.deleteByNamespace() method + +Deletes all objects from the provided namespace. + +Signature: + +```typescript +deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| namespace | string | | +| options | SavedObjectsDeleteByNamespaceOptions | | + +Returns: + +`Promise` + +{promise} - { took, timed\_out, total, deleted, batches, version\_conflicts, noops, retries, failures } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md new file mode 100644 index 00000000000000..3c2855ed9a50cb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [find](./kibana-plugin-server.savedobjectsrepository.find.md) + +## SavedObjectsRepository.find() method + +Signature: + +```typescript +find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, } | SavedObjectsFindOptions | | + +Returns: + +`Promise>` + +{promise} - { saved\_objects: \[{ id, type, version, attributes }\], total, per\_page, page } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md new file mode 100644 index 00000000000000..dd1d81f225937d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [get](./kibana-plugin-server.savedobjectsrepository.get.md) + +## SavedObjectsRepository.get() method + +Gets a single object + +Signature: + +```typescript +get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| options | SavedObjectsBaseOptions | | + +Returns: + +`Promise>` + +{promise} - { id, type, version, attributes } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md new file mode 100644 index 00000000000000..f20e9a73d99a14 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md @@ -0,0 +1,43 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [incrementCounter](./kibana-plugin-server.savedobjectsrepository.incrementcounter.md) + +## SavedObjectsRepository.incrementCounter() method + +Increases a counter field by one. Creates the document if one doesn't exist for the given id. + +Signature: + +```typescript +incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{ + id: string; + type: string; + updated_at: string; + references: any; + version: string; + attributes: any; + }>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| counterFieldName | string | | +| options | SavedObjectsIncrementCounterOptions | | + +Returns: + +`Promise<{ + id: string; + type: string; + updated_at: string; + references: any; + version: string; + attributes: any; + }>` + +{promise} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md new file mode 100644 index 00000000000000..019363776590cd --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md @@ -0,0 +1,31 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) + +## SavedObjectsRepository class + +Signature: + +```typescript +export declare class SavedObjectsRepository +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [bulkCreate(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkcreate.md) | | Creates multiple documents at once | +| [bulkGet(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkget.md) | | Returns an array of objects by id | +| [bulkUpdate(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkupdate.md) | | Updates multiple objects in bulk | +| [create(type, attributes, options)](./kibana-plugin-server.savedobjectsrepository.create.md) | | Persists an object | +| [delete(type, id, options)](./kibana-plugin-server.savedobjectsrepository.delete.md) | | Deletes an object | +| [deleteByNamespace(namespace, options)](./kibana-plugin-server.savedobjectsrepository.deletebynamespace.md) | | Deletes all objects from the provided namespace. | +| [find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, })](./kibana-plugin-server.savedobjectsrepository.find.md) | | | +| [get(type, id, options)](./kibana-plugin-server.savedobjectsrepository.get.md) | | Gets a single object | +| [incrementCounter(type, id, counterFieldName, options)](./kibana-plugin-server.savedobjectsrepository.incrementcounter.md) | | Increases a counter field by one. Creates the document if one doesn't exist for the given id. | +| [update(type, id, attributes, options)](./kibana-plugin-server.savedobjectsrepository.update.md) | | Updates an object | + +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsRepository` class. + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md new file mode 100644 index 00000000000000..15890ab9211aa8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md @@ -0,0 +1,29 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [update](./kibana-plugin-server.savedobjectsrepository.update.md) + +## SavedObjectsRepository.update() method + +Updates an object + +Signature: + +```typescript +update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| attributes | Partial<T> | | +| options | SavedObjectsUpdateOptions | | + +Returns: + +`Promise>` + +{promise} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md new file mode 100644 index 00000000000000..e787d737ada17e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) + +## SavedObjectsServiceSetup.addClientWrapper property + +Add a client wrapper with the given priority. + +Signature: + +```typescript +addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md new file mode 100644 index 00000000000000..492aa1a2453a19 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) + +## SavedObjectsServiceSetup.createInternalRepository property + +Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. + +Signature: + +```typescript +createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; +``` + +## Remarks + +The repository should only be used for creating and registering a client factory or client wrapper. Using the repository directly for interacting with Saved Objects is an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md new file mode 100644 index 00000000000000..fc5aa40c21a208 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) + +## SavedObjectsServiceSetup.createScopedRepository property + +Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. + +Signature: + +```typescript +createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; +``` + +## Remarks + +The repository should only be used for creating and registering a client factory or client wrapper. Using the repository directly for interacting with Saved Objects is an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md new file mode 100644 index 00000000000000..dd97b45f590e2d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md @@ -0,0 +1,35 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) + +## SavedObjectsServiceSetup interface + +Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. + +Signature: + +```typescript +export interface SavedObjectsServiceSetup +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory<KibanaRequest>) => void | Add a client wrapper with the given priority. | +| [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | +| [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | +| [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | (customClientFactory: SavedObjectsClientFactory<KibanaRequest>) => void | Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. | + +## Remarks + +Note: The Saved Object setup API's should only be used for creating and registering client wrappers. Constructing a Saved Objects client or repository for use within your own plugin won't have any of the registered wrappers applied and is considered an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. + +When plugins access the Saved Objects client, a new client is created using the factory provided to `setClientFactory` and wrapped by all wrappers registered through `addClientWrapper`. To create a factory or wrapper, plugins will have to construct a Saved Objects client. First create a repository by calling `scopedRepository` or `internalRepository` and then use this repository as the argument to the [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) constructor. + +## Example + +import {SavedObjectsClient, CoreSetup} from 'src/core/server'; + +export class Plugin() { setup: (core: CoreSetup) => { core.savedObjects.setClientFactory(({request: KibanaRequest}) => { return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); }) } } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md new file mode 100644 index 00000000000000..544e0b9d5fa736 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) + +## SavedObjectsServiceSetup.setClientFactory property + +Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. + +Signature: + +```typescript +setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md new file mode 100644 index 00000000000000..e87979a124bdc1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) > [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) + +## SavedObjectsServiceStart.getScopedClient property + +Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client. + +A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md). + +Signature: + +```typescript +getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md new file mode 100644 index 00000000000000..5a869b3b6c1cbc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) + +## SavedObjectsServiceStart interface + +Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. + +Signature: + +```typescript +export interface SavedObjectsServiceStart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) | (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract | Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client.A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md). | + diff --git a/src/core/server/index.ts b/src/core/server/index.ts index b53f04d601ff42..f792f6e604c152 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -45,6 +45,7 @@ import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plug import { ContextSetup } from './context'; import { IUiSettingsClient, UiSettingsServiceSetup } from './ui_settings'; import { SavedObjectsClientContract } from './saved_objects/types'; +import { SavedObjectsServiceSetup, SavedObjectsServiceStart } from './saved_objects'; export { bootstrap } from './bootstrap'; export { ConfigPath, ConfigService, EnvironmentMode, PackageInfo } from './config'; @@ -144,6 +145,7 @@ export { SavedObjectsClientProviderOptions, SavedObjectsClientWrapperFactory, SavedObjectsClientWrapperOptions, + SavedObjectsClientFactory, SavedObjectsCreateOptions, SavedObjectsErrorHelpers, SavedObjectsExportOptions, @@ -165,7 +167,13 @@ export { SavedObjectsLegacyService, SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, + SavedObjectsServiceStart, + SavedObjectsServiceSetup, SavedObjectsDeleteOptions, + ISavedObjectsRepository, + SavedObjectsRepository, + SavedObjectsDeleteByNamespaceOptions, + SavedObjectsIncrementCounterOptions, } from './saved_objects'; export { @@ -233,6 +241,8 @@ export interface CoreSetup { elasticsearch: ElasticsearchServiceSetup; /** {@link HttpServiceSetup} */ http: HttpServiceSetup; + /** {@link SavedObjectsServiceSetup} */ + savedObjects: SavedObjectsServiceSetup; /** {@link UiSettingsServiceSetup} */ uiSettings: UiSettingsServiceSetup; } @@ -242,6 +252,9 @@ export interface CoreSetup { * * @public */ -export interface CoreStart {} // eslint-disable-line @typescript-eslint/no-empty-interface +export interface CoreStart { + /** {@link SavedObjectsServiceStart} */ + savedObjects: SavedObjectsServiceStart; +} export { ContextSetup, PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId }; diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 1330c5aee64fd7..d1a65c6f3437e2 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -21,7 +21,10 @@ import { InternalElasticsearchServiceSetup } from './elasticsearch'; import { InternalHttpServiceSetup } from './http'; import { InternalUiSettingsServiceSetup } from './ui_settings'; import { ContextSetup } from './context'; -import { SavedObjectsServiceStart } from './saved_objects'; +import { + InternalSavedObjectsServiceStart, + InternalSavedObjectsServiceSetup, +} from './saved_objects'; /** @internal */ export interface InternalCoreSetup { @@ -29,11 +32,12 @@ export interface InternalCoreSetup { http: InternalHttpServiceSetup; elasticsearch: InternalElasticsearchServiceSetup; uiSettings: InternalUiSettingsServiceSetup; + savedObjects: InternalSavedObjectsServiceSetup; } /** * @internal */ export interface InternalCoreStart { - savedObjects: SavedObjectsServiceStart; + savedObjects: InternalSavedObjectsServiceStart; } diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 030caa8324521c..286e1a0612c948 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -43,10 +43,9 @@ import { BasePathProxyServer } from '../http'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { DiscoveredPlugin } from '../plugins'; -import { KibanaMigrator } from '../saved_objects/migrations'; -import { ISavedObjectsClientProvider } from '../saved_objects'; import { httpServiceMock } from '../http/http_service.mock'; import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; +import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; @@ -79,7 +78,7 @@ beforeEach(() => { getAuthHeaders: () => undefined, } as any, }, - + savedObjects: savedObjectsServiceMock.createSetupContract(), plugins: { contracts: new Map([['plugin-id', 'plugin-value']]), uiPlugins: { @@ -94,10 +93,7 @@ beforeEach(() => { startDeps = { core: { - savedObjects: { - migrator: {} as KibanaMigrator, - clientProvider: {} as ISavedObjectsClientProvider, - }, + savedObjects: savedObjectsServiceMock.createStartContract(), plugins: { contracts: new Map() }, }, plugins: {}, @@ -128,6 +124,7 @@ describe('once LegacyService is set up with connection info', () => { configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -153,6 +150,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -180,6 +178,7 @@ describe('once LegacyService is set up with connection info', () => { configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot( `"something failed"` @@ -199,9 +198,12 @@ describe('once LegacyService is set up with connection info', () => { configService: configService as any, }); - await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingInlineSnapshot( + await expect(legacyService.discoverPlugins()).rejects.toThrowErrorMatchingInlineSnapshot( `"something failed"` ); + await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingInlineSnapshot( + `"Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()"` + ); await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot( `"Legacy service is not setup yet."` ); @@ -217,6 +219,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -237,6 +240,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -261,6 +265,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -280,7 +285,7 @@ describe('once LegacyService is set up without connection info', () => { let legacyService: LegacyService; beforeEach(async () => { legacyService = new LegacyService({ coreId, env, logger, configService: configService as any }); - + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); }); @@ -329,6 +334,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { configService: configService as any, }); + await devClusterLegacyService.discoverPlugins(); await devClusterLegacyService.setup(setupDeps); await devClusterLegacyService.start(startDeps); @@ -350,6 +356,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { configService: configService as any, }); + await devClusterLegacyService.discoverPlugins(); await devClusterLegacyService.setup(setupDeps); await devClusterLegacyService.start(startDeps); diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 99963ad9ce3e89..fd081b23a0ef2c 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -74,14 +74,17 @@ export interface LegacyServiceStartDeps { } /** @internal */ -export interface LegacyServiceSetup { +export interface LegacyServiceDiscoverPlugins { pluginSpecs: LegacyPluginSpec[]; uiExports: SavedObjectsLegacyUiExports; pluginExtendedConfig: Config; } /** @internal */ -export class LegacyService implements CoreService { +export type ILegacyService = Pick; + +/** @internal */ +export class LegacyService implements CoreService { /** Symbol to represent the legacy platform as a fake "plugin". Used by the ContextService */ public readonly legacyId = Symbol(); private readonly log: Logger; @@ -111,9 +114,7 @@ export class LegacyService implements CoreService { .pipe(map(rawConfig => new HttpConfig(rawConfig, coreContext.env))); } - public async setup(setupDeps: LegacyServiceSetupDeps) { - this.setupDeps = setupDeps; - + public async discoverPlugins(): Promise { this.update$ = this.coreContext.configService.getConfig$().pipe( tap(config => { if (this.kbnServer !== undefined) { @@ -164,6 +165,16 @@ export class LegacyService implements CoreService { }; } + public async setup(setupDeps: LegacyServiceSetupDeps) { + this.log.debug('setting up legacy service'); + if (!this.legacyRawConfig || !this.legacyPlugins || !this.settings) { + throw new Error( + 'Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()' + ); + } + this.setupDeps = setupDeps; + } + public async start(startDeps: LegacyServiceStartDeps) { const { setupDeps } = this; if (!setupDeps || !this.legacyRawConfig || !this.legacyPlugins || !this.settings) { @@ -249,11 +260,19 @@ export class LegacyService implements CoreService { basePath: setupDeps.core.http.basePath, isTlsEnabled: setupDeps.core.http.isTlsEnabled, }, + savedObjects: { + setClientFactory: setupDeps.core.savedObjects.setClientFactory, + addClientWrapper: setupDeps.core.savedObjects.addClientWrapper, + createInternalRepository: setupDeps.core.savedObjects.createInternalRepository, + createScopedRepository: setupDeps.core.savedObjects.createScopedRepository, + }, uiSettings: { register: setupDeps.core.uiSettings.register, }, }; - const coreStart: CoreStart = {}; + const coreStart: CoreStart = { + savedObjects: { getScopedClient: startDeps.core.savedObjects.getScopedClient }, + }; // eslint-disable-next-line @typescript-eslint/no-var-requires const KbnServer = require('../../../legacy/server/kbn_server'); diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index b51d5302e32746..a811efdf4b1b94 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -22,7 +22,9 @@ import { loggingServiceMock } from './logging/logging_service.mock'; import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; import { httpServiceMock } from './http/http_service.mock'; import { contextServiceMock } from './context/context_service.mock'; +import { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; +import { InternalCoreSetup, InternalCoreStart } from './internal_types'; export { httpServerMock } from './http/http_server.mocks'; export { sessionStorageMock } from './http/cookie_session_storage.mocks'; @@ -87,6 +89,7 @@ function createCoreSetupMock() { context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createSetupContract(), http: httpMock, + savedObjects: savedObjectsServiceMock.createSetupContract(), uiSettings: uiSettingsMock, }; @@ -94,24 +97,35 @@ function createCoreSetupMock() { } function createCoreStartMock() { - const mock: MockedKeys = {}; + const mock: MockedKeys = { + savedObjects: savedObjectsServiceMock.createStartContract(), + }; return mock; } function createInternalCoreSetupMock() { - const setupDeps = { + const setupDeps: InternalCoreSetup = { context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createSetupContract(), http: httpServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), + savedObjects: savedObjectsServiceMock.createSetupContract(), }; return setupDeps; } +function createInternalCoreStartMock() { + const startDeps: InternalCoreStart = { + savedObjects: savedObjectsServiceMock.createStartContract(), + }; + return startDeps; +} + export const coreMock = { createSetup: createCoreSetupMock, createStart: createCoreStartMock, createInternalSetup: createInternalCoreSetupMock, + createInternalStart: createInternalCoreStartMock, createPluginInitializerContext: pluginInitializerContextMock, }; diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index 6aab03a01675d5..10259b718577c9 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -66,7 +66,9 @@ configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true })); let coreId: symbol; let env: Env; let coreContext: CoreContext; + const setupDeps = coreMock.createInternalSetup(); + beforeEach(() => { coreId = Symbol('core'); env = Env.createDefault(getEnvOptions()); diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 9885a572ad8c00..6edce1b2533cb6 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -123,6 +123,12 @@ export function createPluginSetupContext( basePath: deps.http.basePath, isTlsEnabled: deps.http.isTlsEnabled, }, + savedObjects: { + setClientFactory: deps.savedObjects.setClientFactory, + addClientWrapper: deps.savedObjects.addClientWrapper, + createInternalRepository: deps.savedObjects.createInternalRepository, + createScopedRepository: deps.savedObjects.createScopedRepository, + }, uiSettings: { register: deps.uiSettings.register, }, @@ -146,5 +152,7 @@ export function createPluginStartContext( deps: PluginsServiceStartDeps, plugin: PluginWrapper ): CoreStart { - return {}; + return { + savedObjects: { getScopedClient: deps.savedObjects.getScopedClient }, + }; } diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index 7e55faa43360e4..df5473bc97d994 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -43,6 +43,7 @@ let configService: ConfigService; let coreId: symbol; let env: Env; let mockPluginSystem: jest.Mocked; + const setupDeps = coreMock.createInternalSetup(); const logger = loggingServiceMock.create(); diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 4c73c2a304dc42..3f9999aad4ab92 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -28,7 +28,7 @@ import { PluginWrapper } from './plugin'; import { DiscoveredPlugin, PluginConfigDescriptor, PluginName, InternalPluginInfo } from './types'; import { PluginsConfig, PluginsConfigType } from './plugins_config'; import { PluginsSystem } from './plugins_system'; -import { InternalCoreSetup } from '../internal_types'; +import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { IConfigService } from '../config'; import { pick } from '../../utils'; @@ -63,7 +63,7 @@ export interface PluginsServiceStart { export type PluginsServiceSetupDeps = InternalCoreSetup; /** @internal */ -export interface PluginsServiceStartDeps {} // eslint-disable-line @typescript-eslint/no-empty-interface +export type PluginsServiceStartDeps = InternalCoreStart; /** @internal */ export class PluginsService implements CoreService { diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 6f1788f717f61f..18c04af3bb6413 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -33,7 +33,6 @@ import { loggingServiceMock } from '../logging/logging_service.mock'; import { PluginWrapper } from './plugin'; import { PluginName } from './types'; import { PluginsSystem } from './plugins_system'; - import { coreMock } from '../mocks'; const logger = loggingServiceMock.create(); @@ -68,7 +67,9 @@ const configService = configServiceMock.create(); configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true })); let env: Env; let coreContext: CoreContext; + const setupDeps = coreMock.createInternalSetup(); +const startDeps = coreMock.createInternalStart(); beforeEach(() => { env = Env.createDefault(getEnvOptions()); @@ -249,7 +250,6 @@ test('correctly orders plugins and returns exposed values for "setup" and "start expect(plugin.setup).toHaveBeenCalledWith(setupContextMap.get(plugin.name), deps.setup); } - const startDeps = {}; expect([...(await pluginsSystem.startPlugins(startDeps))]).toMatchInlineSnapshot(` Array [ Array [ @@ -382,7 +382,7 @@ test('`uiPlugins` returns only ui plugin dependencies', async () => { test('can start without plugins', async () => { await pluginsSystem.setupPlugins(setupDeps); - const pluginsStart = await pluginsSystem.startPlugins({}); + const pluginsStart = await pluginsSystem.startPlugins(startDeps); expect(pluginsStart).toBeInstanceOf(Map); expect(pluginsStart.size).toBe(0); @@ -400,7 +400,7 @@ test('`startPlugins` only starts plugins that were setup', async () => { pluginsSystem.addPlugin(plugin); }); await pluginsSystem.setupPlugins(setupDeps); - const result = await pluginsSystem.startPlugins({}); + const result = await pluginsSystem.startPlugins(startDeps); expect([...result]).toMatchInlineSnapshot(` Array [ Array [ diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 76c62e0841bff7..1100c18bcc72fa 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -35,6 +35,18 @@ export { SavedObjectsSerializer, RawDoc as SavedObjectsRawDoc } from './serializ export { SavedObjectsMigrationLogger } from './migrations/core/migration_logger'; -export { SavedObjectsService, SavedObjectsServiceStart } from './saved_objects_service'; +export { + SavedObjectsService, + InternalSavedObjectsServiceStart, + SavedObjectsServiceStart, + SavedObjectsServiceSetup, + InternalSavedObjectsServiceSetup, +} from './saved_objects_service'; + +export { + ISavedObjectsRepository, + SavedObjectsIncrementCounterOptions, + SavedObjectsDeleteByNamespaceOptions, +} from './service/lib/repository'; export { config } from './saved_objects_config'; diff --git a/src/core/server/saved_objects/migrations/core/build_index_map.ts b/src/core/server/saved_objects/migrations/core/build_index_map.ts index d7a26b7728f444..3b7ed20d946469 100644 --- a/src/core/server/saved_objects/migrations/core/build_index_map.ts +++ b/src/core/server/saved_objects/migrations/core/build_index_map.ts @@ -39,6 +39,7 @@ export interface IndexMap { * This file contains logic to convert savedObjectSchemas into a dictonary of indexes and documents */ export function createIndexMap({ + /** @deprecated Remove once savedObjectsSchemas are exposed from Core */ config, kibanaIndexName, schema, 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 51551ae4887b51..b89abc596ad18f 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 @@ -20,6 +20,7 @@ import _ from 'lodash'; import { KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator'; import { loggingServiceMock } from '../../../logging/logging_service.mock'; +import { SavedObjectsSchema } from '../../schema'; describe('KibanaMigrator', () => { describe('getActiveMappings', () => { @@ -112,12 +113,12 @@ function mockOptions({ configValues }: { configValues?: any } = {}): KibanaMigra }, }, ], - savedObjectSchemas: { + savedObjectSchemas: new SavedObjectsSchema({ testtype2: { isNamespaceAgnostic: false, indexPattern: 'other-index', }, - }, + }), kibanaConfig: { enabled: true, index: '.my-index', diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index 5bde5deec93820..1b01680c427ee2 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -25,7 +25,7 @@ import { Logger } from 'src/core/server/logging'; import { KibanaConfigType } from 'src/core/server/kibana_config'; import { MappingProperties, SavedObjectsMapping, IndexMapping } from '../../mappings'; -import { SavedObjectsSchema, SavedObjectsSchemaDefinition } from '../../schema'; +import { SavedObjectsSchema } from '../../schema'; import { RawSavedObjectDoc, SavedObjectsSerializer } from '../../serialization'; import { docValidator, PropertyValidators } from '../../validation'; import { buildActiveMappings, CallCluster, IndexMigrator } from '../core'; @@ -47,7 +47,7 @@ export interface KibanaMigratorOptions { logger: Logger; savedObjectMappings: SavedObjectsMapping[]; savedObjectMigrations: MigrationDefinition; - savedObjectSchemas: SavedObjectsSchemaDefinition; + savedObjectSchemas: SavedObjectsSchema; savedObjectValidations: PropertyValidators; } @@ -87,7 +87,7 @@ export class KibanaMigrator { this.callCluster = callCluster; this.kibanaConfig = kibanaConfig; this.savedObjectsConfig = savedObjectsConfig; - this.schema = new SavedObjectsSchema(savedObjectSchemas); + this.schema = savedObjectSchemas; this.serializer = new SavedObjectsSerializer(this.schema); this.mappingProperties = mergeProperties(savedObjectMappings || []); this.log = logger; diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts index 0a021ee97e26ad..b2596146a02d47 100644 --- a/src/core/server/saved_objects/saved_objects_service.mock.ts +++ b/src/core/server/saved_objects/saved_objects_service.mock.ts @@ -17,21 +17,44 @@ * under the License. */ -import { SavedObjectsService, SavedObjectsServiceStart } from './saved_objects_service'; +import { + SavedObjectsService, + InternalSavedObjectsServiceSetup, + InternalSavedObjectsServiceStart, +} from './saved_objects_service'; import { mockKibanaMigrator } from './migrations/kibana/kibana_migrator.mock'; import { savedObjectsClientProviderMock } from './service/lib/scoped_client_provider.mock'; +import { savedObjectsRepositoryMock } from './service/lib/repository.mock'; +import { savedObjectsClientMock } from './service/saved_objects_client.mock'; type SavedObjectsServiceContract = PublicMethodsOf; const createStartContractMock = () => { - const startContract: jest.Mocked = { + const startContract: jest.Mocked = { clientProvider: savedObjectsClientProviderMock.create(), + getScopedClient: jest.fn(), migrator: mockKibanaMigrator.create(), }; return startContract; }; +const createSetupContractMock = () => { + const setupContract: jest.Mocked = { + getScopedClient: jest.fn(), + setClientFactory: jest.fn(), + addClientWrapper: jest.fn(), + createInternalRepository: jest.fn(), + createScopedRepository: jest.fn(), + }; + + setupContract.getScopedClient.mockReturnValue(savedObjectsClientMock.create()); + setupContract.createInternalRepository.mockReturnValue(savedObjectsRepositoryMock.create()); + setupContract.createScopedRepository.mockReturnValue(savedObjectsRepositoryMock.create()); + + return setupContract; +}; + const createsavedObjectsServiceMock = () => { const mocked: jest.Mocked = { setup: jest.fn(), @@ -39,7 +62,7 @@ const createsavedObjectsServiceMock = () => { stop: jest.fn(), }; - mocked.setup.mockResolvedValue({ clientProvider: savedObjectsClientProviderMock.create() }); + mocked.setup.mockResolvedValue(createSetupContractMock()); mocked.start.mockResolvedValue(createStartContractMock()); mocked.stop.mockResolvedValue(); return mocked; @@ -47,5 +70,6 @@ const createsavedObjectsServiceMock = () => { export const savedObjectsServiceMock = { create: createsavedObjectsServiceMock, + createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, }; 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 c31ad900118654..f58939c58e85e4 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -27,7 +27,6 @@ import { of } from 'rxjs'; import * as legacyElasticsearch from 'elasticsearch'; import { Env } from '../config'; import { configServiceMock } from '../mocks'; -import { SavedObjectsClientProvider } from '.'; afterEach(() => { jest.clearAllMocks(); @@ -51,7 +50,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = ({ elasticsearch: { adminClient$: of(clusterClient) }, - legacy: { uiExports: { savedObjectMappings: [] }, pluginExtendedConfig: {} }, + legacyPlugins: { uiExports: { savedObjectMappings: [] }, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup, 1); @@ -60,18 +59,6 @@ describe('SavedObjectsService', () => { 'success' ); }); - - it('resolves with clientProvider', async () => { - const coreContext = mockCoreContext.create(); - const soService = new SavedObjectsService(coreContext); - const coreSetup = ({ - elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, - legacy: { uiExports: {}, pluginExtendedConfig: {} }, - } as unknown) as SavedObjectsSetupDeps; - - const savedObjectsSetup = await soService.setup(coreSetup); - expect(savedObjectsSetup.clientProvider).toBeInstanceOf(SavedObjectsClientProvider); - }); }); describe('#start()', () => { @@ -82,7 +69,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = ({ elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, - legacy: { uiExports: {}, pluginExtendedConfig: {} }, + legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup); @@ -96,7 +83,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = ({ elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, - legacy: { uiExports: {}, pluginExtendedConfig: {} }, + legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup); @@ -110,7 +97,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = ({ elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, - legacy: { uiExports: {}, pluginExtendedConfig: {} }, + legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index 43c3afa3ed6399..589cd9cce400fb 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -22,40 +22,159 @@ import { first } from 'rxjs/operators'; import { SavedObjectsClient, SavedObjectsSchema, - SavedObjectsRepository, - SavedObjectsSerializer, SavedObjectsClientProvider, ISavedObjectsClientProvider, + SavedObjectsClientProviderOptions, } from './'; -import { getRootPropertiesObjects } from './mappings'; import { KibanaMigrator, IKibanaMigrator } from './migrations'; import { CoreContext } from '../core_context'; -import { LegacyServiceSetup } from '../legacy/legacy_service'; -import { ElasticsearchServiceSetup } from '../elasticsearch'; +import { LegacyServiceDiscoverPlugins } from '../legacy/legacy_service'; +import { ElasticsearchServiceSetup, APICaller } from '../elasticsearch'; import { KibanaConfigType } from '../kibana_config'; -import { retryCallCluster, migrationsRetryCallCluster } from '../elasticsearch/retry_call_cluster'; +import { migrationsRetryCallCluster } from '../elasticsearch/retry_call_cluster'; import { SavedObjectsConfigType } from './saved_objects_config'; import { KibanaRequest } from '../http'; +import { SavedObjectsClientContract } from './types'; +import { ISavedObjectsRepository } from './service/lib/repository'; +import { + SavedObjectsClientFactory, + SavedObjectsClientWrapperFactory, +} from './service/lib/scoped_client_provider'; +import { createRepository } from './service/lib/create_repository'; import { Logger } from '..'; /** + * Saved Objects is Kibana's data persisentence mechanism allowing plugins to + * use Elasticsearch for storing and querying state. The + * SavedObjectsServiceSetup API exposes methods for creating and registering + * Saved Object client wrappers. + * + * @remarks + * Note: The Saved Object setup API's should only be used for creating and + * registering client wrappers. Constructing a Saved Objects client or + * repository for use within your own plugin won't have any of the registered + * wrappers applied and is considered an anti-pattern. Use the Saved Objects + * client from the + * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient } + * method or the {@link RequestHandlerContext | route handler context} instead. + * + * When plugins access the Saved Objects client, a new client is created using + * the factory provided to `setClientFactory` and wrapped by all wrappers + * registered through `addClientWrapper`. To create a factory or wrapper, + * plugins will have to construct a Saved Objects client. First create a + * repository by calling `scopedRepository` or `internalRepository` and then + * use this repository as the argument to the {@link SavedObjectsClient} + * constructor. + * + * @example + * import {SavedObjectsClient, CoreSetup} from 'src/core/server'; + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * core.savedObjects.setClientFactory(({request: KibanaRequest}) => { + * return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); + * }) + * } + * } + * * @public */ export interface SavedObjectsServiceSetup { - clientProvider: ISavedObjectsClientProvider; + /** + * Set a default factory for creating Saved Objects clients. Only one client + * factory can be set, subsequent calls to this method will fail. + */ + setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; + + /** + * Add a client wrapper with the given priority. + */ + addClientWrapper: ( + priority: number, + id: string, + factory: SavedObjectsClientWrapperFactory + ) => void; + + /** + * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that + * uses the credentials from the passed in request to authenticate with + * Elasticsearch. + * + * @remarks + * The repository should only be used for creating and registering a client + * factory or client wrapper. Using the repository directly for interacting + * with Saved Objects is an anti-pattern. Use the Saved Objects client from + * the + * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient } + * method or the {@link RequestHandlerContext | route handler context} + * instead. + */ + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + + /** + * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that + * uses the internal Kibana user for authenticating with Elasticsearch. + * + * @remarks + * The repository should only be used for creating and registering a client + * factory or client wrapper. Using the repository directly for interacting + * with Saved Objects is an anti-pattern. Use the Saved Objects client from + * the + * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient } + * method or the {@link RequestHandlerContext | route handler context} + * instead. + */ + createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; +} + +/** + * @internal + */ +export interface InternalSavedObjectsServiceSetup extends SavedObjectsServiceSetup { + getScopedClient: ( + req: KibanaRequest, + options?: SavedObjectsClientProviderOptions + ) => SavedObjectsClientContract; } /** + * Saved Objects is Kibana's data persisentence mechanism allowing plugins to + * use Elasticsearch for storing and querying state. The + * SavedObjectsServiceStart API provides a scoped Saved Objects client for + * interacting with Saved Objects. + * * @public */ export interface SavedObjectsServiceStart { + /** + * Creates a {@link SavedObjectsClientContract | Saved Objects client} that + * uses the credentials from the passed in request to authenticate with + * Elasticsearch. If other plugins have registered Saved Objects client + * wrappers, these will be applied to extend the functionality of the client. + * + * A client that is already scoped to the incoming request is also exposed + * from the route handler context see {@link RequestHandlerContext}. + */ + getScopedClient: ( + req: KibanaRequest, + options?: SavedObjectsClientProviderOptions + ) => SavedObjectsClientContract; +} + +export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceStart { + /** + * @deprecated Exposed only for injecting into Legacy + */ migrator: IKibanaMigrator; + /** + * @deprecated Exposed only for injecting into Legacy + */ clientProvider: ISavedObjectsClientProvider; } /** @internal */ export interface SavedObjectsSetupDeps { - legacy: LegacyServiceSetup; + legacyPlugins: LegacyServiceDiscoverPlugins; elasticsearch: ElasticsearchServiceSetup; } @@ -64,7 +183,7 @@ export interface SavedObjectsSetupDeps { export interface SavedObjectsStartDeps {} export class SavedObjectsService - implements CoreService { + implements CoreService { private migrator: KibanaMigrator | undefined; private logger: Logger; private clientProvider: ISavedObjectsClientProvider | undefined; @@ -74,19 +193,21 @@ export class SavedObjectsService } public async setup( - coreSetup: SavedObjectsSetupDeps, + setupDeps: SavedObjectsSetupDeps, migrationsRetryDelay?: number - ): Promise { + ): Promise { this.logger.debug('Setting up SavedObjects service'); const { - savedObjectSchemas, + savedObjectSchemas: savedObjectsSchemasDefinition, savedObjectMappings, savedObjectMigrations, savedObjectValidations, - } = await coreSetup.legacy.uiExports; + } = setupDeps.legacyPlugins.uiExports; + + const savedObjectSchemas = new SavedObjectsSchema(savedObjectsSchemasDefinition); - const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(first()).toPromise(); + const adminClient = await setupDeps.elasticsearch.adminClient$.pipe(first()).toPromise(); const kibanaConfig = await this.coreContext.configService .atPath('kibana') @@ -105,7 +226,7 @@ export class SavedObjectsService savedObjectValidations, logger: this.coreContext.logger.get('migrations'), kibanaVersion: this.coreContext.env.packageInfo.version, - config: coreSetup.legacy.pluginExtendedConfig, + config: setupDeps.legacyPlugins.pluginExtendedConfig, savedObjectsConfig, kibanaConfig, callCluster: migrationsRetryCallCluster( @@ -115,35 +236,36 @@ export class SavedObjectsService ), })); - const mappings = this.migrator.getActiveMappings(); - const allTypes = Object.keys(getRootPropertiesObjects(mappings)); - const schema = new SavedObjectsSchema(savedObjectSchemas); - const serializer = new SavedObjectsSerializer(schema); - const visibleTypes = allTypes.filter(type => !schema.isHiddenType(type)); + const createSORepository = (callCluster: APICaller, extraTypes: string[] = []) => { + return createRepository( + migrator, + savedObjectSchemas, + setupDeps.legacyPlugins.pluginExtendedConfig, + kibanaConfig.index, + callCluster, + extraTypes + ); + }; this.clientProvider = new SavedObjectsClientProvider({ defaultClientFactory({ request }) { - const repository = new SavedObjectsRepository({ - index: kibanaConfig.index, - config: coreSetup.legacy.pluginExtendedConfig, - migrator, - mappings, - schema, - serializer, - allowedTypes: visibleTypes, - callCluster: retryCallCluster(adminClient.asScoped(request).callAsCurrentUser), - }); - + const repository = createSORepository(adminClient.asScoped(request).callAsCurrentUser); return new SavedObjectsClient(repository); }, }); return { - clientProvider: this.clientProvider, + getScopedClient: this.clientProvider.getClient.bind(this.clientProvider), + setClientFactory: this.clientProvider.setClientFactory.bind(this.clientProvider), + addClientWrapper: this.clientProvider.addClientWrapperFactory.bind(this.clientProvider), + createInternalRepository: (extraTypes?: string[]) => + createSORepository(adminClient.callAsInternalUser, extraTypes), + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => + createSORepository(adminClient.asScoped(req).callAsCurrentUser, extraTypes), }; } - public async start(core: SavedObjectsStartDeps): Promise { + public async start(core: SavedObjectsStartDeps): Promise { if (!this.clientProvider) { throw new Error('#setup() needs to be run first'); } @@ -171,6 +293,7 @@ export class SavedObjectsService return { migrator: this.migrator!, clientProvider: this.clientProvider, + getScopedClient: this.clientProvider.getClient.bind(this.clientProvider), }; } diff --git a/src/core/server/saved_objects/schema/schema.ts b/src/core/server/saved_objects/schema/schema.ts index 06d29bf7dcf326..6be5ca9bfce608 100644 --- a/src/core/server/saved_objects/schema/schema.ts +++ b/src/core/server/saved_objects/schema/schema.ts @@ -46,6 +46,7 @@ export class SavedObjectsSchema { return false; } + // TODO: Remove dependency on config when we move SavedObjectsSchema to NP public getIndexForType(config: Config, type: string): string | undefined { if (this.definition != null && this.definition.hasOwnProperty(type)) { const { indexPattern } = this.definition[type]; diff --git a/src/core/server/saved_objects/service/index.ts b/src/core/server/saved_objects/service/index.ts index cf0769fced460e..f50ee1759dad7c 100644 --- a/src/core/server/saved_objects/service/index.ts +++ b/src/core/server/saved_objects/service/index.ts @@ -58,6 +58,7 @@ export { SavedObjectsClientWrapperFactory, SavedObjectsClientWrapperOptions, SavedObjectsErrorHelpers, + SavedObjectsClientFactory, } from './lib'; export * from './saved_objects_client'; diff --git a/src/core/server/saved_objects/service/lib/create_repository.test.ts b/src/core/server/saved_objects/service/lib/create_repository.test.ts new file mode 100644 index 00000000000000..d40a5d04dcd8a3 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/create_repository.test.ts @@ -0,0 +1,119 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { SavedObjectsRepository } from './repository'; +import { mockKibanaMigrator } from '../../migrations/kibana/kibana_migrator.mock'; +import { SavedObjectsSchema } from '../../schema'; +import { KibanaMigrator } from '../../migrations'; +import { Config } from 'src/core/server/config'; +import { createRepository } from './create_repository'; +jest.mock('./repository'); + +describe('#createRepository', () => { + const callAdminCluster = jest.fn(); + const schema = new SavedObjectsSchema({ + nsAgnosticType: { isNamespaceAgnostic: true }, + nsType: { indexPattern: 'beats', isNamespaceAgnostic: false }, + hiddenType: { isNamespaceAgnostic: true, hidden: true }, + }); + const mappings = [ + { + pluginId: 'testplugin', + properties: { + nsAgnosticType: { + properties: { + name: { type: 'keyword' }, + }, + }, + nsType: { + properties: { + name: { type: 'keyword' }, + }, + }, + hiddenType: { + properties: { + name: { type: 'keyword' }, + }, + }, + }, + }, + ]; + const migrator = mockKibanaMigrator.create({ savedObjectMappings: mappings }); + const RepositoryConstructor = (SavedObjectsRepository as unknown) as jest.Mock< + SavedObjectsRepository + >; + + beforeEach(() => { + RepositoryConstructor.mockClear(); + }); + + it('should not allow a repository with an undefined type', () => { + try { + createRepository( + (migrator as unknown) as KibanaMigrator, + schema, + {} as Config, + '.kibana-test', + callAdminCluster, + ['unMappedType1', 'unmappedType2'] + ); + } catch (e) { + expect(e).toMatchInlineSnapshot( + `[Error: Missing mappings for saved objects types: 'unMappedType1, unmappedType2']` + ); + } + }); + + it('should create a repository without hidden types', () => { + const repository = createRepository( + (migrator as unknown) as KibanaMigrator, + schema, + {} as Config, + '.kibana-test', + callAdminCluster + ); + expect(repository).toBeDefined(); + expect(RepositoryConstructor.mock.calls[0][0].allowedTypes).toMatchInlineSnapshot(` + Array [ + "config", + "nsAgnosticType", + "nsType", + ] + `); + }); + + it('should create a repository with a unique list of hidden types', () => { + const repository = createRepository( + (migrator as unknown) as KibanaMigrator, + schema, + {} as Config, + '.kibana-test', + callAdminCluster, + ['hiddenType', 'hiddenType', 'hiddenType'] + ); + expect(repository).toBeDefined(); + expect(RepositoryConstructor.mock.calls[0][0].allowedTypes).toMatchInlineSnapshot(` + Array [ + "config", + "nsAgnosticType", + "nsType", + "hiddenType", + ] + `); + }); +}); diff --git a/src/core/server/saved_objects/service/lib/create_repository.ts b/src/core/server/saved_objects/service/lib/create_repository.ts new file mode 100644 index 00000000000000..a0e920a95c2c62 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/create_repository.ts @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { APICaller } from 'src/core/server/elasticsearch'; +import { Config } from 'src/core/server/config'; +import { retryCallCluster } from '../../../elasticsearch/retry_call_cluster'; +import { KibanaMigrator } from '../../migrations'; +import { SavedObjectsSchema } from '../../schema'; +import { getRootPropertiesObjects } from '../../mappings'; +import { SavedObjectsSerializer } from '../../serialization'; +import { SavedObjectsRepository } from '.'; + +export const createRepository = ( + migrator: KibanaMigrator, + schema: SavedObjectsSchema, + config: Config, + indexName: string, + callCluster: APICaller, + extraTypes: string[] = [] +) => { + const mappings = migrator.getActiveMappings(); + const allTypes = Object.keys(getRootPropertiesObjects(mappings)); + const serializer = new SavedObjectsSerializer(schema); + const visibleTypes = allTypes.filter(type => !schema.isHiddenType(type)); + + const missingTypeMappings = extraTypes.filter(type => !allTypes.includes(type)); + if (missingTypeMappings.length > 0) { + throw new Error( + `Missing mappings for saved objects types: '${missingTypeMappings.join(', ')}'` + ); + } + + const allowedTypes = [...new Set(visibleTypes.concat(extraTypes))]; + + return new SavedObjectsRepository({ + index: indexName, + config, + migrator, + mappings, + schema, + serializer, + allowedTypes, + callCluster: retryCallCluster(callCluster), + }); +}; diff --git a/src/core/server/saved_objects/service/lib/index.ts b/src/core/server/saved_objects/service/lib/index.ts index 4bc159e17ec0fb..afac50a680ed52 100644 --- a/src/core/server/saved_objects/service/lib/index.ts +++ b/src/core/server/saved_objects/service/lib/index.ts @@ -17,13 +17,19 @@ * under the License. */ -export { SavedObjectsRepository, SavedObjectsRepositoryOptions } from './repository'; +export { + ISavedObjectsRepository, + SavedObjectsRepository, + SavedObjectsRepositoryOptions, +} from './repository'; + export { SavedObjectsClientWrapperFactory, SavedObjectsClientWrapperOptions, ISavedObjectsClientProvider, SavedObjectsClientProvider, SavedObjectsClientProviderOptions, + SavedObjectsClientFactory, } from './scoped_client_provider'; export { SavedObjectsErrorHelpers } from './errors'; diff --git a/src/core/server/saved_objects/service/lib/repository.mock.ts b/src/core/server/saved_objects/service/lib/repository.mock.ts new file mode 100644 index 00000000000000..e69c0ff37d1bef --- /dev/null +++ b/src/core/server/saved_objects/service/lib/repository.mock.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ISavedObjectsRepository } from './repository'; + +const create = (): jest.Mocked => ({ + create: jest.fn(), + bulkCreate: jest.fn(), + bulkUpdate: jest.fn(), + delete: jest.fn(), + bulkGet: jest.fn(), + find: jest.fn(), + get: jest.fn(), + update: jest.fn(), + deleteByNamespace: jest.fn(), + incrementCounter: jest.fn(), +}); + +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 3d81c2c2efd526..07ad3494ab78c9 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -16,14 +16,11 @@ * specific language governing permissions and limitations * under the License. */ - -import { delay } from 'bluebird'; import _ from 'lodash'; import { SavedObjectsRepository } from './repository'; import * as getSearchDslNS from './search_dsl/search_dsl'; import { SavedObjectsErrorHelpers } from './errors'; -import * as legacyElasticsearch from 'elasticsearch'; import { SavedObjectsSchema } from '../../schema'; import { SavedObjectsSerializer } from '../../serialization'; import { getRootPropertiesObjects } from '../../mappings/lib/get_root_properties_objects'; @@ -36,7 +33,6 @@ jest.mock('./search_dsl/search_dsl', () => ({ getSearchDsl: jest.fn() })); describe('SavedObjectsRepository', () => { let callAdminCluster; - let onBeforeWrite; let savedObjectsRepository; let migrator; const mockTimestamp = '2017-08-14T15:49:14.886Z'; @@ -254,7 +250,6 @@ describe('SavedObjectsRepository', () => { beforeEach(() => { callAdminCluster = jest.fn(); - onBeforeWrite = jest.fn(); migrator = { migrateDocument: jest.fn(doc => doc), runMigrations: async () => ({ status: 'skipped' }), @@ -272,7 +267,6 @@ describe('SavedObjectsRepository', () => { schema, serializer, allowedTypes, - onBeforeWrite, }); savedObjectsRepository._getCurrentTime = jest.fn(() => mockTimestamp); @@ -350,7 +344,6 @@ describe('SavedObjectsRepository', () => { expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith('index', expect.any(Object)); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('should use default index', async () => { @@ -359,8 +352,8 @@ describe('SavedObjectsRepository', () => { title: 'Logstash', }); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - expect(onBeforeWrite).toHaveBeenCalledWith( + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster).toHaveBeenCalledWith( 'index', expect.objectContaining({ index: '.kibana-test', @@ -374,8 +367,8 @@ describe('SavedObjectsRepository', () => { title: 'Logstash', }); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - expect(onBeforeWrite).toHaveBeenCalledWith( + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster).toHaveBeenCalledWith( 'index', expect.objectContaining({ index: 'beats', @@ -447,7 +440,6 @@ describe('SavedObjectsRepository', () => { expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith('create', expect.any(Object)); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('allows for id to be provided', async () => { @@ -466,8 +458,6 @@ describe('SavedObjectsRepository', () => { id: 'index-pattern:logstash-*', }) ); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('self-generates an ID', async () => { @@ -482,8 +472,6 @@ describe('SavedObjectsRepository', () => { id: expect.objectContaining(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/), }) ); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('prepends namespace to the id and adds namespace to body when providing namespace for namespaced type', async () => { @@ -510,7 +498,6 @@ describe('SavedObjectsRepository', () => { }), }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { @@ -535,7 +522,6 @@ describe('SavedObjectsRepository', () => { }), }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { @@ -561,7 +547,6 @@ describe('SavedObjectsRepository', () => { }), }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('defaults to empty references array if none are provided', async () => { @@ -658,8 +643,6 @@ describe('SavedObjectsRepository', () => { references: [{ name: 'ref_0', type: 'test', id: '2' }], }, ]); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('defaults to a refresh setting of `wait_for`', async () => { @@ -821,10 +804,7 @@ describe('SavedObjectsRepository', () => { }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - callAdminCluster.mockReset(); - onBeforeWrite.mockReset(); callAdminCluster.mockReturnValue({ items: [{ create: { type: 'foo', id: 'bar', _primary_term: 1, _seq_no: 1 } }], @@ -844,8 +824,6 @@ describe('SavedObjectsRepository', () => { ], }) ); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('mockReturnValue document errors', async () => { @@ -997,7 +975,6 @@ describe('SavedObjectsRepository', () => { ], }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { @@ -1044,7 +1021,6 @@ describe('SavedObjectsRepository', () => { ], }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { @@ -1072,7 +1048,6 @@ describe('SavedObjectsRepository', () => { ], }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('should return objects in the same order regardless of type', () => { }); @@ -1116,8 +1091,6 @@ describe('SavedObjectsRepository', () => { index: '.kibana-test', ignore: [404], }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id when providing no namespace for namespaced type`, async () => { @@ -1131,8 +1104,6 @@ describe('SavedObjectsRepository', () => { index: '.kibana-test', ignore: [404], }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id when providing namespace for namespace agnostic type`, async () => { @@ -1148,8 +1119,6 @@ describe('SavedObjectsRepository', () => { index: '.kibana-test', ignore: [404], }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('defaults to a refresh setting of `wait_for`', async () => { @@ -1180,7 +1149,6 @@ describe('SavedObjectsRepository', () => { callAdminCluster.mockReturnValue(deleteByQueryResults); expect(savedObjectsRepository.deleteByNamespace()).rejects.toThrowErrorMatchingSnapshot(); expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('requires namespace to be a string', async () => { @@ -1189,7 +1157,6 @@ describe('SavedObjectsRepository', () => { savedObjectsRepository.deleteByNamespace(['namespace-1', 'namespace-2']) ).rejects.toThrowErrorMatchingSnapshot(); expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('constructs a deleteByQuery call using all types that are namespace aware', async () => { @@ -1198,7 +1165,6 @@ describe('SavedObjectsRepository', () => { expect(result).toEqual(deleteByQueryResults); expect(callAdminCluster).toHaveBeenCalledTimes(1); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, schema, { namespace: 'my-namespace', @@ -1247,7 +1213,6 @@ describe('SavedObjectsRepository', () => { it('requires type to be defined', async () => { await expect(savedObjectsRepository.find({})).rejects.toThrow(/options\.type must be/); expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('requires searchFields be an array if defined', async () => { @@ -1257,7 +1222,6 @@ describe('SavedObjectsRepository', () => { throw new Error('expected find() to reject'); } catch (error) { expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(error.message).toMatch('must be an array'); } }); @@ -1269,7 +1233,6 @@ describe('SavedObjectsRepository', () => { throw new Error('expected find() to reject'); } catch (error) { expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(error.message).toMatch('must be an array'); } }); @@ -1371,7 +1334,6 @@ describe('SavedObjectsRepository', () => { getSearchDslNS.getSearchDsl.mockReturnValue({ query: 1, aggregations: 2 }); await savedObjectsRepository.find({ type: 'foo' }); expect(callAdminCluster).toHaveBeenCalledTimes(1); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(callAdminCluster).toHaveBeenCalledWith( 'search', expect.objectContaining({ @@ -1440,8 +1402,6 @@ describe('SavedObjectsRepository', () => { from: 50, }) ); - - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('can filter by fields', async () => { @@ -1463,8 +1423,6 @@ describe('SavedObjectsRepository', () => { ], }) ); - - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('should set rest_total_hits_as_int to true on a request', async () => { @@ -1516,7 +1474,6 @@ describe('SavedObjectsRepository', () => { it('formats Elasticsearch response when there is no namespace', async () => { callAdminCluster.mockResolvedValue(noNamespaceResult); const response = await savedObjectsRepository.get('index-pattern', 'logstash-*'); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(response).toEqual({ id: 'logstash-*', type: 'index-pattern', @@ -1532,7 +1489,6 @@ describe('SavedObjectsRepository', () => { it('formats Elasticsearch response when there are namespaces', async () => { callAdminCluster.mockResolvedValue(namespacedResult); const response = await savedObjectsRepository.get('index-pattern', 'logstash-*'); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(response).toEqual({ id: 'logstash-*', type: 'index-pattern', @@ -1551,7 +1507,6 @@ describe('SavedObjectsRepository', () => { namespace: 'foo-namespace', }); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith( expect.any(String), @@ -1565,7 +1520,6 @@ describe('SavedObjectsRepository', () => { callAdminCluster.mockResolvedValue(noNamespaceResult); await savedObjectsRepository.get('index-pattern', 'logstash-*'); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith( expect.any(String), @@ -1581,7 +1535,6 @@ describe('SavedObjectsRepository', () => { namespace: 'foo-namespace', }); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith( expect.any(String), @@ -1630,8 +1583,6 @@ describe('SavedObjectsRepository', () => { }, }) ); - - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('prepends namespace and type appropriately to id when getting objects when there is a namespace', async () => { @@ -1661,8 +1612,6 @@ describe('SavedObjectsRepository', () => { }, }) ); - - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('mockReturnValue early for empty objects argument', async () => { @@ -1672,7 +1621,6 @@ describe('SavedObjectsRepository', () => { expect(response.saved_objects).toHaveLength(0); expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('handles missing ids gracefully', async () => { @@ -1723,7 +1671,6 @@ describe('SavedObjectsRepository', () => { { id: 'bad', type: 'config' }, ]); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(savedObjects).toHaveLength(2); @@ -1991,8 +1938,6 @@ describe('SavedObjectsRepository', () => { refresh: 'wait_for', index: '.kibana-test', }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { @@ -2033,8 +1978,6 @@ describe('SavedObjectsRepository', () => { refresh: 'wait_for', index: '.kibana-test', }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { @@ -2076,8 +2019,6 @@ describe('SavedObjectsRepository', () => { refresh: 'wait_for', index: '.kibana-test', }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('defaults to a refresh setting of `wait_for`', async () => { @@ -2504,8 +2445,6 @@ describe('SavedObjectsRepository', () => { }, ], }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { @@ -2562,8 +2501,6 @@ describe('SavedObjectsRepository', () => { }, ], }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { @@ -2723,8 +2660,6 @@ describe('SavedObjectsRepository', () => { expect(requestDoc.body.script.params.type).toBe('config'); expect(requestDoc.body.upsert.type).toBe('config'); expect(requestDoc).toHaveProperty('body.upsert.config'); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { @@ -2737,8 +2672,6 @@ describe('SavedObjectsRepository', () => { expect(requestDoc.body.script.params.type).toBe('config'); expect(requestDoc.body.upsert.type).toBe('config'); expect(requestDoc).toHaveProperty('body.upsert.config'); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { @@ -2769,8 +2702,6 @@ describe('SavedObjectsRepository', () => { expect(requestDoc.body.script.params.type).toBe('globaltype'); expect(requestDoc.body.upsert.type).toBe('globaltype'); expect(requestDoc).toHaveProperty('body.upsert.globaltype'); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('should assert that the "type" and "counterFieldName" arguments are strings', () => { @@ -2819,39 +2750,6 @@ describe('SavedObjectsRepository', () => { }); }); - describe('onBeforeWrite', () => { - it('blocks calls to callCluster of requests', async () => { - onBeforeWrite.mockReturnValue(delay(500)); - callAdminCluster.mockReturnValue({ result: 'deleted', found: true }); - - const deletePromise = savedObjectsRepository.delete('foo', 'id'); - await delay(100); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - expect(callAdminCluster).not.toHaveBeenCalled(); - await deletePromise; - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - expect(callAdminCluster).toHaveBeenCalledTimes(1); - }); - - it('can throw es errors and have them decorated as SavedObjectsClient errors', async () => { - expect.assertions(4); - - const es401 = new legacyElasticsearch.errors[401](); - expect(SavedObjectsErrorHelpers.isNotAuthorizedError(es401)).toBe(false); - onBeforeWrite.mockImplementation(() => { - throw es401; - }); - - try { - await savedObjectsRepository.delete('foo', 'id'); - } catch (error) { - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - expect(error).toBe(es401); - expect(SavedObjectsErrorHelpers.isNotAuthorizedError(error)).toBe(true); - } - }); - }); - describe('types on custom index', () => { it('should error when attempting to \'update\' an unsupported type', async () => { await expect( diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index e8f1fb16461c1d..f9e48aba5a70e0 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -19,7 +19,6 @@ import { omit } from 'lodash'; import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; - import { getRootPropertiesObjects, IndexMapping } from '../../mappings'; import { getSearchDsl } from './search_dsl'; import { includedFields } from './included_fields'; @@ -42,7 +41,6 @@ import { SavedObjectsBulkUpdateObject, SavedObjectsBulkUpdateOptions, SavedObjectsDeleteOptions, - SavedObjectsDeleteByNamespaceOptions, } from '../saved_objects_client'; import { SavedObject, @@ -75,6 +73,7 @@ const isLeft = (either: Either): either is Left => { export interface SavedObjectsRepositoryOptions { index: string; + /** @deprecated Will be removed once SavedObjectsSchema is exposed from Core */ config: Config; mappings: IndexMapping; callCluster: CallCluster; @@ -82,17 +81,38 @@ export interface SavedObjectsRepositoryOptions { serializer: SavedObjectsSerializer; migrator: KibanaMigrator; allowedTypes: string[]; - onBeforeWrite?: (...args: Parameters) => Promise; } -export interface IncrementCounterOptions extends SavedObjectsBaseOptions { +/** + * @public + */ +export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions { migrationVersion?: SavedObjectsMigrationVersion; /** The Elasticsearch Refresh setting for this operation */ refresh?: MutatingOperationRefreshSetting; } +/** + * + * @public + */ +export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; +} + const DEFAULT_REFRESH_SETTING = 'wait_for'; +/** + * See {@link SavedObjectsRepository} + * + * @public + */ +export type ISavedObjectsRepository = Pick; + +/** + * @public + */ export class SavedObjectsRepository { private _migrator: KibanaMigrator; private _index: string; @@ -100,10 +120,10 @@ export class SavedObjectsRepository { private _mappings: IndexMapping; private _schema: SavedObjectsSchema; private _allowedTypes: string[]; - private _onBeforeWrite: (...args: Parameters) => Promise; private _unwrappedCallCluster: CallCluster; private _serializer: SavedObjectsSerializer; + /** @internal */ constructor(options: SavedObjectsRepositoryOptions) { const { index, @@ -114,7 +134,6 @@ export class SavedObjectsRepository { serializer, migrator, allowedTypes = [], - onBeforeWrite = () => Promise.resolve(), } = options; // It's important that we migrate documents / mark them as up-to-date @@ -134,8 +153,6 @@ export class SavedObjectsRepository { } this._allowedTypes = allowedTypes; - this._onBeforeWrite = onBeforeWrite; - this._unwrappedCallCluster = async (...args: Parameters) => { await migrator.runMigrations(); return callCluster(...args); @@ -805,7 +822,7 @@ export class SavedObjectsRepository { type: string, id: string, counterFieldName: string, - options: IncrementCounterOptions = {} + options: SavedObjectsIncrementCounterOptions = {} ) { if (typeof type !== 'string') { throw new Error('"type" argument must be a string'); @@ -871,7 +888,6 @@ export class SavedObjectsRepository { private async _writeToCluster(...args: Parameters) { try { - await this._onBeforeWrite(...args); return await this._callCluster(...args); } catch (err) { throw decorateEsError(err); diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts index 87607acd94fc48..0b67727455333b 100644 --- a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts @@ -55,8 +55,7 @@ export interface SavedObjectsClientProviderOptions { } /** - * @public - * See {@link SavedObjectsClientProvider} + * @internal */ export type ISavedObjectsClientProvider = Pick< SavedObjectsClientProvider, @@ -66,6 +65,8 @@ export type ISavedObjectsClientProvider = Pick< /** * Provider for the Scoped Saved Objects Client. * + * @internal + * * @internalRemarks Because `getClient` is synchronous the Client Provider does * not support creating factories that react to new ES clients emitted from * elasticsearch.adminClient$. The Client Provider therefore doesn't support 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 550e8a1de0d80b..b0b2633646e10d 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -17,7 +17,7 @@ * under the License. */ -import { SavedObjectsRepository } from './lib'; +import { ISavedObjectsRepository } from './lib'; import { SavedObject, SavedObjectAttributes, @@ -126,15 +126,6 @@ export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { refresh?: MutatingOperationRefreshSetting; } -/** - * - * @public - */ -export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { - /** The Elasticsearch Refresh setting for this operation */ - refresh?: MutatingOperationRefreshSetting; -} - /** * * @public @@ -180,9 +171,10 @@ export class SavedObjectsClient { public static errors = SavedObjectsErrorHelpers; public errors = SavedObjectsErrorHelpers; - private _repository: SavedObjectsRepository; + private _repository: ISavedObjectsRepository; - constructor(repository: SavedObjectsRepository) { + /** @internal */ + constructor(repository: ISavedObjectsRepository) { this._repository = repository; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 3bbcb85fea9e54..411e5636069c18 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -512,11 +512,15 @@ export interface CoreSetup { // (undocumented) http: HttpServiceSetup; // (undocumented) + savedObjects: SavedObjectsServiceSetup; + // (undocumented) uiSettings: UiSettingsServiceSetup; } // @public export interface CoreStart { + // (undocumented) + savedObjects: SavedObjectsServiceStart; } // @public @@ -729,6 +733,9 @@ export interface IRouter { // @public export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolean; +// @public +export type ISavedObjectsRepository = Pick; + // @public export type IScopedClusterClient = Pick; @@ -1200,8 +1207,8 @@ export interface SavedObjectsBulkUpdateResponse(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; @@ -1219,6 +1226,11 @@ export class SavedObjectsClient { // @public export type SavedObjectsClientContract = Pick; +// @public +export type SavedObjectsClientFactory = ({ request, }: { + request: Request; +}) => SavedObjectsClientContract; + // @public export interface SavedObjectsClientProviderOptions { // (undocumented) @@ -1246,6 +1258,11 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { refresh?: MutatingOperationRefreshSetting; } +// @public (undocumented) +export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { + refresh?: MutatingOperationRefreshSetting; +} + // @public (undocumented) export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { refresh?: MutatingOperationRefreshSetting; @@ -1456,6 +1473,13 @@ export interface SavedObjectsImportUnsupportedTypeError { type: 'unsupported_type'; } +// @public (undocumented) +export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions { + // (undocumented) + migrationVersion?: SavedObjectsMigrationVersion; + refresh?: MutatingOperationRefreshSetting; +} + // @internal @deprecated (undocumented) export interface SavedObjectsLegacyService { // Warning: (ae-forgotten-export) The symbol "SavedObjectsClientProvider" needs to be exported by the entry point index.d.ts @@ -1515,6 +1539,32 @@ export interface SavedObjectsRawDoc { _type?: string; } +// @public (undocumented) +export class SavedObjectsRepository { + // Warning: (ae-forgotten-export) The symbol "SavedObjectsRepositoryOptions" needs to be exported by the entry point index.d.ts + // + // @internal + constructor(options: SavedObjectsRepositoryOptions); + bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; + bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; + bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; + create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; + delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; + deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; + // (undocumented) + find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; + get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; + incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{ + id: string; + type: string; + updated_at: string; + references: any; + version: string; + attributes: any; + }>; + update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; + } + // @public export interface SavedObjectsResolveImportErrorsOptions { // (undocumented) @@ -1555,6 +1605,19 @@ export class SavedObjectsSerializer { savedObjectToRaw(savedObj: SanitizedSavedObjectDoc): SavedObjectsRawDoc; } +// @public +export interface SavedObjectsServiceSetup { + addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; + createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; +} + +// @public +export interface SavedObjectsServiceStart { + getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; +} + // @public (undocumented) export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions { references?: SavedObjectReference[]; diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts index f8eb5e32f4c5a8..b378273a7075aa 100644 --- a/src/core/server/server.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -35,9 +35,11 @@ jest.doMock('./elasticsearch/elasticsearch_service', () => ({ ElasticsearchService: jest.fn(() => mockElasticsearchService), })); -export const mockLegacyService = { +import { ILegacyService } from './legacy/legacy_service'; +export const mockLegacyService: ILegacyService = { legacyId: Symbol(), - setup: jest.fn().mockReturnValue({ uiExports: {} }), + discoverPlugins: jest.fn().mockReturnValue({ uiExports: {} }), + setup: jest.fn(), start: jest.fn(), stop: jest.fn(), }; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 6c38de03f0f2d3..b36468b85d7a15 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -38,7 +38,6 @@ import { config as savedObjectsConfig } from './saved_objects'; import { config as uiSettingsConfig } from './ui_settings'; import { mapToObject } from '../utils/'; import { ContextService } from './context'; -import { SavedObjectsServiceSetup } from './saved_objects/saved_objects_service'; import { RequestHandlerContext } from '.'; import { InternalCoreSetup } from './internal_types'; @@ -78,6 +77,7 @@ export class Server { // Discover any plugins before continuing. This allows other systems to utilize the plugin dependency graph. const pluginDependencies = await this.plugins.discover(); + const legacyPlugins = await this.legacy.discoverPlugins(); const contextServiceSetup = this.context.setup({ // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins: // 1) Can access context from any NP plugin @@ -103,40 +103,41 @@ export class Server { http: httpSetup, }); + const savedObjectsSetup = await this.savedObjects.setup({ + elasticsearch: elasticsearchServiceSetup, + legacyPlugins, + }); + const coreSetup: InternalCoreSetup = { context: contextServiceSetup, elasticsearch: elasticsearchServiceSetup, http: httpSetup, uiSettings: uiSettingsSetup, + savedObjects: savedObjectsSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); - const legacySetup = await this.legacy.setup({ + await this.legacy.setup({ core: { ...coreSetup, plugins: pluginsSetup }, plugins: mapToObject(pluginsSetup.contracts), }); - const savedObjectsSetup = await this.savedObjects.setup({ - elasticsearch: elasticsearchServiceSetup, - legacy: legacySetup, - }); - - this.registerCoreContext(coreSetup, savedObjectsSetup); + this.registerCoreContext(coreSetup); return coreSetup; } public async start() { this.log.debug('starting server'); - const pluginsStart = await this.plugins.start({}); const savedObjectsStart = await this.savedObjects.start({}); + const pluginsStart = await this.plugins.start({ savedObjects: savedObjectsStart }); + const coreStart = { savedObjects: savedObjectsStart, plugins: pluginsStart, }; - await this.legacy.start({ core: coreStart, plugins: mapToObject(pluginsStart.contracts), @@ -164,17 +165,14 @@ export class Server { ); } - private registerCoreContext( - coreSetup: InternalCoreSetup, - savedObjects: SavedObjectsServiceSetup - ) { + private registerCoreContext(coreSetup: InternalCoreSetup) { coreSetup.http.registerRouteHandlerContext( coreId, 'core', async (context, req): Promise => { const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise(); const dataClient = await coreSetup.elasticsearch.dataClient$.pipe(take(1)).toPromise(); - const savedObjectsClient = savedObjects.clientProvider.getClient(req); + const savedObjectsClient = coreSetup.savedObjects.getScopedClient(req); return { savedObjects: { diff --git a/src/es_archiver/lib/indices/kibana_index.js b/src/es_archiver/lib/indices/kibana_index.js index 6f491783829a8c..1d88b7dc4e6340 100644 --- a/src/es_archiver/lib/indices/kibana_index.js +++ b/src/es_archiver/lib/indices/kibana_index.js @@ -26,6 +26,7 @@ import { toArray } from 'rxjs/operators'; import { deleteIndex } from './delete_index'; import { collectUiExports } from '../../../legacy/ui/ui_exports'; import { KibanaMigrator } from '../../../core/server/saved_objects/migrations'; +import { SavedObjectsSchema } from '../../../core/server/saved_objects'; import { findPluginSpecs } from '../../../legacy/plugin_discovery'; /** @@ -101,7 +102,7 @@ export async function migrateKibanaIndex({ client, log, kibanaPluginIds }) { error: log.error.bind(log), }, version: kibanaVersion, - savedObjectSchemas: uiExports.savedObjectSchemas, + savedObjectSchemas: new SavedObjectsSchema(uiExports.savedObjectSchemas), savedObjectMappings: uiExports.savedObjectMappings, savedObjectMigrations: uiExports.savedObjectMigrations, savedObjectValidations: uiExports.savedObjectValidations, From 6b5108a57dce73b01ba1cfad038e58ac11e03bbf Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Wed, 27 Nov 2019 17:22:15 +0100 Subject: [PATCH 04/52] fixes pagination tests (#51822) --- .../smoke_tests/pagination/pagination.spec.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts index 7822f4d30365d8..ebd0ad0125efb6 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts @@ -23,19 +23,19 @@ describe('Pagination', () => { return logout(); }); - it.skip('pagination updates results and page number', () => { + it('pagination updates results and page number', () => { loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); cy.get(getPageButtonSelector(0)).should('have.class', 'euiPaginationButton-isActive'); - cy.get(getDraggableField('user.name')) + cy.get(getDraggableField('process.name')) .first() .invoke('text') .then(text1 => { cy.get(getPageButtonSelector(2)).click({ force: true }); // wait for table to be done loading waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); - cy.get(getDraggableField('user.name')) + cy.get(getDraggableField('process.name')) .first() .invoke('text') .should(text2 => { @@ -55,7 +55,7 @@ describe('Pagination', () => { // wait for table to be done loading waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); - cy.get(getDraggableField('user.name')) + cy.get(getDraggableField('process.name')) .first() .invoke('text') .then(text2 => { @@ -70,7 +70,7 @@ describe('Pagination', () => { waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); // check uncommon processes table picks up at 3 cy.get(getPageButtonSelector(2)).should('have.class', 'euiPaginationButton-isActive'); - cy.get(getDraggableField('user.name')) + cy.get(getDraggableField('process.name')) .first() .invoke('text') .should(text1 => { @@ -82,7 +82,7 @@ describe('Pagination', () => { * We only want to comment this code/test for now because it can be nondeterministic * when we figure out a way to really mock the data, we should come back to it */ - it.skip('pagination resets results and page number to first page when refresh is clicked', () => { + it('pagination resets results and page number to first page when refresh is clicked', () => { loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); cy.get(NUMBERED_PAGINATION, { timeout: DEFAULT_TIMEOUT }); cy.get(getPageButtonSelector(0)).should('have.class', 'euiPaginationButton-isActive'); @@ -100,7 +100,7 @@ describe('Pagination', () => { .last() .click({ force: true }); waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); - cy.get(getPageButtonSelector(0)).should('have.class', 'euiPaginationButton-isActive'); + cy.get(getPageButtonSelector(2)).should('have.class', 'euiPaginationButton-isActive'); // cy.get(getDraggableField('user.name')) // .first() // .invoke('text') From 7a0940958749bc11660f1517a6b8ac6279ab6591 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Wed, 27 Nov 2019 18:40:28 +0200 Subject: [PATCH 05/52] =?UTF-8?q?Move=20errors=20and=20validate=20index=20?= =?UTF-8?q?pattern=20=E2=87=92=20NP=20(#51805)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move errors and validate index pattern to NP * removed unused mock * remvoed irrelevant mock * Removed unneeded mocks * fix test --- src/legacy/core_plugins/data/public/index.ts | 14 +---- .../index_patterns/index_pattern.ts | 4 +- .../index_patterns/index_patterns.test.ts | 4 -- .../index_patterns_api_client.ts | 4 +- .../index_patterns/index_patterns_service.ts | 18 +------ .../data/public/index_patterns/utils.ts | 41 --------------- .../public/index_patterns/__mocks__/index.ts | 14 +---- src/legacy/ui/public/index_patterns/index.ts | 22 ++++---- .../indices/validate/validate_index.test.js | 1 - src/plugins/data/public/index.ts | 1 + .../data/public/index_patterns/errors.ts | 30 +---------- .../data/public/index_patterns/index.ts | 36 +++++++++++++ .../data/public/index_patterns/lib/index.ts | 2 + .../data/public/index_patterns/lib/types.ts | 23 +++++++++ .../lib/validate_index_pattern.test.ts} | 15 +++--- .../lib/validate_index_pattern.ts | 51 +++++++++++++++++++ .../auto_follow_pattern_add.test.js | 1 - .../auto_follow_pattern_edit.test.js | 1 - .../auto_follow_pattern_list.test.js | 1 - .../follower_index_add.test.js | 1 - .../follower_index_edit.test.js | 1 - .../follower_indices_list.test.js | 1 - .../__jest__/client_integration/home.test.js | 1 - .../auto_follow_pattern_form.test.js | 1 - .../auto_follow_pattern_validators.test.js | 1 - .../job_create_clone.test.js | 1 - .../job_create_date_histogram.test.js | 1 - .../job_create_histogram.test.js | 1 - .../job_create_logistics.test.js | 1 - .../job_create_metrics.test.js | 1 - .../job_create_review.test.js | 1 - .../job_create_terms.test.js | 1 - .../client_integration/job_list.test.js | 1 - .../client_integration/job_list_clone.test.js | 1 - 34 files changed, 136 insertions(+), 162 deletions(-) rename src/{legacy/core_plugins => plugins}/data/public/index_patterns/errors.ts (59%) create mode 100644 src/plugins/data/public/index_patterns/index.ts create mode 100644 src/plugins/data/public/index_patterns/lib/types.ts rename src/{legacy/core_plugins/data/public/index_patterns/utils.test.ts => plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts} (79%) create mode 100644 src/plugins/data/public/index_patterns/lib/validate_index_pattern.ts diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 01f67a63ca9bec..184084e3cc3e65 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -43,16 +43,4 @@ export { SearchBar, SearchBarProps, SavedQueryAttributes, SavedQuery } from './s /** @public static code */ export * from '../common'; export { FilterStateManager } from './filter/filter_manager'; -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - validateIndexPattern, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from './index_patterns'; +export { getFromSavedObject, getRoutes } from './index_patterns'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts index f77342c7bc2744..de364b6c217dd7 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -32,10 +32,10 @@ import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, + indexPatterns, } from '../../../../../../plugins/data/public'; import { findIndexPatternByTitle, getRoutes } from '../utils'; -import { IndexPatternMissingIndices } from '../errors'; import { Field, FieldList, FieldListInterface, FieldType } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; @@ -499,7 +499,7 @@ export class IndexPattern implements IIndexPattern { // so do not rethrow the error here const { toasts } = getNotifications(); - if (err instanceof IndexPatternMissingIndices) { + if (err instanceof indexPatterns.IndexPatternMissingIndices) { toasts.addDanger((err as any).message); return []; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts index 0a5d1bfcae21f9..2ad0a1f1394e59 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts @@ -25,10 +25,6 @@ import { HttpServiceBase, } from 'kibana/public'; -jest.mock('../errors', () => ({ - IndexPatternMissingIndices: jest.fn(), -})); - jest.mock('./index_pattern', () => { class IndexPattern { init = async () => { diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index c0e8516a75bb35..87dd7a68e30614 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -18,7 +18,7 @@ */ import { HttpServiceBase } from 'src/core/public'; -import { IndexPatternMissingIndices } from '../errors'; +import { indexPatterns } from '../../../../../../plugins/data/public'; const API_BASE_URL: string = `/api/index_patterns/`; @@ -46,7 +46,7 @@ export class IndexPatternsApiClient { }) .catch((resp: any) => { if (resp.body.statusCode === 404 && resp.body.statuscode === 'no_matching_indices') { - throw new IndexPatternMissingIndices(resp.body.message); + throw new indexPatterns.IndexPatternMissingIndices(resp.body.message); } throw new Error(resp.body.message || resp.body.error || `${resp.body.statusCode} Response`); diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts index 381cd491f02103..9973a7081443dc 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts @@ -89,23 +89,7 @@ export class IndexPatternsService { // static code /** @public */ -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - validateIndexPattern, -} from './utils'; - -/** @public */ -export { - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from './errors'; +export { getFromSavedObject, getRoutes } from './utils'; // types diff --git a/src/legacy/core_plugins/data/public/index_patterns/utils.ts b/src/legacy/core_plugins/data/public/index_patterns/utils.ts index 8c2878a3ff9bad..0d0d5705a0cccd 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/utils.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/utils.ts @@ -21,27 +21,6 @@ import { find, get } from 'lodash'; import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; -export const ILLEGAL_CHARACTERS = 'ILLEGAL_CHARACTERS'; -export const CONTAINS_SPACES = 'CONTAINS_SPACES'; -export const INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE = ['\\', '/', '?', '"', '<', '>', '|']; -export const INDEX_PATTERN_ILLEGAL_CHARACTERS = INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.concat( - ' ' -); - -function findIllegalCharacters(indexPattern: string): string[] { - const illegalCharacters = INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.reduce( - (chars: string[], char: string) => { - if (indexPattern.includes(char)) { - chars.push(char); - } - return chars; - }, - [] - ); - - return illegalCharacters; -} - /** * Returns an object matching a given title * @@ -71,26 +50,6 @@ export async function findIndexPatternByTitle( ); } -function indexPatternContainsSpaces(indexPattern: string): boolean { - return indexPattern.includes(' '); -} - -export function validateIndexPattern(indexPattern: string) { - const errors: Record = {}; - - const illegalCharacters = findIllegalCharacters(indexPattern); - - if (illegalCharacters.length) { - errors[ILLEGAL_CHARACTERS] = illegalCharacters; - } - - if (indexPatternContainsSpaces(indexPattern)) { - errors[CONTAINS_SPACES] = true; - } - - return errors; -} - export function getFromSavedObject(savedObject: any) { if (get(savedObject, 'attributes.fields') === undefined) { return; diff --git a/src/legacy/ui/public/index_patterns/__mocks__/index.ts b/src/legacy/ui/public/index_patterns/__mocks__/index.ts index 145045a90ade8f..9ff09835e48da2 100644 --- a/src/legacy/ui/public/index_patterns/__mocks__/index.ts +++ b/src/legacy/ui/public/index_patterns/__mocks__/index.ts @@ -31,16 +31,4 @@ export const { } = dataSetup.indexPatterns!; // static code -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - validateIndexPattern, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from '../../../../core_plugins/data/public'; +export { getFromSavedObject, getRoutes } from '../../../../core_plugins/data/public'; diff --git a/src/legacy/ui/public/index_patterns/index.ts b/src/legacy/ui/public/index_patterns/index.ts index d0ff0aaa8c72c7..06001667c9e53a 100644 --- a/src/legacy/ui/public/index_patterns/index.ts +++ b/src/legacy/ui/public/index_patterns/index.ts @@ -32,20 +32,16 @@ export const { formatHitProvider, } = data.indexPatterns; +import { indexPatterns } from '../../../../plugins/data/public'; + // static code -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - validateIndexPattern, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from '../../../core_plugins/data/public'; +export { getFromSavedObject, getRoutes } from '../../../core_plugins/data/public'; + +export const INDEX_PATTERN_ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS; +export const INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE; +export const ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS_KEY; +export const CONTAINS_SPACES = indexPatterns.CONTAINS_SPACES_KEY; +export const validateIndexPattern = indexPatterns.validate; // types export { diff --git a/src/legacy/ui/public/indices/validate/validate_index.test.js b/src/legacy/ui/public/indices/validate/validate_index.test.js index 62a6c8610fd40c..f81ba9d4bcab52 100644 --- a/src/legacy/ui/public/indices/validate/validate_index.test.js +++ b/src/legacy/ui/public/indices/validate/validate_index.test.js @@ -18,7 +18,6 @@ */ jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); import { INDEX_ILLEGAL_CHARACTERS_VISIBLE } from '../constants'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index ace0b44378b459..eca6258099141e 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -30,6 +30,7 @@ export * from '../common'; export * from './autocomplete_provider'; export * from './field_formats_provider'; +export * from './index_patterns'; export * from './types'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/errors.ts b/src/plugins/data/public/index_patterns/errors.ts similarity index 59% rename from src/legacy/core_plugins/data/public/index_patterns/errors.ts rename to src/plugins/data/public/index_patterns/errors.ts index c64da47b8c7850..3eb43eaf460cc8 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/errors.ts +++ b/src/plugins/data/public/index_patterns/errors.ts @@ -19,17 +19,7 @@ /* eslint-disable */ -import { KbnError } from '../../../../../plugins/kibana_utils/public'; - -/** - * when a mapping already exists for a field the user is attempting to add - * @param {String} name - the field name - */ -export class IndexPatternAlreadyExists extends KbnError { - constructor(name: string) { - super(`An index pattern of "${name}" already exists`); - } -} +import { KbnError } from '../../../kibana_utils/public'; /** * Tried to call a method that relies on SearchSource having an indexPattern assigned @@ -43,21 +33,3 @@ export class IndexPatternMissingIndices extends KbnError { ); } } - -/** - * Tried to call a method that relies on SearchSource having an indexPattern assigned - */ -export class NoDefinedIndexPatterns extends KbnError { - constructor() { - super('Define at least one index pattern to continue'); - } -} - -/** - * Tried to load a route besides management/kibana/index but you don't have a default index pattern! - */ -export class NoDefaultIndexPattern extends KbnError { - constructor() { - super('Please specify a default index pattern'); - } -} diff --git a/src/plugins/data/public/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index.ts new file mode 100644 index 00000000000000..aedfc18db3ade3 --- /dev/null +++ b/src/plugins/data/public/index_patterns/index.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IndexPatternMissingIndices } from './errors'; +import { + ILLEGAL_CHARACTERS_KEY, + CONTAINS_SPACES_KEY, + ILLEGAL_CHARACTERS_VISIBLE, + ILLEGAL_CHARACTERS, + validateIndexPattern, +} from './lib'; + +export const indexPatterns = { + ILLEGAL_CHARACTERS_KEY, + CONTAINS_SPACES_KEY, + ILLEGAL_CHARACTERS_VISIBLE, + ILLEGAL_CHARACTERS, + IndexPatternMissingIndices, + validate: validateIndexPattern, +}; diff --git a/src/plugins/data/public/index_patterns/lib/index.ts b/src/plugins/data/public/index_patterns/lib/index.ts index d1c229513aa339..3b87d91bb9fffb 100644 --- a/src/plugins/data/public/index_patterns/lib/index.ts +++ b/src/plugins/data/public/index_patterns/lib/index.ts @@ -18,3 +18,5 @@ */ export { getIndexPatternTitle } from './get_index_pattern_title'; +export * from './types'; +export { validateIndexPattern } from './validate_index_pattern'; diff --git a/src/plugins/data/public/index_patterns/lib/types.ts b/src/plugins/data/public/index_patterns/lib/types.ts new file mode 100644 index 00000000000000..5eb309a1e5a9ce --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/types.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const ILLEGAL_CHARACTERS_KEY = 'ILLEGAL_CHARACTERS'; +export const CONTAINS_SPACES_KEY = 'CONTAINS_SPACES'; +export const ILLEGAL_CHARACTERS_VISIBLE = ['\\', '/', '?', '"', '<', '>', '|']; +export const ILLEGAL_CHARACTERS = ILLEGAL_CHARACTERS_VISIBLE.concat(' '); diff --git a/src/legacy/core_plugins/data/public/index_patterns/utils.test.ts b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts similarity index 79% rename from src/legacy/core_plugins/data/public/index_patterns/utils.test.ts rename to src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts index cff48144489f05..74e420ffeb5c05 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/utils.test.ts +++ b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts @@ -17,24 +17,21 @@ * under the License. */ -import { - CONTAINS_SPACES, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - validateIndexPattern, -} from './utils'; +import { CONTAINS_SPACES_KEY, ILLEGAL_CHARACTERS_KEY, ILLEGAL_CHARACTERS_VISIBLE } from './types'; + +import { validateIndexPattern } from './validate_index_pattern'; describe('Index Pattern Utils', () => { describe('Validation', () => { it('should not allow space in the pattern', () => { const errors = validateIndexPattern('my pattern'); - expect(errors[CONTAINS_SPACES]).toBe(true); + expect(errors[CONTAINS_SPACES_KEY]).toBe(true); }); it('should not allow illegal characters', () => { - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.forEach(char => { + ILLEGAL_CHARACTERS_VISIBLE.forEach(char => { const errors = validateIndexPattern(`pattern${char}`); - expect(errors[ILLEGAL_CHARACTERS]).toEqual([char]); + expect(errors[ILLEGAL_CHARACTERS_KEY]).toEqual([char]); }); }); diff --git a/src/plugins/data/public/index_patterns/lib/validate_index_pattern.ts b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.ts new file mode 100644 index 00000000000000..70f5971c91bd5d --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ILLEGAL_CHARACTERS_VISIBLE, CONTAINS_SPACES_KEY, ILLEGAL_CHARACTERS_KEY } from './types'; + +function indexPatternContainsSpaces(indexPattern: string): boolean { + return indexPattern.includes(' '); +} + +function findIllegalCharacters(indexPattern: string): string[] { + const illegalCharacters = ILLEGAL_CHARACTERS_VISIBLE.reduce((chars: string[], char: string) => { + if (indexPattern.includes(char)) { + chars.push(char); + } + return chars; + }, []); + + return illegalCharacters; +} + +export function validateIndexPattern(indexPattern: string) { + const errors: Record = {}; + + const illegalCharacters = findIllegalCharacters(indexPattern); + + if (illegalCharacters.length) { + errors[ILLEGAL_CHARACTERS_KEY] = illegalCharacters; + } + + if (indexPatternContainsSpaces(indexPattern)) { + errors[CONTAINS_SPACES_KEY] = true; + } + + return errors; +} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_add.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_add.test.js index 476f01940d892b..7359a240981867 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_add.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_add.test.js @@ -8,7 +8,6 @@ import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './help import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from '../../../../../../src/legacy/ui/public/index_patterns'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); const { setup } = pageHelpers.autoFollowPatternAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_edit.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_edit.test.js index 9ef412883522a7..03155f5f55000b 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_edit.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_edit.test.js @@ -9,7 +9,6 @@ import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { AUTO_FOLLOW_PATTERN_EDIT } from './helpers/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); const { setup } = pageHelpers.autoFollowPatternEdit; const { setup: setupAutoFollowPatternAdd } = pageHelpers.autoFollowPatternAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js index 8a6d3821909458..904434e46dee06 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js @@ -9,7 +9,6 @@ import { setupEnvironment, pageHelpers, nextTick, findTestSubject, getRandomStri import { getAutoFollowPatternClientMock } from '../../fixtures/auto_follow_pattern'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('ui/chrome', () => ({ addBasePath: () => 'api/cross_cluster_replication', diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_add.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_add.test.js index d28d671fb2ace5..0d90d4cf3d2720 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_add.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_add.test.js @@ -10,7 +10,6 @@ import { RemoteClustersFormField } from '../../public/app/components'; import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from '../../../../../../src/legacy/ui/public/index_patterns'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); const { setup } = pageHelpers.followerIndexAdd; const { setup: setupAutoFollowPatternAdd } = pageHelpers.autoFollowPatternAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_edit.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_edit.test.js index 5e74d923d3af5f..de1426bf4b72f2 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_edit.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_edit.test.js @@ -10,7 +10,6 @@ import { FollowerIndexForm } from '../../public/app/components/follower_index_fo import { FOLLOWER_INDEX_EDIT } from './helpers/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); const { setup } = pageHelpers.followerIndexEdit; const { setup: setupFollowerIndexAdd } = pageHelpers.followerIndexAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js index 6aef850672179c..13adea4592534b 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js @@ -9,7 +9,6 @@ import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './help import { getFollowerIndexMock } from '../../fixtures/follower_index'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('ui/chrome', () => ({ addBasePath: () => 'api/cross_cluster_replication', diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js index 35ec99846990ab..5691ff3a8bc3ba 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js @@ -8,7 +8,6 @@ import { setupEnvironment, pageHelpers, nextTick } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); const { setup } = pageHelpers.home; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js index f6cc9cb3742eab..eda275ba50c1a6 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js @@ -12,7 +12,6 @@ jest.mock('../services/auto_follow_pattern_validators', () => ({ })); jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); describe(' { describe('updateFormErrors()', () => { diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js index f70caf2f8080b8..6c1d5c8ce171c2 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js @@ -8,7 +8,6 @@ import { validateAutoFollowPattern } from './auto_follow_pattern_validators'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); describe('Auto-follow pattern validators', () => { describe('validateAutoFollowPattern()', () => { diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_clone.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_clone.test.js index 29d2d00163ad8c..204bab5c497be2 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_clone.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_clone.test.js @@ -8,7 +8,6 @@ import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { JOB_TO_CLONE, JOB_CLONE_INDEX_PATTERN_CHECK } from './helpers/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_date_histogram.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_date_histogram.test.js index 59814474396feb..b7b555d986597e 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_date_histogram.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_date_histogram.test.js @@ -9,7 +9,6 @@ import moment from 'moment-timezone'; import { setupEnvironment, pageHelpers } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_histogram.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_histogram.test.js index 09417fa8ed3072..dbbd7501b15181 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_histogram.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_histogram.test.js @@ -7,7 +7,6 @@ import { setupEnvironment, pageHelpers } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js index 99a0aa09351520..a853ef36e01cde 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js @@ -9,7 +9,6 @@ import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from '../../../../../../src/ import { setupEnvironment, pageHelpers } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_metrics.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_metrics.test.js index 2f26d2a7475de3..d2f63983a3e369 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_metrics.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_metrics.test.js @@ -7,7 +7,6 @@ import { setupEnvironment, pageHelpers } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js index 8ca736e62be7f1..c89d37f4e0ac3e 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js @@ -9,7 +9,6 @@ import { first } from 'lodash'; import { JOBS } from './helpers/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_terms.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_terms.test.js index 78e8d9ec0c53a5..c27b9d0e4ef0fe 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_terms.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_terms.test.js @@ -7,7 +7,6 @@ import { setupEnvironment, pageHelpers } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js index 05272bf2226123..db7dddad4e3c13 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js @@ -9,7 +9,6 @@ import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { JOBS } from './helpers/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('../../public/crud_app/services', () => { const services = require.requireActual('../../public/crud_app/services'); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list_clone.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list_clone.test.js index ce62f6c67ae033..6feabe7f772eef 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list_clone.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list_clone.test.js @@ -10,7 +10,6 @@ import { getRouter } from '../../public/crud_app/services/routing'; import { CRUD_APP_BASE_PATH } from '../../public/crud_app/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); From 2ce0f789bbfd61850d555a609e32e681af5653ef Mon Sep 17 00:00:00 2001 From: Andrea Del Rio Date: Wed, 27 Nov 2019 12:32:49 -0800 Subject: [PATCH 06/52] [Monitoring] Sass cleanup (#51100) --- .../public/components/chart/_chart.scss | 15 +- .../components/chart/horizontal_legend.js | 72 +- .../chart/monitoring_timeseries_container.js | 33 +- .../elasticsearch/indices/indices.js | 65 +- .../components/elasticsearch/nodes/nodes.js | 169 +- .../shard_allocation/_shard_allocation.scss | 89 +- .../shard_allocation/components/assigned.js | 56 +- .../shard_allocation/components/unassigned.js | 34 +- .../shard_allocation/shard_allocation.js | 2 +- .../public/components/status_icon/index.js | 19 +- .../__snapshots__/summary_status.test.js.snap | 38 +- .../public/components/table/_table.scss | 5 + .../nodes/__tests__/get_node_summary.js | 52 +- .../__tests__/get_node_type_class_label.js | 8 +- .../handle_response.test.js.snap | 16 +- .../__snapshots__/map_nodes_info.test.js.snap | 4 +- .../server/lib/elasticsearch/nodes/lookups.js | 30 +- .../fixtures/node_detail_advanced.json | 3126 +++++++++-------- .../fixtures/nodes_listing_cgroup.json | 8 +- .../fixtures/nodes_listing_green.json | 8 +- .../fixtures/nodes_listing_red.json | 8 +- 21 files changed, 1957 insertions(+), 1900 deletions(-) diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/_chart.scss b/x-pack/legacy/plugins/monitoring/public/components/chart/_chart.scss index d3b705a5eb4926..1b8ebb762533d9 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/_chart.scss +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/_chart.scss @@ -1,7 +1,5 @@ -@mixin monitoringNoUserSelect(){ +@mixin monitoringNoUserSelect { user-select: none; - -webkit-touch-callout: none; - -webkit-tap-highlight-color: transparent; } .monRhythmChart__wrapper .monRhythmChart__zoom { @@ -12,7 +10,7 @@ .monRhythmChart__wrapper:hover .monRhythmChart__zoom { visibility: visible; } - + .monRhythmChart { position: relative; display: flex; @@ -50,7 +48,7 @@ // SASSTODO: generic selector div { - @include monitoringNoUserSelect(); + @include monitoringNoUserSelect; } } @@ -58,6 +56,9 @@ font-size: $euiFontSizeXS; cursor: pointer; color: $euiTextColor; + display: flex; + flex-direction: row; + align-items: center; &-isDisabled { opacity: 0.5; @@ -71,7 +72,11 @@ .monRhythmChart__legendLabel { overflow: hidden; white-space: nowrap; + display: flex; + flex-direction: row; + align-items: center; } + .monRhythmChart__legendValue { overflow: hidden; white-space: nowrap; diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/horizontal_legend.js b/x-pack/legacy/plugins/monitoring/public/components/chart/horizontal_legend.js index 9ce4d6224c45ea..ab322324ac2006 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/horizontal_legend.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/horizontal_legend.js @@ -6,9 +6,7 @@ import React from 'react'; import { includes, isFunction } from 'lodash'; -import { - EuiKeyboardAccessible, -} from '@elastic/eui'; +import { EuiFlexItem, EuiFlexGroup, EuiIcon, EuiKeyboardAccessible } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -23,11 +21,7 @@ export class HorizontalLegend extends React.Component { * @param {Number} value Final value to display */ displayValue(value) { - return ( - - { value } - - ); + return {value}; } /** @@ -44,10 +38,12 @@ export class HorizontalLegend extends React.Component { */ formatter(value, row) { if (!this.validValue(value)) { - return (); + return ( + + ); } if (row && row.tickFormatter) { @@ -61,38 +57,38 @@ export class HorizontalLegend extends React.Component { } createSeries(row, rowIdx) { - const classes = ['col-md-4 col-xs-6 monRhythmChart__legendItem']; + const classes = ['monRhythmChart__legendItem']; if (!includes(this.props.seriesFilter, row.id)) { classes.push('monRhythmChart__legendItem-isDisabled'); } if (!row.label || row.legend === false) { - return ( -
- ); + return
; } return ( -
this.props.onToggle(event, row.id)} - > - - - { ' ' + row.label + ' ' } - - { this.formatter(this.props.seriesValues[row.id], row) } -
+ + +
); } @@ -102,9 +98,9 @@ export class HorizontalLegend extends React.Component { return (
-
- { rows } -
+ + {rows} +
); } diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/monitoring_timeseries_container.js b/x-pack/legacy/plugins/monitoring/public/components/chart/monitoring_timeseries_container.js index 9216ac7c28705b..6760a037fbe8a0 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/monitoring_timeseries_container.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/monitoring_timeseries_container.js @@ -12,12 +12,18 @@ import { MonitoringTimeseries } from './monitoring_timeseries'; import { InfoTooltip } from './info_tooltip'; import { - EuiIconTip, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiScreenReaderOnly, EuiTextAlign, EuiButtonEmpty + EuiIconTip, + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiScreenReaderOnly, + EuiTextAlign, + EuiButtonEmpty, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -const zoomOutBtn = (zoomInfo) => { +const zoomOutBtn = zoomInfo => { if (!zoomInfo || !zoomInfo.showZoomOutBtn()) { return null; } @@ -28,9 +34,9 @@ const zoomOutBtn = (zoomInfo) => { - {' '} `${item.metric.label}: ${item.metric.description}`)); + bucketSize, + }, + }), + ].concat(series.map(item => `${item.metric.label}: ${item.metric.description}`)); return ( @@ -68,7 +73,8 @@ export function MonitoringTimeseriesContainer({ series, onBrush, zoomInfo }) {

- { getTitle(series) }{ units ? ` (${units})` : '' } + {getTitle(series)} + {units ? ` (${units})` : ''} } + content={} /> @@ -95,14 +101,11 @@ export function MonitoringTimeseriesContainer({ series, onBrush, zoomInfo }) { - { zoomOutBtn(zoomInfo) } + {zoomOutBtn(zoomInfo)} - + ); diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js index d53f2678652325..232815e930388e 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js @@ -31,12 +31,9 @@ const columns = [ field: 'name', width: '350px', sortable: true, - render: (value) => ( + render: value => (
- + {value}
@@ -48,12 +45,13 @@ const columns = [ }), field: 'status', sortable: true, - render: (value) => ( -
-   + render: value => ( +
+ +   {capitalize(value)}
- ) + ), }, { name: i18n.translate('xpack.monitoring.elasticsearch.indices.documentCountTitle', { @@ -62,10 +60,8 @@ const columns = [ field: 'doc_count', sortable: true, render: value => ( -
- {formatMetric(value, LARGE_ABBREVIATED)} -
- ) +
{formatMetric(value, LARGE_ABBREVIATED)}
+ ), }, { name: i18n.translate('xpack.monitoring.elasticsearch.indices.dataTitle', { @@ -73,11 +69,7 @@ const columns = [ }), field: 'data_size', sortable: true, - render: value => ( -
- {formatMetric(value, LARGE_BYTES)} -
- ) + render: value =>
{formatMetric(value, LARGE_BYTES)}
, }, { name: i18n.translate('xpack.monitoring.elasticsearch.indices.indexRateTitle', { @@ -85,11 +77,7 @@ const columns = [ }), field: 'index_rate', sortable: true, - render: value => ( -
- {formatMetric(value, LARGE_FLOAT, '/s')} -
- ) + render: value =>
{formatMetric(value, LARGE_FLOAT, '/s')}
, }, { name: i18n.translate('xpack.monitoring.elasticsearch.indices.searchRateTitle', { @@ -98,10 +86,8 @@ const columns = [ field: 'search_rate', sortable: true, render: value => ( -
- {formatMetric(value, LARGE_FLOAT, '/s')} -
- ) +
{formatMetric(value, LARGE_FLOAT, '/s')}
+ ), }, { name: i18n.translate('xpack.monitoring.elasticsearch.indices.unassignedShardsTitle', { @@ -109,12 +95,8 @@ const columns = [ }), field: 'unassigned_shards', sortable: true, - render: value => ( -
- {formatMetric(value, '0')} -
- ) - } + render: value =>
{formatMetric(value, '0')}
, + }, ]; const getNoDataMessage = () => { @@ -154,16 +136,16 @@ export const ElasticsearchIndices = ({ - )} + } checked={showSystemIndices} onChange={e => toggleShowSystemIndices(e.target.checked)} /> - + diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index 72a74964fd35ed..b06cbb44503d12 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -12,6 +12,7 @@ import { EuiMonitoringSSPTable } from '../../table'; import { MetricCell, OfflineCell } from './cells'; import { SetupModeBadge } from '../../setup_mode/badge'; import { + EuiIcon, EuiLink, EuiToolTip, EuiSpacer, @@ -21,20 +22,23 @@ import { EuiPanel, EuiCallOut, EuiButton, - EuiText + EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import { ELASTICSEARCH_SYSTEM_ID } from '../../../../common/constants'; import { ListingCallOut } from '../../setup_mode/listing_callout'; -const getSortHandler = (type) => (item) => _.get(item, [type, 'summary', 'lastVal']); +const getSortHandler = type => item => _.get(item, [type, 'summary', 'lastVal']); const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { const cols = []; - const cpuUsageColumnTitle = i18n.translate('xpack.monitoring.elasticsearch.nodes.cpuUsageColumnTitle', { - defaultMessage: 'CPU Usage', - }); + const cpuUsageColumnTitle = i18n.translate( + 'xpack.monitoring.elasticsearch.nodes.cpuUsageColumnTitle', + { + defaultMessage: 'CPU Usage', + } + ); cols.push({ name: i18n.translate('xpack.monitoring.elasticsearch.nodes.nameColumnTitle', { @@ -59,7 +63,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { const status = list[node.resolver] || {}; const instance = { uuid: node.resolver, - name: node.name + name: node.name, }; setupModeStatus = ( @@ -82,25 +86,18 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => {
- - + + {node.nodeTypeClass && }   - - {nameLink} - + {nameLink}
-
- {extractIp(node.transport_address)} -
+
{extractIp(node.transport_address)}
{setupModeStatus}
); - } + }, }); cols.push({ @@ -110,21 +107,19 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { field: 'isOnline', sortable: true, render: value => { - const status = value ? i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumn.onlineLabel', { - defaultMessage: 'Online', - }) : i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumn.offlineLabel', { - defaultMessage: 'Offline', - }); + const status = value + ? i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumn.onlineLabel', { + defaultMessage: 'Online', + }) + : i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumn.offlineLabel', { + defaultMessage: 'Offline', + }); return (
- {' '} - {status} + {status}
); - } + }, }); cols.push({ @@ -138,8 +133,10 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => {
{value}
- ) : ; - } + ) : ( + + ); + }, }); if (showCgroupMetricsElasticsearch) { @@ -154,7 +151,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={true} data-test-subj="cpuQuota" /> - ) + ), }); cols.push({ @@ -170,7 +167,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={false} data-test-subj="cpuThrottled" /> - ) + ), }); } else { cols.push({ @@ -184,7 +181,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={true} data-test-subj="cpuUsage" /> - ) + ), }); cols.push({ @@ -200,7 +197,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={false} data-test-subj="loadAverage" /> - ) + ), }); } @@ -208,8 +205,8 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { name: i18n.translate('xpack.monitoring.elasticsearch.nodes.jvmMemoryColumnTitle', { defaultMessage: '{javaVirtualMachine} Heap', values: { - javaVirtualMachine: 'JVM' - } + javaVirtualMachine: 'JVM', + }, }), field: 'node_jvm_mem_percent', sortable: getSortHandler('node_jvm_mem_percent'), @@ -220,7 +217,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={true} data-test-subj="jvmMemory" /> - ) + ), }); cols.push({ @@ -236,7 +233,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={false} data-test-subj="diskFreeSpace" /> - ) + ), }); return cols; @@ -252,18 +249,22 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear // We want to create a seamless experience for the user by merging in the setup data // and the node data from monitoring indices in the likely scenario where some nodes // are using MB collection and some are using no collection - const nodesByUuid = nodes.reduce((byUuid, node) => ({ - ...byUuid, - [node.id || node.resolver]: node - }), {}); + const nodesByUuid = nodes.reduce( + (byUuid, node) => ({ + ...byUuid, + [node.id || node.resolver]: node, + }), + {} + ); - nodes.push(...Object.entries(setupMode.data.byUuid) - .reduce((nodes, [nodeUuid, instance]) => { + nodes.push( + ...Object.entries(setupMode.data.byUuid).reduce((nodes, [nodeUuid, instance]) => { if (!nodesByUuid[nodeUuid] && instance.node) { nodes.push(instance.node); } return nodes; - }, [])); + }, []) + ); } let setupModeCallout = null; @@ -276,64 +277,81 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear customRenderer={() => { const customRenderResponse = { shouldRender: false, - componentToRender: null + componentToRender: null, }; const isNetNewUser = setupMode.data.totalUniqueInstanceCount === 0; - const hasNoInstances = setupMode.data.totalUniqueInternallyCollectedCount === 0 - && setupMode.data.totalUniqueFullyMigratedCount === 0 - && setupMode.data.totalUniquePartiallyMigratedCount === 0; + const hasNoInstances = + setupMode.data.totalUniqueInternallyCollectedCount === 0 && + setupMode.data.totalUniqueFullyMigratedCount === 0 && + setupMode.data.totalUniquePartiallyMigratedCount === 0; if (isNetNewUser || hasNoInstances) { customRenderResponse.shouldRender = true; customRenderResponse.componentToRender = ( 0 ? 'danger' : 'warning'} iconType="flag" >

- {i18n.translate('xpack.monitoring.elasticsearch.nodes.metricbeatMigration.detectedNodeDescription', { - defaultMessage: `The following nodes are not monitored. Click 'Monitor with Metricbeat' below to start monitoring.`, - })} + {i18n.translate( + 'xpack.monitoring.elasticsearch.nodes.metricbeatMigration.detectedNodeDescription', + { + defaultMessage: `The following nodes are not monitored. Click 'Monitor with Metricbeat' below to start monitoring.`, + } + )}

- +
); - } - else if (setupMode.data.totalUniquePartiallyMigratedCount === setupMode.data.totalUniqueInstanceCount) { - const finishMigrationAction = _.get(setupMode.meta, 'liveClusterUuid') === clusterUuid - ? setupMode.shortcutToFinishMigration - : setupMode.openFlyout; + } else if ( + setupMode.data.totalUniquePartiallyMigratedCount === + setupMode.data.totalUniqueInstanceCount + ) { + const finishMigrationAction = + _.get(setupMode.meta, 'liveClusterUuid') === clusterUuid + ? setupMode.shortcutToFinishMigration + : setupMode.openFlyout; customRenderResponse.shouldRender = true; customRenderResponse.componentToRender = (

- {i18n.translate('xpack.monitoring.elasticsearch.nodes.metricbeatMigration.disableInternalCollectionDescription', { - defaultMessage: `Disable self monitoring to finish the migration.` - })} + {i18n.translate( + 'xpack.monitoring.elasticsearch.nodes.metricbeatMigration.disableInternalCollectionDescription', + { + defaultMessage: `Disable self monitoring to finish the migration.`, + } + )}

{i18n.translate( - 'xpack.monitoring.elasticsearch.nodes.metricbeatMigration.disableInternalCollectionMigrationButtonLabel', { - defaultMessage: 'Disable self monitoring' + 'xpack.monitoring.elasticsearch.nodes.metricbeatMigration.disableInternalCollectionMigrationButtonLabel', + { + defaultMessage: 'Disable self monitoring', } )}
- +
); } @@ -375,9 +393,12 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear search={{ box: { incremental: true, - placeholder: i18n.translate('xpack.monitoring.elasticsearch.nodes.monitoringTablePlaceholder', { - defaultMessage: 'Filter Nodes…' - }), + placeholder: i18n.translate( + 'xpack.monitoring.elasticsearch.nodes.monitoringTablePlaceholder', + { + defaultMessage: 'Filter Nodes…', + } + ), }, }} onTableChange={onTableChange} diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/_shard_allocation.scss b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/_shard_allocation.scss index 690b1b81a0d03c..50e92d572908cb 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/_shard_allocation.scss +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/_shard_allocation.scss @@ -1,9 +1,3 @@ -// SASSTODO: Generic selector -monitoring-shard-allocation { - display: block; - border-top: $euiSizeS solid $euiColorLightestShade; -} - .monClusterTitle { font-size: $euiFontSizeL; margin: 0; @@ -11,55 +5,48 @@ monitoring-shard-allocation { // SASSTODO: This needs a full rewrite / redesign .monCluster { - cluster-view { - display: block; - } - .parent { - padding-top: 14px; - border-left: 3px solid $euiColorSuccess !important; - &.red { - border-left: 3px solid $euiColorDanger !important; - } - &.yellow { - border-left: 3px solid $euiColorWarning !important; - } - } - td.unassigned { + .monUnassigned { vertical-align: middle; width: 150px; } - .child { + .monUnassigned__children, + .monAssigned__children { + padding-top: $euiSizeL; + } + + .monChild { float: left; align-self: center; - + background-color: $euiColorLightestShade; + margin: $euiSizeS; + border: 1px solid $euiColorMediumShade; + border-radius: $euiSizeXS; + padding: $euiSizeXS/2 0; + // SASS-TODO: Rename this class following Eui conventions &.index { border-left: $euiSizeXS solid $euiColorSuccess; - &.red { + + &.monChild--danger { border-left: $euiSizeXS solid $euiColorDanger; } - &.yellow { + + &.monChild--warning { border-left: $euiSizeXS solid $euiColorWarning; } } - background-color: $euiColorDarkShade; - margin: 5px; - .title { - padding: 5px 7px; - display: inline-block; + + .monChild__title { + padding: $euiSizeXS $euiSizeS; text-align: center; - font-size: 12px; - font: 10px sans-serif; + font-size: $euiFontSizeXS; color: $euiColorGhost; - a { - color: $euiColorGhost; - text-decoration: none; - } - i { - margin-left: 5px; - } + display: flex; + flex-direction: row; + align-items: center; } - &.unassigned { + + &.monClusterUnassigned { .title { display: none; } @@ -73,30 +60,12 @@ monitoring-shard-allocation { td:first-child { width: 200px; } - + // SASS-TODO: Rename this class following Eui conventions .shard { align-self: center; - padding: 5px 7px; - font: 10px sans-serif; - border-left: 1px solid $euiColorEmptyShade; + padding: $euiSizeXS $euiSizeS; + font-size: $euiFontSizeXS; position: relative; display: inline-block; } - - .legend { - font-size: 12px; - background-color: $euiColorEmptyShade; - .title { - margin-left: 5px; - font-weight: bold; - } - color: $euiColorDarkestShade; - padding: 5px; - span.shard { - float: none; - display: inline-block; - margin: 0 5px 0 10px; - padding: 0 4px; - } - } } diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js index ec1b36837af923..012bc81135e341 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js @@ -4,39 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ - - import { get, sortBy } from 'lodash'; import React from 'react'; import { Shard } from './shard'; import { calculateClass } from '../lib/calculate_class'; import { generateQueryAndLink } from '../lib/generate_query_and_link'; -import { - EuiKeyboardAccessible, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiKeyboardAccessible } from '@elastic/eui'; function sortByName(item) { if (item.type === 'node') { - return [ !item.master, item.name]; + return [!item.master, item.name]; } - return [ item.name ]; + return [item.name]; } export class Assigned extends React.Component { - createShard = (shard) => { + createShard = shard => { const type = shard.primary ? 'primary' : 'replica'; const key = `${shard.index}.${shard.node}.${type}.${shard.state}.${shard.shard}`; - return ( - - ); + return ; }; - createChild = (data) => { + createChild = data => { const key = data.id; - const initialClasses = ['child']; + const initialClasses = ['monChild']; const shardStats = get(this.props.shardStats.indices, key); if (shardStats) { - initialClasses.push(shardStats.status); + switch (shardStats.status) { + case 'red': + initialClasses.push('monChild--danger'); + break; + case 'yellow': + initialClasses.push('monChild--warning'); + break; + } } const changeUrl = () => { @@ -52,28 +53,39 @@ export class Assigned extends React.Component { ); - const master = (data.node_type === 'master') ? : null; + const master = + data.node_type === 'master' ? : null; const shards = sortBy(data.children, 'shard').map(this.createShard); return ( -
-
{name}{master}
- {shards} -
+ + + + {name} + {master} + + + + {shards} + + + ); }; render() { const data = sortBy(this.props.data, sortByName).map(this.createChild); return ( - -
+ + {data} -
+ ); } diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js index e350e3b037712a..728165386cd181 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js @@ -4,30 +4,44 @@ * you may not use this file except in compliance with the Elastic License. */ - - import _ from 'lodash'; import React from 'react'; import { Shard } from './shard'; import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup } from '@elastic/eui'; export class Unassigned extends React.Component { - static displayName = i18n.translate('xpack.monitoring.elasticsearch.shardAllocation.unassignedDisplayName', { - defaultMessage: 'Unassigned', - }); + static displayName = i18n.translate( + 'xpack.monitoring.elasticsearch.shardAllocation.unassignedDisplayName', + { + defaultMessage: 'Unassigned', + } + ); - createShard = (shard) => { + createShard = shard => { const type = shard.primary ? 'primary' : 'replica'; const additionId = shard.state === 'UNASSIGNED' ? Math.random() : ''; - const key = shard.index + '.' + shard.node + '.' + type + '.' + shard.state + '.' + shard.shard + additionId; - return (); + const key = + shard.index + + '.' + + shard.node + + '.' + + type + + '.' + + shard.state + + '.' + + shard.shard + + additionId; + return ; }; render() { const shards = _.sortBy(this.props.shards, 'shard').map(this.createShard); return ( - -
{shards}
+ + + {shards} + ); } diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/shard_allocation.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/shard_allocation.js index 50ab2653ced37f..5e93e698a33a90 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/shard_allocation.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/shard_allocation.js @@ -66,7 +66,7 @@ export const ShardAllocation = ({

- + { types.map(type => ( diff --git a/x-pack/legacy/plugins/monitoring/public/components/status_icon/index.js b/x-pack/legacy/plugins/monitoring/public/components/status_icon/index.js index a054f837041761..a31823ef2e7732 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/status_icon/index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/status_icon/index.js @@ -5,25 +5,18 @@ */ import React from 'react'; +import { EuiIcon } from '@elastic/eui'; export function StatusIcon({ type, label }) { const typeToIconMap = { - [StatusIcon.TYPES.RED]: 'health-red.svg', - [StatusIcon.TYPES.YELLOW]: 'health-yellow.svg', - [StatusIcon.TYPES.GREEN]: 'health-green.svg', - [StatusIcon.TYPES.GRAY]: 'health-gray.svg', + [StatusIcon.TYPES.RED]: 'danger', + [StatusIcon.TYPES.YELLOW]: 'warning', + [StatusIcon.TYPES.GREEN]: 'success', + [StatusIcon.TYPES.GRAY]: 'subdued', }; const icon = typeToIconMap[type]; - return ( - - {label} - - ); + return ; } StatusIcon.TYPES = { diff --git a/x-pack/legacy/plugins/monitoring/public/components/summary_status/__snapshots__/summary_status.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/summary_status/__snapshots__/summary_status.test.js.snap index 0842406774f731..a3d321f9e39b66 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/summary_status/__snapshots__/summary_status.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/summary_status/__snapshots__/summary_status.test.js.snap @@ -28,15 +28,16 @@ exports[`Summary Status Component should allow label to be optional 1`] = ` class="euiTitle euiTitle--xsmall euiStat__title" > Status: - - Status: yellow - +  Yellow

Status: - - Status: green - +  Green

{ it('should handle incomplete shardStats data', () => { const clusterState = { nodes: { - fooNode: {} - } + fooNode: {}, + }, }; const shardStats = { nodes: { - fooNode: {} - } + fooNode: {}, + }, }; const resolver = 'fooNode'; @@ -62,7 +62,7 @@ describe('Elasticsearch Node Summary get_node_summary handleResponse', () => { totalSpace: undefined, usedHeap: undefined, nodeTypeLabel: 'Node', - nodeTypeClass: 'fa-server', + nodeTypeClass: 'storage', node_ids: [], status: 'Online', isOnline: true, @@ -72,17 +72,17 @@ describe('Elasticsearch Node Summary get_node_summary handleResponse', () => { it('should handle incomplete shardStats data, master node', () => { const clusterState = { nodes: { - 'fooNode-Uuid': {} + 'fooNode-Uuid': {}, }, - master_node: 'fooNode-Uuid' + master_node: 'fooNode-Uuid', }; const shardStats = { nodes: { 'fooNode-Uuid': { shardCount: 22, - indexCount: 11 - } - } + indexCount: 11, + }, + }, }; const resolver = 'fooNode-Uuid'; @@ -101,28 +101,28 @@ describe('Elasticsearch Node Summary get_node_summary handleResponse', () => { node_stats: { indices: { docs: { - count: 11000 + count: 11000, }, store: { - size_in_bytes: 35000 - } + size_in_bytes: 35000, + }, }, fs: { total: { available_in_bytes: 8700, - total_in_bytes: 10000 - } + total_in_bytes: 10000, + }, }, jvm: { mem: { - heap_used_percent: 33 - } - } - } - } - } - ] - } + heap_used_percent: 33, + }, + }, + }, + }, + }, + ], + }, }; const result = handleFn(response); @@ -140,10 +140,8 @@ describe('Elasticsearch Node Summary get_node_summary handleResponse', () => { totalSpace: 10000, usedHeap: 33, nodeTypeLabel: 'Master Node', - nodeTypeClass: 'fa-star', - node_ids: [ - 'fooNode-Uuid' - ], + nodeTypeClass: 'starFilled', + node_ids: ['fooNode-Uuid'], status: 'Online', isOnline: true, }); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/get_node_type_class_label.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/get_node_type_class_label.js index 4c21391a9ae624..2dc30a57db3d9e 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/get_node_type_class_label.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/get_node_type_class_label.js @@ -11,12 +11,12 @@ describe('Node Type and Label', () => { describe('when master node', () => { it('type is indicated by boolean flag', () => { const node = { - master: true + master: true, }; const { nodeType, nodeTypeLabel, nodeTypeClass } = getNodeTypeClassLabel(node); expect(nodeType).to.be('master'); expect(nodeTypeLabel).to.be('Master Node'); - expect(nodeTypeClass).to.be('fa-star'); + expect(nodeTypeClass).to.be('starFilled'); }); it('type is indicated by string', () => { const node = {}; @@ -24,7 +24,7 @@ describe('Node Type and Label', () => { const { nodeType, nodeTypeLabel, nodeTypeClass } = getNodeTypeClassLabel(node, type); expect(nodeType).to.be('master'); expect(nodeTypeLabel).to.be('Master Node'); - expect(nodeTypeClass).to.be('fa-star'); + expect(nodeTypeClass).to.be('starFilled'); }); }); it('when type is generic node', () => { @@ -33,6 +33,6 @@ describe('Node Type and Label', () => { const { nodeType, nodeTypeLabel, nodeTypeClass } = getNodeTypeClassLabel(node, type); expect(nodeType).to.be('node'); expect(nodeTypeLabel).to.be('Node'); - expect(nodeTypeClass).to.be('fa-server'); + expect(nodeTypeClass).to.be('storage'); }); }); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap index ba72d697388c6f..db74cc5e330a16 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap @@ -5,7 +5,7 @@ Array [ Object { "isOnline": false, "name": "hello01", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "resolver": "_x_V2YzPQU-a9KRRBxUxZQ", "shardCount": 6, @@ -15,7 +15,7 @@ Array [ Object { "isOnline": false, "name": "hello02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "resolver": "DAiX7fFjS3Wii7g2HYKrOg", "shardCount": 6, @@ -32,7 +32,7 @@ Array [ Object { "isOnline": true, "name": "hello01", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "nodeTypeLabel": "Master Node", "node_cgroup_quota": Object { "metric": Object { @@ -160,7 +160,7 @@ Array [ Object { "isOnline": true, "name": "hello02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "node_cgroup_quota": undefined, "node_cgroup_throttled": Object { @@ -274,7 +274,7 @@ Array [ Object { "isOnline": true, "name": "hello01", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "nodeTypeLabel": "Master Node", "node_cgroup_quota": null, "node_cgroup_throttled": null, @@ -290,7 +290,7 @@ Array [ Object { "isOnline": true, "name": "hello02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "node_cgroup_quota": null, "node_cgroup_throttled": null, @@ -311,7 +311,7 @@ Array [ Object { "isOnline": true, "name": "hello01", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "nodeTypeLabel": "Master Node", "node_cgroup_quota": Object { "metric": Object { @@ -439,7 +439,7 @@ Array [ Object { "isOnline": true, "name": "hello02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "node_cgroup_quota": undefined, "node_cgroup_throttled": Object { diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/map_nodes_info.test.js.snap b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/map_nodes_info.test.js.snap index 9f75dd1f1ee0fd..7eb22b00637459 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/map_nodes_info.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/map_nodes_info.test.js.snap @@ -5,7 +5,7 @@ Object { "ENVgDIKRSdCVJo-YqY4kUQ": Object { "isOnline": true, "name": "node01", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "nodeTypeLabel": "Master Node", "shardCount": 57, "transport_address": "127.0.0.1:9300", @@ -14,7 +14,7 @@ Object { "t9J9jvHpQ2yDw9c1LJ0tHA": Object { "isOnline": false, "name": "node02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "shardCount": 0, "transport_address": "127.0.0.1:9301", diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/lookups.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/lookups.js index f8d97acf792c36..23b4021ee7c0c5 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/lookups.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/lookups.js @@ -12,25 +12,31 @@ import { i18n } from '@kbn/i18n'; export const nodeTypeClass = { - invalid: 'fa-exclamation-triangle', - node: 'fa-server', - master: 'fa-star', - master_only: 'fa-star-o', - data: 'fa-database', - client: 'fa-binoculars' + invalid: 'alert', + node: 'storage', + master: 'starFilled', + master_only: 'starEmpty', + data: 'database', + client: 'glasses', }; export const nodeTypeLabel = { invalid: i18n.translate('xpack.monitoring.es.nodeType.invalidNodeLabel', { - defaultMessage: 'Invalid Node' }), + defaultMessage: 'Invalid Node', + }), node: i18n.translate('xpack.monitoring.es.nodeType.nodeLabel', { - defaultMessage: 'Node' }), + defaultMessage: 'Node', + }), master: i18n.translate('xpack.monitoring.es.nodeType.masterNodeLabel', { - defaultMessage: 'Master Node' }), + defaultMessage: 'Master Node', + }), master_only: i18n.translate('xpack.monitoring.es.nodeType.masterOnlyNodeLabel', { - defaultMessage: 'Master Only Node' }), + defaultMessage: 'Master Only Node', + }), data: i18n.translate('xpack.monitoring.es.nodeType.dataOnlyNodeLabel', { - defaultMessage: 'Data Only Node' }), + defaultMessage: 'Data Only Node', + }), client: i18n.translate('xpack.monitoring.es.nodeType.clientNodeLabel', { - defaultMessage: 'Client Node' }) + defaultMessage: 'Client Node', + }), }; diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/node_detail_advanced.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/node_detail_advanced.json index f84d0c73bed075..2eb7d54effdfb0 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/node_detail_advanced.json +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/node_detail_advanced.json @@ -10,7 +10,7 @@ "name": "whatever-01", "type": "master", "nodeTypeLabel": "Master Node", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "totalShards": 38, "indexCount": 20, "documents": 24830, @@ -22,1540 +22,1594 @@ "isOnline": true }, "metrics": { - "node_jvm_mem": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.jvm.mem.heap_max_in_bytes", - "metricAgg": "max", - "label": "Max Heap", - "title": "JVM Heap", - "description": "Total heap available to Elasticsearch running in the JVM.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 709623808], - [1507235530000, 709623808], - [1507235540000, 709623808], - [1507235550000, 709623808], - [1507235560000, 709623808], - [1507235570000, 709623808], - [1507235580000, 709623808], - [1507235590000, 709623808], - [1507235600000, 709623808], - [1507235610000, 709623808], - [1507235620000, 709623808], - [1507235630000, 709623808], - [1507235640000, 709623808], - [1507235650000, 709623808], - [1507235660000, 709623808], - [1507235670000, 709623808], - [1507235680000, 709623808], - [1507235690000, 709623808], - [1507235700000, 709623808] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.jvm.mem.heap_used_in_bytes", - "metricAgg": "max", - "label": "Used Heap", - "title": "JVM Heap", - "description": "Total heap used by Elasticsearch running in the JVM.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 317052776], - [1507235530000, 344014976], - [1507235540000, 368593248], - [1507235550000, 253850400], - [1507235560000, 348095032], - [1507235570000, 182919712], - [1507235580000, 212395016], - [1507235590000, 244004144], - [1507235600000, 270412240], - [1507235610000, 245052864], - [1507235620000, 370270616], - [1507235630000, 196944168], - [1507235640000, 223491760], - [1507235650000, 253878472], - [1507235660000, 280811736], - [1507235670000, 371931976], - [1507235680000, 329874616], - [1507235690000, 363869776], - [1507235700000, 211045968] - ] - }], - "node_gc": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.jvm.gc.collectors.old.collection_count", - "metricAgg": "max", - "label": "Old", - "title": "GC Rate", - "description": "Number of old Garbage Collections.", - "units": "", - "format": "0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.jvm.gc.collectors.young.collection_count", - "metricAgg": "max", - "label": "Young", - "title": "GC Rate", - "description": "Number of young Garbage Collections.", - "units": "", - "format": "0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0.1], - [1507235560000, 0], - [1507235570000, 0.1], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0.1], - [1507235620000, 0], - [1507235630000, 0.1], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0.1], - [1507235690000, 0], - [1507235700000, 0.1] - ] - }], - "node_gc_time": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.jvm.gc.collectors.old.collection_time_in_millis", - "metricAgg": "max", - "label": "Old", - "title": "GC Duration", - "description": "Time spent performing old Garbage Collections.", - "units": "ms", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.jvm.gc.collectors.young.collection_time_in_millis", - "metricAgg": "max", - "label": "Young", - "title": "GC Duration", - "description": "Time spent performing young Garbage Collections.", - "units": "ms", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 1.1], - [1507235560000, 0], - [1507235570000, 1.2], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 1], - [1507235620000, 0], - [1507235630000, 1.1], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 2.9], - [1507235690000, 0], - [1507235700000, 2.1] - ] - }], - "node_index_1": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.memory_in_bytes", - "metricAgg": "max", - "label": "Lucene Total", - "title": "Index Memory - Lucene 1", - "description": "Total heap memory used by Lucene for current index. This is the sum of other fields for primary and replica shards on this node.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 4797457], - [1507235530000, 4797457], - [1507235540000, 4797457], - [1507235550000, 4797457], - [1507235560000, 4823580], - [1507235570000, 4823580], - [1507235580000, 4823580], - [1507235590000, 4823580], - [1507235600000, 4823580], - [1507235610000, 4838368], - [1507235620000, 4741420], - [1507235630000, 4741420], - [1507235640000, 4741420], - [1507235650000, 4741420], - [1507235660000, 4741420], - [1507235670000, 4757998], - [1507235680000, 4787542], - [1507235690000, 4787542], - [1507235700000, 4787542] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.stored_fields_memory_in_bytes", - "metricAgg": "max", - "label": "Stored Fields", - "title": "Index Memory", - "description": "Heap memory used by Stored Fields (e.g., _source). This is a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 56792], - [1507235530000, 56792], - [1507235540000, 56792], - [1507235550000, 56792], - [1507235560000, 57728], - [1507235570000, 57728], - [1507235580000, 57728], - [1507235590000, 57728], - [1507235600000, 57728], - [1507235610000, 58352], - [1507235620000, 56192], - [1507235630000, 56192], - [1507235640000, 56192], - [1507235650000, 56192], - [1507235660000, 56192], - [1507235670000, 56816], - [1507235680000, 57440], - [1507235690000, 57440], - [1507235700000, 57440] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.doc_values_memory_in_bytes", - "metricAgg": "max", - "label": "Doc Values", - "title": "Index Memory", - "description": "Heap memory used by Doc Values. This is a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 516824], - [1507235530000, 516824], - [1507235540000, 516824], - [1507235550000, 516824], - [1507235560000, 517292], - [1507235570000, 517292], - [1507235580000, 517292], - [1507235590000, 517292], - [1507235600000, 517292], - [1507235610000, 517612], - [1507235620000, 514808], - [1507235630000, 514808], - [1507235640000, 514808], - [1507235650000, 514808], - [1507235660000, 514808], - [1507235670000, 515312], - [1507235680000, 516008], - [1507235690000, 516008], - [1507235700000, 516008] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.norms_memory_in_bytes", - "metricAgg": "max", - "label": "Norms", - "title": "Index Memory", - "description": "Heap memory used by Norms (normalization factors for query-time, text scoring). This is a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 447232], - [1507235530000, 447232], - [1507235540000, 447232], - [1507235550000, 447232], - [1507235560000, 449600], - [1507235570000, 449600], - [1507235580000, 449600], - [1507235590000, 449600], - [1507235600000, 449600], - [1507235610000, 450880], - [1507235620000, 442304], - [1507235630000, 442304], - [1507235640000, 442304], - [1507235650000, 442304], - [1507235660000, 442304], - [1507235670000, 443840], - [1507235680000, 446400], - [1507235690000, 446400], - [1507235700000, 446400] - ] - }], - "node_index_2": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.memory_in_bytes", - "metricAgg": "max", - "label": "Lucene Total", - "title": "Index Memory - Lucene 2", - "description": "Total heap memory used by Lucene for current index. This is the sum of other fields for primary and replica shards on this node.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 4797457], - [1507235530000, 4797457], - [1507235540000, 4797457], - [1507235550000, 4797457], - [1507235560000, 4823580], - [1507235570000, 4823580], - [1507235580000, 4823580], - [1507235590000, 4823580], - [1507235600000, 4823580], - [1507235610000, 4838368], - [1507235620000, 4741420], - [1507235630000, 4741420], - [1507235640000, 4741420], - [1507235650000, 4741420], - [1507235660000, 4741420], - [1507235670000, 4757998], - [1507235680000, 4787542], - [1507235690000, 4787542], - [1507235700000, 4787542] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.terms_memory_in_bytes", - "metricAgg": "max", - "label": "Terms", - "title": "Index Memory", - "description": "Heap memory used by Terms (e.g., text). This is a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 3764438], - [1507235530000, 3764438], - [1507235540000, 3764438], - [1507235550000, 3764438], - [1507235560000, 3786762], - [1507235570000, 3786762], - [1507235580000, 3786762], - [1507235590000, 3786762], - [1507235600000, 3786762], - [1507235610000, 3799306], - [1507235620000, 3715996], - [1507235630000, 3715996], - [1507235640000, 3715996], - [1507235650000, 3715996], - [1507235660000, 3715996], - [1507235670000, 3729890], - [1507235680000, 3755528], - [1507235690000, 3755528], - [1507235700000, 3755528] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.points_memory_in_bytes", - "metricAgg": "max", - "label": "Points", - "title": "Index Memory", - "description": "Heap memory used by Points (e.g., numbers, IPs, and geo data). This is a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 12171], - [1507235530000, 12171], - [1507235540000, 12171], - [1507235550000, 12171], - [1507235560000, 12198], - [1507235570000, 12198], - [1507235580000, 12198], - [1507235590000, 12198], - [1507235600000, 12198], - [1507235610000, 12218], - [1507235620000, 12120], - [1507235630000, 12120], - [1507235640000, 12120], - [1507235650000, 12120], - [1507235660000, 12120], - [1507235670000, 12140], - [1507235680000, 12166], - [1507235690000, 12166], - [1507235700000, 12166] - ] - }], - "node_index_3": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.memory_in_bytes", - "metricAgg": "max", - "label": "Lucene Total", - "title": "Index Memory - Lucene 3", - "description": "Total heap memory used by Lucene for current index. This is the sum of other fields for primary and replica shards on this node.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 4797457], - [1507235530000, 4797457], - [1507235540000, 4797457], - [1507235550000, 4797457], - [1507235560000, 4823580], - [1507235570000, 4823580], - [1507235580000, 4823580], - [1507235590000, 4823580], - [1507235600000, 4823580], - [1507235610000, 4838368], - [1507235620000, 4741420], - [1507235630000, 4741420], - [1507235640000, 4741420], - [1507235650000, 4741420], - [1507235660000, 4741420], - [1507235670000, 4757998], - [1507235680000, 4787542], - [1507235690000, 4787542], - [1507235700000, 4787542] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.fixed_bit_set_memory_in_bytes", - "metricAgg": "max", - "label": "Fixed Bitsets", - "title": "Index Memory", - "description": "Heap memory used by Fixed Bit Sets (e.g., deeply nested documents). This is a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 4024], - [1507235530000, 4024], - [1507235540000, 4024], - [1507235550000, 4024], - [1507235560000, 4120], - [1507235570000, 4120], - [1507235580000, 4120], - [1507235590000, 4120], - [1507235600000, 4120], - [1507235610000, 4168], - [1507235620000, 3832], - [1507235630000, 3832], - [1507235640000, 3832], - [1507235650000, 3832], - [1507235660000, 3832], - [1507235670000, 3880], - [1507235680000, 3976], - [1507235690000, 3976], - [1507235700000, 3976] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.term_vectors_memory_in_bytes", - "metricAgg": "max", - "label": "Term Vectors", - "title": "Index Memory", - "description": "Heap memory used by Term Vectors. This is a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 0], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.version_map_memory_in_bytes", - "metricAgg": "max", - "label": "Version Map", - "title": "Index Memory", - "description": "Heap memory used by Versioning (e.g., updates and deletes). This is NOT a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 5551], - [1507235530000, 5551], - [1507235540000, 5551], - [1507235550000, 6594], - [1507235560000, 6662], - [1507235570000, 6662], - [1507235580000, 6662], - [1507235590000, 6662], - [1507235600000, 6662], - [1507235610000, 7531], - [1507235620000, 7837], - [1507235630000, 7837], - [1507235640000, 7837], - [1507235650000, 7837], - [1507235660000, 7837], - [1507235670000, 9974], - [1507235680000, 9716], - [1507235690000, 9716], - [1507235700000, 9716] - ] - }], - "node_index_4": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.query_cache.memory_size_in_bytes", - "metricAgg": "max", - "label": "Query Cache", - "title": "Index Memory - Elasticsearch", - "description": "Heap memory used by Query Cache (e.g., cached filters). This is for the same shards, but not a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 0], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.request_cache.memory_size_in_bytes", - "metricAgg": "max", - "label": "Request Cache", - "title": "Index Memory", - "description": "Heap memory used by Request Cache (e.g., instant aggregations). This is for the same shards, but not a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 2921], - [1507235530000, 2921], - [1507235540000, 2921], - [1507235550000, 2921], - [1507235560000, 2921], - [1507235570000, 2921], - [1507235580000, 2921], - [1507235590000, 2921], - [1507235600000, 2921], - [1507235610000, 2921], - [1507235620000, 2921], - [1507235630000, 2921], - [1507235640000, 2921], - [1507235650000, 2921], - [1507235660000, 2921], - [1507235670000, 2921], - [1507235680000, 2921], - [1507235690000, 2921], - [1507235700000, 2921] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.fielddata.memory_size_in_bytes", - "metricAgg": "max", - "label": "Fielddata", - "title": "Index Memory", - "description": "Heap memory used by Fielddata (e.g., global ordinals or explicitly enabled fielddata on text fields). This is for the same shards, but not a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 0], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.index_writer_memory_in_bytes", - "metricAgg": "max", - "label": "Index Writer", - "title": "Index Memory", - "description": "Heap memory used by the Index Writer. This is NOT a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 153549], - [1507235530000, 153549], - [1507235540000, 153549], - [1507235550000, 849833], - [1507235560000, 156505], - [1507235570000, 156505], - [1507235580000, 156505], - [1507235590000, 156505], - [1507235600000, 156505], - [1507235610000, 3140275], - [1507235620000, 159637], - [1507235630000, 159637], - [1507235640000, 159637], - [1507235650000, 159637], - [1507235660000, 159637], - [1507235670000, 3737997], - [1507235680000, 164351], - [1507235690000, 164351], - [1507235700000, 164351] - ] - }], - "node_request_total": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.search.query_total", - "metricAgg": "max", - "label": "Search Total", - "title": "Request Rate", - "description": "Amount of search operations (per shard).", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0.3], - [1507235540000, 0.3], - [1507235550000, 0.3], - [1507235560000, 0.3], - [1507235570000, 0.3], - [1507235580000, 0.3], - [1507235590000, 0.4], - [1507235600000, 0.3], - [1507235610000, 0.5], - [1507235620000, 0.3], - [1507235630000, 0.3], - [1507235640000, 0.2], - [1507235650000, 0.3], - [1507235660000, 0.3], - [1507235670000, 0.5], - [1507235680000, 0.5], - [1507235690000, 0.1], - [1507235700000, 0.4] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.indexing.index_total", - "metricAgg": "max", - "label": "Indexing Total", - "title": "Request Rate", - "description": "Amount of indexing operations.", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0.9], - [1507235560000, 0.6], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0.9], - [1507235620000, 0.6], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 1.8], - [1507235680000, 0.8], - [1507235690000, 0], - [1507235700000, 0] - ] - }], - "node_index_time": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.indexing.index_time_in_millis", - "metricAgg": "max", - "label": "Index Time", - "title": "Indexing Time", - "description": "Amount of time spent on indexing operations.", - "units": "ms", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0.8], - [1507235560000, 0.7], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 1.2], - [1507235620000, 0.7], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 4.2], - [1507235680000, 2.3], - [1507235690000, 0], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.indexing.throttle_time_in_millis", - "metricAgg": "max", - "label": "Index Throttling Time", - "title": "Indexing Time", - "description": "Amount of time spent with index throttling, which indicates slow disks on a node.", - "units": "ms", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }], - "node_index_threads": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.thread_pool.write.queue", - "metricAgg": "max", - "label": "Write Queue", - "title": "Indexing Threads", - "description": "Number of index, bulk, and write operations in the queue. The bulk threadpool was renamed to write in 6.3, and the index threadpool is deprecated.", - "units": "", - "format": "0.[00]", - "hasCalculation": true, - "isDerivative": false - }, - "data": [ - [1507235520000, 0], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.thread_pool.write.rejected", - "metricAgg": "max", - "label": "Write Rejections", - "title": "Indexing Threads", - "description": "Number of index, bulk, and write operations that have been rejected, which occurs when the queue is full. The bulk threadpool was renamed to write in 6.3, and the index threadpool is deprecated.", - "units": "", - "format": "0.[00]", - "hasCalculation": true, - "isDerivative": false - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }], - "node_read_threads": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.thread_pool.search.queue", - "metricAgg": "max", - "label": "Search Queue", - "title": "Read Threads", - "description": "Number of search operations in the queue (e.g., shard level searches).", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0.2], - [1507235680000, null], - [1507235690000, 0], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.thread_pool.search.rejected", - "metricAgg": "max", - "label": "Search Rejections", - "title": "Read Threads", - "description": "Number of search operations that have been rejected, which occurs when the queue is full.", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.thread_pool.get.queue", - "metricAgg": "max", - "label": "GET Queue", - "title": "Read Threads", - "description": "Number of GET operations in the queue.", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.thread_pool.get.rejected", - "metricAgg": "max", - "label": "GET Rejections", - "title": "Read Threads", - "description": "Number of GET operations that have been rejected, which occurs when the queue is full.", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0], - [1507235560000, 0], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }], - "node_cpu_utilization": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.process.cpu.percent", - "metricAgg": "max", - "label": "CPU Utilization", - "description": "Percentage of CPU usage for the Elasticsearch process.", - "units": "%", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1507235520000, 1], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 1], - [1507235560000, 2], - [1507235570000, 0], - [1507235580000, 2], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 3], - [1507235620000, 2], - [1507235630000, 2], - [1507235640000, 0], - [1507235650000, 1], - [1507235660000, 0], - [1507235670000, 2], - [1507235680000, 2], - [1507235690000, 1], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.process.cpu.percent", - "metricAgg": "max", - "label": "Cgroup CPU Utilization", - "title": "CPU Utilization", - "description": "CPU Usage time compared to the CPU quota shown in percentage. If CPU quotas are not set, then no data will be shown.", - "units": "%", - "format": "0,0.[00]", - "hasCalculation": true, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, null], - [1507235540000, null], - [1507235550000, null], - [1507235560000, null], - [1507235570000, null], - [1507235580000, null], - [1507235590000, null], - [1507235600000, null], - [1507235610000, null], - [1507235620000, null], - [1507235630000, null], - [1507235640000, null], - [1507235650000, null], - [1507235660000, null], - [1507235670000, null], - [1507235680000, null], - [1507235690000, null], - [1507235700000, null] - ] - }], - "node_cgroup_cpu": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.os.cgroup.cpuacct.usage_nanos", - "metricAgg": "max", - "label": "Cgroup Usage", - "title": "Cgroup CPU Performance", - "description": "The usage, reported in nanoseconds, of the cgroup. Compare this with the throttling to discover issues.", - "units": "ns", - "format": "0,0.[0]a", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, null], - [1507235540000, null], - [1507235550000, null], - [1507235560000, null], - [1507235570000, null], - [1507235580000, null], - [1507235590000, null], - [1507235600000, null], - [1507235610000, null], - [1507235620000, null], - [1507235630000, null], - [1507235640000, null], - [1507235650000, null], - [1507235660000, null], - [1507235670000, null], - [1507235680000, null], - [1507235690000, null], - [1507235700000, null] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.os.cgroup.cpu.stat.time_throttled_nanos", - "metricAgg": "max", - "label": "Cgroup Throttling", - "title": "Cgroup CPU Performance", - "description": "The amount of throttled time, reported in nanoseconds, of the cgroup.", - "units": "ns", - "format": "0,0.[0]a", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, null], - [1507235540000, null], - [1507235550000, null], - [1507235560000, null], - [1507235570000, null], - [1507235580000, null], - [1507235590000, null], - [1507235600000, null], - [1507235610000, null], - [1507235620000, null], - [1507235630000, null], - [1507235640000, null], - [1507235650000, null], - [1507235660000, null], - [1507235670000, null], - [1507235680000, null], - [1507235690000, null], - [1507235700000, null] - ] - }], - "node_cgroup_stats": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.os.cgroup.cpu.stat.number_of_elapsed_periods", - "metricAgg": "max", - "label": "Cgroup Elapsed Periods", - "title": "Cgroup CFS Stats", - "description": "The number of sampling periods from the Completely Fair Scheduler (CFS). Compare against the number of times throttled.", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, null], - [1507235540000, null], - [1507235550000, null], - [1507235560000, null], - [1507235570000, null], - [1507235580000, null], - [1507235590000, null], - [1507235600000, null], - [1507235610000, null], - [1507235620000, null], - [1507235630000, null], - [1507235640000, null], - [1507235650000, null], - [1507235660000, null], - [1507235670000, null], - [1507235680000, null], - [1507235690000, null], - [1507235700000, null] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.os.cgroup.cpu.stat.number_of_times_throttled", - "metricAgg": "max", - "label": "Cgroup Throttled Count", - "title": "Cgroup CFS Stats", - "description": "The number of times that the CPU was throttled by the cgroup.", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1507235520000, null], - [1507235530000, null], - [1507235540000, null], - [1507235550000, null], - [1507235560000, null], - [1507235570000, null], - [1507235580000, null], - [1507235590000, null], - [1507235600000, null], - [1507235610000, null], - [1507235620000, null], - [1507235630000, null], - [1507235640000, null], - [1507235650000, null], - [1507235660000, null], - [1507235670000, null], - [1507235680000, null], - [1507235690000, null], - [1507235700000, null] - ] - }], - "node_latency": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.search.query_total", - "metricAgg": "sum", - "label": "Search", - "title": "Latency", - "description": "Average latency for searching, which is time it takes to execute searches divided by number of searches submitted. This considers primary and replica shards.", - "units": "ms", - "format": "0,0.[00]", - "hasCalculation": true, - "isDerivative": false - }, - "data": [ - [1507235520000, null], - [1507235530000, 0.33333333333333337], - [1507235540000, 0], - [1507235550000, 0.33333333333333337], - [1507235560000, 0], - [1507235570000, 0.33333333333333337], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0.33333333333333337], - [1507235610000, 0], - [1507235620000, 0], - [1507235630000, 0.33333333333333337], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 0.2], - [1507235680000, 0], - [1507235690000, 0], - [1507235700000, 0] - ] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.indexing.index_total", - "metricAgg": "sum", - "label": "Indexing", - "title": "Latency", - "description": "Average latency for indexing documents, which is time it takes to index documents divided by number that were indexed. This considers any shard located on this node, including replicas.", - "units": "ms", - "format": "0,0.[00]", - "hasCalculation": true, - "isDerivative": false - }, - "data": [ - [1507235520000, null], - [1507235530000, 0], - [1507235540000, 0], - [1507235550000, 0.888888888888889], - [1507235560000, 1.1666666666666667], - [1507235570000, 0], - [1507235580000, 0], - [1507235590000, 0], - [1507235600000, 0], - [1507235610000, 1.3333333333333333], - [1507235620000, 1.1666666666666667], - [1507235630000, 0], - [1507235640000, 0], - [1507235650000, 0], - [1507235660000, 0], - [1507235670000, 2.3333333333333335], - [1507235680000, 2.8749999999999996], - [1507235690000, 0], - [1507235700000, 0] - ] - }] + "node_jvm_mem": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.jvm.mem.heap_max_in_bytes", + "metricAgg": "max", + "label": "Max Heap", + "title": "JVM Heap", + "description": "Total heap available to Elasticsearch running in the JVM.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 709623808], + [1507235530000, 709623808], + [1507235540000, 709623808], + [1507235550000, 709623808], + [1507235560000, 709623808], + [1507235570000, 709623808], + [1507235580000, 709623808], + [1507235590000, 709623808], + [1507235600000, 709623808], + [1507235610000, 709623808], + [1507235620000, 709623808], + [1507235630000, 709623808], + [1507235640000, 709623808], + [1507235650000, 709623808], + [1507235660000, 709623808], + [1507235670000, 709623808], + [1507235680000, 709623808], + [1507235690000, 709623808], + [1507235700000, 709623808] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.jvm.mem.heap_used_in_bytes", + "metricAgg": "max", + "label": "Used Heap", + "title": "JVM Heap", + "description": "Total heap used by Elasticsearch running in the JVM.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 317052776], + [1507235530000, 344014976], + [1507235540000, 368593248], + [1507235550000, 253850400], + [1507235560000, 348095032], + [1507235570000, 182919712], + [1507235580000, 212395016], + [1507235590000, 244004144], + [1507235600000, 270412240], + [1507235610000, 245052864], + [1507235620000, 370270616], + [1507235630000, 196944168], + [1507235640000, 223491760], + [1507235650000, 253878472], + [1507235660000, 280811736], + [1507235670000, 371931976], + [1507235680000, 329874616], + [1507235690000, 363869776], + [1507235700000, 211045968] + ] + } + ], + "node_gc": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.jvm.gc.collectors.old.collection_count", + "metricAgg": "max", + "label": "Old", + "title": "GC Rate", + "description": "Number of old Garbage Collections.", + "units": "", + "format": "0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.jvm.gc.collectors.young.collection_count", + "metricAgg": "max", + "label": "Young", + "title": "GC Rate", + "description": "Number of young Garbage Collections.", + "units": "", + "format": "0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0.1], + [1507235560000, 0], + [1507235570000, 0.1], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0.1], + [1507235620000, 0], + [1507235630000, 0.1], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0.1], + [1507235690000, 0], + [1507235700000, 0.1] + ] + } + ], + "node_gc_time": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.jvm.gc.collectors.old.collection_time_in_millis", + "metricAgg": "max", + "label": "Old", + "title": "GC Duration", + "description": "Time spent performing old Garbage Collections.", + "units": "ms", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.jvm.gc.collectors.young.collection_time_in_millis", + "metricAgg": "max", + "label": "Young", + "title": "GC Duration", + "description": "Time spent performing young Garbage Collections.", + "units": "ms", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 1.1], + [1507235560000, 0], + [1507235570000, 1.2], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 1], + [1507235620000, 0], + [1507235630000, 1.1], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 2.9], + [1507235690000, 0], + [1507235700000, 2.1] + ] + } + ], + "node_index_1": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.memory_in_bytes", + "metricAgg": "max", + "label": "Lucene Total", + "title": "Index Memory - Lucene 1", + "description": "Total heap memory used by Lucene for current index. This is the sum of other fields for primary and replica shards on this node.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 4797457], + [1507235530000, 4797457], + [1507235540000, 4797457], + [1507235550000, 4797457], + [1507235560000, 4823580], + [1507235570000, 4823580], + [1507235580000, 4823580], + [1507235590000, 4823580], + [1507235600000, 4823580], + [1507235610000, 4838368], + [1507235620000, 4741420], + [1507235630000, 4741420], + [1507235640000, 4741420], + [1507235650000, 4741420], + [1507235660000, 4741420], + [1507235670000, 4757998], + [1507235680000, 4787542], + [1507235690000, 4787542], + [1507235700000, 4787542] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.stored_fields_memory_in_bytes", + "metricAgg": "max", + "label": "Stored Fields", + "title": "Index Memory", + "description": "Heap memory used by Stored Fields (e.g., _source). This is a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 56792], + [1507235530000, 56792], + [1507235540000, 56792], + [1507235550000, 56792], + [1507235560000, 57728], + [1507235570000, 57728], + [1507235580000, 57728], + [1507235590000, 57728], + [1507235600000, 57728], + [1507235610000, 58352], + [1507235620000, 56192], + [1507235630000, 56192], + [1507235640000, 56192], + [1507235650000, 56192], + [1507235660000, 56192], + [1507235670000, 56816], + [1507235680000, 57440], + [1507235690000, 57440], + [1507235700000, 57440] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.doc_values_memory_in_bytes", + "metricAgg": "max", + "label": "Doc Values", + "title": "Index Memory", + "description": "Heap memory used by Doc Values. This is a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 516824], + [1507235530000, 516824], + [1507235540000, 516824], + [1507235550000, 516824], + [1507235560000, 517292], + [1507235570000, 517292], + [1507235580000, 517292], + [1507235590000, 517292], + [1507235600000, 517292], + [1507235610000, 517612], + [1507235620000, 514808], + [1507235630000, 514808], + [1507235640000, 514808], + [1507235650000, 514808], + [1507235660000, 514808], + [1507235670000, 515312], + [1507235680000, 516008], + [1507235690000, 516008], + [1507235700000, 516008] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.norms_memory_in_bytes", + "metricAgg": "max", + "label": "Norms", + "title": "Index Memory", + "description": "Heap memory used by Norms (normalization factors for query-time, text scoring). This is a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 447232], + [1507235530000, 447232], + [1507235540000, 447232], + [1507235550000, 447232], + [1507235560000, 449600], + [1507235570000, 449600], + [1507235580000, 449600], + [1507235590000, 449600], + [1507235600000, 449600], + [1507235610000, 450880], + [1507235620000, 442304], + [1507235630000, 442304], + [1507235640000, 442304], + [1507235650000, 442304], + [1507235660000, 442304], + [1507235670000, 443840], + [1507235680000, 446400], + [1507235690000, 446400], + [1507235700000, 446400] + ] + } + ], + "node_index_2": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.memory_in_bytes", + "metricAgg": "max", + "label": "Lucene Total", + "title": "Index Memory - Lucene 2", + "description": "Total heap memory used by Lucene for current index. This is the sum of other fields for primary and replica shards on this node.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 4797457], + [1507235530000, 4797457], + [1507235540000, 4797457], + [1507235550000, 4797457], + [1507235560000, 4823580], + [1507235570000, 4823580], + [1507235580000, 4823580], + [1507235590000, 4823580], + [1507235600000, 4823580], + [1507235610000, 4838368], + [1507235620000, 4741420], + [1507235630000, 4741420], + [1507235640000, 4741420], + [1507235650000, 4741420], + [1507235660000, 4741420], + [1507235670000, 4757998], + [1507235680000, 4787542], + [1507235690000, 4787542], + [1507235700000, 4787542] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.terms_memory_in_bytes", + "metricAgg": "max", + "label": "Terms", + "title": "Index Memory", + "description": "Heap memory used by Terms (e.g., text). This is a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 3764438], + [1507235530000, 3764438], + [1507235540000, 3764438], + [1507235550000, 3764438], + [1507235560000, 3786762], + [1507235570000, 3786762], + [1507235580000, 3786762], + [1507235590000, 3786762], + [1507235600000, 3786762], + [1507235610000, 3799306], + [1507235620000, 3715996], + [1507235630000, 3715996], + [1507235640000, 3715996], + [1507235650000, 3715996], + [1507235660000, 3715996], + [1507235670000, 3729890], + [1507235680000, 3755528], + [1507235690000, 3755528], + [1507235700000, 3755528] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.points_memory_in_bytes", + "metricAgg": "max", + "label": "Points", + "title": "Index Memory", + "description": "Heap memory used by Points (e.g., numbers, IPs, and geo data). This is a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 12171], + [1507235530000, 12171], + [1507235540000, 12171], + [1507235550000, 12171], + [1507235560000, 12198], + [1507235570000, 12198], + [1507235580000, 12198], + [1507235590000, 12198], + [1507235600000, 12198], + [1507235610000, 12218], + [1507235620000, 12120], + [1507235630000, 12120], + [1507235640000, 12120], + [1507235650000, 12120], + [1507235660000, 12120], + [1507235670000, 12140], + [1507235680000, 12166], + [1507235690000, 12166], + [1507235700000, 12166] + ] + } + ], + "node_index_3": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.memory_in_bytes", + "metricAgg": "max", + "label": "Lucene Total", + "title": "Index Memory - Lucene 3", + "description": "Total heap memory used by Lucene for current index. This is the sum of other fields for primary and replica shards on this node.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 4797457], + [1507235530000, 4797457], + [1507235540000, 4797457], + [1507235550000, 4797457], + [1507235560000, 4823580], + [1507235570000, 4823580], + [1507235580000, 4823580], + [1507235590000, 4823580], + [1507235600000, 4823580], + [1507235610000, 4838368], + [1507235620000, 4741420], + [1507235630000, 4741420], + [1507235640000, 4741420], + [1507235650000, 4741420], + [1507235660000, 4741420], + [1507235670000, 4757998], + [1507235680000, 4787542], + [1507235690000, 4787542], + [1507235700000, 4787542] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.fixed_bit_set_memory_in_bytes", + "metricAgg": "max", + "label": "Fixed Bitsets", + "title": "Index Memory", + "description": "Heap memory used by Fixed Bit Sets (e.g., deeply nested documents). This is a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 4024], + [1507235530000, 4024], + [1507235540000, 4024], + [1507235550000, 4024], + [1507235560000, 4120], + [1507235570000, 4120], + [1507235580000, 4120], + [1507235590000, 4120], + [1507235600000, 4120], + [1507235610000, 4168], + [1507235620000, 3832], + [1507235630000, 3832], + [1507235640000, 3832], + [1507235650000, 3832], + [1507235660000, 3832], + [1507235670000, 3880], + [1507235680000, 3976], + [1507235690000, 3976], + [1507235700000, 3976] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.term_vectors_memory_in_bytes", + "metricAgg": "max", + "label": "Term Vectors", + "title": "Index Memory", + "description": "Heap memory used by Term Vectors. This is a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 0], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.version_map_memory_in_bytes", + "metricAgg": "max", + "label": "Version Map", + "title": "Index Memory", + "description": "Heap memory used by Versioning (e.g., updates and deletes). This is NOT a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 5551], + [1507235530000, 5551], + [1507235540000, 5551], + [1507235550000, 6594], + [1507235560000, 6662], + [1507235570000, 6662], + [1507235580000, 6662], + [1507235590000, 6662], + [1507235600000, 6662], + [1507235610000, 7531], + [1507235620000, 7837], + [1507235630000, 7837], + [1507235640000, 7837], + [1507235650000, 7837], + [1507235660000, 7837], + [1507235670000, 9974], + [1507235680000, 9716], + [1507235690000, 9716], + [1507235700000, 9716] + ] + } + ], + "node_index_4": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.query_cache.memory_size_in_bytes", + "metricAgg": "max", + "label": "Query Cache", + "title": "Index Memory - Elasticsearch", + "description": "Heap memory used by Query Cache (e.g., cached filters). This is for the same shards, but not a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 0], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.request_cache.memory_size_in_bytes", + "metricAgg": "max", + "label": "Request Cache", + "title": "Index Memory", + "description": "Heap memory used by Request Cache (e.g., instant aggregations). This is for the same shards, but not a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 2921], + [1507235530000, 2921], + [1507235540000, 2921], + [1507235550000, 2921], + [1507235560000, 2921], + [1507235570000, 2921], + [1507235580000, 2921], + [1507235590000, 2921], + [1507235600000, 2921], + [1507235610000, 2921], + [1507235620000, 2921], + [1507235630000, 2921], + [1507235640000, 2921], + [1507235650000, 2921], + [1507235660000, 2921], + [1507235670000, 2921], + [1507235680000, 2921], + [1507235690000, 2921], + [1507235700000, 2921] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.fielddata.memory_size_in_bytes", + "metricAgg": "max", + "label": "Fielddata", + "title": "Index Memory", + "description": "Heap memory used by Fielddata (e.g., global ordinals or explicitly enabled fielddata on text fields). This is for the same shards, but not a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 0], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.index_writer_memory_in_bytes", + "metricAgg": "max", + "label": "Index Writer", + "title": "Index Memory", + "description": "Heap memory used by the Index Writer. This is NOT a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 153549], + [1507235530000, 153549], + [1507235540000, 153549], + [1507235550000, 849833], + [1507235560000, 156505], + [1507235570000, 156505], + [1507235580000, 156505], + [1507235590000, 156505], + [1507235600000, 156505], + [1507235610000, 3140275], + [1507235620000, 159637], + [1507235630000, 159637], + [1507235640000, 159637], + [1507235650000, 159637], + [1507235660000, 159637], + [1507235670000, 3737997], + [1507235680000, 164351], + [1507235690000, 164351], + [1507235700000, 164351] + ] + } + ], + "node_request_total": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.search.query_total", + "metricAgg": "max", + "label": "Search Total", + "title": "Request Rate", + "description": "Amount of search operations (per shard).", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0.3], + [1507235540000, 0.3], + [1507235550000, 0.3], + [1507235560000, 0.3], + [1507235570000, 0.3], + [1507235580000, 0.3], + [1507235590000, 0.4], + [1507235600000, 0.3], + [1507235610000, 0.5], + [1507235620000, 0.3], + [1507235630000, 0.3], + [1507235640000, 0.2], + [1507235650000, 0.3], + [1507235660000, 0.3], + [1507235670000, 0.5], + [1507235680000, 0.5], + [1507235690000, 0.1], + [1507235700000, 0.4] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.indexing.index_total", + "metricAgg": "max", + "label": "Indexing Total", + "title": "Request Rate", + "description": "Amount of indexing operations.", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0.9], + [1507235560000, 0.6], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0.9], + [1507235620000, 0.6], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 1.8], + [1507235680000, 0.8], + [1507235690000, 0], + [1507235700000, 0] + ] + } + ], + "node_index_time": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.indexing.index_time_in_millis", + "metricAgg": "max", + "label": "Index Time", + "title": "Indexing Time", + "description": "Amount of time spent on indexing operations.", + "units": "ms", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0.8], + [1507235560000, 0.7], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 1.2], + [1507235620000, 0.7], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 4.2], + [1507235680000, 2.3], + [1507235690000, 0], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.indexing.throttle_time_in_millis", + "metricAgg": "max", + "label": "Index Throttling Time", + "title": "Indexing Time", + "description": "Amount of time spent with index throttling, which indicates slow disks on a node.", + "units": "ms", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + } + ], + "node_index_threads": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.thread_pool.write.queue", + "metricAgg": "max", + "label": "Write Queue", + "title": "Indexing Threads", + "description": "Number of index, bulk, and write operations in the queue. The bulk threadpool was renamed to write in 6.3, and the index threadpool is deprecated.", + "units": "", + "format": "0.[00]", + "hasCalculation": true, + "isDerivative": false + }, + "data": [ + [1507235520000, 0], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.thread_pool.write.rejected", + "metricAgg": "max", + "label": "Write Rejections", + "title": "Indexing Threads", + "description": "Number of index, bulk, and write operations that have been rejected, which occurs when the queue is full. The bulk threadpool was renamed to write in 6.3, and the index threadpool is deprecated.", + "units": "", + "format": "0.[00]", + "hasCalculation": true, + "isDerivative": false + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + } + ], + "node_read_threads": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.thread_pool.search.queue", + "metricAgg": "max", + "label": "Search Queue", + "title": "Read Threads", + "description": "Number of search operations in the queue (e.g., shard level searches).", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0.2], + [1507235680000, null], + [1507235690000, 0], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.thread_pool.search.rejected", + "metricAgg": "max", + "label": "Search Rejections", + "title": "Read Threads", + "description": "Number of search operations that have been rejected, which occurs when the queue is full.", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.thread_pool.get.queue", + "metricAgg": "max", + "label": "GET Queue", + "title": "Read Threads", + "description": "Number of GET operations in the queue.", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.thread_pool.get.rejected", + "metricAgg": "max", + "label": "GET Rejections", + "title": "Read Threads", + "description": "Number of GET operations that have been rejected, which occurs when the queue is full.", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0], + [1507235560000, 0], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + } + ], + "node_cpu_utilization": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.process.cpu.percent", + "metricAgg": "max", + "label": "CPU Utilization", + "description": "Percentage of CPU usage for the Elasticsearch process.", + "units": "%", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1507235520000, 1], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 1], + [1507235560000, 2], + [1507235570000, 0], + [1507235580000, 2], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 3], + [1507235620000, 2], + [1507235630000, 2], + [1507235640000, 0], + [1507235650000, 1], + [1507235660000, 0], + [1507235670000, 2], + [1507235680000, 2], + [1507235690000, 1], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.process.cpu.percent", + "metricAgg": "max", + "label": "Cgroup CPU Utilization", + "title": "CPU Utilization", + "description": "CPU Usage time compared to the CPU quota shown in percentage. If CPU quotas are not set, then no data will be shown.", + "units": "%", + "format": "0,0.[00]", + "hasCalculation": true, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, null], + [1507235540000, null], + [1507235550000, null], + [1507235560000, null], + [1507235570000, null], + [1507235580000, null], + [1507235590000, null], + [1507235600000, null], + [1507235610000, null], + [1507235620000, null], + [1507235630000, null], + [1507235640000, null], + [1507235650000, null], + [1507235660000, null], + [1507235670000, null], + [1507235680000, null], + [1507235690000, null], + [1507235700000, null] + ] + } + ], + "node_cgroup_cpu": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.os.cgroup.cpuacct.usage_nanos", + "metricAgg": "max", + "label": "Cgroup Usage", + "title": "Cgroup CPU Performance", + "description": "The usage, reported in nanoseconds, of the cgroup. Compare this with the throttling to discover issues.", + "units": "ns", + "format": "0,0.[0]a", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, null], + [1507235540000, null], + [1507235550000, null], + [1507235560000, null], + [1507235570000, null], + [1507235580000, null], + [1507235590000, null], + [1507235600000, null], + [1507235610000, null], + [1507235620000, null], + [1507235630000, null], + [1507235640000, null], + [1507235650000, null], + [1507235660000, null], + [1507235670000, null], + [1507235680000, null], + [1507235690000, null], + [1507235700000, null] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.os.cgroup.cpu.stat.time_throttled_nanos", + "metricAgg": "max", + "label": "Cgroup Throttling", + "title": "Cgroup CPU Performance", + "description": "The amount of throttled time, reported in nanoseconds, of the cgroup.", + "units": "ns", + "format": "0,0.[0]a", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, null], + [1507235540000, null], + [1507235550000, null], + [1507235560000, null], + [1507235570000, null], + [1507235580000, null], + [1507235590000, null], + [1507235600000, null], + [1507235610000, null], + [1507235620000, null], + [1507235630000, null], + [1507235640000, null], + [1507235650000, null], + [1507235660000, null], + [1507235670000, null], + [1507235680000, null], + [1507235690000, null], + [1507235700000, null] + ] + } + ], + "node_cgroup_stats": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.os.cgroup.cpu.stat.number_of_elapsed_periods", + "metricAgg": "max", + "label": "Cgroup Elapsed Periods", + "title": "Cgroup CFS Stats", + "description": "The number of sampling periods from the Completely Fair Scheduler (CFS). Compare against the number of times throttled.", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, null], + [1507235540000, null], + [1507235550000, null], + [1507235560000, null], + [1507235570000, null], + [1507235580000, null], + [1507235590000, null], + [1507235600000, null], + [1507235610000, null], + [1507235620000, null], + [1507235630000, null], + [1507235640000, null], + [1507235650000, null], + [1507235660000, null], + [1507235670000, null], + [1507235680000, null], + [1507235690000, null], + [1507235700000, null] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.os.cgroup.cpu.stat.number_of_times_throttled", + "metricAgg": "max", + "label": "Cgroup Throttled Count", + "title": "Cgroup CFS Stats", + "description": "The number of times that the CPU was throttled by the cgroup.", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1507235520000, null], + [1507235530000, null], + [1507235540000, null], + [1507235550000, null], + [1507235560000, null], + [1507235570000, null], + [1507235580000, null], + [1507235590000, null], + [1507235600000, null], + [1507235610000, null], + [1507235620000, null], + [1507235630000, null], + [1507235640000, null], + [1507235650000, null], + [1507235660000, null], + [1507235670000, null], + [1507235680000, null], + [1507235690000, null], + [1507235700000, null] + ] + } + ], + "node_latency": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.search.query_total", + "metricAgg": "sum", + "label": "Search", + "title": "Latency", + "description": "Average latency for searching, which is time it takes to execute searches divided by number of searches submitted. This considers primary and replica shards.", + "units": "ms", + "format": "0,0.[00]", + "hasCalculation": true, + "isDerivative": false + }, + "data": [ + [1507235520000, null], + [1507235530000, 0.33333333333333337], + [1507235540000, 0], + [1507235550000, 0.33333333333333337], + [1507235560000, 0], + [1507235570000, 0.33333333333333337], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0.33333333333333337], + [1507235610000, 0], + [1507235620000, 0], + [1507235630000, 0.33333333333333337], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 0.2], + [1507235680000, 0], + [1507235690000, 0], + [1507235700000, 0] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.indexing.index_total", + "metricAgg": "sum", + "label": "Indexing", + "title": "Latency", + "description": "Average latency for indexing documents, which is time it takes to index documents divided by number that were indexed. This considers any shard located on this node, including replicas.", + "units": "ms", + "format": "0,0.[00]", + "hasCalculation": true, + "isDerivative": false + }, + "data": [ + [1507235520000, null], + [1507235530000, 0], + [1507235540000, 0], + [1507235550000, 0.888888888888889], + [1507235560000, 1.1666666666666667], + [1507235570000, 0], + [1507235580000, 0], + [1507235590000, 0], + [1507235600000, 0], + [1507235610000, 1.3333333333333333], + [1507235620000, 1.1666666666666667], + [1507235630000, 0], + [1507235640000, 0], + [1507235650000, 0], + [1507235660000, 0], + [1507235670000, 2.3333333333333335], + [1507235680000, 2.8749999999999996], + [1507235690000, 0], + [1507235700000, 0] + ] + } + ] } } diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json index 83391e1a746165..2e71a6a1551e47 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json @@ -6,9 +6,7 @@ "dataSize": 1604573, "nodesCount": 2, "upTime": 1126458, - "version": [ - "7.0.0-alpha1" - ], + "version": ["7.0.0-alpha1"], "memUsed": 259061752, "memMax": 835321856, "unassignedShards": 0, @@ -21,7 +19,7 @@ "type": "master", "isOnline": true, "nodeTypeLabel": "Master Node", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "shardCount": 6, "node_cgroup_quota": { "metric": { @@ -149,7 +147,7 @@ "type": "node", "isOnline": true, "nodeTypeLabel": "Node", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "shardCount": 6, "node_cgroup_throttled": { "metric": { diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json index d7a30c466c2bf7..0a18664faf4458 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json @@ -10,15 +10,13 @@ "totalShards": 108, "unassignedShards": 0, "upTime": 1787957, - "version": [ - "7.0.0-alpha1" - ] + "version": ["7.0.0-alpha1"] }, "nodes": [ { "isOnline": true, "name": "node01", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "nodeTypeLabel": "Master Node", "node_cpu_utilization": { "metric": { @@ -106,7 +104,7 @@ { "isOnline": true, "name": "node02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "node_cpu_utilization": { "metric": { diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json index 5046ddb2762721..d9c04838fab10a 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json @@ -10,15 +10,13 @@ "totalShards": 46, "unassignedShards": 23, "upTime": 1403187, - "version": [ - "7.0.0-alpha1" - ] + "version": ["7.0.0-alpha1"] }, "nodes": [ { "isOnline": true, "name": "whatever-01", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "nodeTypeLabel": "Master Node", "node_cpu_utilization": { "metric": { @@ -106,7 +104,7 @@ { "isOnline": false, "name": "whatever-02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "resolver": "1jxg5T33TWub-jJL4qP0Wg", "shardCount": 0, From 7d34a113ebb5bf50006bcfe6ab2b5b334560a983 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Wed, 27 Nov 2019 14:18:32 -0700 Subject: [PATCH 07/52] [SIEM][Detection Engine] Change security model to use SIEM permissions ## Summary * Changes incorrect `access:signals-all` to be the correct `access:siem` * Adds a boom transformer to push back better error messages to the client in some cases ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../routes/create_rules_route.ts | 76 ++++++++++--------- .../routes/delete_rules_route.ts | 28 ++++--- .../routes/find_rules_route.ts | 26 ++++--- .../routes/read_rules_route.ts | 26 ++++--- .../routes/update_rules_route.ts | 74 +++++++++--------- .../lib/detection_engine/routes/utils.test.ts | 39 ++++++++++ .../lib/detection_engine/routes/utils.ts | 14 ++++ 7 files changed, 178 insertions(+), 105 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts index 4ff3a9b96b93e6..2b69e57f2c2eea 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts @@ -14,13 +14,13 @@ import { RulesRequest } from '../alerts/types'; import { createRulesSchema } from './schemas'; import { ServerFacade } from '../../../types'; import { readRules } from '../alerts/read_rules'; -import { transformOrError } from './utils'; +import { transformOrError, transformError } from './utils'; export const createCreateRulesRoute: Hapi.ServerRoute = { method: 'POST', path: DETECTION_ENGINE_RULES_URL, options: { - tags: ['access:signals-all'], + tags: ['access:siem'], validate: { options: { abortEarly: false, @@ -62,42 +62,46 @@ export const createCreateRulesRoute: Hapi.ServerRoute = { return headers.response().code(404); } - if (ruleId != null) { - const rule = await readRules({ alertsClient, ruleId }); - if (rule != null) { - return new Boom(`rule_id ${ruleId} already exists`, { statusCode: 409 }); + try { + if (ruleId != null) { + const rule = await readRules({ alertsClient, ruleId }); + if (rule != null) { + return new Boom(`rule_id ${ruleId} already exists`, { statusCode: 409 }); + } } - } - const createdRule = await createRules({ - alertsClient, - actionsClient, - description, - enabled, - falsePositives, - filter, - from, - immutable, - query, - language, - outputIndex, - savedId, - meta, - filters, - ruleId: ruleId != null ? ruleId : uuid.v4(), - index, - interval, - maxSignals, - riskScore, - name, - severity, - tags, - to, - type, - threats, - references, - }); - return transformOrError(createdRule); + const createdRule = await createRules({ + alertsClient, + actionsClient, + description, + enabled, + falsePositives, + filter, + from, + immutable, + query, + language, + outputIndex, + savedId, + meta, + filters, + ruleId: ruleId != null ? ruleId : uuid.v4(), + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threats, + references, + }); + return transformOrError(createdRule); + } catch (err) { + return transformError(err); + } }, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.ts index 12dff0dd60c147..fe8b139f11c019 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.ts @@ -12,13 +12,13 @@ import { deleteRules } from '../alerts/delete_rules'; import { ServerFacade } from '../../../types'; import { queryRulesSchema } from './schemas'; import { QueryRequest } from '../alerts/types'; -import { getIdError, transformOrError } from './utils'; +import { getIdError, transformOrError, transformError } from './utils'; export const createDeleteRulesRoute: Hapi.ServerRoute = { method: 'DELETE', path: DETECTION_ENGINE_RULES_URL, options: { - tags: ['access:signals-all'], + tags: ['access:siem'], validate: { options: { abortEarly: false, @@ -35,17 +35,21 @@ export const createDeleteRulesRoute: Hapi.ServerRoute = { return headers.response().code(404); } - const rule = await deleteRules({ - actionsClient, - alertsClient, - id, - ruleId, - }); + try { + const rule = await deleteRules({ + actionsClient, + alertsClient, + id, + ruleId, + }); - if (rule != null) { - return transformOrError(rule); - } else { - return getIdError({ id, ruleId }); + if (rule != null) { + return transformOrError(rule); + } else { + return getIdError({ id, ruleId }); + } + } catch (err) { + return transformError(err); } }, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.ts index 893fb3f689d164..137dd9352699e6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.ts @@ -11,13 +11,13 @@ import { findRules } from '../alerts/find_rules'; import { FindRulesRequest } from '../alerts/types'; import { findRulesSchema } from './schemas'; import { ServerFacade } from '../../../types'; -import { transformFindAlertsOrError } from './utils'; +import { transformFindAlertsOrError, transformError } from './utils'; export const createFindRulesRoute: Hapi.ServerRoute = { method: 'GET', path: `${DETECTION_ENGINE_RULES_URL}/_find`, options: { - tags: ['access:signals-all'], + tags: ['access:siem'], validate: { options: { abortEarly: false, @@ -34,15 +34,19 @@ export const createFindRulesRoute: Hapi.ServerRoute = { return headers.response().code(404); } - const rules = await findRules({ - alertsClient, - perPage: query.per_page, - page: query.page, - sortField: query.sort_field, - sortOrder: query.sort_order, - filter: query.filter, - }); - return transformFindAlertsOrError(rules); + try { + const rules = await findRules({ + alertsClient, + perPage: query.per_page, + page: query.page, + sortField: query.sort_field, + sortOrder: query.sort_order, + filter: query.filter, + }); + return transformFindAlertsOrError(rules); + } catch (err) { + return transformError(err); + } }, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.ts index 4642c34fbe3393..a7bda40fdc5234 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.ts @@ -7,7 +7,7 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; -import { getIdError, transformOrError } from './utils'; +import { getIdError, transformOrError, transformError } from './utils'; import { readRules } from '../alerts/read_rules'; import { ServerFacade } from '../../../types'; @@ -18,7 +18,7 @@ export const createReadRulesRoute: Hapi.ServerRoute = { method: 'GET', path: DETECTION_ENGINE_RULES_URL, options: { - tags: ['access:signals-all'], + tags: ['access:siem'], validate: { options: { abortEarly: false, @@ -34,15 +34,19 @@ export const createReadRulesRoute: Hapi.ServerRoute = { if (!alertsClient || !actionsClient) { return headers.response().code(404); } - const rule = await readRules({ - alertsClient, - id, - ruleId, - }); - if (rule != null) { - return transformOrError(rule); - } else { - return getIdError({ id, ruleId }); + try { + const rule = await readRules({ + alertsClient, + id, + ruleId, + }); + if (rule != null) { + return transformOrError(rule); + } else { + return getIdError({ id, ruleId }); + } + } catch (err) { + return transformError(err); } }, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts index c5fb1675fb3430..156756698435fe 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts @@ -11,13 +11,13 @@ import { updateRules } from '../alerts/update_rules'; import { UpdateRulesRequest } from '../alerts/types'; import { updateRulesSchema } from './schemas'; import { ServerFacade } from '../../../types'; -import { getIdError, transformOrError } from './utils'; +import { getIdError, transformOrError, transformError } from './utils'; export const createUpdateRulesRoute: Hapi.ServerRoute = { method: 'PUT', path: DETECTION_ENGINE_RULES_URL, options: { - tags: ['access:signals-all'], + tags: ['access:siem'], validate: { options: { abortEarly: false, @@ -61,39 +61,43 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { return headers.response().code(404); } - const rule = await updateRules({ - alertsClient, - actionsClient, - description, - enabled, - falsePositives, - filter, - from, - immutable, - query, - language, - outputIndex, - savedId, - meta, - filters, - id, - ruleId, - index, - interval, - maxSignals, - riskScore, - name, - severity, - tags, - to, - type, - threats, - references, - }); - if (rule != null) { - return transformOrError(rule); - } else { - return getIdError({ id, ruleId }); + try { + const rule = await updateRules({ + alertsClient, + actionsClient, + description, + enabled, + falsePositives, + filter, + from, + immutable, + query, + language, + outputIndex, + savedId, + meta, + filters, + id, + ruleId, + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threats, + references, + }); + if (rule != null) { + return transformOrError(rule); + } else { + return getIdError({ id, ruleId }); + } + } catch (err) { + return transformError(err); } }, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts index 4ef2d87d1d736b..1461c75295ee39 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts @@ -5,11 +5,13 @@ */ import Boom from 'boom'; + import { transformAlertToRule, getIdError, transformFindAlertsOrError, transformOrError, + transformError, } from './utils'; import { getResult } from './__mocks__/request_responses'; @@ -474,4 +476,41 @@ describe('utils', () => { expect((output as Boom).message).toEqual('Internal error transforming'); }); }); + + describe('transformError', () => { + test('returns boom if it is a boom object', () => { + const boom = new Boom(''); + const transformed = transformError(boom); + expect(transformed).toBe(boom); + }); + + test('returns a boom if it is some non boom object that has a statusCode', () => { + const error: Error & { statusCode?: number } = { + statusCode: 403, + name: 'some name', + message: 'some message', + }; + const transformed = transformError(error); + expect(Boom.isBoom(transformed)).toBe(true); + }); + + test('returns a boom with the message set', () => { + const error: Error & { statusCode?: number } = { + statusCode: 403, + name: 'some name', + message: 'some message', + }; + const transformed = transformError(error); + expect(transformed.message).toBe('some message'); + }); + + test('does not return a boom if it is some non boom object but it does not have a status Code.', () => { + const error: Error = { + name: 'some name', + message: 'some message', + }; + const transformed = transformError(error); + expect(Boom.isBoom(transformed)).toBe(false); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts index 947fb27a89c3a8..40a33e9d97a182 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -74,3 +74,17 @@ export const transformOrError = (alert: unknown): Partial | return new Boom('Internal error transforming', { statusCode: 500 }); } }; + +export const transformError = (err: Error & { statusCode?: number }) => { + if (Boom.isBoom(err)) { + return err; + } else { + if (err.statusCode != null) { + return new Boom(err.message, { statusCode: err.statusCode }); + } else { + // natively return the err and allow the regular framework + // to deal with the error when it is a non Boom + return err; + } + } +}; From 3ed18b4e06b0c8a155880bdd14338b1e46791b4e Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Wed, 27 Nov 2019 20:07:27 -0600 Subject: [PATCH 08/52] De-angularize visLegend (#50613) * deangularize visLegend * update vislib controller to mount react legend directly * convert legend components to eui * Position popover based on legend position * Styles cleanup including removing of unused/unnecessary styles --- .../kbn_vislib_vis_types/public/controller.js | 86 +++--- .../vis_types/__tests__/vislib_vis_legend.js | 159 ---------- .../vis/vis_types/_vislib_vis_legend.scss | 96 ++---- .../vis/vis_types/vislib_vis_legend.html | 99 ------- .../public/vis/vis_types/vislib_vis_legend.js | 186 ------------ .../vislib_vis_legend.test.tsx.snap | 5 + .../vis/vis_types/vislib_vis_legend/index.ts | 21 ++ .../vis/vis_types/vislib_vis_legend/models.ts | 84 ++++++ .../vislib_vis_legend.test.tsx | 279 ++++++++++++++++++ .../vislib_vis_legend/vislib_vis_legend.tsx | 264 +++++++++++++++++ .../vislib_vis_legend_item.tsx | 203 +++++++++++++ .../functional/page_objects/visualize_page.js | 15 +- .../common/layouts/preserve_layout.css | 5 - .../export_types/common/layouts/print.css | 5 - .../translations/translations/ja-JP.json | 1 + .../translations/translations/zh-CN.json | 1 + 16 files changed, 942 insertions(+), 567 deletions(-) delete mode 100644 src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js delete mode 100644 src/legacy/ui/public/vis/vis_types/vislib_vis_legend.html delete mode 100644 src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js create mode 100644 src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap create mode 100644 src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts create mode 100644 src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts create mode 100644 src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx create mode 100644 src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx create mode 100644 src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js index 319f7d9b9fa9f9..014606fb375ab3 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js @@ -19,9 +19,12 @@ import $ from 'jquery'; -import { CUSTOM_LEGEND_VIS_TYPES } from '../../../ui/public/vis/vis_types/vislib_vis_legend'; +import React from 'react'; + +import { CUSTOM_LEGEND_VIS_TYPES, VisLegend } from '../../../ui/public/vis/vis_types/vislib_vis_legend'; import { VislibVisProvider } from '../../../ui/public/vislib/vis'; import chrome from '../../../ui/public/chrome'; +import { mountReactNode } from '../../../../core/public/utils'; const legendClassName = { top: 'visLib--legend-top', @@ -30,24 +33,30 @@ const legendClassName = { right: 'visLib--legend-right', }; - export class vislibVisController { constructor(el, vis) { this.el = el; this.vis = vis; - this.$scope = null; + this.unmount = null; + this.legendRef = React.createRef(); + // vis mount point this.container = document.createElement('div'); this.container.className = 'visLib'; this.el.appendChild(this.container); + // chart mount point this.chartEl = document.createElement('div'); this.chartEl.className = 'visLib__chart'; this.container.appendChild(this.chartEl); + // legend mount point + this.legendEl = document.createElement('div'); + this.legendEl.className = 'visLib__legend'; + this.container.appendChild(this.legendEl); } render(esResponse, visParams) { - if (this.vis.vislibVis) { + if (this.vislibVis) { this.destroy(); } @@ -56,62 +65,69 @@ export class vislibVisController { const $injector = await chrome.dangerouslyGetActiveInjector(); const Private = $injector.get('Private'); this.Vislib = Private(VislibVisProvider); - this.$compile = $injector.get('$compile'); - this.$rootScope = $injector.get('$rootScope'); } if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { return resolve(); } - this.vis.vislibVis = new this.Vislib(this.chartEl, visParams); - this.vis.vislibVis.on('brush', this.vis.API.events.brush); - this.vis.vislibVis.on('click', this.vis.API.events.filter); - this.vis.vislibVis.on('renderComplete', resolve); + this.vislibVis = new this.Vislib(this.chartEl, visParams); + this.vislibVis.on('brush', this.vis.API.events.brush); + this.vislibVis.on('click', this.vis.API.events.filter); + this.vislibVis.on('renderComplete', resolve); - this.vis.vislibVis.initVisConfig(esResponse, this.vis.getUiState()); + this.vislibVis.initVisConfig(esResponse, this.vis.getUiState()); if (visParams.addLegend) { $(this.container).attr('class', (i, cls) => { return cls.replace(/visLib--legend-\S+/g, ''); }).addClass(legendClassName[visParams.legendPosition]); - this.$scope = this.$rootScope.$new(); - this.$scope.refreshLegend = 0; - this.$scope.vis = this.vis; - this.$scope.visData = esResponse; - this.$scope.visParams = visParams; - this.$scope.uiState = this.$scope.vis.getUiState(); - const legendHtml = this.$compile('')(this.$scope); - this.container.appendChild(legendHtml[0]); - this.$scope.$digest(); + this.mountLegend(esResponse, visParams.legendPosition); } - this.vis.vislibVis.render(esResponse, this.vis.getUiState()); + this.vislibVis.render(esResponse, this.vis.getUiState()); // refreshing the legend after the chart is rendered. // this is necessary because some visualizations // provide data necessary for the legend only after a render cycle. - if (visParams.addLegend && CUSTOM_LEGEND_VIS_TYPES.includes(this.vis.vislibVis.visConfigArgs.type)) { - this.$scope.refreshLegend++; - this.$scope.$digest(); - - this.vis.vislibVis.render(esResponse, this.vis.getUiState()); + if (visParams.addLegend && CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type)) { + this.unmountLegend(); + this.mountLegend(esResponse, visParams.legendPosition); + this.vislibVis.render(esResponse, this.vis.getUiState()); } }); } + mountLegend(visData, position) { + this.unmount = mountReactNode( + + )(this.legendEl); + } + + unmountLegend() { + if (this.unmount) { + this.unmount(); + } + } + destroy() { - if (this.vis.vislibVis) { - this.vis.vislibVis.off('brush', this.vis.API.events.brush); - this.vis.vislibVis.off('click', this.vis.API.events.filter); - this.vis.vislibVis.destroy(); - delete this.vis.vislibVis; + if (this.unmount) { + this.unmount(); } - $(this.container).find('vislib-legend').remove(); - if (this.$scope) { - this.$scope.$destroy(); - this.$scope = null; + + if (this.vislibVis) { + this.vislibVis.off('brush', this.vis.API.events.brush); + this.vislibVis.off('click', this.vis.API.events.filter); + this.vislibVis.destroy(); + delete this.vislibVis; } } } diff --git a/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js b/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js deleted file mode 100644 index 4ad579e1e45f9a..00000000000000 --- a/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import $ from 'jquery'; -import _ from 'lodash'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { Vis } from '../../../../../core_plugins/visualizations/public/np_ready/public/vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('visualize_legend directive', function () { - let $rootScope; - let $compile; - let $timeout; - let $el; - let indexPattern; - let fixtures; - - beforeEach(ngMock.module('kibana', 'kibana/table_vis')); - beforeEach(ngMock.inject(function (Private, $injector) { - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - $timeout = $injector.get('$timeout'); - fixtures = require('fixtures/fake_hierarchical_data'); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - // basically a parameterized beforeEach - function init(vis, esResponse) { - vis.aggs.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); }); - - $rootScope.vis = vis; - $rootScope.visData = esResponse; - $rootScope.uiState = require('fixtures/mock_ui_state'); - $el = $(''); - $compile($el)($rootScope); - $rootScope.$apply(); - } - - function CreateVis(params, requiresSearch) { - const vis = new Vis(indexPattern, { - type: 'line', - params: params || {}, - aggs: [ - { type: 'count', schema: 'metric' }, - { - type: 'range', - schema: 'bucket', - params: { - field: 'bytes', - ranges: [ - { from: 0, to: 1000 }, - { from: 1000, to: 2000 } - ] - } - } - ] - }); - - vis.type.requestHandler = requiresSearch ? 'default' : 'none'; - vis.type.responseHandler = 'none'; - vis.type.requiresSearch = false; - return vis; - } - - it('calls highlight handler when highlight function is called', () => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - let highlight = 0; - _.set(vis, 'vislibVis.handler.highlight', () => { highlight++; }); - $rootScope.highlight({ currentTarget: null }); - expect(highlight).to.equal(1); - }); - - it('calls unhighlight handler when unhighlight function is called', () => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - let unhighlight = 0; - _.set(vis, 'vislibVis.handler.unHighlight', () => { unhighlight++; }); - $rootScope.unhighlight({ currentTarget: null }); - expect(unhighlight).to.equal(1); - }); - - describe('setColor function', () => { - beforeEach(() => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - }); - - it('sets the color in the UI state', () => { - $rootScope.setColor('test', '#ffffff'); - const colors = $rootScope.uiState.get('vis.colors'); - expect(colors.test).to.equal('#ffffff'); - }); - }); - - describe('toggleLegend function', () => { - let vis; - - beforeEach(() => { - const requiresSearch = false; - vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - }); - - it('sets the color in the UI state', () => { - $rootScope.open = true; - $rootScope.toggleLegend(); - $rootScope.$digest(); - $timeout.flush(); - $timeout.verifyNoPendingTasks(); - let legendOpen = $rootScope.uiState.get('vis.legendOpen'); - expect(legendOpen).to.equal(false); - - $rootScope.toggleLegend(); - $rootScope.$digest(); - $timeout.flush(); - $timeout.verifyNoPendingTasks(); - legendOpen = $rootScope.uiState.get('vis.legendOpen'); - expect(legendOpen).to.equal(true); - }); - }); - - it('does not update scope.data if visData is null', () => { - $rootScope.visData = null; - $rootScope.$digest(); - expect($rootScope.data).to.not.equal(null); - }); - - it('works without handler set', () => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - vis.vislibVis = {}; - init(vis, fixtures.oneRangeBucket); - expect(() => { - $rootScope.highlight({ currentTarget: null }); - $rootScope.unhighlight({ currentTarget: null }); - }).to.not.throwError(); - }); -}); diff --git a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss b/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss index 8de88959cfb59b..4d7c0e2bdcadb4 100644 --- a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss +++ b/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss @@ -11,9 +11,11 @@ $visLegendLineHeight: $euiSize; position: absolute; bottom: 0; left: 0; + display: flex; + padding: $euiSizeXS; background-color: $euiColorEmptyShade; transition: opacity $euiAnimSpeedFast $euiAnimSlightResistance, - background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow; + background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow; &:focus { box-shadow: none; @@ -22,13 +24,11 @@ $visLegendLineHeight: $euiSize; } .visLegend__toggle--isOpen { - background-color: transparentize($euiColorDarkestShade, .9); + background-color: transparentize($euiColorDarkestShade, 0.9); opacity: 1; } - .visLegend { - @include euiFontSizeXS; display: flex; min-height: 0; height: 100%; @@ -46,27 +46,30 @@ $visLegendLineHeight: $euiSize; } } -/** - * 1. Position the .visLegend__valueDetails absolutely against the legend item - * 2. Make sure the .visLegend__valueDetails is visible outside the list bounds - * 3. Make sure the currently selected item is top most in z level - */ .visLegend__list { @include euiScrollBar; display: flex; - line-height: $visLegendLineHeight; width: $visLegendWidth; // Must be a hard-coded width for the chart to get its correct dimensions flex: 1 1 auto; flex-direction: column; overflow-x: hidden; overflow-y: auto; + .visLegend__button { + font-size: $euiFontSizeXS; + text-align: left; + overflow: hidden; // Ensures scrollbars don't appear because EuiButton__text has a high line-height + + .visLegend__valueTitle { + vertical-align: middle; + } + } + .visLib--legend-top &, .visLib--legend-bottom & { width: auto; flex-direction: row; flex-wrap: wrap; - overflow: visible; /* 2 */ .visLegend__value { flex-grow: 0; @@ -79,74 +82,19 @@ $visLegendLineHeight: $euiSize; } } -.visLegend__value { - cursor: pointer; - padding: $euiSizeXS; - display: flex; - flex-shrink: 0; - position: relative; /* 1 */ - - > * { - width: 100%; - } - - &.disabled { - opacity: 0.5; - } +.visLegend__valueColorPicker { + width: ($euiSizeL * 8); // 8 columns } -.visLegend__valueTitle { - @include euiTextTruncate; // ALWAYS truncate - color: $visTextColor; +.visLegend__valueColorPickerDot { + cursor: pointer; &:hover { - text-decoration: underline; - } -} - -.visLegend__valueTitle--full ~ .visLegend__valueDetails { - z-index: 2; /* 3 */ -} - -.visLegend__valueDetails { - background-color: $euiColorEmptyShade; - - .visLib--legend-left &, - .visLib--legend-right & { - margin-top: $euiSizeXS; - border-bottom: $euiBorderThin; - } - - .visLib--legend-top &, - .visLib--legend-bottom & { - @include euiBottomShadowMedium; - position: absolute; /* 1 */ - border-radius: $euiBorderRadius; + transform: scale(1.4); } - .visLib--legend-bottom & { - bottom: $visLegendLineHeight + 2 * $euiSizeXS; - } - - .visLib--legend-top & { - margin-top: $euiSizeXS; - } -} - -.visLegend__valueColorPicker { - width: $visColorPickerWidth; - margin: auto; - - .visLegend__valueColorPickerDot { - $colorPickerDotsPerRow: 8; - $colorPickerDotMargin: $euiSizeXS / 2; - $colorPickerDotWidth: $visColorPickerWidth / $colorPickerDotsPerRow - 2 * $colorPickerDotMargin; - - margin: $colorPickerDotMargin; - width: $colorPickerDotWidth; - - &:hover { - transform: scale(1.4); - } + &-isSelected { + border: $euiSizeXS solid; + border-radius: 100%; } } diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.html b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.html deleted file mode 100644 index 70d2a796658f2f..00000000000000 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.html +++ /dev/null @@ -1,99 +0,0 @@ -

- -
    - -
  • - -
    -
    - - {{legendData.label}} -
    - -
    -
    - - - -
    - -
    - - - - -
    - -
    -
    - -
  • -
-
diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js deleted file mode 100644 index 3d054b8f8a2fbd..00000000000000 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; -import html from './vislib_vis_legend.html'; -import { Data } from '../../vislib/lib/data'; -import { uiModules } from '../../modules'; -import { createFiltersFromEvent } from '../../../../core_plugins/visualizations/public'; -import { htmlIdGenerator, keyCodes } from '@elastic/eui'; -import { getTableAggs } from '../../visualize/loader/pipeline_helpers/utilities'; - -export const CUSTOM_LEGEND_VIS_TYPES = ['heatmap', 'gauge']; - -uiModules.get('kibana') - .directive('vislibLegend', function ($timeout) { - - return { - restrict: 'E', - template: html, - link: function ($scope) { - $scope.legendId = htmlIdGenerator()('legend'); - $scope.open = $scope.uiState.get('vis.legendOpen', true); - - $scope.$watch('visData', function (data) { - if (!data) return; - $scope.data = data; - }); - - $scope.$watch('refreshLegend', () => { - refresh(); - }); - - $scope.highlight = function (event) { - const el = event.currentTarget; - const handler = $scope.vis.vislibVis.handler; - - //there is no guarantee that a Chart will set the highlight-function on its handler - if (!handler || typeof handler.highlight !== 'function') { - return; - } - handler.highlight.call(el, handler.el); - }; - - $scope.unhighlight = function (event) { - const el = event.currentTarget; - const handler = $scope.vis.vislibVis.handler; - //there is no guarantee that a Chart will set the unhighlight-function on its handler - if (!handler || typeof handler.unHighlight !== 'function') { - return; - } - handler.unHighlight.call(el, handler.el); - }; - - $scope.setColor = function (label, color) { - const colors = $scope.uiState.get('vis.colors') || {}; - if (colors[label] === color) delete colors[label]; - else colors[label] = color; - $scope.uiState.setSilent('vis.colors', null); - $scope.uiState.set('vis.colors', colors); - $scope.uiState.emit('colorChanged'); - refresh(); - }; - - $scope.toggleLegend = function () { - const bwcAddLegend = $scope.vis.params.addLegend; - const bwcLegendStateDefault = bwcAddLegend == null ? true : bwcAddLegend; - $scope.open = !$scope.uiState.get('vis.legendOpen', bwcLegendStateDefault); - // open should be applied on template before we update uiState - $timeout(() => { - $scope.uiState.set('vis.legendOpen', $scope.open); - }); - }; - - $scope.filter = function (legendData, negate) { - $scope.vis.API.events.filter({ data: legendData.values, negate: negate }); - }; - - $scope.canFilter = function (legendData) { - if (CUSTOM_LEGEND_VIS_TYPES.includes($scope.vis.vislibVis.visConfigArgs.type)) { - return false; - } - const filters = createFiltersFromEvent({ aggConfigs: $scope.tableAggs, data: legendData.values }); - return filters.length; - }; - - /** - * Keydown listener for a legend entry. - * This will close the details panel of this legend entry when pressing Escape. - */ - $scope.onLegendEntryKeydown = function (event) { - if (event.keyCode === keyCodes.ESCAPE) { - event.preventDefault(); - event.stopPropagation(); - $scope.shownDetails = undefined; - } - }; - - $scope.toggleDetails = function (label) { - $scope.shownDetails = $scope.shownDetails === label ? undefined : label; - }; - - $scope.areDetailsVisible = function (label) { - return $scope.shownDetails === label; - }; - - $scope.colors = [ - '#3F6833', '#967302', '#2F575E', '#99440A', '#58140C', '#052B51', '#511749', '#3F2B5B', //6 - '#508642', '#CCA300', '#447EBC', '#C15C17', '#890F02', '#0A437C', '#6D1F62', '#584477', //2 - '#629E51', '#E5AC0E', '#64B0C8', '#E0752D', '#BF1B00', '#0A50A1', '#962D82', '#614D93', //4 - '#7EB26D', '#EAB839', '#6ED0E0', '#EF843C', '#E24D42', '#1F78C1', '#BA43A9', '#705DA0', // Normal - '#9AC48A', '#F2C96D', '#65C5DB', '#F9934E', '#EA6460', '#5195CE', '#D683CE', '#806EB7', //5 - '#B7DBAB', '#F4D598', '#70DBED', '#F9BA8F', '#F29191', '#82B5D8', '#E5A8E2', '#AEA2E0', //3 - '#E0F9D7', '#FCEACA', '#CFFAFF', '#F9E2D2', '#FCE2DE', '#BADFF4', '#F9D9F9', '#DEDAF7' //7 - ]; - - function refresh() { - const vislibVis = $scope.vis.vislibVis; - if (!vislibVis || !vislibVis.visConfig) { - $scope.labels = [{ label: i18n.translate('common.ui.vis.visTypes.legend.loadingLabel', { defaultMessage: 'loading…' }) }]; - return; - } // make sure vislib is defined at this point - - if ($scope.uiState.get('vis.legendOpen') == null && $scope.vis.params.addLegend != null) { - $scope.open = $scope.vis.params.addLegend; - } - - if (CUSTOM_LEGEND_VIS_TYPES.includes(vislibVis.visConfigArgs.type)) { - const labels = vislibVis.getLegendLabels(); - if (labels) { - $scope.labels = _.map(labels, label => { - return { label: label }; - }); - } - } else { - $scope.labels = getLabels($scope.data, vislibVis.visConfigArgs.type); - } - - if (vislibVis.visConfig) { - $scope.getColor = vislibVis.visConfig.data.getColorFunc(); - } - - $scope.tableAggs = getTableAggs($scope.vis); - } - - // Most of these functions were moved directly from the old Legend class. Not a fan of this. - function getLabels(data, type) { - if (!data) return []; - data = data.columns || data.rows || [data]; - if (type === 'pie') return Data.prototype.pieNames(data); - return getSeriesLabels(data); - } - - function getSeriesLabels(data) { - const values = data.map(function (chart) { - return chart.series; - }) - .reduce(function (a, b) { - return a.concat(b); - }, []); - return _.compact(_.uniq(values, 'label')).map(label => { - return { - ...label, - values: [label.values[0].seriesRaw], - }; - }); - } - } - }; - }); diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap new file mode 100644 index 00000000000000..f2c9f4e1b53ec3 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`VisLegend Component Legend closed should match the snapshot 1`] = `"
"`; + +exports[`VisLegend Component Legend open should match the snapshot 1`] = `"
"`; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts new file mode 100644 index 00000000000000..ebf132f0ab697f --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { VisLegend } from './vislib_vis_legend'; +export { CUSTOM_LEGEND_VIS_TYPES } from './models'; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts new file mode 100644 index 00000000000000..1c8d5baf011a34 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts @@ -0,0 +1,84 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface LegendItem { + label: string; + values: any[]; +} + +export const CUSTOM_LEGEND_VIS_TYPES = ['heatmap', 'gauge']; + +export const legendColors: string[] = [ + '#3F6833', + '#967302', + '#2F575E', + '#99440A', + '#58140C', + '#052B51', + '#511749', + '#3F2B5B', // 6 + '#508642', + '#CCA300', + '#447EBC', + '#C15C17', + '#890F02', + '#0A437C', + '#6D1F62', + '#584477', // 2 + '#629E51', + '#E5AC0E', + '#64B0C8', + '#E0752D', + '#BF1B00', + '#0A50A1', + '#962D82', + '#614D93', // 4 + '#7EB26D', + '#EAB839', + '#6ED0E0', + '#EF843C', + '#E24D42', + '#1F78C1', + '#BA43A9', + '#705DA0', // Normal + '#9AC48A', + '#F2C96D', + '#65C5DB', + '#F9934E', + '#EA6460', + '#5195CE', + '#D683CE', + '#806EB7', // 5 + '#B7DBAB', + '#F4D598', + '#70DBED', + '#F9BA8F', + '#F29191', + '#82B5D8', + '#E5A8E2', + '#AEA2E0', // 3 + '#E0F9D7', + '#FCEACA', + '#CFFAFF', + '#F9E2D2', + '#FCE2DE', + '#BADFF4', + '#F9D9F9', + '#DEDAF7', // 7 +]; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx new file mode 100644 index 00000000000000..ba47de73964f6b --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx @@ -0,0 +1,279 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; +import { act } from 'react-hooks-testing-library'; + +import { I18nProvider } from '@kbn/i18n/react'; +import { EuiButtonGroup } from '@elastic/eui'; + +import { VisLegend, VisLegendProps } from '../vislib_vis_legend/vislib_vis_legend'; +import { legendColors } from './models'; + +jest.mock('@elastic/eui', () => ({ + ...jest.requireActual('@elastic/eui'), + htmlIdGenerator: jest.fn().mockReturnValue(() => 'legendId'), +})); + +jest.mock('../../../visualize/loader/pipeline_helpers/utilities', () => ({ + getTableAggs: jest.fn(), +})); +jest.mock('../../../../../core_plugins/visualizations/public', () => ({ + createFiltersFromEvent: jest.fn().mockReturnValue(['yes']), +})); + +const vis = { + params: { + addLegend: true, + }, + API: { + events: { + filter: jest.fn(), + }, + }, +}; +const vislibVis = { + handler: { + highlight: jest.fn(), + unHighlight: jest.fn(), + }, + getLegendLabels: jest.fn(), + visConfigArgs: { + type: 'area', + }, + visConfig: { + data: { + getColorFunc: jest.fn().mockReturnValue(() => 'red'), + }, + }, +}; + +const visData = { + series: [ + { + label: 'A', + values: [ + { + seriesRaw: 'valuesA', + }, + ], + }, + { + label: 'B', + values: [ + { + seriesRaw: 'valuesB', + }, + ], + }, + ], +}; + +const mockState = new Map(); +const uiState = { + get: jest + .fn() + .mockImplementation((key, fallback) => (mockState.has(key) ? mockState.get(key) : fallback)), + set: jest.fn().mockImplementation((key, value) => mockState.set(key, value)), + emit: jest.fn(), + setSilent: jest.fn(), +}; + +const getWrapper = (props?: Partial) => + mount( + + + + ); + +const getLegendItems = (wrapper: ReactWrapper) => wrapper.find('.visLegend__button'); + +describe('VisLegend Component', () => { + let wrapper: ReactWrapper; + + afterEach(() => { + mockState.clear(); + jest.clearAllMocks(); + }); + + describe('Legend open', () => { + beforeEach(() => { + mockState.set('vis.legendOpen', true); + wrapper = getWrapper(); + }); + + it('should match the snapshot', () => { + expect(wrapper.html()).toMatchSnapshot(); + }); + }); + + describe('Legend closed', () => { + beforeEach(() => { + mockState.set('vis.legendOpen', false); + wrapper = getWrapper(); + }); + + it('should match the snapshot', () => { + expect(wrapper.html()).toMatchSnapshot(); + }); + }); + + describe('Highlighting', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('should call highlight handler when legend item is focused', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('focus'); + + expect(vislibVis.handler.highlight).toHaveBeenCalledTimes(1); + }); + + it('should call highlight handler when legend item is hovered', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('mouseEnter'); + + expect(vislibVis.handler.highlight).toHaveBeenCalledTimes(1); + }); + + it('should call unHighlight handler when legend item is blurred', () => { + let first = getLegendItems(wrapper).first(); + first.simulate('focus'); + first = getLegendItems(wrapper).first(); + first.simulate('blur'); + + expect(vislibVis.handler.unHighlight).toHaveBeenCalledTimes(1); + }); + + it('should call unHighlight handler when legend item is unhovered', () => { + const first = getLegendItems(wrapper).first(); + + act(() => { + first.simulate('mouseEnter'); + first.simulate('mouseLeave'); + }); + + expect(vislibVis.handler.unHighlight).toHaveBeenCalledTimes(1); + }); + + it('should work with no handlers set', () => { + const newVis = { + ...vis, + vislibVis: { + ...vislibVis, + handler: null, + }, + }; + + expect(() => { + wrapper = getWrapper({ vis: newVis }); + const first = getLegendItems(wrapper).first(); + first.simulate('focus'); + first.simulate('blur'); + }).not.toThrow(); + }); + }); + + describe('Filtering', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('should filter out when clicked', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + const filterGroup = wrapper.find(EuiButtonGroup).first(); + filterGroup.getElement().props.onChange('filterIn'); + + expect(vis.API.events.filter).toHaveBeenCalledWith({ data: ['valuesA'], negate: false }); + expect(vis.API.events.filter).toHaveBeenCalledTimes(1); + }); + + it('should filter in when clicked', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + const filterGroup = wrapper.find(EuiButtonGroup).first(); + filterGroup.getElement().props.onChange('filterOut'); + + expect(vis.API.events.filter).toHaveBeenCalledWith({ data: ['valuesA'], negate: true }); + expect(vis.API.events.filter).toHaveBeenCalledTimes(1); + }); + }); + + describe('Toggles details', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('should show details when clicked', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + + expect(wrapper.exists('.visLegend__valueDetails')).toBe(true); + }); + }); + + describe('setColor', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('sets the color in the UI state', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + + const popover = wrapper.find('.visLegend__valueDetails').first(); + const firstColor = popover.find('.visLegend__valueColorPickerDot').first(); + firstColor.simulate('click'); + + const colors = mockState.get('vis.colors'); + + expect(colors.A).toBe(legendColors[0]); + }); + }); + + describe('toggleLegend function', () => { + it('click should show legend once toggled from hidden', () => { + mockState.set('vis.legendOpen', false); + wrapper = getWrapper(); + const toggleButton = wrapper.find('.visLegend__toggle').first(); + toggleButton.simulate('click'); + + expect(wrapper.exists('.visLegend__list')).toBe(true); + }); + + it('click should hide legend once toggled from shown', () => { + mockState.set('vis.legendOpen', true); + wrapper = getWrapper(); + const toggleButton = wrapper.find('.visLegend__toggle').first(); + toggleButton.simulate('click'); + + expect(wrapper.exists('.visLegend__list')).toBe(false); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx new file mode 100644 index 00000000000000..f0100e369f0507 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx @@ -0,0 +1,264 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { BaseSyntheticEvent, KeyboardEvent, PureComponent } from 'react'; +import classNames from 'classnames'; +import { compact, uniq, map } from 'lodash'; + +import { i18n } from '@kbn/i18n'; +import { EuiPopoverProps, EuiIcon, keyCodes, htmlIdGenerator } from '@elastic/eui'; + +// @ts-ignore +import { Data } from '../../../vislib/lib/data'; +// @ts-ignore +import { createFiltersFromEvent } from '../../../../../core_plugins/visualizations/public'; +import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models'; +import { VisLegendItem } from './vislib_vis_legend_item'; +import { getTableAggs } from '../../../visualize/loader/pipeline_helpers/utilities'; + +export interface VisLegendProps { + vis: any; + vislibVis: any; + visData: any; + uiState: any; + position: 'top' | 'bottom' | 'left' | 'right'; +} + +export interface VisLegendState { + open: boolean; + labels: any[]; + tableAggs: any[]; + selectedLabel: string | null; +} + +export class VisLegend extends PureComponent { + legendId = htmlIdGenerator()('legend'); + getColor: (label: string) => string = () => ''; + + constructor(props: VisLegendProps) { + super(props); + const open = props.uiState.get('vis.legendOpen', true); + + this.state = { + open, + labels: [], + tableAggs: [], + selectedLabel: null, + }; + } + + componentDidMount() { + this.refresh(); + } + + toggleLegend = () => { + const bwcAddLegend = this.props.vis.params.addLegend; + const bwcLegendStateDefault = bwcAddLegend == null ? true : bwcAddLegend; + const newOpen = !this.props.uiState.get('vis.legendOpen', bwcLegendStateDefault); + this.setState({ open: newOpen }); + // open should be applied on template before we update uiState + setTimeout(() => { + this.props.uiState.set('vis.legendOpen', newOpen); + }); + }; + + setColor = (label: string, color: string) => (event: BaseSyntheticEvent) => { + if ((event as KeyboardEvent).keyCode && (event as KeyboardEvent).keyCode !== keyCodes.ENTER) { + return; + } + + const colors = this.props.uiState.get('vis.colors') || {}; + if (colors[label] === color) delete colors[label]; + else colors[label] = color; + this.props.uiState.setSilent('vis.colors', null); + this.props.uiState.set('vis.colors', colors); + this.props.uiState.emit('colorChanged'); + this.refresh(); + }; + + filter = ({ values: data }: LegendItem, negate: boolean) => { + this.props.vis.API.events.filter({ data, negate }); + }; + + canFilter = (item: LegendItem): boolean => { + if (CUSTOM_LEGEND_VIS_TYPES.includes(this.props.vislibVis.visConfigArgs.type)) { + return false; + } + const filters = createFiltersFromEvent({ aggConfigs: this.state.tableAggs, data: item.values }); + return Boolean(filters.length); + }; + + toggleDetails = (label: string | null) => (event?: BaseSyntheticEvent) => { + if ( + event && + (event as KeyboardEvent).keyCode && + (event as KeyboardEvent).keyCode !== keyCodes.ENTER + ) { + return; + } + this.setState({ selectedLabel: this.state.selectedLabel === label ? null : label }); + }; + + getSeriesLabels = (data: any[]) => { + const values = data.map(chart => chart.series).reduce((a, b) => a.concat(b), []); + + return compact(uniq(values, 'label')).map((label: any) => ({ + ...label, + values: [label.values[0].seriesRaw], + })); + }; + + // Most of these functions were moved directly from the old Legend class. Not a fan of this. + getLabels = (data: any, type: string) => { + if (!data) return []; + data = data.columns || data.rows || [data]; + + if (type === 'pie') return Data.prototype.pieNames(data); + + return this.getSeriesLabels(data); + }; + + refresh = () => { + const vislibVis = this.props.vislibVis; + if (!vislibVis || !vislibVis.visConfig) { + this.setState({ + labels: [ + { + label: i18n.translate('common.ui.vis.visTypes.legend.loadingLabel', { + defaultMessage: 'loading…', + }), + }, + ], + }); + return; + } // make sure vislib is defined at this point + + if ( + this.props.uiState.get('vis.legendOpen') == null && + this.props.vis.params.addLegend != null + ) { + this.setState({ open: this.props.vis.params.addLegend }); + } + + if (CUSTOM_LEGEND_VIS_TYPES.includes(vislibVis.visConfigArgs.type)) { + const legendLabels = this.props.vislibVis.getLegendLabels(); + if (legendLabels) { + this.setState({ + labels: map(legendLabels, label => { + return { label }; + }), + }); + } + } else { + this.setState({ labels: this.getLabels(this.props.visData, vislibVis.visConfigArgs.type) }); + } + + if (vislibVis.visConfig) { + this.getColor = this.props.vislibVis.visConfig.data.getColorFunc(); + } + + this.setState({ tableAggs: getTableAggs(this.props.vis) }); + }; + + highlight = (event: BaseSyntheticEvent) => { + const el = event.currentTarget; + const handler = this.props.vislibVis && this.props.vislibVis.handler; + + // there is no guarantee that a Chart will set the highlight-function on its handler + if (!handler || typeof handler.highlight !== 'function') { + return; + } + handler.highlight.call(el, handler.el); + }; + + unhighlight = (event: BaseSyntheticEvent) => { + const el = event.currentTarget; + const handler = this.props.vislibVis && this.props.vislibVis.handler; + + // there is no guarantee that a Chart will set the unhighlight-function on its handler + if (!handler || typeof handler.unHighlight !== 'function') { + return; + } + handler.unHighlight.call(el, handler.el); + }; + + getAnchorPosition = () => { + const { position } = this.props; + + switch (position) { + case 'bottom': + return 'upCenter'; + case 'left': + return 'rightUp'; + case 'right': + return 'leftUp'; + default: + return 'downCenter'; + } + }; + + renderLegend = (anchorPosition: EuiPopoverProps['anchorPosition']) => ( +
    + {this.state.labels.map(item => ( + + ))} +
+ ); + + render() { + const { open } = this.state; + const anchorPosition = this.getAnchorPosition(); + + return ( +
+ + {open && this.renderLegend(anchorPosition)} +
+ ); + } +} diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx new file mode 100644 index 00000000000000..7376fabfe738be --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx @@ -0,0 +1,203 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { memo, BaseSyntheticEvent, KeyboardEvent } from 'react'; +import classNames from 'classnames'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiPopover, + keyCodes, + EuiIcon, + EuiSpacer, + EuiButtonEmpty, + EuiPopoverProps, + EuiButtonGroup, + EuiButtonGroupOption, +} from '@elastic/eui'; + +import { legendColors, LegendItem } from './models'; + +interface Props { + item: LegendItem; + legendId: string; + selected: boolean; + canFilter: boolean; + anchorPosition: EuiPopoverProps['anchorPosition']; + onFilter: (item: LegendItem, negate: boolean) => void; + onSelect: (label: string | null) => (event?: BaseSyntheticEvent) => void; + onHighlight: (event: BaseSyntheticEvent) => void; + onUnhighlight: (event: BaseSyntheticEvent) => void; + setColor: (label: string, color: string) => (event: BaseSyntheticEvent) => void; + getColor: (label: string) => string; +} + +const VisLegendItemComponent = ({ + item, + legendId, + selected, + canFilter, + anchorPosition, + onFilter, + onSelect, + onHighlight, + onUnhighlight, + setColor, + getColor, +}: Props) => { + /** + * Keydown listener for a legend entry. + * This will close the details panel of this legend entry when pressing Escape. + */ + const onLegendEntryKeydown = (event: KeyboardEvent) => { + if (event.keyCode === keyCodes.ESCAPE) { + event.preventDefault(); + event.stopPropagation(); + onSelect(null)(); + } + }; + + const filterOptions: EuiButtonGroupOption[] = [ + { + id: 'filterIn', + label: i18n.translate('common.ui.vis.visTypes.legend.filterForValueButtonAriaLabel', { + defaultMessage: 'Filter for value {legendDataLabel}', + values: { legendDataLabel: item.label }, + }), + iconType: 'plusInCircle', + 'data-test-subj': `legend-${item.label}-filterIn`, + }, + { + id: 'filterOut', + label: i18n.translate('common.ui.vis.visTypes.legend.filterOutValueButtonAriaLabel', { + defaultMessage: 'Filter out value {legendDataLabel}', + values: { legendDataLabel: item.label }, + }), + iconType: 'minusInCircle', + 'data-test-subj': `legend-${item.label}-filterOut`, + }, + ]; + + const handleFilterChange = (id: string) => { + onFilter(item, id !== 'filterIn'); + }; + + const renderFilterBar = () => ( + <> + + + + ); + + const button = ( + + + {item.label} + + ); + + const renderDetails = () => ( + +
+ {canFilter && renderFilterBar()} + +
+ + + + {legendColors.map(color => ( + + ))} +
+
+
+ ); + + return ( +
  • + {renderDetails()} +
  • + ); +}; + +export const VisLegendItem = memo(VisLegendItemComponent); diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js index 81d26a4b69478e..a6792670fdb3fa 100644 --- a/test/functional/page_objects/visualize_page.js +++ b/test/functional/page_objects/visualize_page.js @@ -1186,8 +1186,13 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli } async getLegendEntries() { - const legendEntries = await find.allByCssSelector('.visLegend__valueTitle', defaultFindTimeout * 2); - return await Promise.all(legendEntries.map(async chart => await chart.getAttribute('data-label'))); + const legendEntries = await find.allByCssSelector( + '.visLegend__button', + defaultFindTimeout * 2 + ); + return await Promise.all( + legendEntries.map(async chart => await chart.getAttribute('data-label')) + ); } async openLegendOptionColors(name) { @@ -1217,7 +1222,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli async toggleLegend(show = true) { await retry.try(async () => { - const isVisible = find.byCssSelector('vislib-legend'); + const isVisible = find.byCssSelector('.visLegend'); if ((show && !isVisible) || (!show && isVisible)) { await testSubjects.click('vislibToggleLegend'); } @@ -1227,7 +1232,9 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli async filterLegend(name) { await this.toggleLegend(); await testSubjects.click(`legend-${name}`); - await testSubjects.click(`legend-${name}-filterIn`); + const filters = await testSubjects.find(`legend-${name}-filters`); + const [filterIn] = await filters.findAllByCssSelector(`input`); + await filterIn.click(); await this.waitForVisualizationRenderingStabilized(); } diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css index 9e8415a1ff18c1..ab88e4780936ea 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css @@ -92,11 +92,6 @@ visualize-app .visEditor__canvas { display: none; } -/* slightly increate legend text size for readability */ -.visualize visualize-legend .visLegend__valueTitle { - font-size: 1.2em; -} - /* Ensure the min-height of the small breakpoint isn't used */ .vis-editor visualization { min-height: 0 !important; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css index 30c253f36840af..8aca042144b3b4 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css @@ -91,11 +91,6 @@ visualize-app .visEditor__canvas { display: none; } -/* slightly increate legend text size for readability */ -.visualize visualize-legend .visLegend__valueTitle { - font-size: 1.2em; -} - /* Ensure the min-height of the small breakpoint isn't used */ .vis-editor visualization { min-height: 0 !important; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 217b20797492a6..23ebdf0ad6aadd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -529,6 +529,7 @@ "common.ui.vis.kibanaMap.zoomWarning": "ズームレベルが最大に達しました。完全にズームインするには、Elasticsearch と Kibana の {defaultDistribution} にアップグレードしてください。{ems} でより多くのズームレベルが利用できます。または、独自のマップサーバーを構成できます。詳細は { wms } または { configSettings} をご覧ください。", "common.ui.vis.visTypes.legend.filterForValueButtonAriaLabel": "値 {legendDataLabel} でフィルタリング", "common.ui.vis.visTypes.legend.filterOutValueButtonAriaLabel": "値 {legendDataLabel} を除外", + "common.ui.vis.visTypes.legend.filterOptionsLegend": "{legendDataLabel}, フィルターオプション", "common.ui.vis.visTypes.legend.loadingLabel": "読み込み中…", "common.ui.vis.visTypes.legend.setColorScreenReaderDescription": "値 {legendDataLabel} の色を設定", "common.ui.vis.visTypes.legend.toggleLegendButtonAriaLabel": "凡例を切り替える", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6a2ba20af7714b..28f6c2857d51d3 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -530,6 +530,7 @@ "common.ui.vis.kibanaMap.zoomWarning": "已达到缩放级别最大数目。要一直放大,请升级到 Elasticsearch 和 Kibana 的 {defaultDistribution}。您可以通过 {ems} 免费使用其他缩放级别。或者,您可以配置自己的地图服务器。请前往 { wms } 或 { configSettings} 以获取详细信息。", "common.ui.vis.visTypes.legend.filterForValueButtonAriaLabel": "筛留值 {legendDataLabel}", "common.ui.vis.visTypes.legend.filterOutValueButtonAriaLabel": "筛除值 {legendDataLabel}", + "common.ui.vis.visTypes.legend.filterOptionsLegend": "{legendDataLabel}, 篩選器選項", "common.ui.vis.visTypes.legend.loadingLabel": "正在加载……", "common.ui.vis.visTypes.legend.setColorScreenReaderDescription": "为值 {legendDataLabel} 设置颜色", "common.ui.vis.visTypes.legend.toggleLegendButtonAriaLabel": "切换图例", From 60896e80a646fe9f95fa5f8ba17708663ce1e063 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 27 Nov 2019 22:58:41 -0500 Subject: [PATCH 09/52] [SIEM] [Detection Engine] Add edit on rule creation (#51670) * Add creation rule on Detection Engine * review + bug fixes * review II + clean up * fix persistence saved query * fix eui prop + add type security to add rule * fix more bug from review III * review IV * add edit on creation on rule * review * fix status icon color * fix filter label translation --- .../components/add_item_form/index.tsx | 76 +++++---- .../description_step/filter_label.tsx | 93 +++++++++++ .../description_step/filter_operator.tsx | 119 ++++++++++++++ .../components/description_step/index.tsx | 155 ++++++++++++++++++ .../description_step/translations.tsx | 19 +++ .../components/query_bar/index.tsx | 2 +- .../create_rule/components/shared_imports.ts | 5 + .../components/status_icon/index.tsx | 5 +- .../step_about_rule/default_value.ts | 9 +- .../components/step_about_rule/index.tsx | 25 +-- .../components/step_about_rule/schema.tsx | 7 +- .../components/step_define_rule/index.tsx | 48 +++--- .../components/step_define_rule/schema.tsx | 14 +- .../components/step_schedule_rule/index.tsx | 23 ++- .../components/step_schedule_rule/schema.tsx | 3 +- .../detection_engine/create_rule/index.tsx | 112 ++++++++++--- .../create_rule/translations.ts | 4 + .../detection_engine/create_rule/types.ts | 42 +++-- 18 files changed, 634 insertions(+), 127 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_label.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_operator.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/translations.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx index 6673262a159062..04bca0cdbd61b7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx @@ -5,7 +5,7 @@ */ import { EuiButtonEmpty, EuiButtonIcon, EuiFormRow, EuiFieldText, EuiSpacer } from '@elastic/eui'; -import { isEmpty, isEqual } from 'lodash/fp'; +import { isEmpty } from 'lodash/fp'; import React, { ChangeEvent, useCallback, useEffect, useState, useRef } from 'react'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; @@ -21,15 +21,22 @@ interface AddItemProps { export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: AddItemProps) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - const [items, setItems] = useState(['']); - const [haveBeenKeyboardDeleted, setHaveBeenKeyboardDeleted] = useState(false); + // const [items, setItems] = useState(['']); + const [haveBeenKeyboardDeleted, setHaveBeenKeyboardDeleted] = useState(-1); - const lastInputRef = useRef(null); + const inputsRef = useRef([]); const removeItem = useCallback( (index: number) => { const values = field.value as string[]; field.setValue([...values.slice(0, index), ...values.slice(index + 1)]); + inputsRef.current = [ + ...inputsRef.current.slice(0, index), + ...inputsRef.current.slice(index + 1), + ]; + if (inputsRef.current[index] != null) { + inputsRef.current[index].value = 're-render'; + } }, [field] ); @@ -38,16 +45,26 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad const values = field.value as string[]; if (!isEmpty(values[values.length - 1])) { field.setValue([...values, '']); + } else { + field.setValue(['']); } }, [field]); const updateItem = useCallback( (event: ChangeEvent, index: number) => { + event.persist(); const values = field.value as string[]; const value = event.target.value; if (isEmpty(value)) { - setHaveBeenKeyboardDeleted(true); field.setValue([...values.slice(0, index), ...values.slice(index + 1)]); + inputsRef.current = [ + ...inputsRef.current.slice(0, index), + ...inputsRef.current.slice(index + 1), + ]; + setHaveBeenKeyboardDeleted(inputsRef.current.length - 1); + if (inputsRef.current[index] != null) { + inputsRef.current[index].value = 're-render'; + } } else { field.setValue([...values.slice(0, index), value, ...values.slice(index + 1)]); } @@ -56,31 +73,30 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad ); const handleLastInputRef = useCallback( - (element: HTMLInputElement | null) => { - lastInputRef.current = element; + (index: number, element: HTMLInputElement | null) => { + if (element != null) { + inputsRef.current = [ + ...inputsRef.current.slice(0, index), + element, + ...inputsRef.current.slice(index + 1), + ]; + } }, - [lastInputRef] + [inputsRef] ); useEffect(() => { - if (!isEqual(field.value, items)) { - setItems( - isEmpty(field.value) - ? [''] - : haveBeenKeyboardDeleted - ? [...(field.value as string[]), ''] - : (field.value as string[]) - ); - setHaveBeenKeyboardDeleted(false); - } - }, [field.value]); - - useEffect(() => { - if (!haveBeenKeyboardDeleted && lastInputRef != null && lastInputRef.current != null) { - lastInputRef.current.focus(); + if ( + haveBeenKeyboardDeleted !== -1 && + !isEmpty(inputsRef.current) && + inputsRef.current[haveBeenKeyboardDeleted] != null + ) { + inputsRef.current[haveBeenKeyboardDeleted].focus(); + setHaveBeenKeyboardDeleted(-1); } - }, [haveBeenKeyboardDeleted, lastInputRef]); + }, [haveBeenKeyboardDeleted, inputsRef.current]); + const values = field.value as string[]; return ( <> - {items.map((item, index) => { + {values.map((item, index) => { const euiFieldProps = { disabled: isDisabled, - ...(index === items.length - 1 ? { inputRef: handleLastInputRef } : {}), + ...(index === values.length - 1 + ? { inputRef: handleLastInputRef.bind(null, index) } + : {}), + ...(inputsRef.current[index] != null && inputsRef.current[index].value !== item + ? { value: item } + : {}), }; return (
    @@ -109,13 +130,12 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad aria-label={I18n.DELETE} /> } - value={item} onChange={e => updateItem(e, index)} compressed fullWidth {...euiFieldProps} /> - {items.length - 1 !== index && } + {values.length - 1 !== index && }
    ); })} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_label.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_label.tsx new file mode 100644 index 00000000000000..15844f50122910 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_label.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo } from 'react'; +import { EuiTextColor } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { esFilters } from '../../../../../../../../../../src/plugins/data/public'; +import { existsOperator, isOneOfOperator } from './filter_operator'; + +interface Props { + filter: esFilters.Filter; + valueLabel?: string; +} + +export const FilterLabel = memo(({ filter, valueLabel }) => { + const prefixText = filter.meta.negate + ? ` ${i18n.translate('xpack.siem.detectionEngine.createRule.filterLabel.negatedFilterPrefix', { + defaultMessage: 'NOT ', + })}` + : ''; + const prefix = + filter.meta.negate && !filter.meta.disabled ? ( + {prefixText} + ) : ( + prefixText + ); + + if (filter.meta.alias !== null) { + return ( + <> + {prefix} + {filter.meta.alias} + + ); + } + + switch (filter.meta.type) { + case esFilters.FILTERS.EXISTS: + return ( + <> + {prefix} + {`${filter.meta.key}: ${existsOperator.message}`} + + ); + case esFilters.FILTERS.GEO_BOUNDING_BOX: + return ( + <> + {prefix} + {`${filter.meta.key}: ${valueLabel}`} + + ); + case esFilters.FILTERS.GEO_POLYGON: + return ( + <> + {prefix} + {`${filter.meta.key}: ${valueLabel}`} + + ); + case esFilters.FILTERS.PHRASES: + return ( + <> + {prefix} + {filter.meta.key} {isOneOfOperator.message} {valueLabel} + + ); + case esFilters.FILTERS.QUERY_STRING: + return ( + <> + {prefix} + {valueLabel} + + ); + case esFilters.FILTERS.PHRASE: + case esFilters.FILTERS.RANGE: + return ( + <> + {prefix} + {`${filter.meta.key}: ${valueLabel}`} + + ); + default: + return ( + <> + {prefix} + {JSON.stringify(filter.query)} + + ); + } +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_operator.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_operator.tsx new file mode 100644 index 00000000000000..7aa5b0beed2d65 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_operator.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +import { esFilters } from '../../../../../../../../../../src/plugins/data/public'; + +export interface Operator { + message: string; + type: esFilters.FILTERS; + negate: boolean; + fieldTypes?: string[]; +} + +export const isOperator = { + message: i18n.translate( + 'xpack.siem.detectionEngine.createRule.filterLabel.isOperatorOptionLabel', + { + defaultMessage: 'is', + } + ), + type: esFilters.FILTERS.PHRASE, + negate: false, +}; + +export const isNotOperator = { + message: i18n.translate( + 'xpack.siem.detectionEngine.createRule.filterLabel.isNotOperatorOptionLabel', + { + defaultMessage: 'is not', + } + ), + type: esFilters.FILTERS.PHRASE, + negate: true, +}; + +export const isOneOfOperator = { + message: i18n.translate( + 'xpack.siem.detectionEngine.createRule.filterLabel.isOneOfOperatorOptionLabel', + { + defaultMessage: 'is one of', + } + ), + type: esFilters.FILTERS.PHRASES, + negate: false, + fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'], +}; + +export const isNotOneOfOperator = { + message: i18n.translate( + 'xpack.siem.detectionEngine.createRule.filterLabel.isNotOneOfOperatorOptionLabel', + { + defaultMessage: 'is not one of', + } + ), + type: esFilters.FILTERS.PHRASES, + negate: true, + fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'], +}; + +export const isBetweenOperator = { + message: i18n.translate( + 'xpack.siem.detectionEngine.createRule.filterLabel.isBetweenOperatorOptionLabel', + { + defaultMessage: 'is between', + } + ), + type: esFilters.FILTERS.RANGE, + negate: false, + fieldTypes: ['number', 'date', 'ip'], +}; + +export const isNotBetweenOperator = { + message: i18n.translate( + 'xpack.siem.detectionEngine.createRule.filterLabel.isNotBetweenOperatorOptionLabel', + { + defaultMessage: 'is not between', + } + ), + type: esFilters.FILTERS.RANGE, + negate: true, + fieldTypes: ['number', 'date', 'ip'], +}; + +export const existsOperator = { + message: i18n.translate( + 'xpack.siem.detectionEngine.createRule.filterLabel.existsOperatorOptionLabel', + { + defaultMessage: 'exists', + } + ), + type: esFilters.FILTERS.EXISTS, + negate: false, +}; + +export const doesNotExistOperator = { + message: i18n.translate( + 'xpack.siem.detectionEngine.createRule.filterLabel.doesNotExistOperatorOptionLabel', + { + defaultMessage: 'does not exist', + } + ), + type: esFilters.FILTERS.EXISTS, + negate: true, +}; + +export const FILTER_OPERATORS: Operator[] = [ + isOperator, + isNotOperator, + isOneOfOperator, + isNotOneOfOperator, + isBetweenOperator, + isNotBetweenOperator, + existsOperator, + doesNotExistOperator, +]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx new file mode 100644 index 00000000000000..3e8147e5ca3c19 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiBadge, EuiDescriptionList, EuiFlexGroup, EuiFlexItem, EuiTextArea } from '@elastic/eui'; +import { isEmpty, chunk, get, pick } from 'lodash/fp'; +import React, { memo, ReactNode } from 'react'; +import styled from 'styled-components'; + +import { + IIndexPattern, + esFilters, + Query, + utils, +} from '../../../../../../../../../../src/plugins/data/public'; + +import { FilterLabel } from './filter_label'; +import { FormSchema } from '../shared_imports'; +import * as I18n from './translations'; + +interface StepRuleDescriptionProps { + data: unknown; + indexPatterns?: IIndexPattern; + schema: FormSchema; +} + +const EuiBadgeWrap = styled(EuiBadge)` + .euiBadge__text { + white-space: pre-wrap !important; + } +`; + +const EuiFlexItemWidth = styled(EuiFlexItem)` + width: 50%; +`; + +export const StepRuleDescription = memo( + ({ data, indexPatterns, schema }) => { + const keys = Object.keys(schema); + return ( + + {chunk(keys.includes('queryBar') ? 3 : Math.ceil(keys.length / 2), keys).map(key => ( + + + + ))} + + ); + } +); + +interface ListItems { + title: NonNullable; + description: NonNullable; +} + +const buildListItems = ( + data: unknown, + schema: FormSchema, + indexPatterns?: IIndexPattern +): ListItems[] => + Object.keys(schema).reduce( + (acc, field) => [ + ...acc, + ...getDescriptionItem(field, get([field, 'label'], schema), data, indexPatterns), + ], + [] + ); + +const getDescriptionItem = ( + field: string, + label: string, + value: unknown, + indexPatterns?: IIndexPattern +): ListItems[] => { + if (field === 'queryBar' && indexPatterns != null) { + const filters = get('queryBar.filters', value) as esFilters.Filter[]; + const query = get('queryBar.query', value) as Query; + const savedId = get('queryBar.saved_id', value); + let items: ListItems[] = []; + if (!isEmpty(filters)) { + items = [ + ...items, + { + title: <>{I18n.FILTERS_LABEL}, + description: ( + + {filters.map((filter, index) => ( + + + + + + ))} + + ), + }, + ]; + } + if (!isEmpty(query.query)) { + items = [ + ...items, + { + title: <>{I18n.QUERY_LABEL}, + description: <>{query.query}, + }, + ]; + } + if (!isEmpty(savedId)) { + items = [ + ...items, + { + title: <>{I18n.SAVED_ID_LABEL}, + description: <>{savedId}, + }, + ]; + } + return items; + } else if (field === 'description') { + return [ + { + title: label, + description: , + }, + ]; + } else if (Array.isArray(get(field, value))) { + return [ + { + title: label, + description: ( + + {get(field, value).map((val: string) => ( + + {val} + + ))} + + ), + }, + ]; + } + return [ + { + title: label, + description: get(field, value), + }, + ]; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/translations.tsx new file mode 100644 index 00000000000000..0995e0e9166527 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/translations.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const FILTERS_LABEL = i18n.translate('xpack.siem.detectionEngine.createRule.filtersLabel', { + defaultMessage: 'Filters', +}); + +export const QUERY_LABEL = i18n.translate('xpack.siem.detectionEngine.createRule.QueryLabel', { + defaultMessage: 'Query', +}); + +export const SAVED_ID_LABEL = i18n.translate('xpack.siem.detectionEngine.createRule.savedIdLabel', { + defaultMessage: 'Saved query name', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx index 4e7832c890255b..8db9d3b44e3f5f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx @@ -28,7 +28,7 @@ import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; export interface FieldValueQueryBar { filters: esFilters.Filter[]; query: Query; - saved_id: string; + saved_id: string | null; } interface QueryBarDefineRuleProps { dataTestSubj: string; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/shared_imports.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/shared_imports.ts index 6c91c4a02edf90..8eb85c9fe3faef 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/shared_imports.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/shared_imports.ts @@ -8,9 +8,14 @@ export { getUseField, getFieldValidityAndErrorMessage, FieldHook, + FIELD_TYPES, Form, FormDataProvider, + FormSchema, UseField, useForm, + ValidationFunc, } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { Field } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/components'; +export { fieldValidators } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/helpers'; +export { ERROR_CODE } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/status_icon/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/status_icon/index.tsx index ad0011ff8ed188..22b116557ae6e5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/status_icon/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/status_icon/index.tsx @@ -9,8 +9,7 @@ import React, { memo } from 'react'; import styled from 'styled-components'; import { useEuiTheme } from '../../../../../lib/theme/use_eui_theme'; - -export type RuleStatusType = 'passive' | 'active' | 'valid'; +import { RuleStatusType } from '../../types'; export interface RuleStatusIconProps { name: string; @@ -32,7 +31,7 @@ export const RuleStatusIcon = memo(({ name, type }) => { return ( - {type === 'valid' ? : null} + {type === 'valid' ? : null} ); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts index b94fa8c9339377..7c4d78f364479e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts @@ -4,12 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -export const defaultValue = { +import { AboutStepRule } from '../../types'; + +export const defaultValue: AboutStepRule = { name: '', description: '', + isNew: true, severity: 'low', riskScore: 50, - references: [], - falsePositives: [], + references: [''], + falsePositives: [''], tags: [], }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx index 4393f39ad2f859..56830f252748f9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx @@ -5,9 +5,9 @@ */ import { EuiButton, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React, { memo, useCallback } from 'react'; +import React, { memo, useCallback, useState } from 'react'; -import { RuleStepProps, RuleStep } from '../../types'; +import { RuleStepProps, RuleStep, AboutStepRule } from '../../types'; import * as CreateRuleI18n from '../../translations'; import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports'; import { AddItem } from '../add_item_form'; @@ -15,24 +15,29 @@ import { defaultRiskScoreBySeverity, severityOptions, SeverityValue } from './da import { defaultValue } from './default_value'; import { schema } from './schema'; import * as I18n from './translations'; +import { StepRuleDescription } from '../description_step'; const CommonUseField = getUseField({ component: Field }); -export const StepAboutRule = memo(({ isLoading, setStepData }) => { +export const StepAboutRule = memo(({ isEditView, isLoading, setStepData }) => { + const [myStepData, setMyStepData] = useState(defaultValue); const { form } = useForm({ - schema, - defaultValue, + defaultValue: myStepData, options: { stripEmptyFields: false }, + schema, }); const onSubmit = useCallback(async () => { - const { isValid: newIsValid, data } = await form.submit(); - if (newIsValid) { - setStepData(RuleStep.aboutRule, data, newIsValid); + const { isValid, data } = await form.submit(); + if (isValid) { + setStepData(RuleStep.aboutRule, data, isValid); + setMyStepData({ ...data, isNew: false } as AboutStepRule); } }, [form]); - return ( + return isEditView && myStepData != null ? ( + + ) : ( <>
    (({ isLoading, setStepData }) => - {CreateRuleI18n.CONTINUE} + {myStepData.isNew ? CreateRuleI18n.CONTINUE : CreateRuleI18n.UPDATE} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/schema.tsx index 97ad3d595a9384..da908bdf02e432 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/schema.tsx @@ -8,13 +8,8 @@ import { EuiText } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { - FormSchema, - FIELD_TYPES, -} from '../../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; -import { fieldValidators } from '../../../../../../../../../../src/plugins/es_ui_shared/static/forms/helpers'; - import * as CreateRuleI18n from '../../translations'; +import { FIELD_TYPES, fieldValidators, FormSchema } from '../shared_imports'; const { emptyField } = fieldValidators; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx index b09d0df962793f..26306d3573926f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx @@ -8,11 +8,13 @@ import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elasti import { isEqual } from 'lodash/fp'; import React, { memo, useCallback, useEffect, useState } from 'react'; +import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules/fetch_index_patterns'; import { DEFAULT_INDEX_KEY, DEFAULT_SIGNALS_INDEX_KEY } from '../../../../../../common/constants'; import { useKibanaUiSetting } from '../../../../../lib/settings/use_kibana_ui_setting'; import * as CreateRuleI18n from '../../translations'; -import { RuleStep, RuleStepProps } from '../../types'; +import { DefineStepRule, RuleStep, RuleStepProps } from '../../types'; +import { StepRuleDescription } from '../description_step'; import { QueryBarDefineRule } from '../query_bar'; import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports'; import { schema } from './schema'; @@ -20,7 +22,7 @@ import * as I18n from './translations'; const CommonUseField = getUseField({ component: Field }); -export const StepDefineRule = memo(({ isLoading, setStepData }) => { +export const StepDefineRule = memo(({ isEditView, isLoading, setStepData }) => { const [initializeOutputIndex, setInitializeOutputIndex] = useState(true); const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(''); const [ @@ -29,26 +31,28 @@ export const StepDefineRule = memo(({ isLoading, setStepData }) = ] = useFetchIndexPatterns(); const [indicesConfig] = useKibanaUiSetting(DEFAULT_INDEX_KEY); const [signalIndexConfig] = useKibanaUiSetting(DEFAULT_SIGNALS_INDEX_KEY); - + const [myStepData, setMyStepData] = useState({ + index: indicesConfig || [], + isNew: true, + outputIndex: signalIndexConfig, + queryBar: { + query: { query: '', language: 'kuery' }, + filters: [], + saved_id: null, + }, + useIndicesConfig: 'true', + }); const { form } = useForm({ schema, - defaultValue: { - index: indicesConfig || [], - outputIndex: signalIndexConfig, - queryBar: { - query: { query: '', language: 'kuery' }, - filters: [], - saved_id: null, - }, - useIndicesConfig: 'true', - }, + defaultValue: myStepData, options: { stripEmptyFields: false }, }); const onSubmit = useCallback(async () => { - const { isValid: newIsValid, data } = await form.submit(); - if (newIsValid) { - setStepData(RuleStep.defineRule, data, newIsValid); + const { isValid, data } = await form.submit(); + if (isValid) { + setStepData(RuleStep.defineRule, data, isValid); + setMyStepData({ ...data, isNew: false } as DefineStepRule); } }, [form]); @@ -60,7 +64,13 @@ export const StepDefineRule = memo(({ isLoading, setStepData }) = } }, [initializeOutputIndex, signalIndexConfig, form]); - return ( + return isEditView && myStepData != null ? ( + + ) : ( <> (({ isLoading, setStepData }) = } else if ( indexField != null && useIndicesConfig === 'false' && - !isEqual(indexField.value, []) + isEqual(indexField.value, indicesConfig) ) { indexField.setValue([]); setIndices([]); @@ -150,7 +160,7 @@ export const StepDefineRule = memo(({ isLoading, setStepData }) = - {CreateRuleI18n.CONTINUE} + {myStepData.isNew ? CreateRuleI18n.CONTINUE : CreateRuleI18n.UPDATE} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx index 58a9e57b32ce60..9f1644e73bf0b9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx @@ -9,18 +9,18 @@ import { EuiText } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React from 'react'; -import { - FormSchema, - FIELD_TYPES, - ValidationFunc, -} from '../../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; -import { fieldValidators } from '../../../../../../../../../../src/plugins/es_ui_shared/static/forms/helpers'; -import { ERROR_CODE } from '../../../../../../../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; import { esKuery } from '../../../../../../../../../../src/plugins/data/public'; import * as CreateRuleI18n from '../../translations'; import { FieldValueQueryBar } from '../query_bar'; +import { + ERROR_CODE, + FIELD_TYPES, + fieldValidators, + FormSchema, + ValidationFunc, +} from '../shared_imports'; import { CUSTOM_QUERY_REQUIRED, INVALID_CUSTOM_QUERY } from './translations'; const { emptyField } = fieldValidators; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/index.tsx index 10b95ac6c87421..bd4d5aa4f8ca15 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/index.tsx @@ -5,21 +5,25 @@ */ import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; -import React, { memo, useCallback } from 'react'; +import React, { memo, useCallback, useState } from 'react'; -import { RuleStep, RuleStepProps } from '../../types'; +import { RuleStep, RuleStepProps, ScheduleStepRule } from '../../types'; +import { StepRuleDescription } from '../description_step'; import { ScheduleItem } from '../schedule_item_form'; import { Form, UseField, useForm } from '../shared_imports'; import { schema } from './schema'; import * as I18n from './translations'; -export const StepScheduleRule = memo(({ isLoading, setStepData }) => { +export const StepScheduleRule = memo(({ isEditView, isLoading, setStepData }) => { + const [myStepData, setMyStepData] = useState({ + enabled: true, + interval: '5m', + isNew: true, + from: '0m', + }); const { form } = useForm({ schema, - defaultValue: { - interval: '5m', - from: '0m', - }, + defaultValue: myStepData, options: { stripEmptyFields: false }, }); @@ -28,12 +32,15 @@ export const StepScheduleRule = memo(({ isLoading, setStepData }) const { isValid: newIsValid, data } = await form.submit(); if (newIsValid) { setStepData(RuleStep.scheduleRule, { ...data, enabled }, newIsValid); + setMyStepData({ ...data, isNew: false } as ScheduleStepRule); } }, [form] ); - return ( + return isEditView && myStepData != null ? ( + + ) : ( <> { [RuleStep.aboutRule]: { isValid: false, data: {} }, [RuleStep.scheduleRule]: { isValid: false, data: {} }, }); + const [isStepRuleInEditView, setIsStepRuleInEditView] = useState>({ + [RuleStep.defineRule]: false, + [RuleStep.aboutRule]: false, + [RuleStep.scheduleRule]: false, + }); const [{ isLoading, isSaved }, setRule] = usePersistRule(); const setStepData = (step: RuleStep, data: unknown, isValid: boolean) => { - stepsData.current[step] = { data, isValid }; + stepsData.current[step] = { ...stepsData.current[step], data, isValid }; if (isValid) { const stepRuleIdx = stepsRuleOrder.findIndex(item => step === item); if ([0, 1].includes(stepRuleIdx)) { - openCloseAccordion(step); - openCloseAccordion(stepsRuleOrder[stepRuleIdx + 1]); - setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]); + setIsStepRuleInEditView({ + ...isStepRuleInEditView, + [step]: true, + }); + if (openAccordionId !== stepsRuleOrder[stepRuleIdx + 1]) { + openCloseAccordion(stepsRuleOrder[stepRuleIdx + 1]); + setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]); + } } else if ( stepRuleIdx === 2 && stepsData.current[RuleStep.defineRule].isValid && @@ -106,6 +116,7 @@ export const CreateRuleComponent = React.memo(() => { const manageAccordions = useCallback( (id: RuleStep, isOpen: boolean) => { + const activeRuleIdx = stepsRuleOrder.findIndex(step => step === openAccordionId); const stepRuleIdx = stepsRuleOrder.findIndex(step => step === id); const isLatestStepsRuleValid = stepRuleIdx === 0 @@ -114,23 +125,35 @@ export const CreateRuleComponent = React.memo(() => { .filter((stepRule, index) => index < stepRuleIdx) .every(stepRule => stepsData.current[stepRule].isValid); - if ( - openAccordionId != null && - openAccordionId !== id && - !stepsData.current[openAccordionId].isValid && - isOpen - ) { - openCloseAccordion(id); - } else if (!isLatestStepsRuleValid && isOpen) { + if (stepRuleIdx < activeRuleIdx && !isOpen) { openCloseAccordion(id); - } else if (openAccordionId != null && id !== openAccordionId && isOpen) { - openCloseAccordion(openAccordionId); - setOpenAccordionId(id); - } else if (openAccordionId == null && isOpen) { - setOpenAccordionId(id); + } else if (stepRuleIdx >= activeRuleIdx) { + if ( + openAccordionId != null && + openAccordionId !== id && + !stepsData.current[openAccordionId].isValid && + !isStepRuleInEditView[id] && + isOpen + ) { + openCloseAccordion(id); + } else if (!isLatestStepsRuleValid && isOpen) { + openCloseAccordion(id); + } else if (id !== openAccordionId && isOpen) { + setOpenAccordionId(id); + } } }, - [openAccordionId] + [isStepRuleInEditView, openAccordionId] + ); + + const manageIsEditable = useCallback( + (id: RuleStep) => { + setIsStepRuleInEditView({ + ...isStepRuleInEditView, + [id]: false, + }); + }, + [isStepRuleInEditView] ); if (isSaved && stepsData.current[RuleStep.scheduleRule].isValid) { @@ -154,9 +177,24 @@ export const CreateRuleComponent = React.memo(() => { paddingSize="xs" ref={defineRuleRef} onToggle={manageAccordions.bind(null, RuleStep.defineRule)} + extraAction={ + stepsData.current[RuleStep.defineRule].isValid && ( + + {`Edit`} + + ) + } > - + @@ -168,9 +206,24 @@ export const CreateRuleComponent = React.memo(() => { paddingSize="xs" ref={aboutRuleRef} onToggle={manageAccordions.bind(null, RuleStep.aboutRule)} + extraAction={ + stepsData.current[RuleStep.aboutRule].isValid && ( + + {`Edit`} + + ) + } > - + @@ -182,9 +235,24 @@ export const CreateRuleComponent = React.memo(() => { paddingSize="xs" ref={scheduleRuleRef} onToggle={manageAccordions.bind(null, RuleStep.scheduleRule)} + extraAction={ + stepsData.current[RuleStep.scheduleRule].isValid && ( + + {`Edit`} + + ) + } > - + diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/translations.ts index ca96566305a6b0..1ef3a435bbc305 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/translations.ts @@ -38,3 +38,7 @@ export const CONTINUE = i18n.translate( defaultMessage: 'Continue', } ); + +export const UPDATE = i18n.translate('xpack.siem.detectionEngine.createRule.updateButtonTitle', { + defaultMessage: 'Update', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/types.ts index a03f6a0b11bee7..8c395c458e59af 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/types.ts @@ -12,24 +12,46 @@ export enum RuleStep { aboutRule = 'about-rule', scheduleRule = 'schedule-rule', } +export type RuleStatusType = 'passive' | 'active' | 'valid'; export interface RuleStepData { - isValid: boolean; data: unknown; + isValid: boolean; } export interface RuleStepProps { setStepData: (step: RuleStep, data: unknown, isValid: boolean) => void; + isEditView: boolean; isLoading: boolean; } -export interface DefineStepRule { +interface StepRuleData { + isNew: boolean; +} +export interface AboutStepRule extends StepRuleData { + name: string; + description: string; + severity: string; + riskScore: number; + references: string[]; + falsePositives: string[]; + tags: string[]; +} + +export interface DefineStepRule extends StepRuleData { outputIndex: string; useIndicesConfig: string; index: string[]; queryBar: FieldValueQueryBar; } +export interface ScheduleStepRule extends StepRuleData { + enabled: boolean; + interval: string; + from: string; + to?: string; +} + export interface DefineStepRuleJson { output_index: string; index: string[]; @@ -39,16 +61,6 @@ export interface DefineStepRuleJson { language: string; } -export interface AboutStepRule { - name: string; - description: string; - severity: string; - riskScore: number; - references: string[]; - falsePositives: string[]; - tags: string[]; -} - export interface AboutStepRuleJson { name: string; description: string; @@ -59,12 +71,6 @@ export interface AboutStepRuleJson { tags: string[]; } -export interface ScheduleStepRule { - enabled: boolean; - interval: string; - from: string; - to?: string; -} export type ScheduleStepRuleJson = ScheduleStepRule; export type FormatRuleType = 'query' | 'saved_query'; From 85d438cb0371e0e58e918ebea3f0c6ba147777d0 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Thu, 28 Nov 2019 08:18:11 +0100 Subject: [PATCH 10/52] [ML] Re-activate after method in transform test (#51815) --- .../functional/apps/transform/creation_saved_search.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/transform/creation_saved_search.ts b/x-pack/test/functional/apps/transform/creation_saved_search.ts index 8a69700bee578c..4528a2c84d9de1 100644 --- a/x-pack/test/functional/apps/transform/creation_saved_search.ts +++ b/x-pack/test/functional/apps/transform/creation_saved_search.ts @@ -23,10 +23,10 @@ export default function({ getService }: FtrProviderContext) { await esArchiver.load('ml/farequote'); }); - // after(async () => { - // await esArchiver.unload('ml/farequote'); - // await transform.api.cleanTransformIndices(); - // }); + after(async () => { + await esArchiver.unload('ml/farequote'); + await transform.api.cleanTransformIndices(); + }); const testDataList = [ { From bbd517b3cab4764e2ec3bcbe58da5c52a1164753 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Thu, 28 Nov 2019 11:22:29 +0200 Subject: [PATCH 11/52] =?UTF-8?q?Move=20saved=20queries=20service=20+=20la?= =?UTF-8?q?nguage=20switcher=20=E2=87=92=20NP=20(#51812)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move saved queries service + language switcher to NP * test fixes * test fix * fix ts * mock search service --- src/legacy/core_plugins/data/index.ts | 2 +- src/legacy/core_plugins/data/public/index.ts | 7 ++- src/legacy/core_plugins/data/public/plugin.ts | 6 +-- .../components/query_string_input.test.tsx | 2 +- .../components/query_string_input.tsx | 2 +- .../core_plugins/data/public/search/index.ts | 1 - .../save_query_form.tsx | 7 ++- .../saved_query_list_item.tsx | 2 +- .../saved_query_management_component.tsx | 3 +- .../search_bar/components/search_bar.test.tsx | 1 + .../search_bar/components/search_bar.tsx | 8 +-- .../data/public/search/search_bar/index.tsx | 19 ------- .../kibana/public/dashboard/application.ts | 2 +- .../kibana/public/dashboard/dashboard_app.tsx | 4 +- .../dashboard/dashboard_app_controller.tsx | 4 +- .../kibana/public/dashboard/plugin.ts | 2 +- .../public/discover/angular/discover.js | 5 +- .../kibana/public/discover/kibana_services.ts | 4 +- .../kibana/public/visualize/editor/editor.js | 5 +- .../public/visualize/kibana_services.ts | 6 +-- .../new_platform/new_platform.karma_mock.js | 8 +++ src/plugins/data/public/plugin.ts | 2 +- src/plugins/data/public/query/index.tsx | 3 +- src/plugins/data/public/query/mocks.ts | 1 + .../data/public/query/query_service.ts | 9 ++-- .../data/public/query/saved_query/index.ts} | 29 +--------- .../saved_query}/saved_query_service.test.ts | 3 +- .../query/saved_query}/saved_query_service.ts | 21 +------- .../data/public/query/saved_query/types.ts | 53 +++++++++++++++++++ src/plugins/data/public/ui/index.ts | 2 + .../language_switcher.test.tsx.snap | 0 .../language_switcher.test.tsx | 2 +- .../query_string_input}/language_switcher.tsx | 2 +- .../maps/public/angular/map_controller.js | 3 +- .../public/components/query_bar/index.tsx | 2 +- .../public/components/search_bar/index.tsx | 3 +- .../components/timeline/query_bar/index.tsx | 10 ++-- .../components/query_bar/index.tsx | 4 +- .../utils/saved_query_services/index.tsx | 2 +- 39 files changed, 131 insertions(+), 120 deletions(-) rename src/{legacy/core_plugins/data/public/search/search_service.ts => plugins/data/public/query/saved_query/index.ts} (57%) rename src/{legacy/core_plugins/data/public/search/search_bar/lib => plugins/data/public/query/saved_query}/saved_query_service.test.ts (98%) rename src/{legacy/core_plugins/data/public/search/search_bar/lib => plugins/data/public/query/saved_query}/saved_query_service.ts (88%) create mode 100644 src/plugins/data/public/query/saved_query/types.ts rename src/{legacy/core_plugins/data/public/query/query_bar/components => plugins/data/public/ui/query_string_input}/__snapshots__/language_switcher.test.tsx.snap (100%) rename src/{legacy/core_plugins/data/public/query/query_bar/components => plugins/data/public/ui/query_string_input}/language_switcher.test.tsx (96%) rename src/{legacy/core_plugins/data/public/query/query_bar/components => plugins/data/public/ui/query_string_input}/language_switcher.tsx (98%) diff --git a/src/legacy/core_plugins/data/index.ts b/src/legacy/core_plugins/data/index.ts index 71f2fa5ffec7cf..c91500cd545d4b 100644 --- a/src/legacy/core_plugins/data/index.ts +++ b/src/legacy/core_plugins/data/index.ts @@ -20,7 +20,7 @@ import { resolve } from 'path'; import { Legacy } from '../../../../kibana'; import { mappings } from './mappings'; -import { SavedQuery } from './public'; +import { SavedQuery } from '../../../plugins/data/public'; // eslint-disable-next-line import/no-default-export export default function DataPlugin(kibana: any) { diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 184084e3cc3e65..833d8c248f46aa 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -38,7 +38,12 @@ export { StaticIndexPattern, } from './index_patterns'; export { QueryStringInput } from './query'; -export { SearchBar, SearchBarProps, SavedQueryAttributes, SavedQuery } from './search'; +export { SearchBar, SearchBarProps } from './search'; +export { + SavedQueryAttributes, + SavedQuery, + SavedQueryTimeFilter, +} from '../../../../plugins/data/public'; /** @public static code */ export * from '../common'; diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index da24576655d2b3..6cce91a5a25b5e 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -18,7 +18,7 @@ */ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { SearchService, SearchStart, createSearchBar, StatetfulSearchBarProps } from './search'; +import { createSearchBar, StatetfulSearchBarProps } from './search'; import { IndexPatternsService, IndexPatternsSetup, IndexPatternsStart } from './index_patterns'; import { Storage, IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; @@ -51,7 +51,6 @@ export interface DataSetup { */ export interface DataStart { indexPatterns: IndexPatternsStart; - search: SearchStart; ui: { SearchBar: React.ComponentType; }; @@ -71,7 +70,6 @@ export interface DataStart { export class DataPlugin implements Plugin { private readonly indexPatterns: IndexPatternsService = new IndexPatternsService(); - private readonly search: SearchService = new SearchService(); private setupApi!: DataSetup; private storage!: IStorageWrapper; @@ -119,7 +117,6 @@ export class DataPlugin implements Plugin { return { FilterBar: () =>
    , + createSavedQueryService: () => {}, }; }); diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx index 6a1ef77a566538..da08165289afc2 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx @@ -26,11 +26,9 @@ import { get, isEqual } from 'lodash'; import { IndexPattern } from '../../../../../data/public'; import { QueryBarTopRow } from '../../../query'; -import { SavedQuery, SavedQueryAttributes } from '../index'; import { SavedQueryMeta, SaveQueryForm } from './saved_query_management/save_query_form'; import { SavedQueryManagementComponent } from './saved_query_management/saved_query_management_component'; -import { SavedQueryService } from '../lib/saved_query_service'; -import { createSavedQueryService } from '../lib/saved_query_service'; + import { withKibana, KibanaReactContextValue, @@ -42,6 +40,10 @@ import { esFilters, TimeHistoryContract, FilterBar, + SavedQueryService, + createSavedQueryService, + SavedQuery, + SavedQueryAttributes, } from '../../../../../../../plugins/data/public'; interface SearchBarInjectedDeps { diff --git a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx index f369bf997c1a91..faf6e24aa6ed5d 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx @@ -17,23 +17,4 @@ * under the License. */ -import { RefreshInterval, TimeRange, Query, esFilters } from 'src/plugins/data/public'; - export * from './components'; - -export type SavedQueryTimeFilter = TimeRange & { - refreshInterval: RefreshInterval; -}; - -export interface SavedQuery { - id: string; - attributes: SavedQueryAttributes; -} - -export interface SavedQueryAttributes { - title: string; - description: string; - query: Query; - filters?: esFilters.Filter[]; - timefilter?: SavedQueryTimeFilter; -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index 9c50adeeefccbd..f98a4ca53f4672 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -67,7 +67,7 @@ export interface RenderDeps { uiSettings: UiSettingsClientContract; chrome: ChromeStart; addBasePath: (path: string) => string; - savedQueryService: DataStart['search']['services']['savedQueryService']; + savedQueryService: NpDataStart['query']['savedQueries']; embeddables: IEmbeddableStart; localStorage: Storage; share: SharePluginStart; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 0ce8f2ef59fc0c..26b86204b03dbc 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { StaticIndexPattern, SavedQuery } from 'plugins/data'; +import { StaticIndexPattern } from 'plugins/data'; import moment from 'moment'; import { Subscription } from 'rxjs'; @@ -31,7 +31,7 @@ import { import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; -import { TimeRange, Query, esFilters } from '../../../../../../src/plugins/data/public'; +import { TimeRange, Query, esFilters, SavedQuery } from '../../../../../../src/plugins/data/public'; import { DashboardAppController } from './dashboard_app_controller'; import { RenderDeps } from './application'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 1a0e13417d1e1f..dcd25033e9d151 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -38,8 +38,8 @@ import { SavedObjectFinder, unhashUrl, } from './legacy_imports'; -import { FilterStateManager, IndexPattern, SavedQuery } from '../../../data/public'; -import { Query } from '../../../../../plugins/data/public'; +import { FilterStateManager, IndexPattern } from '../../../data/public'; +import { Query, SavedQuery } from '../../../../../plugins/data/public'; import './dashboard_empty_screen_directive'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index 609bd717f3c485..cb0980c914983f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -104,7 +104,7 @@ export class DashboardPlugin implements Plugin { chrome: contextCore.chrome, addBasePath: contextCore.http.basePath.prepend, uiSettings: contextCore.uiSettings, - savedQueryService: dataStart.search.services.savedQueryService, + savedQueryService: npDataStart.query.savedQueries, embeddables, dashboardCapabilities: contextCore.application.capabilities.dashboard, localStorage: new Storage(localStorage), diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index ba74ea069c4ab7..0e92d048a65a98 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -68,16 +68,17 @@ const { share, StateProvider, timefilter, + npData, toastNotifications, uiModules, uiRoutes, } = getServices(); import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; -import { start as data } from '../../../../data/public/legacy'; import { generateFilters } from '../../../../../../plugins/data/public'; +import { start as data } from '../../../../data/public/legacy'; -const { savedQueryService } = data.search.services; +const savedQueryService = npData.query.savedQueries; const fetchStatuses = { UNINITIALIZED: 'uninitialized', diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 5a10e02ba8131f..822bf69e52e0b7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -34,7 +34,6 @@ import { StateProvider } from 'ui/state_management/state'; import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { timefilter } from 'ui/timefilter'; // @ts-ignore import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; import { wrapInI18nContext } from 'ui/i18n'; @@ -58,7 +57,9 @@ const services = { uiSettings: npStart.core.uiSettings, uiActions: npStart.plugins.uiActions, embeddable: npStart.plugins.embeddable, + npData: npStart.plugins.data, share: npStart.plugins.share, + timefilter: npStart.plugins.data.query.timefilter.timefilter, // legacy docTitle, docViewsRegistry, @@ -70,7 +71,6 @@ const services = { SavedObjectProvider, SearchSource, StateProvider, - timefilter, uiModules, uiRoutes, wrapInI18nContext, diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 6840c1386bee23..5410289bfc2d73 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -54,19 +54,20 @@ const { capabilities, chrome, chromeLegacy, + npData, data, docTitle, FilterBarQueryFilterProvider, getBasePath, toastNotifications, - timefilter, uiModules, uiRoutes, visualizations, share, } = getServices(); -const { savedQueryService } = data.search.services; +const savedQueryService = npData.query.savedQueries; +const { timefilter } = npData.query.timefilter; uiRoutes .when(VisualizeConstants.CREATE_PATH, { diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index e2201cdca9a576..61b1cde0dcaf9e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -36,7 +36,6 @@ import { wrapInI18nContext } from 'ui/i18n'; // @ts-ignore import { uiModules } from 'ui/modules'; import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { timefilter } from 'ui/timefilter'; // Saved objects import { SavedObjectsClientProvider } from 'ui/saved_objects'; @@ -46,8 +45,8 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; -import { start as data } from '../../../data/public/legacy'; import { start as embeddables } from '../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; +import { start as data } from '../../../data/public/legacy'; const services = { // new platform @@ -63,6 +62,7 @@ const services = { core: npStart.core, share: npStart.plugins.share, + npData: npStart.plugins.data, data, embeddables, visualizations, @@ -78,7 +78,7 @@ const services = { SavedObjectProvider, SavedObjectRegistryProvider, SavedObjectsClientProvider, - timefilter, + timefilter: npStart.plugins.data.query.timefilter.timefilter, uiModules, uiRoutes, wrapInI18nContext, diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index ff89ef69d53cad..e816b1858f21e8 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -80,6 +80,14 @@ export const npSetup = { timefilter: sinon.fake(), history: sinon.fake(), }, + savedQueries: { + saveQuery: sinon.fake(), + getAllSavedQueries: sinon.fake(), + findSavedQueries: sinon.fake(), + getSavedQuery: sinon.fake(), + deleteSavedQuery: sinon.fake(), + getSavedQueryCount: sinon.fake(), + } }, fieldFormats: getFieldFormatsRegistry(mockUiSettings), }, diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index d8c45b6786c0cf..35d8edc4884671 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -59,7 +59,7 @@ export class DataPublicPlugin implements Plugin { const startContract = { filterManager: jest.fn() as any, timefilter: timefilterServiceMock.createStartContract(), + savedQueries: jest.fn() as any, }; return startContract; diff --git a/src/plugins/data/public/query/query_service.ts b/src/plugins/data/public/query/query_service.ts index 206f8ac284ec37..ebef8b8d450500 100644 --- a/src/plugins/data/public/query/query_service.ts +++ b/src/plugins/data/public/query/query_service.ts @@ -17,10 +17,11 @@ * under the License. */ -import { UiSettingsClientContract } from 'src/core/public'; +import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { FilterManager } from './filter_manager'; import { TimefilterService, TimefilterSetup } from './timefilter'; +import { createSavedQueryService } from './saved_query/saved_query_service'; /** * Query Service @@ -29,9 +30,8 @@ import { TimefilterService, TimefilterSetup } from './timefilter'; export interface QueryServiceDependencies { storage: IStorageWrapper; - uiSettings: UiSettingsClientContract; + uiSettings: CoreStart['uiSettings']; } - export class QueryService { filterManager!: FilterManager; timefilter!: TimefilterSetup; @@ -51,10 +51,11 @@ export class QueryService { }; } - public start() { + public start(savedObjects: CoreStart['savedObjects']) { return { filterManager: this.filterManager, timefilter: this.timefilter, + savedQueries: createSavedQueryService(savedObjects.client), }; } diff --git a/src/legacy/core_plugins/data/public/search/search_service.ts b/src/plugins/data/public/query/saved_query/index.ts similarity index 57% rename from src/legacy/core_plugins/data/public/search/search_service.ts rename to src/plugins/data/public/query/saved_query/index.ts index 90ac288912f642..f9b58e137b2768 100644 --- a/src/legacy/core_plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/query/saved_query/index.ts @@ -17,30 +17,5 @@ * under the License. */ -import { SavedObjectsClientContract } from 'src/core/public'; -import { createSavedQueryService } from './search_bar/lib/saved_query_service'; - -/** - * Search Service - * @internal - */ - -export class SearchService { - public setup() { - // Service requires index patterns, which are only available in `start` - } - - public start(savedObjectsClient: SavedObjectsClientContract) { - return { - services: { - savedQueryService: createSavedQueryService(savedObjectsClient), - }, - }; - } - - public stop() {} -} - -/** @public */ - -export type SearchStart = ReturnType; +export { SavedQuery, SavedQueryAttributes, SavedQueryService, SavedQueryTimeFilter } from './types'; +export { createSavedQueryService } from './saved_query_service'; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts similarity index 98% rename from src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts rename to src/plugins/data/public/query/saved_query/saved_query_service.test.ts index 415da8a2c32cc1..ecb3fc2d606ec7 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts @@ -17,9 +17,8 @@ * under the License. */ -import { SavedQueryAttributes } from '../index'; import { createSavedQueryService } from './saved_query_service'; -import { esFilters } from '../../../../../../../plugins/data/public'; +import { esFilters, SavedQueryAttributes } from '../..'; const savedQueryAttributes: SavedQueryAttributes = { title: 'foo', diff --git a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts b/src/plugins/data/public/query/saved_query/saved_query_service.ts similarity index 88% rename from src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts rename to src/plugins/data/public/query/saved_query/saved_query_service.ts index 2668ce911c3718..434efe80ecd8cd 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.ts @@ -17,9 +17,8 @@ * under the License. */ -import { SavedObjectAttributes } from 'src/core/server'; -import { SavedObjectsClientContract } from 'src/core/public'; -import { SavedQueryAttributes, SavedQuery } from '../index'; +import { SavedObjectsClientContract, SavedObjectAttributes } from 'src/core/public'; +import { SavedQueryAttributes, SavedQuery, SavedQueryService } from './types'; type SerializedSavedQueryAttributes = SavedObjectAttributes & SavedQueryAttributes & { @@ -29,22 +28,6 @@ type SerializedSavedQueryAttributes = SavedObjectAttributes & }; }; -export interface SavedQueryService { - saveQuery: ( - attributes: SavedQueryAttributes, - config?: { overwrite: boolean } - ) => Promise; - getAllSavedQueries: () => Promise; - findSavedQueries: ( - searchText?: string, - perPage?: number, - activePage?: number - ) => Promise; - getSavedQuery: (id: string) => Promise; - deleteSavedQuery: (id: string) => Promise<{}>; - getSavedQueryCount: () => Promise; -} - export const createSavedQueryService = ( savedObjectsClient: SavedObjectsClientContract ): SavedQueryService => { diff --git a/src/plugins/data/public/query/saved_query/types.ts b/src/plugins/data/public/query/saved_query/types.ts new file mode 100644 index 00000000000000..c278c2476c2e72 --- /dev/null +++ b/src/plugins/data/public/query/saved_query/types.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { RefreshInterval, TimeRange, Query, esFilters } from '../..'; + +export type SavedQueryTimeFilter = TimeRange & { + refreshInterval: RefreshInterval; +}; + +export interface SavedQuery { + id: string; + attributes: SavedQueryAttributes; +} + +export interface SavedQueryAttributes { + title: string; + description: string; + query: Query; + filters?: esFilters.Filter[]; + timefilter?: SavedQueryTimeFilter; +} + +export interface SavedQueryService { + saveQuery: ( + attributes: SavedQueryAttributes, + config?: { overwrite: boolean } + ) => Promise; + getAllSavedQueries: () => Promise; + findSavedQueries: ( + searchText?: string, + perPage?: number, + activePage?: number + ) => Promise; + getSavedQuery: (id: string) => Promise; + deleteSavedQuery: (id: string) => Promise<{}>; + getSavedQueryCount: () => Promise; +} diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts index 607f690d41c67b..6fb8e260dd7205 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -21,3 +21,5 @@ export { SuggestionsComponent } from './typeahead/suggestions_component'; export { IndexPatternSelect } from './index_pattern_select'; export { FilterBar } from './filter_bar'; export { applyFiltersPopover } from './apply_filters'; +// temp export +export { QueryLanguageSwitcher } from './query_string_input/language_switcher'; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/language_switcher.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/language_switcher.test.tsx.snap rename to src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.test.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx similarity index 96% rename from src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.test.tsx rename to src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx index ab210071870ca4..e3ec5212abfd2c 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { QueryLanguageSwitcher } from './language_switcher'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { coreMock } from '../../../../../core/public/mocks'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; const startMock = coreMock.createStart(); diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx similarity index 98% rename from src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.tsx rename to src/plugins/data/public/ui/query_string_input/language_switcher.tsx index 31b0e375eaac69..d86a8a970a8e70 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx @@ -31,7 +31,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState } from 'react'; -import { useKibana } from '../../../../../../../plugins/kibana_react/public'; +import { useKibana } from '../../../../kibana_react/public'; interface Props { language: string; diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js index b9354dd0a0dddd..810dc263f8b78a 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js +++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js @@ -53,11 +53,10 @@ import { MAP_SAVED_OBJECT_TYPE, MAP_APP_PATH } from '../../common/constants'; -import { start as data } from '../../../../../../src/legacy/core_plugins/data/public/legacy'; import { npStart } from 'ui/new_platform'; import { esFilters } from '../../../../../../src/plugins/data/public'; -const { savedQueryService } = data.search.services; +const savedQueryService = npStart.plugins.data.query.savedQueries; const REACT_ANCHOR_DOM_ELEMENT_ID = 'react-maps-root'; diff --git a/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx index 3f460560b79b58..591fe6a73359da 100644 --- a/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx @@ -15,8 +15,8 @@ import { Query, TimeHistory, TimeRange, + SavedQueryTimeFilter, } from '../../../../../../../src/plugins/data/public'; -import { SavedQueryTimeFilter } from '../../../../../../../src/legacy/core_plugins/data/public/search'; import { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; export interface QueryBarComponentProps { diff --git a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx index 33fb2b9239a6a4..710c1e230fabab 100644 --- a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx @@ -38,11 +38,10 @@ import { TimeRange, Query, esFilters } from '../../../../../../../src/plugins/da const { ui: { SearchBar }, - search, } = data; export const siemFilterManager = npStart.plugins.data.query.filterManager; -export const savedQueryService = search.services.savedQueryService; +export const savedQueryService = npStart.plugins.data.query.savedQueries; interface SiemSearchBarRedux { end: number; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx index f24ee3155c9242..a49ec1b7583670 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx @@ -9,9 +9,13 @@ import React, { memo, useCallback, useState, useEffect } from 'react'; import { StaticIndexPattern } from 'ui/index_patterns'; import { Subscription } from 'rxjs'; -import { SavedQueryTimeFilter } from '../../../../../../../../src/legacy/core_plugins/data/public/search'; -import { SavedQuery } from '../../../../../../../../src/legacy/core_plugins/data/public'; -import { Query, esFilters, FilterManager } from '../../../../../../../../src/plugins/data/public'; +import { + Query, + esFilters, + FilterManager, + SavedQuery, + SavedQueryTimeFilter, +} from '../../../../../../../../src/plugins/data/public'; import { BrowserFields } from '../../../containers/source'; import { convertKueryToElasticSearchQuery } from '../../../lib/keury'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx index 8db9d3b44e3f5f..e3180c150b2390 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx @@ -11,12 +11,12 @@ import { StaticIndexPattern } from 'ui/index_patterns'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; -import { SavedQueryTimeFilter } from '../../../../../../../../../../src/legacy/core_plugins/data/public/search'; -import { SavedQuery } from '../../../../../../../../../../src/legacy/core_plugins/data/public'; import { esFilters, Query, FilterManager, + SavedQuery, + SavedQueryTimeFilter, } from '../../../../../../../../../../src/plugins/data/public'; import { QueryBar } from '../../../../../components/query_bar'; diff --git a/x-pack/legacy/plugins/siem/public/utils/saved_query_services/index.tsx b/x-pack/legacy/plugins/siem/public/utils/saved_query_services/index.tsx index f1e4cf34113980..cda6882fe17143 100644 --- a/x-pack/legacy/plugins/siem/public/utils/saved_query_services/index.tsx +++ b/x-pack/legacy/plugins/siem/public/utils/saved_query_services/index.tsx @@ -8,7 +8,7 @@ import { useState, useEffect } from 'react'; import { SavedQueryService, createSavedQueryService, -} from '../../../../../../../src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service'; +} from '../../../../../../../src/plugins/data/public'; import { useKibanaCore } from '../../lib/compose/kibana_core'; From 066613e2a6b839ad893aa61fb3624187042b5bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Thu, 28 Nov 2019 09:23:56 +0000 Subject: [PATCH 12/52] Allow routes to define some payload config values (#50783) * Allow routes to define some payload config values * Documentation typo * Move hapi `payload` config under `body` + additional validations * Update API docs * Amend explanation in API docs * Add stream and buffer types to @kbn/config-schema * Fixes based on PR feedback: - Add 'patch' and 'options' to valid RouteMethod - Add tests for all the new flags - Allow `stream` and `buffer` schema in the body validations (findings from tests) * API documentation update * Fix type definitions * Fix the NITs in the PR comments + better typing inheritance * API docs update * Fix APM-legacy wrapper's types * Fix KibanaRequest.from type exposure of hapi in API docs * Move RouterRoute interface back to private + Expose some public docs * Update @kbn/config-schema docs --- .../kibana-plugin-server.basepath.get.md | 2 +- .../server/kibana-plugin-server.basepath.md | 4 +- .../kibana-plugin-server.basepath.set.md | 2 +- .../kibana-plugin-server.irouter.delete.md | 2 +- .../kibana-plugin-server.irouter.get.md | 2 +- .../server/kibana-plugin-server.irouter.md | 9 +- .../kibana-plugin-server.irouter.patch.md | 13 ++ .../kibana-plugin-server.irouter.post.md | 2 +- .../kibana-plugin-server.irouter.put.md | 2 +- .../kibana-plugin-server.kibanarequest.md | 4 +- ...ibana-plugin-server.kibanarequest.route.md | 2 +- ...kibana-plugin-server.kibanarequestroute.md | 6 +- ...plugin-server.kibanarequestroute.method.md | 2 +- ...lugin-server.kibanarequestroute.options.md | 2 +- ...plugin-server.kibanarequestrouteoptions.md | 13 ++ .../core/server/kibana-plugin-server.md | 7 +- .../kibana-plugin-server.requesthandler.md | 2 +- .../kibana-plugin-server.routeconfig.md | 4 +- ...ibana-plugin-server.routeconfig.options.md | 2 +- ...a-plugin-server.routeconfigoptions.body.md | 13 ++ ...kibana-plugin-server.routeconfigoptions.md | 3 +- ...n-server.routeconfigoptionsbody.accepts.md | 15 ++ ...-server.routeconfigoptionsbody.maxbytes.md | 15 ++ ...na-plugin-server.routeconfigoptionsbody.md | 23 +++ ...in-server.routeconfigoptionsbody.output.md | 15 ++ ...gin-server.routeconfigoptionsbody.parse.md | 15 ++ .../kibana-plugin-server.routecontenttype.md | 13 ++ .../kibana-plugin-server.routemethod.md | 2 +- .../kibana-plugin-server.routeregistrar.md | 4 +- .../kibana-plugin-server.routeschemas.body.md | 11 ++ .../kibana-plugin-server.routeschemas.md | 22 +++ ...ibana-plugin-server.routeschemas.params.md | 11 ++ ...kibana-plugin-server.routeschemas.query.md | 11 ++ .../kibana-plugin-server.validbodyoutput.md | 13 ++ packages/kbn-config-schema/README.md | 32 ++++ packages/kbn-config-schema/src/index.ts | 13 ++ .../kbn-config-schema/src/internals/index.ts | 28 ++++ .../__snapshots__/buffer_type.test.ts.snap | 11 ++ .../__snapshots__/stream_type.test.ts.snap | 11 ++ .../src/types/buffer_type.test.ts | 57 +++++++ .../src/types/buffer_type.ts | 34 ++++ packages/kbn-config-schema/src/types/index.ts | 2 + .../src/types/stream_type.test.ts | 71 ++++++++ .../src/types/stream_type.ts | 35 ++++ packages/kbn-config-schema/types/joi.d.ts | 1 + src/core/server/http/http_server.mocks.ts | 6 +- src/core/server/http/http_server.test.ts | 152 ++++++++++++++++++ src/core/server/http/http_server.ts | 9 +- src/core/server/http/http_service.mock.ts | 1 + src/core/server/http/index.ts | 7 +- src/core/server/http/router/error_wrapper.ts | 7 +- src/core/server/http/router/index.ts | 11 +- src/core/server/http/router/request.ts | 69 +++++--- src/core/server/http/router/route.ts | 97 ++++++++++- src/core/server/http/router/router.test.ts | 42 +++++ src/core/server/http/router/router.ts | 138 ++++++++++++---- src/core/server/index.ts | 7 +- src/core/server/server.api.md | 67 +++++--- .../apm/server/routes/create_api/index.ts | 4 +- .../server/routes/authentication/saml.test.ts | 4 +- 60 files changed, 1064 insertions(+), 120 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.irouter.patch.md create mode 100644 docs/development/core/server/kibana-plugin-server.kibanarequestrouteoptions.md create mode 100644 docs/development/core/server/kibana-plugin-server.routeconfigoptions.body.md create mode 100644 docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.accepts.md create mode 100644 docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.maxbytes.md create mode 100644 docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.md create mode 100644 docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.output.md create mode 100644 docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.parse.md create mode 100644 docs/development/core/server/kibana-plugin-server.routecontenttype.md create mode 100644 docs/development/core/server/kibana-plugin-server.routeschemas.body.md create mode 100644 docs/development/core/server/kibana-plugin-server.routeschemas.md create mode 100644 docs/development/core/server/kibana-plugin-server.routeschemas.params.md create mode 100644 docs/development/core/server/kibana-plugin-server.routeschemas.query.md create mode 100644 docs/development/core/server/kibana-plugin-server.validbodyoutput.md create mode 100644 packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap create mode 100644 packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap create mode 100644 packages/kbn-config-schema/src/types/buffer_type.test.ts create mode 100644 packages/kbn-config-schema/src/types/buffer_type.ts create mode 100644 packages/kbn-config-schema/src/types/stream_type.test.ts create mode 100644 packages/kbn-config-schema/src/types/stream_type.ts diff --git a/docs/development/core/server/kibana-plugin-server.basepath.get.md b/docs/development/core/server/kibana-plugin-server.basepath.get.md index 2b3b6c899e8ded..6ef7022f10e624 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.get.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.get.md @@ -9,5 +9,5 @@ returns `basePath` value, specific for an incoming request. Signature: ```typescript -get: (request: KibanaRequest | LegacyRequest) => string; +get: (request: KibanaRequest | LegacyRequest) => string; ``` diff --git a/docs/development/core/server/kibana-plugin-server.basepath.md b/docs/development/core/server/kibana-plugin-server.basepath.md index 478e29696966cd..77f50abc60369e 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.md @@ -16,11 +16,11 @@ export declare class BasePath | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [get](./kibana-plugin-server.basepath.get.md) | | (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest) => string | returns basePath value, specific for an incoming request. | +| [get](./kibana-plugin-server.basepath.get.md) | | (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest) => string | returns basePath value, specific for an incoming request. | | [prepend](./kibana-plugin-server.basepath.prepend.md) | | (path: string) => string | Prepends path with the basePath. | | [remove](./kibana-plugin-server.basepath.remove.md) | | (path: string) => string | Removes the prepended basePath from the path. | | [serverBasePath](./kibana-plugin-server.basepath.serverbasepath.md) | | string | returns the server's basePathSee [BasePath.get](./kibana-plugin-server.basepath.get.md) for getting the basePath value for a specific request | -| [set](./kibana-plugin-server.basepath.set.md) | | (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest, requestSpecificBasePath: string) => void | sets basePath value, specific for an incoming request. | +| [set](./kibana-plugin-server.basepath.set.md) | | (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest, requestSpecificBasePath: string) => void | sets basePath value, specific for an incoming request. | ## Remarks diff --git a/docs/development/core/server/kibana-plugin-server.basepath.set.md b/docs/development/core/server/kibana-plugin-server.basepath.set.md index 1272a134ef5c44..56a7f644d34ccc 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.set.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.set.md @@ -9,5 +9,5 @@ sets `basePath` value, specific for an incoming request. Signature: ```typescript -set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; +set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; ``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.delete.md b/docs/development/core/server/kibana-plugin-server.irouter.delete.md index 5202e0cfd5ebb7..a479c03ecede3a 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.delete.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.delete.md @@ -9,5 +9,5 @@ Register a route handler for `DELETE` request. Signature: ```typescript -delete: RouteRegistrar; +delete: RouteRegistrar<'delete'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.get.md b/docs/development/core/server/kibana-plugin-server.irouter.get.md index 32552a49cb999a..0d52ef26f008c7 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.get.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.get.md @@ -9,5 +9,5 @@ Register a route handler for `GET` request. Signature: ```typescript -get: RouteRegistrar; +get: RouteRegistrar<'get'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.md b/docs/development/core/server/kibana-plugin-server.irouter.md index b5d3c893d745dc..73e96191e02e71 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.md @@ -16,10 +16,11 @@ export interface IRouter | Property | Type | Description | | --- | --- | --- | -| [delete](./kibana-plugin-server.irouter.delete.md) | RouteRegistrar | Register a route handler for DELETE request. | -| [get](./kibana-plugin-server.irouter.get.md) | RouteRegistrar | Register a route handler for GET request. | +| [delete](./kibana-plugin-server.irouter.delete.md) | RouteRegistrar<'delete'> | Register a route handler for DELETE request. | +| [get](./kibana-plugin-server.irouter.get.md) | RouteRegistrar<'get'> | Register a route handler for GET request. | | [handleLegacyErrors](./kibana-plugin-server.irouter.handlelegacyerrors.md) | <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B> | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. | -| [post](./kibana-plugin-server.irouter.post.md) | RouteRegistrar | Register a route handler for POST request. | -| [put](./kibana-plugin-server.irouter.put.md) | RouteRegistrar | Register a route handler for PUT request. | +| [patch](./kibana-plugin-server.irouter.patch.md) | RouteRegistrar<'patch'> | Register a route handler for PATCH request. | +| [post](./kibana-plugin-server.irouter.post.md) | RouteRegistrar<'post'> | Register a route handler for POST request. | +| [put](./kibana-plugin-server.irouter.put.md) | RouteRegistrar<'put'> | Register a route handler for PUT request. | | [routerPath](./kibana-plugin-server.irouter.routerpath.md) | string | Resulted path | diff --git a/docs/development/core/server/kibana-plugin-server.irouter.patch.md b/docs/development/core/server/kibana-plugin-server.irouter.patch.md new file mode 100644 index 00000000000000..460f1b9d23640f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.irouter.patch.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IRouter](./kibana-plugin-server.irouter.md) > [patch](./kibana-plugin-server.irouter.patch.md) + +## IRouter.patch property + +Register a route handler for `PATCH` request. + +Signature: + +```typescript +patch: RouteRegistrar<'patch'>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.post.md b/docs/development/core/server/kibana-plugin-server.irouter.post.md index cd655c9ce0dc8b..a2ac27ebc731ae 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.post.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.post.md @@ -9,5 +9,5 @@ Register a route handler for `POST` request. Signature: ```typescript -post: RouteRegistrar; +post: RouteRegistrar<'post'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.put.md b/docs/development/core/server/kibana-plugin-server.irouter.put.md index e553d4b79dd2b3..219c5d8805661f 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.put.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.put.md @@ -9,5 +9,5 @@ Register a route handler for `PUT` request. Signature: ```typescript -put: RouteRegistrar; +put: RouteRegistrar<'put'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.md index b2460cd58f7a72..bc805fdc0b86f1 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.md @@ -9,7 +9,7 @@ Kibana specific abstraction for an incoming request. Signature: ```typescript -export declare class KibanaRequest +export declare class KibanaRequest ``` ## Constructors @@ -26,7 +26,7 @@ export declare class KibanaRequestHeaders | Readonly copy of incoming request headers. | | [params](./kibana-plugin-server.kibanarequest.params.md) | | Params | | | [query](./kibana-plugin-server.kibanarequest.query.md) | | Query | | -| [route](./kibana-plugin-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute> | matched route details | +| [route](./kibana-plugin-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute<Method>> | matched route details | | [socket](./kibana-plugin-server.kibanarequest.socket.md) | | IKibanaSocket | | | [url](./kibana-plugin-server.kibanarequest.url.md) | | Url | a WHATWG URL standard object. | diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md index 88954eedf4cfb5..1905070a99068b 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md @@ -9,5 +9,5 @@ matched route details Signature: ```typescript -readonly route: RecursiveReadonly; +readonly route: RecursiveReadonly>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md index b92fe45d19edb8..29836394582007 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md @@ -9,14 +9,14 @@ Request specific route information exposed to a handler. Signature: ```typescript -export interface KibanaRequestRoute +export interface KibanaRequestRoute ``` ## Properties | Property | Type | Description | | --- | --- | --- | -| [method](./kibana-plugin-server.kibanarequestroute.method.md) | RouteMethod | 'patch' | 'options' | | -| [options](./kibana-plugin-server.kibanarequestroute.options.md) | Required<RouteConfigOptions> | | +| [method](./kibana-plugin-server.kibanarequestroute.method.md) | Method | | +| [options](./kibana-plugin-server.kibanarequestroute.options.md) | KibanaRequestRouteOptions<Method> | | | [path](./kibana-plugin-server.kibanarequestroute.path.md) | string | | diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md index c003b06e128e43..5775d28b1e053b 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md @@ -7,5 +7,5 @@ Signature: ```typescript -method: RouteMethod | 'patch' | 'options'; +method: Method; ``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md index 98c898449a5b1d..438263f61eb20a 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md @@ -7,5 +7,5 @@ Signature: ```typescript -options: Required; +options: KibanaRequestRouteOptions; ``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestrouteoptions.md b/docs/development/core/server/kibana-plugin-server.kibanarequestrouteoptions.md new file mode 100644 index 00000000000000..f48711ac11f927 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestrouteoptions.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) + +## KibanaRequestRouteOptions type + +Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. + +Signature: + +```typescript +export declare type KibanaRequestRouteOptions = Method extends 'get' | 'options' ? Required, 'body'>> : Required>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 38c7ad75d1db97..17c5136fdc3188 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -84,6 +84,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request | | [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | | [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | +| [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route | +| [RouteSchemas](./kibana-plugin-server.routeschemas.md) | RouteSchemas contains the schemas for validating the different parts of a request. | | [SavedObject](./kibana-plugin-server.savedobject.md) | | | [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | | [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. | @@ -133,6 +135,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Variable | Description | | --- | --- | | [kibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution. | +| [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) | The set of valid body.output | ## Type Aliases @@ -156,6 +159,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | | [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | +| [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. | | [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | | [KnownHeaders](./kibana-plugin-server.knownheaders.md) | Set of well-known HTTP headers. | | [LifecycleResponseFactory](./kibana-plugin-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. | @@ -176,8 +180,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ResponseError](./kibana-plugin-server.responseerror.md) | Error message and optional data send to the client in case of error. | | [ResponseErrorAttributes](./kibana-plugin-server.responseerrorattributes.md) | Additional data to provide error details. | | [ResponseHeaders](./kibana-plugin-server.responseheaders.md) | Http response headers to set. | +| [RouteContentType](./kibana-plugin-server.routecontenttype.md) | The set of supported parseable Content-Types | | [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. | -| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Handler to declare a route. | +| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Route handler common definition | | [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | diff --git a/docs/development/core/server/kibana-plugin-server.requesthandler.md b/docs/development/core/server/kibana-plugin-server.requesthandler.md index 035d16c9fca3c0..79abfd4293e9fe 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandler.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandler.md @@ -9,7 +9,7 @@ A function executed when route path matched requested resource path. Request han Signature: ```typescript -export declare type RequestHandler

    = (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; +export declare type RequestHandler

    | Type, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf, Method>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; ``` ## Example diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.md b/docs/development/core/server/kibana-plugin-server.routeconfig.md index 769d0dda426447..1970b23c7ec099 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfig.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.md @@ -9,14 +9,14 @@ Route specific configuration. Signature: ```typescript -export interface RouteConfig

    +export interface RouteConfig

    | Type, Method extends RouteMethod> ``` ## Properties | Property | Type | Description | | --- | --- | --- | -| [options](./kibana-plugin-server.routeconfig.options.md) | RouteConfigOptions | Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md). | +| [options](./kibana-plugin-server.routeconfig.options.md) | RouteConfigOptions<Method> | Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md). | | [path](./kibana-plugin-server.routeconfig.path.md) | string | The endpoint \_within\_ the router path to register the route. | | [validate](./kibana-plugin-server.routeconfig.validate.md) | RouteSchemas<P, Q, B> | false | A schema created with @kbn/config-schema that every request will be validated against. | diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.options.md b/docs/development/core/server/kibana-plugin-server.routeconfig.options.md index 12ca36da6de7cb..90ad294457101f 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfig.options.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.options.md @@ -9,5 +9,5 @@ Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfig Signature: ```typescript -options?: RouteConfigOptions; +options?: RouteConfigOptions; ``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.body.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.body.md new file mode 100644 index 00000000000000..fee5528ce3378e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.body.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) > [body](./kibana-plugin-server.routeconfigoptions.body.md) + +## RouteConfigOptions.body property + +Additional body options [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md). + +Signature: + +```typescript +body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md index b4d210ac0b7110..99339db81065c1 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md @@ -9,7 +9,7 @@ Additional route options. Signature: ```typescript -export interface RouteConfigOptions +export interface RouteConfigOptions ``` ## Properties @@ -17,5 +17,6 @@ export interface RouteConfigOptions | Property | Type | Description | | --- | --- | --- | | [authRequired](./kibana-plugin-server.routeconfigoptions.authrequired.md) | boolean | A flag shows that authentication for a route: enabled when true disabled when falseEnabled by default. | +| [body](./kibana-plugin-server.routeconfigoptions.body.md) | Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody | Additional body options [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md). | | [tags](./kibana-plugin-server.routeconfigoptions.tags.md) | readonly string[] | Additional metadata tag strings to attach to the route. | diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.accepts.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.accepts.md new file mode 100644 index 00000000000000..f48c9a1d73b11e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.accepts.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [accepts](./kibana-plugin-server.routeconfigoptionsbody.accepts.md) + +## RouteConfigOptionsBody.accepts property + +A string or an array of strings with the allowed mime types for the endpoint. Use this settings to limit the set of allowed mime types. Note that allowing additional mime types not listed above will not enable them to be parsed, and if parse is true, the request will result in an error response. + +Default value: allows parsing of the following mime types: \* application/json \* application/\*+json \* application/octet-stream \* application/x-www-form-urlencoded \* multipart/form-data \* text/\* + +Signature: + +```typescript +accepts?: RouteContentType | RouteContentType[] | string | string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.maxbytes.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.maxbytes.md new file mode 100644 index 00000000000000..3d22dc07d5bae0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.maxbytes.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [maxBytes](./kibana-plugin-server.routeconfigoptionsbody.maxbytes.md) + +## RouteConfigOptionsBody.maxBytes property + +Limits the size of incoming payloads to the specified byte count. Allowing very large payloads may cause the server to run out of memory. + +Default value: The one set in the kibana.yml config file under the parameter `server.maxPayloadBytes`. + +Signature: + +```typescript +maxBytes?: number; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.md new file mode 100644 index 00000000000000..6ef04de459fcf2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) + +## RouteConfigOptionsBody interface + +Additional body options for a route + +Signature: + +```typescript +export interface RouteConfigOptionsBody +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [accepts](./kibana-plugin-server.routeconfigoptionsbody.accepts.md) | RouteContentType | RouteContentType[] | string | string[] | A string or an array of strings with the allowed mime types for the endpoint. Use this settings to limit the set of allowed mime types. Note that allowing additional mime types not listed above will not enable them to be parsed, and if parse is true, the request will result in an error response.Default value: allows parsing of the following mime types: \* application/json \* application/\*+json \* application/octet-stream \* application/x-www-form-urlencoded \* multipart/form-data \* text/\* | +| [maxBytes](./kibana-plugin-server.routeconfigoptionsbody.maxbytes.md) | number | Limits the size of incoming payloads to the specified byte count. Allowing very large payloads may cause the server to run out of memory.Default value: The one set in the kibana.yml config file under the parameter server.maxPayloadBytes. | +| [output](./kibana-plugin-server.routeconfigoptionsbody.output.md) | typeof validBodyOutput[number] | The processed payload format. The value must be one of: \* 'data' - the incoming payload is read fully into memory. If parse is true, the payload is parsed (JSON, form-decoded, multipart) based on the 'Content-Type' header. If parse is false, a raw Buffer is returned. \* 'stream' - the incoming payload is made available via a Stream.Readable interface. If the payload is 'multipart/form-data' and parse is true, field values are presented as text while files are provided as streams. File streams from a 'multipart/form-data' upload will also have a hapi property containing the filename and headers properties. Note that payload streams for multipart payloads are a synthetic interface created on top of the entire multipart content loaded into memory. To avoid loading large multipart payloads into memory, set parse to false and handle the multipart payload in the handler using a streaming parser (e.g. pez).Default value: 'data', unless no validation.body is provided in the route definition. In that case the default is 'stream' to alleviate memory pressure. | +| [parse](./kibana-plugin-server.routeconfigoptionsbody.parse.md) | boolean | 'gunzip' | Determines if the incoming payload is processed or presented raw. Available values: \* true - if the request 'Content-Type' matches the allowed mime types set by allow (for the whole payload as well as parts), the payload is converted into an object when possible. If the format is unknown, a Bad Request (400) error response is sent. Any known content encoding is decoded. \* false - the raw payload is returned unmodified. \* 'gunzip' - the raw payload is returned unmodified after any known content encoding is decoded.Default value: true, unless no validation.body is provided in the route definition. In that case the default is false to alleviate memory pressure. | + diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.output.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.output.md new file mode 100644 index 00000000000000..b84bc709df3eca --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.output.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [output](./kibana-plugin-server.routeconfigoptionsbody.output.md) + +## RouteConfigOptionsBody.output property + +The processed payload format. The value must be one of: \* 'data' - the incoming payload is read fully into memory. If parse is true, the payload is parsed (JSON, form-decoded, multipart) based on the 'Content-Type' header. If parse is false, a raw Buffer is returned. \* 'stream' - the incoming payload is made available via a Stream.Readable interface. If the payload is 'multipart/form-data' and parse is true, field values are presented as text while files are provided as streams. File streams from a 'multipart/form-data' upload will also have a hapi property containing the filename and headers properties. Note that payload streams for multipart payloads are a synthetic interface created on top of the entire multipart content loaded into memory. To avoid loading large multipart payloads into memory, set parse to false and handle the multipart payload in the handler using a streaming parser (e.g. pez). + +Default value: 'data', unless no validation.body is provided in the route definition. In that case the default is 'stream' to alleviate memory pressure. + +Signature: + +```typescript +output?: typeof validBodyOutput[number]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.parse.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.parse.md new file mode 100644 index 00000000000000..d395f67c696690 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.parse.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [parse](./kibana-plugin-server.routeconfigoptionsbody.parse.md) + +## RouteConfigOptionsBody.parse property + +Determines if the incoming payload is processed or presented raw. Available values: \* true - if the request 'Content-Type' matches the allowed mime types set by allow (for the whole payload as well as parts), the payload is converted into an object when possible. If the format is unknown, a Bad Request (400) error response is sent. Any known content encoding is decoded. \* false - the raw payload is returned unmodified. \* 'gunzip' - the raw payload is returned unmodified after any known content encoding is decoded. + +Default value: true, unless no validation.body is provided in the route definition. In that case the default is false to alleviate memory pressure. + +Signature: + +```typescript +parse?: boolean | 'gunzip'; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routecontenttype.md b/docs/development/core/server/kibana-plugin-server.routecontenttype.md new file mode 100644 index 00000000000000..010388c7b8f177 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routecontenttype.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteContentType](./kibana-plugin-server.routecontenttype.md) + +## RouteContentType type + +The set of supported parseable Content-Types + +Signature: + +```typescript +export declare type RouteContentType = 'application/json' | 'application/*+json' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/*'; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routemethod.md b/docs/development/core/server/kibana-plugin-server.routemethod.md index dd1a050708bb35..4f83344f842b3c 100644 --- a/docs/development/core/server/kibana-plugin-server.routemethod.md +++ b/docs/development/core/server/kibana-plugin-server.routemethod.md @@ -9,5 +9,5 @@ The set of common HTTP methods supported by Kibana routing. Signature: ```typescript -export declare type RouteMethod = 'get' | 'post' | 'put' | 'delete'; +export declare type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; ``` diff --git a/docs/development/core/server/kibana-plugin-server.routeregistrar.md b/docs/development/core/server/kibana-plugin-server.routeregistrar.md index 535927dc73743f..0f5f49636fdd5e 100644 --- a/docs/development/core/server/kibana-plugin-server.routeregistrar.md +++ b/docs/development/core/server/kibana-plugin-server.routeregistrar.md @@ -4,10 +4,10 @@ ## RouteRegistrar type -Handler to declare a route. +Route handler common definition Signature: ```typescript -export declare type RouteRegistrar =

    (route: RouteConfig, handler: RequestHandler) => void; +export declare type RouteRegistrar =

    | Type>(route: RouteConfig, handler: RequestHandler) => void; ``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.body.md b/docs/development/core/server/kibana-plugin-server.routeschemas.body.md new file mode 100644 index 00000000000000..78a9d25c25d9d6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.body.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [body](./kibana-plugin-server.routeschemas.body.md) + +## RouteSchemas.body property + +Signature: + +```typescript +body?: B; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.md b/docs/development/core/server/kibana-plugin-server.routeschemas.md new file mode 100644 index 00000000000000..77b980551a8ffe --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) + +## RouteSchemas interface + +RouteSchemas contains the schemas for validating the different parts of a request. + +Signature: + +```typescript +export interface RouteSchemas

    | Type> +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [body](./kibana-plugin-server.routeschemas.body.md) | B | | +| [params](./kibana-plugin-server.routeschemas.params.md) | P | | +| [query](./kibana-plugin-server.routeschemas.query.md) | Q | | + diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.params.md b/docs/development/core/server/kibana-plugin-server.routeschemas.params.md new file mode 100644 index 00000000000000..3dbf9fed94dc09 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.params.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [params](./kibana-plugin-server.routeschemas.params.md) + +## RouteSchemas.params property + +Signature: + +```typescript +params?: P; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.query.md b/docs/development/core/server/kibana-plugin-server.routeschemas.query.md new file mode 100644 index 00000000000000..5be5830cb4bc87 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.query.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [query](./kibana-plugin-server.routeschemas.query.md) + +## RouteSchemas.query property + +Signature: + +```typescript +query?: Q; +``` diff --git a/docs/development/core/server/kibana-plugin-server.validbodyoutput.md b/docs/development/core/server/kibana-plugin-server.validbodyoutput.md new file mode 100644 index 00000000000000..ea866abf887fb8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.validbodyoutput.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) + +## validBodyOutput variable + +The set of valid body.output + +Signature: + +```typescript +validBodyOutput: readonly ["data", "stream"] +``` diff --git a/packages/kbn-config-schema/README.md b/packages/kbn-config-schema/README.md index 8ba2c43b5e1fe6..fd62f1b3c03b22 100644 --- a/packages/kbn-config-schema/README.md +++ b/packages/kbn-config-schema/README.md @@ -12,6 +12,8 @@ Kibana configuration entries providing developers with a fully typed model of th - [`schema.number()`](#schemanumber) - [`schema.boolean()`](#schemaboolean) - [`schema.literal()`](#schemaliteral) + - [`schema.buffer()`](#schemabuffer) + - [`schema.stream()`](#schemastream) - [Composite types](#composite-types) - [`schema.arrayOf()`](#schemaarrayof) - [`schema.object()`](#schemaobject) @@ -173,6 +175,36 @@ const valueSchema = [ ]; ``` +#### `schema.buffer()` + +Validates input data as a NodeJS `Buffer`. + +__Output type:__ `Buffer` + +__Options:__ + * `defaultValue: TBuffer | Reference | (() => TBuffer)` - defines a default value, see [Default values](#default-values) section for more details. + * `validate: (value: TBuffer) => Buffer | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details. + +__Usage:__ +```typescript +const valueSchema = schema.buffer({ defaultValue: Buffer.from('Hi, there!') }); +``` + +#### `schema.stream()` + +Validates input data as a NodeJS `stream`. + +__Output type:__ `Stream`, `Readable` or `Writtable` + +__Options:__ + * `defaultValue: TStream | Reference | (() => TStream)` - defines a default value, see [Default values](#default-values) section for more details. + * `validate: (value: TStream) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details. + +__Usage:__ +```typescript +const valueSchema = schema.stream({ defaultValue: new Stream() }); +``` + ### Composite types #### `schema.arrayOf()` diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index 210b044421e7e7..56b3096433c247 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -18,6 +18,7 @@ */ import { Duration } from 'moment'; +import { Stream } from 'stream'; import { ByteSizeValue } from './byte_size_value'; import { ContextReference, Reference, SiblingReference } from './references'; @@ -26,6 +27,7 @@ import { ArrayOptions, ArrayType, BooleanType, + BufferType, ByteSizeOptions, ByteSizeType, ConditionalType, @@ -52,6 +54,7 @@ import { UnionType, URIOptions, URIType, + StreamType, } from './types'; export { ObjectType, TypeOf, Type }; @@ -65,6 +68,14 @@ function boolean(options?: TypeOptions): Type { return new BooleanType(options); } +function buffer(options?: TypeOptions): Type { + return new BufferType(options); +} + +function stream(options?: TypeOptions): Type { + return new StreamType(options); +} + function string(options?: StringOptions): Type { return new StringType(options); } @@ -188,6 +199,7 @@ export const schema = { any, arrayOf, boolean, + buffer, byteSize, conditional, contextRef, @@ -201,6 +213,7 @@ export const schema = { object, oneOf, recordOf, + stream, siblingRef, string, uri, diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index e5a5b446de4f5c..4d5091eaa09b13 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -29,6 +29,7 @@ import { } from 'joi'; import { isPlainObject } from 'lodash'; import { isDuration } from 'moment'; +import { Stream } from 'stream'; import { ByteSizeValue, ensureByteSizeValue } from '../byte_size_value'; import { ensureDuration } from '../duration'; @@ -89,6 +90,33 @@ export const internals = Joi.extend([ }, rules: [anyCustomRule], }, + { + name: 'binary', + + base: Joi.binary(), + coerce(value: any, state: State, options: ValidationOptions) { + // If value isn't defined, let Joi handle default value if it's defined. + if (value !== undefined && !(typeof value === 'object' && Buffer.isBuffer(value))) { + return this.createError('binary.base', { value }, state, options); + } + + return value; + }, + rules: [anyCustomRule], + }, + { + name: 'stream', + + pre(value: any, state: State, options: ValidationOptions) { + // If value isn't defined, let Joi handle default value if it's defined. + if (value instanceof Stream) { + return value as any; + } + + return this.createError('stream.base', { value }, state, options); + }, + rules: [anyCustomRule], + }, { name: 'string', diff --git a/packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap new file mode 100644 index 00000000000000..96a7ab34dac26a --- /dev/null +++ b/packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [Buffer] but got [undefined]"`; + +exports[`is required by default 1`] = `"expected value of type [Buffer] but got [undefined]"`; + +exports[`returns error when not a buffer 1`] = `"expected value of type [Buffer] but got [number]"`; + +exports[`returns error when not a buffer 2`] = `"expected value of type [Buffer] but got [Array]"`; + +exports[`returns error when not a buffer 3`] = `"expected value of type [Buffer] but got [string]"`; diff --git a/packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap new file mode 100644 index 00000000000000..e813b4f68a09e1 --- /dev/null +++ b/packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [Stream] but got [undefined]"`; + +exports[`is required by default 1`] = `"expected value of type [Buffer] but got [undefined]"`; + +exports[`returns error when not a stream 1`] = `"expected value of type [Stream] but got [number]"`; + +exports[`returns error when not a stream 2`] = `"expected value of type [Stream] but got [Array]"`; + +exports[`returns error when not a stream 3`] = `"expected value of type [Stream] but got [string]"`; diff --git a/packages/kbn-config-schema/src/types/buffer_type.test.ts b/packages/kbn-config-schema/src/types/buffer_type.test.ts new file mode 100644 index 00000000000000..63d59296aec843 --- /dev/null +++ b/packages/kbn-config-schema/src/types/buffer_type.test.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema } from '..'; + +test('returns value by default', () => { + const value = Buffer.from('Hi!'); + expect(schema.buffer().validate(value)).toStrictEqual(value); +}); + +test('is required by default', () => { + expect(() => schema.buffer().validate(undefined)).toThrowErrorMatchingSnapshot(); +}); + +test('includes namespace in failure', () => { + expect(() => + schema.buffer().validate(undefined, {}, 'foo-namespace') + ).toThrowErrorMatchingSnapshot(); +}); + +describe('#defaultValue', () => { + test('returns default when undefined', () => { + const value = Buffer.from('Hi!'); + expect(schema.buffer({ defaultValue: value }).validate(undefined)).toStrictEqual(value); + }); + + test('returns value when specified', () => { + const value = Buffer.from('Hi!'); + expect(schema.buffer({ defaultValue: Buffer.from('Bye!') }).validate(value)).toStrictEqual( + value + ); + }); +}); + +test('returns error when not a buffer', () => { + expect(() => schema.buffer().validate(123)).toThrowErrorMatchingSnapshot(); + + expect(() => schema.buffer().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); + + expect(() => schema.buffer().validate('abc')).toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/kbn-config-schema/src/types/buffer_type.ts b/packages/kbn-config-schema/src/types/buffer_type.ts new file mode 100644 index 00000000000000..194163e5096f04 --- /dev/null +++ b/packages/kbn-config-schema/src/types/buffer_type.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import typeDetect from 'type-detect'; +import { internals } from '../internals'; +import { Type, TypeOptions } from './type'; + +export class BufferType extends Type { + constructor(options?: TypeOptions) { + super(internals.binary(), options); + } + + protected handleError(type: string, { value }: Record) { + if (type === 'any.required' || type === 'binary.base') { + return `expected value of type [Buffer] but got [${typeDetect(value)}]`; + } + } +} diff --git a/packages/kbn-config-schema/src/types/index.ts b/packages/kbn-config-schema/src/types/index.ts index cfa8cc4b7553d5..9db79b8bf9e00b 100644 --- a/packages/kbn-config-schema/src/types/index.ts +++ b/packages/kbn-config-schema/src/types/index.ts @@ -21,6 +21,7 @@ export { Type, TypeOptions } from './type'; export { AnyType } from './any_type'; export { ArrayOptions, ArrayType } from './array_type'; export { BooleanType } from './boolean_type'; +export { BufferType } from './buffer_type'; export { ByteSizeOptions, ByteSizeType } from './byte_size_type'; export { ConditionalType, ConditionalTypeValue } from './conditional_type'; export { DurationOptions, DurationType } from './duration_type'; @@ -30,6 +31,7 @@ export { MapOfOptions, MapOfType } from './map_type'; export { NumberOptions, NumberType } from './number_type'; export { ObjectType, ObjectTypeOptions, Props, TypeOf } from './object_type'; export { RecordOfOptions, RecordOfType } from './record_type'; +export { StreamType } from './stream_type'; export { StringOptions, StringType } from './string_type'; export { UnionType } from './union_type'; export { URIOptions, URIType } from './uri_type'; diff --git a/packages/kbn-config-schema/src/types/stream_type.test.ts b/packages/kbn-config-schema/src/types/stream_type.test.ts new file mode 100644 index 00000000000000..011fa6373df335 --- /dev/null +++ b/packages/kbn-config-schema/src/types/stream_type.test.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema } from '..'; +import { Stream, Readable, Writable, PassThrough } from 'stream'; + +test('returns value by default', () => { + const value = new Stream(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('Readable is valid', () => { + const value = new Readable(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('Writable is valid', () => { + const value = new Writable(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('Passthrough is valid', () => { + const value = new PassThrough(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('is required by default', () => { + expect(() => schema.buffer().validate(undefined)).toThrowErrorMatchingSnapshot(); +}); + +test('includes namespace in failure', () => { + expect(() => + schema.stream().validate(undefined, {}, 'foo-namespace') + ).toThrowErrorMatchingSnapshot(); +}); + +describe('#defaultValue', () => { + test('returns default when undefined', () => { + const value = new Stream(); + expect(schema.stream({ defaultValue: value }).validate(undefined)).toStrictEqual(value); + }); + + test('returns value when specified', () => { + const value = new Stream(); + expect(schema.stream({ defaultValue: new PassThrough() }).validate(value)).toStrictEqual(value); + }); +}); + +test('returns error when not a stream', () => { + expect(() => schema.stream().validate(123)).toThrowErrorMatchingSnapshot(); + + expect(() => schema.stream().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); + + expect(() => schema.stream().validate('abc')).toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/kbn-config-schema/src/types/stream_type.ts b/packages/kbn-config-schema/src/types/stream_type.ts new file mode 100644 index 00000000000000..db1559f537490e --- /dev/null +++ b/packages/kbn-config-schema/src/types/stream_type.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import typeDetect from 'type-detect'; +import { Stream } from 'stream'; +import { internals } from '../internals'; +import { Type, TypeOptions } from './type'; + +export class StreamType extends Type { + constructor(options?: TypeOptions) { + super(internals.stream(), options); + } + + protected handleError(type: string, { value }: Record) { + if (type === 'any.required' || type === 'stream.base') { + return `expected value of type [Stream] but got [${typeDetect(value)}]`; + } + } +} diff --git a/packages/kbn-config-schema/types/joi.d.ts b/packages/kbn-config-schema/types/joi.d.ts index 5c7e42d0d6f5fd..770314faa8ebd8 100644 --- a/packages/kbn-config-schema/types/joi.d.ts +++ b/packages/kbn-config-schema/types/joi.d.ts @@ -38,6 +38,7 @@ declare module 'joi' { duration: () => AnySchema; map: () => MapSchema; record: () => RecordSchema; + stream: () => AnySchema; }; interface AnySchema { diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index 0ac2f59525c326..8469a1d23a44b8 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -54,7 +54,7 @@ function createKibanaRequestMock({ }: RequestFixtureOptions = {}) { const queryString = querystring.stringify(query); return KibanaRequest.from( - { + createRawRequestMock({ headers, params, query, @@ -71,13 +71,13 @@ function createKibanaRequestMock({ raw: { req: { socket }, }, - } as any, + }), { params: schema.object({}, { allowUnknowns: true }), body: schema.object({}, { allowUnknowns: true }), query: schema.object({}, { allowUnknowns: true }), } - ); + ) as KibanaRequest, Readonly<{}>, Readonly<{}>>; } type DeepPartial = T extends any[] diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index ceecfcfea1449c..df47ffdc1176b9 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -30,6 +30,7 @@ import { HttpConfig } from './http_config'; import { Router } from './router'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { HttpServer } from './http_server'; +import { Readable } from 'stream'; const cookieOptions = { name: 'sid', @@ -577,6 +578,157 @@ test('exposes route details of incoming request to a route handler', async () => }); }); +test('exposes route details of incoming request to a route handler (POST + payload options)', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.post( + { + path: '/', + validate: { body: schema.object({ test: schema.number() }) }, + options: { body: { accepts: 'application/json' } }, + }, + (context, req, res) => res.ok({ body: req.route }) + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener) + .post('/') + .send({ test: 1 }) + .expect(200, { + method: 'post', + path: '/', + options: { + authRequired: true, + tags: [], + body: { + parse: true, // hapi populates the default + maxBytes: 1024, // hapi populates the default + accepts: ['application/json'], + output: 'data', + }, + }, + }); +}); + +describe('body options', () => { + test('should reject the request because the Content-Type in the request is not valid', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.post( + { + path: '/', + validate: { body: schema.object({ test: schema.number() }) }, + options: { body: { accepts: 'multipart/form-data' } }, // supertest sends 'application/json' + }, + (context, req, res) => res.ok({ body: req.route }) + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener) + .post('/') + .send({ test: 1 }) + .expect(415, { + statusCode: 415, + error: 'Unsupported Media Type', + message: 'Unsupported Media Type', + }); + }); + + test('should reject the request because the payload is too large', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.post( + { + path: '/', + validate: { body: schema.object({ test: schema.number() }) }, + options: { body: { maxBytes: 1 } }, + }, + (context, req, res) => res.ok({ body: req.route }) + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener) + .post('/') + .send({ test: 1 }) + .expect(413, { + statusCode: 413, + error: 'Request Entity Too Large', + message: 'Payload content length greater than maximum allowed: 1', + }); + }); + + test('should not parse the content in the request', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.post( + { + path: '/', + validate: { body: schema.buffer() }, + options: { body: { parse: false } }, + }, + (context, req, res) => { + try { + expect(req.body).toBeInstanceOf(Buffer); + expect(req.body.toString()).toBe(JSON.stringify({ test: 1 })); + return res.ok({ body: req.route.options.body }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener) + .post('/') + .send({ test: 1 }) + .expect(200, { + parse: false, + maxBytes: 1024, // hapi populates the default + output: 'data', + }); + }); +}); + +test('should return a stream in the body', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.put( + { + path: '/', + validate: { body: schema.stream() }, + options: { body: { output: 'stream' } }, + }, + (context, req, res) => { + try { + expect(req.body).toBeInstanceOf(Readable); + return res.ok({ body: req.route.options.body }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener) + .put('/') + .send({ test: 1 }) + .expect(200, { + parse: true, + maxBytes: 1024, // hapi populates the default + output: 'stream', + }); +}); + describe('setup contract', () => { describe('#createSessionStorage', () => { it('creates session storage factory', async () => { diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index da97ab535516c8..a587eed1f54eca 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -127,21 +127,26 @@ export class HttpServer { for (const router of this.registeredRouters) { for (const route of router.getRoutes()) { this.log.debug(`registering route handler for [${route.path}]`); - const { authRequired = true, tags } = route.options; // Hapi does not allow payload validation to be specified for 'head' or 'get' requests const validate = ['head', 'get'].includes(route.method) ? undefined : { payload: true }; + const { authRequired = true, tags, body = {} } = route.options; + const { accepts: allow, maxBytes, output, parse } = body; this.server.route({ handler: route.handler, method: route.method, path: route.path, options: { - auth: authRequired ? undefined : false, + // Enforcing the comparison with true because plugins could overwrite the auth strategy by doing `options: { authRequired: authStrategy as any }` + auth: authRequired === true ? undefined : false, tags: tags ? Array.from(tags) : undefined, // TODO: This 'validate' section can be removed once the legacy platform is completely removed. // We are telling Hapi that NP routes can accept any payload, so that it can bypass the default // validation applied in ./http_tools#getServerOptions // (All NP routes are already required to specify their own validation in order to access the payload) validate, + payload: [allow, maxBytes, output, parse].some(v => typeof v !== 'undefined') + ? { allow, maxBytes, output, parse } + : undefined, }, }); } diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index e9a2571382edc4..6dab120b20e50d 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -43,6 +43,7 @@ const createRouterMock = (): jest.Mocked => ({ get: jest.fn(), post: jest.fn(), put: jest.fn(), + patch: jest.fn(), delete: jest.fn(), getRoutes: jest.fn(), handleLegacyErrors: jest.fn().mockImplementation(handler => handler), diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index bed76201bb4f99..f9a3a91ec18adb 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -30,6 +30,7 @@ export { ErrorHttpResponseOptions, KibanaRequest, KibanaRequestRoute, + KibanaRequestRouteOptions, IKibanaResponse, KnownHeaders, LegacyRequest, @@ -44,8 +45,12 @@ export { RouteConfig, IRouter, RouteMethod, - RouteConfigOptions, RouteRegistrar, + RouteConfigOptions, + RouteSchemas, + RouteConfigOptionsBody, + RouteContentType, + validBodyOutput, } from './router'; export { BasePathProxyServer } from './base_path_proxy_server'; export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth'; diff --git a/src/core/server/http/router/error_wrapper.ts b/src/core/server/http/router/error_wrapper.ts index 706a9fe3b88871..c4b4d3840d1b95 100644 --- a/src/core/server/http/router/error_wrapper.ts +++ b/src/core/server/http/router/error_wrapper.ts @@ -23,13 +23,14 @@ import { KibanaRequest } from './request'; import { KibanaResponseFactory } from './response'; import { RequestHandler } from './router'; import { RequestHandlerContext } from '../../../server'; +import { RouteMethod } from './route'; export const wrapErrors =

    ( - handler: RequestHandler -): RequestHandler => { + handler: RequestHandler +): RequestHandler => { return async ( context: RequestHandlerContext, - request: KibanaRequest, TypeOf, TypeOf>, + request: KibanaRequest, TypeOf, TypeOf, RouteMethod>, response: KibanaResponseFactory ) => { try { diff --git a/src/core/server/http/router/index.ts b/src/core/server/http/router/index.ts index f07ad3cfe85c03..35bfb3ba9c33ad 100644 --- a/src/core/server/http/router/index.ts +++ b/src/core/server/http/router/index.ts @@ -22,11 +22,20 @@ export { Router, RequestHandler, IRouter, RouteRegistrar } from './router'; export { KibanaRequest, KibanaRequestRoute, + KibanaRequestRouteOptions, isRealRequest, LegacyRequest, ensureRawRequest, } from './request'; -export { RouteMethod, RouteConfig, RouteConfigOptions } from './route'; +export { + RouteMethod, + RouteConfig, + RouteConfigOptions, + RouteSchemas, + RouteContentType, + RouteConfigOptionsBody, + validBodyOutput, +} from './route'; export { HapiResponseAdapter } from './response_adapter'; export { CustomHttpResponseOptions, diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index 5d3b70ba27eeef..b132899910569e 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -20,23 +20,32 @@ import { Url } from 'url'; import { Request } from 'hapi'; -import { ObjectType, TypeOf } from '@kbn/config-schema'; +import { ObjectType, Type, TypeOf } from '@kbn/config-schema'; +import { Stream } from 'stream'; import { deepFreeze, RecursiveReadonly } from '../../../utils'; import { Headers } from './headers'; -import { RouteMethod, RouteSchemas, RouteConfigOptions } from './route'; +import { RouteMethod, RouteSchemas, RouteConfigOptions, validBodyOutput } from './route'; import { KibanaSocket, IKibanaSocket } from './socket'; const requestSymbol = Symbol('request'); +/** + * Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. + * @public + */ +export type KibanaRequestRouteOptions = Method extends 'get' | 'options' + ? Required, 'body'>> + : Required>; + /** * Request specific route information exposed to a handler. * @public * */ -export interface KibanaRequestRoute { +export interface KibanaRequestRoute { path: string; - method: RouteMethod | 'patch' | 'options'; - options: Required; + method: Method; + options: KibanaRequestRouteOptions; } /** @@ -50,17 +59,22 @@ export interface LegacyRequest extends Request {} // eslint-disable-line @typesc * Kibana specific abstraction for an incoming request. * @public */ -export class KibanaRequest { +export class KibanaRequest< + Params = unknown, + Query = unknown, + Body = unknown, + Method extends RouteMethod = any +> { /** * Factory for creating requests. Validates the request before creating an * instance of a KibanaRequest. * @internal */ - public static from

    ( - req: Request, - routeSchemas?: RouteSchemas, - withoutSecretHeaders: boolean = true - ) { + public static from< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type + >(req: Request, routeSchemas?: RouteSchemas, withoutSecretHeaders: boolean = true) { const requestParts = KibanaRequest.validate(req, routeSchemas); return new KibanaRequest( req, @@ -77,7 +91,11 @@ export class KibanaRequest { * received in the route handler. * @internal */ - private static validate

    ( + private static validate< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type + >( req: Request, routeSchemas: RouteSchemas | undefined ): { @@ -113,7 +131,7 @@ export class KibanaRequest { /** a WHATWG URL standard object. */ public readonly url: Url; /** matched route details */ - public readonly route: RecursiveReadonly; + public readonly route: RecursiveReadonly>; /** * Readonly copy of incoming request headers. * @remarks @@ -148,15 +166,28 @@ export class KibanaRequest { this.socket = new KibanaSocket(request.raw.req.socket); } - private getRouteInfo() { + private getRouteInfo(): KibanaRequestRoute { const request = this[requestSymbol]; + const method = request.method as Method; + const { parse, maxBytes, allow, output } = request.route.settings.payload || {}; + + const options = ({ + authRequired: request.route.settings.auth !== false, + tags: request.route.settings.tags || [], + body: ['get', 'options'].includes(method) + ? undefined + : { + parse, + maxBytes, + accepts: allow, + output: output as typeof validBodyOutput[number], // We do not support all the HAPI-supported outputs and TS complains + }, + } as unknown) as KibanaRequestRouteOptions; // TS does not understand this is OK so I'm enforced to do this enforced casting + return { path: request.path, - method: request.method, - options: { - authRequired: request.route.settings.auth !== false, - tags: request.route.settings.tags || [], - }, + method, + options, }; } } diff --git a/src/core/server/http/router/route.ts b/src/core/server/http/router/route.ts index bffa23551dd52d..129cf4c922ffd3 100644 --- a/src/core/server/http/router/route.ts +++ b/src/core/server/http/router/route.ts @@ -17,18 +17,89 @@ * under the License. */ -import { ObjectType } from '@kbn/config-schema'; +import { ObjectType, Type } from '@kbn/config-schema'; +import { Stream } from 'stream'; + /** * The set of common HTTP methods supported by Kibana routing. * @public */ -export type RouteMethod = 'get' | 'post' | 'put' | 'delete'; +export type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; + +/** + * The set of valid body.output + * @public + */ +export const validBodyOutput = ['data', 'stream'] as const; + +/** + * The set of supported parseable Content-Types + * @public + */ +export type RouteContentType = + | 'application/json' + | 'application/*+json' + | 'application/octet-stream' + | 'application/x-www-form-urlencoded' + | 'multipart/form-data' + | 'text/*'; + +/** + * Additional body options for a route + * @public + */ +export interface RouteConfigOptionsBody { + /** + * A string or an array of strings with the allowed mime types for the endpoint. Use this settings to limit the set of allowed mime types. Note that allowing additional mime types not listed + * above will not enable them to be parsed, and if parse is true, the request will result in an error response. + * + * Default value: allows parsing of the following mime types: + * * application/json + * * application/*+json + * * application/octet-stream + * * application/x-www-form-urlencoded + * * multipart/form-data + * * text/* + */ + accepts?: RouteContentType | RouteContentType[] | string | string[]; + + /** + * Limits the size of incoming payloads to the specified byte count. Allowing very large payloads may cause the server to run out of memory. + * + * Default value: The one set in the kibana.yml config file under the parameter `server.maxPayloadBytes`. + */ + maxBytes?: number; + + /** + * The processed payload format. The value must be one of: + * * 'data' - the incoming payload is read fully into memory. If parse is true, the payload is parsed (JSON, form-decoded, multipart) based on the 'Content-Type' header. If parse is false, a raw + * Buffer is returned. + * * 'stream' - the incoming payload is made available via a Stream.Readable interface. If the payload is 'multipart/form-data' and parse is true, field values are presented as text while files + * are provided as streams. File streams from a 'multipart/form-data' upload will also have a hapi property containing the filename and headers properties. Note that payload streams for multipart + * payloads are a synthetic interface created on top of the entire multipart content loaded into memory. To avoid loading large multipart payloads into memory, set parse to false and handle the + * multipart payload in the handler using a streaming parser (e.g. pez). + * + * Default value: 'data', unless no validation.body is provided in the route definition. In that case the default is 'stream' to alleviate memory pressure. + */ + output?: typeof validBodyOutput[number]; + + /** + * Determines if the incoming payload is processed or presented raw. Available values: + * * true - if the request 'Content-Type' matches the allowed mime types set by allow (for the whole payload as well as parts), the payload is converted into an object when possible. If the + * format is unknown, a Bad Request (400) error response is sent. Any known content encoding is decoded. + * * false - the raw payload is returned unmodified. + * * 'gunzip' - the raw payload is returned unmodified after any known content encoding is decoded. + * + * Default value: true, unless no validation.body is provided in the route definition. In that case the default is false to alleviate memory pressure. + */ + parse?: boolean | 'gunzip'; +} /** * Additional route options. * @public */ -export interface RouteConfigOptions { +export interface RouteConfigOptions { /** * A flag shows that authentication for a route: * `enabled` when true @@ -42,13 +113,23 @@ export interface RouteConfigOptions { * Additional metadata tag strings to attach to the route. */ tags?: readonly string[]; + + /** + * Additional body options {@link RouteConfigOptionsBody}. + */ + body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; } /** * Route specific configuration. * @public */ -export interface RouteConfig

    { +export interface RouteConfig< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type, + Method extends RouteMethod +> { /** * The endpoint _within_ the router path to register the route. * @@ -125,7 +206,7 @@ export interface RouteConfig

    ; } /** @@ -133,7 +214,11 @@ export interface RouteConfig

    { +export interface RouteSchemas< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type +> { params?: P; query?: Q; body?: B; diff --git a/src/core/server/http/router/router.test.ts b/src/core/server/http/router/router.test.ts index 9fdf7297ed7755..f5469a95b51069 100644 --- a/src/core/server/http/router/router.test.ts +++ b/src/core/server/http/router/router.test.ts @@ -19,6 +19,7 @@ import { Router } from './router'; import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { schema } from '@kbn/config-schema'; const logger = loggingServiceMock.create().get(); const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); @@ -45,5 +46,46 @@ describe('Router', () => { `"Expected a valid schema declared with '@kbn/config-schema' package at key: [params]."` ); }); + + it('throws if options.body.output is not a valid value', () => { + const router = new Router('', logger, enhanceWithContext); + expect(() => + router.post( + // we use 'any' because TS already checks we cannot provide this body.output + { + path: '/', + options: { body: { output: 'file' } } as any, // We explicitly don't support 'file' + validate: { body: schema.object({}, { allowUnknowns: true }) }, + }, + (context, req, res) => res.ok({}) + ) + ).toThrowErrorMatchingInlineSnapshot( + `"[options.body.output: 'file'] in route POST / is not valid. Only 'data' or 'stream' are valid."` + ); + }); + + it('should default `output: "stream" and parse: false` when no body validation is required but not a GET', () => { + const router = new Router('', logger, enhanceWithContext); + router.post({ path: '/', validate: {} }, (context, req, res) => res.ok({})); + const [route] = router.getRoutes(); + expect(route.options).toEqual({ body: { output: 'stream', parse: false } }); + }); + + it('should NOT default `output: "stream" and parse: false` when the user has specified body options (he cares about it)', () => { + const router = new Router('', logger, enhanceWithContext); + router.post( + { path: '/', options: { body: { maxBytes: 1 } }, validate: {} }, + (context, req, res) => res.ok({}) + ); + const [route] = router.getRoutes(); + expect(route.options).toEqual({ body: { maxBytes: 1 } }); + }); + + it('should NOT default `output: "stream" and parse: false` when no body validation is required and GET', () => { + const router = new Router('', logger, enhanceWithContext); + router.get({ path: '/', validate: {} }, (context, req, res) => res.ok({})); + const [route] = router.getRoutes(); + expect(route.options).toEqual({}); + }); }); }); diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index a13eae51a19a61..3bed8fe4186ac8 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -21,10 +21,17 @@ import { ObjectType, TypeOf, Type } from '@kbn/config-schema'; import { Request, ResponseObject, ResponseToolkit } from 'hapi'; import Boom from 'boom'; +import { Stream } from 'stream'; import { Logger } from '../../logging'; import { KibanaRequest } from './request'; import { KibanaResponseFactory, kibanaResponseFactory, IKibanaResponse } from './response'; -import { RouteConfig, RouteConfigOptions, RouteMethod, RouteSchemas } from './route'; +import { + RouteConfig, + RouteConfigOptions, + RouteMethod, + RouteSchemas, + validBodyOutput, +} from './route'; import { HapiResponseAdapter } from './response_adapter'; import { RequestHandlerContext } from '../../../server'; import { wrapErrors } from './error_wrapper'; @@ -32,17 +39,22 @@ import { wrapErrors } from './error_wrapper'; interface RouterRoute { method: RouteMethod; path: string; - options: RouteConfigOptions; + options: RouteConfigOptions; handler: (req: Request, responseToolkit: ResponseToolkit) => Promise>; } /** - * Handler to declare a route. + * Route handler common definition + * * @public */ -export type RouteRegistrar =

    ( - route: RouteConfig, - handler: RequestHandler +export type RouteRegistrar = < + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type +>( + route: RouteConfig, + handler: RequestHandler ) => void; /** @@ -62,28 +74,35 @@ export interface IRouter { * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - get: RouteRegistrar; + get: RouteRegistrar<'get'>; /** * Register a route handler for `POST` request. * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - post: RouteRegistrar; + post: RouteRegistrar<'post'>; /** * Register a route handler for `PUT` request. * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - put: RouteRegistrar; + put: RouteRegistrar<'put'>; + + /** + * Register a route handler for `PATCH` request. + * @param route {@link RouteConfig} - a route configuration. + * @param handler {@link RequestHandler} - a function to call to respond to an incoming request + */ + patch: RouteRegistrar<'patch'>; /** * Register a route handler for `DELETE` request. * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - delete: RouteRegistrar; + delete: RouteRegistrar<'delete'>; /** * Wrap a router handler to catch and converts legacy boom errors to proper custom errors. @@ -94,16 +113,19 @@ export interface IRouter { ) => RequestHandler; /** - * Returns all routes registered with the this router. + * Returns all routes registered with this router. * @returns List of registered routes. * @internal */ getRoutes: () => RouterRoute[]; } -export type ContextEnhancer

    = ( - handler: RequestHandler -) => RequestHandlerEnhanced; +export type ContextEnhancer< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType, + Method extends RouteMethod +> = (handler: RequestHandler) => RequestHandlerEnhanced; function getRouteFullPath(routerPath: string, routePath: string) { // If router's path ends with slash and route's path starts with slash, @@ -121,8 +143,8 @@ function getRouteFullPath(routerPath: string, routePath: string) { function routeSchemasFromRouteConfig< P extends ObjectType, Q extends ObjectType, - B extends ObjectType ->(route: RouteConfig, routeMethod: RouteMethod) { + B extends ObjectType | Type | Type +>(route: RouteConfig, routeMethod: RouteMethod) { // The type doesn't allow `validate` to be undefined, but it can still // happen when it's used from JavaScript. if (route.validate === undefined) { @@ -144,6 +166,49 @@ function routeSchemasFromRouteConfig< return route.validate ? route.validate : undefined; } +/** + * Create a valid options object with "sensible" defaults + adding some validation to the options fields + * + * @param method HTTP verb for these options + * @param routeConfig The route config definition + */ +function validOptions( + method: RouteMethod, + routeConfig: RouteConfig< + ObjectType, + ObjectType, + ObjectType | Type | Type, + typeof method + > +) { + const shouldNotHavePayload = ['head', 'get'].includes(method); + const { options = {}, validate } = routeConfig; + const shouldValidateBody = (validate && !!validate.body) || !!options.body; + + const { output } = options.body || {}; + if (typeof output === 'string' && !validBodyOutput.includes(output)) { + throw new Error( + `[options.body.output: '${output}'] in route ${method.toUpperCase()} ${ + routeConfig.path + } is not valid. Only '${validBodyOutput.join("' or '")}' are valid.` + ); + } + + const body = shouldNotHavePayload + ? undefined + : { + // If it's not a GET (requires payload) but no body validation is required (or no body options are specified), + // We assume the route does not care about the body => use the memory-cheapest approach (stream and no parsing) + output: !shouldValidateBody ? ('stream' as const) : undefined, + parse: !shouldValidateBody ? false : undefined, + + // User's settings should overwrite any of the "desired" values + ...options.body, + }; + + return { ...options, body }; +} + /** * @internal */ @@ -153,21 +218,21 @@ export class Router implements IRouter { public post: IRouter['post']; public delete: IRouter['delete']; public put: IRouter['put']; + public patch: IRouter['patch']; constructor( public readonly routerPath: string, private readonly log: Logger, - private readonly enhanceWithContext: ContextEnhancer + private readonly enhanceWithContext: ContextEnhancer ) { - const buildMethod = (method: RouteMethod) => < + const buildMethod = (method: Method) => < P extends ObjectType, Q extends ObjectType, - B extends ObjectType + B extends ObjectType | Type | Type >( - route: RouteConfig, - handler: RequestHandler + route: RouteConfig, + handler: RequestHandler ) => { - const { path, options = {} } = route; const routeSchemas = routeSchemasFromRouteConfig(route, method); this.routes.push({ @@ -179,8 +244,8 @@ export class Router implements IRouter { handler: this.enhanceWithContext(handler), }), method, - path: getRouteFullPath(this.routerPath, path), - options, + path: getRouteFullPath(this.routerPath, route.path), + options: validOptions(method, route), }); }; @@ -188,6 +253,7 @@ export class Router implements IRouter { this.post = buildMethod('post'); this.delete = buildMethod('delete'); this.put = buildMethod('put'); + this.patch = buildMethod('patch'); } public getRoutes() { @@ -200,7 +266,11 @@ export class Router implements IRouter { return wrapErrors(handler); } - private async handle

    ({ + private async handle< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type + >({ routeSchemas, request, responseToolkit, @@ -208,10 +278,10 @@ export class Router implements IRouter { }: { request: Request; responseToolkit: ResponseToolkit; - handler: RequestHandlerEnhanced; + handler: RequestHandlerEnhanced; routeSchemas?: RouteSchemas; }) { - let kibanaRequest: KibanaRequest, TypeOf, TypeOf>; + let kibanaRequest: KibanaRequest, TypeOf, TypeOf, typeof request.method>; const hapiResponseAdapter = new HapiResponseAdapter(responseToolkit); try { kibanaRequest = KibanaRequest.from(request, routeSchemas); @@ -236,8 +306,9 @@ type WithoutHeadArgument = T extends (first: any, ...rest: infer Params) => i type RequestHandlerEnhanced< P extends ObjectType, Q extends ObjectType, - B extends ObjectType -> = WithoutHeadArgument>; + B extends ObjectType | Type | Type, + Method extends RouteMethod +> = WithoutHeadArgument>; /** * A function executed when route path matched requested resource path. @@ -272,8 +343,13 @@ type RequestHandlerEnhanced< * ``` * @public */ -export type RequestHandler

    = ( +export type RequestHandler< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type, + Method extends RouteMethod = any +> = ( context: RequestHandlerContext, - request: KibanaRequest, TypeOf, TypeOf>, + request: KibanaRequest, TypeOf, TypeOf, Method>, response: KibanaResponseFactory ) => IKibanaResponse | Promise>; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index f792f6e604c152..a54ada233bbc94 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -94,6 +94,7 @@ export { IsAuthenticated, KibanaRequest, KibanaRequestRoute, + KibanaRequestRouteOptions, IKibanaResponse, LifecycleResponseFactory, KnownHeaders, @@ -113,9 +114,13 @@ export { KibanaResponseFactory, RouteConfig, IRouter, + RouteRegistrar, RouteMethod, RouteConfigOptions, - RouteRegistrar, + RouteSchemas, + RouteConfigOptionsBody, + RouteContentType, + validBodyOutput, SessionStorage, SessionStorageCookieOptions, SessionCookieValidationResult, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 411e5636069c18..25ca8ade77aca5 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -449,11 +449,11 @@ export interface AuthToolkit { export class BasePath { // @internal constructor(serverBasePath?: string); - get: (request: KibanaRequest | LegacyRequest) => string; + get: (request: KibanaRequest | LegacyRequest) => string; prepend: (path: string) => string; remove: (path: string) => string; readonly serverBasePath: string; - set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; + set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; } // Warning: (ae-forgotten-export) The symbol "BootstrapArgs" needs to be exported by the entry point index.d.ts @@ -718,15 +718,16 @@ export interface IndexSettingsDeprecationInfo { // @public export interface IRouter { - delete: RouteRegistrar; - get: RouteRegistrar; + delete: RouteRegistrar<'delete'>; + get: RouteRegistrar<'get'>; // Warning: (ae-forgotten-export) The symbol "RouterRoute" needs to be exported by the entry point index.d.ts // // @internal getRoutes: () => RouterRoute[]; handleLegacyErrors:

    (handler: RequestHandler) => RequestHandler; - post: RouteRegistrar; - put: RouteRegistrar; + patch: RouteRegistrar<'patch'>; + post: RouteRegistrar<'post'>; + put: RouteRegistrar<'put'>; routerPath: string; } @@ -753,37 +754,38 @@ export interface IUiSettingsClient { } // @public -export class KibanaRequest { +export class KibanaRequest { // @internal (undocumented) protected readonly [requestSymbol]: Request; constructor(request: Request, params: Params, query: Query, body: Body, withoutSecretHeaders: boolean); // (undocumented) readonly body: Body; - // Warning: (ae-forgotten-export) The symbol "RouteSchemas" needs to be exported by the entry point index.d.ts - // // @internal - static from

    (req: Request, routeSchemas?: RouteSchemas, withoutSecretHeaders?: boolean): KibanaRequest; + static from

    | Type>(req: Request, routeSchemas?: RouteSchemas, withoutSecretHeaders?: boolean): KibanaRequest; readonly headers: Headers; // (undocumented) readonly params: Params; // (undocumented) readonly query: Query; - readonly route: RecursiveReadonly; + readonly route: RecursiveReadonly>; // (undocumented) readonly socket: IKibanaSocket; readonly url: Url; } // @public -export interface KibanaRequestRoute { +export interface KibanaRequestRoute { // (undocumented) - method: RouteMethod | 'patch' | 'options'; + method: Method; // (undocumented) - options: Required; + options: KibanaRequestRouteOptions; // (undocumented) path: string; } +// @public +export type KibanaRequestRouteOptions = Method extends 'get' | 'options' ? Required, 'body'>> : Required>; + // @public export type KibanaResponseFactory = typeof kibanaResponseFactory; @@ -1050,7 +1052,7 @@ export type RedirectResponseOptions = HttpResponseOptions & { }; // @public -export type RequestHandler

    = (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; +export type RequestHandler

    | Type, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf, Method>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; // @public export interface RequestHandlerContext { @@ -1092,23 +1094,45 @@ export type ResponseHeaders = { }; // @public -export interface RouteConfig

    { - options?: RouteConfigOptions; +export interface RouteConfig

    | Type, Method extends RouteMethod> { + options?: RouteConfigOptions; path: string; validate: RouteSchemas | false; } // @public -export interface RouteConfigOptions { +export interface RouteConfigOptions { authRequired?: boolean; + body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; tags?: readonly string[]; } // @public -export type RouteMethod = 'get' | 'post' | 'put' | 'delete'; +export interface RouteConfigOptionsBody { + accepts?: RouteContentType | RouteContentType[] | string | string[]; + maxBytes?: number; + output?: typeof validBodyOutput[number]; + parse?: boolean | 'gunzip'; +} + +// @public +export type RouteContentType = 'application/json' | 'application/*+json' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/*'; + +// @public +export type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; // @public -export type RouteRegistrar =

    (route: RouteConfig, handler: RequestHandler) => void; +export type RouteRegistrar =

    | Type>(route: RouteConfig, handler: RequestHandler) => void; + +// @public +export interface RouteSchemas

    | Type> { + // (undocumented) + body?: B; + // (undocumented) + params?: P; + // (undocumented) + query?: Q; +} // @public (undocumented) export interface SavedObject { @@ -1696,6 +1720,9 @@ export interface UserProvidedValues { userValue?: T; } +// @public +export const validBodyOutput: readonly ["data", "stream"]; + // Warnings were encountered during analysis: // diff --git a/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts b/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts index 2e97b01d0d108b..2bbd8b6ddfb621 100644 --- a/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import * as t from 'io-ts'; import { PathReporter } from 'io-ts/lib/PathReporter'; import { isLeft } from 'fp-ts/lib/Either'; -import { KibanaResponseFactory } from 'src/core/server'; +import { KibanaResponseFactory, RouteRegistrar } from 'src/core/server'; import { APMConfig } from '../../../../../../plugins/apm/server'; import { ServerAPI, @@ -65,7 +65,7 @@ export function createApi() { body: bodyRt && 'props' in bodyRt ? t.exact(bodyRt) : fallbackBodyRt }; - router[routerMethod]( + (router[routerMethod] as RouteRegistrar)( { path, options, diff --git a/x-pack/plugins/security/server/routes/authentication/saml.test.ts b/x-pack/plugins/security/server/routes/authentication/saml.test.ts index cdef1826ddaa88..c8735f9f87f4a5 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.test.ts @@ -41,8 +41,8 @@ describe('SAML authentication routes', () => { }); describe('Assertion consumer service endpoint', () => { - let routeHandler: RequestHandler; - let routeConfig: RouteConfig; + let routeHandler: RequestHandler; + let routeConfig: RouteConfig; beforeEach(() => { const [acsRouteConfig, acsRouteHandler] = router.post.mock.calls.find( ([{ path }]) => path === '/api/security/saml/callback' From 439708a6f9e9fd70e773409bbd03e1e723906170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Thu, 28 Nov 2019 15:26:47 +0530 Subject: [PATCH 13/52] [Dependencies]: upgrade react to latest v16.12.0 (#51145) --- package.json | 28 +- packages/eslint-config-kibana/javascript.js | 2 +- packages/eslint-config-kibana/jest.js | 2 +- packages/eslint-config-kibana/typescript.js | 2 +- packages/kbn-i18n/package.json | 2 +- .../src/components/guide_nav/guide_nav.js | 2 +- .../components/guide_sandbox/guide_sandbox.js | 2 +- .../components/guide_section/guide_section.js | 2 +- .../doc_site/src/views/app_view.js | 2 +- packages/kbn-ui-framework/package.json | 6 +- .../public/components/editor/field_select.js | 2 +- .../kibana/public/discover/doc/doc.test.tsx | 10 - .../discover/doc/use_es_doc_search.test.tsx | 12 +- .../home/components/tutorial/tutorial.js | 2 +- .../components/indices_list/indices_list.js | 2 +- .../step_index_pattern/step_index_pattern.js | 2 +- .../create_index_pattern_wizard.js | 2 +- .../indexed_fields_table.js | 2 +- .../scripted_fields_table.js | 2 +- .../source_filters_table.js | 2 +- .../components/relationships/relationships.js | 4 +- .../sections/settings/advanced_settings.js | 2 +- .../settings/components/field/field.js | 2 +- .../public/components/telemetry_form.js | 2 +- .../public/components/aggs/calculation.js | 2 +- .../public/components/aggs/math.js | 2 +- .../public/components/aggs/percentile.js | 2 +- .../public/components/custom_color_picker.js | 9 +- .../public/components/panel_config/gauge.js | 2 +- .../public/components/panel_config/metric.js | 2 +- .../public/components/panel_config/table.js | 2 +- .../public/components/panel_config/top_n.js | 2 +- .../public/components/split.js | 2 +- .../components/vis_types/table/config.js | 2 +- .../public/visualizations/views/gauge.js | 2 +- .../public/visualizations/views/gauge_vis.js | 2 +- .../public/visualizations/views/metric.js | 2 +- .../exit_full_screen_button.tsx | 2 +- .../vislib_vis_legend.test.tsx | 2 +- .../index_pattern_select.tsx | 2 +- .../public/lib/panel/embeddable_panel.tsx | 2 +- .../eui_utils/public/eui_utils.test.tsx | 2 +- .../exit_full_screen_button.tsx | 2 +- .../table_list_view/table_list_view.tsx | 2 +- .../public/util/use_observable.test.tsx | 2 +- .../util/use_shallow_compare_effect.test.ts | 2 +- .../plugins/kbn_tp_run_pipeline/package.json | 4 +- .../kbn_tp_custom_visualizations/package.json | 2 +- .../kbn_tp_embeddable_explorer/package.json | 2 +- .../kbn_tp_sample_panel_action/package.json | 2 +- x-pack/dev-tools/jest/setup/setup_test.js | 1 + .../__test__/ServiceOverview.test.tsx | 13 +- .../__jest__/TransactionOverview.test.tsx | 17 +- .../__test__/KeyValueTable.test.tsx | 4 +- .../__test__/ManagedTable.test.js | 6 +- .../components/shared/ManagedTable/index.tsx | 2 +- .../__test__/ErrorMetadata.test.tsx | 5 +- .../__test__/SpanMetadata.test.tsx | 4 +- .../__test__/TransactionMetadata.test.tsx | 5 +- .../__test__/MetadataTable.test.tsx | 4 +- .../MetadataTable/__test__/Section.test.tsx | 3 +- .../__test__/TransactionActionMenu.test.tsx | 4 +- .../useDelayedVisibility/index.test.tsx | 14 +- .../hooks/useAvgDurationByBrowser.test.ts | 2 +- .../hooks/useFetcher.integration.test.tsx | 12 +- .../apm/public/hooks/useFetcher.test.tsx | 14 +- .../plugins/apm/public/utils/testHelpers.tsx | 3 +- .../public/components/inputs/code_editor.tsx | 2 +- .../public/components/inputs/input.tsx | 2 +- .../public/components/inputs/multi_input.tsx | 2 +- .../public/components/inputs/select.tsx | 2 +- .../public/pages/beat/details.tsx | 2 +- .../public/pages/beat/tags.tsx | 2 +- .../public/pages/tag/edit.tsx | 2 +- .../beats_management/public/router.tsx | 2 +- .../components/time_picker/time_picker.tsx | 2 +- .../__tests__/workpad_telemetry.test.tsx | 4 +- .../components/arg_form/arg_template_form.js | 2 +- .../components/enhance/stateful_prop.js | 2 +- .../components/fullscreen/fullscreen.js | 2 +- .../function_form_context_pending.js | 2 +- .../render_with_fn/render_with_fn.js | 2 +- .../canvas/public/components/router/router.js | 2 +- .../workpad_loader/workpad_loader.js | 2 +- .../components/__tests__/app.test.tsx | 8 +- .../public/app/app.js | 2 +- .../components/field_manager/field_editor.tsx | 5 +- .../components/settings/settings.test.tsx | 2 +- .../node_attrs_details/node_attrs_details.js | 2 +- .../helpers/home.helpers.ts | 1 - .../__jest__/client_integration/home.test.ts | 14 +- .../template_clone.test.tsx | 7 +- .../template_create.test.tsx | 16 +- .../client_integration/template_edit.test.tsx | 7 +- .../edit_settings_json/edit_settings_json.js | 2 +- .../detail_panel/show_json/show_json.js | 4 +- .../use_metrics_explorer_data.test.tsx | 2 +- .../use_metrics_explorer_options.test.tsx | 10 +- .../use_metric_explorer_state.test.tsx | 6 +- .../pages/link_to/use_host_ip_to_name.test.ts | 2 +- .../dimension_panel/dimension_panel.test.tsx | 22 +- .../dimension_panel/dimension_panel.tsx | 6 +- .../start_trial/start_trial.js | 2 +- .../maps/public/components/map_listing.js | 2 +- .../severity_cell/severity_cell.test.tsx | 4 +- .../job_selector/id_badges/id_badges.test.js | 31 +- .../job_selector_table.test.js | 60 +- .../new_selection_id_badges.test.js | 23 +- .../analytics_list/action_delete.test.tsx | 5 +- .../ml/public/application/util/test_utils.ts | 6 +- .../public/components/chart/chart_target.js | 2 +- .../chart/timeseries_visualization.js | 2 +- .../components/cluster_view.js | 2 +- .../public/components/renderers/setup_mode.js | 2 +- .../public/components/sparkline/index.js | 2 +- .../edit_role/components/edit_role_page.tsx | 2 +- .../components/charts/areachart.test.tsx | 6 +- .../public/components/charts/areachart.tsx | 28 +- .../components/charts/barchart.test.tsx | 6 +- .../public/components/charts/barchart.tsx | 26 +- .../__snapshots__/embedded_map.test.tsx.snap | 2 +- .../embeddables/embedded_map.test.tsx | 6 +- .../components/embeddables/embedded_map.tsx | 276 ++++----- .../index_patterns_missing_prompt.test.tsx | 4 +- .../index_patterns_missing_prompt.tsx | 10 +- .../point_tool_tip_content.test.tsx.snap | 2 +- .../line_tool_tip_content.test.tsx | 4 +- .../map_tool_tip/line_tool_tip_content.tsx | 75 +-- .../map_tool_tip/map_tool_tip.test.tsx | 6 +- .../embeddables/map_tool_tip/map_tool_tip.tsx | 230 +++---- .../point_tool_tip_content.test.tsx | 6 +- .../map_tool_tip/point_tool_tip_content.tsx | 62 +- .../map_tool_tip/tooltip_footer.test.tsx | 36 +- .../map_tool_tip/tooltip_footer.tsx | 79 +-- .../events_viewer/events_viewer.test.tsx | 11 +- .../components/events_viewer/index.test.tsx | 10 - .../components/fields_browser/index.test.tsx | 29 +- .../components/formatted_bytes/index.test.tsx | 12 +- .../components/formatted_bytes/index.tsx | 8 +- .../__snapshots__/markdown_hint.test.tsx.snap | 56 +- .../markdown/markdown_hint.test.tsx | 22 +- .../components/markdown/markdown_hint.tsx | 9 +- .../components/ml/entity_draggable.test.tsx | 14 +- .../public/components/ml/entity_draggable.tsx | 70 ++- .../ml/score/anomaly_score.test.tsx | 8 +- .../components/ml/score/anomaly_score.tsx | 76 +-- .../ml/score/anomaly_scores.test.tsx | 16 +- .../components/ml/score/anomaly_scores.tsx | 69 ++- .../ml/score/draggable_score.test.tsx | 8 +- .../components/ml/score/draggable_score.tsx | 80 +-- .../__snapshots__/jobs_table.test.tsx.snap | 2 +- .../filters/groups_filter_popover.test.tsx | 6 +- .../filters/groups_filter_popover.tsx | 111 ++-- .../filters/jobs_table_filters.test.tsx | 14 +- .../jobs_table/filters/jobs_table_filters.tsx | 112 ++-- .../ml_popover/jobs_table/job_switch.test.tsx | 10 +- .../ml_popover/jobs_table/job_switch.tsx | 58 +- .../ml_popover/jobs_table/jobs_table.test.tsx | 40 +- .../ml_popover/jobs_table/jobs_table.tsx | 8 +- .../jobs_table/showing_count.test.tsx | 4 +- .../ml_popover/jobs_table/showing_count.tsx | 8 +- .../components/ml_popover/ml_popover.test.tsx | 12 - .../ml_popover/popover_description.test.tsx | 4 +- .../ml_popover/popover_description.tsx | 8 +- .../ml_popover/upgrade_contents.test.tsx | 4 +- .../ml_popover/upgrade_contents.tsx | 90 +-- .../navigation/tab_navigation/index.test.tsx | 14 +- .../navigation/tab_navigation/index.tsx | 9 +- .../hosts/first_last_seen_host/index.test.tsx | 24 +- .../page/hosts/host_overview/index.test.tsx | 21 - .../page/hosts/kpi_hosts/index.test.tsx | 10 +- .../components/page/hosts/kpi_hosts/index.tsx | 65 +- .../public/components/tables/helpers.test.tsx | 10 +- .../siem/public/components/tables/helpers.tsx | 28 +- .../body/column_headers/header/index.test.tsx | 24 +- .../body/column_headers/header/index.tsx | 98 +-- .../body/column_headers/index.test.tsx | 10 +- .../timeline/body/column_headers/index.tsx | 237 ++++---- .../timeline/body/renderers/args.test.tsx | 21 +- .../timeline/body/renderers/args.tsx | 8 +- .../components/timeline/footer/index.test.tsx | 24 +- .../components/timeline/footer/index.tsx | 339 ++++++----- .../components/timeline/header/index.test.tsx | 8 +- .../components/timeline/header/index.tsx | 88 +-- .../components/timeline/timeline.test.tsx | 26 +- .../public/components/timeline/timeline.tsx | 270 ++++----- .../truncatable_text/index.test.tsx | 3 +- .../import_rule_modal/index.test.tsx | 8 +- .../components/import_rule_modal/index.tsx | 220 +++---- .../components/json_downloader/index.test.tsx | 4 +- .../components/json_downloader/index.tsx | 48 +- .../components/rule_switch/index.test.tsx | 9 +- .../rules/components/rule_switch/index.tsx | 49 +- .../siem/public/pages/hosts/hosts.test.tsx | 11 - .../pages/network/ip_details/index.test.tsx | 6 - .../public/pages/network/ip_details/index.tsx | 432 +++++++------- .../public/pages/network/network.test.tsx | 10 - .../helpers/home.helpers.ts | 3 - .../__jest__/client_integration/home.test.ts | 14 - .../client_integration/policy_add.test.ts | 3 - .../client_integration/policy_edit.test.ts | 3 - .../client_integration/repository_add.test.ts | 5 - .../repository_edit.test.ts | 2 - .../copy_to_space_flyout.test.tsx | 2 +- .../toast_notification_text.test.tsx | 4 +- .../overview/deprecation_logging_toggle.tsx | 2 +- .../__tests__/filter_popover.test.tsx | 2 +- .../helpers/watch_list.helpers.ts | 2 - .../helpers/watch_status.helpers.ts | 4 - .../watch_create_json.test.ts | 5 - .../watch_create_threshold.test.tsx | 14 - .../client_integration/watch_edit.test.ts | 4 - .../client_integration/watch_list.test.ts | 3 - .../client_integration/watch_status.test.ts | 2 - x-pack/package.json | 22 +- x-pack/test_utils/testbed/mount_component.tsx | 9 - yarn.lock | 564 ++++++++++++------ 217 files changed, 2673 insertions(+), 2554 deletions(-) diff --git a/package.json b/package.json index 45a376a2913591..4b48731bd9a887 100644 --- a/package.json +++ b/package.json @@ -79,17 +79,16 @@ }, "resolutions": { "**/@types/node": "10.12.27", - "**/@types/react": "16.8.3", + "**/@types/react": "^16.9.13", "**/@types/hapi": "^17.0.18", "**/@types/angular": "^1.6.56", "**/typescript": "3.7.2", "**/graphql-toolkit/lodash": "^4.17.13", "**/isomorphic-git/**/base64-js": "^1.2.1", "**/image-diff/gm/debug": "^2.6.9", - "**/deepmerge": "^4.2.2", - "**/react": "16.8.6", - "**/react-dom": "16.8.6", - "**/react-test-renderer": "16.8.6" + "**/react-dom": "^16.12.0", + "**/react-test-renderer": "^16.12.0", + "**/deepmerge": "^4.2.2" }, "workspaces": { "packages": [ @@ -216,15 +215,13 @@ "pug": "^2.0.3", "querystring-browser": "1.0.4", "raw-loader": "3.1.0", - "react": "^16.8.6", - "react-addons-shallow-compare": "15.6.2", + "react": "^16.12.0", "react-color": "^2.13.8", - "react-dom": "^16.8.6", + "react-dom": "^16.12.0", "react-grid-layout": "^0.16.2", - "react-hooks-testing-library": "^0.5.0", "react-input-range": "^1.3.0", "react-markdown": "^3.4.1", - "react-redux": "^5.1.1", + "react-redux": "^5.1.2", "react-router-dom": "^4.3.1", "react-sizeme": "^2.3.6", "reactcss": "1.2.3", @@ -287,6 +284,8 @@ "@microsoft/api-documenter": "7.4.3", "@microsoft/api-extractor": "7.4.2", "@percy/agent": "^0.11.0", + "@testing-library/react": "^9.3.2", + "@testing-library/react-hooks": "^3.2.1", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", "@types/babel__core": "^7.1.2", @@ -312,7 +311,7 @@ "@types/has-ansi": "^3.0.0", "@types/history": "^4.7.3", "@types/hoek": "^4.1.3", - "@types/jest": "^24.0.18", + "@types/jest": "24.0.19", "@types/joi": "^13.4.2", "@types/jquery": "^3.3.31", "@types/js-yaml": "^3.11.1", @@ -332,8 +331,8 @@ "@types/podium": "^1.0.0", "@types/prop-types": "^15.5.3", "@types/reach__router": "^1.2.6", - "@types/react": "^16.8.0", - "@types/react-dom": "^16.8.0", + "@types/react": "^16.9.11", + "@types/react-dom": "^16.9.4", "@types/react-redux": "^6.0.6", "@types/react-router-dom": "^4.3.1", "@types/react-virtualized": "^9.18.7", @@ -347,6 +346,8 @@ "@types/styled-components": "^4.4.0", "@types/supertest": "^2.0.5", "@types/supertest-as-promised": "^2.0.38", + "@types/testing-library__react": "^9.1.2", + "@types/testing-library__react-hooks": "^3.1.0", "@types/type-detect": "^4.0.1", "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", @@ -410,7 +411,6 @@ "istanbul-instrumenter-loader": "3.0.1", "jest": "^24.9.0", "jest-cli": "^24.9.0", - "jest-dom": "^3.5.0", "jest-raw-loader": "^1.0.1", "jimp": "0.8.4", "json5": "^1.0.1", diff --git a/packages/eslint-config-kibana/javascript.js b/packages/eslint-config-kibana/javascript.js index 0c79669c15e733..7afd3da3a7b939 100644 --- a/packages/eslint-config-kibana/javascript.js +++ b/packages/eslint-config-kibana/javascript.js @@ -40,7 +40,7 @@ module.exports = { rules: { 'block-scoped-var': 'error', - camelcase: [ 'error', { properties: 'never' } ], + camelcase: [ 'error', { properties: 'never', allow: ['^UNSAFE_'] } ], 'comma-dangle': 'off', 'comma-spacing': ['error', { before: false, after: true }], 'comma-style': [ 'error', 'last' ], diff --git a/packages/eslint-config-kibana/jest.js b/packages/eslint-config-kibana/jest.js index 2aa9627404a6c2..d682277ff905a4 100644 --- a/packages/eslint-config-kibana/jest.js +++ b/packages/eslint-config-kibana/jest.js @@ -16,7 +16,7 @@ module.exports = { rules: { 'jest/no-focused-tests': 'error', 'jest/no-identical-title': 'error', - 'import/order': 'off' + 'import/order': 'off', }, } ] diff --git a/packages/eslint-config-kibana/typescript.js b/packages/eslint-config-kibana/typescript.js index 8ffae5edc88ebe..3337bfc8eb1015 100644 --- a/packages/eslint-config-kibana/typescript.js +++ b/packages/eslint-config-kibana/typescript.js @@ -94,7 +94,7 @@ module.exports = { '@typescript-eslint/camelcase': ['error', { 'properties': 'never', 'ignoreDestructuring': true, - 'allow': ['^[A-Z0-9_]+$'] + 'allow': ['^[A-Z0-9_]+$', '^UNSAFE_'] }], '@typescript-eslint/class-name-casing': 'error', '@typescript-eslint/explicit-member-accessibility': ['error', diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index 0146111941044f..bbc5126da1dce0 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -28,7 +28,7 @@ "intl-messageformat": "^2.2.0", "intl-relativeformat": "^2.1.0", "prop-types": "^15.6.2", - "react": "^16.8.6", + "react": "^16.12.0", "react-intl": "^2.8.0" } } diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js b/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js index 53bc42ce332761..cb4654522e4e36 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js @@ -47,7 +47,7 @@ export class GuideNav extends Component { }); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const currentRoute = nextProps.routes[1]; const nextRoute = this.props.getNextRoute(currentRoute.name); const previousRoute = this.props.getPreviousRoute(currentRoute.name); diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_sandbox/guide_sandbox.js b/packages/kbn-ui-framework/doc_site/src/components/guide_sandbox/guide_sandbox.js index 6d4c9cddae0be3..f5a71bfb1b296f 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_sandbox/guide_sandbox.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_sandbox/guide_sandbox.js @@ -49,7 +49,7 @@ function mapDispatchToProps(dispatch) { } class GuideSandboxComponent extends Component { - componentWillMount() { + UNSAFE_componentWillMount() { this.props.openSandbox(); } diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_section/guide_section.js b/packages/kbn-ui-framework/doc_site/src/components/guide_section/guide_section.js index 99a68e95751144..dbad59ffb3bd51 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_section/guide_section.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_section/guide_section.js @@ -36,7 +36,7 @@ export class GuideSection extends Component { this.props.openCodeViewer(this.props.source, this.props.title); } - componentWillMount() { + UNSAFE_componentWillMount() { this.props.registerSection(this.getId(), this.props.title); } diff --git a/packages/kbn-ui-framework/doc_site/src/views/app_view.js b/packages/kbn-ui-framework/doc_site/src/views/app_view.js index 7a9d7a01b820a0..fc14417564d727 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/app_view.js +++ b/packages/kbn-ui-framework/doc_site/src/views/app_view.js @@ -81,7 +81,7 @@ export class AppView extends Component { }); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { // Only force the chrome to be hidden if we're navigating from a non-sandbox to a sandbox. if (!this.props.isSandbox && nextProps.isSandbox) { this.setState({ diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index ee5424370fb06a..b4d9d3dfee03f6 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -19,7 +19,7 @@ "focus-trap-react": "^3.1.1", "lodash": "npm:@elastic/lodash@3.10.1-kibana3", "prop-types": "15.6.0", - "react": "^16.8.6", + "react": "^16.12.0", "react-ace": "^5.9.0", "react-color": "^2.13.8", "tabbable": "1.1.3", @@ -57,8 +57,8 @@ "postcss": "^7.0.5", "postcss-loader": "^3.0.0", "raw-loader": "^3.1.0", - "react-dom": "^16.8.6", - "react-redux": "^5.0.6", + "react-dom": "^16.12.0", + "react-redux": "^5.1.2", "react-router": "^3.2.0", "react-router-redux": "^4.0.8", "redux": "3.7.2", diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js index 11c6f27af38c5f..ce7d48a3f13762 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js @@ -49,7 +49,7 @@ class FieldSelectUi extends Component { this.loadFields(this.state.indexPatternId); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (this.props.indexPatternId !== nextProps.indexPatternId) { this.loadFields(nextProps.indexPatternId); } diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx index b3efd23ea48d03..ee80f29c053dce 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx @@ -45,16 +45,6 @@ beforeEach(() => { jest.clearAllMocks(); }); -// Suppress warnings about "act" until we use React 16.9 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - export const waitForPromises = () => new Promise(resolve => setTimeout(resolve, 0)); /** diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx index 083a5997ac5dd9..2420eb2cd22bb7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx @@ -16,20 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { renderHook, act } from 'react-hooks-testing-library'; +import { renderHook, act } from '@testing-library/react-hooks'; import { buildSearchBody, useEsDocSearch, ElasticRequestState } from './use_es_doc_search'; import { DocProps } from './doc'; -// Suppress warnings about "act" until we use React 16.9 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - describe('Test of helper / hook', () => { test('buildSearchBody', () => { const indexPattern = { diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js index 086fa5a0591216..567bb3f83f22c8 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js @@ -67,7 +67,7 @@ class TutorialUi extends React.Component { } } - componentWillMount() { + UNSAFE_componentWillMount() { this._isMounted = true; } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js index df90abce00e48d..d0441ce3ceb8b3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js @@ -61,7 +61,7 @@ export class IndicesList extends Component { this.pager = new Pager(props.indices.length, this.state.perPage, this.state.page); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.indices.length !== this.props.indices.length) { this.pager.setTotalItems(nextProps.indices.length); this.resetPageTo0(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js index 617262c13b034f..4764b516dffec4 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js @@ -75,7 +75,7 @@ export class StepIndexPattern extends Component { this.lastQuery = null; } - async componentWillMount() { + async UNSAFE_componentWillMount() { this.fetchExistingIndexPatterns(); if (this.state.query) { this.lastQuery = this.state.query; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js index a1f302dc87f6c1..566277802174a9 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js @@ -65,7 +65,7 @@ export class CreateIndexPatternWizard extends Component { }; } - async componentWillMount() { + async UNSAFE_componentWillMount() { this.fetchData(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js index cb1c316c8af9be..b32c325e936535 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js @@ -47,7 +47,7 @@ export class IndexedFieldsTable extends Component { }; } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.fields !== this.props.fields) { this.setState({ fields: this.mapFields(nextProps.fields) diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js index d91f4836ee1d8f..c7677955a8069b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js @@ -59,7 +59,7 @@ export class ScriptedFieldsTable extends Component { }; } - componentWillMount() { + UNSAFE_componentWillMount() { this.fetchFields(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js index ba93485a1739a0..1201e23c48e443 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js @@ -58,7 +58,7 @@ export class SourceFiltersTable extends Component { }; } - componentWillMount() { + UNSAFE_componentWillMount() { this.updateFilters(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js index 278f7de38b19d9..ee9fb70e31fb26 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js @@ -58,11 +58,11 @@ export class Relationships extends Component { }; } - componentWillMount() { + UNSAFE_componentWillMount() { this.getRelationshipData(); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.savedObject.id !== this.props.savedObject.id) { this.getRelationshipData(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js index c2dabdffb2cd21..525f8495027c17 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js @@ -81,7 +81,7 @@ export class AdvancedSettings extends Component { }, {}); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { config } = nextProps; const { query } = this.state; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js index a953d09906ed11..c790930e32aa0f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js @@ -76,7 +76,7 @@ export class Field extends PureComponent { this.changeImageForm = null; } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { unsavedValue } = this.state; const { type, value, defVal } = nextProps.setting; const editableValue = this.getEditableValue(type, value, defVal); diff --git a/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js index 6c6ace71af4d09..d4bbe1029b40d7 100644 --- a/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js +++ b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js @@ -52,7 +52,7 @@ export class TelemetryForm extends Component { queryMatches: null, } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { query } = nextProps; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js index 2eefeb35c26ff8..76306ecf28994c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js @@ -42,7 +42,7 @@ import { } from '@elastic/eui'; export class CalculationAgg extends Component { - componentWillMount() { + UNSAFE_componentWillMount() { if (!this.props.model.variables) { this.props.onChange( _.assign({}, this.props.model, { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js index a73460798ebd81..c62012927f951b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js @@ -42,7 +42,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; export class MathAgg extends Component { - componentWillMount() { + UNSAFE_componentWillMount() { if (!this.props.model.variables) { this.props.onChange( _.assign({}, this.props.model, { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js index ec16a0f2eb3eec..3ce5be5b6875a8 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js @@ -42,7 +42,7 @@ const RESTRICT_FIELDS = [KBN_FIELD_TYPES.NUMBER]; export class PercentileAgg extends Component { // eslint-disable-line react/no-multi-comp - componentWillMount() { + UNSAFE_componentWillMount() { if (!this.props.model.percentiles) { this.props.onChange( _.assign({}, this.props.model, { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js index 9fe237d9cb6713..835628368efab6 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js @@ -18,7 +18,7 @@ */ import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import { ColorWrap as colorWrap, @@ -32,18 +32,13 @@ import ChromePointer from 'react-color/lib/components/chrome/ChromePointer'; import ChromePointerCircle from 'react-color/lib/components/chrome/ChromePointerCircle'; import CompactColor from 'react-color/lib/components/compact/CompactColor'; import color from 'react-color/lib/helpers/color'; -import shallowCompare from 'react-addons-shallow-compare'; -class CustomColorPickerUI extends Component { +class CustomColorPickerUI extends PureComponent { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } - shouldComponentUpdate(nextProps, nextState) { - return shallowCompare(nextProps, nextState); - } - handleChange(data) { this.props.onChange(data); } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js index 6da10f6a808165..19770519ef010a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js @@ -52,7 +52,7 @@ class GaugePanelConfigUi extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; const parts = {}; if ( diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js index 705adc07e7314c..526649766a008a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js @@ -48,7 +48,7 @@ export class MetricPanelConfig extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; if ( !model.background_color_rules || diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js index 029962f189afff..3acaa728bb50fc 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js @@ -51,7 +51,7 @@ export class TablePanelConfig extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; const parts = {}; if (!model.bar_color_rules || (model.bar_color_rules && model.bar_color_rules.length === 0)) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js index 8f5619909754e6..7dbecb9e674b01 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js @@ -51,7 +51,7 @@ export class TopNPanelConfig extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; const parts = {}; if (!model.bar_color_rules || (model.bar_color_rules && model.bar_color_rules.length === 0)) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js index fe91cb39f4acec..d1c53899db8793 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js @@ -37,7 +37,7 @@ const SPLIT_MODES = { }; export class Split extends Component { - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { model } = nextProps; if (model.split_mode === 'filters' && !model.split_filters) { this.props.onChange({ diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js index d0e3acb2ef2fa4..a917a93ffbf5e5 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js @@ -44,7 +44,7 @@ import { getDefaultQueryLanguage } from '../../lib/get_default_query_language'; import { QueryBarWrapper } from '../../query_bar_wrapper'; class TableSeriesConfigUI extends Component { - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; if (!model.color_rules || (model.color_rules && model.color_rules.length === 0)) { this.props.onChange({ diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js index 68b738503c9b3d..3be2e9daed58c6 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js @@ -42,7 +42,7 @@ export class Gauge extends Component { this.handleResize = this.handleResize.bind(this); } - componentWillMount() { + UNSAFE_componentWillMount() { const check = () => { this.timeout = setTimeout(() => { const newState = calculateCoordinates(this.inner, this.resize, this.state); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js index aa4ac992433971..d66eddae253a15 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js @@ -37,7 +37,7 @@ export class GaugeVis extends Component { this.handleResize = this.handleResize.bind(this); } - componentWillMount() { + UNSAFE_componentWillMount() { const check = () => { this.timeout = setTimeout(() => { const newState = calculateCoordinates(this.inner, this.resize, this.state); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js index d6831769c6a6a6..004d59efca3330 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js @@ -37,7 +37,7 @@ export class Metric extends Component { this.handleResize = this.handleResize.bind(this); } - componentWillMount() { + UNSAFE_componentWillMount() { const check = () => { this.timeout = setTimeout(() => { const newState = calculateCoordinates(this.inner, this.resize, this.state); diff --git a/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx b/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx index 2e9a39b047e0b2..db4101010f6d67 100644 --- a/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx +++ b/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx @@ -31,7 +31,7 @@ interface Props { } export class ExitFullScreenButton extends PureComponent { - public componentWillMount() { + public UNSAFE_componentWillMount() { chrome.setVisible(false); } diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx index ba47de73964f6b..66acc9e247e630 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; -import { act } from 'react-hooks-testing-library'; +import { act } from '@testing-library/react-hooks'; import { I18nProvider } from '@kbn/i18n/react'; import { EuiButtonGroup } from '@elastic/eui'; diff --git a/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx index f868e4b1f7504c..f41024ed16191c 100644 --- a/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx +++ b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx @@ -88,7 +88,7 @@ export class IndexPatternSelect extends Component { this.fetchSelectedIndexPattern(this.props.indexPatternId); } - componentWillReceiveProps(nextProps: IndexPatternSelectProps) { + UNSAFE_componentWillReceiveProps(nextProps: IndexPatternSelectProps) { if (nextProps.indexPatternId !== this.props.indexPatternId) { this.fetchSelectedIndexPattern(nextProps.indexPatternId); } diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index cad095a6b0814d..2b48bf237829c0 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -102,7 +102,7 @@ export class EmbeddablePanel extends React.Component { } } - public componentWillMount() { + public UNSAFE_componentWillMount() { this.mounted = true; const { embeddable } = this.props; const { parent } = embeddable; diff --git a/src/plugins/eui_utils/public/eui_utils.test.tsx b/src/plugins/eui_utils/public/eui_utils.test.tsx index 019ca4fcbc18d9..a42eba838fe23f 100644 --- a/src/plugins/eui_utils/public/eui_utils.test.tsx +++ b/src/plugins/eui_utils/public/eui_utils.test.tsx @@ -18,7 +18,7 @@ */ import { BehaviorSubject } from 'rxjs'; -import { renderHook, act } from 'react-hooks-testing-library'; +import { renderHook, act } from '@testing-library/react-hooks'; import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; import { EuiUtils } from './eui_utils'; diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx index a880d3c6cf87c3..09e702c55ac783 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx +++ b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx @@ -35,7 +35,7 @@ class ExitFullScreenButtonUi extends PureComponent { } }; - public componentWillMount() { + public UNSAFE_componentWillMount() { document.addEventListener('keydown', this.onKeyDown, false); } diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index dde8efa7e11066..e3be0b08ab83fe 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -112,7 +112,7 @@ class TableListView extends React.Component { diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 97ad71eaddd7cb..51b9a5c5f17860 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -8,7 +8,7 @@ "license": "Apache-2.0", "dependencies": { "@elastic/eui": "16.0.0", - "react": "^16.8.6", - "react-dom": "^16.8.6" + "react": "^16.12.0", + "react-dom": "^16.12.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index ca584b4b4e7714..fd0ce478eb6fb2 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -8,6 +8,6 @@ "license": "Apache-2.0", "dependencies": { "@elastic/eui": "16.0.0", - "react": "^16.8.6" + "react": "^16.12.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json index 71545fa582c664..98df7f4b246dc6 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -9,7 +9,7 @@ "license": "Apache-2.0", "dependencies": { "@elastic/eui": "16.0.0", - "react": "^16.8.6" + "react": "^16.12.0" }, "scripts": { "kbn": "node ../../../../scripts/kbn.js", diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index d5c97bb212ea05..32f441ba6ccda8 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -9,7 +9,7 @@ "license": "Apache-2.0", "dependencies": { "@elastic/eui": "16.0.0", - "react": "^16.8.6" + "react": "^16.12.0" }, "scripts": { "kbn": "node ../../../../scripts/kbn.js", diff --git a/x-pack/dev-tools/jest/setup/setup_test.js b/x-pack/dev-tools/jest/setup/setup_test.js index 533ea58a561ac5..f54be89f309556 100644 --- a/x-pack/dev-tools/jest/setup/setup_test.js +++ b/x-pack/dev-tools/jest/setup/setup_test.js @@ -10,3 +10,4 @@ */ import 'jest-styled-components'; +import '@testing-library/jest-dom/extend-expect'; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx index 118473a471561c..9f48880090369d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx @@ -5,8 +5,7 @@ */ import React from 'react'; -import { render, wait, waitForElement } from 'react-testing-library'; -import 'react-testing-library/cleanup-after-each'; +import { render, wait, waitForElement } from '@testing-library/react'; import { ServiceOverview } from '..'; import * as urlParamsHooks from '../../../../hooks/useUrlParams'; import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; @@ -61,16 +60,6 @@ describe('Service Overview -> View', () => { jest.resetAllMocks(); }); - // Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 - /* eslint-disable no-console */ - const originalError = console.error; - beforeAll(() => { - console.error = jest.fn(); - }); - afterAll(() => { - console.error = originalError; - }); - it('should render services, when list is not empty', async () => { // mock rest requests coreMock.http.get.mockResolvedValueOnce({ diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx index 1f3403be70aa08..a5356be72f5e40 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx @@ -8,12 +8,11 @@ import React from 'react'; import { queryByLabelText, render, - queryBySelectText, getByText, getByDisplayValue, queryByDisplayValue, fireEvent -} from 'react-testing-library'; +} from '@testing-library/react'; import { omit } from 'lodash'; import { history } from '../../../../utils/history'; import { TransactionOverview } from '..'; @@ -32,16 +31,6 @@ const coreMock = ({ notifications: { toasts: { addWarning: () => {} } } } as unknown) as LegacyCoreStart; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - function setup({ urlParams, serviceTransactionTypes @@ -107,8 +96,8 @@ describe('TransactionOverview', () => { }); // secondType is selected in the dropdown - expect(queryBySelectText(container, 'secondType')).not.toBeNull(); - expect(queryBySelectText(container, 'firstType')).toBeNull(); + expect(queryByDisplayValue(container, 'secondType')).not.toBeNull(); + expect(queryByDisplayValue(container, 'firstType')).toBeNull(); expect(getByText(container, 'firstType')).not.toBeNull(); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx index 2ce8feb08d4adb..20125afb52f482 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { KeyValueTable } from '..'; -import { cleanup, render } from 'react-testing-library'; +import { render } from '@testing-library/react'; function getKeys(output: ReturnType) { const keys = output.getAllByTestId('dot-key'); @@ -19,8 +19,6 @@ function getValues(output: ReturnType) { } describe('KeyValueTable', () => { - afterEach(cleanup); - it('displays key and value table', () => { const data = [ { key: 'name.first', value: 'First Name' }, diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js index ff8d54935e9b29..1b63274dd3cf40 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js @@ -6,7 +6,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { ManagedTable } from '..'; +import { UnoptimizedManagedTable } from '..'; describe('ManagedTable component', () => { let people; @@ -31,14 +31,14 @@ describe('ManagedTable component', () => { it('should render a page-full of items, with defaults', () => { expect( - shallow() + shallow() ).toMatchSnapshot(); }); it('should render when specifying initial values', () => { expect( shallow( - { - afterEach(cleanup); - it('should render a error with all sections', () => { const error = getError(); const output = render(); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx index 8c848722b32b2d..3c851252666e0c 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx @@ -5,8 +5,7 @@ */ import React from 'react'; -import 'jest-dom/extend-expect'; -import { render, cleanup } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { SpanMetadata } from '..'; import { Span } from '../../../../../../typings/es_schemas/ui/Span'; import { @@ -15,7 +14,6 @@ import { } from '../../../../../utils/testHelpers'; describe('SpanMetadata', () => { - afterEach(cleanup); describe('render', () => { it('renders', () => { const span = ({ diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx index d503929cf04d2d..1e06648f21eead 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx @@ -6,9 +6,8 @@ import React from 'react'; import { TransactionMetadata } from '..'; -import { render, cleanup } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { Transaction } from '../../../../../../typings/es_schemas/ui/Transaction'; -import 'jest-dom/extend-expect'; import { expectTextsInDocument, expectTextsNotInDocument @@ -37,8 +36,6 @@ function getTransaction() { } describe('TransactionMetadata', () => { - afterEach(cleanup); - it('should render a transaction with all sections', () => { const transaction = getTransaction(); const output = render(); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx index 4398c129aa7b84..fbdd6bad3457df 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx @@ -5,14 +5,12 @@ */ import React from 'react'; -import 'jest-dom/extend-expect'; -import { render, cleanup } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { MetadataTable } from '..'; import { expectTextsInDocument } from '../../../../utils/testHelpers'; import { SectionsWithRows } from '../helper'; describe('MetadataTable', () => { - afterEach(cleanup); it('shows sections', () => { const sectionsWithRows = ([ { key: 'foo', label: 'Foo', required: true }, diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx index 4378c7fdeee0c3..7a150f81580d86 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import 'jest-dom/extend-expect'; -import { render } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { Section } from '../Section'; import { expectTextsInDocument } from '../../../../utils/testHelpers'; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx index e9e7466cd81a8d..4bb018c760f1f6 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx @@ -5,8 +5,7 @@ */ import React from 'react'; -import { render, fireEvent, cleanup } from 'react-testing-library'; -import 'react-testing-library/cleanup-after-each'; +import { render, fireEvent } from '@testing-library/react'; import { TransactionActionMenu } from '../TransactionActionMenu'; import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; import * as Transactions from './mockData'; @@ -38,7 +37,6 @@ describe('TransactionActionMenu component', () => { afterEach(() => { jest.clearAllMocks(); - cleanup(); }); it('should always render the discover link', async () => { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx index f55d8d470351ca..57e634df22837e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx @@ -4,21 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { cleanup, renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import { useDelayedVisibility } from '.'; -afterEach(cleanup); - -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - describe('useFetcher', () => { let hook; beforeEach(() => { diff --git a/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.test.ts b/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.test.ts index 38f26c2ba9fbd7..4763a560e0f857 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.test.ts +++ b/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import theme from '@elastic/eui/dist/eui_theme_light.json'; import * as useFetcherModule from './useFetcher'; import { useAvgDurationByBrowser } from './useAvgDurationByBrowser'; diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx index 94c2ee09b5d177..36a8377c02527c 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx +++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx @@ -5,22 +5,12 @@ */ import React from 'react'; -import { render } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { delay, tick } from '../utils/testHelpers'; import { useFetcher } from './useFetcher'; import { KibanaCoreContext } from '../../../observability/public/context/kibana_core'; import { LegacyCoreStart } from 'kibana/public'; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - // Wrap the hook with a provider so it can useKibanaCore const wrapper = ({ children }: { children?: React.ReactNode }) => ( { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - // Wrap the hook with a provider so it can useKibanaCore const wrapper = ({ children }: { children?: React.ReactNode }) => ( { }; } - public async componentWillMount() { + public async UNSAFE_componentWillMount() { const tags = await this.props.libs.tags.getTagsWithIds(this.props.beat.tags); const blocksResult = await this.props.libs.configBlocks.getForTags( this.props.beat.tags, diff --git a/x-pack/legacy/plugins/beats_management/public/pages/beat/tags.tsx b/x-pack/legacy/plugins/beats_management/public/pages/beat/tags.tsx index 3cbb84dfb954b1..672c0d89bb0028 100644 --- a/x-pack/legacy/plugins/beats_management/public/pages/beat/tags.tsx +++ b/x-pack/legacy/plugins/beats_management/public/pages/beat/tags.tsx @@ -34,7 +34,7 @@ export class BeatTagsPage extends React.PureComponent { }; } - public async componentWillMount() { + public async UNSAFE_componentWillMount() { if (this.state.loading === true) { try { await this.props.beatsContainer.reload(); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx index 66a7aa0639dba7..fb0baa22c16f4c 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx @@ -48,7 +48,7 @@ export class TimePicker extends Component { }; // TODO: Refactor to no longer use componentWillReceiveProps since it is being deprecated - componentWillReceiveProps({ from, to }: Props) { + UNSAFE_componentWillReceiveProps({ from, to }: Props) { if (from !== this.props.from || to !== this.props.to) { this.setState({ range: { from, to }, diff --git a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx index b8779e7d44fcfb..d486440c1fd7db 100644 --- a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx +++ b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { render, cleanup } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { withUnconnectedElementsLoadedTelemetry, WorkpadLoadedMetric, @@ -63,8 +63,6 @@ describe('Elements Loaded Telemetry', () => { trackMetric.mockReset(); }); - afterEach(cleanup); - it('tracks when all resolvedArgs are completed', () => { const { rerender } = render( > + } selectedOptions={[ { value: currentField.name, diff --git a/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx b/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx index 43ad52abc4cc1e..a615901f40e253 100644 --- a/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx @@ -9,7 +9,7 @@ import { EuiTab, EuiListGroupItem, EuiButton, EuiAccordion, EuiFieldText } from import * as Rx from 'rxjs'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { Settings, AngularProps } from './settings'; -import { act } from 'react-testing-library'; +import { act } from '@testing-library/react'; import { ReactWrapper } from 'enzyme'; import { UrlTemplateForm } from './url_template_form'; import { diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_attrs_details/node_attrs_details.js b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_attrs_details/node_attrs_details.js index 81272c748f60a5..2478dec36547ce 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_attrs_details/node_attrs_details.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_attrs_details/node_attrs_details.js @@ -27,7 +27,7 @@ export class NodeAttrsDetails extends PureComponent { selectedNodeAttrs: PropTypes.string.isRequired, }; - componentWillMount() { + UNSAFE_componentWillMount() { this.props.fetchNodeDetails(this.props.selectedNodeAttrs); } diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts index 50dd8215102ffe..4a4896347333c8 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts @@ -105,7 +105,6 @@ export const setup = async (): Promise => { const { rows } = table.getMetaData('templateTable'); const templateLink = findTestSubject(rows[index].reactWrapper, 'templateDetailsLink'); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { const { href } = templateLink.props(); router.navigateTo(href!); diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/home.test.ts b/x-pack/legacy/plugins/index_management/__jest__/client_integration/home.test.ts index a7c0ac41816184..9e8af02b74631c 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/home.test.ts +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/home.test.ts @@ -22,9 +22,7 @@ const removeWhiteSpaceOnArrayValues = (array: any[]) => jest.mock('ui/new_platform'); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: IdxMgmtHomeTestBed; @@ -38,7 +36,6 @@ describe.skip('', () => { testBed = await setup(); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { const { component } = testBed; @@ -81,7 +78,6 @@ describe.skip('', () => { actions.selectHomeTab('templatesTab'); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); component.update(); @@ -101,7 +97,6 @@ describe.skip('', () => { actions.selectHomeTab('templatesTab'); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); component.update(); @@ -147,7 +142,6 @@ describe.skip('', () => { actions.selectHomeTab('templatesTab'); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); component.update(); @@ -186,7 +180,6 @@ describe.skip('', () => { expect(exists('reloadButton')).toBe(true); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickReloadButton(); await nextTick(); @@ -214,7 +207,6 @@ describe.skip('', () => { expect(exists('systemTemplatesSwitch')).toBe(true); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { form.toggleEuiSwitch('systemTemplatesSwitch'); await nextTick(); @@ -290,7 +282,6 @@ describe.skip('', () => { test('should show a warning message when attempting to delete a system template', async () => { const { component, form, actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { form.toggleEuiSwitch('systemTemplatesSwitch'); await nextTick(); @@ -328,7 +319,6 @@ describe.skip('', () => { }, }); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { confirmButton!.click(); await nextTick(); @@ -384,7 +374,6 @@ describe.skip('', () => { actions.clickCloseDetailsButton(); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); component.update(); @@ -474,7 +463,6 @@ describe.skip('', () => { await actions.clickTemplateAt(0); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); component.update(); diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_clone.test.tsx b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_clone.test.tsx index bd8d9b8e356754..997fe8cff2dace 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_clone.test.tsx +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_clone.test.tsx @@ -38,9 +38,7 @@ jest.mock('@elastic/eui', () => ({ ), })); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -59,7 +57,6 @@ describe.skip('', () => { testBed = await setup(); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); testBed.component.update(); @@ -77,7 +74,6 @@ describe.skip('', () => { beforeEach(async () => { const { actions, component } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) // Specify index patterns, but do not change name (keep default) @@ -105,7 +101,6 @@ describe.skip('', () => { it('should send the correct payload', async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickSubmitButton(); await nextTick(); diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_create.test.tsx b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_create.test.tsx index a391811257a9f3..e678b7a7f52d61 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_create.test.tsx +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_create.test.tsx @@ -43,9 +43,7 @@ jest.mock('@elastic/eui', () => ({ ), })); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -71,7 +69,6 @@ describe.skip('', () => { expect(find('nextButton').props().disabled).toEqual(false); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickNextButton(); await nextTick(); @@ -90,7 +87,6 @@ describe.skip('', () => { beforeEach(async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); @@ -107,7 +103,6 @@ describe.skip('', () => { it('should not allow invalid json', async () => { const { form, actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.completeStepTwo('{ invalidJsonString '); }); @@ -120,7 +115,6 @@ describe.skip('', () => { beforeEach(async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); @@ -140,7 +134,6 @@ describe.skip('', () => { it('should not allow invalid json', async () => { const { actions, form } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 3 (mappings) with invalid json await actions.completeStepThree('{ invalidJsonString '); @@ -154,7 +147,6 @@ describe.skip('', () => { beforeEach(async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); @@ -177,7 +169,6 @@ describe.skip('', () => { it('should not allow invalid json', async () => { const { actions, form } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 4 (aliases) with invalid json await actions.completeStepFour('{ invalidJsonString '); @@ -194,7 +185,6 @@ describe.skip('', () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ @@ -249,7 +239,6 @@ describe.skip('', () => { const { actions, exists, find } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ @@ -280,7 +269,6 @@ describe.skip('', () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ @@ -302,7 +290,6 @@ describe.skip('', () => { it('should send the correct payload', async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickSubmitButton(); await nextTick(); @@ -333,7 +320,6 @@ describe.skip('', () => { httpRequestsMockHelpers.setCreateTemplateResponse(undefined, { body: error }); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickSubmitButton(); await nextTick(); diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_edit.test.tsx b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_edit.test.tsx index 4056bd2ad63e7c..975d82b9360540 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_edit.test.tsx +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_edit.test.tsx @@ -40,9 +40,7 @@ jest.mock('@elastic/eui', () => ({ ), })); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -61,7 +59,6 @@ describe.skip('', () => { testBed = await setup(); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); testBed.component.update(); @@ -87,7 +84,6 @@ describe.skip('', () => { beforeEach(async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ @@ -108,7 +104,6 @@ describe.skip('', () => { it('should send the correct payload with changed values', async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickSubmitButton(); await nextTick(); diff --git a/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js b/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js index fe919ed7ae6b72..92b46e8e0da003 100644 --- a/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js +++ b/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js @@ -56,7 +56,7 @@ export class EditSettingsJson extends React.PureComponent { } return newSettings; } - componentWillMount() { + UNSAFE_componentWillMount() { const { indexName } = this.props; this.props.loadIndexData({ dataType: TAB_SETTINGS, indexName }); } diff --git a/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/show_json/show_json.js b/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/show_json/show_json.js index d41be90ba6ad3c..854ccba2d3d190 100644 --- a/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/show_json/show_json.js +++ b/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/show_json/show_json.js @@ -10,10 +10,10 @@ import { EuiCodeEditor } from '@elastic/eui'; import 'brace/theme/textmate'; export class ShowJson extends React.PureComponent { - componentWillMount() { + UNSAFE_componentWillMount() { this.props.loadIndexData(this.props); } - componentWillUpdate(newProps) { + UNSAFE_componentWillUpdate(newProps) { const { data, loadIndexData } = newProps; if (!data) { loadIndexData(newProps); diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.test.tsx b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.test.tsx index 932415bfe1afc5..c43e2f5a544cf0 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.test.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.test.tsx @@ -8,7 +8,7 @@ import { fetch } from '../../utils/fetch'; import { useMetricsExplorerData } from './use_metrics_explorer_data'; import { MetricsExplorerAggregation } from '../../../server/routes/metrics_explorer/types'; -import { renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import { options, diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.test.tsx b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.test.tsx index 184655398bd9c2..e58184c78b4b83 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.test.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { renderHook, act } from 'react-hooks-testing-library'; +import { renderHook, act } from '@testing-library/react-hooks'; import { useMetricsExplorerOptions, MetricsExplorerOptionsContainer, @@ -65,7 +65,7 @@ describe('useMetricExplorerOptions', () => { }); it('should change the store when options update', () => { - const { result, waitForNextUpdate } = renderUseMetricsExplorerOptionsHook(); + const { result, rerender } = renderUseMetricsExplorerOptionsHook(); const newOptions: MetricsExplorerOptions = { ...DEFAULT_OPTIONS, metrics: [{ aggregation: MetricsExplorerAggregation.count }], @@ -73,13 +73,13 @@ describe('useMetricExplorerOptions', () => { act(() => { result.current.setOptions(newOptions); }); - waitForNextUpdate(); + rerender(); expect(result.current.options).toEqual(newOptions); expect(STORE.MetricsExplorerOptions).toEqual(JSON.stringify(newOptions)); }); it('should change the store when timerange update', () => { - const { result, waitForNextUpdate } = renderUseMetricsExplorerOptionsHook(); + const { result, rerender } = renderUseMetricsExplorerOptionsHook(); const newTimeRange: MetricsExplorerTimeOptions = { ...DEFAULT_TIMERANGE, from: 'now-15m', @@ -87,7 +87,7 @@ describe('useMetricExplorerOptions', () => { act(() => { result.current.setTimeRange(newTimeRange); }); - waitForNextUpdate(); + rerender(); expect(result.current.currentTimerange).toEqual(newTimeRange); expect(STORE.MetricsExplorerTimeRange).toEqual(JSON.stringify(newTimeRange)); }); diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.test.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.test.tsx index 4c0d95c5529e8e..0512fb0a46b901 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.test.tsx @@ -5,7 +5,7 @@ */ import { fetch } from '../../../utils/fetch'; -import { renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import { useMetricsExplorerState } from './use_metric_explorer_state'; import { MetricsExplorerOptionsContainer } from '../../../containers/metrics_explorer/use_metrics_explorer_options'; import React from 'react'; @@ -172,7 +172,7 @@ describe('useMetricsExplorerState', () => { describe('handleLoadMore', () => { it('should load more based on the afterKey', async () => { - const { result, waitForNextUpdate } = renderUseMetricsExplorerStateHook(); + const { result, waitForNextUpdate, rerender } = renderUseMetricsExplorerStateHook(); expect(result.current.data).toBe(null); expect(result.current.loading).toBe(true); await waitForNextUpdate(); @@ -189,7 +189,7 @@ describe('useMetricsExplorerState', () => { } as any); const { handleLoadMore } = result.current; handleLoadMore(pageInfo.afterKey!); - await waitForNextUpdate(); + await rerender(); expect(result.current.loading).toBe(true); await waitForNextUpdate(); expect(result.current.loading).toBe(false); diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/use_host_ip_to_name.test.ts b/x-pack/legacy/plugins/infra/public/pages/link_to/use_host_ip_to_name.test.ts index 32f3864bbfe4e3..3b61181dfc6e09 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/use_host_ip_to_name.test.ts +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/use_host_ip_to_name.test.ts @@ -6,7 +6,7 @@ import { useHostIpToName } from './use_host_ip_to_name'; import { fetch } from '../../utils/fetch'; -import { renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; const renderUseHostIpToNameHook = () => renderHook(props => useHostIpToName(props.ipAddress, props.indexPattern), { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx index d4cf4f7ffbaa61..f615914360a350 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx @@ -9,7 +9,11 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { EuiComboBox, EuiSideNav, EuiPopover } from '@elastic/eui'; import { changeColumn } from '../state_helpers'; -import { IndexPatternDimensionPanel, IndexPatternDimensionPanelProps } from './dimension_panel'; +import { + IndexPatternDimensionPanel, + IndexPatternDimensionPanelComponent, + IndexPatternDimensionPanelProps, +} from './dimension_panel'; import { DropHandler, DragContextState } from '../../drag_drop'; import { createMockedDragDropContext } from '../mocks'; import { mountWithIntl as mount, shallowWithIntl as shallow } from 'test_utils/enzyme_helpers'; @@ -164,7 +168,7 @@ describe('IndexPatternDimensionPanel', () => { const filterOperations = jest.fn().mockReturnValue(true); wrapper = shallow( - + ); expect(filterOperations).toBeCalled(); @@ -1076,7 +1080,7 @@ describe('IndexPatternDimensionPanel', () => { it('is not droppable if the dragged item has no field', () => { wrapper = shallow( - { it('is not droppable if field is not supported by filterOperations', () => { wrapper = shallow( - { it('is droppable if the field is supported by filterOperations', () => { wrapper = shallow( - { it('is notdroppable if the field belongs to another index pattern', () => { wrapper = shallow( - { }; const testState = dragDropState(); wrapper = shallow( - { }; const testState = dragDropState(); wrapper = shallow( - { }; const testState = dragDropState(); wrapper = shallow( - >; } -export const IndexPatternDimensionPanel = memo(function IndexPatternDimensionPanel( +export const IndexPatternDimensionPanelComponent = function IndexPatternDimensionPanel( props: IndexPatternDimensionPanelProps ) { const layerId = props.layerId; @@ -188,4 +188,6 @@ export const IndexPatternDimensionPanel = memo(function IndexPatternDimensionPan ); -}); +}; + +export const IndexPatternDimensionPanel = memo(IndexPatternDimensionPanelComponent); diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js b/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js index 6b3bc1f1065313..20b130d80a211b 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js +++ b/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js @@ -36,7 +36,7 @@ export class StartTrial extends React.PureComponent { super(props); this.state = { showConfirmation: false }; } - componentWillMount() { + UNSAFE_componentWillMount() { this.props.loadTrialStatus(); } startLicenseTrial = () => { diff --git a/x-pack/legacy/plugins/maps/public/components/map_listing.js b/x-pack/legacy/plugins/maps/public/components/map_listing.js index 5a9cb54109363b..6fb5930e81a209 100644 --- a/x-pack/legacy/plugins/maps/public/components/map_listing.js +++ b/x-pack/legacy/plugins/maps/public/components/map_listing.js @@ -44,7 +44,7 @@ export class MapListing extends React.Component { perPage: 20, }; - componentWillMount() { + UNSAFE_componentWillMount() { this._isMounted = true; } diff --git a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx index 0ac7e5eb093318..98439d76627b9d 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx @@ -5,12 +5,10 @@ */ import React from 'react'; -import { cleanup, render } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { SeverityCell } from './severity_cell'; describe('SeverityCell', () => { - afterEach(cleanup); - test('should render a single-bucket marker with rounded severity score', () => { const props = { score: 75.2, diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/id_badges/id_badges.test.js b/x-pack/legacy/plugins/ml/public/application/components/job_selector/id_badges/id_badges.test.js index 5f94e89ad2ba5b..3f72209f224568 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/id_badges/id_badges.test.js +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/id_badges/id_badges.test.js @@ -4,37 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ - import React from 'react'; -import { cleanup, render } from 'react-testing-library'; +import { render } from '@testing-library/react'; // eslint-disable-line import/no-extraneous-dependencies import { IdBadges } from './id_badges'; - - const props = { limit: 2, maps: { groupsMap: { - 'group1': ['job1', 'job2'], - 'group2': ['job3'] + group1: ['job1', 'job2'], + group2: ['job3'], }, jobsMap: { - 'job1': ['group1'], - 'job2': ['group1'], - 'job3': ['group2'], - 'job4': [] - } + job1: ['group1'], + job2: ['group1'], + job3: ['group2'], + job4: [], + }, }, onLinkClick: jest.fn(), selectedIds: ['group1', 'job1', 'job3'], - showAllBarBadges: false + showAllBarBadges: false, }; -const overLimitProps = { ...props, selectedIds: ['group1', 'job1', 'job3', 'job4'], }; +const overLimitProps = { ...props, selectedIds: ['group1', 'job1', 'job3', 'job4'] }; describe('IdBadges', () => { - afterEach(cleanup); - test('When group selected renders groupId and not corresponding jobIds', () => { const { getByText, queryByText } = render(); // group1 badge should be present @@ -46,7 +41,6 @@ describe('IdBadges', () => { }); describe('showAllBarBadges is false', () => { - test('shows link to show more badges if selection is over limit', () => { const { getByText } = render(); const showMoreLink = getByText('And 1 more'); @@ -58,14 +52,13 @@ describe('IdBadges', () => { const showMoreLink = queryByText(/ more/); expect(showMoreLink).toBeNull(); }); - }); describe('showAllBarBadges is true', () => { const overLimitShowAllProps = { ...props, showAllBarBadges: true, - selectedIds: ['group1', 'job1', 'job3', 'job4'] + selectedIds: ['group1', 'job1', 'job3', 'job4'], }; test('shows all badges when selection is over limit', () => { @@ -86,7 +79,5 @@ describe('IdBadges', () => { const hideLink = getByText('Hide'); expect(hideLink).toBeDefined(); }); - }); - }); diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.test.js b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.test.js index af300e51eef999..41e510459fceae 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.test.js +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.test.js @@ -4,20 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ - - import React from 'react'; -import { cleanup, fireEvent, render } from 'react-testing-library'; +import { fireEvent, render } from '@testing-library/react'; // eslint-disable-line import/no-extraneous-dependencies import { JobSelectorTable } from './job_selector_table'; - jest.mock('../../../services/job_service', () => ({ mlJobService: { - getJob: jest.fn() - } + getJob: jest.fn(), + }, })); - const props = { ganttBarWidth: 299, groupsList: [ @@ -27,8 +23,8 @@ const props = { timeRange: { fromPx: 15.1, label: 'Apr 20th 2019, 20: 39 to Jun 20th 2019, 17: 45', - widthPx: 283.89 - } + widthPx: 283.89, + }, }, { id: 'ecommerce', @@ -36,8 +32,8 @@ const props = { timeRange: { fromPx: 1, label: 'Apr 17th 2019, 20:04 to May 18th 2019, 19:45', - widthPx: 144.5 - } + widthPx: 144.5, + }, }, { id: 'flights', @@ -45,9 +41,9 @@ const props = { timeRange: { fromPx: 19.6, label: 'Apr 21st 2019, 20:00 to Jun 2nd 2019, 19:50', - widthPx: 195.8 - } - } + widthPx: 195.8, + }, + }, ], jobs: [ { @@ -59,8 +55,8 @@ const props = { timeRange: { fromPx: 12.3, label: 'Apr 20th 2019, 20:39 to Jun 20th 2019, 17:45', - widthPx: 228.6 - } + widthPx: 228.6, + }, }, { groups: ['logs'], @@ -71,8 +67,8 @@ const props = { timeRange: { fromPx: 10, label: 'Apr 20th 2019, 20:39 to Jun 20th 2019, 17:45', - widthPx: 182.9 - } + widthPx: 182.9, + }, }, { groups: ['ecommerce'], @@ -83,7 +79,7 @@ const props = { timeRange: { fromPx: 1, label: 'Apr 17th 2019, 20:04 to May 18th 2019, 19:45', - widthPx: 93.1 + widthPx: 93.1, }, }, { @@ -95,19 +91,16 @@ const props = { timeRange: { fromPx: 1, label: 'Apr 17th 2019, 20:04 to May 18th 2019, 19:45', - widthPx: 93.1 + widthPx: 93.1, }, - } + }, ], onSelection: jest.fn(), selectedIds: ['price-by-day'], }; describe('JobSelectorTable', () => { - afterEach(cleanup); - describe('Single Selection', () => { - test('Does not render tabs', () => { const singleSelectionProps = { ...props, singleSelection: true }; const { queryByRole } = render(); @@ -128,28 +121,26 @@ describe('JobSelectorTable', () => { const radioButton = getByTestId('non-timeseries-job-radio-button'); expect(radioButton.firstChild.disabled).toEqual(true); }); - }); describe('Not Single Selection', () => { - test('renders tabs when not singleSelection', () => { - const { getByRole } = render(); - const tabs = getByRole('tab'); + const { getAllByRole } = render(); + const tabs = getAllByRole('tab'); expect(tabs).toBeDefined(); }); test('toggles content when tabs clicked', () => { // Default is Jobs tab so select Groups tab - const { getByText } = render(); + const { getByText, getAllByText } = render(); const groupsTab = getByText('Groups'); fireEvent.click(groupsTab); - const groupsTableHeader = getByText('jobs in group'); + const groupsTableHeader = getAllByText('jobs in group'); expect(groupsTableHeader).toBeDefined(); // switch back to Jobs tab const jobsTab = getByText('Jobs'); fireEvent.click(jobsTab); - const jobsTableHeader = getByText('job ID'); + const jobsTableHeader = getAllByText('job ID'); expect(jobsTableHeader).toBeDefined(); }); @@ -160,7 +151,10 @@ describe('JobSelectorTable', () => { }); test('incoming selectedIds are checked in the table when multiple ids', () => { - const multipleSelectedIdsProps = { ...props, selectedIds: ['price-by-day', 'bytes-by-geo-dest'] }; + const multipleSelectedIdsProps = { + ...props, + selectedIds: ['price-by-day', 'bytes-by-geo-dest'], + }; const { getByTestId } = render(); const priceByDayCheckbox = getByTestId('price-by-day-checkbox'); const bytesByGeoCheckbox = getByTestId('bytes-by-geo-dest-checkbox'); @@ -175,7 +169,5 @@ describe('JobSelectorTable', () => { const groupDropdownButton = getByText('Group'); expect(groupDropdownButton).toBeDefined(); }); - }); - }); diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.test.js b/x-pack/legacy/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.test.js index a9e07c7e15f460..0ddeb15d5c2c76 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.test.js +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.test.js @@ -4,32 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ - import React from 'react'; -import { cleanup, render } from 'react-testing-library'; +import { render } from '@testing-library/react'; // eslint-disable-line import/no-extraneous-dependencies import { NewSelectionIdBadges } from './new_selection_id_badges'; - - const props = { limit: 2, maps: { groupsMap: { - 'group1': ['job1', 'job2'], - 'group2': ['job3'] - } + group1: ['job1', 'job2'], + group2: ['job3'], + }, }, onLinkClick: jest.fn(), onDeleteClick: jest.fn(), newSelection: ['group1', 'job1', 'job3'], - showAllBadges: false + showAllBadges: false, }; describe('NewSelectionIdBadges', () => { - afterEach(cleanup); - describe('showAllBarBadges is false', () => { - test('shows link to show more badges if selection is over limit', () => { const { getByText } = render(); const showMoreLink = getByText('And 1 more'); @@ -37,18 +31,17 @@ describe('NewSelectionIdBadges', () => { }); test('does not show link to show more badges if selection is within limit', () => { - const underLimitProps = { ...props, newSelection: ['group1', 'job1'], }; + const underLimitProps = { ...props, newSelection: ['group1', 'job1'] }; const { queryByText } = render(); const showMoreLink = queryByText(/ more/); expect(showMoreLink).toBeNull(); }); - }); describe('showAllBarBadges is true', () => { const showAllTrueProps = { ...props, - showAllBadges: true + showAllBadges: true, }; test('shows all badges when selection is over limit', () => { @@ -69,7 +62,5 @@ describe('NewSelectionIdBadges', () => { const hideLink = getByText('Hide'); expect(hideLink).toBeDefined(); }); - }); - }); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx index 6fd80c524f8ef7..2a939d93a48b3c 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx @@ -5,8 +5,7 @@ */ import React from 'react'; -import { cleanup, render } from 'react-testing-library'; -import 'jest-dom/extend-expect'; +import { render } from '@testing-library/react'; import * as CheckPrivilige from '../../../../../privilege/check_privilege'; import { queryByTestSubj } from '../../../../../util/test_utils'; @@ -22,8 +21,6 @@ jest.mock('../../../../../privilege/check_privilege', () => ({ jest.mock('ui/new_platform'); describe('DeleteAction', () => { - afterEach(cleanup); - test('When canDeleteDataFrameAnalytics permission is false, button should be disabled.', () => { const { container } = render(); expect(queryByTestSubj(container, 'mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled'); diff --git a/x-pack/legacy/plugins/ml/public/application/util/test_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/test_utils.ts index 00c06757beca8f..5c020840182e57 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/test_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/test_utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { queryHelpers, Matcher } from 'react-testing-library'; +import { queryHelpers } from '@testing-library/react'; /** * 'react-testing-library provides 'queryByTestId()' to get @@ -14,5 +14,5 @@ import { queryHelpers, Matcher } from 'react-testing-library'; * @param {Matcher} id The 'data-test-subj' id. * @returns {HTMLElement | null} */ -export const queryByTestSubj = (container: HTMLElement, id: Matcher) => - queryHelpers.queryByAttribute('data-test-subj', container, id); + +export const queryByTestSubj = queryHelpers.queryByAttribute.bind(null, 'data-test-subj'); diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js index 5443d6cbee6b59..78501aca566f69 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js @@ -47,7 +47,7 @@ export class ChartTarget extends React.Component { return (_metric) => true; } - componentWillReceiveProps(newProps) { + UNSAFE_componentWillReceiveProps(newProps) { if (this.plot && !_.isEqual(newProps, this.props)) { const { series, timeRange } = newProps; diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/timeseries_visualization.js b/x-pack/legacy/plugins/monitoring/public/components/chart/timeseries_visualization.js index 1ae997c5ebaa47..540460478cf53d 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/timeseries_visualization.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/timeseries_visualization.js @@ -101,7 +101,7 @@ export class TimeseriesVisualization extends React.Component { } - componentWillReceiveProps(props) { + UNSAFE_componentWillReceiveProps(props) { const values = this.getLastValues(props); const currentKeys = _.keys(this.state.values); const keys = _.keys(values); diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/cluster_view.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/cluster_view.js index 8c85c40951777c..a4640fa45119b4 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/cluster_view.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/cluster_view.js @@ -43,7 +43,7 @@ export class ClusterView extends React.Component { this.setState({ shardStats: stats }); }; - componentWillMount() { + UNSAFE_componentWillMount() { this.props.scope.$watch('showing', this.setShowing); this.props.scope.$watch(() => this.props.scope.pageData.shardStats, this.setShardStats); } diff --git a/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js b/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js index dadb31f2cc83bb..83c42c6dff37b8 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js +++ b/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js @@ -35,7 +35,7 @@ export class SetupModeRenderer extends React.Component { isSettingUpNew: false, }; - componentWillMount() { + UNSAFE_componentWillMount() { const { scope, injector } = this.props; initSetupModeState(scope, injector, _oldData => { const newState = { renderState: true }; diff --git a/x-pack/legacy/plugins/monitoring/public/components/sparkline/index.js b/x-pack/legacy/plugins/monitoring/public/components/sparkline/index.js index 49d4dcfbf9a66f..6f52a82c40820f 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/sparkline/index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/sparkline/index.js @@ -22,7 +22,7 @@ export class Sparkline extends React.Component { }; } - componentWillReceiveProps({ series, options }) { + UNSAFE_componentWillReceiveProps({ series, options }) { if (!isEqual(options, this.props.options)) { this.sparklineFlotChart.shutdown(); this.makeSparklineFlotChart(options); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx index b91c67b8f7d0ae..c5bf910b007d00 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx @@ -86,7 +86,7 @@ class EditRolePageUI extends Component { }; } - public componentWillMount() { + public UNSAFE_componentWillMount() { if (this.props.action === 'clone' && isReservedRole(this.props.role)) { this.backToRoleList(); } diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx index 910e576e6e1e71..25bd2a9d560593 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx @@ -7,7 +7,7 @@ import { ShallowWrapper, shallow } from 'enzyme'; import * as React from 'react'; -import { AreaChartBaseComponent, AreaChart } from './areachart'; +import { AreaChartBaseComponent, AreaChartComponent } from './areachart'; import { ChartSeriesData } from './common'; import { ScaleType, AreaSeries, Axis } from '@elastic/charts'; @@ -325,7 +325,7 @@ describe('AreaChart', () => { }; describe.each(chartDataSets as Array<[ChartSeriesData[]]>)('with valid data [%o]', data => { beforeAll(() => { - shallowWrapper = shallow(); + shallowWrapper = shallow(); }); it(`should render area chart`, () => { @@ -338,7 +338,7 @@ describe('AreaChart', () => { 'with invalid data [%o]', data => { beforeAll(() => { - shallowWrapper = shallow(); + shallowWrapper = shallow(); }); it(`should render a chart place holder`, () => { diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx index d51f5e081468c5..c644d148cc1c3d 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx @@ -63,12 +63,15 @@ const checkIfAnyValidSeriesExist = ( Array.isArray(data) && data.some(checkIfAllTheDataInTheSeriesAreValid); // https://ela.st/multi-areaseries -export const AreaChartBaseComponent = React.memo<{ +export const AreaChartBaseComponent = ({ + data, + ...chartConfigs +}: { data: ChartSeriesData[]; width: string | null | undefined; height: string | null | undefined; configs?: ChartSeriesConfigs | undefined; -}>(({ data, ...chartConfigs }) => { +}) => { const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); const xAxisId = getAxisId(`group-${data[0].key}-x`); @@ -113,14 +116,21 @@ export const AreaChartBaseComponent = React.memo<{

    ) : null; -}); +}; AreaChartBaseComponent.displayName = 'AreaChartBaseComponent'; -export const AreaChart = React.memo<{ +export const AreaChartBase = React.memo(AreaChartBaseComponent); + +AreaChartBase.displayName = 'AreaChartBase'; + +export const AreaChartComponent = ({ + areaChart, + configs, +}: { areaChart: ChartSeriesData[] | null | undefined; configs?: ChartSeriesConfigs | undefined; -}>(({ areaChart, configs }) => { +}) => { const customHeight = get('customHeight', configs); const customWidth = get('customWidth', configs); @@ -128,7 +138,7 @@ export const AreaChart = React.memo<{ {({ measureRef, content: { height, width } }) => ( - ); -}); +}; + +AreaChartComponent.displayName = 'AreaChartComponent'; + +export const AreaChart = React.memo(AreaChartComponent); AreaChart.displayName = 'AreaChart'; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx index 4b3ec577e64880..e28d330d31ba90 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx @@ -7,7 +7,7 @@ import { shallow, ShallowWrapper } from 'enzyme'; import * as React from 'react'; -import { BarChartBaseComponent, BarChart } from './barchart'; +import { BarChartBaseComponent, BarChartComponent } from './barchart'; import { ChartSeriesData } from './common'; import { BarSeries, ScaleType, Axis } from '@elastic/charts'; @@ -272,7 +272,7 @@ describe.each(chartDataSets)('BarChart with valid data [%o]', data => { let shallowWrapper: ShallowWrapper; beforeAll(() => { - shallowWrapper = shallow(); + shallowWrapper = shallow(); }); it(`should render chart`, () => { @@ -285,7 +285,7 @@ describe.each(chartHolderDataSets)('BarChart with invalid data [%o]', data => { let shallowWrapper: ShallowWrapper; beforeAll(() => { - shallowWrapper = shallow(); + shallowWrapper = shallow(); }); it(`should render chart holder`, () => { diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx index 04bedb827aa402..7218d7a497f19d 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx @@ -45,12 +45,15 @@ const checkIfAnyValidSeriesExist = ( data.some(checkIfAllTheDataInTheSeriesAreValid); // Bar chart rotation: https://ela.st/chart-rotations -export const BarChartBaseComponent = React.memo<{ +export const BarChartBaseComponent = ({ + data, + ...chartConfigs +}: { data: ChartSeriesData[]; width: string | null | undefined; height: string | null | undefined; configs?: ChartSeriesConfigs | undefined; -}>(({ data, ...chartConfigs }) => { +}) => { const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); const tickSize = getOr(0, 'configs.axis.tickSize', chartConfigs); @@ -96,14 +99,21 @@ export const BarChartBaseComponent = React.memo<{ ) : null; -}); +}; BarChartBaseComponent.displayName = 'BarChartBaseComponent'; -export const BarChart = React.memo<{ +export const BarChartBase = React.memo(BarChartBaseComponent); + +BarChartBase.displayName = 'BarChartBase'; + +export const BarChartComponent = ({ + barChart, + configs, +}: { barChart: ChartSeriesData[] | null | undefined; configs?: ChartSeriesConfigs | undefined; -}>(({ barChart, configs }) => { +}) => { const customHeight = get('customHeight', configs); const customWidth = get('customWidth', configs); return checkIfAnyValidSeriesExist(barChart) ? ( @@ -126,6 +136,10 @@ export const BarChart = React.memo<{ data={barChart} /> ); -}); +}; + +BarChartComponent.displayName = 'BarChartComponent'; + +export const BarChart = React.memo(BarChartComponent); BarChart.displayName = 'BarChart'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap index bf0dfd9417875a..2444fd0bc2b7d4 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EmbeddedMap renders correctly against snapshot 1`] = ` +exports[`EmbeddedMapComponent renders correctly against snapshot 1`] = ` ({ timezoneProvider: () => () => 'America/New_York', })); -describe('EmbeddedMap', () => { +describe('EmbeddedMapComponent', () => { let setQuery: SetQuery; beforeEach(() => { @@ -48,7 +48,7 @@ describe('EmbeddedMap', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - ( - ({ endDate, filters, query, setQuery, startDate }) => { - const [embeddable, setEmbeddable] = React.useState(null); - const [isLoading, setIsLoading] = useState(true); - const [isError, setIsError] = useState(false); - const [isIndexError, setIsIndexError] = useState(false); - - const [, dispatchToaster] = useStateToaster(); - const [loadingKibanaIndexPatterns, kibanaIndexPatterns] = useIndexPatterns(); - const [siemDefaultIndices] = useKibanaUiSetting(DEFAULT_INDEX_KEY); - - // This portalNode provided by react-reverse-portal allows us re-parent the MapToolTip within our - // own component tree instead of the embeddables (default). This is necessary to have access to - // the Redux store, theme provider, etc, which is required to register and un-register the draggable - // Search InPortal/OutPortal for implementation touch points - const portalNode = React.useMemo(() => createPortalNode(), []); - - const plugins = useKibanaPlugins(); - const core = useKibanaCore(); - - // Setup embeddables API (i.e. detach extra actions) useEffect - useEffect(() => { - try { - setupEmbeddablesAPI(plugins); - } catch (e) { - displayErrorToast(i18n.ERROR_CONFIGURING_EMBEDDABLES_API, e.message, dispatchToaster); +export const EmbeddedMapComponent = ({ + endDate, + filters, + query, + setQuery, + startDate, +}: EmbeddedMapProps) => { + const [embeddable, setEmbeddable] = React.useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isError, setIsError] = useState(false); + const [isIndexError, setIsIndexError] = useState(false); + + const [, dispatchToaster] = useStateToaster(); + const [loadingKibanaIndexPatterns, kibanaIndexPatterns] = useIndexPatterns(); + const [siemDefaultIndices] = useKibanaUiSetting(DEFAULT_INDEX_KEY); + + // This portalNode provided by react-reverse-portal allows us re-parent the MapToolTip within our + // own component tree instead of the embeddables (default). This is necessary to have access to + // the Redux store, theme provider, etc, which is required to register and un-register the draggable + // Search InPortal/OutPortal for implementation touch points + const portalNode = React.useMemo(() => createPortalNode(), []); + + const plugins = useKibanaPlugins(); + const core = useKibanaCore(); + + // Setup embeddables API (i.e. detach extra actions) useEffect + useEffect(() => { + try { + setupEmbeddablesAPI(plugins); + } catch (e) { + displayErrorToast(i18n.ERROR_CONFIGURING_EMBEDDABLES_API, e.message, dispatchToaster); + setIsLoading(false); + setIsError(true); + } + }, []); + + // Initial Load useEffect + useEffect(() => { + let isSubscribed = true; + async function setupEmbeddable() { + // Ensure at least one `siem:defaultIndex` index pattern exists before trying to import + const matchingIndexPatterns = kibanaIndexPatterns.filter(ip => + siemDefaultIndices.includes(ip.attributes.title) + ); + if (matchingIndexPatterns.length === 0 && isSubscribed) { setIsLoading(false); - setIsError(true); + setIsIndexError(true); + return; } - }, []); - - // Initial Load useEffect - useEffect(() => { - let isSubscribed = true; - async function setupEmbeddable() { - // Ensure at least one `siem:defaultIndex` index pattern exists before trying to import - const matchingIndexPatterns = kibanaIndexPatterns.filter(ip => - siemDefaultIndices.includes(ip.attributes.title) - ); - if (matchingIndexPatterns.length === 0 && isSubscribed) { - setIsLoading(false); - setIsIndexError(true); - return; - } - // Create & set Embeddable - try { - const embeddableObject = await createEmbeddable( - filters, - getIndexPatternTitleIdMapping(matchingIndexPatterns), - query, - startDate, - endDate, - setQuery, - portalNode, - plugins.embeddable - ); - if (isSubscribed) { - setEmbeddable(embeddableObject); - } - } catch (e) { - if (isSubscribed) { - displayErrorToast(i18n.ERROR_CREATING_EMBEDDABLE, e.message, dispatchToaster); - setIsError(true); - } + // Create & set Embeddable + try { + const embeddableObject = await createEmbeddable( + filters, + getIndexPatternTitleIdMapping(matchingIndexPatterns), + query, + startDate, + endDate, + setQuery, + portalNode, + plugins.embeddable + ); + if (isSubscribed) { + setEmbeddable(embeddableObject); } + } catch (e) { if (isSubscribed) { - setIsLoading(false); + displayErrorToast(i18n.ERROR_CREATING_EMBEDDABLE, e.message, dispatchToaster); + setIsError(true); } } - - if (!loadingKibanaIndexPatterns) { - setupEmbeddable(); + if (isSubscribed) { + setIsLoading(false); } - return () => { - isSubscribed = false; + } + + if (!loadingKibanaIndexPatterns) { + setupEmbeddable(); + } + return () => { + isSubscribed = false; + }; + }, [loadingKibanaIndexPatterns, kibanaIndexPatterns]); + + // queryExpression updated useEffect + useEffect(() => { + if (embeddable != null) { + embeddable.updateInput({ query }); + } + }, [query]); + + useEffect(() => { + if (embeddable != null) { + embeddable.updateInput({ filters }); + } + }, [filters]); + + // DateRange updated useEffect + useEffect(() => { + if (embeddable != null && startDate != null && endDate != null) { + const timeRange = { + from: new Date(startDate).toISOString(), + to: new Date(endDate).toISOString(), }; - }, [loadingKibanaIndexPatterns, kibanaIndexPatterns]); - - // queryExpression updated useEffect - useEffect(() => { - if (embeddable != null) { - embeddable.updateInput({ query }); - } - }, [query]); - - useEffect(() => { - if (embeddable != null) { - embeddable.updateInput({ filters }); - } - }, [filters]); - - // DateRange updated useEffect - useEffect(() => { - if (embeddable != null && startDate != null && endDate != null) { - const timeRange = { - from: new Date(startDate).toISOString(), - to: new Date(endDate).toISOString(), - }; - embeddable.updateInput({ timeRange }); - } - }, [startDate, endDate]); - - return isError ? null : ( - - - - - {i18n.EMBEDDABLE_HEADER_HELP} - - - - - - - - - - {embeddable != null ? ( - - ) : !isLoading && isIndexError ? ( - - ) : ( - - )} - - - ); - } -); + embeddable.updateInput({ timeRange }); + } + }, [startDate, endDate]); + + return isError ? null : ( + + + + + {i18n.EMBEDDABLE_HEADER_HELP} + + + + + + + + + + {embeddable != null ? ( + + ) : !isLoading && isIndexError ? ( + + ) : ( + + )} + + + ); +}; + +EmbeddedMapComponent.displayName = 'EmbeddedMapComponent'; + +export const EmbeddedMap = React.memo(EmbeddedMapComponent); EmbeddedMap.displayName = 'EmbeddedMap'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx index 48a49835b284fc..d32b62900431c2 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx @@ -7,7 +7,7 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt'; +import { IndexPatternsMissingPromptComponent } from './index_patterns_missing_prompt'; jest.mock('ui/documentation_links', () => ({ ELASTIC_WEBSITE_URL: 'https://www.elastic.co', @@ -16,7 +16,7 @@ jest.mock('ui/documentation_links', () => ({ describe('IndexPatternsMissingPrompt', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx index e71398455ee883..1e29676415d79f 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx @@ -12,7 +12,7 @@ import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; import * as i18n from './translations'; -export const IndexPatternsMissingPrompt = React.memo(() => ( +export const IndexPatternsMissingPromptComponent = () => ( {i18n.ERROR_TITLE}} @@ -58,4 +58,10 @@ export const IndexPatternsMissingPrompt = React.memo(() => ( } /> -)); +); + +IndexPatternsMissingPromptComponent.displayName = 'IndexPatternsMissingPromptComponent'; + +export const IndexPatternsMissingPrompt = React.memo(IndexPatternsMissingPromptComponent); + +IndexPatternsMissingPrompt.displayName = 'IndexPatternsMissingPrompt'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap index 2a17a2aae84973..2ef4d9df89a1bd 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap @@ -2,7 +2,7 @@ exports[`PointToolTipContent renders correctly against snapshot 1`] = ` - { test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx index 7cdf3a545a2d6d..0c416868bfb031 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx @@ -26,41 +26,46 @@ interface LineToolTipContentProps { featureProps: FeatureProperty[]; } -export const LineToolTipContent = React.memo( - ({ contextId, featureProps }) => { - const lineProps = featureProps.reduce>( - (acc, f) => ({ - ...acc, - ...{ [f._propertyKey]: Array.isArray(f._rawValue) ? f._rawValue : [f._rawValue] }, - }), - {} - ); +export const LineToolTipContentComponent = ({ + contextId, + featureProps, +}: LineToolTipContentProps) => { + const lineProps = featureProps.reduce>( + (acc, f) => ({ + ...acc, + ...{ [f._propertyKey]: Array.isArray(f._rawValue) ? f._rawValue : [f._rawValue] }, + }), + {} + ); - return ( - - - - - {i18n.SOURCE} - - - - - - - - {i18n.DESTINATION} - - - - - ); - } -); + return ( + + + + + {i18n.SOURCE} + + + + + + + + {i18n.DESTINATION} + + + + + ); +}; + +LineToolTipContentComponent.displayName = 'LineToolTipContentComponent'; + +export const LineToolTipContent = React.memo(LineToolTipContentComponent); LineToolTipContent.displayName = 'LineToolTipContent'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx index a73e6dabc68ae0..13eefb252fb04b 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx @@ -7,7 +7,7 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { MapToolTip } from './map_tool_tip'; +import { MapToolTipComponent } from './map_tool_tip'; import { MapFeature } from '../types'; jest.mock('../../search_bar', () => ({ @@ -18,7 +18,7 @@ jest.mock('../../search_bar', () => ({ describe('MapToolTip', () => { test('placeholder component renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); @@ -36,7 +36,7 @@ describe('MapToolTip', () => { const loadFeatureGeometry = jest.fn(); const wrapper = shallow( - ( - ({ - addFilters, - closeTooltip, - features = [], - isLocked, - getLayerName, - loadFeatureProperties, - loadFeatureGeometry, - }) => { - const [isLoading, setIsLoading] = useState(true); - const [isLoadingNextFeature, setIsLoadingNextFeature] = useState(false); - const [isError, setIsError] = useState(false); - const [featureIndex, setFeatureIndex] = useState(0); - const [featureProps, setFeatureProps] = useState([]); - const [featureGeometry, setFeatureGeometry] = useState(null); - const [, setLayerName] = useState(''); +export const MapToolTipComponent = ({ + addFilters, + closeTooltip, + features = [], + isLocked, + getLayerName, + loadFeatureProperties, + loadFeatureGeometry, +}: MapToolTipProps) => { + const [isLoading, setIsLoading] = useState(true); + const [isLoadingNextFeature, setIsLoadingNextFeature] = useState(false); + const [isError, setIsError] = useState(false); + const [featureIndex, setFeatureIndex] = useState(0); + const [featureProps, setFeatureProps] = useState([]); + const [featureGeometry, setFeatureGeometry] = useState(null); + const [, setLayerName] = useState(''); - useEffect(() => { - // Early return if component doesn't yet have props -- result of mounting in portal before actual rendering - if ( - features.length === 0 || - getLayerName == null || - loadFeatureProperties == null || - loadFeatureGeometry == null - ) { - return; - } + useEffect(() => { + // Early return if component doesn't yet have props -- result of mounting in portal before actual rendering + if ( + features.length === 0 || + getLayerName == null || + loadFeatureProperties == null || + loadFeatureGeometry == null + ) { + return; + } - // Separate loaders for initial load vs loading next feature to keep tooltip from drastically resizing - if (!isLoadingNextFeature) { - setIsLoading(true); - } - setIsError(false); + // Separate loaders for initial load vs loading next feature to keep tooltip from drastically resizing + if (!isLoadingNextFeature) { + setIsLoading(true); + } + setIsError(false); - const fetchFeatureProps = async () => { - if (features[featureIndex] != null) { - const layerId = features[featureIndex].layerId; - const featureId = features[featureIndex].id; + const fetchFeatureProps = async () => { + if (features[featureIndex] != null) { + const layerId = features[featureIndex].layerId; + const featureId = features[featureIndex].id; - try { - const featureGeo = loadFeatureGeometry({ layerId, featureId }); - const [featureProperties, layerNameString] = await Promise.all([ - loadFeatureProperties({ layerId, featureId }), - getLayerName(layerId), - ]); + try { + const featureGeo = loadFeatureGeometry({ layerId, featureId }); + const [featureProperties, layerNameString] = await Promise.all([ + loadFeatureProperties({ layerId, featureId }), + getLayerName(layerId), + ]); - setFeatureProps(featureProperties); - setFeatureGeometry(featureGeo); - setLayerName(layerNameString); - } catch (e) { - setIsError(true); - } finally { - setIsLoading(false); - setIsLoadingNextFeature(false); - } + setFeatureProps(featureProperties); + setFeatureGeometry(featureGeo); + setLayerName(layerNameString); + } catch (e) { + setIsError(true); + } finally { + setIsLoading(false); + setIsLoadingNextFeature(false); } - }; - - fetchFeatureProps(); - }, [ - featureIndex, - features - .map(f => `${f.id}-${f.layerId}`) - .sort() - .join(), - ]); + } + }; - if (isError) { - return ( - - {i18n.MAP_TOOL_TIP_ERROR} - - ); - } + fetchFeatureProps(); + }, [ + featureIndex, + features + .map(f => `${f.id}-${f.layerId}`) + .sort() + .join(), + ]); - return isLoading && !isLoadingNextFeature ? ( + if (isError) { + return ( - - - + {i18n.MAP_TOOL_TIP_ERROR} - ) : ( - - { - if (closeTooltip != null) { - closeTooltip(); - setFeatureIndex(0); - } - }} - > -
    - {featureGeometry != null && featureGeometry.type === 'LineString' ? ( - - ) : ( - - )} - {features.length > 1 && ( - { - setFeatureIndex(featureIndex - 1); - setIsLoadingNextFeature(true); - }} - nextFeature={() => { - setFeatureIndex(featureIndex + 1); - setIsLoadingNextFeature(true); - }} - /> - )} - {isLoadingNextFeature && } -
    -
    -
    ); } -); + + return isLoading && !isLoadingNextFeature ? ( + + + + + + ) : ( + + { + if (closeTooltip != null) { + closeTooltip(); + setFeatureIndex(0); + } + }} + > +
    + {featureGeometry != null && featureGeometry.type === 'LineString' ? ( + + ) : ( + + )} + {features.length > 1 && ( + { + setFeatureIndex(featureIndex - 1); + setIsLoadingNextFeature(true); + }} + nextFeature={() => { + setFeatureIndex(featureIndex + 1); + setIsLoadingNextFeature(true); + }} + /> + )} + {isLoadingNextFeature && } +
    +
    +
    + ); +}; + +MapToolTipComponent.displayName = 'MapToolTipComponent'; + +export const MapToolTip = React.memo(MapToolTipComponent); MapToolTip.displayName = 'MapToolTip'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx index 567f091e78cb56..1733fb3aa74809 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx @@ -8,7 +8,7 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import { FeatureProperty } from '../types'; -import { getRenderedFieldValue, PointToolTipContent } from './point_tool_tip_content'; +import { getRenderedFieldValue, PointToolTipContentComponent } from './point_tool_tip_content'; import { TestProviders } from '../../../mock'; import { getEmptyStringTag } from '../../empty_value'; import { HostDetailsLink, IPDetailsLink } from '../../links'; @@ -39,7 +39,7 @@ describe('PointToolTipContent', () => { const wrapper = shallow( - { const wrapper = mount( - ( - ({ contextId, featureProps, closeTooltip }) => { - const featureDescriptionListItems = featureProps.map( - ({ _propertyKey: key, _rawValue: value }) => ({ - title: sourceDestinationFieldMappings[key], - description: ( - - {value != null ? ( - getRenderedFieldValue(key, item)} - /> - ) : ( - getEmptyTagValue() - )} - - ), - }) - ); +export const PointToolTipContentComponent = ({ + contextId, + featureProps, + closeTooltip, +}: PointToolTipContentProps) => { + const featureDescriptionListItems = featureProps.map( + ({ _propertyKey: key, _rawValue: value }) => ({ + title: sourceDestinationFieldMappings[key], + description: ( + + {value != null ? ( + getRenderedFieldValue(key, item)} + /> + ) : ( + getEmptyTagValue() + )} + + ), + }) + ); - return ; - } -); + return ; +}; + +PointToolTipContentComponent.displayName = 'PointToolTipContentComponent'; + +export const PointToolTipContent = React.memo(PointToolTipContentComponent); PointToolTipContent.displayName = 'PointToolTipContent'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx index f2673c17d246cf..4c77570cfbc9f2 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx @@ -7,7 +7,7 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { ToolTipFooter } from './tooltip_footer'; +import { ToolTipFooterComponent } from './tooltip_footer'; describe('ToolTipFilter', () => { let nextFeature = jest.fn(); @@ -20,7 +20,7 @@ describe('ToolTipFilter', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - { describe('Lower bounds', () => { test('previousButton is disabled when featureIndex is 0', () => { const wrapper = mount( - { test('previousFeature is not called when featureIndex is 0', () => { const wrapper = mount( - { test('nextButton is enabled when featureIndex is < totalFeatures', () => { const wrapper = mount( - { test('nextFeature is called when featureIndex is < totalFeatures', () => { const wrapper = mount( - { describe('Upper bounds', () => { test('previousButton is enabled when featureIndex >== totalFeatures', () => { const wrapper = mount( - { test('previousFunction is called when featureIndex >== totalFeatures', () => { const wrapper = mount( - { test('nextButton is disabled when featureIndex >== totalFeatures', () => { const wrapper = mount( - { test('nextFunction is not called when featureIndex >== totalFeatures', () => { const wrapper = mount( - { describe('Within bounds, single feature', () => { test('previousButton is not enabled when only a single feature is provided', () => { const wrapper = mount( - { test('previousFunction is not called when only a single feature is provided', () => { const wrapper = mount( - { test('nextButton is not enabled when only a single feature is provided', () => { const wrapper = mount( - { test('nextFunction is not called when only a single feature is provided', () => { const wrapper = mount( - { describe('Within bounds, multiple features', () => { test('previousButton is enabled when featureIndex > 0 && featureIndex < totalFeatures', () => { const wrapper = mount( - { test('previousFunction is called when featureIndex > 0 && featureIndex < totalFeatures', () => { const wrapper = mount( - { test('nextButton is enabled when featureIndex > 0 && featureIndex < totalFeatures', () => { const wrapper = mount( - { test('nextFunction is called when featureIndex > 0 && featureIndex < totalFeatures', () => { const wrapper = mount( - void; } -export const ToolTipFooter = React.memo( - ({ featureIndex, totalFeatures, previousFeature, nextFeature }) => { - return ( - <> - - - - - {i18n.MAP_TOOL_TIP_FEATURES_FOOTER(featureIndex + 1, totalFeatures)} - - - - - - = totalFeatures - 1} - /> - - - - - ); - } -); +export const ToolTipFooterComponent = ({ + featureIndex, + totalFeatures, + previousFeature, + nextFeature, +}: MapToolTipFooterProps) => { + return ( + <> + + + + + {i18n.MAP_TOOL_TIP_FEATURES_FOOTER(featureIndex + 1, totalFeatures)} + + + + + + = totalFeatures - 1} + /> + + + + + ); +}; + +ToolTipFooterComponent.displayName = 'ToolTipFooterComponent'; + +export const ToolTipFooter = React.memo(ToolTipFooterComponent); ToolTipFooter.displayName = 'ToolTipFooter'; diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx index 4e59acc4f67136..25b2427d34d6ab 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx @@ -27,17 +27,8 @@ mockUseKibanaCore.mockImplementation(() => ({ const from = 1566943856794; const to = 1566857456791; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; -describe('EventsViewer', () => { - beforeAll(() => { - console.error = jest.fn(); - }); - afterAll(() => { - console.error = originalError; - }); +describe('EventsViewer', () => { test('it renders the "Showing..." subtitle with the expected event count', async () => { const wrapper = mount( diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx index 671711c60bd165..4d2e44f9a3d92b 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx @@ -27,17 +27,7 @@ mockUseKibanaCore.mockImplementation(() => ({ const from = 1566943856794; const to = 1566857456791; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; describe('StatefulEventsViewer', () => { - beforeAll(() => { - console.error = jest.fn(); - }); - - afterAll(() => { - console.error = originalError; - }); test('it renders the events viewer', async () => { const wrapper = mount( diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx index e943ca6f3e8631..54847cda281f49 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx @@ -12,24 +12,15 @@ import { TestProviders } from '../../mock'; import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from './helpers'; -import { StatefulFieldsBrowser } from '.'; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; -describe('StatefulFieldsBrowser', () => { - beforeAll(() => { - console.error = jest.fn(); - }); +import { StatefulFieldsBrowserComponent } from '.'; - afterAll(() => { - console.error = originalError; - }); +describe('StatefulFieldsBrowser', () => { const timelineId = 'test'; test('it renders the Fields button, which displays the fields browser on click', () => { const wrapper = mount( - { test('it does NOT render the fields browser until the Fields button is clicked', () => { const wrapper = mount( - { test('it renders the fields browser when the Fields button is clicked', () => { const wrapper = mount( - { test('it updates the selectedCategoryId state, which makes the category bold, when the user clicks a category name in the left hand side of the field browser', () => { const wrapper = mount( - { test('it updates the selectedCategoryId state according to most fields returned', () => { const wrapper = mount( - { const wrapper = mount( - { const wrapper = mount( - { const wrapper = mount( - ({ @@ -27,13 +27,13 @@ describe('formatted_bytes', () => { mockUseKibanaUiSetting.mockImplementation( getMockKibanaUiSetting(mockFrameworks.default_browser) ); - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); test('it renders bytes to hardcoded format when no configuration exists', () => { mockUseKibanaUiSetting.mockImplementation(() => [null]); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toEqual('2.7MB'); }); @@ -41,7 +41,7 @@ describe('formatted_bytes', () => { mockUseKibanaUiSetting.mockImplementation( getMockKibanaUiSetting(mockFrameworks.default_browser) ); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toEqual('2.7MB'); }); @@ -49,7 +49,7 @@ describe('formatted_bytes', () => { mockUseKibanaUiSetting.mockImplementation( getMockKibanaUiSetting(mockFrameworks.default_browser) ); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toEqual('2.7MB'); }); @@ -57,7 +57,7 @@ describe('formatted_bytes', () => { mockUseKibanaUiSetting.mockImplementation( getMockKibanaUiSetting(mockFrameworks.bytes_short) ); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toEqual('3MB'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx index 76d2c1ea7e3d08..408e8d7ad4d802 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx @@ -10,11 +10,15 @@ import numeral from '@elastic/numeral'; import { DEFAULT_BYTES_FORMAT } from '../../../common/constants'; import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; -export const PreferenceFormattedBytes = React.memo<{ value: string | number }>(({ value }) => { +export const PreferenceFormattedBytesComponent = ({ value }: { value: string | number }) => { const [bytesFormat] = useKibanaUiSetting(DEFAULT_BYTES_FORMAT); return ( <>{bytesFormat ? numeral(value).format(bytesFormat) : numeral(value).format('0,0.[0]b')} ); -}); +}; + +PreferenceFormattedBytesComponent.displayName = 'PreferenceFormattedBytesComponent'; + +export const PreferenceFormattedBytes = React.memo(PreferenceFormattedBytesComponent); PreferenceFormattedBytes.displayName = 'PreferenceFormattedBytes'; diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/__snapshots__/markdown_hint.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/markdown/__snapshots__/markdown_hint.test.tsx.snap index 60464c46f1ac07..7f350072439c52 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/__snapshots__/markdown_hint.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/markdown/__snapshots__/markdown_hint.test.tsx.snap @@ -1,7 +1,55 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MarkdownHint rendering it renders the expected hints 1`] = ` - +exports[`MarkdownHintComponent rendering it renders the expected hints 1`] = ` + + + # heading + + + **bold** + + + _italics_ + + + \`code\` + + + [link](url) + + + * bullet + + + \`\`\`preformatted\`\`\` + + + >quote + + ~~ + + strikethrough + + ~~ + + ![image](url) + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx index 6319af3e6ffa13..c3268270919e28 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx @@ -8,11 +8,11 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { MarkdownHint } from './markdown_hint'; +import { MarkdownHintComponent } from './markdown_hint'; -describe('MarkdownHint', () => { +describe.skip('MarkdownHintComponent ', () => { test('it has inline visibility when show is true', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="markdown-hint"]').first()).toHaveStyleRule( 'visibility', @@ -21,7 +21,7 @@ describe('MarkdownHint', () => { }); test('it has hidden visibility when show is false', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="markdown-hint"]').first()).toHaveStyleRule( 'visibility', @@ -30,7 +30,7 @@ describe('MarkdownHint', () => { }); test('it renders the heading hint', () => { - const wrapper = mount(); + const wrapper = mount(); expect( wrapper @@ -41,7 +41,7 @@ describe('MarkdownHint', () => { }); test('it renders the bold hint with a bold font-weight', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="bold-hint"]').first()).toHaveStyleRule( 'font-weight', @@ -50,7 +50,7 @@ describe('MarkdownHint', () => { }); test('it renders the italic hint with an italic font-style', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="italic-hint"]').first()).toHaveStyleRule( 'font-style', @@ -59,7 +59,7 @@ describe('MarkdownHint', () => { }); test('it renders the code hint with a monospace font family', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="code-hint"]').first()).toHaveStyleRule( 'font-family', @@ -68,7 +68,7 @@ describe('MarkdownHint', () => { }); test('it renders the preformatted hint with a monospace font family', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="preformatted-hint"]').first()).toHaveStyleRule( 'font-family', @@ -77,7 +77,7 @@ describe('MarkdownHint', () => { }); test('it renders the strikethrough hint with a line-through text-decoration', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="strikethrough-hint"]').first()).toHaveStyleRule( 'text-decoration', @@ -87,7 +87,7 @@ describe('MarkdownHint', () => { describe('rendering', () => { test('it renders the expected hints', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx index 18f3a35a23f7f5..5ecd1d4c9d2ad3 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx @@ -6,7 +6,6 @@ import { EuiText } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import * as i18n from './translations'; @@ -62,7 +61,7 @@ const TrailingWhitespace = styled.span` TrailingWhitespace.displayName = 'TrailingWhitespace'; -export const MarkdownHint = pure<{ show: boolean }>(({ show }) => ( +export const MarkdownHintComponent = ({ show }: { show: boolean }) => ( (({ show }) => ( {'~~'} {i18n.MARKDOWN_HINT_IMAGE_URL} -)); +); + +MarkdownHintComponent.displayName = 'MarkdownHintComponent'; + +export const MarkdownHint = React.memo(MarkdownHintComponent); MarkdownHint.displayName = 'MarkdownHint'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx index e3894ee6e7c66f..c401075af42ce2 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx @@ -7,13 +7,17 @@ import React from 'react'; import toJson from 'enzyme-to-json'; import { shallow, mount } from 'enzyme'; -import { EntityDraggable } from './entity_draggable'; +import { EntityDraggableComponent } from './entity_draggable'; import { TestProviders } from '../../mock/test_providers'; describe('entity_draggable', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); @@ -21,7 +25,11 @@ describe('entity_draggable', () => { test('renders with entity name with entity value as text', () => { const wrapper = mount( - + ); expect(wrapper.text()).toEqual('entity-name: "entity-value"'); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.tsx index d7f25c49fd7cae..b0636b08a56346 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.tsx @@ -16,37 +16,43 @@ interface Props { entityValue: string; } -export const EntityDraggable = React.memo( - ({ idPrefix, entityName, entityValue }): JSX.Element => { - const id = escapeDataProviderId(`entity-draggable-${idPrefix}-${entityName}-${entityValue}`); - return ( - - snapshot.isDragging ? ( - - - - ) : ( - <>{`${entityName}: "${entityValue}"`} - ) - } - /> - ); - } -); +export const EntityDraggableComponent = ({ + idPrefix, + entityName, + entityValue, +}: Props): JSX.Element => { + const id = escapeDataProviderId(`entity-draggable-${idPrefix}-${entityName}-${entityValue}`); + return ( + + snapshot.isDragging ? ( + + + + ) : ( + <>{`${entityName}: "${entityValue}"`} + ) + } + /> + ); +}; + +EntityDraggableComponent.displayName = 'EntityDraggableComponent'; + +export const EntityDraggable = React.memo(EntityDraggableComponent); EntityDraggable.displayName = 'EntityDraggable'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx index 3509d92ce70513..a28077ba63ddd6 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx @@ -8,7 +8,7 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; import * as React from 'react'; -import { AnomalyScore } from './anomaly_score'; +import { AnomalyScoreComponent } from './anomaly_score'; import { mockAnomalies } from '../mock'; import { TestProviders } from '../../../mock/test_providers'; import { Anomalies } from '../types'; @@ -26,7 +26,7 @@ describe('anomaly_scores', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - { test('should not show a popover on initial render', () => { const wrapper = mount( - { test('show a popover on a mouse click', () => { const wrapper = mount( - ( - ({ jobKey, startDate, endDate, index = 0, score, interval, narrowDateRange }): JSX.Element => { - const [isOpen, setIsOpen] = useState(false); - return ( - <> - - { + const [isOpen, setIsOpen] = useState(false); + return ( + <> + + + + + setIsOpen(!isOpen)} + closePopover={() => setIsOpen(!isOpen)} + button={} + > + - - - setIsOpen(!isOpen)} - closePopover={() => setIsOpen(!isOpen)} - button={} - > - - - - - ); - } -); + + + + ); +}; + +AnomalyScoreComponent.displayName = 'AnomalyScoreComponent'; + +export const AnomalyScore = React.memo(AnomalyScoreComponent); AnomalyScore.displayName = 'AnomalyScore'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx index 17d36ffcc90998..5bd11169e48408 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx @@ -8,7 +8,7 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; import * as React from 'react'; -import { AnomalyScores, createJobKey } from './anomaly_scores'; +import { AnomalyScoresComponent, createJobKey } from './anomaly_scores'; import { mockAnomalies } from '../mock'; import { TestProviders } from '../../../mock/test_providers'; import { getEmptyValue } from '../../empty_value'; @@ -28,7 +28,7 @@ describe('anomaly_scores', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - { test('renders spinner when isLoading is true is passed', () => { const wrapper = mount( - { test('does NOT render spinner when isLoading is false is passed', () => { const wrapper = mount( - { test('renders an empty value if anomalies is null', () => { const wrapper = mount( - { anomalies.anomalies = []; const wrapper = mount( - { test('should not show a popover on initial render', () => { const wrapper = mount( - { test('showing a popover on a mouse click', () => { const wrapper = mount( - `${score.jobId}-${score.severity}-${score.entityName}-${score.entityValue}`; -export const AnomalyScores = React.memo( - ({ anomalies, startDate, endDate, isLoading, narrowDateRange, limit }): JSX.Element => { - if (isLoading) { - return ; - } else if (anomalies == null || anomalies.anomalies.length === 0) { - return getEmptyTagValue(); - } else { - return ( - <> - - {getTopSeverityJobs(anomalies.anomalies, limit).map((score, index) => { - const jobKey = createJobKey(score); - return ( - - ); - })} - - - ); - } +export const AnomalyScoresComponent = ({ + anomalies, + startDate, + endDate, + isLoading, + narrowDateRange, + limit, +}: Args): JSX.Element => { + if (isLoading) { + return ; + } else if (anomalies == null || anomalies.anomalies.length === 0) { + return getEmptyTagValue(); + } else { + return ( + <> + + {getTopSeverityJobs(anomalies.anomalies, limit).map((score, index) => { + const jobKey = createJobKey(score); + return ( + + ); + })} + + + ); } -); +}; + +AnomalyScoresComponent.displayName = 'AnomalyScoresComponent'; + +export const AnomalyScores = React.memo(AnomalyScoresComponent); AnomalyScores.displayName = 'AnomalyScores'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx index eec0c65c7679f5..0d389ae14a8255 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx @@ -9,7 +9,7 @@ import toJson from 'enzyme-to-json'; import { mockAnomalies } from '../mock'; import { cloneDeep } from 'lodash/fp'; import { shallow } from 'enzyme'; -import { DraggableScore } from './draggable_score'; +import { DraggableScoreComponent } from './draggable_score'; describe('draggable_score', () => { let anomalies = cloneDeep(mockAnomalies); @@ -20,13 +20,15 @@ describe('draggable_score', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); test('renders correctly against snapshot when the index is not included', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.tsx index d156b5f0463f62..6ae31c0ac1fb9b 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.tsx @@ -12,46 +12,52 @@ import { Provider } from '../../timeline/data_providers/provider'; import { Spacer } from '../../page'; import { getScoreString } from './score_health'; -export const DraggableScore = React.memo<{ +export const DraggableScoreComponent = ({ + id, + index = 0, + score, +}: { id: string; index?: number; score: Anomaly; -}>( - ({ id, index = 0, score }): JSX.Element => ( - - snapshot.isDragging ? ( - - - - ) : ( - <> - {index !== 0 && ( - <> - {','} - - - )} - {getScoreString(score.severity)} - - ) - } - /> - ) +}): JSX.Element => ( + + snapshot.isDragging ? ( + + + + ) : ( + <> + {index !== 0 && ( + <> + {','} + + + )} + {getScoreString(score.severity)} + + ) + } + /> ); +DraggableScoreComponent.displayName = 'DraggableScoreComponent'; + +export const DraggableScore = React.memo(DraggableScoreComponent); + DraggableScore.displayName = 'DraggableScore'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap index 2c4f750ffeac5d..983eb2409bd775 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`JobsTable renders correctly against snapshot 1`] = ` +exports[`JobsTableComponent renders correctly against snapshot 1`] = ` { test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); @@ -29,7 +29,7 @@ describe('GroupsFilterPopover', () => { test('when a filter is clicked, it becomes checked ', () => { const mockOnSelectedGroupsChanged = jest.fn(); const wrapper = mount( - diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx index e39046ba013c74..9f05ce8a5bfcee 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx @@ -31,61 +31,66 @@ interface GroupsFilterPopoverProps { * @param siemJobs jobs to fetch groups from to display for filtering * @param onSelectedGroupsChanged change listener to be notified when group selection changes */ -export const GroupsFilterPopover = React.memo( - ({ siemJobs, onSelectedGroupsChanged }) => { - const [isGroupPopoverOpen, setIsGroupPopoverOpen] = useState(false); - const [selectedGroups, setSelectedGroups] = useState([]); +export const GroupsFilterPopoverComponent = ({ + siemJobs, + onSelectedGroupsChanged, +}: GroupsFilterPopoverProps) => { + const [isGroupPopoverOpen, setIsGroupPopoverOpen] = useState(false); + const [selectedGroups, setSelectedGroups] = useState([]); - const groups = siemJobs - .map(j => j.groups) - .flat() - .filter(g => g !== 'siem'); - const uniqueGroups = Array.from(new Set(groups)); + const groups = siemJobs + .map(j => j.groups) + .flat() + .filter(g => g !== 'siem'); + const uniqueGroups = Array.from(new Set(groups)); - useEffect(() => { - onSelectedGroupsChanged(selectedGroups); - }, [selectedGroups.sort().join()]); + useEffect(() => { + onSelectedGroupsChanged(selectedGroups); + }, [selectedGroups.sort().join()]); - return ( - setIsGroupPopoverOpen(!isGroupPopoverOpen)} - isSelected={isGroupPopoverOpen} - hasActiveFilters={selectedGroups.length > 0} - numActiveFilters={selectedGroups.length} - > - {i18n.GROUPS} - - } - isOpen={isGroupPopoverOpen} - closePopover={() => setIsGroupPopoverOpen(!isGroupPopoverOpen)} - panelPaddingSize="none" - > - {uniqueGroups.map((group, index) => ( - toggleSelectedGroup(group, selectedGroups, setSelectedGroups)} - > - {`${group} (${groups.filter(g => g === group).length})`} - - ))} - {uniqueGroups.length === 0 && ( - - - - -

    {i18n.NO_GROUPS_AVAILABLE}

    -
    -
    - )} -
    - ); - } -); + return ( + setIsGroupPopoverOpen(!isGroupPopoverOpen)} + isSelected={isGroupPopoverOpen} + hasActiveFilters={selectedGroups.length > 0} + numActiveFilters={selectedGroups.length} + > + {i18n.GROUPS} + + } + isOpen={isGroupPopoverOpen} + closePopover={() => setIsGroupPopoverOpen(!isGroupPopoverOpen)} + panelPaddingSize="none" + > + {uniqueGroups.map((group, index) => ( + toggleSelectedGroup(group, selectedGroups, setSelectedGroups)} + > + {`${group} (${groups.filter(g => g === group).length})`} + + ))} + {uniqueGroups.length === 0 && ( + + + + +

    {i18n.NO_GROUPS_AVAILABLE}

    +
    +
    + )} +
    + ); +}; + +GroupsFilterPopoverComponent.displayName = 'GroupsFilterPopoverComponent'; + +export const GroupsFilterPopover = React.memo(GroupsFilterPopoverComponent); GroupsFilterPopover.displayName = 'GroupsFilterPopover'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx index 5838c3105de6d8..0711cc1c879660 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx @@ -7,7 +7,7 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { JobsTableFilters } from './jobs_table_filters'; +import { JobsTableFiltersComponent } from './jobs_table_filters'; import { SiemJob } from '../../types'; import { cloneDeep } from 'lodash/fp'; import { mockSiemJobs } from '../../__mocks__/api'; @@ -20,14 +20,16 @@ describe('JobsTableFilters', () => { }); test('renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(toJson(wrapper)).toMatchSnapshot(); }); test('when you click Elastic Jobs filter, state is updated and it is selected', () => { const onFilterChanged = jest.fn(); const wrapper = mount( - + ); wrapper @@ -47,7 +49,7 @@ describe('JobsTableFilters', () => { test('when you click Custom Jobs filter, state is updated and it is selected', () => { const onFilterChanged = jest.fn(); const wrapper = mount( - + ); wrapper @@ -67,7 +69,7 @@ describe('JobsTableFilters', () => { test('when you click Custom Jobs filter once, then Elastic Jobs filter, state is updated and selected changed', () => { const onFilterChanged = jest.fn(); const wrapper = mount( - + ); wrapper @@ -99,7 +101,7 @@ describe('JobsTableFilters', () => { test('when you click Custom Jobs filter twice, state is updated and it is revert', () => { const onFilterChanged = jest.fn(); const wrapper = mount( - + ); wrapper diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.tsx index ba080757d34a84..74e61f27fb2d19 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.tsx @@ -31,65 +31,67 @@ interface JobsTableFiltersProps { * @param siemJobs jobs to fetch groups from to display for filtering * @param onFilterChanged change listener to be notified on filter changes */ -export const JobsTableFilters = React.memo( - ({ siemJobs, onFilterChanged }) => { - const [filterQuery, setFilterQuery] = useState(''); - const [selectedGroups, setSelectedGroups] = useState([]); - const [showCustomJobs, setShowCustomJobs] = useState(false); - const [showElasticJobs, setShowElasticJobs] = useState(false); +export const JobsTableFiltersComponent = ({ siemJobs, onFilterChanged }: JobsTableFiltersProps) => { + const [filterQuery, setFilterQuery] = useState(''); + const [selectedGroups, setSelectedGroups] = useState([]); + const [showCustomJobs, setShowCustomJobs] = useState(false); + const [showElasticJobs, setShowElasticJobs] = useState(false); - // Propagate filter changes to parent - useEffect(() => { - onFilterChanged({ filterQuery, showCustomJobs, showElasticJobs, selectedGroups }); - }, [filterQuery, selectedGroups.sort().join(), showCustomJobs, showElasticJobs]); + // Propagate filter changes to parent + useEffect(() => { + onFilterChanged({ filterQuery, showCustomJobs, showElasticJobs, selectedGroups }); + }, [filterQuery, selectedGroups.sort().join(), showCustomJobs, showElasticJobs]); - return ( - - - + + setFilterQuery(query.queryText.trim())} + /> + + + + + + + + + + + { + setShowElasticJobs(!showElasticJobs); + setShowCustomJobs(false); + }} + data-test-subj="show-elastic-jobs-filter-button" + withNext + > + {i18n.SHOW_ELASTIC_JOBS} + + { + setShowCustomJobs(!showCustomJobs); + setShowElasticJobs(false); }} - onChange={(query: EuiSearchBarQuery) => setFilterQuery(query.queryText.trim())} - /> - + data-test-subj="show-custom-jobs-filter-button" + > + {i18n.SHOW_CUSTOM_JOBS} + + + + + ); +}; - - - - - +JobsTableFiltersComponent.displayName = 'JobsTableFiltersComponent'; - - - { - setShowElasticJobs(!showElasticJobs); - setShowCustomJobs(false); - }} - data-test-subj="show-elastic-jobs-filter-button" - withNext - > - {i18n.SHOW_ELASTIC_JOBS} - - { - setShowCustomJobs(!showCustomJobs); - setShowElasticJobs(false); - }} - data-test-subj="show-custom-jobs-filter-button" - > - {i18n.SHOW_CUSTOM_JOBS} - - - - - ); - } -); +export const JobsTableFilters = React.memo(JobsTableFiltersComponent); JobsTableFilters.displayName = 'JobsTableFilters'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx index de703ca819388d..91e5510f4938d4 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx @@ -8,7 +8,7 @@ import { shallow, mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { isChecked, isFailure, isJobLoading, JobSwitch } from './job_switch'; +import { isChecked, isFailure, isJobLoading, JobSwitchComponent } from './job_switch'; import { cloneDeep } from 'lodash/fp'; import { mockSiemJobs } from '../__mocks__/api'; import { SiemJob } from '../types'; @@ -23,7 +23,7 @@ describe('JobSwitch', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - { test('should call onJobStateChange when the switch is clicked to be true/open', () => { const wrapper = mount( - { test('should have a switch when it is not in the loading state', () => { const wrapper = mount( - { test('should not have a switch when it is in the loading state', () => { const wrapper = mount( - { return failureStates.includes(jobState) || failureStates.includes(datafeedState); }; -export const JobSwitch = React.memo( - ({ job, isSiemJobsLoading, onJobStateChange }) => { - const [isLoading, setIsLoading] = useState(false); +export const JobSwitchComponent = ({ + job, + isSiemJobsLoading, + onJobStateChange, +}: JobSwitchProps) => { + const [isLoading, setIsLoading] = useState(false); - return ( - - - {isSiemJobsLoading || isLoading || isJobLoading(job.jobState, job.datafeedId) ? ( - - ) : ( - { - setIsLoading(true); - onJobStateChange(job, job.latestTimestampMs || 0, e.target.checked); - }} - showLabel={false} - label="" - /> - )} - - - ); - } -); + return ( + + + {isSiemJobsLoading || isLoading || isJobLoading(job.jobState, job.datafeedId) ? ( + + ) : ( + { + setIsLoading(true); + onJobStateChange(job, job.latestTimestampMs || 0, e.target.checked); + }} + showLabel={false} + label="" + /> + )} + + + ); +}; + +JobSwitchComponent.displayName = 'JobSwitchComponent'; + +export const JobSwitch = React.memo(JobSwitchComponent); JobSwitch.displayName = 'JobSwitch'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx index 10c9587ea10ad4..691d43a8b18b36 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx @@ -7,12 +7,12 @@ import { shallow, mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { JobsTable } from './jobs_table'; +import { JobsTableComponent } from './jobs_table'; import { mockSiemJobs } from '../__mocks__/api'; import { cloneDeep } from 'lodash/fp'; import { SiemJob } from '../types'; -describe('JobsTable', () => { +describe('JobsTableComponent', () => { let siemJobs: SiemJob[]; let onJobStateChangeMock = jest.fn(); beforeEach(() => { @@ -22,14 +22,22 @@ describe('JobsTable', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); test('should render the hyperlink which points specifically to the job id', () => { const wrapper = mount( - + ); expect( wrapper @@ -44,7 +52,11 @@ describe('JobsTable', () => { test('should render the hyperlink with URI encodings which points specifically to the job id', () => { siemJobs[0].id = 'job id with spaces'; const wrapper = mount( - + ); expect( wrapper @@ -56,7 +68,11 @@ describe('JobsTable', () => { test('should call onJobStateChange when the switch is clicked to be true/open', () => { const wrapper = mount( - + ); wrapper .find('button[data-test-subj="job-switch"]') @@ -69,14 +85,22 @@ describe('JobsTable', () => { test('should have a switch when it is not in the loading state', () => { const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="job-switch"]').exists()).toBe(true); }); test('should not have a switch when it is in the loading state', () => { const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="job-switch"]').exists()).toBe(false); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx index b15c684b1bbbea..86f28ebda2086d 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx @@ -93,7 +93,7 @@ export interface JobTableProps { onJobStateChange: (job: SiemJob, latestTimestampMs: number, enable: boolean) => void; } -export const JobsTable = React.memo(({ isLoading, jobs, onJobStateChange }: JobTableProps) => { +export const JobsTableComponent = ({ isLoading, jobs, onJobStateChange }: JobTableProps) => { const [pageIndex, setPageIndex] = useState(0); const pageSize = 5; @@ -123,7 +123,11 @@ export const JobsTable = React.memo(({ isLoading, jobs, onJobStateChange }: JobT }} /> ); -}); +}; + +JobsTableComponent.displayName = 'JobsTableComponent'; + +export const JobsTable = React.memo(JobsTableComponent); JobsTable.displayName = 'JobsTable'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx index 6502dc909a775c..2e2445fe933bbc 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx @@ -7,11 +7,11 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { ShowingCount } from './showing_count'; +import { ShowingCountComponent } from './showing_count'; describe('ShowingCount', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.tsx index ef8a4fb197f934..1f008ecf712ef5 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.tsx @@ -21,7 +21,7 @@ export interface ShowingCountProps { filterResultsLength: number; } -export const ShowingCount = React.memo(({ filterResultsLength }) => ( +export const ShowingCountComponent = ({ filterResultsLength }: ShowingCountProps) => ( (({ filterResultsLength /> -)); +); + +ShowingCountComponent.displayName = 'ShowingCountComponent'; + +export const ShowingCount = React.memo(ShowingCountComponent); ShowingCount.displayName = 'ShowingCount'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx index 198f99fdd84a22..4ea9e0cdafacb2 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx @@ -8,10 +8,6 @@ import { mount } from 'enzyme'; import * as React from 'react'; import { MlPopover } from './ml_popover'; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; - jest.mock('../../lib/settings/use_kibana_ui_setting'); jest.mock('../ml/permissions/has_ml_admin_permissions', () => ({ @@ -19,14 +15,6 @@ jest.mock('../ml/permissions/has_ml_admin_permissions', () => ({ })); describe('MlPopover', () => { - beforeAll(() => { - console.error = jest.fn(); - }); - - afterAll(() => { - console.error = originalError; - }); - test('shows upgrade popover on mouse click', () => { const wrapper = mount(); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx index 8f90877feb72fb..d409f5de200a47 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx @@ -7,11 +7,11 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { PopoverDescription } from './popover_description'; +import { PopoverDescriptionComponent } from './popover_description'; describe('JobsTableFilters', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx index 67a4654d8368ab..c9cc1c5d4e5396 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiLink, EuiText } from '@elastic/eui'; import chrome from 'ui/chrome'; -export const PopoverDescription = React.memo(() => ( +export const PopoverDescriptionComponent = () => ( ( }} /> -)); +); + +PopoverDescriptionComponent.displayName = 'PopoverDescriptionComponent'; + +export const PopoverDescription = React.memo(PopoverDescriptionComponent); PopoverDescription.displayName = 'PopoverDescription'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx index 13d48c0e62b6d3..c522b7750c4149 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx @@ -7,11 +7,11 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { UpgradeContents } from './upgrade_contents'; +import { UpgradeContentsComponent } from './upgrade_contents'; describe('JobsTableFilters', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.tsx index 45ea80d6a303eb..a337e234f11d3b 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.tsx @@ -26,50 +26,50 @@ const PopoverContentsDiv = styled.div` PopoverContentsDiv.displayName = 'PopoverContentsDiv'; -export const UpgradeContents = React.memo(() => { - return ( - - {i18n.UPGRADE_TITLE} - - - - - ), - }} - /> - - - - - - {i18n.UPGRADE_BUTTON} - - - - - {i18n.LICENSE_BUTTON} - - - - - ); -}); +export const UpgradeContentsComponent = () => ( + + {i18n.UPGRADE_TITLE} + + + + + ), + }} + /> + + + + + + {i18n.UPGRADE_BUTTON} + + + + + {i18n.LICENSE_BUTTON} + + + + +); + +export const UpgradeContents = React.memo(UpgradeContentsComponent); UpgradeContents.displayName = 'UpgradeContents'; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx index c2156bd6c046cc..e84e3066e4f695 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx @@ -13,7 +13,7 @@ import { navTabsHostDetails } from '../../../pages/hosts/details/nav_tabs'; import { HostsTableType } from '../../../store/hosts/model'; import { RouteSpyState } from '../../../utils/route/types'; import { CONSTANTS } from '../../url_state/constants'; -import { TabNavigation } from './'; +import { TabNavigationComponent } from './'; import { TabNavigationProps } from './types'; describe('Tab Navigation', () => { @@ -60,12 +60,12 @@ describe('Tab Navigation', () => { }, }; test('it mounts with correct tab highlighted', () => { - const wrapper = shallow(); + const wrapper = shallow(); const hostsTab = wrapper.find('[data-test-subj="navigation-hosts"]'); expect(hostsTab.prop('isSelected')).toBeTruthy(); }); test('it changes active tab when nav changes by props', () => { - const wrapper = mount(); + const wrapper = mount(); const networkTab = () => wrapper.find('[data-test-subj="navigation-network"]').first(); expect(networkTab().prop('isSelected')).toBeFalsy(); wrapper.setProps({ @@ -77,7 +77,7 @@ describe('Tab Navigation', () => { expect(networkTab().prop('isSelected')).toBeTruthy(); }); test('it carries the url state in the link', () => { - const wrapper = shallow(); + const wrapper = shallow(); const firstTab = wrapper.find('[data-test-subj="navigation-network"]'); expect(firstTab.props().href).toBe( "#/link-to/network?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))" @@ -124,7 +124,7 @@ describe('Tab Navigation', () => { }, }; test('it mounts with correct tab highlighted', () => { - const wrapper = shallow(); + const wrapper = shallow(); const tableNavigationTab = wrapper.find( `[data-test-subj="navigation-${HostsTableType.authentications}"]` ); @@ -132,7 +132,7 @@ describe('Tab Navigation', () => { expect(tableNavigationTab.prop('isSelected')).toBeTruthy(); }); test('it changes active tab when nav changes by props', () => { - const wrapper = mount(); + const wrapper = mount(); const tableNavigationTab = () => wrapper.find(`[data-test-subj="navigation-${HostsTableType.events}"]`).first(); expect(tableNavigationTab().prop('isSelected')).toBeFalsy(); @@ -145,7 +145,7 @@ describe('Tab Navigation', () => { expect(tableNavigationTab().prop('isSelected')).toBeTruthy(); }); test('it carries the url state in the link', () => { - const wrapper = shallow(); + const wrapper = shallow(); const firstTab = wrapper.find( `[data-test-subj="navigation-${HostsTableType.authentications}"]` ); diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx index 27d10cb02a856a..d405ec404b1112 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx @@ -11,7 +11,7 @@ import { trackUiAction as track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../l import { getSearch } from '../helpers'; import { TabNavigationProps } from './types'; -export const TabNavigation = React.memo(props => { +export const TabNavigationComponent = (props: TabNavigationProps) => { const { display, navTabs, pageName, tabName } = props; const mapLocationToTab = (): string => { @@ -51,5 +51,10 @@ export const TabNavigation = React.memo(props => { )); return {renderTabs()}; -}); +}; + +TabNavigationComponent.displayName = 'TabNavigationComponent'; + +export const TabNavigation = React.memo(TabNavigationComponent); + TabNavigation.displayName = 'TabNavigation'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx index bd3f736bf2d19a..234d5ac959c8cb 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx @@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash/fp'; import * as React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; -import { render } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { mockFirstLastSeenHostQuery } from '../../../../containers/hosts/first_last_seen/mock'; import { wait } from '../../../../lib/helpers'; @@ -19,31 +19,9 @@ import { FirstLastSeenHost, FirstLastSeenHostType } from '.'; jest.mock('../../../../lib/settings/use_kibana_ui_setting'); describe('FirstLastSeen Component', () => { - // this is just a little hack to silence a warning that we'll get until react - // fixes this: https://github.com/facebook/react/pull/14853 - // For us that mean we need to upgrade to 16.9.0 - // and we will be able to do that when we are in master - const firstSeen = 'Apr 8, 2019 @ 16:09:40.692'; const lastSeen = 'Apr 8, 2019 @ 18:35:45.064'; - // eslint-disable-next-line no-console - const originalError = console.error; - beforeAll(() => { - // eslint-disable-next-line no-console - console.error = (...args: string[]) => { - if (/Warning.*not wrapped in act/.test(args[0])) { - return; - } - originalError.call(console, ...args); - }; - }); - - afterAll(() => { - // eslint-disable-next-line no-console - console.error = originalError; - }); - test('Loading', async () => { const { container } = render( diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx index 63642119b430c2..2cdac754198af7 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx @@ -14,27 +14,6 @@ import { mockData } from './mock'; import { mockAnomalies } from '../../../ml/mock'; describe('Host Summary Component', () => { - // this is just a little hack to silence a warning that we'll get until react - // fixes this: https://github.com/facebook/react/pull/14853 - // For us that mean we need to upgrade to 16.9.0 - // and we will be able to do that when we are in master - // eslint-disable-next-line no-console - const originalError = console.error; - beforeAll(() => { - // eslint-disable-next-line no-console - console.error = (...args: string[]) => { - if (/Warning.*not wrapped in act/.test(args[0])) { - return; - } - originalError.call(console, ...args); - }; - }); - - afterAll(() => { - // eslint-disable-next-line no-console - console.error = originalError; - }); - describe('rendering', () => { test('it renders the default Host Summary', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx index 135d45907b35e7..577ec5ff514700 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx @@ -8,7 +8,7 @@ import { mockKpiHostsData, mockKpiHostDetailsData } from './mock'; import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import toJson from 'enzyme-to-json'; -import { KpiHostsComponent } from '.'; +import { KpiHostsComponentBase } from '.'; import * as statItems from '../../../stat_items'; import { kpiHostsMapping } from './kpi_hosts_mapping'; import { kpiHostDetailsMapping } from './kpi_host_details_mapping'; @@ -21,7 +21,7 @@ describe('kpiHostsComponent', () => { describe('render', () => { test('it should render spinner if it is loading', () => { const wrapper: ShallowWrapper = shallow( - { test('it should render KpiHostsData', () => { const wrapper: ShallowWrapper = shallow( - { test('it should render KpiHostDetailsData', () => { const wrapper: ShallowWrapper = shallow( - { beforeEach(() => { shallow( - ( - ({ data, from, loading, id, to, narrowDateRange }) => { - const mappings = - (data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping; - const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( - mappings, - data, - id, - from, - to, - narrowDateRange - ); - return loading ? ( - - - - - - ) : ( - - {statItemsProps.map((mappedStatItemProps, idx) => { - return ; - })} - - ); - } -); +export const KpiHostsComponentBase = ({ + data, + from, + loading, + id, + to, + narrowDateRange, +}: KpiHostsProps | KpiHostDetailsProps) => { + const mappings = + (data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping; + const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( + mappings, + data, + id, + from, + to, + narrowDateRange + ); + return loading ? ( + + + + + + ) : ( + + {statItemsProps.map((mappedStatItemProps, idx) => { + return ; + })} + + ); +}; + +KpiHostsComponentBase.displayName = 'KpiHostsComponentBase'; + +export const KpiHostsComponent = React.memo(KpiHostsComponentBase); + +KpiHostsComponent.displayName = 'KpiHostsComponent'; diff --git a/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx index f8afd3aeb9dca1..eb06fe8a01d79a 100644 --- a/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx @@ -8,7 +8,7 @@ import { getRowItemDraggables, getRowItemOverflow, getRowItemDraggable, - OverflowField, + OverflowFieldComponent, } from './helpers'; import * as React from 'react'; import { mount, shallow } from 'enzyme'; @@ -210,19 +210,21 @@ describe('Table Helpers', () => { describe('OverflowField', () => { test('it returns correctly against snapshot', () => { const overflowString = 'This string is exactly fifty-one chars in length!!!'; - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(toJson(wrapper)).toMatchSnapshot(); }); test('it does not truncates as per custom overflowLength value', () => { const overflowString = 'This string is short'; - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toBe('This string is short'); }); test('it truncates as per custom overflowLength value', () => { const overflowString = 'This string is exactly fifty-one chars in length!!!'; - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toBe('This string is exact'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/tables/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/tables/helpers.tsx index b4ee93f9963e43..f4f7375c26d14c 100644 --- a/x-pack/legacy/plugins/siem/public/components/tables/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/tables/helpers.tsx @@ -177,11 +177,15 @@ export const getRowItemOverflow = ( ); }; -export const Popover = React.memo<{ +export const PopoverComponent = ({ + children, + count, + idPrefix, +}: { children: React.ReactNode; count: number; idPrefix: string; -}>(({ children, count, idPrefix }) => { +}) => { const [isOpen, setIsOpen] = useState(false); return ( @@ -196,15 +200,23 @@ export const Popover = React.memo<{ ); -}); +}; + +PopoverComponent.displayName = 'PopoverComponent'; + +export const Popover = React.memo(PopoverComponent); Popover.displayName = 'Popover'; -export const OverflowField = React.memo<{ +export const OverflowFieldComponent = ({ + value, + showToolTip = true, + overflowLength = 50, +}: { value: string; showToolTip?: boolean; overflowLength?: number; -}>(({ value, showToolTip = true, overflowLength = 50 }) => ( +}) => ( {showToolTip ? ( @@ -219,6 +231,10 @@ export const OverflowField = React.memo<{ )} -)); +); + +OverflowFieldComponent.displayName = 'OverflowFieldComponent'; + +export const OverflowField = React.memo(OverflowFieldComponent); OverflowField.displayName = 'OverflowField'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx index ce465ac4f837e2..35a4f4a74ae200 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx @@ -15,7 +15,7 @@ import { CloseButton } from '../actions'; import { ColumnHeaderType } from '../column_header'; import { defaultHeaders } from '../default_headers'; -import { Header } from '.'; +import { HeaderComponent } from '.'; import { getNewSortDirectionOnClick, getNextSortDirection, getSortDirection } from './helpers'; const filteredColumnHeader: ColumnHeaderType = 'text-filter'; @@ -30,7 +30,7 @@ describe('Header', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( -
    { test('it renders the header text', () => { const wrapper = mount( -
    { const headerSortable = { ...columnHeader, aggregatable: true }; const wrapper = mount( -
    { const wrapper = mount( -
    { const headerSortable = { ...columnHeader, aggregatable: true }; const wrapper = mount( -
    { const headerSortable = { ...columnHeader, aggregatable: false }; const wrapper = mount( -
    { const headerSortable = { ...columnHeader }; const wrapper = mount( -
    { const headerSortable = { ...columnHeader, aggregatable: undefined }; const wrapper = mount( -
    { test('truncates the header text with an ellipsis', () => { const wrapper = mount( -
    { test('it has a tooltip to display the properties of the field', () => { const wrapper = mount( -
    { const mockSetIsResizing = jest.fn(); mount( -
    ( - ({ - header, - onColumnRemoved, - onColumnResized, - onColumnSorted, - onFilterChange = noop, - setIsResizing, - sort, - }) => { - const onClick = () => { - onColumnSorted!({ - columnId: header.id, - sortDirection: getNewSortDirectionOnClick({ - clickedHeader: header, - currentSort: sort, - }), - }); - }; - - const onResize: OnResize = ({ delta, id }) => { - onColumnResized({ columnId: id, delta }); - }; - - const renderActions = (isResizing: boolean) => { - setIsResizing(isResizing); - return ( - <> - - - - - - - ); - }; +export const HeaderComponent = ({ + header, + onColumnRemoved, + onColumnResized, + onColumnSorted, + onFilterChange = noop, + setIsResizing, + sort, +}: Props) => { + const onClick = () => { + onColumnSorted!({ + columnId: header.id, + sortDirection: getNewSortDirectionOnClick({ + clickedHeader: header, + currentSort: sort, + }), + }); + }; + const onResize: OnResize = ({ delta, id }) => { + onColumnResized({ columnId: id, delta }); + }; + + const renderActions = (isResizing: boolean) => { + setIsResizing(isResizing); return ( - } - id={header.id} - onResize={onResize} - positionAbsolute - render={renderActions} - right="-1px" - top={0} - /> + <> + + + + + + ); - } -); + }; + + return ( + } + id={header.id} + onResize={onResize} + positionAbsolute + render={renderActions} + right="-1px" + top={0} + /> + ); +}; + +HeaderComponent.displayName = 'HeaderComponent'; + +export const Header = React.memo(HeaderComponent); Header.displayName = 'Header'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx index 851d48a19c2e4c..370f864f51f3c7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx @@ -15,7 +15,7 @@ import { mockBrowserFields } from '../../../../../public/containers/source/mock' import { Sort } from '../sort'; import { TestProviders } from '../../../../mock/test_providers'; -import { ColumnHeaders } from '.'; +import { ColumnHeadersComponent } from '.'; jest.mock('../../../resize_handle/is_resizing', () => ({ ...jest.requireActual('../../../resize_handle/is_resizing'), @@ -34,7 +34,7 @@ describe('ColumnHeaders', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - { test('it renders the field browser', () => { const wrapper = mount( - { test('it renders every column header', () => { const wrapper = mount( - { test('it disables dragging during a column resize', () => { const wrapper = mount( - ( - ({ - actionsColumnWidth, - browserFields, - columnHeaders, - isEventViewer = false, - onColumnRemoved, - onColumnResized, - onColumnSorted, - onUpdateColumns, - onFilterChange = noop, - showEventsSelect, - sort, - timelineId, - toggleColumn, - }) => { - const { isResizing, setIsResizing } = useIsContainerResizing(); - - return ( - - - - {showEventsSelect && ( - - - - - - )} +export const ColumnHeadersComponent = ({ + actionsColumnWidth, + browserFields, + columnHeaders, + isEventViewer = false, + onColumnRemoved, + onColumnResized, + onColumnSorted, + onUpdateColumns, + onFilterChange = noop, + showEventsSelect, + sort, + timelineId, + toggleColumn, +}: Props) => { + const { isResizing, setIsResizing } = useIsContainerResizing(); + return ( + + + + {showEventsSelect && ( - + - + )} + + + + + + + + + + {dropProvided => ( + + {columnHeaders.map((header, i) => ( + + {(dragProvided, dragSnapshot) => ( + + {!dragSnapshot.isDragging ? ( + +
    + + ) : ( + + + + )} + + )} + + ))} + + )} + + + + ); +}; + +ColumnHeadersComponent.displayName = 'ColumnHeadersComponent'; + +export const ColumnHeaders = React.memo(ColumnHeadersComponent); - - {dropProvided => ( - - {columnHeaders.map((header, i) => ( - - {(dragProvided, dragSnapshot) => ( - - {!dragSnapshot.isDragging ? ( - -
    - - ) : ( - - - - )} - - )} - - ))} - - )} - - - - ); - } -); ColumnHeaders.displayName = 'ColumnHeaders'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx index 284cd0b49cb584..dbf6db6cd2bd92 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx @@ -10,13 +10,13 @@ import * as React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TestProviders } from '../../../../mock'; -import { Args } from './args'; +import { ArgsComponent } from './args'; describe('Args', () => { describe('rendering', () => { test('it renders against shallow snapshot', () => { const wrapper = shallow( - { test('it returns an empty string when both args and process title are undefined', () => { const wrapper = mountWithIntl( - { test('it returns an empty string when both args and process title are null', () => { const wrapper = mountWithIntl( - + ); expect(wrapper.text()).toEqual(''); @@ -52,7 +57,7 @@ describe('Args', () => { test('it returns an empty string when args is an empty array, and title is an empty string', () => { const wrapper = mountWithIntl( - + ); expect(wrapper.text()).toEqual(''); @@ -61,7 +66,7 @@ describe('Args', () => { test('it returns args when args are provided, and process title is NOT provided', () => { const wrapper = mountWithIntl( - { test('it returns process title when process title is provided, and args is NOT provided', () => { const wrapper = mountWithIntl( - { test('it returns both args and process title, when both are provided', () => { const wrapper = mountWithIntl( - (({ args, contextId, eventId, processTitle }) => { +export const ArgsComponent = ({ args, contextId, eventId, processTitle }: Props) => { if (isNillEmptyOrNotFinite(args) && isNillEmptyOrNotFinite(processTitle)) { return null; } @@ -47,6 +47,10 @@ export const Args = React.memo(({ args, contextId, eventId, processTitle )} ); -}); +}; + +ArgsComponent.displayName = 'ArgsComponent'; + +export const Args = React.memo(ArgsComponent); Args.displayName = 'Args'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx index 6e8a0e8cfb17fc..07b7741e5c1521 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx @@ -11,7 +11,7 @@ import * as React from 'react'; import { TestProviders } from '../../../mock/test_providers'; -import { Footer, PagingControl } from './index'; +import { FooterComponent, PagingControlComponent } from './index'; import { mockData } from './mock'; describe('Footer Timeline Component', () => { @@ -23,7 +23,7 @@ describe('Footer Timeline Component', () => { describe('rendering', () => { test('it renders the default timeline footer', () => { const wrapper = shallow( -