[Core] Explicit typings for request handler context (#88718) (#88975)

* move context to server part. couple with RequestHandlerContext

Context implementation will be simplified in follow-up.

* adopt core code

* adopt bfetch code

* adopt data code

* adopt search examples

* adopt vis_type_timelion

* adopt vis_type_timeseries

* adopt plugin functional tests

* adopt actions

* adopt alerting plugin

* adopt APM plugin

* adopt beats_management

* adopt case plugin

* adopt cross_cluster_replication

* adopt data_enhanced

* adopt event_log

* adopt global_search

* adopt index_management

* adopt infra

* adopt licensing

* adopt lists

* adopt logstash

* adopt reporting

* adopt observability

* adopt monitoring

* adopt rollup

* adopt so tagging

* adopt security

* adopt security_solutions

* adopt watcher

* adopt uptime

* adopt spaces

* adopt snapshot_restore

* adopt features changes

* mute error when null used to extend context

* update docs

* small cleanup

* add type safety for return type

* refactor registerRouteHandlerContext type

* update docs

* update license header

* update docs

* fix type error. fetch body does not accept array of strings

* fix telemetry test

* remove unnecessary ts-ignore

* address comments

* update docs
# Conflicts:
#	docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md
#	src/plugins/data/server/server.api.md
#	x-pack/plugins/monitoring/server/plugin.ts
This commit is contained in:
Mikhail Shustov 2021-01-21 18:13:51 +01:00 committed by GitHub
parent d8ef1b4adb
commit 99cd66f72d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
382 changed files with 2306 additions and 1872 deletions

View file

@ -421,29 +421,25 @@ the request handler context:
[source,typescript]
----
import type { CoreSetup, IScopedClusterClient } from 'kibana/server';
import type { CoreSetup, RequestHandlerContext, IScopedClusterClient } from 'kibana/server';
export interface MyPluginContext {
client: IScopedClusterClient;
}
// extend RequestHandlerContext when a dependent plugin imports MyPluginContext from the file
declare module 'kibana/server' {
interface RequestHandlerContext {
myPlugin?: MyPluginContext;
}
interface MyRequestHandlerContext extends RequestHandlerContext {
myPlugin: {
client: IScopedClusterClient;
};
}
class MyPlugin {
setup(core: CoreSetup) {
const client = core.elasticsearch.createClient('myClient');
core.http.registerRouteHandlerContext('myPlugin', (context, req, res) => {
core.http.registerRouteHandlerContext<MyRequestHandlerContext, 'myPlugin'>('myPlugin', (context, req, res) => {
return { client: client.asScoped(req) };
});
const router = core.http.createRouter();
const router = core.http.createRouter<MyRequestHandlerContext>();
router.get(
{ path: '/api/my-plugin/', validate: … },
async (context, req, res) => {
// context type is inferred as MyPluginContext
const data = await context.myPlugin.client.asCurrentUser('endpoint');
}
);

View file

@ -16,5 +16,5 @@ export interface HttpResources
| Property | Type | Description |
| --- | --- | --- |
| [register](./kibana-plugin-core-server.httpresources.register.md) | <code>&lt;P, Q, B&gt;(route: RouteConfig&lt;P, Q, B, 'get'&gt;, handler: HttpResourcesRequestHandler&lt;P, Q, B&gt;) =&gt; void</code> | To register a route handler executing passed function to form response. |
| [register](./kibana-plugin-core-server.httpresources.register.md) | <code>&lt;P, Q, B, Context extends RequestHandlerContext = RequestHandlerContext&gt;(route: RouteConfig&lt;P, Q, B, 'get'&gt;, handler: HttpResourcesRequestHandler&lt;P, Q, B, Context&gt;) =&gt; void</code> | To register a route handler executing passed function to form response. |

View file

@ -9,5 +9,5 @@ To register a route handler executing passed function to form response.
<b>Signature:</b>
```typescript
register: <P, Q, B>(route: RouteConfig<P, Q, B, 'get'>, handler: HttpResourcesRequestHandler<P, Q, B>) => void;
register: <P, Q, B, Context extends RequestHandlerContext = RequestHandlerContext>(route: RouteConfig<P, Q, B, 'get'>, handler: HttpResourcesRequestHandler<P, Q, B, Context>) => void;
```

View file

@ -9,7 +9,7 @@ Extended version of [RequestHandler](./kibana-plugin-core-server.requesthandler.
<b>Signature:</b>
```typescript
export declare type HttpResourcesRequestHandler<P = unknown, Q = unknown, B = unknown> = RequestHandler<P, Q, B, 'get', KibanaResponseFactory & HttpResourcesServiceToolkit>;
export declare type HttpResourcesRequestHandler<P = unknown, Q = unknown, B = unknown, Context extends RequestHandlerContext = RequestHandlerContext> = RequestHandler<P, Q, B, Context, 'get', KibanaResponseFactory & HttpResourcesServiceToolkit>;
```
## Example

View file

@ -9,7 +9,7 @@ Provides ability to declare a handler function for a particular path and HTTP re
<b>Signature:</b>
```typescript
createRouter: () => IRouter;
createRouter: <Context extends RequestHandlerContext = RequestHandlerContext>() => IRouter<Context>;
```
## Remarks

View file

@ -84,7 +84,7 @@ async (context, request, response) => {
| [auth](./kibana-plugin-core-server.httpservicesetup.auth.md) | <code>HttpAuth</code> | Auth status. See [HttpAuth](./kibana-plugin-core-server.httpauth.md) |
| [basePath](./kibana-plugin-core-server.httpservicesetup.basepath.md) | <code>IBasePath</code> | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-core-server.ibasepath.md)<!-- -->. |
| [createCookieSessionStorageFactory](./kibana-plugin-core-server.httpservicesetup.createcookiesessionstoragefactory.md) | <code>&lt;T&gt;(cookieOptions: SessionStorageCookieOptions&lt;T&gt;) =&gt; Promise&lt;SessionStorageFactory&lt;T&gt;&gt;</code> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-core-server.sessionstoragefactory.md) |
| [createRouter](./kibana-plugin-core-server.httpservicesetup.createrouter.md) | <code>() =&gt; IRouter</code> | Provides ability to declare a handler function for a particular path and HTTP request method. |
| [createRouter](./kibana-plugin-core-server.httpservicesetup.createrouter.md) | <code>&lt;Context extends RequestHandlerContext = RequestHandlerContext&gt;() =&gt; IRouter&lt;Context&gt;</code> | Provides ability to declare a handler function for a particular path and HTTP request method. |
| [csp](./kibana-plugin-core-server.httpservicesetup.csp.md) | <code>ICspConfig</code> | The CSP config used for Kibana. |
| [getServerInfo](./kibana-plugin-core-server.httpservicesetup.getserverinfo.md) | <code>() =&gt; HttpServerInfo</code> | Provides common [information](./kibana-plugin-core-server.httpserverinfo.md) about the running http server. |
| [registerAuth](./kibana-plugin-core-server.httpservicesetup.registerauth.md) | <code>(handler: AuthenticationHandler) =&gt; void</code> | To define custom authentication and/or authorization mechanism for incoming requests. |
@ -92,5 +92,5 @@ async (context, request, response) => {
| [registerOnPreAuth](./kibana-plugin-core-server.httpservicesetup.registeronpreauth.md) | <code>(handler: OnPreAuthHandler) =&gt; void</code> | To define custom logic to perform for incoming requests before the Auth interceptor performs a check that user has access to requested resources. |
| [registerOnPreResponse](./kibana-plugin-core-server.httpservicesetup.registeronpreresponse.md) | <code>(handler: OnPreResponseHandler) =&gt; void</code> | To define custom logic to perform for the server response. |
| [registerOnPreRouting](./kibana-plugin-core-server.httpservicesetup.registeronprerouting.md) | <code>(handler: OnPreRoutingHandler) =&gt; void</code> | To define custom logic to perform for incoming requests before server performs a route lookup. |
| [registerRouteHandlerContext](./kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md) | <code>&lt;T extends keyof RequestHandlerContext&gt;(contextName: T, provider: RequestHandlerContextProvider&lt;T&gt;) =&gt; RequestHandlerContextContainer</code> | Register a context provider for a route handler. |
| [registerRouteHandlerContext](./kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md) | <code>&lt;Context extends RequestHandlerContext, ContextName extends keyof Context&gt;(contextName: ContextName, provider: RequestHandlerContextProvider&lt;Context, ContextName&gt;) =&gt; RequestHandlerContextContainer</code> | Register a context provider for a route handler. |

View file

@ -9,7 +9,7 @@ Register a context provider for a route handler.
<b>Signature:</b>
```typescript
registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer;
registerRouteHandlerContext: <Context extends RequestHandlerContext, ContextName extends keyof Context>(contextName: ContextName, provider: RequestHandlerContextProvider<Context, ContextName>) => RequestHandlerContextContainer;
```
## Example
@ -17,7 +17,10 @@ registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(contextName
```ts
// my-plugin.ts
deps.http.registerRouteHandlerContext(
interface MyRequestHandlerContext extends RequestHandlerContext {
myApp: { search(id: string): Promise<Result> };
}
deps.http.registerRouteHandlerContext<MyRequestHandlerContext, 'myApp'>(
'myApp',
(context, req) => {
async function search (id: string) {
@ -28,6 +31,8 @@ registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(contextName
);
// my-route-handler.ts
import type { MyRequestHandlerContext } from './my-plugin.ts';
const router = createRouter<MyRequestHandlerContext>();
router.get({ path: '/', validate: false }, async (context, req, res) => {
const response = await context.myApp.search(...);
return res.ok(response);

View file

@ -9,7 +9,7 @@ An object that handles registration of context providers and configuring handler
<b>Signature:</b>
```typescript
export interface IContextContainer<THandler extends HandlerFunction<any>>
export interface IContextContainer<THandler extends RequestHandler>
```
## Remarks

View file

@ -9,7 +9,7 @@ Register a new context provider.
<b>Signature:</b>
```typescript
registerContext<TContextName extends keyof HandlerContextType<THandler>>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider<THandler, TContextName>): this;
registerContext<Context extends RequestHandlerContext, ContextName extends keyof Context>(pluginOpaqueId: PluginOpaqueId, contextName: ContextName, provider: IContextProvider<Context, ContextName>): this;
```
## Parameters
@ -17,8 +17,8 @@ registerContext<TContextName extends keyof HandlerContextType<THandler>>(pluginO
| Parameter | Type | Description |
| --- | --- | --- |
| pluginOpaqueId | <code>PluginOpaqueId</code> | The plugin opaque ID for the plugin that registers this context. |
| contextName | <code>TContextName</code> | The key of the <code>TContext</code> object this provider supplies the value for. |
| provider | <code>IContextProvider&lt;THandler, TContextName&gt;</code> | A [IContextProvider](./kibana-plugin-core-server.icontextprovider.md) to be called each time a new context is created. |
| contextName | <code>ContextName</code> | The key of the <code>TContext</code> object this provider supplies the value for. |
| provider | <code>IContextProvider&lt;Context, ContextName&gt;</code> | A [IContextProvider](./kibana-plugin-core-server.icontextprovider.md) to be called each time a new context is created. |
<b>Returns:</b>

View file

@ -9,7 +9,7 @@ A function that returns a context value for a specific key of given context type
<b>Signature:</b>
```typescript
export declare type IContextProvider<THandler extends HandlerFunction<any>, TContextName extends keyof HandlerContextType<THandler>> = (context: PartialExceptFor<HandlerContextType<THandler>, 'core'>, ...rest: HandlerParameters<THandler>) => Promise<HandlerContextType<THandler>[TContextName]> | HandlerContextType<THandler>[TContextName];
export declare type IContextProvider<Context extends RequestHandlerContext, ContextName extends keyof Context> = (context: Omit<Context, ContextName>, ...rest: HandlerParameters<RequestHandler>) => Promise<Context[ContextName]> | Context[ContextName];
```
## Remarks

View file

@ -9,5 +9,5 @@ Register a route handler for `DELETE` request.
<b>Signature:</b>
```typescript
delete: RouteRegistrar<'delete'>;
delete: RouteRegistrar<'delete', Context>;
```

View file

@ -9,5 +9,5 @@ Register a route handler for `GET` request.
<b>Signature:</b>
```typescript
get: RouteRegistrar<'get'>;
get: RouteRegistrar<'get', Context>;
```

View file

@ -9,18 +9,18 @@ Registers route handlers for specified resource path and method. See [RouteConfi
<b>Signature:</b>
```typescript
export interface IRouter
export interface IRouter<Context extends RequestHandlerContext = RequestHandlerContext>
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [delete](./kibana-plugin-core-server.irouter.delete.md) | <code>RouteRegistrar&lt;'delete'&gt;</code> | Register a route handler for <code>DELETE</code> request. |
| [get](./kibana-plugin-core-server.irouter.get.md) | <code>RouteRegistrar&lt;'get'&gt;</code> | Register a route handler for <code>GET</code> request. |
| [delete](./kibana-plugin-core-server.irouter.delete.md) | <code>RouteRegistrar&lt;'delete', Context&gt;</code> | Register a route handler for <code>DELETE</code> request. |
| [get](./kibana-plugin-core-server.irouter.get.md) | <code>RouteRegistrar&lt;'get', Context&gt;</code> | Register a route handler for <code>GET</code> request. |
| [handleLegacyErrors](./kibana-plugin-core-server.irouter.handlelegacyerrors.md) | <code>RequestHandlerWrapper</code> | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. |
| [patch](./kibana-plugin-core-server.irouter.patch.md) | <code>RouteRegistrar&lt;'patch'&gt;</code> | Register a route handler for <code>PATCH</code> request. |
| [post](./kibana-plugin-core-server.irouter.post.md) | <code>RouteRegistrar&lt;'post'&gt;</code> | Register a route handler for <code>POST</code> request. |
| [put](./kibana-plugin-core-server.irouter.put.md) | <code>RouteRegistrar&lt;'put'&gt;</code> | Register a route handler for <code>PUT</code> request. |
| [patch](./kibana-plugin-core-server.irouter.patch.md) | <code>RouteRegistrar&lt;'patch', Context&gt;</code> | Register a route handler for <code>PATCH</code> request. |
| [post](./kibana-plugin-core-server.irouter.post.md) | <code>RouteRegistrar&lt;'post', Context&gt;</code> | Register a route handler for <code>POST</code> request. |
| [put](./kibana-plugin-core-server.irouter.put.md) | <code>RouteRegistrar&lt;'put', Context&gt;</code> | Register a route handler for <code>PUT</code> request. |
| [routerPath](./kibana-plugin-core-server.irouter.routerpath.md) | <code>string</code> | Resulted path |

View file

@ -9,5 +9,5 @@ Register a route handler for `PATCH` request.
<b>Signature:</b>
```typescript
patch: RouteRegistrar<'patch'>;
patch: RouteRegistrar<'patch', Context>;
```

View file

@ -9,5 +9,5 @@ Register a route handler for `POST` request.
<b>Signature:</b>
```typescript
post: RouteRegistrar<'post'>;
post: RouteRegistrar<'post', Context>;
```

View file

@ -9,5 +9,5 @@ Register a route handler for `PUT` request.
<b>Signature:</b>
```typescript
put: RouteRegistrar<'put'>;
put: RouteRegistrar<'put', Context>;
```

View file

@ -9,7 +9,7 @@ A function executed when route path matched requested resource path. Request han
<b>Signature:</b>
```typescript
export declare type RequestHandler<P = unknown, Q = unknown, B = unknown, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory> = (context: RequestHandlerContext, request: KibanaRequest<P, Q, B, Method>, response: ResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
export declare type RequestHandler<P = unknown, Q = unknown, B = unknown, Context extends RequestHandlerContext = RequestHandlerContext, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory> = (context: Context, request: KibanaRequest<P, Q, B, Method>, response: ResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
```
## Example

View file

@ -9,5 +9,5 @@ An object that handles registration of http request context providers.
<b>Signature:</b>
```typescript
export declare type RequestHandlerContextContainer = IContextContainer<RequestHandler<any, any, any>>;
export declare type RequestHandlerContextContainer = IContextContainer<RequestHandler>;
```

View file

@ -9,5 +9,5 @@ Context provider for request handler. Extends request context object with provid
<b>Signature:</b>
```typescript
export declare type RequestHandlerContextProvider<TContextName extends keyof RequestHandlerContext> = IContextProvider<RequestHandler<any, any, any>, TContextName>;
export declare type RequestHandlerContextProvider<Context extends RequestHandlerContext, ContextName extends keyof Context> = IContextProvider<Context, ContextName>;
```

View file

@ -9,7 +9,7 @@ Type-safe wrapper for [RequestHandler](./kibana-plugin-core-server.requesthandle
<b>Signature:</b>
```typescript
export declare type RequestHandlerWrapper = <P, Q, B, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory>(handler: RequestHandler<P, Q, B, Method, ResponseFactory>) => RequestHandler<P, Q, B, Method, ResponseFactory>;
export declare type RequestHandlerWrapper = <P, Q, B, Context extends RequestHandlerContext = RequestHandlerContext, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory>(handler: RequestHandler<P, Q, B, Context, Method, ResponseFactory>) => RequestHandler<P, Q, B, Context, Method, ResponseFactory>;
```
## Example

View file

@ -9,5 +9,5 @@ Route handler common definition
<b>Signature:</b>
```typescript
export declare type RouteRegistrar<Method extends RouteMethod> = <P, Q, B>(route: RouteConfig<P, Q, B, Method>, handler: RequestHandler<P, Q, B, Method>) => void;
export declare type RouteRegistrar<Method extends RouteMethod, Context extends RequestHandlerContext = RequestHandlerContext> = <P, Q, B>(route: RouteConfig<P, Q, B, Method>, handler: RequestHandler<P, Q, B, Context, Method>) => void;
```

View file

@ -15,13 +15,13 @@ Using `createSearchSource`<!-- -->, the instance can be re-created.
```typescript
serialize(): {
searchSourceJSON: string;
references: import("src/core/server").SavedObjectReference[];
references: import("../../../../../core/types").SavedObjectReference[];
};
```
<b>Returns:</b>
`{
searchSourceJSON: string;
references: import("src/core/server").SavedObjectReference[];
references: import("../../../../../core/types").SavedObjectReference[];
}`

View file

@ -0,0 +1,18 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md)
## DataApiRequestHandlerContext interface
<b>Signature:</b>
```typescript
export interface DataApiRequestHandlerContext extends ISearchClient
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [session](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md) | <code>IScopedSessionService</code> | |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md) &gt; [session](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md)
## DataApiRequestHandlerContext.session property
<b>Signature:</b>
```typescript
session: IScopedSessionService;
```

View file

@ -45,6 +45,7 @@
| --- | --- |
| [AggFunctionsMapping](./kibana-plugin-plugins-data-server.aggfunctionsmapping.md) | A global list of the expression function definitions for each agg type function. |
| [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md) | |
| [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md) | |
| [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) | |
| [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) | |
| [FieldFormatConfig](./kibana-plugin-plugins-data-server.fieldformatconfig.md) | |

View file

@ -9,10 +9,10 @@
```typescript
start(core: CoreStart): {
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("src/core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise<import("../public").IndexPatternsService>;
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise<import("../public").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
};
@ -28,10 +28,10 @@ start(core: CoreStart): {
`{
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("src/core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise<import("../public").IndexPatternsService>;
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise<import("../public").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
}`

View file

@ -6,13 +6,16 @@
* Public License, v 1.
*/
import {
import type {
PluginInitializerContext,
CoreSetup,
CoreStart,
Plugin,
Logger,
} from '../../../src/core/server';
RequestHandlerContext,
} from 'src/core/server';
import type { DataApiRequestHandlerContext } from 'src/plugins/data/server';
import {
SearchExamplesPluginSetup,
@ -42,12 +45,14 @@ export class SearchExamplesPlugin
deps: SearchExamplesPluginSetupDeps
) {
this.logger.debug('search_examples: Setup');
const router = core.http.createRouter();
const router = core.http.createRouter<
RequestHandlerContext & { search: DataApiRequestHandlerContext }
>();
core.getStartServices().then(([_, depsStart]) => {
const myStrategy = mySearchStrategyProvider(depsStart.data);
deps.data.search.registerSearchStrategy('myStrategy', myStrategy);
registerRoutes(router, depsStart.data);
registerRoutes(router);
});
return {};

View file

@ -6,10 +6,12 @@
* Public License, v 1.
*/
import { IRouter } from 'kibana/server';
import { PluginStart as DataPluginStart } from 'src/plugins/data/server';
import type { IRouter, RequestHandlerContext } from 'kibana/server';
import { DataApiRequestHandlerContext } from 'src/plugins/data/server';
import { registerServerSearchRoute } from './server_search_route';
export function registerRoutes(router: IRouter, data: DataPluginStart) {
registerServerSearchRoute(router, data);
export function registerRoutes(
router: IRouter<RequestHandlerContext & { search: DataApiRequestHandlerContext }>
) {
registerServerSearchRoute(router);
}

View file

@ -6,13 +6,16 @@
* Public License, v 1.
*/
import { PluginStart as DataPluginStart, IEsSearchRequest } from 'src/plugins/data/server';
import { IEsSearchRequest } from 'src/plugins/data/server';
import { schema } from '@kbn/config-schema';
import { IEsSearchResponse } from 'src/plugins/data/common';
import { IRouter } from '../../../../src/core/server';
import type { DataApiRequestHandlerContext } from 'src/plugins/data/server';
import type { IRouter, RequestHandlerContext } from 'src/core/server';
import { SERVER_SEARCH_ROUTE_PATH } from '../../common';
export function registerServerSearchRoute(router: IRouter, data: DataPluginStart) {
export function registerServerSearchRoute(
router: IRouter<RequestHandlerContext & { search: DataApiRequestHandlerContext }>
) {
router.get(
{
path: SERVER_SEARCH_ROUTE_PATH,

View file

@ -12,6 +12,7 @@ export type ContextContainerMock = jest.Mocked<IContextContainer<any>>;
const createContextMock = (mockContext = {}) => {
const contextMock: ContextContainerMock = {
// @ts-expect-error tsc cannot infer ContextName and uses never
registerContext: jest.fn(),
createHandler: jest.fn(),
};

View file

@ -0,0 +1,319 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { ContextContainer } from './context';
import { PluginOpaqueId } from '../..';
import { httpServerMock } from '../../http/http_server.mocks';
const pluginA = Symbol('pluginA');
const pluginB = Symbol('pluginB');
const pluginC = Symbol('pluginC');
const pluginD = Symbol('pluginD');
const plugins: ReadonlyMap<PluginOpaqueId, PluginOpaqueId[]> = new Map([
[pluginA, []],
[pluginB, [pluginA]],
[pluginC, [pluginA, pluginB]],
[pluginD, []],
]);
const coreId = Symbol();
interface MyContext {
core: any;
core1: string;
core2: number;
ctxFromA: string;
ctxFromB: number;
ctxFromC: boolean;
ctxFromD: object;
}
describe('ContextContainer', () => {
it('does not allow the same context to be registered twice', () => {
const contextContainer = new ContextContainer(plugins, coreId);
contextContainer.registerContext<{ ctxFromA: string; core: any }, 'ctxFromA'>(
coreId,
'ctxFromA',
() => 'aString'
);
expect(() =>
contextContainer.registerContext<{ ctxFromA: string; core: any }, 'ctxFromA'>(
coreId,
'ctxFromA',
() => 'aString'
)
).toThrowErrorMatchingInlineSnapshot(
`"Context provider for ctxFromA has already been registered."`
);
});
describe('registerContext', () => {
it('throws error if called with an unknown symbol', async () => {
const contextContainer = new ContextContainer(plugins, coreId);
await expect(() =>
contextContainer.registerContext<{ ctxFromA: string; core: any }, 'ctxFromA'>(
Symbol('unknown'),
'ctxFromA',
jest.fn()
)
).toThrowErrorMatchingInlineSnapshot(
`"Cannot register context for unknown plugin: Symbol(unknown)"`
);
});
});
describe('context building', () => {
it('resolves dependencies', async () => {
const contextContainer = new ContextContainer(plugins, coreId);
expect.assertions(8);
contextContainer.registerContext<{ core1: string; core: any }, 'core1'>(
coreId,
'core1',
(context) => {
expect(context).toEqual({});
return 'core';
}
);
contextContainer.registerContext<{ ctxFromA: string; core: any }, 'ctxFromA'>(
pluginA,
'ctxFromA',
(context) => {
expect(context).toEqual({ core1: 'core' });
return 'aString';
}
);
contextContainer.registerContext<{ ctxFromB: number; core: any }, 'ctxFromB'>(
pluginB,
'ctxFromB',
(context) => {
expect(context).toEqual({ core1: 'core', ctxFromA: 'aString' });
return 299;
}
);
contextContainer.registerContext<{ ctxFromC: boolean; core: any }, 'ctxFromC'>(
pluginC,
'ctxFromC',
(context) => {
expect(context).toEqual({
core1: 'core',
ctxFromA: 'aString',
ctxFromB: 299,
});
return false;
}
);
contextContainer.registerContext<{ ctxFromD: {}; core: any }, 'ctxFromD'>(
pluginD,
'ctxFromD',
(context) => {
expect(context).toEqual({ core1: 'core' });
return {};
}
);
const rawHandler1 = jest.fn(() => 'handler1' as any);
const handler1 = contextContainer.createHandler(pluginC, rawHandler1);
const rawHandler2 = jest.fn(() => 'handler2' as any);
const handler2 = contextContainer.createHandler(pluginD, rawHandler2);
const request = httpServerMock.createKibanaRequest();
const response = httpServerMock.createResponseFactory();
await handler1(request, response);
await handler2(request, response);
// Should have context from pluginC, its deps, and core
expect(rawHandler1).toHaveBeenCalledWith(
{
core1: 'core',
ctxFromA: 'aString',
ctxFromB: 299,
ctxFromC: false,
},
request,
response
);
// Should have context from pluginD, and core
expect(rawHandler2).toHaveBeenCalledWith(
{
core1: 'core',
ctxFromD: {},
},
request,
response
);
});
it('exposes all core context to all providers regardless of registration order', async () => {
expect.assertions(4);
const contextContainer = new ContextContainer(plugins, coreId);
contextContainer
.registerContext<MyContext, 'ctxFromA'>(pluginA, 'ctxFromA', (context) => {
expect(context).toEqual({ core1: 'core', core2: 101 });
return `aString ${context.core1} ${context.core2}`;
})
.registerContext<MyContext, 'core1'>(coreId, 'core1', () => 'core')
.registerContext<MyContext, 'core2'>(coreId, 'core2', () => 101)
.registerContext<MyContext, 'ctxFromB'>(pluginB, 'ctxFromB', (context) => {
expect(context).toEqual({
core1: 'core',
core2: 101,
ctxFromA: 'aString core 101',
});
return 277;
});
const rawHandler1 = jest.fn(() => 'handler1' as any);
const handler1 = contextContainer.createHandler(pluginB, rawHandler1);
const request = httpServerMock.createKibanaRequest();
const response = httpServerMock.createResponseFactory();
expect(await handler1(request, response)).toEqual('handler1');
expect(rawHandler1).toHaveBeenCalledWith(
{
core1: 'core',
core2: 101,
ctxFromA: 'aString core 101',
ctxFromB: 277,
},
request,
response
);
});
it('exposes all core context to core providers', async () => {
expect.assertions(4);
const contextContainer = new ContextContainer(plugins, coreId);
contextContainer
.registerContext<MyContext, 'core1'>(coreId, 'core1', (context) => {
expect(context).toEqual({});
return 'core';
})
.registerContext<MyContext, 'core2'>(coreId, 'core2', (context) => {
expect(context).toEqual({ core1: 'core' });
return 101;
});
const rawHandler1 = jest.fn(() => 'handler1' as any);
const handler1 = contextContainer.createHandler(pluginA, rawHandler1);
const request = httpServerMock.createKibanaRequest();
const response = httpServerMock.createResponseFactory();
expect(await handler1(request, response)).toEqual('handler1');
// If no context is registered for pluginA, only core contexts should be exposed
expect(rawHandler1).toHaveBeenCalledWith(
{
core1: 'core',
core2: 101,
},
request,
response
);
});
it('does not expose plugin contexts to core handler', async () => {
const contextContainer = new ContextContainer(plugins, coreId);
contextContainer
.registerContext<MyContext, 'core1'>(coreId, 'core1', (context) => 'core')
.registerContext<MyContext, 'ctxFromA'>(pluginA, 'ctxFromA', (context) => 'aString');
const rawHandler1 = jest.fn(() => 'handler1' as any);
const handler1 = contextContainer.createHandler(coreId, rawHandler1);
const request = httpServerMock.createKibanaRequest();
const response = httpServerMock.createResponseFactory();
expect(await handler1(request, response)).toEqual('handler1');
// pluginA context should not be present in a core handler
expect(rawHandler1).toHaveBeenCalledWith(
{
core1: 'core',
},
request,
response
);
});
it('passes additional arguments to providers', async () => {
expect.assertions(6);
const contextContainer = new ContextContainer(plugins, coreId);
const request = httpServerMock.createKibanaRequest();
const response = httpServerMock.createResponseFactory();
contextContainer.registerContext<MyContext, 'core1'>(coreId, 'core1', (context, req, res) => {
expect(req).toBe(request);
expect(res).toBe(response);
return 'core';
});
contextContainer.registerContext<MyContext, 'ctxFromB'>(
pluginD,
'ctxFromB',
(context, req, res) => {
expect(req).toBe(request);
expect(res).toBe(response);
return 77;
}
);
const rawHandler1 = jest.fn(() => 'handler1' as any);
const handler1 = contextContainer.createHandler(pluginD, rawHandler1);
expect(await handler1(request, response)).toEqual('handler1');
expect(rawHandler1).toHaveBeenCalledWith(
{
core1: 'core',
ctxFromB: 77,
},
request,
response
);
});
});
describe('createHandler', () => {
it('throws error if called with an unknown symbol', async () => {
const contextContainer = new ContextContainer(plugins, coreId);
await expect(() =>
contextContainer.createHandler(Symbol('unknown'), jest.fn())
).toThrowErrorMatchingInlineSnapshot(
`"Cannot create handler for unknown plugin: Symbol(unknown)"`
);
});
it('returns value from original handler', async () => {
const contextContainer = new ContextContainer(plugins, coreId);
const rawHandler1 = jest.fn(() => 'handler1' as any);
const handler1 = contextContainer.createHandler(pluginA, rawHandler1);
const request = httpServerMock.createKibanaRequest();
const response = httpServerMock.createResponseFactory();
expect(await handler1(request, response)).toEqual('handler1');
});
it('passes additional arguments to handlers', async () => {
const contextContainer = new ContextContainer(plugins, coreId);
const rawHandler1 = jest.fn(() => 'handler1' as any);
const handler1 = contextContainer.createHandler(pluginA, rawHandler1);
const request = httpServerMock.createKibanaRequest();
const response = httpServerMock.createResponseFactory();
await handler1(request, response);
expect(rawHandler1).toHaveBeenCalledWith({}, request, response);
});
});
});

View file

@ -8,13 +8,8 @@
import { flatten } from 'lodash';
import { ShallowPromise } from '@kbn/utility-types';
import { pick } from '@kbn/std';
import type { CoreId, PluginOpaqueId } from '../server';
/**
* Make all properties in T optional, except for the properties whose keys are in the union K
*/
type PartialExceptFor<T, K extends keyof T> = Partial<T> & Pick<T, K>;
import { pick } from 'lodash';
import type { CoreId, PluginOpaqueId, RequestHandler, RequestHandlerContext } from '../..';
/**
* A function that returns a context value for a specific key of given context type.
@ -30,15 +25,13 @@ type PartialExceptFor<T, K extends keyof T> = Partial<T> & Pick<T, K>;
* @public
*/
export type IContextProvider<
THandler extends HandlerFunction<any>,
TContextName extends keyof HandlerContextType<THandler>
Context extends RequestHandlerContext,
ContextName extends keyof Context
> = (
// context.core will always be available, but plugin contexts are typed as optional
context: PartialExceptFor<HandlerContextType<THandler>, 'core'>,
...rest: HandlerParameters<THandler>
) =>
| Promise<HandlerContextType<THandler>[TContextName]>
| HandlerContextType<THandler>[TContextName];
context: Omit<Context, ContextName>,
...rest: HandlerParameters<RequestHandler>
) => Promise<Context[ContextName]> | Context[ContextName];
/**
* A function that accepts a context object and an optional number of additional arguments. Used for the generic types
@ -142,7 +135,7 @@ export type HandlerParameters<T extends HandlerFunction<any>> = T extends (
*
* @public
*/
export interface IContextContainer<THandler extends HandlerFunction<any>> {
export interface IContextContainer<THandler extends RequestHandler> {
/**
* Register a new context provider.
*
@ -157,10 +150,10 @@ export interface IContextContainer<THandler extends HandlerFunction<any>> {
* @param provider - A {@link IContextProvider} to be called each time a new context is created.
* @returns The {@link IContextContainer} for method chaining.
*/
registerContext<TContextName extends keyof HandlerContextType<THandler>>(
registerContext<Context extends RequestHandlerContext, ContextName extends keyof Context>(
pluginOpaqueId: PluginOpaqueId,
contextName: TContextName,
provider: IContextProvider<THandler, TContextName>
contextName: ContextName,
provider: IContextProvider<Context, ContextName>
): this;
/**
@ -178,21 +171,21 @@ export interface IContextContainer<THandler extends HandlerFunction<any>> {
}
/** @internal */
export class ContextContainer<THandler extends HandlerFunction<any>>
export class ContextContainer<THandler extends RequestHandler>
implements IContextContainer<THandler> {
/**
* Used to map contexts to their providers and associated plugin. In registration order which is tightly coupled to
* plugin load order.
*/
private readonly contextProviders = new Map<
keyof HandlerContextType<THandler>,
string,
{
provider: IContextProvider<THandler, keyof HandlerContextType<THandler>>;
provider: IContextProvider<any, any>;
source: symbol;
}
>();
/** Used to keep track of which plugins registered which contexts for dependency resolution. */
private readonly contextNamesBySource: Map<symbol, Array<keyof HandlerContextType<THandler>>>;
private readonly contextNamesBySource: Map<symbol, string[]>;
/**
* @param pluginDependencies - A map of plugins to an array of their dependencies.
@ -201,16 +194,18 @@ export class ContextContainer<THandler extends HandlerFunction<any>>
private readonly pluginDependencies: ReadonlyMap<PluginOpaqueId, PluginOpaqueId[]>,
private readonly coreId: CoreId
) {
this.contextNamesBySource = new Map<symbol, Array<keyof HandlerContextType<THandler>>>([
[coreId, []],
]);
this.contextNamesBySource = new Map<symbol, string[]>([[coreId, []]]);
}
public registerContext = <TContextName extends keyof HandlerContextType<THandler>>(
public registerContext = <
Context extends RequestHandlerContext,
ContextName extends keyof Context
>(
source: symbol,
contextName: TContextName,
provider: IContextProvider<THandler, TContextName>
name: ContextName,
provider: IContextProvider<Context, ContextName>
): this => {
const contextName = name as string;
if (this.contextProviders.has(contextName)) {
throw new Error(`Context provider for ${contextName} has already been registered.`);
}
@ -234,6 +229,7 @@ export class ContextContainer<THandler extends HandlerFunction<any>>
return (async (...args: HandlerParameters<THandler>) => {
const context = await this.buildContext(source, ...args);
// @ts-expect-error requires explicit handler arity
return handler(context, ...args);
}) as (...args: HandlerParameters<THandler>) => ShallowPromise<ReturnType<THandler>>;
};
@ -242,9 +238,7 @@ export class ContextContainer<THandler extends HandlerFunction<any>>
source: symbol,
...contextArgs: HandlerParameters<THandler>
): Promise<HandlerContextType<THandler>> {
const contextsToBuild: ReadonlySet<keyof HandlerContextType<THandler>> = new Set(
this.getContextNamesForSource(source)
);
const contextsToBuild = new Set(this.getContextNamesForSource(source));
return [...this.contextProviders]
.sort(sortByCoreFirst(this.coreId))
@ -256,18 +250,17 @@ export class ContextContainer<THandler extends HandlerFunction<any>>
// registered that provider.
const exposedContext = pick(resolvedContext, [
...this.getContextNamesForSource(providerSource),
]) as PartialExceptFor<HandlerContextType<THandler>, 'core'>;
]);
return {
...resolvedContext,
// @ts-expect-error requires explicit provider arity
[contextName]: await provider(exposedContext, ...contextArgs),
};
}, Promise.resolve({}) as Promise<HandlerContextType<THandler>>);
}
private getContextNamesForSource(
source: symbol
): ReadonlySet<keyof HandlerContextType<THandler>> {
private getContextNamesForSource(source: symbol): ReadonlySet<string> {
if (source === this.coreId) {
return this.getContextNamesForCore();
} else {

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
export * from './context';

View file

@ -9,7 +9,7 @@
import type { PublicMethodsOf } from '@kbn/utility-types';
import { ContextService, ContextSetup } from './context_service';
import { contextMock } from '../../utils/context.mock';
import { contextMock } from './container/context.mock';
const createSetupContractMock = (mockContext = {}) => {
const setupContract: jest.Mocked<ContextSetup> = {

View file

@ -6,9 +6,9 @@
* Public License, v 1.
*/
import { contextMock } from '../../utils/context.mock';
import { contextMock } from './container/context.mock';
export const MockContextConstructor = jest.fn(contextMock.create);
jest.doMock('../../utils/context', () => ({
jest.doMock('./container/context', () => ({
ContextContainer: MockContextConstructor,
}));

View file

@ -7,7 +7,7 @@
*/
import { PluginOpaqueId } from '../../server';
import { IContextContainer, ContextContainer, HandlerFunction } from '../../utils/context';
import { IContextContainer, ContextContainer, HandlerFunction } from './container';
import { CoreContext } from '../core_context';
interface SetupDeps {

View file

@ -13,4 +13,4 @@ export {
HandlerFunction,
HandlerContextType,
HandlerParameters,
} from '../../utils/context';
} from './container';

View file

@ -89,6 +89,7 @@ const createInternalSetupContractMock = () => {
registerOnPreAuth: jest.fn(),
registerAuth: jest.fn(),
registerOnPostAuth: jest.fn(),
// @ts-expect-error tsc cannot infer ContextName and uses never
registerRouteHandlerContext: jest.fn(),
registerOnPreResponse: jest.fn(),
createRouter: jest.fn().mockImplementation(() => mockRouter.create({})),
@ -125,6 +126,7 @@ const createSetupContractMock = () => {
basePath: internalMock.basePath,
csp: CspConfig.DEFAULT,
createRouter: jest.fn(),
// @ts-expect-error tsc cannot infer ContextName and uses never
registerRouteHandlerContext: jest.fn(),
auth: {
get: internalMock.auth.get,

View file

@ -11,6 +11,7 @@ import { first, map } from 'rxjs/operators';
import { Server } from '@hapi/hapi';
import { pick } from '@kbn/std';
import type { RequestHandlerContext } from 'src/core/server';
import { CoreService } from '../../types';
import { Logger, LoggerFactory } from '../logging';
import { ContextSetup } from '../context';
@ -31,7 +32,6 @@ import {
InternalHttpServiceStart,
} from './types';
import { RequestHandlerContext } from '../../server';
import { registerCoreHandlers } from './lifecycle_handlers';
import {
ExternalUrlConfigType,
@ -100,17 +100,23 @@ export class HttpService
externalUrl: new ExternalUrlConfig(config.externalUrl),
createRouter: (path: string, pluginId: PluginOpaqueId = this.coreContext.coreId) => {
createRouter: <Context extends RequestHandlerContext = RequestHandlerContext>(
path: string,
pluginId: PluginOpaqueId = this.coreContext.coreId
) => {
const enhanceHandler = this.requestHandlerContext!.createHandler.bind(null, pluginId);
const router = new Router(path, this.log, enhanceHandler);
const router = new Router<Context>(path, this.log, enhanceHandler);
registerRouter(router);
return router;
},
registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(
registerRouteHandlerContext: <
Context extends RequestHandlerContext,
ContextName extends keyof Context
>(
pluginOpaqueId: PluginOpaqueId,
contextName: T,
provider: RequestHandlerContextProvider<T>
contextName: ContextName,
provider: RequestHandlerContextProvider<Context, ContextName>
) => this.requestHandlerContext!.registerContext(pluginOpaqueId, contextName, provider),
};

View file

@ -175,19 +175,19 @@ describe('core lifecycle handlers', () => {
});
destructiveMethods.forEach((method) => {
((router as any)[method.toLowerCase()] as RouteRegistrar<any>)<any, any, any>(
((router as any)[method.toLowerCase()] as RouteRegistrar<any, any>)<any, any, any>(
{ path: testPath, validate: false },
(context, req, res) => {
return res.ok({ body: 'ok' });
}
);
((router as any)[method.toLowerCase()] as RouteRegistrar<any>)<any, any, any>(
((router as any)[method.toLowerCase()] as RouteRegistrar<any, any>)<any, any, any>(
{ path: allowlistedTestPath, validate: false },
(context, req, res) => {
return res.ok({ body: 'ok' });
}
);
((router as any)[method.toLowerCase()] as RouteRegistrar<any>)<any, any, any>(
((router as any)[method.toLowerCase()] as RouteRegistrar<any, any>)<any, any, any>(
{ path: xsrfDisabledTestPath, validate: false, options: { xsrfRequired: false } },
(context, req, res) => {
return res.ok({ body: 'ok' });

View file

@ -8,7 +8,7 @@
import { IRouter } from './router';
export type RouterMock = jest.Mocked<IRouter>;
export type RouterMock = jest.Mocked<IRouter<any>>;
function create({ routerPath = '' }: { routerPath?: string } = {}): RouterMock {
return {

View file

@ -44,9 +44,12 @@ interface RouterRoute {
*
* @public
*/
export type RouteRegistrar<Method extends RouteMethod> = <P, Q, B>(
export type RouteRegistrar<
Method extends RouteMethod,
Context extends RequestHandlerContext = RequestHandlerContext
> = <P, Q, B>(
route: RouteConfig<P, Q, B, Method>,
handler: RequestHandler<P, Q, B, Method>
handler: RequestHandler<P, Q, B, Context, Method>
) => void;
/**
@ -55,7 +58,7 @@ export type RouteRegistrar<Method extends RouteMethod> = <P, Q, B>(
*
* @public
*/
export interface IRouter {
export interface IRouter<Context extends RequestHandlerContext = RequestHandlerContext> {
/**
* Resulted path
*/
@ -66,35 +69,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'>;
get: RouteRegistrar<'get', Context>;
/**
* 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'>;
post: RouteRegistrar<'post', Context>;
/**
* 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'>;
put: RouteRegistrar<'put', Context>;
/**
* 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'>;
patch: RouteRegistrar<'patch', Context>;
/**
* 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'>;
delete: RouteRegistrar<'delete', Context>;
/**
* Wrap a router handler to catch and converts legacy boom errors to proper custom errors.
@ -110,9 +113,13 @@ export interface IRouter {
getRoutes: () => RouterRoute[];
}
export type ContextEnhancer<P, Q, B, Method extends RouteMethod> = (
handler: RequestHandler<P, Q, B, Method>
) => RequestHandlerEnhanced<P, Q, B, Method>;
export type ContextEnhancer<
P,
Q,
B,
Method extends RouteMethod,
Context extends RequestHandlerContext
> = (handler: RequestHandler<P, Q, B, Context, Method>) => RequestHandlerEnhanced<P, Q, B, Method>;
function getRouteFullPath(routerPath: string, routePath: string) {
// If router's path ends with slash and route's path starts with slash,
@ -195,22 +202,23 @@ function validOptions(
/**
* @internal
*/
export class Router implements IRouter {
export class Router<Context extends RequestHandlerContext = RequestHandlerContext>
implements IRouter<Context> {
public routes: Array<Readonly<RouterRoute>> = [];
public get: IRouter['get'];
public post: IRouter['post'];
public delete: IRouter['delete'];
public put: IRouter['put'];
public patch: IRouter['patch'];
public get: IRouter<Context>['get'];
public post: IRouter<Context>['post'];
public delete: IRouter<Context>['delete'];
public put: IRouter<Context>['put'];
public patch: IRouter<Context>['patch'];
constructor(
public readonly routerPath: string,
private readonly log: Logger,
private readonly enhanceWithContext: ContextEnhancer<any, any, any, any>
private readonly enhanceWithContext: ContextEnhancer<any, any, any, any, any>
) {
const buildMethod = <Method extends RouteMethod>(method: Method) => <P, Q, B>(
route: RouteConfig<P, Q, B, Method>,
handler: RequestHandler<P, Q, B, Method>
handler: RequestHandler<P, Q, B, Context, Method>
) => {
const routeSchemas = routeSchemasFromRouteConfig(route, method);
@ -300,7 +308,7 @@ type WithoutHeadArgument<T> = T extends (first: any, ...rest: infer Params) => i
: never;
type RequestHandlerEnhanced<P, Q, B, Method extends RouteMethod> = WithoutHeadArgument<
RequestHandler<P, Q, B, Method>
RequestHandler<P, Q, B, RequestHandlerContext, Method>
>;
/**
@ -341,10 +349,11 @@ export type RequestHandler<
P = unknown,
Q = unknown,
B = unknown,
Context extends RequestHandlerContext = RequestHandlerContext,
Method extends RouteMethod = any,
ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory
> = (
context: RequestHandlerContext,
context: Context,
request: KibanaRequest<P, Q, B, Method>,
response: ResponseFactory
) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
@ -366,8 +375,9 @@ export type RequestHandlerWrapper = <
P,
Q,
B,
Context extends RequestHandlerContext = RequestHandlerContext,
Method extends RouteMethod = any,
ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory
>(
handler: RequestHandler<P, Q, B, Method, ResponseFactory>
) => RequestHandler<P, Q, B, Method, ResponseFactory>;
handler: RequestHandler<P, Q, B, Context, Method, ResponseFactory>
) => RequestHandler<P, Q, B, Context, Method, ResponseFactory>;

View file

@ -21,13 +21,13 @@ import { OnPostAuthHandler } from './lifecycle/on_post_auth';
import { OnPreResponseHandler } from './lifecycle/on_pre_response';
import { IBasePath } from './base_path_service';
import { ExternalUrlConfig } from '../external_url';
import { PluginOpaqueId, RequestHandlerContext } from '..';
import type { PluginOpaqueId, RequestHandlerContext } from '..';
/**
* An object that handles registration of http request context providers.
* @public
*/
export type RequestHandlerContextContainer = IContextContainer<RequestHandler<any, any, any>>;
export type RequestHandlerContextContainer = IContextContainer<RequestHandler>;
/**
* Context provider for request handler.
@ -36,8 +36,9 @@ export type RequestHandlerContextContainer = IContextContainer<RequestHandler<an
* @public
*/
export type RequestHandlerContextProvider<
TContextName extends keyof RequestHandlerContext
> = IContextProvider<RequestHandler<any, any, any>, TContextName>;
Context extends RequestHandlerContext,
ContextName extends keyof Context
> = IContextProvider<Context, ContextName>;
/**
* @public
@ -230,14 +231,19 @@ export interface HttpServiceSetup {
* ```
* @public
*/
createRouter: () => IRouter;
createRouter: <
Context extends RequestHandlerContext = RequestHandlerContext
>() => IRouter<Context>;
/**
* Register a context provider for a route handler.
* @example
* ```ts
* // my-plugin.ts
* deps.http.registerRouteHandlerContext(
* interface MyRequestHandlerContext extends RequestHandlerContext {
* myApp: { search(id: string): Promise<Result> };
* }
* deps.http.registerRouteHandlerContext<MyRequestHandlerContext, 'myApp'>(
* 'myApp',
* (context, req) => {
* async function search (id: string) {
@ -248,6 +254,8 @@ export interface HttpServiceSetup {
* );
*
* // my-route-handler.ts
* import type { MyRequestHandlerContext } from './my-plugin.ts';
* const router = createRouter<MyRequestHandlerContext>();
* router.get({ path: '/', validate: false }, async (context, req, res) => {
* const response = await context.myApp.search(...);
* return res.ok(response);
@ -255,9 +263,12 @@ export interface HttpServiceSetup {
* ```
* @public
*/
registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(
contextName: T,
provider: RequestHandlerContextProvider<T>
registerRouteHandlerContext: <
Context extends RequestHandlerContext,
ContextName extends keyof Context
>(
contextName: ContextName,
provider: RequestHandlerContextProvider<Context, ContextName>
) => RequestHandlerContextContainer;
/**
@ -272,13 +283,19 @@ export interface InternalHttpServiceSetup
auth: HttpServerSetup['auth'];
server: HttpServerSetup['server'];
externalUrl: ExternalUrlConfig;
createRouter: (path: string, plugin?: PluginOpaqueId) => IRouter;
createRouter: <Context extends RequestHandlerContext = RequestHandlerContext>(
path: string,
plugin?: PluginOpaqueId
) => IRouter<Context>;
registerStaticDir: (path: string, dirPath: string) => void;
getAuthHeaders: GetAuthHeaders;
registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(
registerRouteHandlerContext: <
Context extends RequestHandlerContext,
ContextName extends keyof Context
>(
pluginOpaqueId: PluginOpaqueId,
contextName: T,
provider: RequestHandlerContextProvider<T>
contextName: ContextName,
provider: RequestHandlerContextProvider<Context, ContextName>
) => RequestHandlerContextContainer;
}

View file

@ -53,12 +53,12 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
private createRegistrar(deps: SetupDeps, router: IRouter): HttpResources {
return {
register: <P, Q, B>(
register: <P, Q, B, Context extends RequestHandlerContext = RequestHandlerContext>(
route: RouteConfig<P, Q, B, 'get'>,
handler: HttpResourcesRequestHandler<P, Q, B>
handler: HttpResourcesRequestHandler<P, Q, B, Context>
) => {
return router.get<P, Q, B>(route, (context, request, response) => {
return handler(context, request, {
return handler(context as Context, request, {
...response,
...this.createResponseToolkit(deps, context, request, response),
});

View file

@ -6,7 +6,8 @@
* Public License, v 1.
*/
import {
import type { RequestHandlerContext } from 'src/core/server';
import type {
IRouter,
RouteConfig,
IKibanaResponse,
@ -72,13 +73,12 @@ export interface HttpResourcesServiceToolkit {
* });
* @public
*/
export type HttpResourcesRequestHandler<P = unknown, Q = unknown, B = unknown> = RequestHandler<
P,
Q,
B,
'get',
KibanaResponseFactory & HttpResourcesServiceToolkit
>;
export type HttpResourcesRequestHandler<
P = unknown,
Q = unknown,
B = unknown,
Context extends RequestHandlerContext = RequestHandlerContext
> = RequestHandler<P, Q, B, Context, 'get', KibanaResponseFactory & HttpResourcesServiceToolkit>;
/**
* Allows to configure HTTP response parameters
@ -98,8 +98,8 @@ export interface InternalHttpResourcesSetup {
*/
export interface HttpResources {
/** To register a route handler executing passed function to form response. */
register: <P, Q, B>(
register: <P, Q, B, Context extends RequestHandlerContext = RequestHandlerContext>(
route: RouteConfig<P, Q, B, 'get'>,
handler: HttpResourcesRequestHandler<P, Q, B>
handler: HttpResourcesRequestHandler<P, Q, B, Context>
) => void;
}

View file

@ -11,6 +11,7 @@ import { first, map, publishReplay, tap } from 'rxjs/operators';
import type { PublicMethodsOf } from '@kbn/utility-types';
import { PathConfigType } from '@kbn/utils';
import type { RequestHandlerContext } from 'src/core/server';
// @ts-expect-error legacy config class
import { Config as LegacyConfigClass } from '../../../legacy/server/config';
import { CoreService } from '../../types';
@ -18,7 +19,14 @@ import { Config } from '../config';
import { CoreContext } from '../core_context';
import { CspConfigType, config as cspConfig } from '../csp';
import { DevConfig, DevConfigType, config as devConfig } from '../dev';
import { BasePathProxyServer, HttpConfig, HttpConfigType, config as httpConfig } from '../http';
import {
BasePathProxyServer,
HttpConfig,
HttpConfigType,
config as httpConfig,
IRouter,
RequestHandlerContextProvider,
} from '../http';
import { Logger } from '../logging';
import { LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyConfig, LegacyVars } from './types';
import { ExternalUrlConfigType, config as externalUrlConfig } from '../external_url';
@ -225,11 +233,15 @@ export class LegacyService implements CoreService {
},
http: {
createCookieSessionStorageFactory: setupDeps.core.http.createCookieSessionStorageFactory,
registerRouteHandlerContext: setupDeps.core.http.registerRouteHandlerContext.bind(
null,
this.legacyId
),
createRouter: () => router,
registerRouteHandlerContext: <
Context extends RequestHandlerContext,
ContextName extends keyof Context
>(
contextName: ContextName,
provider: RequestHandlerContextProvider<Context, ContextName>
) => setupDeps.core.http.registerRouteHandlerContext(this.legacyId, contextName, provider),
createRouter: <Context extends RequestHandlerContext = RequestHandlerContext>() =>
router as IRouter<Context>,
resources: setupDeps.core.httpResources.createRegistrar(router),
registerOnPreRouting: setupDeps.core.http.registerOnPreRouting,
registerOnPreAuth: setupDeps.core.http.registerOnPreAuth,

View file

@ -10,6 +10,7 @@ import { map, shareReplay } from 'rxjs/operators';
import { combineLatest } from 'rxjs';
import { PathConfigType, config as pathConfig } from '@kbn/utils';
import { pick, deepFreeze } from '@kbn/std';
import type { RequestHandlerContext } from 'src/core/server';
import { CoreContext } from '../core_context';
import { PluginWrapper } from './plugin';
import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service';
@ -24,6 +25,7 @@ import {
ElasticsearchConfigType,
config as elasticsearchConfig,
} from '../elasticsearch/elasticsearch_config';
import { IRouter, RequestHandlerContextProvider } from '../http';
import { SavedObjectsConfigType, savedObjectsConfig } from '../saved_objects/saved_objects_config';
import { CoreSetup, CoreStart } from '..';
@ -149,11 +151,15 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
},
http: {
createCookieSessionStorageFactory: deps.http.createCookieSessionStorageFactory,
registerRouteHandlerContext: deps.http.registerRouteHandlerContext.bind(
null,
plugin.opaqueId
),
createRouter: () => router,
registerRouteHandlerContext: <
Context extends RequestHandlerContext,
ContextName extends keyof Context
>(
contextName: ContextName,
provider: RequestHandlerContextProvider<Context, ContextName>
) => deps.http.registerRouteHandlerContext(plugin.opaqueId, contextName, provider),
createRouter: <Context extends RequestHandlerContext = RequestHandlerContext>() =>
router as IRouter<Context>,
resources: deps.httpResources.createRegistrar(router),
registerOnPreRouting: deps.http.registerOnPreRouting,
registerOnPreAuth: deps.http.registerOnPreAuth,

View file

@ -132,6 +132,7 @@ import { ReindexParams } from 'elasticsearch';
import { ReindexRethrottleParams } from 'elasticsearch';
import { RenderSearchTemplateParams } from 'elasticsearch';
import { Request } from '@hapi/hapi';
import { RequestHandlerContext as RequestHandlerContext_2 } from 'src/core/server';
import { ResponseObject } from '@hapi/hapi';
import { ResponseToolkit } from '@hapi/hapi';
import { SchemaTypeError } from '@kbn/config-schema';
@ -968,7 +969,7 @@ export interface HttpAuth {
// @public
export interface HttpResources {
register: <P, Q, B>(route: RouteConfig<P, Q, B, 'get'>, handler: HttpResourcesRequestHandler<P, Q, B>) => void;
register: <P, Q, B, Context extends RequestHandlerContext_2 = RequestHandlerContext_2>(route: RouteConfig<P, Q, B, 'get'>, handler: HttpResourcesRequestHandler<P, Q, B, Context>) => void;
}
// @public
@ -977,7 +978,7 @@ export interface HttpResourcesRenderOptions {
}
// @public
export type HttpResourcesRequestHandler<P = unknown, Q = unknown, B = unknown> = RequestHandler<P, Q, B, 'get', KibanaResponseFactory & HttpResourcesServiceToolkit>;
export type HttpResourcesRequestHandler<P = unknown, Q = unknown, B = unknown, Context extends RequestHandlerContext_2 = RequestHandlerContext_2> = RequestHandler<P, Q, B, Context, 'get', KibanaResponseFactory & HttpResourcesServiceToolkit>;
// @public
export type HttpResourcesResponseOptions = HttpResponseOptions;
@ -1013,7 +1014,7 @@ export interface HttpServiceSetup {
auth: HttpAuth;
basePath: IBasePath;
createCookieSessionStorageFactory: <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>>;
createRouter: () => IRouter;
createRouter: <Context extends RequestHandlerContext = RequestHandlerContext>() => IRouter<Context>;
csp: ICspConfig;
getServerInfo: () => HttpServerInfo;
registerAuth: (handler: AuthenticationHandler) => void;
@ -1021,7 +1022,7 @@ export interface HttpServiceSetup {
registerOnPreAuth: (handler: OnPreAuthHandler) => void;
registerOnPreResponse: (handler: OnPreResponseHandler) => void;
registerOnPreRouting: (handler: OnPreRoutingHandler) => void;
registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer;
registerRouteHandlerContext: <Context extends RequestHandlerContext, ContextName extends keyof Context>(contextName: ContextName, provider: RequestHandlerContextProvider<Context, ContextName>) => RequestHandlerContextContainer;
}
// @public (undocumented)
@ -1047,15 +1048,13 @@ export interface IClusterClient {
}
// @public
export interface IContextContainer<THandler extends HandlerFunction<any>> {
export interface IContextContainer<THandler extends RequestHandler> {
createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters<THandler>) => ShallowPromise<ReturnType<THandler>>;
registerContext<TContextName extends keyof HandlerContextType<THandler>>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider<THandler, TContextName>): this;
registerContext<Context extends RequestHandlerContext, ContextName extends keyof Context>(pluginOpaqueId: PluginOpaqueId, contextName: ContextName, provider: IContextProvider<Context, ContextName>): this;
}
// Warning: (ae-forgotten-export) The symbol "PartialExceptFor" needs to be exported by the entry point index.d.ts
//
// @public
export type IContextProvider<THandler extends HandlerFunction<any>, TContextName extends keyof HandlerContextType<THandler>> = (context: PartialExceptFor<HandlerContextType<THandler>, 'core'>, ...rest: HandlerParameters<THandler>) => Promise<HandlerContextType<THandler>[TContextName]> | HandlerContextType<THandler>[TContextName];
export type IContextProvider<Context extends RequestHandlerContext, ContextName extends keyof Context> = (context: Omit<Context, ContextName>, ...rest: HandlerParameters<RequestHandler>) => Promise<Context[ContextName]> | Context[ContextName];
// @public
export interface ICspConfig {
@ -1140,17 +1139,17 @@ export interface IRenderOptions {
}
// @public
export interface IRouter {
delete: RouteRegistrar<'delete'>;
get: RouteRegistrar<'get'>;
export interface IRouter<Context extends RequestHandlerContext = RequestHandlerContext> {
delete: RouteRegistrar<'delete', Context>;
get: RouteRegistrar<'get', Context>;
// Warning: (ae-forgotten-export) The symbol "RouterRoute" needs to be exported by the entry point index.d.ts
//
// @internal
getRoutes: () => RouterRoute[];
handleLegacyErrors: RequestHandlerWrapper;
patch: RouteRegistrar<'patch'>;
post: RouteRegistrar<'post'>;
put: RouteRegistrar<'put'>;
patch: RouteRegistrar<'patch', Context>;
post: RouteRegistrar<'post', Context>;
put: RouteRegistrar<'put', Context>;
routerPath: string;
}
@ -1549,7 +1548,7 @@ export type LegacyElasticsearchClientConfig = Pick<ConfigOptions, 'keepAlive' |
// @public
export interface LegacyElasticsearchError extends Boom.Boom {
// (undocumented)
[code]?: string;
[code_2]?: string;
}
// @public
@ -1891,7 +1890,7 @@ export type RedirectResponseOptions = HttpResponseOptions & {
};
// @public
export type RequestHandler<P = unknown, Q = unknown, B = unknown, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory> = (context: RequestHandlerContext, request: KibanaRequest<P, Q, B, Method>, response: ResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
export type RequestHandler<P = unknown, Q = unknown, B = unknown, Context extends RequestHandlerContext = RequestHandlerContext, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory> = (context: Context, request: KibanaRequest<P, Q, B, Method>, response: ResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
// @public
export interface RequestHandlerContext {
@ -1916,13 +1915,13 @@ export interface RequestHandlerContext {
}
// @public
export type RequestHandlerContextContainer = IContextContainer<RequestHandler<any, any, any>>;
export type RequestHandlerContextContainer = IContextContainer<RequestHandler>;
// @public
export type RequestHandlerContextProvider<TContextName extends keyof RequestHandlerContext> = IContextProvider<RequestHandler<any, any, any>, TContextName>;
export type RequestHandlerContextProvider<Context extends RequestHandlerContext, ContextName extends keyof Context> = IContextProvider<Context, ContextName>;
// @public
export type RequestHandlerWrapper = <P, Q, B, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory>(handler: RequestHandler<P, Q, B, Method, ResponseFactory>) => RequestHandler<P, Q, B, Method, ResponseFactory>;
export type RequestHandlerWrapper = <P, Q, B, Context extends RequestHandlerContext = RequestHandlerContext, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory>(handler: RequestHandler<P, Q, B, Context, Method, ResponseFactory>) => RequestHandler<P, Q, B, Context, Method, ResponseFactory>;
// @public
export interface ResolveCapabilitiesOptions {
@ -1975,7 +1974,7 @@ export type RouteContentType = 'application/json' | 'application/*+json' | 'appl
export type RouteMethod = SafeRouteMethod | DestructiveRouteMethod;
// @public
export type RouteRegistrar<Method extends RouteMethod> = <P, Q, B>(route: RouteConfig<P, Q, B, Method>, handler: RequestHandler<P, Q, B, Method>) => void;
export type RouteRegistrar<Method extends RouteMethod, Context extends RequestHandlerContext = RequestHandlerContext> = <P, Q, B>(route: RouteConfig<P, Q, B, Method>, handler: RequestHandler<P, Q, B, Context, Method>) => void;
// @public
export class RouteValidationError extends SchemaTypeError {

View file

@ -294,7 +294,7 @@ export class Server {
coreSetup.http.registerRouteHandlerContext(
coreId,
'core',
async (context, req, res): Promise<RequestHandlerContext['core']> => {
(context, req, res): RequestHandlerContext['core'] => {
return new CoreRouteHandlerContext(this.coreStart!, req);
}
);

View file

@ -1,268 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { ContextContainer } from './context';
import { PluginOpaqueId } from '../server';
const pluginA = Symbol('pluginA');
const pluginB = Symbol('pluginB');
const pluginC = Symbol('pluginC');
const pluginD = Symbol('pluginD');
const plugins: ReadonlyMap<PluginOpaqueId, PluginOpaqueId[]> = new Map([
[pluginA, []],
[pluginB, [pluginA]],
[pluginC, [pluginA, pluginB]],
[pluginD, []],
]);
interface MyContext {
core1: string;
core2: number;
ctxFromA: string;
ctxFromB: number;
ctxFromC: boolean;
ctxFromD: object;
}
const coreId = Symbol();
describe('ContextContainer', () => {
it('does not allow the same context to be registered twice', () => {
const contextContainer = new ContextContainer<(context: MyContext) => string>(plugins, coreId);
contextContainer.registerContext(coreId, 'ctxFromA', () => 'aString');
expect(() =>
contextContainer.registerContext(coreId, 'ctxFromA', () => 'aString')
).toThrowErrorMatchingInlineSnapshot(
`"Context provider for ctxFromA has already been registered."`
);
});
describe('registerContext', () => {
it('throws error if called with an unknown symbol', async () => {
const contextContainer = new ContextContainer<(context: MyContext) => string>(
plugins,
coreId
);
await expect(() =>
contextContainer.registerContext(Symbol('unknown'), 'ctxFromA', jest.fn())
).toThrowErrorMatchingInlineSnapshot(
`"Cannot register context for unknown plugin: Symbol(unknown)"`
);
});
});
describe('context building', () => {
it('resolves dependencies', async () => {
const contextContainer = new ContextContainer<(context: MyContext) => string>(
plugins,
coreId
);
expect.assertions(8);
contextContainer.registerContext(coreId, 'core1', (context) => {
expect(context).toEqual({});
return 'core';
});
contextContainer.registerContext(pluginA, 'ctxFromA', (context) => {
expect(context).toEqual({ core1: 'core' });
return 'aString';
});
contextContainer.registerContext(pluginB, 'ctxFromB', (context) => {
expect(context).toEqual({ core1: 'core', ctxFromA: 'aString' });
return 299;
});
contextContainer.registerContext(pluginC, 'ctxFromC', (context) => {
expect(context).toEqual({ core1: 'core', ctxFromA: 'aString', ctxFromB: 299 });
return false;
});
contextContainer.registerContext(pluginD, 'ctxFromD', (context) => {
expect(context).toEqual({ core1: 'core' });
return {};
});
const rawHandler1 = jest.fn<string, []>(() => 'handler1');
const handler1 = contextContainer.createHandler(pluginC, rawHandler1);
const rawHandler2 = jest.fn<string, []>(() => 'handler2');
const handler2 = contextContainer.createHandler(pluginD, rawHandler2);
await handler1();
await handler2();
// Should have context from pluginC, its deps, and core
expect(rawHandler1).toHaveBeenCalledWith({
core1: 'core',
ctxFromA: 'aString',
ctxFromB: 299,
ctxFromC: false,
});
// Should have context from pluginD, and core
expect(rawHandler2).toHaveBeenCalledWith({
core1: 'core',
ctxFromD: {},
});
});
it('exposes all core context to all providers regardless of registration order', async () => {
expect.assertions(4);
const contextContainer = new ContextContainer<(context: MyContext) => string>(
plugins,
coreId
);
contextContainer
.registerContext(pluginA, 'ctxFromA', (context) => {
expect(context).toEqual({ core1: 'core', core2: 101 });
return `aString ${context.core1} ${context.core2}`;
})
.registerContext(coreId, 'core1', () => 'core')
.registerContext(coreId, 'core2', () => 101)
.registerContext(pluginB, 'ctxFromB', (context) => {
expect(context).toEqual({ core1: 'core', core2: 101, ctxFromA: 'aString core 101' });
return 277;
});
const rawHandler1 = jest.fn<string, []>(() => 'handler1');
const handler1 = contextContainer.createHandler(pluginB, rawHandler1);
expect(await handler1()).toEqual('handler1');
expect(rawHandler1).toHaveBeenCalledWith({
core1: 'core',
core2: 101,
ctxFromA: 'aString core 101',
ctxFromB: 277,
});
});
it('exposes all core context to core providers', async () => {
expect.assertions(4);
const contextContainer = new ContextContainer<(context: MyContext) => string>(
plugins,
coreId
);
contextContainer
.registerContext(coreId, 'core1', (context) => {
expect(context).toEqual({});
return 'core';
})
.registerContext(coreId, 'core2', (context) => {
expect(context).toEqual({ core1: 'core' });
return 101;
});
const rawHandler1 = jest.fn<string, []>(() => 'handler1');
const handler1 = contextContainer.createHandler(pluginA, rawHandler1);
expect(await handler1()).toEqual('handler1');
// If no context is registered for pluginA, only core contexts should be exposed
expect(rawHandler1).toHaveBeenCalledWith({
core1: 'core',
core2: 101,
});
});
it('does not expose plugin contexts to core handler', async () => {
const contextContainer = new ContextContainer<(context: MyContext) => string>(
plugins,
coreId
);
contextContainer
.registerContext(coreId, 'core1', (context) => 'core')
.registerContext(pluginA, 'ctxFromA', (context) => 'aString');
const rawHandler1 = jest.fn<string, []>(() => 'handler1');
const handler1 = contextContainer.createHandler(coreId, rawHandler1);
expect(await handler1()).toEqual('handler1');
// pluginA context should not be present in a core handler
expect(rawHandler1).toHaveBeenCalledWith({
core1: 'core',
});
});
it('passes additional arguments to providers', async () => {
expect.assertions(6);
const contextContainer = new ContextContainer<
(context: MyContext, arg1: string, arg2: number) => string
>(plugins, coreId);
contextContainer.registerContext(coreId, 'core1', (context, str, num) => {
expect(str).toEqual('passed string');
expect(num).toEqual(77);
return `core ${str}`;
});
contextContainer.registerContext(pluginD, 'ctxFromD', (context, str, num) => {
expect(str).toEqual('passed string');
expect(num).toEqual(77);
return {
num: 77,
};
});
const rawHandler1 = jest.fn<string, [MyContext, string, number]>(() => 'handler1');
const handler1 = contextContainer.createHandler(pluginD, rawHandler1);
expect(await handler1('passed string', 77)).toEqual('handler1');
expect(rawHandler1).toHaveBeenCalledWith(
{
core1: 'core passed string',
ctxFromD: {
num: 77,
},
},
'passed string',
77
);
});
});
describe('createHandler', () => {
it('throws error if called with an unknown symbol', async () => {
const contextContainer = new ContextContainer<(context: MyContext) => string>(
plugins,
coreId
);
await expect(() =>
contextContainer.createHandler(Symbol('unknown'), jest.fn())
).toThrowErrorMatchingInlineSnapshot(
`"Cannot create handler for unknown plugin: Symbol(unknown)"`
);
});
it('returns value from original handler', async () => {
const contextContainer = new ContextContainer<(context: MyContext) => string>(
plugins,
coreId
);
const rawHandler1 = jest.fn(() => 'handler1');
const handler1 = contextContainer.createHandler(pluginA, rawHandler1);
expect(await handler1()).toEqual('handler1');
});
it('passes additional arguments to handlers', async () => {
const contextContainer = new ContextContainer<
(context: MyContext, arg1: string, arg2: number) => string
>(plugins, coreId);
const rawHandler1 = jest.fn<string, [MyContext, string, number]>(() => 'handler1');
const handler1 = contextContainer.createHandler(pluginA, rawHandler1);
await handler1('passed string', 77);
expect(rawHandler1).toHaveBeenCalledWith({}, 'passed string', 77);
});
});
});

View file

@ -6,12 +6,4 @@
* Public License, v 1.
*/
export {
ContextContainer,
HandlerContextType,
HandlerFunction,
HandlerParameters,
IContextContainer,
IContextProvider,
} from './context';
export { DEFAULT_APP_CATEGORIES } from './default_app_categories';

View file

@ -6,7 +6,7 @@
* Public License, v 1.
*/
import {
import type {
CoreStart,
PluginInitializerContext,
CoreSetup,
@ -15,6 +15,7 @@ import {
KibanaRequest,
RouteMethod,
RequestHandler,
RequestHandlerContext,
} from 'src/core/server';
import { schema } from '@kbn/config-schema';
import { Subject } from 'rxjs';
@ -77,9 +78,16 @@ export interface BfetchServerSetup {
*
* @param streamHandler
*/
createStreamingRequestHandler: <Response, P, Q, B, Method extends RouteMethod = any>(
createStreamingRequestHandler: <
Response,
P,
Q,
B,
Context extends RequestHandlerContext = RequestHandlerContext,
Method extends RouteMethod = any
>(
streamHandler: StreamingRequestHandler<Response, P, Q, B, Method>
) => RequestHandler<P, Q, B, Method>;
) => RequestHandler<P, Q, B, Context, Method>;
}
// eslint-disable-next-line

View file

@ -2388,7 +2388,7 @@ export class SearchSource {
removeField<K extends keyof SearchSourceFields>(field: K): this;
serialize(): {
searchSourceJSON: string;
references: import("src/core/server").SavedObjectReference[];
references: import("../../../../../core/types").SavedObjectReference[];
};
setField<K extends keyof SearchSourceFields>(field: K, value: SearchSourceFields[K]): this;
setFields(newFields: SearchSourceFields): this;

View file

@ -235,6 +235,8 @@ export {
SearchUsage,
SessionService,
ISessionService,
DataApiRequestHandlerContext,
DataRequestHandlerContext,
} from './search';
// Search namespace

View file

@ -6,7 +6,7 @@
* Public License, v 1.
*/
import { RequestHandler, RouteMethod } from 'src/core/server';
import type { RequestHandler, RouteMethod, RequestHandlerContext } from 'src/core/server';
import { ErrorIndexPatternNotFound } from '../../error';
interface ErrorResponseBody {
@ -29,9 +29,15 @@ interface ErrorWithData {
* }
* ```
*/
export const handleErrors = <P, Q, B, Method extends RouteMethod>(
handler: RequestHandler<P, Q, B, Method>
): RequestHandler<P, Q, B, Method> => async (context, request, response) => {
export const handleErrors = <
P,
Q,
B,
Context extends RequestHandlerContext,
Method extends RouteMethod
>(
handler: RequestHandler<P, Q, B, Context, Method>
): RequestHandler<P, Q, B, Context, Method> => async (context, request, response) => {
try {
return await handler(context, request, response);
} catch (error) {

View file

@ -8,12 +8,11 @@
import { schema } from '@kbn/config-schema';
import { IRouter } from 'src/core/server';
import { SearchRouteDependencies } from '../search_service';
import { getCallMsearch } from './call_msearch';
import { reportServerError } from '../../../../kibana_utils/server';
import type { DataPluginRouter } from '../types';
/**
* The msearch route takes in an array of searches, each consisting of header
* and body json, and reformts them into a single request for the _msearch API.
@ -27,7 +26,10 @@ import { reportServerError } from '../../../../kibana_utils/server';
*
* @deprecated
*/
export function registerMsearchRoute(router: IRouter, deps: SearchRouteDependencies): void {
export function registerMsearchRoute(
router: DataPluginRouter,
deps: SearchRouteDependencies
): void {
router.post(
{
path: '/internal/_msearch',

View file

@ -8,12 +8,12 @@
import { first } from 'rxjs/operators';
import { schema } from '@kbn/config-schema';
import type { IRouter } from 'src/core/server';
import { getRequestAbortedSignal } from '../../lib';
import { shimHitsTotal } from './shim_hits_total';
import { reportServerError } from '../../../../kibana_utils/server';
import type { DataPluginRouter } from '../types';
export function registerSearchRoute(router: IRouter): void {
export function registerSearchRoute(router: DataPluginRouter): void {
router.post(
{
path: '/internal/search/{strategy}/{id?}',

View file

@ -21,12 +21,13 @@ import {
import { catchError, first, map } from 'rxjs/operators';
import { BfetchServerSetup } from 'src/plugins/bfetch/server';
import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
import {
import type {
ISearchSetup,
ISearchStart,
ISearchStrategy,
SearchEnhancements,
SearchStrategyDependencies,
DataRequestHandlerContext,
} from './types';
import { AggsService } from './aggs';
@ -64,12 +65,6 @@ import { ConfigSchema } from '../../config';
import { SessionService, IScopedSessionService, ISessionService } from './session';
import { KbnServerError } from '../../../kibana_utils/server';
declare module 'src/core/server' {
interface RequestHandlerContext {
search?: ISearchClient & { session: IScopedSessionService };
}
}
type StrategyMap = Record<string, ISearchStrategy<any, any>>;
/** @internal */
@ -112,7 +107,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
): ISearchSetup {
const usage = usageCollection ? usageProvider(core) : undefined;
const router = core.http.createRouter();
const router = core.http.createRouter<DataRequestHandlerContext>();
const routeDependencies = {
getStartServices: core.getStartServices,
globalConfig$: this.initializerContext.config.legacy.globalConfig$,
@ -124,11 +119,14 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
this.coreStart = coreStart;
});
core.http.registerRouteHandlerContext('search', async (context, request) => {
const search = this.asScopedProvider(this.coreStart!)(request);
const session = this.sessionService.asScopedProvider(this.coreStart!)(request);
return { ...search, session };
});
core.http.registerRouteHandlerContext<DataRequestHandlerContext, 'search'>(
'search',
async (context, request) => {
const search = this.asScopedProvider(this.coreStart!)(request);
const session = this.sessionService.asScopedProvider(this.coreStart!)(request);
return { ...search, session };
}
);
this.registerSearchStrategy(
ES_SEARCH_STRATEGY,

View file

@ -7,11 +7,13 @@
*/
import { Observable } from 'rxjs';
import {
import type {
IRouter,
IScopedClusterClient,
IUiSettingsClient,
SavedObjectsClientContract,
KibanaRequest,
RequestHandlerContext,
} from 'src/core/server';
import {
ISearchOptions,
@ -23,7 +25,7 @@ import {
import { AggsSetup, AggsStart } from './aggs';
import { SearchUsage } from './collectors';
import { IEsSearchRequest, IEsSearchResponse } from './es_search';
import { ISessionService } from './session';
import { ISessionService, IScopedSessionService } from './session';
export interface SearchEnhancements {
defaultStrategy: string;
@ -101,3 +103,16 @@ export interface ISearchStart<
asScoped: (request: KibanaRequest) => Promise<ISearchStartSearchSource>;
};
}
export interface DataApiRequestHandlerContext extends ISearchClient {
session: IScopedSessionService;
}
/**
* @internal
*/
export interface DataRequestHandlerContext extends RequestHandlerContext {
search: DataApiRequestHandlerContext;
}
export type DataPluginRouter = IRouter<DataRequestHandlerContext>;

View file

@ -53,6 +53,7 @@ import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core
import { PublicMethodsOf } from '@kbn/utility-types';
import { RecursiveReadonly } from '@kbn/utility-types';
import { RequestAdapter } from 'src/plugins/inspector/common';
import { RequestHandlerContext } from 'src/core/server';
import { RequestStatistics } from 'src/plugins/inspector/common';
import { SavedObject } from 'src/core/server';
import { SavedObjectsClientContract } from 'src/core/server';
@ -304,6 +305,23 @@ export const castEsToKbnFieldTypeName: (esType: ES_FIELD_TYPES | string) => KBN_
// @public (undocumented)
export const config: PluginConfigDescriptor<ConfigSchema>;
// Warning: (ae-forgotten-export) The symbol "ISearchClient" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "DataApiRequestHandlerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface DataApiRequestHandlerContext extends ISearchClient {
// Warning: (ae-forgotten-export) The symbol "IScopedSessionService" needs to be exported by the entry point index.d.ts
//
// (undocumented)
session: IScopedSessionService;
}
// @internal (undocumented)
export interface DataRequestHandlerContext extends RequestHandlerContext {
// (undocumented)
search: DataApiRequestHandlerContext;
}
// @public (undocumented)
export enum ES_FIELD_TYPES {
// (undocumented)
@ -937,8 +955,6 @@ export interface ISearchStart<SearchStrategyRequest extends IKibanaSearchRequest
//
// (undocumented)
aggs: AggsStart;
// Warning: (ae-forgotten-export) The symbol "ISearchClient" needs to be exported by the entry point index.d.ts
//
// (undocumented)
asScoped: (request: KibanaRequest_2) => ISearchClient;
getSearchStrategy: (name?: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse>;
@ -964,8 +980,6 @@ export interface ISearchStrategy<SearchStrategyRequest extends IKibanaSearchRequ
//
// @public (undocumented)
export interface ISessionService {
// Warning: (ae-forgotten-export) The symbol "IScopedSessionService" needs to be exported by the entry point index.d.ts
//
// (undocumented)
asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService;
}
@ -1129,10 +1143,10 @@ export class Plugin implements Plugin_2<PluginSetup, PluginStart, DataPluginSetu
// (undocumented)
start(core: CoreStart_2): {
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("src/core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise<import("../public").IndexPatternsService>;
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise<import("../public").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
};
@ -1419,23 +1433,23 @@ export function usageProvider(core: CoreSetup_2): SearchUsage;
// src/plugins/data/server/index.ts:100:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:126:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:126:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:243:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:244:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:253:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:254:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:255:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:259:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:260:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:264:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:267:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:268:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:245:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:246:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:255:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:256:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:257:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:261:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:269:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:270:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index_patterns/index_patterns_service.ts:59:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/plugin.ts:79:74 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/search/types.ts:101:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/search/types.ts:103:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)

View file

@ -36,7 +36,7 @@ describe('sendTelemetryOptInStatus', () => {
expect(fetch).toBeCalledTimes(1);
expect(fetch).toBeCalledWith(mockConfig.optInStatusUrl, {
method: 'post',
body: mockOptInStatus,
body: '["mock_opt_in_hashed_value"]',
headers: { 'X-Elastic-Stack-Version': mockConfig.currentKibanaVersion },
});
});

View file

@ -6,7 +6,6 @@
* Public License, v 1.
*/
// @ts-ignore
import fetch from 'node-fetch';
import { IRouter } from 'kibana/server';
@ -35,7 +34,7 @@ export async function sendTelemetryOptInStatus(
await fetch(optInStatusUrl, {
method: 'post',
body: optInStatus,
body: JSON.stringify(optInStatus),
headers: { 'X-Elastic-Stack-Version': currentKibanaVersion },
});
}

View file

@ -11,8 +11,12 @@ import { first } from 'rxjs/operators';
import { TypeOf, schema } from '@kbn/config-schema';
import { RecursiveReadonly } from '@kbn/utility-types';
import { deepFreeze } from '@kbn/std';
import type { RequestHandlerContext } from 'src/core/server';
import { PluginStart } from '../../../../src/plugins/data/server';
import type {
PluginStart,
DataApiRequestHandlerContext,
} from '../../../../src/plugins/data/server';
import { CoreSetup, PluginInitializerContext } from '../../../../src/core/server';
import { configSchema } from '../config';
import loadFunctions from './lib/load_functions';
@ -67,7 +71,9 @@ export class Plugin {
const logger = this.initializerContext.logger.get('timelion');
const router = core.http.createRouter();
const router = core.http.createRouter<
RequestHandlerContext & { search: DataApiRequestHandlerContext }
>();
const deps = {
configManager,
@ -79,7 +85,7 @@ export class Plugin {
functionsRoute(router, deps);
runRoute(router, deps);
validateEsRoute(router, core);
validateEsRoute(router);
core.uiSettings.register({
'timelion:es.timefield': {

View file

@ -7,9 +7,12 @@
*/
import _ from 'lodash';
import { IRouter, CoreSetup } from 'kibana/server';
import { IRouter, RequestHandlerContext } from 'kibana/server';
import type { DataApiRequestHandlerContext } from '../../../data/server';
export function validateEsRoute(router: IRouter, core: CoreSetup) {
export function validateEsRoute(
router: IRouter<RequestHandlerContext & { search: DataApiRequestHandlerContext }>
) {
router.get(
{
path: '/api/timelion/validate/es',

View file

@ -8,14 +8,15 @@
import { uniqBy } from 'lodash';
import { first, map } from 'rxjs/operators';
import { KibanaRequest, RequestHandlerContext } from 'kibana/server';
import { KibanaRequest } from 'kibana/server';
import { Framework } from '../plugin';
import { IndexPatternsFetcher } from '../../../data/server';
import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy';
import { VisTypeTimeseriesRequestHandlerContext } from '../types';
export async function getFields(
requestContext: RequestHandlerContext,
requestContext: VisTypeTimeseriesRequestHandlerContext,
request: KibanaRequest,
framework: Framework,
indexPatternString: string

View file

@ -6,7 +6,7 @@
* Public License, v 1.
*/
import { FakeRequest, RequestHandlerContext } from 'kibana/server';
import { FakeRequest } from 'kibana/server';
import _ from 'lodash';
import { first, map } from 'rxjs/operators';
@ -15,6 +15,7 @@ import { getPanelData } from './vis_data/get_panel_data';
import { Framework } from '../plugin';
import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy';
import { TimeseriesVisData } from '../../common/types';
import type { VisTypeTimeseriesRequestHandlerContext } from '../types';
export interface GetVisDataOptions {
timerange: {
@ -30,13 +31,13 @@ export interface GetVisDataOptions {
}
export type GetVisData = (
requestContext: RequestHandlerContext,
requestContext: VisTypeTimeseriesRequestHandlerContext,
options: GetVisDataOptions,
framework: Framework
) => Promise<TimeseriesVisData>;
export function getVisData(
requestContext: RequestHandlerContext,
requestContext: VisTypeTimeseriesRequestHandlerContext,
request: FakeRequest & { body: GetVisDataOptions },
framework: Framework
): Promise<TimeseriesVisData> {

View file

@ -6,12 +6,7 @@
* Public License, v 1.
*/
import type {
RequestHandlerContext,
FakeRequest,
IUiSettingsClient,
SavedObjectsClientContract,
} from 'kibana/server';
import type { FakeRequest, IUiSettingsClient, SavedObjectsClientContract } from 'kibana/server';
import type { Framework } from '../../../plugin';
import type { IndexPatternsFetcher, IFieldType } from '../../../../../data/server';
@ -19,6 +14,7 @@ import type { VisPayload } from '../../../../common/types';
import type { IndexPatternsService } from '../../../../../data/common';
import { indexPatterns } from '../../../../../data/server';
import { SanitizedFieldType } from '../../../../common/types';
import type { VisTypeTimeseriesRequestHandlerContext } from '../../../types';
/**
* ReqFacade is a regular KibanaRequest object extended with additional service
@ -27,7 +23,7 @@ import { SanitizedFieldType } from '../../../../common/types';
* This will be replaced by standard KibanaRequest and RequestContext objects in a later version.
*/
export interface ReqFacade<T = unknown> extends FakeRequest {
requestContext: RequestHandlerContext;
requestContext: VisTypeTimeseriesRequestHandlerContext;
framework: Framework;
payload: T;
pre: {
@ -58,8 +54,8 @@ export abstract class AbstractSearchStrategy {
bodies.forEach((body) => {
requests.push(
req.requestContext
.search!.search(
req.requestContext.search
.search(
{
indexType,
params: {

View file

@ -11,9 +11,7 @@ import {
CoreSetup,
CoreStart,
Plugin,
RequestHandlerContext,
Logger,
IRouter,
FakeRequest,
} from 'src/core/server';
import { Observable } from 'rxjs';
@ -27,6 +25,7 @@ import { visDataRoutes } from './routes/vis';
import { fieldsRoutes } from './routes/fields';
import { SearchStrategyRegistry } from './lib/search_strategies';
import { uiSettings } from './ui_settings';
import type { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesRouter } from './types';
export interface LegacySetup {
server: Server;
@ -42,7 +41,7 @@ interface VisTypeTimeseriesPluginStartDependencies {
export interface VisTypeTimeseriesSetup {
getVisData: (
requestContext: RequestHandlerContext,
requestContext: VisTypeTimeseriesRequestHandlerContext,
fakeRequest: FakeRequest,
options: GetVisDataOptions
) => ReturnType<GetVisData>;
@ -55,7 +54,7 @@ export interface Framework {
config$: Observable<VisTypeTimeseriesConfig>;
globalConfig$: PluginInitializerContext['config']['legacy']['globalConfig$'];
logger: Logger;
router: IRouter;
router: VisTypeTimeseriesRouter;
searchStrategyRegistry: SearchStrategyRegistry;
}
@ -73,7 +72,7 @@ export class VisTypeTimeseriesPlugin implements Plugin<VisTypeTimeseriesSetup> {
const config$ = this.initializerContext.config.create<VisTypeTimeseriesConfig>();
// Global config contains things like the ES shard timeout
const globalConfig$ = this.initializerContext.config.legacy.globalConfig$;
const router = core.http.createRouter();
const router = core.http.createRouter<VisTypeTimeseriesRequestHandlerContext>();
const searchStrategyRegistry = new SearchStrategyRegistry();
@ -92,7 +91,7 @@ export class VisTypeTimeseriesPlugin implements Plugin<VisTypeTimeseriesSetup> {
return {
getVisData: async (
requestContext: RequestHandlerContext,
requestContext: VisTypeTimeseriesRequestHandlerContext,
fakeRequest: FakeRequest,
options: GetVisDataOptions
) => {

View file

@ -6,17 +6,18 @@
* Public License, v 1.
*/
import { IRouter, KibanaRequest } from 'kibana/server';
import { KibanaRequest } from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { ensureNoUnsafeProperties } from '@kbn/std';
import { getVisData, GetVisDataOptions } from '../lib/get_vis_data';
import { visPayloadSchema } from '../../common/vis_schema';
import { ROUTES } from '../../common/constants';
import { Framework } from '../plugin';
import type { VisTypeTimeseriesRouter } from '../types';
const escapeHatch = schema.object({}, { unknowns: 'allow' });
export const visDataRoutes = (router: IRouter, framework: Framework) => {
export const visDataRoutes = (router: VisTypeTimeseriesRouter, framework: Framework) => {
router.post(
{
path: ROUTES.VIS_DATA,

View file

@ -0,0 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import type { IRouter, RequestHandlerContext } from 'src/core/server';
import type { DataApiRequestHandlerContext } from '../../data/server';
export interface VisTypeTimeseriesRequestHandlerContext extends RequestHandlerContext {
search: DataApiRequestHandlerContext;
}
export type VisTypeTimeseriesRouter = IRouter<VisTypeTimeseriesRequestHandlerContext>;

View file

@ -7,6 +7,6 @@
*/
import { CorePluginAPlugin } from './plugin';
export { PluginARequestContext } from './plugin';
export { PluginAApiRequestContext } from './plugin';
export const plugin = () => new CorePluginAPlugin();

View file

@ -6,26 +6,27 @@
* Public License, v 1.
*/
import { Plugin, CoreSetup } from 'kibana/server';
import type { Plugin, CoreSetup, RequestHandlerContext } from 'kibana/server';
export interface PluginARequestContext {
export interface PluginAApiRequestContext {
ping: () => Promise<string>;
}
declare module 'kibana/server' {
interface RequestHandlerContext {
pluginA?: PluginARequestContext;
}
interface PluginARequstHandlerContext extends RequestHandlerContext {
pluginA: PluginAApiRequestContext;
}
export class CorePluginAPlugin implements Plugin {
public setup(core: CoreSetup, deps: {}) {
core.http.registerRouteHandlerContext('pluginA', (context) => {
return {
ping: () =>
context.core.elasticsearch.legacy.client.callAsInternalUser('ping') as Promise<string>,
};
});
core.http.registerRouteHandlerContext<PluginARequstHandlerContext, 'pluginA'>(
'pluginA',
(context) => {
return {
ping: () =>
context.core.elasticsearch.legacy.client.callAsInternalUser('ping') as Promise<string>,
};
}
);
}
public start() {}

View file

@ -6,19 +6,17 @@
* Public License, v 1.
*/
import { Plugin, CoreSetup } from 'kibana/server';
import { Plugin, CoreSetup, RequestHandlerContext } from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { PluginARequestContext } from '../../core_plugin_a/server';
import { PluginAApiRequestContext } from '../../core_plugin_a/server';
declare module 'kibana/server' {
interface RequestHandlerContext {
pluginA?: PluginARequestContext;
}
interface PluginBContext extends RequestHandlerContext {
pluginA: PluginAApiRequestContext;
}
export class CorePluginBPlugin implements Plugin {
public setup(core: CoreSetup, deps: {}) {
const router = core.http.createRouter();
const router = core.http.createRouter<PluginBContext>();
router.get({ path: '/core_plugin_b', validate: false }, async (context, req, res) => {
if (!context.pluginA) return res.internalError({ body: 'pluginA is disabled' });
const response = await context.pluginA.ping();

View file

@ -7,6 +7,5 @@
*/
import { CorePluginRouteTimeoutsPlugin } from './plugin';
export { PluginARequestContext } from './plugin';
export const plugin = () => new CorePluginRouteTimeoutsPlugin();

View file

@ -9,16 +9,6 @@
import { Plugin, CoreSetup } from 'kibana/server';
import { schema } from '@kbn/config-schema';
export interface PluginARequestContext {
ping: () => Promise<string>;
}
declare module 'kibana/server' {
interface RequestHandlerContext {
pluginA?: PluginARequestContext;
}
}
export class CorePluginRouteTimeoutsPlugin implements Plugin {
public setup(core: CoreSetup, deps: {}) {
const { http } = core;

View file

@ -14,12 +14,13 @@ import { ActionsConfigType } from './types';
export type ActionsClient = PublicMethodsOf<ActionsClientClass>;
export type ActionsAuthorization = PublicMethodsOf<ActionsAuthorizationClass>;
export {
export type {
ActionsPlugin,
ActionResult,
ActionTypeExecutorOptions,
ActionType,
PreConfiguredAction,
ActionsApiRequestHandlerContext,
} from './types';
export type {
@ -45,7 +46,7 @@ export type {
TeamsActionParams,
} from './builtin_action_types';
export { PluginSetupContract, PluginStartContract } from './plugin';
export type { PluginSetupContract, PluginStartContract } from './plugin';
export { asSavedObjectExecutionSource, asHttpRequestExecutionSource } from './lib';

View file

@ -12,7 +12,7 @@ import { featuresPluginMock } from '../../features/server/mocks';
import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks';
import { taskManagerMock } from '../../task_manager/server/mocks';
import { eventLogMock } from '../../event_log/server/mocks';
import { ActionType } from './types';
import { ActionType, ActionsApiRequestHandlerContext } from './types';
import { ActionsConfig } from './config';
import {
ActionsPlugin,
@ -73,7 +73,10 @@ describe('Actions Plugin', () => {
});
expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1);
const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0];
const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0] as [
string,
Function
];
expect(handler[0]).toEqual('actions');
const actionsContextHandler = ((await handler[1](
@ -91,7 +94,7 @@ describe('Actions Plugin', () => {
} as unknown) as RequestHandlerContext,
httpServerMock.createKibanaRequest(),
httpServerMock.createResponseFactory()
)) as unknown) as RequestHandlerContext['actions'];
)) as unknown) as ActionsApiRequestHandlerContext;
actionsContextHandler!.getActionsClient();
});
@ -101,7 +104,10 @@ describe('Actions Plugin', () => {
await plugin.setup(coreSetup as any, pluginsSetup);
expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1);
const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0];
const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0] as [
string,
Function
];
expect(handler[0]).toEqual('actions');
const actionsContextHandler = ((await handler[1](
@ -114,7 +120,7 @@ describe('Actions Plugin', () => {
} as unknown) as RequestHandlerContext,
httpServerMock.createKibanaRequest(),
httpServerMock.createResponseFactory()
)) as unknown) as RequestHandlerContext['actions'];
)) as unknown) as ActionsApiRequestHandlerContext;
expect(() => actionsContextHandler!.getActionsClient()).toThrowErrorMatchingInlineSnapshot(
`"Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
);

View file

@ -14,7 +14,6 @@ import {
CoreStart,
KibanaRequest,
Logger,
RequestHandler,
IContextProvider,
ElasticsearchServiceStart,
ILegacyClusterClient,
@ -46,6 +45,7 @@ import {
ActionTypeConfig,
ActionTypeSecrets,
ActionTypeParams,
ActionsRequestHandlerContext,
} from './types';
import { getActionsConfigurationUtilities } from './actions_config';
@ -228,7 +228,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
}
this.kibanaIndexConfig.subscribe((config) => {
core.http.registerRouteHandlerContext(
core.http.registerRouteHandlerContext<ActionsRequestHandlerContext, 'actions'>(
'actions',
this.createRouteHandlerContext(core, config.kibana.index)
);
@ -243,7 +243,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
});
// Routes
const router = core.http.createRouter();
const router = core.http.createRouter<ActionsRequestHandlerContext>();
createActionRoute(router, this.licenseState);
deleteActionRoute(router, this.licenseState);
getActionRoute(router, this.licenseState);
@ -448,7 +448,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
private createRouteHandlerContext = (
core: CoreSetup<ActionsPluginsStart>,
defaultKibanaIndex: string
): IContextProvider<RequestHandler<unknown, unknown, unknown>, 'actions'> => {
): IContextProvider<ActionsRequestHandlerContext, 'actions'> => {
const {
actionTypeRegistry,
isESOUsingEphemeralEncryptionKey,

View file

@ -4,15 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { ActionResult } from '../types';
import { schema } from '@kbn/config-schema';
import { IRouter } from 'kibana/server';
import { ActionResult, ActionsRequestHandlerContext } from '../types';
import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib';
import { BASE_ACTION_API_PATH } from '../../common';
@ -23,7 +17,10 @@ export const bodySchema = schema.object({
secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
});
export const createActionRoute = (router: IRouter, licenseState: ILicenseState) => {
export const createActionRoute = (
router: IRouter<ActionsRequestHandlerContext>,
licenseState: ILicenseState
) => {
router.post(
{
path: `${BASE_ACTION_API_PATH}/action`,
@ -31,11 +28,7 @@ export const createActionRoute = (router: IRouter, licenseState: ILicenseState)
body: bodySchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<unknown, unknown, TypeOf<typeof bodySchema>>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.actions) {

View file

@ -9,22 +9,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { IRouter } from 'kibana/server';
import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib';
import { BASE_ACTION_API_PATH } from '../../common';
import { ActionsRequestHandlerContext } from '../types';
const paramSchema = schema.object({
id: schema.string(),
});
export const deleteActionRoute = (router: IRouter, licenseState: ILicenseState) => {
export const deleteActionRoute = (
router: IRouter<ActionsRequestHandlerContext>,
licenseState: ILicenseState
) => {
router.delete(
{
path: `${BASE_ACTION_API_PATH}/action/{id}`,
@ -32,11 +30,7 @@ export const deleteActionRoute = (router: IRouter, licenseState: ILicenseState)
params: paramSchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.actions) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' });

View file

@ -3,17 +3,11 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { IRouter } from 'kibana/server';
import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib';
import { ActionTypeExecutorResult } from '../types';
import { ActionTypeExecutorResult, ActionsRequestHandlerContext } from '../types';
import { BASE_ACTION_API_PATH } from '../../common';
import { asHttpRequestExecutionSource } from '../lib/action_execution_source';
@ -25,7 +19,10 @@ const bodySchema = schema.object({
params: schema.recordOf(schema.string(), schema.any()),
});
export const executeActionRoute = (router: IRouter, licenseState: ILicenseState) => {
export const executeActionRoute = (
router: IRouter<ActionsRequestHandlerContext>,
licenseState: ILicenseState
) => {
router.post(
{
path: `${BASE_ACTION_API_PATH}/action/{id}/_execute`,
@ -34,11 +31,7 @@ export const executeActionRoute = (router: IRouter, licenseState: ILicenseState)
params: paramSchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, TypeOf<typeof bodySchema>>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.actions) {

View file

@ -4,22 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { IRouter } from 'kibana/server';
import { ILicenseState, verifyApiAccess } from '../lib';
import { BASE_ACTION_API_PATH } from '../../common';
import { ActionsRequestHandlerContext } from '../types';
const paramSchema = schema.object({
id: schema.string(),
});
export const getActionRoute = (router: IRouter, licenseState: ILicenseState) => {
export const getActionRoute = (
router: IRouter<ActionsRequestHandlerContext>,
licenseState: ILicenseState
) => {
router.get(
{
path: `${BASE_ACTION_API_PATH}/action/{id}`,
@ -27,11 +25,7 @@ export const getActionRoute = (router: IRouter, licenseState: ILicenseState) =>
params: paramSchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.actions) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' });

View file

@ -4,27 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { IRouter } from 'kibana/server';
import { ILicenseState, verifyApiAccess } from '../lib';
import { BASE_ACTION_API_PATH } from '../../common';
import { ActionsRequestHandlerContext } from '../types';
export const getAllActionRoute = (router: IRouter, licenseState: ILicenseState) => {
export const getAllActionRoute = (
router: IRouter<ActionsRequestHandlerContext>,
licenseState: ILicenseState
) => {
router.get(
{
path: `${BASE_ACTION_API_PATH}`,
validate: {},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<unknown, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.actions) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' });

View file

@ -4,27 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { IRouter } from 'kibana/server';
import { ILicenseState, verifyApiAccess } from '../lib';
import { BASE_ACTION_API_PATH } from '../../common';
import { ActionsRequestHandlerContext } from '../types';
export const listActionTypesRoute = (router: IRouter, licenseState: ILicenseState) => {
export const listActionTypesRoute = (
router: IRouter<ActionsRequestHandlerContext>,
licenseState: ILicenseState
) => {
router.get(
{
path: `${BASE_ACTION_API_PATH}/list_action_types`,
validate: {},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<unknown, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.actions) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' });

View file

@ -4,16 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { IRouter } from 'kibana/server';
import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib';
import { BASE_ACTION_API_PATH } from '../../common';
import { ActionsRequestHandlerContext } from '../types';
const paramSchema = schema.object({
id: schema.string(),
@ -25,7 +20,10 @@ const bodySchema = schema.object({
secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
});
export const updateActionRoute = (router: IRouter, licenseState: ILicenseState) => {
export const updateActionRoute = (
router: IRouter<ActionsRequestHandlerContext>,
licenseState: ILicenseState
) => {
router.put(
{
path: `${BASE_ACTION_API_PATH}/action/{id}`,
@ -34,11 +32,7 @@ export const updateActionRoute = (router: IRouter, licenseState: ILicenseState)
params: paramSchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, TypeOf<typeof bodySchema>>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.actions) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' });

View file

@ -15,6 +15,7 @@ import {
SavedObjectsClientContract,
SavedObjectAttributes,
ElasticsearchClient,
RequestHandlerContext,
} from '../../../../src/core/server';
import { ActionTypeExecutorResult } from '../common';
export { ActionTypeExecutorResult } from '../common';
@ -40,13 +41,13 @@ export interface Services {
getLegacyScopedClusterClient(clusterClient: ILegacyClusterClient): ILegacyScopedClusterClient;
}
declare module 'src/core/server' {
interface RequestHandlerContext {
actions?: {
getActionsClient: () => ActionsClient;
listTypes: ActionTypeRegistry['list'];
};
}
export interface ActionsApiRequestHandlerContext {
getActionsClient: () => ActionsClient;
listTypes: ActionTypeRegistry['list'];
}
export interface ActionsRequestHandlerContext extends RequestHandlerContext {
actions: ActionsApiRequestHandlerContext;
}
export interface ActionsPlugin {

View file

@ -12,7 +12,7 @@ import { AlertsConfigType } from './types';
export type AlertsClient = PublicMethodsOf<AlertsClientClass>;
export {
export type {
ActionVariable,
AlertType,
ActionGroup,
@ -26,6 +26,7 @@ export {
PartialAlert,
AlertInstanceState,
AlertInstanceContext,
AlertingApiRequestHandlerContext,
} from './types';
export { PluginSetupContract, PluginStartContract } from './plugin';
export { FindResult } from './alerts_client';

View file

@ -28,13 +28,13 @@ import {
CoreStart,
SavedObjectsServiceStart,
IContextProvider,
RequestHandler,
ElasticsearchServiceStart,
ILegacyClusterClient,
StatusServiceSetup,
ServiceStatus,
SavedObjectsBulkGetObject,
} from '../../../../src/core/server';
import type { AlertingRequestHandlerContext } from './types';
import {
aggregateAlertRoute,
@ -255,10 +255,13 @@ export class AlertingPlugin {
initializeAlertingHealth(this.logger, plugins.taskManager, core.getStartServices());
core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext(core));
core.http.registerRouteHandlerContext<AlertingRequestHandlerContext, 'alerting'>(
'alerting',
this.createRouteHandlerContext(core)
);
// Routes
const router = core.http.createRouter();
const router = core.http.createRouter<AlertingRequestHandlerContext>();
// Register routes
aggregateAlertRoute(router, this.licenseState);
createAlertRoute(router, this.licenseState);
@ -392,7 +395,7 @@ export class AlertingPlugin {
private createRouteHandlerContext = (
core: CoreSetup
): IContextProvider<RequestHandler<unknown, unknown, unknown>, 'alerting'> => {
): IContextProvider<AlertingRequestHandlerContext, 'alerting'> => {
const { alertTypeRegistry, alertsClientFactory } = this;
return async function alertsRouteHandlerContext(context, request) {
const [{ savedObjects }] = await core.getStartServices();

View file

@ -4,18 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
RequestHandlerContext,
KibanaRequest,
KibanaResponseFactory,
ILegacyClusterClient,
} from 'kibana/server';
import { KibanaRequest, KibanaResponseFactory, ILegacyClusterClient } from 'kibana/server';
import { identity } from 'lodash';
import type { MethodKeysOf } from '@kbn/utility-types';
import { httpServerMock } from '../../../../../src/core/server/mocks';
import { alertsClientMock, AlertsClientMock } from '../alerts_client.mock';
import { AlertsHealth, AlertType } from '../../common';
import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks';
import type { AlertingRequestHandlerContext } from '../types';
export function mockHandlerArguments(
{
@ -32,7 +28,11 @@ export function mockHandlerArguments(
},
req: unknown,
res?: Array<MethodKeysOf<KibanaResponseFactory>>
): [RequestHandlerContext, KibanaRequest<unknown, unknown, unknown>, KibanaResponseFactory] {
): [
AlertingRequestHandlerContext,
KibanaRequest<unknown, unknown, unknown>,
KibanaResponseFactory
] {
const listTypes = jest.fn(() => listTypesRes);
return [
({
@ -44,7 +44,7 @@ export function mockHandlerArguments(
},
getFrameworkHealth,
},
} as unknown) as RequestHandlerContext,
} as unknown) as AlertingRequestHandlerContext,
req as KibanaRequest<unknown, unknown, unknown>,
mockResponseFactory(res),
];

View file

@ -4,14 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import type { AlertingRouter } from '../types';
import { ILicenseState } from '../lib/license_state';
import { verifyApiAccess } from '../lib/license_api_access';
import { BASE_ALERT_API_PATH } from '../../common';
@ -38,7 +32,7 @@ const querySchema = schema.object({
filter: schema.maybe(schema.string()),
});
export const aggregateAlertRoute = (router: IRouter, licenseState: ILicenseState) => {
export const aggregateAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => {
router.get(
{
path: `${BASE_ALERT_API_PATH}/_aggregate`,
@ -46,11 +40,7 @@ export const aggregateAlertRoute = (router: IRouter, licenseState: ILicenseState
query: querySchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<unknown, TypeOf<typeof querySchema>, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -4,14 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import type { AlertingRouter } from '../types';
import { ILicenseState } from '../lib/license_state';
import { verifyApiAccess } from '../lib/license_api_access';
import { validateDurationSchema } from '../lib';
@ -48,7 +42,7 @@ export const bodySchema = schema.object({
notifyWhen: schema.nullable(schema.string({ validate: validateNotifyWhenType })),
});
export const createAlertRoute = (router: IRouter, licenseState: ILicenseState) => {
export const createAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => {
router.post(
{
path: `${BASE_ALERT_API_PATH}/alert`,
@ -57,11 +51,7 @@ export const createAlertRoute = (router: IRouter, licenseState: ILicenseState) =
},
},
handleDisabledApiKeysError(
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<unknown, unknown, TypeOf<typeof bodySchema>>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.alerting) {

View file

@ -4,14 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import type { AlertingRouter } from '../types';
import { ILicenseState } from '../lib/license_state';
import { verifyApiAccess } from '../lib/license_api_access';
import { BASE_ALERT_API_PATH } from '../../common';
@ -20,7 +14,7 @@ const paramSchema = schema.object({
id: schema.string(),
});
export const deleteAlertRoute = (router: IRouter, licenseState: ILicenseState) => {
export const deleteAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => {
router.delete(
{
path: `${BASE_ALERT_API_PATH}/alert/{id}`,
@ -28,11 +22,7 @@ export const deleteAlertRoute = (router: IRouter, licenseState: ILicenseState) =
params: paramSchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -4,14 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import type { AlertingRouter } from '../types';
import { ILicenseState } from '../lib/license_state';
import { verifyApiAccess } from '../lib/license_api_access';
import { BASE_ALERT_API_PATH } from '../../common';
@ -21,7 +15,7 @@ const paramSchema = schema.object({
id: schema.string(),
});
export const disableAlertRoute = (router: IRouter, licenseState: ILicenseState) => {
export const disableAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => {
router.post(
{
path: `${BASE_ALERT_API_PATH}/alert/{id}/_disable`,
@ -29,11 +23,7 @@ export const disableAlertRoute = (router: IRouter, licenseState: ILicenseState)
params: paramSchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -4,14 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import type { AlertingRouter } from '../types';
import { ILicenseState } from '../lib/license_state';
import { verifyApiAccess } from '../lib/license_api_access';
import { BASE_ALERT_API_PATH } from '../../common';
@ -22,7 +16,7 @@ const paramSchema = schema.object({
id: schema.string(),
});
export const enableAlertRoute = (router: IRouter, licenseState: ILicenseState) => {
export const enableAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => {
router.post(
{
path: `${BASE_ALERT_API_PATH}/alert/{id}/_enable`,
@ -31,11 +25,7 @@ export const enableAlertRoute = (router: IRouter, licenseState: ILicenseState) =
},
},
handleDisabledApiKeysError(
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -4,14 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import type { AlertingRouter } from '../types';
import { ILicenseState } from '../lib/license_state';
import { verifyApiAccess } from '../lib/license_api_access';
import { BASE_ALERT_API_PATH } from '../../common';
@ -43,7 +38,7 @@ const querySchema = schema.object({
filter: schema.maybe(schema.string()),
});
export const findAlertRoute = (router: IRouter, licenseState: ILicenseState) => {
export const findAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => {
router.get(
{
path: `${BASE_ALERT_API_PATH}/_find`,
@ -51,11 +46,7 @@ export const findAlertRoute = (router: IRouter, licenseState: ILicenseState) =>
query: querySchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<unknown, TypeOf<typeof querySchema>, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -4,23 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { ILicenseState } from '../lib/license_state';
import { verifyApiAccess } from '../lib/license_api_access';
import { BASE_ALERT_API_PATH } from '../../common';
import type { AlertingRouter } from '../types';
const paramSchema = schema.object({
id: schema.string(),
});
export const getAlertRoute = (router: IRouter, licenseState: ILicenseState) => {
export const getAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => {
router.get(
{
path: `${BASE_ALERT_API_PATH}/alert/{id}`,
@ -28,11 +22,7 @@ export const getAlertRoute = (router: IRouter, licenseState: ILicenseState) => {
params: paramSchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -4,14 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import type { AlertingRouter } from '../types';
import { ILicenseState } from '../lib/license_state';
import { verifyApiAccess } from '../lib/license_api_access';
import { BASE_ALERT_API_PATH } from '../../common';
@ -24,7 +18,10 @@ const querySchema = schema.object({
dateStart: schema.maybe(schema.string()),
});
export const getAlertInstanceSummaryRoute = (router: IRouter, licenseState: ILicenseState) => {
export const getAlertInstanceSummaryRoute = (
router: AlertingRouter,
licenseState: ILicenseState
) => {
router.get(
{
path: `${BASE_ALERT_API_PATH}/alert/{id}/_instance_summary`,
@ -33,11 +30,7 @@ export const getAlertInstanceSummaryRoute = (router: IRouter, licenseState: ILic
query: querySchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, TypeOf<typeof querySchema>, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -4,14 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import { schema } from '@kbn/config-schema';
import type { AlertingRouter } from '../types';
import { ILicenseState } from '../lib/license_state';
import { verifyApiAccess } from '../lib/license_api_access';
import { BASE_ALERT_API_PATH } from '../../common';
@ -20,7 +14,7 @@ const paramSchema = schema.object({
id: schema.string(),
});
export const getAlertStateRoute = (router: IRouter, licenseState: ILicenseState) => {
export const getAlertStateRoute = (router: AlertingRouter, licenseState: ILicenseState) => {
router.get(
{
path: `${BASE_ALERT_API_PATH}/alert/{id}/state`,
@ -28,11 +22,7 @@ export const getAlertStateRoute = (router: IRouter, licenseState: ILicenseState)
params: paramSchema,
},
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -4,13 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
IRouter,
RequestHandlerContext,
KibanaRequest,
IKibanaResponse,
KibanaResponseFactory,
} from 'kibana/server';
import type { AlertingRouter } from '../types';
import { ILicenseState } from '../lib/license_state';
import { verifyApiAccess } from '../lib/license_api_access';
import { AlertingFrameworkHealth } from '../types';
@ -28,7 +22,7 @@ interface XPackUsageSecurity {
}
export function healthRoute(
router: IRouter,
router: AlertingRouter,
licenseState: ILicenseState,
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup
) {
@ -37,11 +31,7 @@ export function healthRoute(
path: '/api/alerts/_health',
validate: false,
},
router.handleLegacyErrors(async function (
context: RequestHandlerContext,
req: KibanaRequest<unknown, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse> {
router.handleLegacyErrors(async function (context, req, res) {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -5,22 +5,10 @@
*/
import { i18n } from '@kbn/i18n';
import {
RequestHandler,
KibanaRequest,
KibanaResponseFactory,
RequestHandlerContext,
RouteMethod,
} from 'kibana/server';
import { RequestHandlerWrapper } from 'kibana/server';
export function handleDisabledApiKeysError<P, Q, B>(
handler: RequestHandler<P, Q, B>
): RequestHandler<P, Q, B> {
return async (
context: RequestHandlerContext,
request: KibanaRequest<P, Q, B, RouteMethod>,
response: KibanaResponseFactory
) => {
export const handleDisabledApiKeysError: RequestHandlerWrapper = (handler) => {
return async (context, request, response) => {
try {
return await handler(context, request, response);
} catch (e) {
@ -36,7 +24,7 @@ export function handleDisabledApiKeysError<P, Q, B>(
throw e;
}
};
}
};
export function isApiKeyDisabledError(e: Error) {
return e?.message?.includes('api keys are not enabled') ?? false;

Some files were not shown because too many files have changed in this diff Show more