[ML] New Platform server shim: update analytics routes to use new platform router (#53521) (#53897)

* update dfAnalytics routes to use np router

* add route schemas and only show error message

* convert route file to ts and set handlers inline

* update df analytics param type

* update mlClient type and assert mlClient is not null

* handle errors correctly

* ensure error status gets passed correctly to wrapper
This commit is contained in:
Melissa Alvarez 2020-01-02 21:13:08 -07:00 committed by GitHub
parent 126da30398
commit 6380dfce78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 426 additions and 180 deletions

View file

@ -81,10 +81,11 @@ export const ml = (kibana: any) => {
injectUiAppVars: server.injectUiAppVars,
http: mlHttpService,
savedObjects: server.savedObjects,
elasticsearch: kbnServer.newPlatform.setup.core.elasticsearch, // NP
};
const { usageCollection, cloud, home } = kbnServer.newPlatform.setup.plugins;
const plugins = {
elasticsearch: server.plugins.elasticsearch,
elasticsearch: server.plugins.elasticsearch, // legacy
security: server.plugins.security,
xpackMain: server.plugins.xpack_main,
spaces: server.plugins.spaces,

View file

@ -76,7 +76,7 @@ export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled }) => {
<EuiLoadingSpinner className="mlOverviewPanel__spinner" size="xl" />
)}
    
{isInitialized === true && analytics.length === 0 && (
{errorMessage === undefined && isInitialized === true && analytics.length === 0 && (
<EuiEmptyPrompt
iconType="createAdvancedJob"
title={

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { boomify, isBoom } from 'boom';
import { ResponseError, CustomHttpResponseOptions } from 'src/core/server';
export function wrapError(error: any): CustomHttpResponseOptions<ResponseError> {
const boom = isBoom(error) ? error : boomify(error, { statusCode: error.status });
return {
body: boom,
headers: boom.output.headers,
statusCode: boom.output.statusCode,
};
}

View file

@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { schema } from '@kbn/config-schema';
export const dataAnalyticsJobConfigSchema = {
description: schema.maybe(schema.string()),
dest: schema.object({
index: schema.string(),
results_field: schema.maybe(schema.string()),
}),
source: schema.object({
index: schema.string(),
}),
analysis: schema.any(),
analyzed_fields: schema.any(),
model_memory_limit: schema.string(),
};
export const dataAnalyticsEvaluateSchema = {
index: schema.string(),
query: schema.maybe(schema.any()),
evaluation: schema.maybe(
schema.object({
regression: schema.maybe(schema.any()),
classification: schema.maybe(schema.any()),
})
),
};
export const dataAnalyticsExplainSchema = {
description: schema.maybe(schema.string()),
dest: schema.maybe(schema.any()),
source: schema.object({
index: schema.string(),
}),
analysis: schema.any(),
analyzed_fields: schema.maybe(schema.any()),
model_memory_limit: schema.maybe(schema.string()),
};

View file

@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import {
KibanaRequest,
KibanaResponseFactory,
RequestHandler,
RequestHandlerContext,
} from 'src/core/server';
import { PLUGIN_ID, MlXpackMainPlugin } from './plugin';
export const licensePreRoutingFactory = (
xpackMainPlugin: MlXpackMainPlugin,
handler: RequestHandler<any, any, any>
): RequestHandler<any, any, any> => {
// License checking and enable/disable logic
return function licensePreRouting(
ctx: RequestHandlerContext,
request: KibanaRequest,
response: KibanaResponseFactory
) {
const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN_ID).getLicenseCheckResults();
if (!licenseCheckResults.isAvailable) {
return response.forbidden({
body: {
message: licenseCheckResults.message,
},
});
}
return handler(ctx, request, response);
};
};

View file

@ -8,9 +8,16 @@ import Boom from 'boom';
import { i18n } from '@kbn/i18n';
import { ServerRoute } from 'hapi';
import { KibanaConfig, SavedObjectsLegacyService } from 'src/legacy/server/kbn_server';
import { Logger, PluginInitializerContext, CoreSetup } from 'src/core/server';
import {
Logger,
PluginInitializerContext,
CoreSetup,
IRouter,
IScopedClusterClient,
} from 'src/core/server';
import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { ElasticsearchServiceSetup } from 'src/core/server';
import { CloudSetup } from '../../../../../plugins/cloud/server';
import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main';
import { addLinksToSampleDatasets } from '../lib/sample_data_sets';
@ -56,6 +63,10 @@ import { jobAuditMessagesRoutes } from '../routes/job_audit_messages';
import { fileDataVisualizerRoutes } from '../routes/file_data_visualizer';
import { initMlServerLog, LogInitialization } from '../client/log';
import { HomeServerPluginSetup } from '../../../../../../src/plugins/home/server';
// @ts-ignore: could not find declaration file for module
import { elasticsearchJsPlugin } from '../client/elasticsearch_ml';
export const PLUGIN_ID = 'ml';
type CoreHttpSetup = CoreSetup['http'];
export interface MlHttpServiceSetup extends CoreHttpSetup {
@ -70,6 +81,7 @@ export interface MlCoreSetup {
injectUiAppVars: (id: string, callback: () => {}) => any;
http: MlHttpServiceSetup;
savedObjects: SavedObjectsLegacyService;
elasticsearch: ElasticsearchServiceSetup;
}
export interface MlInitializerContext extends PluginInitializerContext {
legacyConfig: KibanaConfig;
@ -86,12 +98,15 @@ export interface PluginsSetup {
// TODO: this is temporary for `mirrorPluginStatus`
ml: any;
}
export interface RouteInitialization {
commonRouteConfig: any;
config?: any;
elasticsearchPlugin: ElasticsearchPlugin;
elasticsearchService: ElasticsearchServiceSetup;
route(route: ServerRoute | ServerRoute[]): void;
xpackMainPlugin?: MlXpackMainPlugin;
router: IRouter;
xpackMainPlugin: MlXpackMainPlugin;
savedObjects?: SavedObjectsLegacyService;
spacesPlugin: any;
cloud?: CloudSetup;
@ -101,8 +116,16 @@ export interface UsageInitialization {
savedObjects: SavedObjectsLegacyService;
}
declare module 'kibana/server' {
interface RequestHandlerContext {
ml?: {
mlClient: IScopedClusterClient;
};
}
}
export class Plugin {
private readonly pluginId: string = 'ml';
private readonly pluginId: string = PLUGIN_ID;
private config: any;
private log: Logger;
@ -183,17 +206,27 @@ export class Plugin {
};
});
// Can access via new platform router's handler function 'context' parameter - context.ml.mlClient
const mlClient = core.elasticsearch.createClient('ml', { plugins: [elasticsearchJsPlugin] });
http.registerRouteHandlerContext('ml', (context, request) => {
return {
mlClient: mlClient.asScoped(request),
};
});
const routeInitializationDeps: RouteInitialization = {
commonRouteConfig,
route: http.route,
router: http.createRouter(),
elasticsearchPlugin: plugins.elasticsearch,
elasticsearchService: core.elasticsearch,
xpackMainPlugin: plugins.xpackMain,
spacesPlugin: plugins.spaces,
};
const extendedRouteInitializationDeps: RouteInitialization = {
...routeInitializationDeps,
config: this.config,
xpackMainPlugin: plugins.xpackMain,
savedObjects: core.savedObjects,
spacesPlugin: plugins.spaces,
cloud: plugins.cloud,

View file

@ -1,174 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { callWithRequestFactory } from '../client/call_with_request_factory';
import { wrapError } from '../client/errors';
import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages';
export function dataFrameAnalyticsRoutes({ commonRouteConfig, elasticsearchPlugin, route }) {
route({
method: 'GET',
path: '/api/ml/data_frame/analytics',
handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
return callWithRequest('ml.getDataFrameAnalytics').catch(resp => wrapError(resp));
},
config: {
...commonRouteConfig,
},
});
route({
method: 'GET',
path: '/api/ml/data_frame/analytics/{analyticsId}',
handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
const { analyticsId } = request.params;
return callWithRequest('ml.getDataFrameAnalytics', { analyticsId }).catch(resp =>
wrapError(resp)
);
},
config: {
...commonRouteConfig,
},
});
route({
method: 'GET',
path: '/api/ml/data_frame/analytics/_stats',
handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
return callWithRequest('ml.getDataFrameAnalyticsStats').catch(resp => wrapError(resp));
},
config: {
...commonRouteConfig,
},
});
route({
method: 'GET',
path: '/api/ml/data_frame/analytics/{analyticsId}/_stats',
handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
const { analyticsId } = request.params;
return callWithRequest('ml.getDataFrameAnalyticsStats', { analyticsId }).catch(resp =>
wrapError(resp)
);
},
config: {
...commonRouteConfig,
},
});
route({
method: 'PUT',
path: '/api/ml/data_frame/analytics/{analyticsId}',
handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
const { analyticsId } = request.params;
return callWithRequest('ml.createDataFrameAnalytics', {
body: request.payload,
analyticsId,
}).catch(resp => wrapError(resp));
},
config: {
...commonRouteConfig,
},
});
route({
method: 'POST',
path: '/api/ml/data_frame/_evaluate',
handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
return callWithRequest('ml.evaluateDataFrameAnalytics', {
body: request.payload,
}).catch(resp => wrapError(resp));
},
config: {
...commonRouteConfig,
},
});
route({
method: 'POST',
path: '/api/ml/data_frame/analytics/_explain',
handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
return callWithRequest('ml.estimateDataFrameAnalyticsMemoryUsage', {
body: request.payload,
}).catch(resp => wrapError(resp));
},
config: {
...commonRouteConfig,
},
});
route({
method: 'DELETE',
path: '/api/ml/data_frame/analytics/{analyticsId}',
handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
const { analyticsId } = request.params;
return callWithRequest('ml.deleteDataFrameAnalytics', { analyticsId }).catch(resp =>
wrapError(resp)
);
},
config: {
...commonRouteConfig,
},
});
route({
method: 'POST',
path: '/api/ml/data_frame/analytics/{analyticsId}/_start',
handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
const options = {
analyticsId: request.params.analyticsId,
};
return callWithRequest('ml.startDataFrameAnalytics', options).catch(resp => wrapError(resp));
},
config: {
...commonRouteConfig,
},
});
route({
method: 'POST',
path: '/api/ml/data_frame/analytics/{analyticsId}/_stop',
handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
const options = {
analyticsId: request.params.analyticsId,
};
if (request.query.force !== undefined) {
options.force = request.query.force;
}
return callWithRequest('ml.stopDataFrameAnalytics', options).catch(resp => wrapError(resp));
},
config: {
...commonRouteConfig,
},
});
route({
method: 'GET',
path: '/api/ml/data_frame/analytics/{analyticsId}/messages',
handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
const { getAnalyticsAuditMessages } = analyticsAuditMessagesProvider(callWithRequest);
const { analyticsId } = request.params;
return getAnalyticsAuditMessages(analyticsId).catch(resp => wrapError(resp));
},
config: {
...commonRouteConfig,
},
});
}

View file

@ -0,0 +1,289 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { schema } from '@kbn/config-schema';
import { wrapError } from '../client/error_wrapper';
import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages';
import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
import { RouteInitialization } from '../new_platform/plugin';
import {
dataAnalyticsJobConfigSchema,
dataAnalyticsEvaluateSchema,
dataAnalyticsExplainSchema,
} from '../new_platform/data_analytics_schema';
export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteInitialization) {
router.get(
{
path: '/api/ml/data_frame/analytics',
validate: {
params: schema.object({ analyticsId: schema.maybe(schema.string()) }),
},
},
licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser('ml.getDataFrameAnalytics');
return response.ok({
body: { ...results },
});
} catch (e) {
return response.customError(wrapError(e));
}
})
);
router.get(
{
path: '/api/ml/data_frame/analytics/{analyticsId}',
validate: {
params: schema.object({ analyticsId: schema.string() }),
},
},
licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser('ml.getDataFrameAnalytics', {
analyticsId,
});
return response.ok({
body: { ...results },
});
} catch (e) {
return response.customError(wrapError(e));
}
})
);
router.get(
{
path: '/api/ml/data_frame/analytics/_stats',
validate: false,
},
licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser(
'ml.getDataFrameAnalyticsStats'
);
return response.ok({
body: { ...results },
});
} catch (e) {
return response.customError(wrapError(e));
}
})
);
router.get(
{
path: '/api/ml/data_frame/analytics/{analyticsId}/_stats',
validate: {
params: schema.object({ analyticsId: schema.string() }),
},
},
licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser(
'ml.getDataFrameAnalyticsStats',
{
analyticsId,
}
);
return response.ok({
body: { ...results },
});
} catch (e) {
return response.customError(wrapError(e));
}
})
);
router.put(
{
path: '/api/ml/data_frame/analytics/{analyticsId}',
validate: {
params: schema.object({
analyticsId: schema.string(),
}),
body: schema.object({ ...dataAnalyticsJobConfigSchema }),
},
},
licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser(
'ml.createDataFrameAnalytics',
{
body: request.body,
analyticsId,
}
);
return response.ok({
body: { ...results },
});
} catch (e) {
return response.customError(wrapError(e));
}
})
);
router.post(
{
path: '/api/ml/data_frame/_evaluate',
validate: {
body: schema.object({ ...dataAnalyticsEvaluateSchema }),
},
},
licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser(
'ml.evaluateDataFrameAnalytics',
{
body: request.body,
}
);
return response.ok({
body: { ...results },
});
} catch (e) {
return response.customError(wrapError(e));
}
})
);
router.post(
{
path: '/api/ml/data_frame/analytics/_explain',
validate: {
body: schema.object({ ...dataAnalyticsExplainSchema }),
},
},
licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser(
'ml.estimateDataFrameAnalyticsMemoryUsage',
{
body: request.body,
}
);
return response.ok({
body: { ...results },
});
} catch (e) {
return response.customError(wrapError(e));
}
})
);
router.delete(
{
path: '/api/ml/data_frame/analytics/{analyticsId}',
validate: {
params: schema.object({
analyticsId: schema.string(),
}),
},
},
licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser(
'ml.deleteDataFrameAnalytics',
{
analyticsId,
}
);
return response.ok({
body: { ...results },
});
} catch (e) {
return response.customError(wrapError(e));
}
})
);
router.post(
{
path: '/api/ml/data_frame/analytics/{analyticsId}/_start',
validate: {
params: schema.object({
analyticsId: schema.string(),
}),
},
},
licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser('ml.startDataFrameAnalytics', {
analyticsId,
});
return response.ok({
body: { ...results },
});
} catch (e) {
return response.customError(wrapError(e));
}
})
);
router.post(
{
path: '/api/ml/data_frame/analytics/{analyticsId}/_stop',
validate: {
params: schema.object({
analyticsId: schema.string(),
force: schema.maybe(schema.boolean()),
}),
},
},
licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
try {
const options: { analyticsId: string; force?: boolean | undefined } = {
analyticsId: request.params.analyticsId,
};
// @ts-ignore TODO: update types
if (request.url?.query?.force !== undefined) {
// @ts-ignore TODO: update types
options.force = request.url.query.force;
}
const results = await context.ml!.mlClient.callAsCurrentUser(
'ml.stopDataFrameAnalytics',
options
);
return response.ok({
body: { ...results },
});
} catch (e) {
return response.customError(wrapError(e));
}
})
);
router.get(
{
path: '/api/ml/data_frame/analytics/{analyticsId}/messages',
validate: {
params: schema.object({ analyticsId: schema.string() }),
},
},
licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const { getAnalyticsAuditMessages } = analyticsAuditMessagesProvider(
context.ml!.mlClient.callAsCurrentUser
);
const results = await getAnalyticsAuditMessages(analyticsId);
return response.ok({
body: results,
});
} catch (e) {
return response.customError(wrapError(e));
}
})
);
}