diff --git a/packages/kbn-test/types/ftr.d.ts b/packages/kbn-test/types/ftr.d.ts index 8289877ed1b98..e917ed63ca5d3 100644 --- a/packages/kbn-test/types/ftr.d.ts +++ b/packages/kbn-test/types/ftr.d.ts @@ -20,6 +20,8 @@ import { ToolingLog } from '@kbn/dev-utils'; import { Config, Lifecycle } from '../src/functional_test_runner/lib'; +export { Lifecycle, Config }; + interface AsyncInstance { /** * Services that are initialized async are not ready before the tests execute, so you might need @@ -36,14 +38,17 @@ interface AsyncInstance { */ type MaybeAsyncInstance = T extends Promise ? AsyncInstance & X : T; +/** + * Covert a Provider type to the instance type it provides + */ +export type ProvidedType any> = MaybeAsyncInstance>; + /** * Convert a map of providers to a map of the instance types they provide, also converting * promise types into the async instances that other providers will receive. */ type ProvidedTypeMap = { - [K in keyof T]: T[K] extends (...args: any[]) => any - ? MaybeAsyncInstance> - : unknown; + [K in keyof T]: T[K] extends (...args: any[]) => any ? ProvidedType : unknown; }; export interface GenericFtrProviderContext< diff --git a/test/common/services/index.ts b/test/common/services/index.ts index 60b6a5141b918..0dbd2d0262cf4 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -19,7 +19,6 @@ import { EsProvider } from './es'; import { EsArchiverProvider } from './es_archiver'; -// @ts-ignore not TS yet import { KibanaServerProvider } from './kibana_server'; import { RetryProvider } from './retry'; diff --git a/test/common/services/kibana_server/index.js b/test/common/services/kibana_server/index.ts similarity index 98% rename from test/common/services/kibana_server/index.js rename to test/common/services/kibana_server/index.ts index 6d9035008c6b7..edf6cd6522284 100644 --- a/test/common/services/kibana_server/index.js +++ b/test/common/services/kibana_server/index.ts @@ -18,4 +18,5 @@ */ export { KibanaServerProvider } from './kibana_server'; +// @ts-ignore export { extendEsArchiver } from './extend_es_archiver'; diff --git a/test/common/services/kibana_server/kibana_server.js b/test/common/services/kibana_server/kibana_server.ts similarity index 56% rename from test/common/services/kibana_server/kibana_server.js rename to test/common/services/kibana_server/kibana_server.ts index 2d4ae030b153d..1455eb8ed8aa4 100644 --- a/test/common/services/kibana_server/kibana_server.js +++ b/test/common/services/kibana_server/kibana_server.ts @@ -17,23 +17,37 @@ * under the License. */ -import { format as formatUrl } from 'url'; +import Url from 'url'; +import { FtrProviderContext } from '../../ftr_provider_context'; +// @ts-ignore not ts yet import { KibanaServerStatus } from './status'; +// @ts-ignore not ts yet import { KibanaServerUiSettings } from './ui_settings'; +// @ts-ignore not ts yet import { KibanaServerVersion } from './version'; +import { KibanaServerSavedObjects } from './saved_objects'; -export function KibanaServerProvider({ getService }) { +export function KibanaServerProvider({ getService }: FtrProviderContext) { const log = getService('log'); const config = getService('config'); const lifecycle = getService('lifecycle'); - return new class KibanaServer { - constructor() { - const url = formatUrl(config.get('servers.kibana')); - this.status = new KibanaServerStatus(url); - this.version = new KibanaServerVersion(this.status); - this.uiSettings = new KibanaServerUiSettings(url, log, config.get('uiSettings.defaults'), lifecycle); + const url = Url.format(config.get('servers.kibana')); + + return new (class KibanaServer { + public readonly status = new KibanaServerStatus(url); + public readonly version = new KibanaServerVersion(this.status); + public readonly savedObjects = new KibanaServerSavedObjects(url, log); + public readonly uiSettings = new KibanaServerUiSettings( + url, + log, + config.get('uiSettings.defaults'), + lifecycle + ); + + public resolveUrl(path = '/') { + return Url.resolve(url, path); } - }; + })(); } diff --git a/test/common/services/kibana_server/saved_objects.ts b/test/common/services/kibana_server/saved_objects.ts new file mode 100644 index 0000000000000..0e4a9a34bf2e4 --- /dev/null +++ b/test/common/services/kibana_server/saved_objects.ts @@ -0,0 +1,153 @@ +/* + * 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 Url from 'url'; + +import Axios, { AxiosRequestConfig } from 'axios'; +import { ToolingLog } from '@kbn/dev-utils'; + +const joinPath = (...components: Array) => + `/${components + .filter((s): s is string => !!s) + .map(c => encodeURIComponent(c)) + .join('/')}`; + +type MigrationVersion = Record; + +interface Reference { + id: string; + name: string; + type: string; +} + +interface SavedObjectResponse> { + attributes: Attributes; + id: string; + migrationVersion?: MigrationVersion; + references: Reference[]; + type: string; + updated_at?: string; + version?: string; +} + +interface GetOptions { + type: string; + id: string; +} + +interface IndexOptions { + type: string; + attributes: Attributes; + id?: string; + overwrite?: boolean; + migrationVersion?: MigrationVersion; + references?: Reference[]; +} + +interface UpdateOptions extends IndexOptions { + id: string; +} + +export class KibanaServerSavedObjects { + private readonly x = Axios.create({ + baseURL: Url.resolve(this.url, '/api/saved_objects/'), + headers: { + 'kbn-xsrf': 'KibanaServerSavedObjects', + }, + }); + + constructor(private readonly url: string, private readonly log: ToolingLog) {} + + /** + * Get an object + */ + public async get>(options: GetOptions) { + this.log.debug('Gettings saved object: %j', options); + + return await this.request>('get saved object', { + url: joinPath(options.type, options.id), + method: 'GET', + }); + } + + /** + * Create a saved object + */ + public async create>(options: IndexOptions) { + this.log.debug('Creating saved object: %j', options); + + return await this.request>('update saved object', { + url: joinPath(options.type, options.id), + params: { + overwrite: options.overwrite, + }, + method: 'POST', + data: { + attributes: options.attributes, + migrationVersion: options.migrationVersion, + references: options.references, + }, + }); + } + + /** + * Update a saved object + */ + public async update>(options: UpdateOptions) { + this.log.debug('Updating saved object: %j', options); + + return await this.request>('update saved object', { + url: joinPath(options.type, options.id), + params: { + overwrite: options.overwrite, + }, + method: 'PUT', + data: { + attributes: options.attributes, + migrationVersion: options.migrationVersion, + references: options.references, + }, + }); + } + + /** + * Delete an object + */ + public async delete(options: GetOptions) { + this.log.debug('Deleting saved object %s/%s', options); + + return await this.request('delete saved object', { + url: joinPath(options.type, options.id), + method: 'DELETE', + }); + } + + private async request(desc: string, options: AxiosRequestConfig) { + try { + const resp = await this.x.request(options); + return resp.data; + } catch (error) { + if (error.response) { + throw new Error(`Failed to ${desc}:\n${JSON.stringify(error.response.data, null, 2)}`); + } + + throw error; + } + } +}