From b7bcff71deab515331c5b6223f75d67196b9f60a Mon Sep 17 00:00:00 2001 From: Jimmy Kuang Date: Thu, 30 Jan 2020 10:30:14 -0800 Subject: [PATCH 01/13] System index templates can't be edited (#55229) * Removed period validation * Add back period validation * Fix EsLint * Undo Snapshot changes from Index Management bug * Undo Capitalization Co-authored-by: Elastic Machine --- .../template_form/steps/step_logistics.tsx | 3 +- .../template_form/template_form_schemas.tsx | 124 +++++++++--------- 2 files changed, 65 insertions(+), 62 deletions(-) diff --git a/x-pack/legacy/plugins/index_management/public/app/components/template_form/steps/step_logistics.tsx b/x-pack/legacy/plugins/index_management/public/app/components/template_form/steps/step_logistics.tsx index 290ade3504551..d2134837c15e5 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/template_form/steps/step_logistics.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/template_form/steps/step_logistics.tsx @@ -18,7 +18,7 @@ import { } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/components'; import { documentationService } from '../../../services/documentation'; import { StepProps } from '../types'; -import { schemas } from '../template_form_schemas'; +import { schemas, nameConfig, nameConfigWithoutValidations } from '../template_form_schemas'; // Create or Form components with partial props that are common to all instances const UseField = getUseField({ component: Field }); @@ -131,6 +131,7 @@ export const StepLogistics: React.FunctionComponent = ({ ['data-test-subj']: name.testSubject, euiFieldProps: { disabled: isEditing }, }} + config={isEditing ? nameConfigWithoutValidations : nameConfig} /> {/* Index patterns */} diff --git a/x-pack/legacy/plugins/index_management/public/app/components/template_form/template_form_schemas.tsx b/x-pack/legacy/plugins/index_management/public/app/components/template_form/template_form_schemas.tsx index 5f3e28ddffb8e..ed2616cc64e38 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/template_form/template_form_schemas.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/template_form/template_form_schemas.tsx @@ -12,6 +12,7 @@ import { FormSchema, FIELD_TYPES, VALIDATION_TYPES, + FieldConfig, } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; import { @@ -34,70 +35,71 @@ const { const { toInt } = fieldFormatters; const indexPatternInvalidCharacters = INVALID_INDEX_PATTERN_CHARS.join(' '); -export const schemas: Record = { - logistics: { - name: { - type: FIELD_TYPES.TEXT, - label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.fieldNameLabel', { - defaultMessage: 'Name', +export const nameConfig: FieldConfig = { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.fieldNameLabel', { + defaultMessage: 'Name', + }), + validations: [ + { + validator: emptyField( + i18n.translate('xpack.idxMgmt.templateValidation.templateNameRequiredError', { + defaultMessage: 'A template name is required.', + }) + ), + }, + { + validator: containsCharsField({ + chars: ' ', + message: i18n.translate('xpack.idxMgmt.templateValidation.templateNameSpacesError', { + defaultMessage: 'Spaces are not allowed in a template name.', + }), }), - validations: [ - { - validator: emptyField( - i18n.translate('xpack.idxMgmt.templateValidation.templateNameRequiredError', { - defaultMessage: 'A template name is required.', - }) - ), - }, - { - validator: containsCharsField({ - chars: ' ', - message: i18n.translate('xpack.idxMgmt.templateValidation.templateNameSpacesError', { - defaultMessage: 'Spaces are not allowed in a template name.', - }), - }), - }, - { - validator: startsWithField({ - char: '_', - message: i18n.translate( - 'xpack.idxMgmt.templateValidation.templateNameUnderscoreError', - { - defaultMessage: 'A template name must not start with an underscore.', - } - ), - }), - }, - { - validator: startsWithField({ - char: '.', - message: i18n.translate('xpack.idxMgmt.templateValidation.templateNamePeriodError', { - defaultMessage: 'A template name must not start with a period.', - }), - }), - }, - { - validator: containsCharsField({ - chars: INVALID_TEMPLATE_NAME_CHARS, - message: ({ charsFound }) => - i18n.translate( - 'xpack.idxMgmt.templateValidation.templateNameInvalidaCharacterError', - { - defaultMessage: 'A template name must not contain the character "{invalidChar}"', - values: { invalidChar: charsFound[0] }, - } - ), + }, + { + validator: startsWithField({ + char: '_', + message: i18n.translate('xpack.idxMgmt.templateValidation.templateNameUnderscoreError', { + defaultMessage: 'A template name must not start with an underscore.', + }), + }), + }, + { + validator: startsWithField({ + char: '.', + message: i18n.translate('xpack.idxMgmt.templateValidation.templateNamePeriodError', { + defaultMessage: 'A template name must not start with a period.', + }), + }), + }, + { + validator: containsCharsField({ + chars: INVALID_TEMPLATE_NAME_CHARS, + message: ({ charsFound }) => + i18n.translate('xpack.idxMgmt.templateValidation.templateNameInvalidaCharacterError', { + defaultMessage: 'A template name must not contain the character "{invalidChar}"', + values: { invalidChar: charsFound[0] }, }), - }, - { - validator: lowerCaseStringField( - i18n.translate('xpack.idxMgmt.templateValidation.templateNameLowerCaseRequiredError', { - defaultMessage: 'The template name must be in lowercase.', - }) - ), - }, - ], + }), }, + { + validator: lowerCaseStringField( + i18n.translate('xpack.idxMgmt.templateValidation.templateNameLowerCaseRequiredError', { + defaultMessage: 'The template name must be in lowercase.', + }) + ), + }, + ], +}; + +export const nameConfigWithoutValidations: FieldConfig = { + ...nameConfig, + validations: [], +}; + +export const schemas: Record = { + logistics: { + name: nameConfig, indexPatterns: { type: FIELD_TYPES.COMBO_BOX, defaultValue: [], From 2b53c74cd9c1889b84a817b7a2817a839a9b5adf Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 30 Jan 2020 19:03:14 +0000 Subject: [PATCH 02/13] chore(NA): delete data/optimize with kbn clean (#55890) Co-authored-by: Elastic Machine --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9707d3863d295..425527a058e86 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "clean": { "extraPatterns": [ "build", - "optimize", + "data/optimize", "built_assets", ".eslintcache", ".node_binaries" From 8398a1c77cde0e190126ff13804d182fda96e74d Mon Sep 17 00:00:00 2001 From: Dan Panzarella Date: Thu, 30 Jan 2020 14:25:56 -0500 Subject: [PATCH 03/13] [Endpoint] Add Endpoint Details route (#55746) * Add Endpoint Details route * add Endpoint Details tests * sacrifices to the Type gods * update to latest endpoint schema Co-authored-by: Elastic Machine --- .../endpoint/server/routes/endpoints.test.ts | 78 +++++++++++++++++++ .../endpoint/server/routes/endpoints.ts | 32 +++++++- .../endpoint/endpoint_query_builders.test.ts | 32 +++++++- .../endpoint/endpoint_query_builders.ts | 24 ++++++ 4 files changed, 162 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/endpoint/server/routes/endpoints.test.ts b/x-pack/plugins/endpoint/server/routes/endpoints.test.ts index 04a38972401ed..be14554f128c3 100644 --- a/x-pack/plugins/endpoint/server/routes/endpoints.test.ts +++ b/x-pack/plugins/endpoint/server/routes/endpoints.test.ts @@ -120,4 +120,82 @@ describe('test endpoint route', () => { expect(endpointResultList.request_page_index).toEqual(10); expect(endpointResultList.request_page_size).toEqual(10); }); + + describe('Endpoint Details route', () => { + it('should return 404 on no results', async () => { + const mockRequest = httpServerMock.createKibanaRequest({ params: { id: 'BADID' } }); + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => + Promise.resolve({ + took: 3, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 9, + relation: 'eq', + }, + max_score: null, + hits: [], + }, + }) + ); + [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => + path.startsWith('/api/endpoint/endpoints') + )!; + + await routeHandler( + ({ + core: { + elasticsearch: { + dataClient: mockScopedClient, + }, + }, + } as unknown) as RequestHandlerContext, + mockRequest, + mockResponse + ); + + expect(mockScopedClient.callAsCurrentUser).toBeCalled(); + expect(routeConfig.options).toEqual({ authRequired: true }); + expect(mockResponse.notFound).toBeCalled(); + const message = mockResponse.notFound.mock.calls[0][0]?.body; + expect(message).toEqual('Endpoint Not Found'); + }); + + it('should return a single endpoint', async () => { + const mockRequest = httpServerMock.createKibanaRequest({ + params: { id: (data as any).hits.hits[0]._id }, + }); + const response: SearchResponse = (data as unknown) as SearchResponse< + EndpointMetadata + >; + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response)); + [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => + path.startsWith('/api/endpoint/endpoints') + )!; + + await routeHandler( + ({ + core: { + elasticsearch: { + dataClient: mockScopedClient, + }, + }, + } as unknown) as RequestHandlerContext, + mockRequest, + mockResponse + ); + + expect(mockScopedClient.callAsCurrentUser).toBeCalled(); + expect(routeConfig.options).toEqual({ authRequired: true }); + expect(mockResponse.ok).toBeCalled(); + const result = mockResponse.ok.mock.calls[0][0]?.body as EndpointMetadata; + expect(result).toHaveProperty('endpoint'); + }); + }); }); diff --git a/x-pack/plugins/endpoint/server/routes/endpoints.ts b/x-pack/plugins/endpoint/server/routes/endpoints.ts index 4fc3e653f9426..24ad8e3941f5e 100644 --- a/x-pack/plugins/endpoint/server/routes/endpoints.ts +++ b/x-pack/plugins/endpoint/server/routes/endpoints.ts @@ -8,7 +8,10 @@ import { IRouter } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { schema } from '@kbn/config-schema'; -import { kibanaRequestToEndpointListQuery } from '../services/endpoint/endpoint_query_builders'; +import { + kibanaRequestToEndpointListQuery, + kibanaRequestToEndpointFetchQuery, +} from '../services/endpoint/endpoint_query_builders'; import { EndpointMetadata, EndpointResultList } from '../../common/types'; import { EndpointAppContext } from '../types'; @@ -51,6 +54,33 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp } } ); + + router.get( + { + path: '/api/endpoint/endpoints/{id}', + validate: { + params: schema.object({ id: schema.string() }), + }, + options: { authRequired: true }, + }, + async (context, req, res) => { + try { + const query = kibanaRequestToEndpointFetchQuery(req, endpointAppContext); + const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser( + 'search', + query + )) as SearchResponse; + + if (response.hits.hits.length === 0) { + return res.notFound({ body: 'Endpoint Not Found' }); + } + + return res.ok({ body: response.hits.hits[0]._source }); + } catch (err) { + return res.internalError({ body: err }); + } + } + ); } function mapToEndpointResultList( diff --git a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts index 3c931a251d697..e453f777fbd50 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts +++ b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts @@ -5,10 +5,13 @@ */ import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/server/mocks'; import { EndpointConfigSchema } from '../../config'; -import { kibanaRequestToEndpointListQuery } from './endpoint_query_builders'; +import { + kibanaRequestToEndpointListQuery, + kibanaRequestToEndpointFetchQuery, +} from './endpoint_query_builders'; -describe('test query builder', () => { - describe('test query builder request processing', () => { +describe('query builder', () => { + describe('EndpointListQuery', () => { it('test default query params for all endpoints when no params or body is provided', async () => { const mockRequest = httpServerMock.createKibanaRequest({ body: {}, @@ -51,4 +54,27 @@ describe('test query builder', () => { } as Record); }); }); + + describe('EndpointFetchQuery', () => { + it('searches for the correct ID', () => { + const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; + const mockRequest = httpServerMock.createKibanaRequest({ + params: { + id: mockID, + }, + }); + const query = kibanaRequestToEndpointFetchQuery(mockRequest, { + logFactory: loggingServiceMock.create(), + config: () => Promise.resolve(EndpointConfigSchema.validate({})), + }); + expect(query).toEqual({ + body: { + query: { match: { 'host.id.keyword': mockID } }, + sort: [{ 'event.created': { order: 'desc' } }], + size: 1, + }, + index: 'endpoint-agent*', + }); + }); + }); }); diff --git a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts index 102c268cf9ec4..b4f295a64b6ea 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts +++ b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts @@ -65,3 +65,27 @@ async function getPagingProperties( pageIndex: pagingProperties.page_index || config.endpointResultListDefaultFirstPageIndex, }; } + +export const kibanaRequestToEndpointFetchQuery = ( + request: KibanaRequest, + endpointAppContext: EndpointAppContext +) => { + return { + body: { + query: { + match: { + 'host.id.keyword': request.params.id, + }, + }, + sort: [ + { + 'event.created': { + order: 'desc', + }, + }, + ], + size: 1, + }, + index: EndpointAppConstants.ENDPOINT_INDEX_NAME, + }; +}; From 642c694117ac3dcea33c6597a33b94cb276a75d4 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 30 Jan 2020 13:55:17 -0600 Subject: [PATCH 04/13] Add missing docker settings (#56411) Closes #54811 --- .../resources/bin/kibana-docker | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index 7a82ca0b1609c..34ba25f92beb6 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -18,6 +18,11 @@ kibana_vars=( console.enabled console.proxyConfig console.proxyFilter + cpu.cgroup.path.override + cpuacct.cgroup.path.override + csp.rules + csp.strict + csp.warnLegacyBrowsers elasticsearch.customHeaders elasticsearch.hosts elasticsearch.logQueries @@ -30,6 +35,7 @@ kibana_vars=( elasticsearch.sniffInterval elasticsearch.sniffOnConnectionFault elasticsearch.sniffOnStart + elasticsearch.ssl.alwaysPresentCertificate elasticsearch.ssl.certificate elasticsearch.ssl.certificateAuthorities elasticsearch.ssl.key @@ -42,9 +48,13 @@ kibana_vars=( elasticsearch.startupTimeout elasticsearch.username i18n.locale + interpreter.enableInVisualize + kibana.autocompleteTerminateAfter + kibana.autocompleteTimeout kibana.defaultAppId kibana.index logging.dest + logging.json logging.quiet logging.rotate.enabled logging.rotate.everyBytes @@ -55,18 +65,32 @@ kibana_vars=( logging.useUTC logging.verbose map.includeElasticMapsService + map.proxyElasticMapsServiceInMaps + map.regionmap + map.tilemap.options.attribution + map.tilemap.options.maxZoom + map.tilemap.options.minZoom + map.tilemap.options.subdomains + map.tilemap.url + newsfeed.enabled ops.interval path.data pid.file regionmap server.basePath server.customResponseHeaders + server.compression.enabled + server.compression.referrerWhitelist + server.cors + server.cors.origin server.defaultRoute server.host + server.keepAliveTimeout server.maxPayloadBytes server.name server.port server.rewriteBasePath + server.socketTimeout server.ssl.cert server.ssl.certificate server.ssl.certificateAuthorities @@ -82,6 +106,7 @@ kibana_vars=( server.ssl.truststore.password server.ssl.redirectHttpFromPort server.ssl.supportedProtocols + server.xsrf.disableProtection server.xsrf.whitelist status.allowAnonymous status.v6ApiFormat @@ -96,11 +121,13 @@ kibana_vars=( xpack.apm.serviceMapEnabled xpack.apm.ui.enabled xpack.apm.ui.maxTraceItems + xpack.apm.ui.transactionGroupBucketSize apm_oss.apmAgentConfigurationIndex apm_oss.indexPattern apm_oss.errorIndices apm_oss.onboardingIndices apm_oss.spanIndices + apm_oss.sourcemapIndices apm_oss.transactionIndices apm_oss.metricsIndices xpack.canvas.enabled @@ -116,6 +143,8 @@ kibana_vars=( xpack.code.security.gitHostWhitelist xpack.code.security.gitProtocolWhitelist xpack.graph.enabled + xpack.graph.canEditDrillDownUrls + xpack.graph.savePolicy xpack.grokdebugger.enabled xpack.infra.enabled xpack.infra.query.partitionFactor @@ -128,11 +157,14 @@ kibana_vars=( xpack.infra.sources.default.fields.timestamp xpack.infra.sources.default.logAlias xpack.infra.sources.default.metricAlias + xpack.license_management.enabled xpack.ml.enabled + xpack.monitoring.cluster_alerts.email_notifications.email_address xpack.monitoring.elasticsearch.password xpack.monitoring.elasticsearch.pingTimeout xpack.monitoring.elasticsearch.hosts xpack.monitoring.elasticsearch.username + xpack.monitoring.elasticsearch.logFetchCount xpack.monitoring.elasticsearch.ssl.certificateAuthorities xpack.monitoring.elasticsearch.ssl.verificationMode xpack.monitoring.enabled @@ -166,6 +198,7 @@ kibana_vars=( xpack.reporting.csv.maxSizeBytes xpack.reporting.csv.scroll.duration xpack.reporting.csv.scroll.size + xpack.reporting.capture.maxAttempts xpack.reporting.enabled xpack.reporting.encryptionKey xpack.reporting.index @@ -183,6 +216,8 @@ kibana_vars=( xpack.reporting.queue.pollIntervalErrorMultiplier xpack.reporting.queue.timeout xpack.reporting.roles.allow + xpack.rollup.enabled + xpack.security.audit.enabled xpack.searchprofiler.enabled xpack.security.authc.providers xpack.security.authc.oidc.realm @@ -196,7 +231,10 @@ kibana_vars=( xpack.security.session.idleTimeout xpack.security.session.lifespan xpack.security.loginAssistanceMessage + telemetry.allowChangingOptInStatus telemetry.enabled + telemetry.optIn + telemetry.optInStatusUrl telemetry.sendUsageFrom ) From 89b3c428c21dfd6fb91d9245c7b095ebcffa3341 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 30 Jan 2020 21:03:03 +0100 Subject: [PATCH 05/13] [ML] Fix Data Visualizer responsive layout (#56372) * [ML] replace Example component with EuiListGroup * [ML] center alignment --- .../field_data_card/examples_list/example.tsx | 31 ------------------- .../examples_list/examples_list.tsx | 17 +++++++--- 2 files changed, 12 insertions(+), 36 deletions(-) delete mode 100644 x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/example.tsx diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/example.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/example.tsx deleted file mode 100644 index 29fe690f4a43b..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/example.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FC } from 'react'; - -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; - -interface Props { - example: string | object; -} - -export const Example: FC = ({ example }) => { - const exampleStr = typeof example === 'string' ? example : JSON.stringify(example); - - // Use 95% width for each example so that the truncation ellipses show up when - // wrapped inside a tooltip. - return ( - - - - - {exampleStr} - - - - - ); -}; diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/examples_list.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/examples_list.tsx index 0bf911c1edf86..c8eb810115401 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/examples_list.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/examples_list.tsx @@ -6,12 +6,10 @@ import React, { FC } from 'react'; -import { EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiListGroup, EuiListGroupItem, EuiSpacer, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Example } from './example'; - interface Props { examples: Array; } @@ -22,7 +20,14 @@ export const ExamplesList: FC = ({ examples }) => { } const examplesContent = examples.map((example, i) => { - return ; + return ( + + ); }); return ( @@ -39,7 +44,9 @@ export const ExamplesList: FC = ({ examples }) => { - {examplesContent} + + {examplesContent} + ); }; From 08e0cbc3ee90358148eafb2d14a2368004b76f15 Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Thu, 30 Jan 2020 13:05:09 -0700 Subject: [PATCH 06/13] [skip-ci] Add example for migrating pre-handlers (#56080) --- src/core/MIGRATION_EXAMPLES.md | 137 +++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index 568980f50117d..5517dfa7f9a23 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -14,6 +14,7 @@ APIs to their New Platform equivalents. - [3. New Platform shim using New Platform router](#3-new-platform-shim-using-new-platform-router) - [4. New Platform plugin](#4-new-platform-plugin) - [Accessing Services](#accessing-services) + - [Migrating Hapi "pre" handlers](#migrating-hapi-pre-handlers) - [Chrome](#chrome) - [Updating an application navlink](#updating-application-navlink) - [Chromeless Applications](#chromeless-applications) @@ -450,6 +451,142 @@ class Plugin { } ``` +### Migrating Hapi "pre" handlers + +In the Legacy Platform, routes could provide a "pre" option in their config to +register a function that should be run prior to the route handler. These +"pre" handlers allow routes to share some business logic that may do some +pre-work or validation. In Kibana, these are often used for license checks. + +The Kibana Platform's HTTP interface does not provide this functionality, +however it is simple enough to port over using a higher-order function that can +wrap the route handler. + +#### Simple example + +In this simple example, a pre-handler is used to either abort the request with +an error or continue as normal. This is a simple "gate-keeping" pattern. + +```ts +// Legacy pre-handler +const licensePreRouting = (request) => { + const licenseInfo = getMyPluginLicenseInfo(request.server.plugins.xpack_main); + if (!licenseInfo.isOneOf(['gold', 'platinum', 'trial'])) { + throw Boom.forbidden(`You don't have the right license for MyPlugin!`); + } +} + +server.route({ + method: 'GET', + path: '/api/my-plugin/do-something', + config: { + pre: [{ method: licensePreRouting }] + }, + handler: (req) => { + return doSomethingInteresting(); + } +}) +``` + +In the Kibana Platform, the same functionality can be acheived by creating a +function that takes a route handler (or factory for a route handler) as an +argument and either invokes it in the successful case or returns an error +response in the failure case. + +We'll call this a "high-order handler" similar to the "high-order component" +pattern common in the React ecosystem. + +```ts +// New Platform high-order handler +const checkLicense = ( + handler: RequestHandler +): RequestHandler => { + return (context, req, res) => { + const licenseInfo = getMyPluginLicenseInfo(context.licensing.license); + + if (licenseInfo.hasAtLeast('gold')) { + return handler(context, req, res); + } else { + return res.forbidden({ body: `You don't have the right license for MyPlugin!` }); + } + } +} + +router.get( + { path: '/api/my-plugin/do-something', validate: false }, + checkLicense(async (context, req, res) => { + const results = doSomethingInteresting(); + return res.ok({ body: results }); + }), +) +``` + +#### Full Example + +In some cases, the route handler may need access to data that the pre-handler +retrieves. In this case, you can utilize a handler _factory_ rather than a raw +handler. + +```ts +// Legacy pre-handler +const licensePreRouting = (request) => { + const licenseInfo = getMyPluginLicenseInfo(request.server.plugins.xpack_main); + if (licenseInfo.isOneOf(['gold', 'platinum', 'trial'])) { + // In this case, the return value of the pre-handler is made available on + // whatever the 'assign' option is in the route config. + return licenseInfo; + } else { + // In this case, the route handler is never called and the user gets this + // error message + throw Boom.forbidden(`You don't have the right license for MyPlugin!`); + } +} + +server.route({ + method: 'GET', + path: '/api/my-plugin/do-something', + config: { + pre: [{ method: licensePreRouting, assign: 'licenseInfo' }] + }, + handler: (req) => { + const licenseInfo = req.pre.licenseInfo; + return doSomethingInteresting(licenseInfo); + } +}) +``` + +In many cases, it may be simpler to duplicate the function call +to retrieve the data again in the main handler. In this other cases, you can +utilize a handler _factory_ rather than a raw handler as the argument to your +high-order handler. This way the high-order handler can pass arbitrary arguments +to the route handler. + +```ts +// New Platform high-order handler +const checkLicense = ( + handlerFactory: (licenseInfo: MyPluginLicenseInfo) => RequestHandler +): RequestHandler => { + return (context, req, res) => { + const licenseInfo = getMyPluginLicenseInfo(context.licensing.license); + + if (licenseInfo.hasAtLeast('gold')) { + const handler = handlerFactory(licenseInfo); + return handler(context, req, res); + } else { + return res.forbidden({ body: `You don't have the right license for MyPlugin!` }); + } + } +} + +router.get( + { path: '/api/my-plugin/do-something', validate: false }, + checkLicense(licenseInfo => async (context, req, res) => { + const results = doSomethingInteresting(licenseInfo); + return res.ok({ body: results }); + }), +) +``` + ## Chrome In the Legacy Platform, the `ui/chrome` import contained APIs for a very wide From 81f3fbbf2546f33c980477ad617f910f313bdf78 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Thu, 30 Jan 2020 15:06:52 -0600 Subject: [PATCH 07/13] [APM] Service map center button (#56434) Add center button for service map. The fullscreen button is still there until #56351 is merged. Add fit and padding to the layout animation. Make the node labels wider so they aren't cut off. --- .../components/app/ServiceMap/Controls.tsx | 28 +++++++++++++++++-- .../app/ServiceMap/cytoscapeOptions.ts | 18 +++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx index 5b7ddc2b450d6..07ea97f442b7f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, useState, useEffect } from 'react'; import { EuiButtonIcon, EuiPanel } from '@elastic/eui'; import theme from '@elastic/eui/dist/eui_theme_light.json'; -import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; +import React, { useContext, useEffect, useState } from 'react'; +import styled from 'styled-components'; import { CytoscapeContext } from './Cytoscape'; +import { animationOptions, nodeHeight } from './cytoscapeOptions'; import { FullscreenPanel } from './FullscreenPanel'; const ControlsContainer = styled('div')` @@ -58,6 +59,17 @@ export function Controls() { } }, [cy]); + function center() { + if (cy) { + const eles = cy.nodes(); + cy.animate({ + ...animationOptions, + center: { eles }, + fit: { eles, padding: nodeHeight } + }); + } + } + function zoomIn() { doZoom(cy, increment); } @@ -82,6 +94,9 @@ export function Controls() { const zoomOutLabel = i18n.translate('xpack.apm.serviceMap.zoomOut', { defaultMessage: 'Zoom out' }); + const centerLabel = i18n.translate('xpack.apm.serviceMap.center', { + defaultMessage: 'Center' + }); return ( @@ -103,6 +118,15 @@ export function Controls() { title={zoomOutLabel} /> + +