Migrate kql telemetry and scripts api (#52651) (#53717)

This commit is contained in:
Joe Reuter 2019-12-20 22:27:04 +01:00 committed by GitHub
parent 69ccb62b83
commit 66222f088c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 226 additions and 89 deletions

View file

@ -9,5 +9,5 @@ returns `basePath` value, specific for an incoming request.
<b>Signature:</b>
```typescript
get: (request: LegacyRequest | KibanaRequest<unknown, unknown, unknown, any>) => string;
get: (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest) => string;
```

View file

@ -16,11 +16,11 @@ export declare class BasePath
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [get](./kibana-plugin-server.basepath.get.md) | | <code>(request: LegacyRequest &#124; KibanaRequest&lt;unknown, unknown, unknown, any&gt;) =&gt; string</code> | returns <code>basePath</code> value, specific for an incoming request. |
| [get](./kibana-plugin-server.basepath.get.md) | | <code>(request: KibanaRequest&lt;unknown, unknown, unknown, any&gt; &#124; LegacyRequest) =&gt; string</code> | returns <code>basePath</code> value, specific for an incoming request. |
| [prepend](./kibana-plugin-server.basepath.prepend.md) | | <code>(path: string) =&gt; string</code> | Prepends <code>path</code> with the basePath. |
| [remove](./kibana-plugin-server.basepath.remove.md) | | <code>(path: string) =&gt; string</code> | Removes the prepended basePath from the <code>path</code>. |
| [serverBasePath](./kibana-plugin-server.basepath.serverbasepath.md) | | <code>string</code> | returns the server's basePath<!-- -->See [BasePath.get](./kibana-plugin-server.basepath.get.md) for getting the basePath value for a specific request |
| [set](./kibana-plugin-server.basepath.set.md) | | <code>(request: LegacyRequest &#124; KibanaRequest&lt;unknown, unknown, unknown, any&gt;, requestSpecificBasePath: string) =&gt; void</code> | sets <code>basePath</code> value, specific for an incoming request. |
| [set](./kibana-plugin-server.basepath.set.md) | | <code>(request: KibanaRequest&lt;unknown, unknown, unknown, any&gt; &#124; LegacyRequest, requestSpecificBasePath: string) =&gt; void</code> | sets <code>basePath</code> value, specific for an incoming request. |
## Remarks

View file

@ -9,5 +9,5 @@ sets `basePath` value, specific for an incoming request.
<b>Signature:</b>
```typescript
set: (request: LegacyRequest | KibanaRequest<unknown, unknown, unknown, any>, requestSpecificBasePath: string) => void;
set: (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest, requestSpecificBasePath: string) => void;
```

View file

@ -449,11 +449,11 @@ export interface AuthToolkit {
export class BasePath {
// @internal
constructor(serverBasePath?: string);
get: (request: LegacyRequest | KibanaRequest<unknown, unknown, unknown, any>) => string;
get: (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest) => string;
prepend: (path: string) => string;
remove: (path: string) => string;
readonly serverBasePath: string;
set: (request: LegacyRequest | KibanaRequest<unknown, unknown, unknown, any>, requestSpecificBasePath: string) => void;
set: (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest, requestSpecificBasePath: string) => void;
}
// Warning: (ae-forgotten-export) The symbol "BootstrapArgs" needs to be exported by the entry point index.d.ts

View file

@ -26,14 +26,11 @@ import { importApi } from './server/routes/api/import';
import { exportApi } from './server/routes/api/export';
import { homeApi } from './server/routes/api/home';
import { managementApi } from './server/routes/api/management';
import { scriptsApi } from './server/routes/api/scripts';
import { registerKqlTelemetryApi } from './server/routes/api/kql_telemetry';
import { registerFieldFormats } from './server/field_formats/register';
import { registerTutorials } from './server/tutorials/register';
import * as systemApi from './server/lib/system_api';
import mappings from './mappings.json';
import { getUiSettingDefaults } from './ui_setting_defaults';
import { makeKQLUsageCollector } from './server/lib/kql_usage_collector';
import { registerCspCollector } from './server/lib/csp_usage_collector';
import { injectVars } from './inject_vars';
import { i18n } from '@kbn/i18n';
@ -325,15 +322,12 @@ export default function(kibana) {
init: async function(server) {
const { usageCollection } = server.newPlatform.setup.plugins;
// routes
scriptsApi(server);
importApi(server);
exportApi(server);
homeApi(server);
managementApi(server);
registerKqlTelemetryApi(server);
registerFieldFormats(server);
registerTutorials(server);
makeKQLUsageCollector(usageCollection, server);
registerCspCollector(usageCollection, server);
server.expose('systemApi', systemApi);
server.injectUiAppVars('kibana', () => injectVars(server));

View file

@ -20,6 +20,7 @@
import moment from 'moment-timezone';
import numeralLanguages from '@elastic/numeral/languages';
import { i18n } from '@kbn/i18n';
import { DEFAULT_QUERY_LANGUAGE } from '../../../plugins/data/common';
export function getUiSettingDefaults() {
const weekdays = moment.weekdays().slice();
@ -121,7 +122,7 @@ export function getUiSettingDefaults() {
},
'search:queryLanguage': {
name: queryLanguageSettingName,
value: 'kuery',
value: DEFAULT_QUERY_LANGUAGE,
description: i18n.translate('kbn.advancedSettings.searchQueryLanguageText', {
defaultMessage:
'Query language used by the query bar. KQL is a new language built specifically for Kibana.',

View file

@ -17,8 +17,4 @@
* under the License.
*/
import { registerLanguages } from './register_languages';
export function scriptsApi(server) {
registerLanguages(server);
}
export const DEFAULT_QUERY_LANGUAGE = 'kuery';

View file

@ -24,3 +24,4 @@ export * from './index_patterns';
export * from './es_query';
export * from './utils';
export * from './types';
export * from './constants';

View file

@ -3,5 +3,6 @@
"version": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["uiActions"]
"requiredPlugins": ["uiActions"],
"optionalPlugins": ["usageCollection"]
}

View file

@ -17,12 +17,4 @@
* under the License.
*/
export function registerLanguages(server) {
server.route({
path: '/api/kibana/scripts/languages',
method: 'GET',
handler: function() {
return ['painless', 'expression'];
},
});
}
export { KqlTelemetryService } from './kql_telemetry_service';

View file

@ -0,0 +1,53 @@
/*
* 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 { first } from 'rxjs/operators';
import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server';
import { registerKqlTelemetryRoute } from './route';
import { UsageCollectionSetup } from '../../../usage_collection/server';
import { makeKQLUsageCollector } from './usage_collector';
export class KqlTelemetryService implements Plugin<void> {
constructor(private initializerContext: PluginInitializerContext) {}
public setup(
{ http, savedObjects }: CoreSetup,
{ usageCollection }: { usageCollection?: UsageCollectionSetup }
) {
registerKqlTelemetryRoute(
http.createRouter(),
savedObjects,
this.initializerContext.logger.get('data', 'kql-telemetry')
);
if (usageCollection) {
this.initializerContext.config.legacy.globalConfig$
.pipe(first())
.toPromise()
.then(config => makeKQLUsageCollector(usageCollection, config.kibana.index))
.catch(e => {
this.initializerContext.logger
.get('kql-telemetry')
.warn(`Registering KQL telemetry collector failed: ${e}`);
});
}
}
public start() {}
}

View file

@ -17,30 +17,28 @@
* under the License.
*/
import Joi from 'joi';
import Boom from 'boom';
import { CoreSetup, IRouter, Logger } from 'kibana/server';
import { schema } from '@kbn/config-schema';
export function registerKqlTelemetryApi(server) {
server.route({
path: '/api/kibana/kql_opt_in_telemetry',
method: 'POST',
config: {
export function registerKqlTelemetryRoute(
router: IRouter,
savedObjects: CoreSetup['savedObjects'],
logger: Logger
) {
router.post(
{
path: '/api/kibana/kql_opt_in_telemetry',
validate: {
payload: Joi.object({
opt_in: Joi.bool().required(),
body: schema.object({
opt_in: schema.boolean(),
}),
},
tags: ['api'],
},
handler: async function(request) {
const {
savedObjects: { getSavedObjectsRepository },
} = server;
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
async (context, request, response) => {
const internalRepository = savedObjects.createScopedRepository(request);
const {
payload: { opt_in: optIn },
body: { opt_in: optIn },
} = request;
const counterName = optIn ? 'optInCount' : 'optOutCount';
@ -48,13 +46,19 @@ export function registerKqlTelemetryApi(server) {
try {
await internalRepository.incrementCounter('kql-telemetry', 'kql-telemetry', counterName);
} catch (error) {
return new Boom('Something went wrong', {
logger.warn(`Unable to increment counter: ${error}`);
return response.customError({
statusCode: error.status,
data: { success: false },
body: {
message: 'Something went wrong',
attributes: {
success: false,
},
},
});
}
return { success: true };
},
});
return response.ok({ body: { success: true } });
}
);
}

View file

@ -17,18 +17,22 @@
* under the License.
*/
jest.mock('../../../ui_setting_defaults', () => ({
getUiSettingDefaults: () => ({ 'search:queryLanguage': { value: 'lucene' } }),
import { fetchProvider } from './fetch';
import { APICaller } from 'kibana/server';
jest.mock('../../../common', () => ({
DEFAULT_QUERY_LANGUAGE: 'lucene',
}));
import { fetchProvider } from './fetch';
let fetch: ReturnType<typeof fetchProvider>;
let callCluster: APICaller;
let fetch;
let callCluster;
function setupMockCallCluster(optCount, language) {
callCluster = jest.fn((method, params) => {
if ('id' in params && params.id === 'kql-telemetry:kql-telemetry') {
function setupMockCallCluster(
optCount: { optInCount?: number; optOutCount?: number } | null,
language: string | undefined | null
) {
callCluster = (jest.fn((method, params) => {
if (params && 'id' in params && params.id === 'kql-telemetry:kql-telemetry') {
if (optCount === null) {
return Promise.resolve({
_index: '.kibana_1',
@ -47,9 +51,9 @@ function setupMockCallCluster(optCount, language) {
},
});
}
} else if ('body' in params && params.body.query.term.type === 'config') {
} else if (params && 'body' in params && params.body.query.term.type === 'config') {
if (language === 'missingConfigDoc') {
Promise.resolve({
return Promise.resolve({
hits: {
hits: [],
},
@ -70,7 +74,9 @@ function setupMockCallCluster(optCount, language) {
});
}
}
});
throw new Error('invalid call');
}) as unknown) as APICaller;
}
describe('makeKQLUsageCollector', () => {

View file

@ -17,14 +17,14 @@
* under the License.
*/
import { getUiSettingDefaults } from '../../../ui_setting_defaults';
import { get } from 'lodash';
import { APICaller } from 'kibana/server';
import { DEFAULT_QUERY_LANGUAGE } from '../../../common';
const uiSettingDefaults = getUiSettingDefaults();
const defaultSearchQueryLanguageSetting = uiSettingDefaults['search:queryLanguage'].value;
const defaultSearchQueryLanguageSetting = DEFAULT_QUERY_LANGUAGE;
export function fetchProvider(index) {
return async callCluster => {
export function fetchProvider(index: string) {
return async (callCluster: APICaller) => {
const [response, config] = await Promise.all([
callCluster('get', {
index,

View file

@ -18,33 +18,26 @@
*/
import { makeKQLUsageCollector } from './make_kql_usage_collector';
import { UsageCollectionSetup } from '../../../../usage_collection/server';
describe('makeKQLUsageCollector', () => {
let server;
let makeUsageCollectorStub;
let registerStub;
let usageCollection;
let usageCollectionMock: jest.Mocked<UsageCollectionSetup>;
beforeEach(() => {
makeUsageCollectorStub = jest.fn();
registerStub = jest.fn();
usageCollection = {
makeUsageCollector: makeUsageCollectorStub,
registerCollector: registerStub,
};
server = {
config: () => ({ get: () => '.kibana' }),
};
usageCollectionMock = ({
makeUsageCollector: jest.fn(),
registerCollector: jest.fn(),
} as unknown) as jest.Mocked<UsageCollectionSetup>;
});
it('should call registerCollector', () => {
makeKQLUsageCollector(usageCollection, server);
expect(registerStub).toHaveBeenCalledTimes(1);
makeKQLUsageCollector(usageCollectionMock, '.kibana');
expect(usageCollectionMock.registerCollector).toHaveBeenCalledTimes(1);
});
it('should call makeUsageCollector with type = kql', () => {
makeKQLUsageCollector(usageCollection, server);
expect(makeUsageCollectorStub).toHaveBeenCalledTimes(1);
expect(makeUsageCollectorStub.mock.calls[0][0].type).toBe('kql');
makeKQLUsageCollector(usageCollectionMock, '.kibana');
expect(usageCollectionMock.makeUsageCollector).toHaveBeenCalledTimes(1);
expect(usageCollectionMock.makeUsageCollector.mock.calls[0][0].type).toBe('kql');
});
});

View file

@ -18,10 +18,13 @@
*/
import { fetchProvider } from './fetch';
import { UsageCollectionSetup } from '../../../../usage_collection/server';
export function makeKQLUsageCollector(usageCollection, server) {
const index = server.config().get('kibana.index');
const fetch = fetchProvider(index);
export async function makeKQLUsageCollector(
usageCollection: UsageCollectionSetup,
kibanaIndex: string
) {
const fetch = fetchProvider(kibanaIndex);
const kqlUsageCollector = usageCollection.makeUsageCollector({
type: 'kql',
fetch,

View file

@ -21,29 +21,42 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../..
import { IndexPatternsService } from './index_patterns';
import { ISearchSetup } from './search';
import { SearchService } from './search/search_service';
import { ScriptsService } from './scripts';
import { KqlTelemetryService } from './kql_telemetry';
import { UsageCollectionSetup } from '../../usage_collection/server';
import { AutocompleteService } from './autocomplete';
export interface DataPluginSetup {
search: ISearchSetup;
}
export interface DataPluginSetupDependencies {
usageCollection?: UsageCollectionSetup;
}
export class DataServerPlugin implements Plugin<DataPluginSetup> {
private readonly searchService: SearchService;
private readonly scriptsService: ScriptsService;
private readonly kqlTelemetryService: KqlTelemetryService;
private readonly autocompleteService = new AutocompleteService();
private readonly indexPatterns = new IndexPatternsService();
constructor(initializerContext: PluginInitializerContext) {
this.searchService = new SearchService(initializerContext);
this.scriptsService = new ScriptsService();
this.kqlTelemetryService = new KqlTelemetryService(initializerContext);
}
public setup(core: CoreSetup) {
public setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies) {
this.indexPatterns.setup(core);
this.scriptsService.setup(core);
this.autocompleteService.setup(core);
this.kqlTelemetryService.setup(core, { usageCollection });
return {
search: this.searchService.setup(core),
};
}
public start(core: CoreStart) {}
public stop() {}
}

View file

@ -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 { ScriptsService } from './scripts_service';

View file

@ -0,0 +1,31 @@
/*
* 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 { IRouter } from 'kibana/server';
export function registerScriptsRoute(router: IRouter) {
router.get(
{ path: '/api/kibana/scripts/languages', validate: false },
async (context, request, response) => {
return response.ok({
body: ['painless', 'expression'],
});
}
);
}

View file

@ -0,0 +1,29 @@
/*
* 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 { CoreSetup, Plugin } from 'kibana/server';
import { registerScriptsRoute } from './route';
export class ScriptsService implements Plugin<void> {
public setup({ http }: CoreSetup) {
registerScriptsRoute(http.createRouter());
}
public start() {}
}