vis_type_timeseries server side new platform migration (#52501) (#53623)

This commit is contained in:
Kerry Gallagher 2019-12-19 20:20:15 +00:00 committed by GitHub
parent d20a6bc912
commit 68d5f31b3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 292 additions and 143 deletions

View file

@ -19,13 +19,9 @@
import { resolve } from 'path';
import { Legacy } from 'kibana';
import { PluginInitializerContext } from 'src/core/server';
import { CoreSetup } from 'src/core/server';
import { plugin } from './server/';
import { CustomCoreSetup } from './server/plugin';
import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types';
import { VisTypeTimeseriesSetup } from '../../../plugins/vis_type_timeseries/server';
const metricsPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) =>
new Plugin({
@ -38,10 +34,9 @@ const metricsPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPlu
injectDefaultVars: server => ({}),
},
init: (server: Legacy.Server) => {
const initializerContext = {} as PluginInitializerContext;
const core = { http: { server } } as CoreSetup & CustomCoreSetup;
plugin(initializerContext).setup(core);
const visTypeTimeSeriesPlugin = server.newPlatform.setup.plugins
.metrics as VisTypeTimeseriesSetup;
visTypeTimeSeriesPlugin.__legacy.registerLegacyAPI({ server });
},
config(Joi: any) {
return Joi.object({

View file

@ -17,9 +17,5 @@
* under the License.
*/
import { PluginInitializerContext } from 'kibana/server';
import { MetricsServerPlugin as Plugin } from './plugin';
export function plugin(initializerContext: PluginInitializerContext) {
return new Plugin(initializerContext);
}
export { init } from './init';
export { getVisData, GetVisData, GetVisDataOptions } from './lib/get_vis_data';

View file

@ -17,9 +17,6 @@
* under the License.
*/
import { Legacy } from 'kibana';
import { PluginInitializerContext, CoreSetup } from 'kibana/server';
// @ts-ignore
import { fieldsRoutes } from './routes/fields';
// @ts-ignore
@ -28,30 +25,15 @@ import { visDataRoutes } from './routes/vis';
import { SearchStrategiesRegister } from './lib/search_strategies/search_strategies_register';
// @ts-ignore
import { getVisData } from './lib/get_vis_data';
import { Framework } from '../../../../plugins/vis_type_timeseries/server';
// TODO: Remove as CoreSetup is completed.
export interface CustomCoreSetup {
http: {
server: Legacy.Server;
};
}
export const init = async (framework: Framework, __LEGACY: any) => {
const { core } = framework;
const router = core.http.createRouter();
export class MetricsServerPlugin {
public initializerContext: PluginInitializerContext;
visDataRoutes(router, framework);
constructor(initializerContext: PluginInitializerContext) {
this.initializerContext = initializerContext;
}
public setup(core: CoreSetup & CustomCoreSetup) {
const { http } = core;
fieldsRoutes(http.server);
visDataRoutes(http.server);
// Expose getVisData to allow plugins to use TSVB's backend for metrics
http.server.expose('getVisData', getVisData);
SearchStrategiesRegister.init(http.server);
}
}
// [LEGACY_TODO]
fieldsRoutes(__LEGACY.server);
SearchStrategiesRegister.init(__LEGACY.server);
};

View file

@ -0,0 +1,101 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { RequestHandlerContext } from 'src/core/server';
import _ from 'lodash';
import { first, map } from 'rxjs/operators';
import { getPanelData } from './vis_data/get_panel_data';
import { Framework } from '../../../../../plugins/vis_type_timeseries/server';
interface GetVisDataResponse {
[key: string]: GetVisDataPanel;
}
interface GetVisDataPanel {
id: string;
series: GetVisDataSeries[];
}
interface GetVisDataSeries {
id: string;
label: string;
data: GetVisDataDataPoint[];
}
type GetVisDataDataPoint = [number, number];
export interface GetVisDataOptions {
timerange?: any;
panels?: any;
filters?: any;
state?: any;
query?: any;
}
export type GetVisData = (
requestContext: RequestHandlerContext,
options: GetVisDataOptions,
framework: Framework
) => Promise<GetVisDataResponse>;
export function getVisData(
requestContext: RequestHandlerContext,
options: GetVisDataOptions,
framework: Framework
): Promise<GetVisDataResponse> {
// NOTE / TODO: This facade has been put in place to make migrating to the New Platform easier. It
// removes the need to refactor many layers of dependencies on "req", and instead just augments the top
// level object passed from here. The layers should be refactored fully at some point, but for now
// this works and we are still using the New Platform services for these vis data portions.
const reqFacade: any = {
payload: options,
getUiSettingsService: () => requestContext.core.uiSettings.client,
getSavedObjectsClient: () => requestContext.core.savedObjects.client,
server: {
plugins: {
elasticsearch: {
getCluster: () => {
return {
callWithRequest: async (req: any, endpoint: string, params: any) => {
return await requestContext.core.elasticsearch.dataClient.callAsCurrentUser(
endpoint,
params
);
},
};
},
},
},
},
getEsShardTimeout: async () => {
return await framework.globalConfig$
.pipe(
first(),
map(config => config.elasticsearch.shardTimeout.asMilliseconds())
)
.toPromise();
},
};
const promises = reqFacade.payload.panels.map(getPanelData(reqFacade));
return Promise.all(promises).then(res => {
return res.reduce((acc, data) => {
return _.assign(acc as any, data);
}, {});
}) as Promise<GetVisDataResponse>;
}

View file

@ -17,20 +17,14 @@
* under the License.
*/
import moment from 'moment';
import { of } from 'rxjs';
import { expect } from 'chai';
import { getEsShardTimeout } from '../../helpers/get_es_shard_timeout';
describe('getEsShardTimeout', () => {
it('should return the elasticsearch.shardTimeout', async () => {
const req = {
server: {
newPlatform: {
__internals: {
elasticsearch: { legacy: { config$: of({ shardTimeout: moment.duration(12345) }) } },
},
},
getEsShardTimeout: async () => {
return 12345;
},
};

View file

@ -17,14 +17,4 @@
* under the License.
*/
import _ from 'lodash';
import { getPanelData } from './vis_data/get_panel_data';
export function getVisData(req) {
const promises = req.payload.panels.map(getPanelData(req));
return Promise.all(promises).then(res => {
return res.reduce((acc, data) => {
return _.assign(acc, data);
}, {});
});
}
export function getPanelData(req: any): any;

View file

@ -48,7 +48,6 @@ export async function getSeriesData(req, panel) {
const data = await searchRequest.search(searches);
const series = data.map(handleResponseBody(panel));
let annotations = null;
if (panel.annotations && panel.annotations.length) {

View file

@ -16,13 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { first, map } from 'rxjs/operators';
export async function getEsShardTimeout(req) {
return await req.server.newPlatform.__internals.elasticsearch.legacy.config$
.pipe(
first(),
map(config => config.shardTimeout.asMilliseconds())
)
.toPromise();
return await req.getEsShardTimeout();
}

View file

@ -17,23 +17,28 @@
* under the License.
*/
import { schema } from '@kbn/config-schema';
import { getVisData } from '../lib/get_vis_data';
import Boom from 'boom';
export const visDataRoutes = server => {
server.route({
path: '/api/metrics/vis/data',
method: 'POST',
handler: async req => {
try {
return await getVisData(req);
} catch (err) {
if (err.isBoom && err.status === 401) {
return err;
}
const escapeHatch = schema.object({}, { allowUnknowns: true });
throw Boom.boomify(err, { statusCode: 500 });
}
export const visDataRoutes = (router, framework) => {
router.post(
{
path: '/api/metrics/vis/data',
validate: {
body: escapeHatch,
},
},
});
async (requestContext, request, response) => {
try {
const results = await getVisData(requestContext, request.body, framework);
return response.ok({ body: results });
} catch (error) {
return response.internalError({
body: error.message,
});
}
}
);
};

View file

@ -0,0 +1,6 @@
{
"id": "metrics",
"version": "8.0.0",
"kibanaVersion": "kibana",
"server": true
}

View file

@ -0,0 +1,35 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import { PluginInitializerContext } from 'src/core/server';
import { VisTypeTimeseriesPlugin } from './plugin';
export { VisTypeTimeseriesSetup, Framework } from './plugin';
export const config = {
schema: schema.object({
enabled: schema.boolean({ defaultValue: true }),
}),
};
export type VisTypeTimeseriesConfig = TypeOf<typeof config.schema>;
export function plugin(initializerContext: PluginInitializerContext) {
return new VisTypeTimeseriesPlugin(initializerContext);
}

View file

@ -0,0 +1,96 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
PluginInitializerContext,
CoreSetup,
CoreStart,
Plugin,
RequestHandlerContext,
Logger,
} from 'src/core/server';
import { Observable } from 'rxjs';
import { Server } from 'hapi';
import { once } from 'lodash';
import { VisTypeTimeseriesConfig } from '.';
import {
init,
getVisData,
GetVisData,
GetVisDataOptions,
} from '../../../legacy/core_plugins/vis_type_timeseries/server';
export interface LegacySetup {
server: Server;
}
export interface VisTypeTimeseriesSetup {
/** @deprecated */
__legacy: {
config$: Observable<VisTypeTimeseriesConfig>;
registerLegacyAPI: (__LEGACY: LegacySetup) => void;
};
getVisData: (
requestContext: RequestHandlerContext,
options: GetVisDataOptions
) => ReturnType<GetVisData>;
}
export interface Framework {
core: CoreSetup;
plugins: any;
config$: Observable<VisTypeTimeseriesConfig>;
globalConfig$: PluginInitializerContext['config']['legacy']['globalConfig$'];
logger: Logger;
}
export class VisTypeTimeseriesPlugin implements Plugin<VisTypeTimeseriesSetup> {
constructor(private readonly initializerContext: PluginInitializerContext) {
this.initializerContext = initializerContext;
}
public setup(core: CoreSetup, plugins: any) {
const logger = this.initializerContext.logger.get('visTypeTimeseries');
const config$ = this.initializerContext.config.create<VisTypeTimeseriesConfig>();
// Global config contains things like the ES shard timeout
const globalConfig$ = this.initializerContext.config.legacy.globalConfig$;
const framework: Framework = {
core,
plugins,
config$,
globalConfig$,
logger,
};
return {
__legacy: {
config$,
registerLegacyAPI: once((__LEGACY: LegacySetup) => {
init(framework, __LEGACY);
}),
},
getVisData: async (requestContext: RequestHandlerContext, options: GetVisDataOptions) => {
return await getVisData(requestContext, options, framework);
},
};
}
public start(core: CoreStart) {}
}

View file

@ -16,6 +16,7 @@ import { plugin, InfraServerPluginDeps } from './server/new_platform_index';
import { InfraSetup } from '../../../plugins/infra/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../../plugins/features/server';
import { SpacesPluginSetup } from '../../../plugins/spaces/server';
import { VisTypeTimeseriesSetup } from '../../../../src/plugins/vis_type_timeseries/server';
import { APMPluginContract } from '../../../plugins/apm/server';
const APP_ID = 'infra';
@ -92,19 +93,9 @@ export function infra(kibana: any) {
indexPatterns: {
indexPatternsServiceFactory: legacyServer.indexPatternsServiceFactory,
},
metrics: legacyServer.plugins.metrics,
metrics: plugins.metrics as VisTypeTimeseriesSetup,
spaces: plugins.spaces as SpacesPluginSetup,
features: plugins.features as FeaturesPluginSetup,
// NP_NOTE: [TSVB_GROUP] Huge hack to make TSVB (getVisData()) work with raw requests that
// originate from the New Platform router (and are very different to the old request object).
// Once TSVB has migrated over to NP, and can work with the new raw requests, or ideally just
// the requestContext, this can be removed.
___legacy: {
tsvb: {
elasticsearch: legacyServer.plugins.elasticsearch,
__internals: legacyServer.newPlatform.__internals,
},
},
apm: plugins.apm as APMPluginContract,
};

View file

@ -11,21 +11,19 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { RouteMethod, RouteConfig } from '../../../../../../../../src/core/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../../plugins/features/server';
import { SpacesPluginSetup } from '../../../../../../../plugins/spaces/server';
import { VisTypeTimeseriesSetup } from '../../../../../../../../src/plugins/vis_type_timeseries/server';
import { APMPluginContract } from '../../../../../../../plugins/apm/server';
// NP_TODO: Compose real types from plugins we depend on, no "any"
export interface InfraServerPluginDeps {
spaces: SpacesPluginSetup;
usageCollection: UsageCollectionSetup;
metrics: {
getVisData: any;
};
metrics: VisTypeTimeseriesSetup;
indexPatterns: {
indexPatternsServiceFactory: any;
};
features: FeaturesPluginSetup;
apm: APMPluginContract;
___legacy: any;
}
export interface CallWithRequestParams extends GenericParams {

View file

@ -36,12 +36,10 @@ import { InfraConfig } from '../../../../../../../plugins/infra/server';
export class KibanaFramework {
public router: IRouter;
private core: CoreSetup;
public plugins: InfraServerPluginDeps;
constructor(core: CoreSetup, config: InfraConfig, plugins: InfraServerPluginDeps) {
this.router = core.http.createRouter();
this.core = core;
this.plugins = plugins;
}
@ -246,45 +244,21 @@ export class KibanaFramework {
}
}
// NP_TODO: [TSVB_GROUP] This method needs fixing when the metrics plugin has migrated to the New Platform
public async makeTSVBRequest(
request: KibanaRequest,
requestContext: RequestHandlerContext,
model: TSVBMetricModel,
timerange: { min: number; max: number },
filters: any[],
requestContext: RequestHandlerContext
filters: any[]
): Promise<InfraTSVBResponse> {
const { getVisData } = this.plugins.metrics;
if (typeof getVisData !== 'function') {
throw new Error('TSVB is not available');
}
const url = this.core.http.basePath.prepend('/api/metrics/vis/data');
// For the following request we need a copy of the instnace of the internal request
// but modified for our TSVB request. This will ensure all the instance methods
// are available along with our overriden values
const requestCopy = Object.assign({}, request, {
url,
method: 'POST',
payload: {
timerange,
panels: [model],
filters,
},
// NP_NOTE: [TSVB_GROUP] Huge hack to make TSVB (getVisData()) work with raw requests that
// originate from the New Platform router (and are very different to the old request object).
// Once TSVB has migrated over to NP, and can work with the new raw requests, or ideally just
// the requestContext, this can be removed.
server: {
plugins: {
elasticsearch: this.plugins.___legacy.tsvb.elasticsearch,
},
newPlatform: {
__internals: this.plugins.___legacy.tsvb.__internals,
},
},
getUiSettingsService: () => requestContext.core.uiSettings.client,
getSavedObjectsClient: () => requestContext.core.savedObjects.client,
});
return getVisData(requestCopy);
const options = {
timerange,
panels: [model],
filters,
};
return getVisData(requestContext, options);
}
}

View file

@ -47,7 +47,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter {
}
const requests = options.metrics.map(metricId =>
this.makeTSVBRequest(metricId, options, rawRequest, nodeField, requestContext)
this.makeTSVBRequest(metricId, options, nodeField, requestContext)
);
return Promise.all(requests)
@ -89,7 +89,6 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter {
async makeTSVBRequest(
metricId: InfraMetric,
options: InfraMetricsRequestOptions,
req: KibanaRequest,
nodeField: string,
requestContext: RequestHandlerContext
) {
@ -150,6 +149,6 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter {
? [{ match: { [model.map_field_to]: id } }]
: [{ match: { [nodeField]: id } }];
return this.framework.makeTSVBRequest(req, model, timerange, filters, requestContext);
return this.framework.makeTSVBRequest(requestContext, model, timerange, filters);
}
}

View file

@ -76,13 +76,7 @@ export const populateSeriesWithTSVBData = (
}
// Get TSVB results using the model, timerange and filters
const tsvbResults = await framework.makeTSVBRequest(
request,
model,
timerange,
filters,
requestContext
);
const tsvbResults = await framework.makeTSVBRequest(requestContext, model, timerange, filters);
// If there is no data `custom` will not exist.
if (!tsvbResults.custom) {