Np migration tsvb route validation (#51850)
This commit is contained in:
parent
9a871d2a7a
commit
9380b6408b
|
@ -379,6 +379,7 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
this.expression = await buildPipeline(this.vis, {
|
||||
searchSource: this.savedVisualization.searchSource,
|
||||
timeRange: this.timeRange,
|
||||
savedObjectId: this.savedVisualization.id,
|
||||
});
|
||||
|
||||
this.vis.filters = { timeRange: this.timeRange };
|
||||
|
|
|
@ -32,6 +32,20 @@ const metricsPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPlu
|
|||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
hacks: [resolve(__dirname, 'public/legacy')],
|
||||
injectDefaultVars: server => ({}),
|
||||
mappings: {
|
||||
'tsvb-validation-telemetry': {
|
||||
properties: {
|
||||
failedRequests: {
|
||||
type: 'long',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
savedObjectSchemas: {
|
||||
'tsvb-validation-telemetry': {
|
||||
isNamespaceAgnostic: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
init: (server: Legacy.Server) => {
|
||||
const visTypeTimeSeriesPlugin = server.newPlatform.setup.plugins
|
||||
|
|
|
@ -31,6 +31,7 @@ type Context = KibanaContext | null;
|
|||
interface Arguments {
|
||||
params: string;
|
||||
uiState: string;
|
||||
savedObjectId: string | null;
|
||||
}
|
||||
|
||||
type VisParams = Required<Arguments>;
|
||||
|
@ -64,10 +65,16 @@ export const createMetricsFn = (): ExpressionFunction<typeof name, Context, Argu
|
|||
default: '"{}"',
|
||||
help: '',
|
||||
},
|
||||
savedObjectId: {
|
||||
types: ['null', 'string'],
|
||||
default: null,
|
||||
help: '',
|
||||
},
|
||||
},
|
||||
async fn(context: Context, args: Arguments) {
|
||||
const params = JSON.parse(args.params);
|
||||
const uiStateParams = JSON.parse(args.uiState);
|
||||
const savedObjectId = args.savedObjectId;
|
||||
const uiState = new PersistedState(uiStateParams);
|
||||
|
||||
const response = await metricsRequestHandler({
|
||||
|
@ -76,6 +83,7 @@ export const createMetricsFn = (): ExpressionFunction<typeof name, Context, Argu
|
|||
filters: get(context, 'filters', null),
|
||||
visParams: params,
|
||||
uiState,
|
||||
savedObjectId,
|
||||
});
|
||||
|
||||
response.visType = 'metrics';
|
||||
|
|
|
@ -23,7 +23,14 @@ import { timefilter } from 'ui/timefilter';
|
|||
import { kfetch } from 'ui/kfetch';
|
||||
import { getUISettings } from './services';
|
||||
|
||||
export const metricsRequestHandler = async ({ uiState, timeRange, filters, query, visParams }) => {
|
||||
export const metricsRequestHandler = async ({
|
||||
uiState,
|
||||
timeRange,
|
||||
filters,
|
||||
query,
|
||||
visParams,
|
||||
savedObjectId,
|
||||
}) => {
|
||||
const config = getUISettings();
|
||||
const timezone = timezoneProvider(config)();
|
||||
const uiStateObj = uiState.get(visParams.type, {});
|
||||
|
@ -49,6 +56,7 @@ export const metricsRequestHandler = async ({ uiState, timeRange, filters, query
|
|||
filters,
|
||||
panels: [visParams],
|
||||
state: uiStateObj,
|
||||
savedObjectId: savedObjectId || 'unsaved',
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -26,12 +26,17 @@ import { SearchStrategiesRegister } from './lib/search_strategies/search_strateg
|
|||
// @ts-ignore
|
||||
import { getVisData } from './lib/get_vis_data';
|
||||
import { Framework } from '../../../../plugins/vis_type_timeseries/server';
|
||||
import { ValidationTelemetryServiceSetup } from '../../../../plugins/vis_type_timeseries/server';
|
||||
|
||||
export const init = async (framework: Framework, __LEGACY: any) => {
|
||||
export const init = async (
|
||||
framework: Framework,
|
||||
__LEGACY: any,
|
||||
validationTelemetry: ValidationTelemetryServiceSetup
|
||||
) => {
|
||||
const { core } = framework;
|
||||
const router = core.http.createRouter();
|
||||
|
||||
visDataRoutes(router, framework);
|
||||
visDataRoutes(router, framework, validationTelemetry);
|
||||
|
||||
// [LEGACY_TODO]
|
||||
fieldsRoutes(__LEGACY.server);
|
||||
|
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* 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 Joi from 'joi';
|
||||
const stringOptionalNullable = Joi.string()
|
||||
.allow('', null)
|
||||
.optional();
|
||||
const stringRequired = Joi.string()
|
||||
.allow('')
|
||||
.required();
|
||||
const arrayNullable = Joi.array().allow(null);
|
||||
const numberIntegerOptional = Joi.number()
|
||||
.integer()
|
||||
.optional();
|
||||
const numberIntegerRequired = Joi.number()
|
||||
.integer()
|
||||
.required();
|
||||
const numberOptional = Joi.number().optional();
|
||||
const numberRequired = Joi.number().required();
|
||||
const queryObject = Joi.object({
|
||||
language: Joi.string().allow(''),
|
||||
query: Joi.string().allow(''),
|
||||
});
|
||||
|
||||
const annotationsItems = Joi.object({
|
||||
color: stringOptionalNullable,
|
||||
fields: stringOptionalNullable,
|
||||
hidden: Joi.boolean().optional(),
|
||||
icon: stringOptionalNullable,
|
||||
id: stringOptionalNullable,
|
||||
ignore_global_filters: numberIntegerOptional,
|
||||
ignore_panel_filters: numberIntegerOptional,
|
||||
index_pattern: stringOptionalNullable,
|
||||
query_string: queryObject.optional(),
|
||||
template: stringOptionalNullable,
|
||||
time_field: stringOptionalNullable,
|
||||
});
|
||||
|
||||
const backgroundColorRulesItems = Joi.object({
|
||||
value: Joi.number()
|
||||
.allow(null)
|
||||
.optional(),
|
||||
id: stringOptionalNullable,
|
||||
background_color: stringOptionalNullable,
|
||||
color: stringOptionalNullable,
|
||||
});
|
||||
|
||||
const gaugeColorRulesItems = Joi.object({
|
||||
gauge: stringOptionalNullable,
|
||||
id: stringOptionalNullable,
|
||||
operator: stringOptionalNullable,
|
||||
value: Joi.number(),
|
||||
});
|
||||
const metricsItems = Joi.object({
|
||||
field: stringOptionalNullable,
|
||||
id: stringRequired,
|
||||
metric_agg: stringOptionalNullable,
|
||||
numerator: stringOptionalNullable,
|
||||
denominator: stringOptionalNullable,
|
||||
sigma: stringOptionalNullable,
|
||||
function: stringOptionalNullable,
|
||||
script: stringOptionalNullable,
|
||||
variables: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
field: stringOptionalNullable,
|
||||
id: stringRequired,
|
||||
name: stringOptionalNullable,
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
type: stringRequired,
|
||||
value: stringOptionalNullable,
|
||||
values: Joi.array()
|
||||
.items(Joi.string().allow('', null))
|
||||
.allow(null)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const splitFiltersItems = Joi.object({
|
||||
id: stringOptionalNullable,
|
||||
color: stringOptionalNullable,
|
||||
filter: Joi.object({
|
||||
language: Joi.string().allow(''),
|
||||
query: Joi.string().allow(''),
|
||||
}).optional(),
|
||||
label: stringOptionalNullable,
|
||||
});
|
||||
|
||||
const seriesItems = Joi.object({
|
||||
aggregate_by: stringOptionalNullable,
|
||||
aggregate_function: stringOptionalNullable,
|
||||
axis_position: stringRequired,
|
||||
axis_max: stringOptionalNullable,
|
||||
axis_min: stringOptionalNullable,
|
||||
chart_type: stringRequired,
|
||||
color: stringRequired,
|
||||
color_rules: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
value: numberOptional,
|
||||
id: stringRequired,
|
||||
text: stringOptionalNullable,
|
||||
operator: stringOptionalNullable,
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
fill: numberOptional,
|
||||
filter: Joi.object({
|
||||
query: stringRequired,
|
||||
language: stringOptionalNullable,
|
||||
}).optional(),
|
||||
formatter: stringRequired,
|
||||
hide_in_legend: numberIntegerOptional,
|
||||
hidden: Joi.boolean().optional(),
|
||||
id: stringRequired,
|
||||
label: stringOptionalNullable,
|
||||
line_width: numberOptional,
|
||||
metrics: Joi.array().items(metricsItems),
|
||||
offset_time: stringOptionalNullable,
|
||||
override_index_pattern: numberOptional,
|
||||
point_size: numberRequired,
|
||||
separate_axis: numberIntegerOptional,
|
||||
seperate_axis: numberIntegerOptional,
|
||||
series_index_pattern: stringOptionalNullable,
|
||||
series_time_field: stringOptionalNullable,
|
||||
series_interval: stringOptionalNullable,
|
||||
series_drop_last_bucket: numberIntegerOptional,
|
||||
split_color_mode: stringOptionalNullable,
|
||||
split_filters: Joi.array()
|
||||
.items(splitFiltersItems)
|
||||
.optional(),
|
||||
split_mode: stringRequired,
|
||||
stacked: stringRequired,
|
||||
steps: numberIntegerOptional,
|
||||
terms_field: stringOptionalNullable,
|
||||
terms_order_by: stringOptionalNullable,
|
||||
terms_size: stringOptionalNullable,
|
||||
terms_direction: stringOptionalNullable,
|
||||
terms_include: stringOptionalNullable,
|
||||
terms_exclude: stringOptionalNullable,
|
||||
time_range_mode: stringOptionalNullable,
|
||||
trend_arrows: numberOptional,
|
||||
type: stringOptionalNullable,
|
||||
value_template: stringOptionalNullable,
|
||||
var_name: stringOptionalNullable,
|
||||
});
|
||||
|
||||
export const visPayloadSchema = Joi.object({
|
||||
filters: arrayNullable,
|
||||
panels: Joi.array().items(
|
||||
Joi.object({
|
||||
annotations: Joi.array()
|
||||
.items(annotationsItems)
|
||||
.optional(),
|
||||
axis_formatter: stringRequired,
|
||||
axis_position: stringRequired,
|
||||
axis_scale: stringRequired,
|
||||
axis_min: stringOptionalNullable,
|
||||
axis_max: stringOptionalNullable,
|
||||
bar_color_rules: arrayNullable.optional(),
|
||||
background_color: stringOptionalNullable,
|
||||
background_color_rules: Joi.array()
|
||||
.items(backgroundColorRulesItems)
|
||||
.optional(),
|
||||
default_index_pattern: stringOptionalNullable,
|
||||
default_timefield: stringOptionalNullable,
|
||||
drilldown_url: stringOptionalNullable,
|
||||
drop_last_bucket: numberIntegerOptional,
|
||||
filter: Joi.alternatives(
|
||||
stringOptionalNullable,
|
||||
Joi.object({
|
||||
language: stringOptionalNullable,
|
||||
query: stringOptionalNullable,
|
||||
})
|
||||
),
|
||||
gauge_color_rules: Joi.array()
|
||||
.items(gaugeColorRulesItems)
|
||||
.optional(),
|
||||
gauge_width: [stringOptionalNullable, numberOptional],
|
||||
gauge_inner_color: stringOptionalNullable,
|
||||
gauge_inner_width: Joi.alternatives(stringOptionalNullable, numberIntegerOptional),
|
||||
gauge_style: stringOptionalNullable,
|
||||
gauge_max: stringOptionalNullable,
|
||||
id: stringRequired,
|
||||
ignore_global_filters: numberOptional,
|
||||
ignore_global_filter: numberOptional,
|
||||
index_pattern: stringRequired,
|
||||
interval: stringRequired,
|
||||
isModelInvalid: Joi.boolean().optional(),
|
||||
legend_position: stringOptionalNullable,
|
||||
markdown: stringOptionalNullable,
|
||||
markdown_scrollbars: numberIntegerOptional,
|
||||
markdown_openLinksInNewTab: numberIntegerOptional,
|
||||
markdown_vertical_align: stringOptionalNullable,
|
||||
markdown_less: stringOptionalNullable,
|
||||
markdown_css: stringOptionalNullable,
|
||||
pivot_id: stringOptionalNullable,
|
||||
pivot_label: stringOptionalNullable,
|
||||
pivot_type: stringOptionalNullable,
|
||||
pivot_rows: stringOptionalNullable,
|
||||
series: Joi.array()
|
||||
.items(seriesItems)
|
||||
.required(),
|
||||
show_grid: numberIntegerRequired,
|
||||
show_legend: numberIntegerRequired,
|
||||
time_field: stringOptionalNullable,
|
||||
time_range_mode: stringOptionalNullable,
|
||||
type: stringRequired,
|
||||
})
|
||||
),
|
||||
// general
|
||||
query: Joi.array()
|
||||
.items(queryObject)
|
||||
.allow(null)
|
||||
.required(),
|
||||
state: Joi.object({
|
||||
sort: Joi.object({
|
||||
column: stringRequired,
|
||||
order: Joi.string()
|
||||
.valid(['asc', 'desc'])
|
||||
.required(),
|
||||
}).optional(),
|
||||
}).required(),
|
||||
savedObjectId: Joi.string().optional(),
|
||||
timerange: Joi.object({
|
||||
timezone: stringRequired,
|
||||
min: stringRequired,
|
||||
max: stringRequired,
|
||||
}).required(),
|
||||
});
|
|
@ -17,12 +17,22 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IRouter } from 'kibana/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { getVisData } from '../lib/get_vis_data';
|
||||
import { visPayloadSchema } from './post_vis_schema';
|
||||
import {
|
||||
Framework,
|
||||
ValidationTelemetryServiceSetup,
|
||||
} from '../../../../../plugins/vis_type_timeseries/server';
|
||||
|
||||
const escapeHatch = schema.object({}, { allowUnknowns: true });
|
||||
|
||||
export const visDataRoutes = (router, framework) => {
|
||||
export const visDataRoutes = (
|
||||
router: IRouter,
|
||||
framework: Framework,
|
||||
{ logFailedValidation }: ValidationTelemetryServiceSetup
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: '/api/metrics/vis/data',
|
||||
|
@ -31,6 +41,16 @@ export const visDataRoutes = (router, framework) => {
|
|||
},
|
||||
},
|
||||
async (requestContext, request, response) => {
|
||||
const { error: validationError } = visPayloadSchema.validate(request.body);
|
||||
if (validationError) {
|
||||
logFailedValidation();
|
||||
const savedObjectId =
|
||||
(typeof request.body === 'object' && (request.body as any).savedObjectId) ||
|
||||
'unavailable';
|
||||
framework.logger.warn(
|
||||
`Request validation error: ${validationError.message} (saved object id: ${savedObjectId}). This most likely means your TSVB visualization contains outdated configuration. You can report this problem under https://github.com/elastic/kibana/issues/new?template=Bug_report.md`
|
||||
);
|
||||
}
|
||||
try {
|
||||
const results = await getVisData(requestContext, request.body, framework);
|
||||
return response.ok({ body: results });
|
|
@ -59,7 +59,12 @@ export interface Schemas {
|
|||
[key: string]: any[] | undefined;
|
||||
}
|
||||
|
||||
type buildVisFunction = (visState: VisState, schemas: Schemas, uiState: any) => string;
|
||||
type buildVisFunction = (
|
||||
visState: VisState,
|
||||
schemas: Schemas,
|
||||
uiState: any,
|
||||
meta?: { savedObjectId?: string }
|
||||
) => string;
|
||||
type buildVisConfigFunction = (schemas: Schemas, visParams?: VisParams) => VisParams;
|
||||
|
||||
interface BuildPipelineVisFunction {
|
||||
|
@ -248,11 +253,13 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
|
|||
input_control_vis: visState => {
|
||||
return `input_control_vis ${prepareJson('visConfig', visState.params)}`;
|
||||
},
|
||||
metrics: (visState, schemas, uiState = {}) => {
|
||||
metrics: (visState, schemas, uiState = {}, meta) => {
|
||||
const paramsJson = prepareJson('params', visState.params);
|
||||
const uiStateJson = prepareJson('uiState', uiState);
|
||||
const savedObjectIdParam = prepareString('savedObjectId', meta?.savedObjectId);
|
||||
|
||||
return `tsvb ${paramsJson} ${uiStateJson}`;
|
||||
const params = [paramsJson, uiStateJson, savedObjectIdParam].filter(param => Boolean(param));
|
||||
return `tsvb ${params.join(' ')}`;
|
||||
},
|
||||
timelion: visState => {
|
||||
const expression = prepareString('expression', visState.params.expression);
|
||||
|
@ -488,6 +495,7 @@ export const buildPipeline = async (
|
|||
params: {
|
||||
searchSource: ISearchSource;
|
||||
timeRange?: any;
|
||||
savedObjectId?: string;
|
||||
}
|
||||
) => {
|
||||
const { searchSource } = params;
|
||||
|
@ -521,7 +529,9 @@ export const buildPipeline = async (
|
|||
|
||||
const schemas = getSchemas(vis, params.timeRange);
|
||||
if (buildPipelineVisFunction[vis.type.name]) {
|
||||
pipeline += buildPipelineVisFunction[vis.type.name](visState, schemas, uiState);
|
||||
pipeline += buildPipelineVisFunction[vis.type.name](visState, schemas, uiState, {
|
||||
savedObjectId: params.savedObjectId,
|
||||
});
|
||||
} else if (vislibCharts.includes(vis.type.name)) {
|
||||
const visConfig = visState.params;
|
||||
visConfig.dimensions = await buildVislibDimensions(vis, params);
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
"id": "metrics",
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"server": true
|
||||
}
|
||||
"server": true,
|
||||
"optionalPlugins": ["usageCollection"]
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ export const config = {
|
|||
|
||||
export type VisTypeTimeseriesConfig = TypeOf<typeof config.schema>;
|
||||
|
||||
export { ValidationTelemetryServiceSetup } from './validation_telemetry';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new VisTypeTimeseriesPlugin(initializerContext);
|
||||
}
|
||||
|
|
|
@ -35,11 +35,17 @@ import {
|
|||
GetVisData,
|
||||
GetVisDataOptions,
|
||||
} from '../../../legacy/core_plugins/vis_type_timeseries/server';
|
||||
import { ValidationTelemetryService } from './validation_telemetry/validation_telemetry_service';
|
||||
import { UsageCollectionSetup } from '../../usage_collection/server';
|
||||
|
||||
export interface LegacySetup {
|
||||
server: Server;
|
||||
}
|
||||
|
||||
interface VisTypeTimeseriesPluginSetupDependencies {
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
}
|
||||
|
||||
export interface VisTypeTimeseriesSetup {
|
||||
/** @deprecated */
|
||||
__legacy: {
|
||||
|
@ -61,11 +67,14 @@ export interface Framework {
|
|||
}
|
||||
|
||||
export class VisTypeTimeseriesPlugin implements Plugin<VisTypeTimeseriesSetup> {
|
||||
private validationTelementryService: ValidationTelemetryService;
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
this.initializerContext = initializerContext;
|
||||
this.validationTelementryService = new ValidationTelemetryService();
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup, plugins: any) {
|
||||
public setup(core: CoreSetup, plugins: VisTypeTimeseriesPluginSetupDependencies) {
|
||||
const logger = this.initializerContext.logger.get('visTypeTimeseries');
|
||||
const config$ = this.initializerContext.config.create<VisTypeTimeseriesConfig>();
|
||||
// Global config contains things like the ES shard timeout
|
||||
|
@ -82,8 +91,13 @@ export class VisTypeTimeseriesPlugin implements Plugin<VisTypeTimeseriesSetup> {
|
|||
return {
|
||||
__legacy: {
|
||||
config$,
|
||||
registerLegacyAPI: once((__LEGACY: LegacySetup) => {
|
||||
init(framework, __LEGACY);
|
||||
registerLegacyAPI: once(async (__LEGACY: LegacySetup) => {
|
||||
const validationTelemetrySetup = await this.validationTelementryService.setup(core, {
|
||||
...plugins,
|
||||
globalConfig$,
|
||||
});
|
||||
|
||||
await init(framework, __LEGACY, validationTelemetrySetup);
|
||||
}),
|
||||
},
|
||||
getVisData: async (requestContext: RequestHandlerContext, options: GetVisDataOptions) => {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export * from './validation_telemetry_service';
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { APICaller, CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server';
|
||||
import { UsageCollectionSetup } from '../../../usage_collection/server';
|
||||
|
||||
export interface ValidationTelemetryServiceSetup {
|
||||
logFailedValidation: () => void;
|
||||
}
|
||||
|
||||
export class ValidationTelemetryService implements Plugin<ValidationTelemetryServiceSetup> {
|
||||
private kibanaIndex: string = '';
|
||||
async setup(
|
||||
core: CoreSetup,
|
||||
{
|
||||
usageCollection,
|
||||
globalConfig$,
|
||||
}: {
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
globalConfig$: PluginInitializerContext['config']['legacy']['globalConfig$'];
|
||||
}
|
||||
) {
|
||||
globalConfig$.subscribe(config => {
|
||||
this.kibanaIndex = config.kibana.index;
|
||||
});
|
||||
if (usageCollection) {
|
||||
usageCollection.registerCollector(
|
||||
usageCollection.makeUsageCollector({
|
||||
type: 'tsvb-validation',
|
||||
isReady: () => this.kibanaIndex !== '',
|
||||
fetch: async (callCluster: APICaller) => {
|
||||
try {
|
||||
const response = await callCluster('get', {
|
||||
index: this.kibanaIndex,
|
||||
id: 'tsvb-validation-telemetry:tsvb-validation-telemetry',
|
||||
ignore: [404],
|
||||
});
|
||||
return {
|
||||
failed_validations:
|
||||
response?._source?.['tsvb-validation-telemetry']?.failedRequests || 0,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
failed_validations: 0,
|
||||
};
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
const internalRepository = core.savedObjects.createInternalRepository();
|
||||
|
||||
return {
|
||||
logFailedValidation: async () => {
|
||||
try {
|
||||
await internalRepository.incrementCounter(
|
||||
'tsvb-validation-telemetry',
|
||||
'tsvb-validation-telemetry',
|
||||
'failedRequests'
|
||||
);
|
||||
} catch (e) {
|
||||
// swallow error, validation telemetry shouldn't fail anything else
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
start() {}
|
||||
}
|
Loading…
Reference in a new issue